diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json
index cbecc491..f1ae4295 100644
--- a/.config/dotnet-tools.json
+++ b/.config/dotnet-tools.json
@@ -3,7 +3,7 @@
"isRoot": true,
"tools": {
"nbgv": {
- "version": "3.6.133",
+ "version": "3.6.139",
"commands": [
"nbgv"
],
diff --git a/.editorconfig b/.editorconfig
index e7d7b135..f2384a03 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -82,6 +82,8 @@ dotnet_style_readonly_field = true:warning
# Parameter preferences
dotnet_code_quality_unused_parameters = all:suggestion
+# AV1561: Signature contains too many parameters
+dotnet_diagnostic.AV1561.severity = suggestion
# Suppression preferences
dotnet_remove_unnecessary_suppression_exclusions = none
@@ -89,6 +91,8 @@ dotnet_remove_unnecessary_suppression_exclusions = none
# XMLDocs preferences
# SA1600: Elements should be documented. We disable this it requires xmldocs for _all_ members. CS1591 already covers documenting public members.
dotnet_diagnostic.SA1600.severity = silent
+# AV2305: Missing XML comment for internally visible type, member or parameter
+dotnet_diagnostic.AV2305.severity = silent
#### C# Coding Conventions ####
[*.cs]
@@ -238,6 +242,9 @@ dotnet_naming_rule.public_fields_should_be_pascalcase.style = pascalcase
dotnet_naming_rule.private_fields_should_be__camelcase.severity = suggestion
dotnet_naming_rule.private_fields_should_be__camelcase.symbols = private_fields
dotnet_naming_rule.private_fields_should_be__camelcase.style = _camelcase
+# SA1309: Field names must not begin with underscore
+# Keep this aligned with `dotnet_naming_rule.private_fields_should_be__camelcase.style`
+dotnet_diagnostic.SA1309.severity = none
dotnet_naming_rule.private_static_fields_should_be_s_camelcase.severity = suggestion
dotnet_naming_rule.private_static_fields_should_be_s_camelcase.symbols = private_static_fields
@@ -381,7 +388,31 @@ dotnet_naming_style.s_camelcase.required_suffix =
dotnet_naming_style.s_camelcase.word_separator =
dotnet_naming_style.s_camelcase.capitalization = camel_case
+# AV1580: Method argument calls a nested method
+# Because debugger breakpoints cannot be set inside expressions, avoid overuse of nested method calls.
+# Example: string result = ConvertToXml(ApplyTransforms(ExecuteQuery(GetConfigurationSettings(source))));
+# requires extra steps to inspect intermediate method return values. On the other hard, were this expression broken into intermediate variables, setting a breakpoint on one of them would be sufficient.
+#
+# This is moved to silent because it's flagging foo.AsSpan()
+dotnet_diagnostic.AV1580.severity = silent
+
# MA0040: Forward the CancellationToken parameter to methods that take one
dotnet_diagnostic.MA0040.severity = error
# Async analyzer
-dotnet_diagnostic.CA2016.severity = error
\ No newline at end of file
+dotnet_diagnostic.CA2016.severity = error
+
+# AV1555: Avoid using named arguments
+# Disabled because it's common to use a named argument when passing `null` or bool arguments to make the parameter's purpose clear
+dotnet_diagnostic.AV1555.severity = none
+
+#### Handling TODOs ####
+# This is a popular rule in analyzers. Everyone has an opinion and
+# some of the severity levels conflict. We don't need all of these
+# to fire, only one. Pick one and mark it as informational so we
+# don't lose track.
+# S1135: Track uses of "TODO" tags
+dotnet_diagnostic.S1135.severity = suggestion
+# AV2318: Work-tracking TODO comment should be removed
+dotnet_diagnostic.AV2318.severity = none
+# MA0026: Fix TODO comment
+dotnet_diagnostic.MA0026.severity = none
\ No newline at end of file
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 00000000..db25cda6
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,23 @@
+version: 2
+updates:
+ - package-ecosystem: github-actions
+ directory: "/"
+ schedule:
+ interval: "daily"
+ time: "08:00"
+ open-pull-requests-limit: 10
+ - package-ecosystem: nuget
+ directory: "/"
+ schedule:
+ interval: "daily"
+ time: "08:30"
+ ignore:
+ # Microsoft.CodeAnalysis.* packages defined in the analyzer project can impact compatibility with older SDKs for
+ # our users. We don't want to bump these without first considering the user impact.
+ #
+ # We don't wildcard Microsoft.CodeAnalysis.* here though, as there are testing libraries and analyzers that
+ # can be upgraded without impacting our users.
+ - dependency-name: "Microsoft.CodeAnalysis.CSharp"
+ - dependency-name: "Microsoft.CodeAnalysis.CSharp.Workspaces"
+ - dependency-name: "Microsoft.CodeAnalysis.Common"
+ - dependency-name: "Microsoft.CodeAnalysis.Workspaces.Common"
diff --git a/.github/labeler.yml b/.github/labeler.yml
new file mode 100644
index 00000000..c2e29f9b
--- /dev/null
+++ b/.github/labeler.yml
@@ -0,0 +1,36 @@
+# Add 'build' label to any change in the 'build' directory
+build:
+- changed-files:
+ - any-glob-to-any-file: 'build/**/*'
+
+# Add 'dependencies' label to any change in one of the packages files
+dependencies:
+- changed-files:
+ - any-glob-to-any-file: ['build/**/Packages.props', 'Directory.Packages.props']
+
+# Add 'documentation' label to any change within the 'docs' directory or any '.md' file
+documentation:
+- changed-files:
+ - any-glob-to-any-file: ['docs/**', '**/*.md']
+
+# Add 'feature' label to any PR where the head branch name starts with `feature` or has a `feature` section in the name
+feature:
+ - head-branch: ['^feature', 'feature']
+
+# Add 'bug' label to any PR where the head branch name starts with `bug` or has a `bug` section in the name
+bug:
+ - head-branch: ['^bug', 'bug']
+
+# Add 'releasable' label to any PR that is opened against the `main` branch
+releasable:
+ - base-branch: 'main'
+
+# Add 'github_actions' label to any change to one of the GitHub workflows or configuration files
+github_actions:
+- changed-files:
+ - any-glob-to-any-file: ['.github/workflows/*.yml', '.github/dependabot.yml', '.github/labeler.yml']
+
+# Add 'analyzers' label to any change to an analyzer, code fix, or shipping documentation
+analyzers:
+- changed-files:
+ - any-glob-to-any-file: ['src/Moq.Analyzers/AnalyzerReleases.*.md', 'src/Moq.Analyzers/**/*Analyzer.cs', 'src/Moq.Analyzers/**/*CodeFix.cs']
diff --git a/.github/workflows/label-issues.yml b/.github/workflows/label-issues.yml
new file mode 100644
index 00000000..b4eb5292
--- /dev/null
+++ b/.github/workflows/label-issues.yml
@@ -0,0 +1,50 @@
+# This workflow will read information about an issue and attempt to label it initially for triage and sorting
+
+name: Label issues
+on:
+ issues:
+ types:
+ - reopened
+ - opened
+
+jobs:
+ label_issues:
+ name: "Issue: add labels"
+ if: ${{ github.event.action == 'opened' || github.event.action == 'reopened' }}
+ runs-on: ubuntu-latest
+ permissions:
+ issues: write
+ steps:
+ - uses: actions/github-script@v7
+ with:
+ github-token: ${{ secrets.GH_ACTIONS_PR_WRITE }}
+ script: |
+ // Get the issue body and title
+ const body = context.payload.issue.body
+ let title = context.payload.issue.title
+
+ // Define the labels array
+ let labels = ["triage"]
+
+ // Check if the body or the title contains the word 'PowerShell' (case-insensitive)
+ if ((body != null && body.match(/powershell/i)) || (title != null && title.match(/powershell/i))) {
+ // Add the 'powershell' label to the array
+ labels.push("powershell")
+ }
+
+ // Check if the body or the title contains the words 'dotnet', '.net', 'c#' or 'csharp' (case-insensitive)
+ if ((body != null && body.match(/.net/i)) || (title != null && title.match(/.net/i)) ||
+ (body != null && body.match(/dotnet/i)) || (title != null && title.match(/dotnet/i)) ||
+ (body != null && body.match(/C#/i)) || (title != null && title.match(/C#/i)) ||
+ (body != null && body.match(/csharp/i)) || (title != null && title.match(/csharp/i))) {
+ // Add the '.NET' label to the array
+ labels.push(".NET")
+ }
+
+ // Add the labels to the issue
+ github.rest.issues.addLabels({
+ issue_number: context.issue.number,
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ labels: labels
+ });
\ No newline at end of file
diff --git a/.github/workflows/label-pr.yml b/.github/workflows/label-pr.yml
new file mode 100644
index 00000000..a516e8b0
--- /dev/null
+++ b/.github/workflows/label-pr.yml
@@ -0,0 +1,21 @@
+# This workflow will triage pull requests and apply a label based on the
+# paths that are modified in the pull request.
+#
+# To use this workflow, you will need to set up a .github/labeler.yml
+# file with configuration. For more information, see:
+# https://github.com/actions/labeler
+
+name: Label PR
+on: [pull_request_target]
+
+jobs:
+ add_label:
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ pull-requests: write
+
+ steps:
+ - uses: actions/labeler@v5
+ with:
+ repo-token: "${{ secrets.GH_ACTIONS_PR_WRITE }}"
\ No newline at end of file
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 699f7e16..014369a6 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -8,6 +8,10 @@ on:
push:
branches:
- main
+ merge_group:
+ branches:
+ - main
+
workflow_call: # Allow to be called from the release workflow
schedule:
- cron: '31 15 * * 0' # Run periodically to keep CodeQL database updated
@@ -26,6 +30,9 @@ jobs:
runs-on: ${{ matrix.os }}
+ env:
+ IS_COVERAGE_ALLOWED: ${{ secrets.CODACY_PROJECT_TOKEN != '' }}
+
steps:
- uses: actions/checkout@v4
with:
@@ -81,10 +88,17 @@ jobs:
name: .NET Code Coverage Reports (${{ matrix.os }})
path: "artifacts/TestResults/coverage/**"
- - name: Publish coverage summary
+ - name: Publish coverage summary to GitHub
run: cat artifacts/TestResults/coverage/SummaryGithub.md >> $GITHUB_STEP_SUMMARY
shell: bash
+ - name: Upload coverage data to Codacy
+ if: ${{ runner.os == 'Linux' && env.IS_COVERAGE_ALLOWED == 'true' }}
+ uses: codacy/codacy-coverage-reporter-action@v1.3.0
+ with:
+ project-token: ${{ secrets.CODACY_PROJECT_TOKEN }}
+ coverage-reports: ${{ github.workspace }}/artifacts/TestResults/coverage/Cobertura.xml
+
- name: Upload binlogs
uses: actions/upload-artifact@v4
with:
diff --git a/Directory.Packages.props b/Directory.Packages.props
index 672bc235..f474f00b 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -20,7 +20,8 @@
+
-
+
\ No newline at end of file
diff --git a/Moq.Analyzers.sln b/Moq.Analyzers.sln
index 8d30b87d..4442c49b 100644
--- a/Moq.Analyzers.sln
+++ b/Moq.Analyzers.sln
@@ -3,9 +3,11 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.10.34928.147
MinimumVisualStudioVersion = 10.0.40219.1
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Moq.Analyzers", "Source\Moq.Analyzers\Moq.Analyzers.csproj", "{41ECC571-F586-460A-9BED-23528C8210C4}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Moq.Analyzers", "src\Moq.Analyzers\Moq.Analyzers.csproj", "{41ECC571-F586-460A-9BED-23528C8210C4}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Moq.Analyzers.Test", "Source\Moq.Analyzers.Test\Moq.Analyzers.Test.csproj", "{D2348836-7129-4BE5-8AE6-D05FC8C28FC1}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Moq.Analyzers.Test", "tests\Moq.Analyzers.Test\Moq.Analyzers.Test.csproj", "{D2348836-7129-4BE5-8AE6-D05FC8C28FC1}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Moq.Analyzers.Benchmarks", "tests\Moq.Analyzers.Benchmarks\Moq.Analyzers.Benchmarks.csproj", "{11B3412F-456C-452E-94D2-B42D5C52F61C}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -21,6 +23,10 @@ Global
{D2348836-7129-4BE5-8AE6-D05FC8C28FC1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D2348836-7129-4BE5-8AE6-D05FC8C28FC1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D2348836-7129-4BE5-8AE6-D05FC8C28FC1}.Release|Any CPU.Build.0 = Release|Any CPU
+ {11B3412F-456C-452E-94D2-B42D5C52F61C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {11B3412F-456C-452E-94D2-B42D5C52F61C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {11B3412F-456C-452E-94D2-B42D5C52F61C}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {11B3412F-456C-452E-94D2-B42D5C52F61C}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/README.md b/README.md
index 366e898b..3e8782eb 100644
--- a/README.md
+++ b/README.md
@@ -3,6 +3,8 @@
[![NuGet Version](https://img.shields.io/nuget/v/Moq.Analyzers?style=flat&logo=nuget&color=blue)](https://www.nuget.org/packages/Moq.Analyzers)
[![NuGet Downloads](https://img.shields.io/nuget/dt/Moq.Analyzers?style=flat&logo=nuget)](https://www.nuget.org/packages/Moq.Analyzers)
[![Main build](https://github.com/rjmurillo/moq.analyzers/actions/workflows/main.yml/badge.svg)](https://github.com/rjmurillo/moq.analyzers/actions/workflows/main.yml)
+[![Codacy Grade Badge](https://app.codacy.com/project/badge/Grade/fc7c184dcb1843d4b1ae1b926fb82d5a)](https://app.codacy.com/gh/rjmurillo/moq.analyzers/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade)
+[![Codacy Coverage Badge](https://app.codacy.com/project/badge/Coverage/fc7c184dcb1843d4b1ae1b926fb82d5a)](https://app.codacy.com/gh/rjmurillo/moq.analyzers/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_coverage)
**Moq.Analyzers** is a Roslyn analyzer that helps you to write unit tests using the popular
[Moq](https://github.com/devlooped/moq) framework. Moq.Analyzers protects you from common mistakes and warns you if
diff --git a/Source/Moq.Analyzers.Test/PackageTests.cs b/Source/Moq.Analyzers.Test/PackageTests.cs
deleted file mode 100644
index d2347c95..00000000
--- a/Source/Moq.Analyzers.Test/PackageTests.cs
+++ /dev/null
@@ -1,23 +0,0 @@
-using System.Reflection;
-
-namespace Moq.Analyzers.Test;
-
-public class PackageTests
-{
- private static readonly FileInfo Package;
-
- static PackageTests()
- {
- Package = new FileInfo(Assembly.GetExecutingAssembly().Location)
- .Directory!
- .GetFiles("Moq.Analyzers*.nupkg")
- .OrderByDescending(f => f.LastWriteTimeUtc)
- .First();
- }
-
- [Fact]
- public Task Baseline()
- {
- return VerifyFile(Package).ScrubNuspec();
- }
-}
diff --git a/Source/Moq.Analyzers/Helpers.cs b/Source/Moq.Analyzers/Helpers.cs
deleted file mode 100644
index ab0165c1..00000000
--- a/Source/Moq.Analyzers/Helpers.cs
+++ /dev/null
@@ -1,99 +0,0 @@
-using System.Diagnostics;
-
-namespace Moq.Analyzers;
-
-internal static class Helpers
-{
- private static readonly MoqMethodDescriptorBase MoqSetupMethodDescriptor = new MoqSetupMethodDescriptor();
-
- internal static bool IsMoqSetupMethod(SemanticModel semanticModel, MemberAccessExpressionSyntax method, CancellationToken cancellationToken)
- {
- return MoqSetupMethodDescriptor.IsMatch(semanticModel, method, cancellationToken);
- }
-
- internal static bool IsCallbackOrReturnInvocation(SemanticModel semanticModel, InvocationExpressionSyntax callbackOrReturnsInvocation)
- {
- MemberAccessExpressionSyntax? callbackOrReturnsMethod = callbackOrReturnsInvocation.Expression as MemberAccessExpressionSyntax;
-
- Debug.Assert(callbackOrReturnsMethod != null, nameof(callbackOrReturnsMethod) + " != null");
-
- if (callbackOrReturnsMethod == null)
- {
- return false;
- }
-
- string? methodName = callbackOrReturnsMethod.Name.ToString();
-
- // First fast check before walking semantic model
- if (!string.Equals(methodName, "Callback", StringComparison.Ordinal)
- && !string.Equals(methodName, "Returns", StringComparison.Ordinal))
- {
- return false;
- }
-
- SymbolInfo symbolInfo = semanticModel.GetSymbolInfo(callbackOrReturnsMethod);
- return symbolInfo.CandidateReason switch
- {
- CandidateReason.OverloadResolutionFailure => symbolInfo.CandidateSymbols.Any(IsCallbackOrReturnSymbol),
- CandidateReason.None => IsCallbackOrReturnSymbol(symbolInfo.Symbol),
- _ => false,
- };
- }
-
- internal static InvocationExpressionSyntax? FindSetupMethodFromCallbackInvocation(SemanticModel semanticModel, ExpressionSyntax expression, CancellationToken cancellationToken)
- {
- InvocationExpressionSyntax? invocation = expression as InvocationExpressionSyntax;
- if (invocation?.Expression is not MemberAccessExpressionSyntax method) return null;
- if (IsMoqSetupMethod(semanticModel, method, cancellationToken)) return invocation;
- return FindSetupMethodFromCallbackInvocation(semanticModel, method.Expression, cancellationToken);
- }
-
- internal static InvocationExpressionSyntax? FindMockedMethodInvocationFromSetupMethod(InvocationExpressionSyntax? setupInvocation)
- {
- LambdaExpressionSyntax? setupLambdaArgument = setupInvocation?.ArgumentList.Arguments[0].Expression as LambdaExpressionSyntax;
- return setupLambdaArgument?.Body as InvocationExpressionSyntax;
- }
-
- internal static ExpressionSyntax? FindMockedMemberExpressionFromSetupMethod(InvocationExpressionSyntax? setupInvocation)
- {
- LambdaExpressionSyntax? setupLambdaArgument = setupInvocation?.ArgumentList.Arguments[0].Expression as LambdaExpressionSyntax;
- return setupLambdaArgument?.Body as ExpressionSyntax;
- }
-
- internal static IEnumerable GetAllMatchingMockedMethodSymbolsFromSetupMethodInvocation(SemanticModel semanticModel, InvocationExpressionSyntax? setupMethodInvocation)
- {
- LambdaExpressionSyntax? setupLambdaArgument = setupMethodInvocation?.ArgumentList.Arguments[0].Expression as LambdaExpressionSyntax;
- InvocationExpressionSyntax? mockedMethodInvocation = setupLambdaArgument?.Body as InvocationExpressionSyntax;
-
- return GetAllMatchingSymbols(semanticModel, mockedMethodInvocation);
- }
-
- internal static IEnumerable GetAllMatchingSymbols(SemanticModel semanticModel, ExpressionSyntax? expression)
- where T : class
- {
- List? matchingSymbols = new List();
- if (expression != null)
- {
- SymbolInfo symbolInfo = semanticModel.GetSymbolInfo(expression);
- if (symbolInfo is { CandidateReason: CandidateReason.None, Symbol: T })
- {
- matchingSymbols.Add(symbolInfo.Symbol as T);
- }
- else if (symbolInfo.CandidateReason == CandidateReason.OverloadResolutionFailure)
- {
- matchingSymbols.AddRange(symbolInfo.CandidateSymbols.OfType());
- }
- }
-
- return matchingSymbols;
- }
-
- private static bool IsCallbackOrReturnSymbol(ISymbol? symbol)
- {
- // TODO: Check what is the best way to do such checks
- if (symbol is not IMethodSymbol methodSymbol) return false;
- string? methodName = methodSymbol.ToString();
- return methodName.StartsWith("Moq.Language.ICallback", StringComparison.Ordinal)
- || methodName.StartsWith("Moq.Language.IReturns", StringComparison.Ordinal);
- }
-}
diff --git a/build/targets/codeanalysis/.globalconfig b/build/targets/codeanalysis/.globalconfig
new file mode 100644
index 00000000..0b6e8efa
--- /dev/null
+++ b/build/targets/codeanalysis/.globalconfig
@@ -0,0 +1,9 @@
+is_global=true
+
+# Prefer configuring diagnostics in .editorconfig over this .globalconfig because it is understood by more editors.
+# Only use this file for configuring diagnostics that aren't tied to a source file, and thus can't be placed under
+# any .editorconfig section.
+
+# AV2210 : Pass -warnaserror to the compiler or add True to your project file
+# This is set as part of the CI build. It is intentionally not set locally to allow for a fast inner dev loop.
+dotnet_diagnostic.AV2210.severity = none
diff --git a/build/targets/codeanalysis/CodeAnalysis.props b/build/targets/codeanalysis/CodeAnalysis.props
index d49d75d9..a66e6a33 100644
--- a/build/targets/codeanalysis/CodeAnalysis.props
+++ b/build/targets/codeanalysis/CodeAnalysis.props
@@ -5,10 +5,13 @@
preview
9999
true
+ true
+ true
-
+
+
@@ -25,5 +28,18 @@
all
runtime; build; native; contentfiles; analyzers; buildtransitive
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
diff --git a/build/targets/codeanalysis/Packages.props b/build/targets/codeanalysis/Packages.props
index 19c793af..a2a0d873 100644
--- a/build/targets/codeanalysis/Packages.props
+++ b/build/targets/codeanalysis/Packages.props
@@ -1,8 +1,12 @@
-
+
+
+
+
+
diff --git a/Source/stylecop.json b/build/targets/codeanalysis/stylecop.json
similarity index 100%
rename from Source/stylecop.json
rename to build/targets/codeanalysis/stylecop.json
diff --git a/build/targets/tests/Packages.props b/build/targets/tests/Packages.props
index fc5e9f1d..e5c094e6 100644
--- a/build/targets/tests/Packages.props
+++ b/build/targets/tests/Packages.props
@@ -1,11 +1,10 @@
-
-
+
-
+
diff --git a/build/targets/tests/Tests.props b/build/targets/tests/Tests.props
index 9a32918b..e26d218c 100644
--- a/build/targets/tests/Tests.props
+++ b/build/targets/tests/Tests.props
@@ -8,5 +8,6 @@
all
runtime; build; native; contentfiles; analyzers; buildtransitive
+
diff --git a/docs/rules/README.md b/docs/rules/README.md
index e6be3865..ab77dfc1 100644
--- a/docs/rules/README.md
+++ b/docs/rules/README.md
@@ -2,11 +2,11 @@
| ID | Title |
| --- | --- |
-[Moq1000](./Moq1000.md) | Sealed classes cannot be mocked
-[Moq1001](./Moq1001.md) | Mocked interfaces cannot have constructor parameters
-[Moq1002](./Moq1002.md) | Parameters provided into mock do not match any existing constructors
-[Moq1100](./Moq1100.md) | Callback signature must match the signature of the mocked method
-[Moq1101](./Moq1101.md) | SetupGet/SetupSet should be used for properties, not for methods
-[Moq1200](./Moq1200.md) | Setup should be used only for overridable members
-[Moq1201](./Moq1201.md) | Setup of async methods should use `.ReturnsAsync` instance instead of `.Result`
-[Moq1300](./Moq1300.md) | `Mock.As()` should take interfaces only
+| [Moq1000](./Moq1000.md) | Sealed classes cannot be mocked |
+| [Moq1001](./Moq1001.md) | Mocked interfaces cannot have constructor parameters |
+| [Moq1002](./Moq1002.md) | Parameters provided into mock do not match any existing constructors |
+| [Moq1100](./Moq1100.md) | Callback signature must match the signature of the mocked method |
+| [Moq1101](./Moq1101.md) | SetupGet/SetupSet should be used for properties, not for methods |
+| [Moq1200](./Moq1200.md) | Setup should be used only for overridable members |
+| [Moq1201](./Moq1201.md) | Setup of async methods should use `.ReturnsAsync` instance instead of `.Result` |
+| [Moq1300](./Moq1300.md) | `Mock.As()` should take interfaces only |
diff --git a/Source/Moq.Analyzers/AnalyzerReleases.Shipped.md b/src/Moq.Analyzers/AnalyzerReleases.Shipped.md
similarity index 100%
rename from Source/Moq.Analyzers/AnalyzerReleases.Shipped.md
rename to src/Moq.Analyzers/AnalyzerReleases.Shipped.md
diff --git a/Source/Moq.Analyzers/AnalyzerReleases.Unshipped.md b/src/Moq.Analyzers/AnalyzerReleases.Unshipped.md
similarity index 100%
rename from Source/Moq.Analyzers/AnalyzerReleases.Unshipped.md
rename to src/Moq.Analyzers/AnalyzerReleases.Unshipped.md
diff --git a/Source/Moq.Analyzers/AsShouldBeUsedOnlyForInterfaceAnalyzer.cs b/src/Moq.Analyzers/AsShouldBeUsedOnlyForInterfaceAnalyzer.cs
similarity index 91%
rename from Source/Moq.Analyzers/AsShouldBeUsedOnlyForInterfaceAnalyzer.cs
rename to src/Moq.Analyzers/AsShouldBeUsedOnlyForInterfaceAnalyzer.cs
index 8ee18fb4..887c9a78 100644
--- a/Source/Moq.Analyzers/AsShouldBeUsedOnlyForInterfaceAnalyzer.cs
+++ b/src/Moq.Analyzers/AsShouldBeUsedOnlyForInterfaceAnalyzer.cs
@@ -32,6 +32,7 @@ public override void Initialize(AnalysisContext context)
context.RegisterSyntaxNodeAction(Analyze, SyntaxKind.InvocationExpression);
}
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Maintainability", "AV1500:Member or local function contains too many statements", Justification = "Tracked in https://github.com/rjmurillo/moq.analyzers/issues/90")]
private static void Analyze(SyntaxNodeAnalysisContext context)
{
if (context.Node is not InvocationExpressionSyntax invocationExpression)
diff --git a/Source/Moq.Analyzers/CallbackSignatureShouldMatchMockedMethodAnalyzer.cs b/src/Moq.Analyzers/CallbackSignatureShouldMatchMockedMethodAnalyzer.cs
similarity index 66%
rename from Source/Moq.Analyzers/CallbackSignatureShouldMatchMockedMethodAnalyzer.cs
rename to src/Moq.Analyzers/CallbackSignatureShouldMatchMockedMethodAnalyzer.cs
index e9bc2d96..8760a9d2 100644
--- a/Source/Moq.Analyzers/CallbackSignatureShouldMatchMockedMethodAnalyzer.cs
+++ b/src/Moq.Analyzers/CallbackSignatureShouldMatchMockedMethodAnalyzer.cs
@@ -1,3 +1,5 @@
+using System.Diagnostics;
+
namespace Moq.Analyzers;
///
@@ -33,16 +35,17 @@ public override void Initialize(AnalysisContext context)
context.RegisterSyntaxNodeAction(Analyze, SyntaxKind.InvocationExpression);
}
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Maintainability", "AV1500:Member or local function contains too many statements", Justification = "Tracked in https://github.com/rjmurillo/moq.analyzers/issues/90")]
private static void Analyze(SyntaxNodeAnalysisContext context)
{
- InvocationExpressionSyntax? callbackOrReturnsInvocation = (InvocationExpressionSyntax)context.Node;
+ InvocationExpressionSyntax callbackOrReturnsInvocation = (InvocationExpressionSyntax)context.Node;
SeparatedSyntaxList callbackOrReturnsMethodArguments = callbackOrReturnsInvocation.ArgumentList.Arguments;
// Ignoring Callback() and Return() calls without lambda arguments
if (callbackOrReturnsMethodArguments.Count == 0) return;
- if (!Helpers.IsCallbackOrReturnInvocation(context.SemanticModel, callbackOrReturnsInvocation)) return;
+ if (!context.SemanticModel.IsCallbackOrReturnInvocation(callbackOrReturnsInvocation)) return;
ParenthesizedLambdaExpressionSyntax? callbackLambda = callbackOrReturnsInvocation.ArgumentList.Arguments[0]?.Expression as ParenthesizedLambdaExpressionSyntax;
@@ -53,28 +56,36 @@ private static void Analyze(SyntaxNodeAnalysisContext context)
SeparatedSyntaxList lambdaParameters = callbackLambda.ParameterList.Parameters;
if (lambdaParameters.Count == 0) return;
- InvocationExpressionSyntax? setupInvocation = Helpers.FindSetupMethodFromCallbackInvocation(context.SemanticModel, callbackOrReturnsInvocation, context.CancellationToken);
- InvocationExpressionSyntax? mockedMethodInvocation = Helpers.FindMockedMethodInvocationFromSetupMethod(setupInvocation);
+ InvocationExpressionSyntax? setupInvocation = context.SemanticModel.FindSetupMethodFromCallbackInvocation(callbackOrReturnsInvocation, context.CancellationToken);
+ InvocationExpressionSyntax? mockedMethodInvocation = setupInvocation.FindMockedMethodInvocationFromSetupMethod();
if (mockedMethodInvocation == null) return;
SeparatedSyntaxList mockedMethodArguments = mockedMethodInvocation.ArgumentList.Arguments;
if (mockedMethodArguments.Count != lambdaParameters.Count)
{
- Diagnostic? diagnostic = Diagnostic.Create(Rule, callbackLambda.ParameterList.GetLocation());
+ Diagnostic diagnostic = Diagnostic.Create(Rule, callbackLambda.ParameterList.GetLocation());
context.ReportDiagnostic(diagnostic);
}
else
{
- for (int i = 0; i < mockedMethodArguments.Count; i++)
+ for (int argumentIndex = 0; argumentIndex < mockedMethodArguments.Count; argumentIndex++)
{
- TypeInfo mockedMethodArgumentType = context.SemanticModel.GetTypeInfo(mockedMethodArguments[i].Expression, context.CancellationToken);
- TypeInfo lambdaParameterType = context.SemanticModel.GetTypeInfo(lambdaParameters[i].Type, context.CancellationToken);
+ TypeSyntax? lambdaParameterTypeSyntax = lambdaParameters[argumentIndex].Type;
+
+ // TODO: Don't know if continue or break is the right thing to do here
+ if (lambdaParameterTypeSyntax is null) continue;
+
+ TypeInfo lambdaParameterType = context.SemanticModel.GetTypeInfo(lambdaParameterTypeSyntax, context.CancellationToken);
+
+ TypeInfo mockedMethodArgumentType = context.SemanticModel.GetTypeInfo(mockedMethodArguments[argumentIndex].Expression, context.CancellationToken);
+
string? mockedMethodTypeName = mockedMethodArgumentType.ConvertedType?.ToString();
string? lambdaParameterTypeName = lambdaParameterType.ConvertedType?.ToString();
+
if (!string.Equals(mockedMethodTypeName, lambdaParameterTypeName, StringComparison.Ordinal))
{
- Diagnostic? diagnostic = Diagnostic.Create(Rule, callbackLambda.ParameterList.GetLocation());
+ Diagnostic diagnostic = Diagnostic.Create(Rule, callbackLambda.ParameterList.GetLocation());
context.ReportDiagnostic(diagnostic);
}
}
diff --git a/Source/Moq.Analyzers/CallbackSignatureShouldMatchMockedMethodCodeFix.cs b/src/Moq.Analyzers/CallbackSignatureShouldMatchMockedMethodCodeFix.cs
similarity index 66%
rename from Source/Moq.Analyzers/CallbackSignatureShouldMatchMockedMethodCodeFix.cs
rename to src/Moq.Analyzers/CallbackSignatureShouldMatchMockedMethodCodeFix.cs
index 87f403a6..70a6eed7 100644
--- a/Source/Moq.Analyzers/CallbackSignatureShouldMatchMockedMethodCodeFix.cs
+++ b/src/Moq.Analyzers/CallbackSignatureShouldMatchMockedMethodCodeFix.cs
@@ -35,7 +35,7 @@ public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)
return;
}
- Diagnostic? diagnostic = context.Diagnostics.First();
+ Diagnostic diagnostic = context.Diagnostics.First();
TextSpan diagnosticSpan = diagnostic.Location.SourceSpan;
// Find the type declaration identified by the diagnostic.
@@ -48,18 +48,17 @@ public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)
// Register a code action that will invoke the fix.
context.RegisterCodeFix(
CodeAction.Create(
- title: "Fix Moq callback signature",
- createChangedDocument: c => FixCallbackSignatureAsync(root, context.Document, badArgumentListSyntax, c),
- equivalenceKey: "Fix Moq callback signature"),
+ "Fix Moq callback signature",
+ cancellationToken => FixCallbackSignatureAsync(root, context.Document, badArgumentListSyntax, cancellationToken),
+ "Fix Moq callback signature"),
diagnostic);
}
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Maintainability", "AV1500:Member or local function contains too many statements", Justification = "Tracked in https://github.com/rjmurillo/moq.analyzers/issues/90")]
private async Task FixCallbackSignatureAsync(SyntaxNode root, Document document, ParameterListSyntax? oldParameters, CancellationToken cancellationToken)
{
SemanticModel? semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);
- Debug.Assert(semanticModel != null, nameof(semanticModel) + " != null");
-
if (semanticModel == null)
{
return document;
@@ -70,23 +69,23 @@ private async Task FixCallbackSignatureAsync(SyntaxNode root, Document
return document;
}
- InvocationExpressionSyntax? setupMethodInvocation = Helpers.FindSetupMethodFromCallbackInvocation(semanticModel, callbackInvocation, cancellationToken);
+ InvocationExpressionSyntax? setupMethodInvocation = semanticModel.FindSetupMethodFromCallbackInvocation(callbackInvocation, cancellationToken);
Debug.Assert(setupMethodInvocation != null, nameof(setupMethodInvocation) + " != null");
- IMethodSymbol[]? matchingMockedMethods = Helpers.GetAllMatchingMockedMethodSymbolsFromSetupMethodInvocation(semanticModel, setupMethodInvocation).ToArray();
+ IMethodSymbol[] matchingMockedMethods = semanticModel.GetAllMatchingMockedMethodSymbolsFromSetupMethodInvocation(setupMethodInvocation).ToArray();
- if (matchingMockedMethods.Length != 1 || oldParameters == null)
+ if (matchingMockedMethods.Length != 1)
{
return document;
}
- ParameterListSyntax? newParameters = SyntaxFactory.ParameterList(SyntaxFactory.SeparatedList(matchingMockedMethods[0].Parameters.Select(
- p =>
+ ParameterListSyntax newParameters = SyntaxFactory.ParameterList(SyntaxFactory.SeparatedList(matchingMockedMethods[0].Parameters.Select(
+ parameterSymbol =>
{
- TypeSyntax? type = SyntaxFactory.ParseTypeName(p.Type.ToMinimalDisplayString(semanticModel, oldParameters.SpanStart));
- return SyntaxFactory.Parameter(default, SyntaxFactory.TokenList(), type, SyntaxFactory.Identifier(p.Name), null);
+ TypeSyntax type = SyntaxFactory.ParseTypeName(parameterSymbol.Type.ToMinimalDisplayString(semanticModel, oldParameters.SpanStart));
+ return SyntaxFactory.Parameter(default, SyntaxFactory.TokenList(), type, SyntaxFactory.Identifier(parameterSymbol.Name), null);
})));
- SyntaxNode? newRoot = root.ReplaceNode(oldParameters, newParameters);
+ SyntaxNode newRoot = root.ReplaceNode(oldParameters, newParameters);
return document.WithSyntaxRoot(newRoot);
}
}
diff --git a/Source/Moq.Analyzers/ConstructorArgumentsShouldMatchAnalyzer.cs b/src/Moq.Analyzers/ConstructorArgumentsShouldMatchAnalyzer.cs
similarity index 54%
rename from Source/Moq.Analyzers/ConstructorArgumentsShouldMatchAnalyzer.cs
rename to src/Moq.Analyzers/ConstructorArgumentsShouldMatchAnalyzer.cs
index b08d5c99..69922915 100644
--- a/Source/Moq.Analyzers/ConstructorArgumentsShouldMatchAnalyzer.cs
+++ b/src/Moq.Analyzers/ConstructorArgumentsShouldMatchAnalyzer.cs
@@ -35,77 +35,105 @@ public override void Initialize(AnalysisContext context)
context.RegisterSyntaxNodeAction(Analyze, SyntaxKind.ObjectCreationExpression);
}
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Maintainability", "AV1500:Member or local function contains too many statements", Justification = "Tracked in https://github.com/rjmurillo/moq.analyzers/issues/90")]
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "MA0051:Method is too long", Justification = "Tracked in #90")]
private static void Analyze(SyntaxNodeAnalysisContext context)
{
- ObjectCreationExpressionSyntax? objectCreation = (ObjectCreationExpressionSyntax)context.Node;
+ ObjectCreationExpressionSyntax objectCreation = (ObjectCreationExpressionSyntax)context.Node;
GenericNameSyntax? genericName = GetGenericNameSyntax(objectCreation.Type);
if (genericName == null) return;
- if (!IsMockGenericType(genericName)) return;
+ if (!IsMockGenericType(genericName))
+ {
+ return;
+ }
// Full check that we are calling new Mock()
IMethodSymbol? constructorSymbol = GetConstructorSymbol(context, objectCreation);
+ // If constructorSymbol is null, we should have caught that earlier (and we cannot proceed)
+ if (constructorSymbol == null)
+ {
+ return;
+ }
+
// Vararg parameter is the one that takes all arguments for mocked class constructor
- IParameterSymbol? varArgsConstructorParameter = constructorSymbol?.Parameters.FirstOrDefault(x => x.IsParams);
+ IParameterSymbol? varArgsConstructorParameter = constructorSymbol.Parameters.FirstOrDefault(parameterSymbol => parameterSymbol.IsParams);
// Vararg parameter are not used, so there are no arguments for mocked class constructor
- if (varArgsConstructorParameter == null) return;
-
- Debug.Assert(constructorSymbol != null, nameof(constructorSymbol) + " != null");
-
- if (constructorSymbol == null)
+ if (varArgsConstructorParameter == null)
{
return;
}
- int varArgsConstructorParameterIdx = constructorSymbol.Parameters.IndexOf(varArgsConstructorParameter);
+ int varArgsConstructorParameterIndex = constructorSymbol.Parameters.IndexOf(varArgsConstructorParameter);
// Find mocked type
INamedTypeSymbol? mockedTypeSymbol = GetMockedSymbol(context, genericName);
- if (mockedTypeSymbol == null) return;
+ if (mockedTypeSymbol == null)
+ {
+ return;
+ }
// Skip first argument if it is not vararg - typically it is MockingBehavior argument
- ArgumentSyntax[]? constructorArguments = objectCreation.ArgumentList?.Arguments.Skip(varArgsConstructorParameterIdx == 0 ? 0 : 1).ToArray();
+ IEnumerable? constructorArguments = objectCreation.ArgumentList?.Arguments.Skip(varArgsConstructorParameterIndex == 0 ? 0 : 1);
if (!mockedTypeSymbol.IsAbstract)
{
- if (constructorArguments != null
- && IsConstructorMismatch(context, objectCreation, genericName, constructorArguments)
- && objectCreation.ArgumentList != null)
- {
- Diagnostic? diagnostic = Diagnostic.Create(Rule, objectCreation.ArgumentList.GetLocation());
- context.ReportDiagnostic(diagnostic);
- }
+ AnalyzeConcrete(context, constructorArguments, objectCreation, genericName);
}
else
{
// Issue #1: Currently detection does not work well for abstract classes because they cannot be instantiated
// The mocked symbol is abstract, so we need to check if the constructor arguments match the abstract class constructor
+ AnalyzeAbstract(context, constructorArguments, mockedTypeSymbol, objectCreation);
+ }
+ }
- // Extract types of arguments passed in the constructor call
- if (constructorArguments != null)
- {
- ITypeSymbol[] argumentTypes = constructorArguments
- .Select(arg => context.SemanticModel.GetTypeInfo(arg.Expression, context.CancellationToken).Type)
- .ToArray()!;
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Maintainability", "AV1500:Member or local function contains too many statements", Justification = "Tracked in https://github.com/rjmurillo/moq.analyzers/issues/90")]
+ private static void AnalyzeAbstract(
+ SyntaxNodeAnalysisContext context,
+ IEnumerable? constructorArguments,
+ INamedTypeSymbol mockedTypeSymbol,
+ ObjectCreationExpressionSyntax objectCreation)
+ {
+ // Extract types of arguments passed in the constructor call
+ if (constructorArguments != null)
+ {
+ ITypeSymbol[] argumentTypes = constructorArguments
+ .Select(arg => context.SemanticModel.GetTypeInfo(arg.Expression, context.CancellationToken).Type)
+ .ToArray()!;
- // Check all constructors of the abstract type
- for (int i = 0; i < mockedTypeSymbol.Constructors.Length; i++)
+ // Check all constructors of the abstract type
+ for (int constructorIndex = 0; constructorIndex < mockedTypeSymbol.Constructors.Length; constructorIndex++)
+ {
+ IMethodSymbol constructor = mockedTypeSymbol.Constructors[constructorIndex];
+ if (AreParametersMatching(constructor.Parameters, argumentTypes))
{
- IMethodSymbol constructor = mockedTypeSymbol.Constructors[i];
- if (AreParametersMatching(constructor.Parameters, argumentTypes))
- {
- return; // Found a matching constructor
- }
+ return;
}
}
+ }
- Debug.Assert(objectCreation.ArgumentList != null, "objectCreation.ArgumentList != null");
+ Debug.Assert(objectCreation.ArgumentList != null, "objectCreation.ArgumentList != null");
- Diagnostic? diagnostic = Diagnostic.Create(Rule, objectCreation.ArgumentList?.GetLocation());
+ Diagnostic diagnostic = Diagnostic.Create(Rule, objectCreation.ArgumentList?.GetLocation());
+ context.ReportDiagnostic(diagnostic);
+ }
+
+ private static void AnalyzeConcrete(
+ SyntaxNodeAnalysisContext context,
+ IEnumerable? constructorArguments,
+ ObjectCreationExpressionSyntax objectCreation,
+ GenericNameSyntax genericName)
+ {
+ if (constructorArguments != null
+ && IsConstructorMismatch(context, objectCreation, genericName, constructorArguments)
+ && objectCreation.ArgumentList != null)
+ {
+ Diagnostic diagnostic = Diagnostic.Create(Rule, objectCreation.ArgumentList.GetLocation());
context.ReportDiagnostic(diagnostic);
}
}
@@ -121,18 +149,20 @@ private static void Analyze(SyntaxNodeAnalysisContext context)
return mockedTypeSymbol;
}
- private static bool AreParametersMatching(ImmutableArray constructorParameters, ITypeSymbol[] argumentTypes2)
+ private static bool AreParametersMatching(
+ ImmutableArray constructorParameters,
+ ITypeSymbol[] argumentTypes)
{
// Check if the number of parameters matches
- if (constructorParameters.Length != argumentTypes2.Length)
+ if (constructorParameters.Length != argumentTypes.Length)
{
return false;
}
// Check if each parameter type matches in order
- for (int i = 0; i < constructorParameters.Length; i++)
+ for (int constructorParameterIndex = 0; constructorParameterIndex < constructorParameters.Length; constructorParameterIndex++)
{
- if (!constructorParameters[i].Type.Equals(argumentTypes2[i], SymbolEqualityComparer.IncludeNullability))
+ if (!constructorParameters[constructorParameterIndex].Type.Equals(argumentTypes[constructorParameterIndex], SymbolEqualityComparer.IncludeNullability))
{
return false;
}
@@ -143,6 +173,8 @@ private static bool AreParametersMatching(ImmutableArray const
private static GenericNameSyntax? GetGenericNameSyntax(TypeSyntax typeSyntax)
{
+ // REVIEW: Switch and ifs are equal in this case, but switch causes AV1535 to trigger
+ // The switch expression adds more instructions to do the same, so stick with ifs
if (typeSyntax is GenericNameSyntax genericNameSyntax)
{
return genericNameSyntax;
@@ -166,6 +198,7 @@ private static bool IsMockGenericType(GenericNameSyntax genericName)
{
SymbolInfo constructorSymbolInfo = context.SemanticModel.GetSymbolInfo(objectCreation, context.CancellationToken);
IMethodSymbol? constructorSymbol = constructorSymbolInfo.Symbol as IMethodSymbol;
+
return constructorSymbol?.MethodKind == MethodKind.Constructor &&
string.Equals(
constructorSymbol.ContainingType?.ConstructedFrom.ToDisplayString(),
@@ -175,12 +208,16 @@ private static bool IsMockGenericType(GenericNameSyntax genericName)
: null;
}
- private static bool IsConstructorMismatch(SyntaxNodeAnalysisContext context, ObjectCreationExpressionSyntax objectCreation, GenericNameSyntax genericName, ArgumentSyntax[] constructorArguments)
+ private static bool IsConstructorMismatch(
+ SyntaxNodeAnalysisContext context,
+ ObjectCreationExpressionSyntax objectCreation,
+ GenericNameSyntax genericName,
+ IEnumerable constructorArguments)
{
- ObjectCreationExpressionSyntax? fakeConstructorCall = SyntaxFactory.ObjectCreationExpression(
+ ObjectCreationExpressionSyntax fakeConstructorCall = SyntaxFactory.ObjectCreationExpression(
genericName.TypeArgumentList.Arguments.First(),
SyntaxFactory.ArgumentList(SyntaxFactory.SeparatedList(constructorArguments)),
- null);
+ initializer: null);
SymbolInfo mockedClassConstructorSymbolInfo = context.SemanticModel.GetSpeculativeSymbolInfo(
objectCreation.SpanStart, fakeConstructorCall, SpeculativeBindingOption.BindAsExpression);
diff --git a/Source/Moq.Analyzers/DiagnosticCategory.cs b/src/Moq.Analyzers/DiagnosticCategory.cs
similarity index 100%
rename from Source/Moq.Analyzers/DiagnosticCategory.cs
rename to src/Moq.Analyzers/DiagnosticCategory.cs
diff --git a/Source/Moq.Analyzers.Test/GlobalSuppressions.cs b/src/Moq.Analyzers/GlobalSuppressions.cs
similarity index 100%
rename from Source/Moq.Analyzers.Test/GlobalSuppressions.cs
rename to src/Moq.Analyzers/GlobalSuppressions.cs
diff --git a/Source/Moq.Analyzers/GlobalUsings.cs b/src/Moq.Analyzers/GlobalUsings.cs
similarity index 100%
rename from Source/Moq.Analyzers/GlobalUsings.cs
rename to src/Moq.Analyzers/GlobalUsings.cs
diff --git a/src/Moq.Analyzers/InvocationExpressionSyntaxExtensions.cs b/src/Moq.Analyzers/InvocationExpressionSyntaxExtensions.cs
new file mode 100644
index 00000000..547074f7
--- /dev/null
+++ b/src/Moq.Analyzers/InvocationExpressionSyntaxExtensions.cs
@@ -0,0 +1,19 @@
+namespace Moq.Analyzers;
+
+///
+/// Extension methods for s.
+///
+internal static class InvocationExpressionSyntaxExtensions
+{
+ internal static InvocationExpressionSyntax? FindMockedMethodInvocationFromSetupMethod(this InvocationExpressionSyntax? setupInvocation)
+ {
+ LambdaExpressionSyntax? setupLambdaArgument = setupInvocation?.ArgumentList.Arguments[0].Expression as LambdaExpressionSyntax;
+ return setupLambdaArgument?.Body as InvocationExpressionSyntax;
+ }
+
+ internal static ExpressionSyntax? FindMockedMemberExpressionFromSetupMethod(this InvocationExpressionSyntax? setupInvocation)
+ {
+ LambdaExpressionSyntax? setupLambdaArgument = setupInvocation?.ArgumentList.Arguments[0].Expression as LambdaExpressionSyntax;
+ return setupLambdaArgument?.Body as ExpressionSyntax;
+ }
+}
diff --git a/Source/Moq.Analyzers/Moq.Analyzers.csproj b/src/Moq.Analyzers/Moq.Analyzers.csproj
similarity index 100%
rename from Source/Moq.Analyzers/Moq.Analyzers.csproj
rename to src/Moq.Analyzers/Moq.Analyzers.csproj
diff --git a/Source/Moq.Analyzers/MoqAsMethodDescriptor.cs b/src/Moq.Analyzers/MoqAsMethodDescriptor.cs
similarity index 82%
rename from Source/Moq.Analyzers/MoqAsMethodDescriptor.cs
rename to src/Moq.Analyzers/MoqAsMethodDescriptor.cs
index afe70d77..46265a13 100644
--- a/Source/Moq.Analyzers/MoqAsMethodDescriptor.cs
+++ b/src/Moq.Analyzers/MoqAsMethodDescriptor.cs
@@ -8,6 +8,7 @@ internal class MoqAsMethodDescriptor : MoqMethodDescriptorBase
{
private const string MethodName = "As";
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Maintainability", "AV1500:Member or local function contains too many statements", Justification = "Tracked in https://github.com/rjmurillo/moq.analyzers/issues/90")]
public override bool IsMatch(SemanticModel semanticModel, MemberAccessExpressionSyntax memberAccessSyntax, CancellationToken cancellationToken)
{
if (!IsFastMatch(memberAccessSyntax, MethodName.AsSpan()))
diff --git a/Source/Moq.Analyzers/MoqMethodDescriptorBase.cs b/src/Moq.Analyzers/MoqMethodDescriptorBase.cs
similarity index 100%
rename from Source/Moq.Analyzers/MoqMethodDescriptorBase.cs
rename to src/Moq.Analyzers/MoqMethodDescriptorBase.cs
diff --git a/Source/Moq.Analyzers/MoqSetupMethodDescriptor.cs b/src/Moq.Analyzers/MoqSetupMethodDescriptor.cs
similarity index 82%
rename from Source/Moq.Analyzers/MoqSetupMethodDescriptor.cs
rename to src/Moq.Analyzers/MoqSetupMethodDescriptor.cs
index fe42697b..9ca4f1ad 100644
--- a/Source/Moq.Analyzers/MoqSetupMethodDescriptor.cs
+++ b/src/Moq.Analyzers/MoqSetupMethodDescriptor.cs
@@ -8,6 +8,7 @@ internal class MoqSetupMethodDescriptor : MoqMethodDescriptorBase
{
private const string MethodName = "Setup";
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Maintainability", "AV1500:Member or local function contains too many statements", Justification = "Tracked in https://github.com/rjmurillo/moq.analyzers/issues/90")]
public override bool IsMatch(SemanticModel semanticModel, MemberAccessExpressionSyntax memberAccessSyntax, CancellationToken cancellationToken)
{
if (!IsFastMatch(memberAccessSyntax, MethodName.AsSpan()))
diff --git a/Source/Moq.Analyzers/SyntaxExtensions.cs b/src/Moq.Analyzers/NameSyntaxExtensions.cs
similarity index 92%
rename from Source/Moq.Analyzers/SyntaxExtensions.cs
rename to src/Moq.Analyzers/NameSyntaxExtensions.cs
index 9099f12e..45a5004a 100644
--- a/Source/Moq.Analyzers/SyntaxExtensions.cs
+++ b/src/Moq.Analyzers/NameSyntaxExtensions.cs
@@ -3,9 +3,9 @@
namespace Moq.Analyzers;
///
-/// Extensions methods for s.
+/// Extensions methods for s.
///
-internal static class SyntaxExtensions
+internal static class NameSyntaxExtensions
{
///
/// Tries to get the generic arguments of a given .
diff --git a/Source/Moq.Analyzers/NoConstructorArgumentsForInterfaceMockAnalyzer.cs b/src/Moq.Analyzers/NoConstructorArgumentsForInterfaceMockAnalyzer.cs
similarity index 79%
rename from Source/Moq.Analyzers/NoConstructorArgumentsForInterfaceMockAnalyzer.cs
rename to src/Moq.Analyzers/NoConstructorArgumentsForInterfaceMockAnalyzer.cs
index 3a037f89..775cbf1d 100644
--- a/Source/Moq.Analyzers/NoConstructorArgumentsForInterfaceMockAnalyzer.cs
+++ b/src/Moq.Analyzers/NoConstructorArgumentsForInterfaceMockAnalyzer.cs
@@ -35,9 +35,10 @@ public override void Initialize(AnalysisContext context)
context.RegisterSyntaxNodeAction(Analyze, SyntaxKind.ObjectCreationExpression);
}
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Maintainability", "AV1500:Member or local function contains too many statements", Justification = "Tracked in https://github.com/rjmurillo/moq.analyzers/issues/90")]
private static void Analyze(SyntaxNodeAnalysisContext context)
{
- ObjectCreationExpressionSyntax? objectCreation = (ObjectCreationExpressionSyntax)context.Node;
+ ObjectCreationExpressionSyntax objectCreation = (ObjectCreationExpressionSyntax)context.Node;
// TODO Think how to make this piece more elegant while fast
GenericNameSyntax? genericName = objectCreation.Type as GenericNameSyntax;
@@ -53,7 +54,13 @@ private static void Analyze(SyntaxNodeAnalysisContext context)
// Full check
SymbolInfo constructorSymbolInfo = context.SemanticModel.GetSymbolInfo(objectCreation, context.CancellationToken);
- if (constructorSymbolInfo.Symbol is not IMethodSymbol constructorSymbol || constructorSymbol.ContainingType == null || constructorSymbol.ContainingType.ConstructedFrom == null) return;
+ if (constructorSymbolInfo.Symbol is not IMethodSymbol constructorSymbol
+ || constructorSymbol.ContainingType == null
+ || constructorSymbol.ContainingType.ConstructedFrom == null)
+ {
+ return;
+ }
+
if (constructorSymbol.MethodKind != MethodKind.Constructor) return;
if (!string.Equals(
constructorSymbol.ContainingType.ConstructedFrom.ToDisplayString(),
@@ -63,8 +70,8 @@ private static void Analyze(SyntaxNodeAnalysisContext context)
return;
}
- if (constructorSymbol.Parameters == null || constructorSymbol.Parameters.Length == 0) return;
- if (!constructorSymbol.Parameters.Any(x => x.IsParams)) return;
+ if (constructorSymbol.Parameters.Length == 0) return;
+ if (!constructorSymbol.Parameters.Any(parameterSymbol => parameterSymbol.IsParams)) return;
// Find mocked type
SeparatedSyntaxList typeArguments = genericName.TypeArgumentList.Arguments;
@@ -77,7 +84,7 @@ private static void Analyze(SyntaxNodeAnalysisContext context)
{
Debug.Assert(objectCreation.ArgumentList != null, "objectCreation.ArgumentList != null");
- Diagnostic? diagnostic = Diagnostic.Create(Rule, objectCreation.ArgumentList?.GetLocation());
+ Diagnostic diagnostic = Diagnostic.Create(Rule, objectCreation.ArgumentList?.GetLocation());
context.ReportDiagnostic(diagnostic);
}
}
diff --git a/Source/Moq.Analyzers/NoMethodsInPropertySetupAnalyzer.cs b/src/Moq.Analyzers/NoMethodsInPropertySetupAnalyzer.cs
similarity index 78%
rename from Source/Moq.Analyzers/NoMethodsInPropertySetupAnalyzer.cs
rename to src/Moq.Analyzers/NoMethodsInPropertySetupAnalyzer.cs
index cd5a59ec..0880481e 100644
--- a/Source/Moq.Analyzers/NoMethodsInPropertySetupAnalyzer.cs
+++ b/src/Moq.Analyzers/NoMethodsInPropertySetupAnalyzer.cs
@@ -33,21 +33,22 @@ public override void Initialize(AnalysisContext context)
context.RegisterSyntaxNodeAction(Analyze, SyntaxKind.InvocationExpression);
}
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Maintainability", "AV1500:Member or local function contains too many statements", Justification = "Tracked in https://github.com/rjmurillo/moq.analyzers/issues/90")]
private static void Analyze(SyntaxNodeAnalysisContext context)
{
- InvocationExpressionSyntax? setupGetOrSetInvocation = (InvocationExpressionSyntax)context.Node;
+ InvocationExpressionSyntax setupGetOrSetInvocation = (InvocationExpressionSyntax)context.Node;
if (setupGetOrSetInvocation.Expression is not MemberAccessExpressionSyntax setupGetOrSetMethod) return;
if (!string.Equals(setupGetOrSetMethod.Name.ToFullString(), "SetupGet", StringComparison.Ordinal)
&& !string.Equals(setupGetOrSetMethod.Name.ToFullString(), "SetupSet", StringComparison.Ordinal)) return;
- InvocationExpressionSyntax? mockedMethodCall = Helpers.FindMockedMethodInvocationFromSetupMethod(setupGetOrSetInvocation);
+ InvocationExpressionSyntax? mockedMethodCall = setupGetOrSetInvocation.FindMockedMethodInvocationFromSetupMethod();
if (mockedMethodCall == null) return;
ISymbol? mockedMethodSymbol = context.SemanticModel.GetSymbolInfo(mockedMethodCall, context.CancellationToken).Symbol;
if (mockedMethodSymbol == null) return;
- Diagnostic? diagnostic = Diagnostic.Create(Rule, mockedMethodCall.GetLocation());
+ Diagnostic diagnostic = Diagnostic.Create(Rule, mockedMethodCall.GetLocation());
context.ReportDiagnostic(diagnostic);
}
}
diff --git a/Source/Moq.Analyzers/NoSealedClassMocksAnalyzer.cs b/src/Moq.Analyzers/NoSealedClassMocksAnalyzer.cs
similarity index 87%
rename from Source/Moq.Analyzers/NoSealedClassMocksAnalyzer.cs
rename to src/Moq.Analyzers/NoSealedClassMocksAnalyzer.cs
index 58b31879..4e5d244f 100644
--- a/Source/Moq.Analyzers/NoSealedClassMocksAnalyzer.cs
+++ b/src/Moq.Analyzers/NoSealedClassMocksAnalyzer.cs
@@ -33,9 +33,10 @@ public override void Initialize(AnalysisContext context)
context.RegisterSyntaxNodeAction(Analyze, SyntaxKind.ObjectCreationExpression);
}
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Maintainability", "AV1500:Member or local function contains too many statements", Justification = "Tracked in https://github.com/rjmurillo/moq.analyzers/issues/90")]
private static void Analyze(SyntaxNodeAnalysisContext context)
{
- ObjectCreationExpressionSyntax? objectCreation = (ObjectCreationExpressionSyntax)context.Node;
+ ObjectCreationExpressionSyntax objectCreation = (ObjectCreationExpressionSyntax)context.Node;
// TODO Think how to make this piece more elegant while fast
GenericNameSyntax? genericName = objectCreation.Type as GenericNameSyntax;
@@ -67,7 +68,7 @@ private static void Analyze(SyntaxNodeAnalysisContext context)
// Checked mocked type
if (symbol.IsSealed && symbol.TypeKind != TypeKind.Delegate)
{
- Diagnostic? diagnostic = Diagnostic.Create(Rule, typeArguments[0].GetLocation());
+ Diagnostic diagnostic = Diagnostic.Create(Rule, typeArguments[0].GetLocation());
context.ReportDiagnostic(diagnostic);
}
}
diff --git a/Source/Moq.Analyzers/Resources.Designer.cs b/src/Moq.Analyzers/Resources.Designer.cs
similarity index 100%
rename from Source/Moq.Analyzers/Resources.Designer.cs
rename to src/Moq.Analyzers/Resources.Designer.cs
diff --git a/Source/Moq.Analyzers/Resources.resx b/src/Moq.Analyzers/Resources.resx
similarity index 100%
rename from Source/Moq.Analyzers/Resources.resx
rename to src/Moq.Analyzers/Resources.resx
diff --git a/src/Moq.Analyzers/SemanticModelExtensions.cs b/src/Moq.Analyzers/SemanticModelExtensions.cs
new file mode 100644
index 00000000..f6899ae1
--- /dev/null
+++ b/src/Moq.Analyzers/SemanticModelExtensions.cs
@@ -0,0 +1,111 @@
+using System.Diagnostics;
+
+namespace Moq.Analyzers;
+
+///
+/// Extensions methods for .
+///
+internal static class SemanticModelExtensions
+{
+ private static readonly MoqMethodDescriptorBase MoqSetupMethodDescriptor = new MoqSetupMethodDescriptor();
+
+ internal static InvocationExpressionSyntax? FindSetupMethodFromCallbackInvocation(this SemanticModel semanticModel, ExpressionSyntax expression, CancellationToken cancellationToken)
+ {
+ InvocationExpressionSyntax? invocation = expression as InvocationExpressionSyntax;
+ if (invocation?.Expression is not MemberAccessExpressionSyntax method) return null;
+ if (IsMoqSetupMethod(semanticModel, method, cancellationToken)) return invocation;
+ return FindSetupMethodFromCallbackInvocation(semanticModel, method.Expression, cancellationToken);
+ }
+
+ internal static IEnumerable GetAllMatchingMockedMethodSymbolsFromSetupMethodInvocation(this SemanticModel semanticModel, InvocationExpressionSyntax? setupMethodInvocation)
+ {
+ LambdaExpressionSyntax? setupLambdaArgument = setupMethodInvocation?.ArgumentList.Arguments[0].Expression as LambdaExpressionSyntax;
+
+ return setupLambdaArgument?.Body is not InvocationExpressionSyntax mockedMethodInvocation
+ ? []
+ : semanticModel.GetAllMatchingSymbols(mockedMethodInvocation);
+ }
+
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Maintainability", "AV1500:Member or local function contains too many statements", Justification = "Tracked in https://github.com/rjmurillo/moq.analyzers/issues/90")]
+ internal static bool IsCallbackOrReturnInvocation(this SemanticModel semanticModel, InvocationExpressionSyntax callbackOrReturnsInvocation)
+ {
+ MemberAccessExpressionSyntax? callbackOrReturnsMethod = callbackOrReturnsInvocation.Expression as MemberAccessExpressionSyntax;
+
+ if (callbackOrReturnsMethod == null)
+ {
+ return false;
+ }
+
+ string methodName = callbackOrReturnsMethod.Name.ToString();
+
+ // First fast check before walking semantic model
+ if (!string.Equals(methodName, "Callback", StringComparison.Ordinal)
+ && !string.Equals(methodName, "Returns", StringComparison.Ordinal))
+ {
+ return false;
+ }
+
+ SymbolInfo symbolInfo = semanticModel.GetSymbolInfo(callbackOrReturnsMethod);
+ return symbolInfo.CandidateReason switch
+ {
+ CandidateReason.OverloadResolutionFailure => symbolInfo.CandidateSymbols.Any(IsCallbackOrReturnSymbol),
+ CandidateReason.None => IsCallbackOrReturnSymbol(symbolInfo.Symbol),
+ _ => false,
+ };
+ }
+
+ internal static bool IsMoqSetupMethod(this SemanticModel semanticModel, MemberAccessExpressionSyntax method, CancellationToken cancellationToken)
+ {
+ return MoqSetupMethodDescriptor.IsMatch(semanticModel, method, cancellationToken);
+ }
+
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Maintainability", "AV1500:Member or local function contains too many statements", Justification = "Tracked in https://github.com/rjmurillo/moq.analyzers/issues/90")]
+ private static List GetAllMatchingSymbols(this SemanticModel semanticModel, ExpressionSyntax expression)
+ where T : class
+ {
+ List matchingSymbols = new();
+
+ SymbolInfo symbolInfo = semanticModel.GetSymbolInfo(expression);
+ switch (symbolInfo)
+ {
+ case { CandidateReason: CandidateReason.None, Symbol: T }:
+ {
+ T? value = symbolInfo.Symbol as T;
+ Debug.Assert(value != null, "Value should not be null.");
+
+#pragma warning disable S2589 // Boolean expressions should not be gratuitous
+ if (value != default(T))
+ {
+ matchingSymbols.Add(value);
+ }
+#pragma warning restore S2589 // Boolean expressions should not be gratuitous
+ break;
+ }
+
+ default:
+ {
+ if (symbolInfo.CandidateReason == CandidateReason.OverloadResolutionFailure)
+ {
+ matchingSymbols.AddRange(symbolInfo.CandidateSymbols.OfType());
+ }
+ else
+ {
+ return matchingSymbols;
+ }
+
+ break;
+ }
+ }
+
+ return matchingSymbols;
+ }
+
+ private static bool IsCallbackOrReturnSymbol(ISymbol? symbol)
+ {
+ // TODO: Check what is the best way to do such checks
+ if (symbol is not IMethodSymbol methodSymbol) return false;
+ string? methodName = methodSymbol.ToString();
+ return methodName.StartsWith("Moq.Language.ICallback", StringComparison.Ordinal)
+ || methodName.StartsWith("Moq.Language.IReturns", StringComparison.Ordinal);
+ }
+}
diff --git a/Source/Moq.Analyzers/SetupShouldBeUsedOnlyForOverridableMembersAnalyzer.cs b/src/Moq.Analyzers/SetupShouldBeUsedOnlyForOverridableMembersAnalyzer.cs
similarity index 75%
rename from Source/Moq.Analyzers/SetupShouldBeUsedOnlyForOverridableMembersAnalyzer.cs
rename to src/Moq.Analyzers/SetupShouldBeUsedOnlyForOverridableMembersAnalyzer.cs
index 4145492f..aaceb995 100644
--- a/Source/Moq.Analyzers/SetupShouldBeUsedOnlyForOverridableMembersAnalyzer.cs
+++ b/src/Moq.Analyzers/SetupShouldBeUsedOnlyForOverridableMembersAnalyzer.cs
@@ -30,13 +30,14 @@ public override void Initialize(AnalysisContext context)
context.RegisterSyntaxNodeAction(Analyze, SyntaxKind.InvocationExpression);
}
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Maintainability", "AV1500:Member or local function contains too many statements", Justification = "Tracked in https://github.com/rjmurillo/moq.analyzers/issues/90")]
private static void Analyze(SyntaxNodeAnalysisContext context)
{
- InvocationExpressionSyntax? setupInvocation = (InvocationExpressionSyntax)context.Node;
+ InvocationExpressionSyntax setupInvocation = (InvocationExpressionSyntax)context.Node;
- if (setupInvocation.Expression is MemberAccessExpressionSyntax memberAccessExpression && Helpers.IsMoqSetupMethod(context.SemanticModel, memberAccessExpression, context.CancellationToken))
+ if (setupInvocation.Expression is MemberAccessExpressionSyntax memberAccessExpression && context.SemanticModel.IsMoqSetupMethod(memberAccessExpression, context.CancellationToken))
{
- ExpressionSyntax? mockedMemberExpression = Helpers.FindMockedMemberExpressionFromSetupMethod(setupInvocation);
+ ExpressionSyntax? mockedMemberExpression = setupInvocation.FindMockedMemberExpressionFromSetupMethod();
if (mockedMemberExpression == null)
{
return;
@@ -46,7 +47,7 @@ private static void Analyze(SyntaxNodeAnalysisContext context)
if (symbolInfo.Symbol is IPropertySymbol or IMethodSymbol
&& !IsMethodOverridable(symbolInfo.Symbol))
{
- Diagnostic? diagnostic = Diagnostic.Create(Rule, mockedMemberExpression.GetLocation());
+ Diagnostic diagnostic = Diagnostic.Create(Rule, mockedMemberExpression.GetLocation());
context.ReportDiagnostic(diagnostic);
}
}
diff --git a/Source/Moq.Analyzers/SetupShouldNotIncludeAsyncResultAnalyzer.cs b/src/Moq.Analyzers/SetupShouldNotIncludeAsyncResultAnalyzer.cs
similarity index 78%
rename from Source/Moq.Analyzers/SetupShouldNotIncludeAsyncResultAnalyzer.cs
rename to src/Moq.Analyzers/SetupShouldNotIncludeAsyncResultAnalyzer.cs
index db703d7a..44f861b3 100644
--- a/Source/Moq.Analyzers/SetupShouldNotIncludeAsyncResultAnalyzer.cs
+++ b/src/Moq.Analyzers/SetupShouldNotIncludeAsyncResultAnalyzer.cs
@@ -30,13 +30,14 @@ public override void Initialize(AnalysisContext context)
context.RegisterSyntaxNodeAction(Analyze, SyntaxKind.InvocationExpression);
}
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Maintainability", "AV1500:Member or local function contains too many statements", Justification = "Tracked in https://github.com/rjmurillo/moq.analyzers/issues/90")]
private static void Analyze(SyntaxNodeAnalysisContext context)
{
- InvocationExpressionSyntax? setupInvocation = (InvocationExpressionSyntax)context.Node;
+ InvocationExpressionSyntax setupInvocation = (InvocationExpressionSyntax)context.Node;
- if (setupInvocation.Expression is MemberAccessExpressionSyntax memberAccessExpression && Helpers.IsMoqSetupMethod(context.SemanticModel, memberAccessExpression, context.CancellationToken))
+ if (setupInvocation.Expression is MemberAccessExpressionSyntax memberAccessExpression && context.SemanticModel.IsMoqSetupMethod(memberAccessExpression, context.CancellationToken))
{
- ExpressionSyntax? mockedMemberExpression = Helpers.FindMockedMemberExpressionFromSetupMethod(setupInvocation);
+ ExpressionSyntax? mockedMemberExpression = setupInvocation.FindMockedMemberExpressionFromSetupMethod();
if (mockedMemberExpression == null)
{
return;
@@ -47,7 +48,7 @@ private static void Analyze(SyntaxNodeAnalysisContext context)
&& !IsMethodOverridable(symbolInfo.Symbol)
&& IsMethodReturnTypeTask(symbolInfo.Symbol))
{
- Diagnostic? diagnostic = Diagnostic.Create(Rule, mockedMemberExpression.GetLocation());
+ Diagnostic diagnostic = Diagnostic.Create(Rule, mockedMemberExpression.GetLocation());
context.ReportDiagnostic(diagnostic);
}
}
@@ -61,7 +62,7 @@ private static bool IsMethodOverridable(ISymbol methodSymbol)
private static bool IsMethodReturnTypeTask(ISymbol methodSymbol)
{
- string? type = methodSymbol.ToDisplayString();
+ string type = methodSymbol.ToDisplayString();
return string.Equals(type, "System.Threading.Tasks.Task", StringComparison.Ordinal)
|| string.Equals(type, "System.Threading.ValueTask", StringComparison.Ordinal)
|| type.StartsWith("System.Threading.Tasks.Task<", StringComparison.Ordinal)
diff --git a/Source/Moq.Analyzers/tools/install.ps1 b/src/Moq.Analyzers/tools/install.ps1
similarity index 100%
rename from Source/Moq.Analyzers/tools/install.ps1
rename to src/Moq.Analyzers/tools/install.ps1
diff --git a/Source/Moq.Analyzers/tools/uninstall.ps1 b/src/Moq.Analyzers/tools/uninstall.ps1
similarity index 100%
rename from Source/Moq.Analyzers/tools/uninstall.ps1
rename to src/Moq.Analyzers/tools/uninstall.ps1
diff --git a/Source/Moq.Analyzers.Test/.editorconfig b/tests/.editorconfig
similarity index 64%
rename from Source/Moq.Analyzers.Test/.editorconfig
rename to tests/.editorconfig
index 7f840f54..576433c2 100644
--- a/Source/Moq.Analyzers.Test/.editorconfig
+++ b/tests/.editorconfig
@@ -12,3 +12,9 @@ dotnet_diagnostic.CS1591.severity = suggestion
# CS1712: Type parameter 'type parameter' has no matching typeparam tag in the XML comment on 'type' (but other type parameters do)
dotnet_diagnostic.CS1712.severity = suggestion
+
+# VSTHRD200: Use "Async" suffix for async methods
+# AV1755: Postfix asynchronous methods with Async or TaskAsync
+# Just about every test method is async, doesn't provide any real value and clustters up test window
+dotnet_diagnostic.VSTHRD200.severity = none
+dotnet_diagnostic.AV1755.severity = none
diff --git a/tests/Moq.Analyzers.Benchmarks/Constants.cs b/tests/Moq.Analyzers.Benchmarks/Constants.cs
new file mode 100644
index 00000000..338f9f04
--- /dev/null
+++ b/tests/Moq.Analyzers.Benchmarks/Constants.cs
@@ -0,0 +1,6 @@
+namespace Moq.Analyzers.Benchmarks;
+
+internal static class Constants
+{
+ public const int NumberOfCodeFiles = 1_000;
+}
diff --git a/tests/Moq.Analyzers.Benchmarks/Helpers/AnalysisResultExtensions.cs b/tests/Moq.Analyzers.Benchmarks/Helpers/AnalysisResultExtensions.cs
new file mode 100644
index 00000000..c9dc8ade
--- /dev/null
+++ b/tests/Moq.Analyzers.Benchmarks/Helpers/AnalysisResultExtensions.cs
@@ -0,0 +1,21 @@
+using Microsoft.CodeAnalysis.Diagnostics;
+
+namespace Moq.Analyzers.Benchmarks.Helpers;
+
+internal static class AnalysisResultExtensions
+{
+ public static AnalysisResult AssertValidAnalysisResult(this AnalysisResult analysisResult)
+ {
+ if (analysisResult.Analyzers.Length != 1)
+ {
+ throw new InvalidOperationException($"Expected a single analyzer but found '{analysisResult.Analyzers.Length}'");
+ }
+
+ if (analysisResult.CompilationDiagnostics.Count != 0)
+ {
+ throw new InvalidOperationException($"Expected no compilation diagnostics but found '{analysisResult.CompilationDiagnostics.Count}'");
+ }
+
+ return analysisResult;
+ }
+}
diff --git a/tests/Moq.Analyzers.Benchmarks/Helpers/AsyncLazy.cs b/tests/Moq.Analyzers.Benchmarks/Helpers/AsyncLazy.cs
new file mode 100644
index 00000000..143dabc1
--- /dev/null
+++ b/tests/Moq.Analyzers.Benchmarks/Helpers/AsyncLazy.cs
@@ -0,0 +1,46 @@
+using System.Runtime.CompilerServices;
+
+namespace Moq.Analyzers.Benchmarks.Helpers;
+
+internal class AsyncLazy : Lazy>
+{
+ public AsyncLazy(
+ Func valueFactory,
+ CancellationToken cancellationToken,
+ TaskScheduler? scheduler = null,
+ TaskCreationOptions taskCreationOptions = TaskCreationOptions.None,
+ LazyThreadSafetyMode mode = LazyThreadSafetyMode.ExecutionAndPublication)
+ : base(
+ () =>
+ Task.Factory.StartNew(
+ valueFactory,
+ cancellationToken,
+ taskCreationOptions,
+ scheduler ?? TaskScheduler.Default),
+ mode)
+ {
+ }
+
+ public AsyncLazy(
+ Func> taskFactory,
+ CancellationToken cancellationToken,
+ TaskScheduler? scheduler = null,
+ TaskCreationOptions taskCreationOptions = TaskCreationOptions.None,
+ LazyThreadSafetyMode mode = LazyThreadSafetyMode.ExecutionAndPublication)
+ : base(
+ () =>
+ Task.Factory.StartNew(
+ () => taskFactory(),
+ cancellationToken,
+ taskCreationOptions,
+ scheduler ?? TaskScheduler.Default)
+ .Unwrap(),
+ mode)
+ {
+ }
+
+ public TaskAwaiter GetAwaiter()
+ {
+ return Value.GetAwaiter();
+ }
+}
diff --git a/tests/Moq.Analyzers.Benchmarks/Helpers/BenchmarkCSharpCompilationCreator.cs b/tests/Moq.Analyzers.Benchmarks/Helpers/BenchmarkCSharpCompilationCreator.cs
new file mode 100644
index 00000000..7f90cb41
--- /dev/null
+++ b/tests/Moq.Analyzers.Benchmarks/Helpers/BenchmarkCSharpCompilationCreator.cs
@@ -0,0 +1,26 @@
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Diagnostics;
+using Microsoft.CodeAnalysis.Testing;
+
+namespace Moq.Analyzers.Benchmarks.Helpers;
+
+internal static class BenchmarkCSharpCompilationCreator
+ where TAnalyzer : DiagnosticAnalyzer, new()
+{
+ public static async Task<(CompilationWithAnalyzers Baseline, CompilationWithAnalyzers Test)> CreateAsync(
+ (string Name, string Contents)[] sources,
+ AnalyzerOptions? options = null)
+ {
+ Compilation? compilation = await CSharpCompilationCreator.CreateAsync(sources).ConfigureAwait(false);
+
+ if (compilation is null)
+ {
+ throw new InvalidOperationException("Failed to create compilation");
+ }
+
+ CompilationWithAnalyzers baseline = compilation.WithAnalyzers([new EmptyDiagnosticAnalyzer()], options, CancellationToken.None);
+ CompilationWithAnalyzers test = compilation.WithAnalyzers([new TAnalyzer()], options, CancellationToken.None);
+
+ return (baseline, test);
+ }
+}
diff --git a/tests/Moq.Analyzers.Benchmarks/Helpers/CSharpCompilationCreator.cs b/tests/Moq.Analyzers.Benchmarks/Helpers/CSharpCompilationCreator.cs
new file mode 100644
index 00000000..c829e9b2
--- /dev/null
+++ b/tests/Moq.Analyzers.Benchmarks/Helpers/CSharpCompilationCreator.cs
@@ -0,0 +1,35 @@
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.Diagnostics;
+using Moq.Analyzers.Benchmarks.Helpers;
+
+namespace Moq.Analyzers.Benchmarks;
+
+// Originally from https://github.com/dotnet/roslyn-analyzers/blob/f1115edce8633ebe03a86191bc05c6969ed9a821/src/PerformanceTests/Utilities/CSharp/CSharpCompilationHelper.cs
+// See https://github.com/dotnet/roslyn-sdk/issues/1165 for discussion on providing these or similar helpers in the testing packages.
+internal static class CSharpCompilationCreator
+{
+ public static async Task CreateAsync((string, string)[] sourceFiles)
+ {
+ (Project project, _) = await CreateProjectAsync(sourceFiles, globalOptions: null).ConfigureAwait(false);
+ return await project.GetCompilationAsync().ConfigureAwait(false);
+ }
+
+ public static async Task<(Compilation? Compilation, AnalyzerOptions Options)> CreateWithOptionsAsync((string, string)[] sourceFiles, (string, string)[] globalOptions)
+ {
+ (Project project, AnalyzerOptions options) = await CreateProjectAsync(sourceFiles, globalOptions).ConfigureAwait(false);
+ return (await project.GetCompilationAsync().ConfigureAwait(false), options);
+ }
+
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Maintainability", "AV1553:Do not use optional parameters with default value null for strings, collections or tasks", Justification = "Minimizing divergence from upstream code")]
+ private static Task<(Project Project, AnalyzerOptions Options)> CreateProjectAsync((string, string)[] sourceFiles, (string, string)[]? globalOptions = null)
+ => CompilationCreator.CreateProjectAsync(
+ sourceFiles,
+ globalOptions,
+ "TestProject",
+ LanguageNames.CSharp,
+ "/0/Test",
+ "cs",
+ new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary),
+ new CSharpParseOptions(LanguageVersion.Default));
+}
diff --git a/tests/Moq.Analyzers.Benchmarks/Helpers/CompilationCreator.cs b/tests/Moq.Analyzers.Benchmarks/Helpers/CompilationCreator.cs
new file mode 100644
index 00000000..d3c73a03
--- /dev/null
+++ b/tests/Moq.Analyzers.Benchmarks/Helpers/CompilationCreator.cs
@@ -0,0 +1,159 @@
+using System.Collections.Immutable;
+using System.Diagnostics.CodeAnalysis;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Diagnostics;
+using Microsoft.CodeAnalysis.Host.Mef;
+using Microsoft.CodeAnalysis.Testing;
+using Microsoft.CodeAnalysis.Testing.Model;
+using Microsoft.VisualStudio.Composition;
+
+namespace Moq.Analyzers.Benchmarks.Helpers;
+
+// Originally from https://github.com/dotnet/roslyn-analyzers/blob/f1115edce8633ebe03a86191bc05c6969ed9a821/src/PerformanceTests/Utilities/Common/CompilationHelper.cs
+// See https://github.com/dotnet/roslyn-sdk/issues/1165 for discussion on providing these or similar helpers in the testing packages.
+internal static class CompilationCreator
+{
+ private static readonly ReferenceAssemblies ReferenceAssemblies = ReferenceAssemblies.Net.Net80.AddPackages([new PackageIdentity("Moq", "4.18.4")]);
+
+ [SuppressMessage("Maintainability", "AV1500:Member or local function contains too many statements", Justification = "Minimizing divergence from upstream code.")]
+ public static async Task<(Project Project, AnalyzerOptions Options)> CreateProjectAsync(
+ (string, string)[] sourceFiles,
+ (string, string)[]? globalOptions,
+ string name,
+ string language,
+ string defaultPrefix,
+ string defaultExtension,
+ CompilationOptions compilationOptions,
+ ParseOptions parseOptions)
+ {
+ ProjectState projectState = new ProjectState(name, language, defaultPrefix, defaultExtension);
+ foreach ((string filename, string content) in sourceFiles)
+ {
+ projectState.Sources.Add((defaultPrefix + filename + "." + defaultExtension, content));
+ }
+
+ EvaluatedProjectState evaluatedProj = new EvaluatedProjectState(projectState, ReferenceAssemblies);
+
+ Project project = await CreateProjectAsync(evaluatedProj, compilationOptions, parseOptions).ConfigureAwait(false);
+
+ if (globalOptions is not null)
+ {
+ OptionsProvider optionsProvider = new(globalOptions);
+ AnalyzerOptions options = new(ImmutableArray.Empty, optionsProvider);
+
+ return (project, options);
+ }
+
+ return (project, project.AnalyzerOptions);
+ }
+
+ [SuppressMessage("Maintainability", "AV1500:Member or local function contains too many statements", Justification = "Minimizing divergence with upstream code")]
+ [SuppressMessage("Maintainability", "AV1551:Method overload should call another overload", Justification = "Minimizing divergence with upstream code")]
+ [SuppressMessage("Maintainability", "AV1555:Avoid using non-(nullable-)boolean named arguments", Justification = "Minimizing divergence with upstream code")]
+ private static async Task CreateProjectAsync(
+ EvaluatedProjectState primaryProject,
+ CompilationOptions compilationOptions,
+ ParseOptions parseOptions)
+ {
+ ProjectId projectId = ProjectId.CreateNewId(debugName: primaryProject.Name);
+ Solution solution = await CreateSolutionAsync(projectId, primaryProject, compilationOptions, parseOptions).ConfigureAwait(false);
+
+ foreach ((string newFileName, Microsoft.CodeAnalysis.Text.SourceText source) in primaryProject.Sources)
+ {
+ DocumentId documentId = DocumentId.CreateNewId(projectId, debugName: newFileName);
+ solution = solution.AddDocument(documentId, newFileName, source, filePath: newFileName);
+ }
+
+ foreach ((string newFileName, Microsoft.CodeAnalysis.Text.SourceText source) in primaryProject.AdditionalFiles)
+ {
+ DocumentId documentId = DocumentId.CreateNewId(projectId, debugName: newFileName);
+ solution = solution.AddAdditionalDocument(documentId, newFileName, source, filePath: newFileName);
+ }
+
+ foreach ((string newFileName, Microsoft.CodeAnalysis.Text.SourceText source) in primaryProject.AnalyzerConfigFiles)
+ {
+ DocumentId documentId = DocumentId.CreateNewId(projectId, debugName: newFileName);
+ solution = solution.AddAnalyzerConfigDocument(documentId, newFileName, source, filePath: newFileName);
+ }
+
+ return solution.GetProject(projectId)!;
+ }
+
+ [SuppressMessage("Maintainability", "AV1500:Member or local function contains too many statements", Justification = "Minimizing divergence from upstream")]
+ [SuppressMessage("Maintainability", "AV1561:Signature contains too many parameters", Justification = "Minimizing divergence from upstream")]
+ private static async Task CreateSolutionAsync(
+ ProjectId projectId,
+ EvaluatedProjectState projectState,
+ CompilationOptions compilationOptions,
+ ParseOptions parseOptions)
+ {
+ ReferenceAssemblies referenceAssemblies = projectState.ReferenceAssemblies ?? ReferenceAssemblies.Default;
+
+ compilationOptions = compilationOptions
+ .WithOutputKind(projectState.OutputKind)
+ .WithAssemblyIdentityComparer(referenceAssemblies.AssemblyIdentityComparer);
+
+ parseOptions = parseOptions
+ .WithDocumentationMode(projectState.DocumentationMode);
+
+ AsyncLazy exportProviderFactory = new(
+ async () =>
+ {
+ AttributedPartDiscovery discovery = new(Resolver.DefaultInstance, isNonPublicSupported: true);
+ DiscoveredParts parts = await discovery.CreatePartsAsync(MefHostServices.DefaultAssemblies).ConfigureAwait(false);
+ ComposableCatalog catalog = ComposableCatalog.Create(Resolver.DefaultInstance).AddParts(parts);
+
+ CompositionConfiguration configuration = CompositionConfiguration.Create(catalog);
+ RuntimeComposition runtimeComposition = RuntimeComposition.CreateRuntimeComposition(configuration);
+ return runtimeComposition.CreateExportProviderFactory();
+ },
+ CancellationToken.None);
+ ExportProvider exportProvider = (await exportProviderFactory).CreateExportProvider();
+ MefHostServices host = MefHostServices.Create(exportProvider.AsCompositionContext());
+ AdhocWorkspace workspace = new AdhocWorkspace(host);
+
+ Solution solution = workspace
+ .CurrentSolution
+ .AddProject(projectId, projectState.Name, projectState.Name, projectState.Language)
+ .WithProjectCompilationOptions(projectId, compilationOptions)
+ .WithProjectParseOptions(projectId, parseOptions);
+
+ ImmutableArray metadataReferences = await referenceAssemblies.ResolveAsync(projectState.Language, CancellationToken.None).ConfigureAwait(false);
+ solution = solution.AddMetadataReferences(projectId, metadataReferences);
+
+ return solution;
+ }
+
+ ///
+ /// This class just passes argument through to the projects options provider and it used to provider custom global options.
+ ///
+ private sealed class OptionsProvider : AnalyzerConfigOptionsProvider
+ {
+ public OptionsProvider((string, string)[] globalOptions)
+ {
+ GlobalOptions = new ConfigOptions(globalOptions);
+ }
+
+ public override AnalyzerConfigOptions GlobalOptions { get; }
+
+ public override AnalyzerConfigOptions GetOptions(SyntaxTree tree)
+ => GlobalOptions;
+
+ public override AnalyzerConfigOptions GetOptions(AdditionalText textFile)
+ => GlobalOptions;
+ }
+
+ ///
+ /// Allows adding additional global options.
+ ///
+ private sealed class ConfigOptions : AnalyzerConfigOptions
+ {
+ private readonly Dictionary _globalOptions;
+
+ public ConfigOptions((string, string)[] globalOptions)
+ => _globalOptions = globalOptions.ToDictionary(kvp => kvp.Item1, kvp => kvp.Item2, StringComparer.OrdinalIgnoreCase);
+
+ public override bool TryGetValue(string key, [NotNullWhen(true)] out string? value)
+ => _globalOptions.TryGetValue(key, out value);
+ }
+}
diff --git a/tests/Moq.Analyzers.Benchmarks/Helpers/ExportProviderExtensions.cs b/tests/Moq.Analyzers.Benchmarks/Helpers/ExportProviderExtensions.cs
new file mode 100644
index 00000000..57323710
--- /dev/null
+++ b/tests/Moq.Analyzers.Benchmarks/Helpers/ExportProviderExtensions.cs
@@ -0,0 +1,89 @@
+using System.Composition;
+using System.Composition.Hosting.Core;
+using System.Diagnostics.CodeAnalysis;
+using System.Reflection;
+using Microsoft.VisualStudio.Composition;
+
+namespace Moq.Analyzers.Benchmarks.Helpers;
+
+// Originally from https://github.com/dotnet/roslyn-analyzers/blob/f1115edce8633ebe03a86191bc05c6969ed9a821/src/PerformanceTests/Utilities/Common/ExportProviderExtensions.cs
+// See https://github.com/dotnet/roslyn-sdk/issues/1165 for discussion on providing these or similar helpers in the testing packages.
+internal static class ExportProviderExtensions
+{
+ public static CompositionContext AsCompositionContext(this ExportProvider exportProvider)
+ {
+ return new CompositionContextShim(exportProvider);
+ }
+
+ private sealed class CompositionContextShim : CompositionContext
+ {
+ private readonly ExportProvider _exportProvider;
+
+ public CompositionContextShim(ExportProvider exportProvider)
+ {
+ _exportProvider = exportProvider;
+ }
+
+ [SuppressMessage("Maintainability", "AV1500:Member or local function contains too many statements", Justification = "Minimizing divergence from upstream")]
+ public override bool TryGetExport(CompositionContract contract, [NotNullWhen(true)] out object? export)
+ {
+ bool importMany = contract.MetadataConstraints.Contains(new KeyValuePair("IsImportMany", true));
+ (Type contractType, Type? metadataType) = GetContractType(contract.ContractType, importMany);
+
+ if (metadataType != null)
+ {
+ MethodInfo methodInfo = (from method in _exportProvider.GetType().GetTypeInfo().GetMethods()
+ where string.Equals(method.Name, nameof(ExportProvider.GetExports), StringComparison.Ordinal)
+ where method.IsGenericMethod && method.GetGenericArguments().Length == 2
+ where method.GetParameters().Length == 1 && method.GetParameters()[0].ParameterType == typeof(string)
+ select method).Single();
+ MethodInfo parameterizedMethod = methodInfo.MakeGenericMethod(contractType, metadataType);
+ export = parameterizedMethod.Invoke(_exportProvider, [contract.ContractName]);
+ }
+ else
+ {
+ MethodInfo methodInfo = (from method in _exportProvider.GetType().GetTypeInfo().GetMethods()
+ where string.Equals(method.Name, nameof(ExportProvider.GetExports), StringComparison.Ordinal)
+ where method.IsGenericMethod && method.GetGenericArguments().Length == 1
+ where method.GetParameters().Length == 1 && method.GetParameters()[0].ParameterType == typeof(string)
+ select method).Single();
+ MethodInfo parameterizedMethod = methodInfo.MakeGenericMethod(contractType);
+ export = parameterizedMethod.Invoke(_exportProvider, [contract.ContractName]);
+ }
+
+#pragma warning disable CS8762 // Parameter must have a non-null value when exiting in some condition.
+ return true;
+#pragma warning restore CS8762 // Parameter must have a non-null value when exiting in some condition.
+ }
+
+ [SuppressMessage("Maintainability", "AV1500:Member or local function contains too many statements", Justification = "Minimizing divergence from upstream")]
+ private static (Type ExportType, Type? MetadataType) GetContractType(Type contractType, bool importMany)
+ {
+ if (importMany && contractType.IsConstructedGenericType &&
+ (contractType.GetGenericTypeDefinition() == typeof(IList<>)
+ || contractType.GetGenericTypeDefinition() == typeof(ICollection<>)
+ || contractType.GetGenericTypeDefinition() == typeof(IEnumerable<>)))
+ {
+ contractType = contractType.GenericTypeArguments[0];
+ }
+
+ if (contractType.IsConstructedGenericType)
+ {
+ if (contractType.GetGenericTypeDefinition() == typeof(Lazy<>))
+ {
+ return (contractType.GenericTypeArguments[0], null);
+ }
+ else if (contractType.GetGenericTypeDefinition() == typeof(Lazy<,>))
+ {
+ return (contractType.GenericTypeArguments[0], contractType.GenericTypeArguments[1]);
+ }
+ else
+ {
+ throw new NotSupportedException();
+ }
+ }
+
+ throw new NotSupportedException();
+ }
+ }
+}
diff --git a/tests/Moq.Analyzers.Benchmarks/Moq.Analyzers.Benchmarks.csproj b/tests/Moq.Analyzers.Benchmarks/Moq.Analyzers.Benchmarks.csproj
new file mode 100644
index 00000000..99cc5890
--- /dev/null
+++ b/tests/Moq.Analyzers.Benchmarks/Moq.Analyzers.Benchmarks.csproj
@@ -0,0 +1,16 @@
+
+
+
+ Exe
+ net8.0
+ false
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/Moq.Analyzers.Benchmarks/Moq1300Benchmarks.cs b/tests/Moq.Analyzers.Benchmarks/Moq1300Benchmarks.cs
new file mode 100644
index 00000000..c8006602
--- /dev/null
+++ b/tests/Moq.Analyzers.Benchmarks/Moq1300Benchmarks.cs
@@ -0,0 +1,84 @@
+using System.Collections.Immutable;
+using System.Diagnostics.CodeAnalysis;
+using BenchmarkDotNet.Attributes;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Diagnostics;
+using Moq.Analyzers.Benchmarks.Helpers;
+
+namespace Moq.Analyzers.Benchmarks;
+
+[InProcess]
+[MemoryDiagnoser]
+public class Moq1300Benchmarks
+{
+ private static CompilationWithAnalyzers? BaselineCompilation { get; set; }
+
+ private static CompilationWithAnalyzers? TestCompilation { get; set; }
+
+ [IterationSetup]
+ [SuppressMessage("Usage", "VSTHRD002:Avoid problematic synchronous waits", Justification = "Async setup not supported in BenchmarkDotNet.See https://github.com/dotnet/BenchmarkDotNet/issues/2442.")]
+ public static void SetupCompilation()
+ {
+ List<(string Name, string Content)> sources = [];
+ for (int index = 0; index < Constants.NumberOfCodeFiles; index++)
+ {
+ string name = "TypeName" + index;
+ sources.Add((name, @$"
+using System;
+using Moq;
+
+public class SampleClass{index}
+{{
+
+ public int Calculate() => 0;
+}}
+
+internal class {name}
+{{
+ private void Test()
+ {{
+ new Mock().As();
+ }}
+}}
+"));
+ }
+
+ (BaselineCompilation, TestCompilation) =
+ BenchmarkCSharpCompilationCreator
+ .CreateAsync(sources.ToArray())
+ .GetAwaiter()
+ .GetResult();
+ }
+
+ [Benchmark]
+ public async Task Moq1300WithDiagnostics()
+ {
+ ImmutableArray diagnostics =
+ (await TestCompilation!
+ .GetAnalysisResultAsync(CancellationToken.None)
+ .ConfigureAwait(false))
+ .AssertValidAnalysisResult()
+ .GetAllDiagnostics();
+
+ if (diagnostics.Length != Constants.NumberOfCodeFiles)
+ {
+ throw new InvalidOperationException($"Expected '{Constants.NumberOfCodeFiles:N0}' analyzer diagnostics but found '{diagnostics.Length}'");
+ }
+ }
+
+ [Benchmark(Baseline = true)]
+ public async Task Moq1300Baseline()
+ {
+ ImmutableArray diagnostics =
+ (await BaselineCompilation!
+ .GetAnalysisResultAsync(CancellationToken.None)
+ .ConfigureAwait(false))
+ .AssertValidAnalysisResult()
+ .GetAllDiagnostics();
+
+ if (diagnostics.Length != 0)
+ {
+ throw new InvalidOperationException($"Expected no analyzer diagnostics but found '{diagnostics.Length}'");
+ }
+ }
+}
diff --git a/tests/Moq.Analyzers.Benchmarks/Program.cs b/tests/Moq.Analyzers.Benchmarks/Program.cs
new file mode 100644
index 00000000..fbd12647
--- /dev/null
+++ b/tests/Moq.Analyzers.Benchmarks/Program.cs
@@ -0,0 +1,19 @@
+using BenchmarkDotNet.Configs;
+using BenchmarkDotNet.Running;
+
+namespace Moq.Analyzers.Benchmarks;
+
+///
+/// Entrypoint for benchmarks.
+///
+public static class Program
+{
+ ///
+ /// Main entrypoint for benchmarks.
+ ///
+ /// Command line arguments.
+ public static void Main(string[] args)
+ => BenchmarkSwitcher
+ .FromAssembly(typeof(Program).Assembly)
+ .Run(args, DefaultConfig.Instance.WithOptions(ConfigOptions.DisableOptimizationsValidator)); // Needed because Microsoft.CodeAnalysis.Testing does not build with optimizations. See https://github.com/dotnet/roslyn-sdk/issues/1165.
+}
diff --git a/Source/Moq.Analyzers.Test/AsAcceptOnlyInterfaceAnalyzerTests.cs b/tests/Moq.Analyzers.Test/AsAcceptOnlyInterfaceAnalyzerTests.cs
similarity index 100%
rename from Source/Moq.Analyzers.Test/AsAcceptOnlyInterfaceAnalyzerTests.cs
rename to tests/Moq.Analyzers.Test/AsAcceptOnlyInterfaceAnalyzerTests.cs
diff --git a/Source/Moq.Analyzers.Test/CallbackSignatureShouldMatchMockedMethodCodeFixTests.cs b/tests/Moq.Analyzers.Test/CallbackSignatureShouldMatchMockedMethodCodeFixTests.cs
similarity index 97%
rename from Source/Moq.Analyzers.Test/CallbackSignatureShouldMatchMockedMethodCodeFixTests.cs
rename to tests/Moq.Analyzers.Test/CallbackSignatureShouldMatchMockedMethodCodeFixTests.cs
index af3280c0..23df15cc 100644
--- a/Source/Moq.Analyzers.Test/CallbackSignatureShouldMatchMockedMethodCodeFixTests.cs
+++ b/tests/Moq.Analyzers.Test/CallbackSignatureShouldMatchMockedMethodCodeFixTests.cs
@@ -4,6 +4,7 @@ namespace Moq.Analyzers.Test;
public class CallbackSignatureShouldMatchMockedMethodCodeFixTests
{
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "MA0051:Method is too long", Justification = "Contains test data")]
public static IEnumerable
\ No newline at end of file
diff --git a/Source/Moq.Analyzers.Test/NoConstructorArgumentsForInterfaceMockAnalyzerTests.cs b/tests/Moq.Analyzers.Test/NoConstructorArgumentsForInterfaceMockAnalyzerTests.cs
similarity index 100%
rename from Source/Moq.Analyzers.Test/NoConstructorArgumentsForInterfaceMockAnalyzerTests.cs
rename to tests/Moq.Analyzers.Test/NoConstructorArgumentsForInterfaceMockAnalyzerTests.cs
diff --git a/Source/Moq.Analyzers.Test/NoMethodsInPropertySetupAnalyzerTests.cs b/tests/Moq.Analyzers.Test/NoMethodsInPropertySetupAnalyzerTests.cs
similarity index 100%
rename from Source/Moq.Analyzers.Test/NoMethodsInPropertySetupAnalyzerTests.cs
rename to tests/Moq.Analyzers.Test/NoMethodsInPropertySetupAnalyzerTests.cs
diff --git a/Source/Moq.Analyzers.Test/NoSealedClassMocksAnalyzerTests.cs b/tests/Moq.Analyzers.Test/NoSealedClassMocksAnalyzerTests.cs
similarity index 100%
rename from Source/Moq.Analyzers.Test/NoSealedClassMocksAnalyzerTests.cs
rename to tests/Moq.Analyzers.Test/NoSealedClassMocksAnalyzerTests.cs
diff --git a/Source/Moq.Analyzers.Test/PackageTests.Baseline#contents.verified.txt b/tests/Moq.Analyzers.Test/PackageTests.Baseline#contents.verified.txt
similarity index 100%
rename from Source/Moq.Analyzers.Test/PackageTests.Baseline#contents.verified.txt
rename to tests/Moq.Analyzers.Test/PackageTests.Baseline#contents.verified.txt
diff --git a/Source/Moq.Analyzers.Test/PackageTests.Baseline#manifest.verified.nuspec b/tests/Moq.Analyzers.Test/PackageTests.Baseline#manifest.verified.nuspec
similarity index 100%
rename from Source/Moq.Analyzers.Test/PackageTests.Baseline#manifest.verified.nuspec
rename to tests/Moq.Analyzers.Test/PackageTests.Baseline#manifest.verified.nuspec
diff --git a/tests/Moq.Analyzers.Test/PackageTests.cs b/tests/Moq.Analyzers.Test/PackageTests.cs
new file mode 100644
index 00000000..8eae37bb
--- /dev/null
+++ b/tests/Moq.Analyzers.Test/PackageTests.cs
@@ -0,0 +1,18 @@
+using System.Reflection;
+
+namespace Moq.Analyzers.Test;
+
+public class PackageTests
+{
+ private static readonly FileInfo Package = new FileInfo(Assembly.GetExecutingAssembly().Location)
+ .Directory!
+ .GetFiles("Moq.Analyzers*.nupkg")
+ .OrderByDescending(fileInfo => fileInfo.LastWriteTimeUtc)
+ .First();
+
+ [Fact]
+ public Task Baseline()
+ {
+ return VerifyFile(Package).ScrubNuspec();
+ }
+}
diff --git a/Source/Moq.Analyzers.Test/SetupShouldBeUsedOnlyForOverridableMembersAnalyzerTests.cs b/tests/Moq.Analyzers.Test/SetupShouldBeUsedOnlyForOverridableMembersAnalyzerTests.cs
similarity index 100%
rename from Source/Moq.Analyzers.Test/SetupShouldBeUsedOnlyForOverridableMembersAnalyzerTests.cs
rename to tests/Moq.Analyzers.Test/SetupShouldBeUsedOnlyForOverridableMembersAnalyzerTests.cs
diff --git a/Source/Moq.Analyzers.Test/SetupShouldNotIncludeAsyncResultAnalyzerTests.cs b/tests/Moq.Analyzers.Test/SetupShouldNotIncludeAsyncResultAnalyzerTests.cs
similarity index 100%
rename from Source/Moq.Analyzers.Test/SetupShouldNotIncludeAsyncResultAnalyzerTests.cs
rename to tests/Moq.Analyzers.Test/SetupShouldNotIncludeAsyncResultAnalyzerTests.cs