From 04f5795933b385f992736119d9c8ad9ffe4c8054 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Mon, 17 Jun 2024 09:10:58 -0700 Subject: [PATCH 01/21] Update README.md to add leading and trailing pipes in table (#94) --- docs/rules/README.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) 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 | From c8d5951f1b10c1050532cfbe3cfcf2b609830c40 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Mon, 17 Jun 2024 09:20:49 -0700 Subject: [PATCH 02/21] Create dependabot.yml (#93) Enable Depndabot to scan GitHub actions and NuGet packages --- .github/dependabot.yml | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..951cd834 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,13 @@ +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" From 2a0c0e12b1cf658e8e4dfeff85f6078f45121b23 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Mon, 17 Jun 2024 09:30:13 -0700 Subject: [PATCH 03/21] Update main.yml to add codacy test coverage (#92) Updates to build to enable integration with Codacy - Rename "Publish coverage summary" to "Publish coverage summary to GitHub" when posting SummaryGithub.md - Add new step to upload Cobertura code coverage information to Codacy - Add Codacy coverage and grade badges to README.md --- .github/workflows/main.yml | 12 +++++++++++- README.md | 2 ++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 699f7e16..1e6e2be7 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -26,6 +26,9 @@ jobs: runs-on: ${{ matrix.os }} + env: + IS_COVERAGE_ALLOWED: ${{ secrets.CODACY_PROJECT_TOKEN != '' }} + steps: - uses: actions/checkout@v4 with: @@ -81,10 +84,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/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 From 7dc0f2a2d09a562b397c8fb34a58bbe4ef557240 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Mon, 17 Jun 2024 09:53:58 -0700 Subject: [PATCH 04/21] Add more analyzers and bump Meziantou.Analzer to latest version (#89) Several changes: 1. Add several new analyzers to check for code correctness and style - [Sonar](https://www.nuget.org/packages/SonarAnalyzer.CSharp) - [Multithreading](https://www.nuget.org/packages/Microsoft.VisualStudio.Threading.Analyzers) - [C# Guidelines](https://www.nuget.org/packages/CSharpGuidelinesAnalyzer) - [Exhaustive matching](https://www.nuget.org/packages/ExhaustiveMatching.Analyzer) 2. Added suppressions for some of the new rules as they'll need more extensive refactoring to satisfy (e.g., methods have multiple responsibilities, there's too much coupling, poor encapsulation, etc.). Tracked in #90 3. Bump Meziantou.Analyzer to 2.0.157 --- .editorconfig | 24 +++++++++++++- Source/Moq.Analyzers.Test/.editorconfig | 4 +++ ...ructorArgumentsShouldMatchAnalyzerTests.cs | 1 + .../Moq.Analyzers.Test.csproj | 1 - Source/Moq.Analyzers.Test/PackageTests.cs | 15 +++------ .../AsShouldBeUsedOnlyForInterfaceAnalyzer.cs | 1 + ...ignatureShouldMatchMockedMethodAnalyzer.cs | 7 ++-- ...SignatureShouldMatchMockedMethodCodeFix.cs | 13 ++++---- ...ConstructorArgumentsShouldMatchAnalyzer.cs | 15 +++++---- Source/Moq.Analyzers/Helpers.cs | 32 ++++++++++++------- Source/Moq.Analyzers/MoqAsMethodDescriptor.cs | 1 + .../Moq.Analyzers/MoqSetupMethodDescriptor.cs | 1 + ...ructorArgumentsForInterfaceMockAnalyzer.cs | 3 +- .../NoMethodsInPropertySetupAnalyzer.cs | 1 + .../NoSealedClassMocksAnalyzer.cs | 1 + ...BeUsedOnlyForOverridableMembersAnalyzer.cs | 1 + ...etupShouldNotIncludeAsyncResultAnalyzer.cs | 1 + build/targets/codeanalysis/CodeAnalysis.props | 13 ++++++++ build/targets/codeanalysis/Packages.props | 6 +++- build/targets/tests/Tests.props | 1 + 20 files changed, 101 insertions(+), 41 deletions(-) diff --git a/.editorconfig b/.editorconfig index e7d7b135..a403677d 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] @@ -381,7 +385,25 @@ 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 + +#### 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 \ No newline at end of file diff --git a/Source/Moq.Analyzers.Test/.editorconfig b/Source/Moq.Analyzers.Test/.editorconfig index 7f840f54..d344cf31 100644 --- a/Source/Moq.Analyzers.Test/.editorconfig +++ b/Source/Moq.Analyzers.Test/.editorconfig @@ -12,3 +12,7 @@ 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 +# Just about every test method is async, doesn't provide any real value and clustters up test window +dotnet_diagnostic.VSTHRD200.severity = none \ No newline at end of file diff --git a/Source/Moq.Analyzers.Test/ConstructorArgumentsShouldMatchAnalyzerTests.cs b/Source/Moq.Analyzers.Test/ConstructorArgumentsShouldMatchAnalyzerTests.cs index f7dcf990..4ce25e00 100644 --- a/Source/Moq.Analyzers.Test/ConstructorArgumentsShouldMatchAnalyzerTests.cs +++ b/Source/Moq.Analyzers.Test/ConstructorArgumentsShouldMatchAnalyzerTests.cs @@ -35,6 +35,7 @@ public static IEnumerable TestData() ["""new Mock>{|Moq1002:(42)|};"""], ["""new Mock>();"""], ["""new Mock>(MockBehavior.Default);"""], + // TODO: "I think this _should_ fail, but currently passes. Tracked by #55." // ["""new Mock();"""], ["""new Mock{|Moq1002:("42")|};"""], diff --git a/Source/Moq.Analyzers.Test/Moq.Analyzers.Test.csproj b/Source/Moq.Analyzers.Test/Moq.Analyzers.Test.csproj index a5f5f708..5fa9c288 100644 --- a/Source/Moq.Analyzers.Test/Moq.Analyzers.Test.csproj +++ b/Source/Moq.Analyzers.Test/Moq.Analyzers.Test.csproj @@ -23,7 +23,6 @@ - diff --git a/Source/Moq.Analyzers.Test/PackageTests.cs b/Source/Moq.Analyzers.Test/PackageTests.cs index d2347c95..8eae37bb 100644 --- a/Source/Moq.Analyzers.Test/PackageTests.cs +++ b/Source/Moq.Analyzers.Test/PackageTests.cs @@ -4,16 +4,11 @@ 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(); - } + private static readonly FileInfo Package = new FileInfo(Assembly.GetExecutingAssembly().Location) + .Directory! + .GetFiles("Moq.Analyzers*.nupkg") + .OrderByDescending(fileInfo => fileInfo.LastWriteTimeUtc) + .First(); [Fact] public Task Baseline() diff --git a/Source/Moq.Analyzers/AsShouldBeUsedOnlyForInterfaceAnalyzer.cs b/Source/Moq.Analyzers/AsShouldBeUsedOnlyForInterfaceAnalyzer.cs index 8ee18fb4..887c9a78 100644 --- a/Source/Moq.Analyzers/AsShouldBeUsedOnlyForInterfaceAnalyzer.cs +++ b/Source/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/Source/Moq.Analyzers/CallbackSignatureShouldMatchMockedMethodAnalyzer.cs index e9bc2d96..1b21a798 100644 --- a/Source/Moq.Analyzers/CallbackSignatureShouldMatchMockedMethodAnalyzer.cs +++ b/Source/Moq.Analyzers/CallbackSignatureShouldMatchMockedMethodAnalyzer.cs @@ -33,6 +33,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) { InvocationExpressionSyntax? callbackOrReturnsInvocation = (InvocationExpressionSyntax)context.Node; @@ -66,10 +67,10 @@ private static void Analyze(SyntaxNodeAnalysisContext context) } 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); + TypeInfo mockedMethodArgumentType = context.SemanticModel.GetTypeInfo(mockedMethodArguments[argumentIndex].Expression, context.CancellationToken); + TypeInfo lambdaParameterType = context.SemanticModel.GetTypeInfo(lambdaParameters[argumentIndex].Type, context.CancellationToken); string? mockedMethodTypeName = mockedMethodArgumentType.ConvertedType?.ToString(); string? lambdaParameterTypeName = lambdaParameterType.ConvertedType?.ToString(); if (!string.Equals(mockedMethodTypeName, lambdaParameterTypeName, StringComparison.Ordinal)) diff --git a/Source/Moq.Analyzers/CallbackSignatureShouldMatchMockedMethodCodeFix.cs b/Source/Moq.Analyzers/CallbackSignatureShouldMatchMockedMethodCodeFix.cs index 87f403a6..29ea2956 100644 --- a/Source/Moq.Analyzers/CallbackSignatureShouldMatchMockedMethodCodeFix.cs +++ b/Source/Moq.Analyzers/CallbackSignatureShouldMatchMockedMethodCodeFix.cs @@ -48,12 +48,13 @@ 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); @@ -80,10 +81,10 @@ private async Task FixCallbackSignatureAsync(SyntaxNode root, Document } ParameterListSyntax? newParameters = SyntaxFactory.ParameterList(SyntaxFactory.SeparatedList(matchingMockedMethods[0].Parameters.Select( - p => + 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); diff --git a/Source/Moq.Analyzers/ConstructorArgumentsShouldMatchAnalyzer.cs b/Source/Moq.Analyzers/ConstructorArgumentsShouldMatchAnalyzer.cs index b08d5c99..65695cae 100644 --- a/Source/Moq.Analyzers/ConstructorArgumentsShouldMatchAnalyzer.cs +++ b/Source/Moq.Analyzers/ConstructorArgumentsShouldMatchAnalyzer.cs @@ -35,6 +35,7 @@ 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; @@ -48,7 +49,7 @@ private static void Analyze(SyntaxNodeAnalysisContext context) IMethodSymbol? constructorSymbol = GetConstructorSymbol(context, objectCreation); // 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; @@ -60,14 +61,14 @@ private static void Analyze(SyntaxNodeAnalysisContext context) 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; // Skip first argument if it is not vararg - typically it is MockingBehavior argument - ArgumentSyntax[]? constructorArguments = objectCreation.ArgumentList?.Arguments.Skip(varArgsConstructorParameterIdx == 0 ? 0 : 1).ToArray(); + ArgumentSyntax[]? constructorArguments = objectCreation.ArgumentList?.Arguments.Skip(varArgsConstructorParameterIndex == 0 ? 0 : 1).ToArray(); if (!mockedTypeSymbol.IsAbstract) { @@ -93,9 +94,9 @@ private static void Analyze(SyntaxNodeAnalysisContext context) .ToArray()!; // Check all constructors of the abstract type - for (int i = 0; i < mockedTypeSymbol.Constructors.Length; i++) + for (int constructorIndex = 0; constructorIndex < mockedTypeSymbol.Constructors.Length; constructorIndex++) { - IMethodSymbol constructor = mockedTypeSymbol.Constructors[i]; + IMethodSymbol constructor = mockedTypeSymbol.Constructors[constructorIndex]; if (AreParametersMatching(constructor.Parameters, argumentTypes)) { return; // Found a matching constructor @@ -130,9 +131,9 @@ private static bool AreParametersMatching(ImmutableArray const } // 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(argumentTypes2[constructorParameterIndex], SymbolEqualityComparer.IncludeNullability)) { return false; } diff --git a/Source/Moq.Analyzers/Helpers.cs b/Source/Moq.Analyzers/Helpers.cs index ab0165c1..7b04bfcd 100644 --- a/Source/Moq.Analyzers/Helpers.cs +++ b/Source/Moq.Analyzers/Helpers.cs @@ -2,6 +2,7 @@ namespace Moq.Analyzers; +[System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "AV1708:Type name contains term that should be avoided", Justification = "Tracked in https://github.com/rjmurillo/moq.analyzers/issues/90")] internal static class Helpers { private static readonly MoqMethodDescriptorBase MoqSetupMethodDescriptor = new MoqSetupMethodDescriptor(); @@ -11,6 +12,7 @@ internal static bool IsMoqSetupMethod(SemanticModel semanticModel, MemberAccessE 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")] internal static bool IsCallbackOrReturnInvocation(SemanticModel semanticModel, InvocationExpressionSyntax callbackOrReturnsInvocation) { MemberAccessExpressionSyntax? callbackOrReturnsMethod = callbackOrReturnsInvocation.Expression as MemberAccessExpressionSyntax; @@ -68,21 +70,29 @@ internal static IEnumerable GetAllMatchingMockedMethodSymbolsFrom return GetAllMatchingSymbols(semanticModel, 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 IEnumerable GetAllMatchingSymbols(SemanticModel semanticModel, ExpressionSyntax? expression) where T : class { - List? matchingSymbols = new List(); - if (expression != null) + 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 Enumerable.Empty(); + } + + List matchingSymbols = new(); + + 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()); + } + else + { + throw new NotSupportedException("Symbol not supported."); } return matchingSymbols; diff --git a/Source/Moq.Analyzers/MoqAsMethodDescriptor.cs b/Source/Moq.Analyzers/MoqAsMethodDescriptor.cs index afe70d77..46265a13 100644 --- a/Source/Moq.Analyzers/MoqAsMethodDescriptor.cs +++ b/Source/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/MoqSetupMethodDescriptor.cs b/Source/Moq.Analyzers/MoqSetupMethodDescriptor.cs index fe42697b..9ca4f1ad 100644 --- a/Source/Moq.Analyzers/MoqSetupMethodDescriptor.cs +++ b/Source/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/NoConstructorArgumentsForInterfaceMockAnalyzer.cs b/Source/Moq.Analyzers/NoConstructorArgumentsForInterfaceMockAnalyzer.cs index 3a037f89..e0dd7528 100644 --- a/Source/Moq.Analyzers/NoConstructorArgumentsForInterfaceMockAnalyzer.cs +++ b/Source/Moq.Analyzers/NoConstructorArgumentsForInterfaceMockAnalyzer.cs @@ -35,6 +35,7 @@ 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; @@ -64,7 +65,7 @@ private static void Analyze(SyntaxNodeAnalysisContext context) } if (constructorSymbol.Parameters == null || constructorSymbol.Parameters.Length == 0) return; - if (!constructorSymbol.Parameters.Any(x => x.IsParams)) return; + if (!constructorSymbol.Parameters.Any(parameterSymbol => parameterSymbol.IsParams)) return; // Find mocked type SeparatedSyntaxList typeArguments = genericName.TypeArgumentList.Arguments; diff --git a/Source/Moq.Analyzers/NoMethodsInPropertySetupAnalyzer.cs b/Source/Moq.Analyzers/NoMethodsInPropertySetupAnalyzer.cs index cd5a59ec..71b012cf 100644 --- a/Source/Moq.Analyzers/NoMethodsInPropertySetupAnalyzer.cs +++ b/Source/Moq.Analyzers/NoMethodsInPropertySetupAnalyzer.cs @@ -33,6 +33,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) { InvocationExpressionSyntax? setupGetOrSetInvocation = (InvocationExpressionSyntax)context.Node; diff --git a/Source/Moq.Analyzers/NoSealedClassMocksAnalyzer.cs b/Source/Moq.Analyzers/NoSealedClassMocksAnalyzer.cs index 58b31879..c30338ce 100644 --- a/Source/Moq.Analyzers/NoSealedClassMocksAnalyzer.cs +++ b/Source/Moq.Analyzers/NoSealedClassMocksAnalyzer.cs @@ -33,6 +33,7 @@ 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; diff --git a/Source/Moq.Analyzers/SetupShouldBeUsedOnlyForOverridableMembersAnalyzer.cs b/Source/Moq.Analyzers/SetupShouldBeUsedOnlyForOverridableMembersAnalyzer.cs index 4145492f..b8a3638c 100644 --- a/Source/Moq.Analyzers/SetupShouldBeUsedOnlyForOverridableMembersAnalyzer.cs +++ b/Source/Moq.Analyzers/SetupShouldBeUsedOnlyForOverridableMembersAnalyzer.cs @@ -30,6 +30,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) { InvocationExpressionSyntax? setupInvocation = (InvocationExpressionSyntax)context.Node; diff --git a/Source/Moq.Analyzers/SetupShouldNotIncludeAsyncResultAnalyzer.cs b/Source/Moq.Analyzers/SetupShouldNotIncludeAsyncResultAnalyzer.cs index db703d7a..b44ea243 100644 --- a/Source/Moq.Analyzers/SetupShouldNotIncludeAsyncResultAnalyzer.cs +++ b/Source/Moq.Analyzers/SetupShouldNotIncludeAsyncResultAnalyzer.cs @@ -30,6 +30,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) { InvocationExpressionSyntax? setupInvocation = (InvocationExpressionSyntax)context.Node; diff --git a/build/targets/codeanalysis/CodeAnalysis.props b/build/targets/codeanalysis/CodeAnalysis.props index d49d75d9..34eb6a65 100644 --- a/build/targets/codeanalysis/CodeAnalysis.props +++ b/build/targets/codeanalysis/CodeAnalysis.props @@ -25,5 +25,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..65c09fc6 100644 --- a/build/targets/codeanalysis/Packages.props +++ b/build/targets/codeanalysis/Packages.props @@ -1,8 +1,12 @@ - + + + + + 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 + From a6bad9db7506c4ed2bc376b54d9dc068dc28609a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Jun 2024 10:22:57 -0700 Subject: [PATCH 05/21] Bump Verify.Xunit from 25.0.1 to 25.0.3 (#100) Bumps [Verify.Xunit](https://github.com/VerifyTests/Verify) from 25.0.1 to 25.0.3.
Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=Verify.Xunit&package-manager=nuget&previous-version=25.0.1&new-version=25.0.3)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build/targets/tests/Packages.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/targets/tests/Packages.props b/build/targets/tests/Packages.props index fc5e9f1d..28106b1a 100644 --- a/build/targets/tests/Packages.props +++ b/build/targets/tests/Packages.props @@ -5,7 +5,7 @@ - + From 9c4dde830ea9828e1e16ce3af26e6c05f2183ad6 Mon Sep 17 00:00:00 2001 From: Matt Kotsenas Date: Mon, 17 Jun 2024 11:15:49 -0700 Subject: [PATCH 06/21] Remove Microsoft.CodeAnalysis.CSharp.Analyzer.Testing (#102) Remove `Microsoft.CodeAnalysis.CSharp.Analyzer.Testing` because it is duplicative with `Microsoft.CodeAnalysis.CSharp.CodeFix.Testing`. This is common when building analyzers that contain code fixes. From a testing perspective, an analyzer test is just a code fix test with an "empty" or "null pattern" code fixer. This also unblocks #97, as the two packages otherwise need upgrading together. However, cleaning up our dependencies is preferred to creating a group in dependabot. --- Source/Moq.Analyzers.Test/Moq.Analyzers.Test.csproj | 1 - build/targets/tests/Packages.props | 1 - 2 files changed, 2 deletions(-) diff --git a/Source/Moq.Analyzers.Test/Moq.Analyzers.Test.csproj b/Source/Moq.Analyzers.Test/Moq.Analyzers.Test.csproj index 5fa9c288..92b40846 100644 --- a/Source/Moq.Analyzers.Test/Moq.Analyzers.Test.csproj +++ b/Source/Moq.Analyzers.Test/Moq.Analyzers.Test.csproj @@ -19,7 +19,6 @@ runtime; build; native; contentfiles; analyzers; buildtransitive all
- diff --git a/build/targets/tests/Packages.props b/build/targets/tests/Packages.props index 28106b1a..dd4347b7 100644 --- a/build/targets/tests/Packages.props +++ b/build/targets/tests/Packages.props @@ -1,7 +1,6 @@ - From 01f5e8553ae143390da365421bdba651b565a4ff Mon Sep 17 00:00:00 2001 From: Matt Kotsenas Date: Mon, 17 Jun 2024 11:45:59 -0700 Subject: [PATCH 07/21] Dependabot ignore packages that impact customer compatibility (#101) Add ignores in `dependabot.yml` for the few packages that can impact compatibility for our users. --- .github/dependabot.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 951cd834..db25cda6 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -11,3 +11,13 @@ updates: 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" From 4f21ef0bc1bf0da37cd0b4a66784fd4eb9ce7416 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Jun 2024 18:56:27 +0000 Subject: [PATCH 08/21] Bump Microsoft.CodeAnalysis.CSharp.CodeFix.Testing from 1.1.2-beta1.24273.1 to 1.1.2-beta1.24314.1 (#103) Bumps [Microsoft.CodeAnalysis.CSharp.CodeFix.Testing](https://github.com/dotnet/roslyn-sdk) from 1.1.2-beta1.24273.1 to 1.1.2-beta1.24314.1.
Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=Microsoft.CodeAnalysis.CSharp.CodeFix.Testing&package-manager=nuget&previous-version=1.1.2-beta1.24273.1&new-version=1.1.2-beta1.24314.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build/targets/tests/Packages.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/targets/tests/Packages.props b/build/targets/tests/Packages.props index dd4347b7..e5c094e6 100644 --- a/build/targets/tests/Packages.props +++ b/build/targets/tests/Packages.props @@ -1,7 +1,7 @@ - + From a2a8bfc3b968da06e0548dc6cb6725af6ffa838b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Jun 2024 14:57:17 -0700 Subject: [PATCH 09/21] Bump Nerdbank.GitVersioning from 3.6.133 to 3.6.139 (#99) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [Nerdbank.GitVersioning](https://github.com/dotnet/Nerdbank.GitVersioning) from 3.6.133 to 3.6.139.
Release notes

Sourced from Nerdbank.GitVersioning's releases.

v3.6.139

What's Changed

New Contributors

Full Changelog: https://github.com/dotnet/Nerdbank.GitVersioning/compare/v3.6.133...v3.6.139

Commits
  • a9e8765 Merge pull request #1024 from Numpsy/fs_assenbly_info
  • e1cc3ec Change F# AssemblyInfo generation to always include a do() after the version ...
  • 9ca060e Merge pull request #994 from dotnet/fix992
  • da36fb9 Fix build.ps1 script
  • da15c4f Merge pull request #982 from dotnet/fix175
  • 4ea125e Fix WPF incremental build
  • See full diff in compare view

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=Nerdbank.GitVersioning&package-manager=nuget&previous-version=3.6.133&new-version=3.6.139)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
--------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Matt Kotsenas --- .config/dotnet-tools.json | 2 +- Directory.Packages.props | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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/Directory.Packages.props b/Directory.Packages.props index 672bc235..847853e9 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -21,6 +21,6 @@
- +
\ No newline at end of file From e239295b623bd8edaebbfa7ba4a5674339b152f6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Jun 2024 14:58:53 -0700 Subject: [PATCH 10/21] Bump Meziantou.Analyzer from 2.0.155 to 2.0.158 (#96) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [Meziantou.Analyzer](https://github.com/meziantou/Meziantou.Analyzer) from 2.0.155 to 2.0.158.
Release notes

Sourced from Meziantou.Analyzer's releases.

2.0.158

NuGet package: https://www.nuget.org/packages/Meziantou.Analyzer/2.0.158

Full Changelog: https://github.com/meziantou/Meziantou.Analyzer/compare/2.0.157...2.0.158

2.0.157

NuGet package: https://www.nuget.org/packages/Meziantou.Analyzer/2.0.157

What's Changed

Full Changelog: https://github.com/meziantou/Meziantou.Analyzer/compare/2.0.156...2.0.157

2.0.156

NuGet package: https://www.nuget.org/packages/Meziantou.Analyzer/2.0.156

Full Changelog: https://github.com/meziantou/Meziantou.Analyzer/compare/2.0.155...2.0.156

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=Meziantou.Analyzer&package-manager=nuget&previous-version=2.0.155&new-version=2.0.158)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build/targets/codeanalysis/Packages.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/targets/codeanalysis/Packages.props b/build/targets/codeanalysis/Packages.props index 65c09fc6..a2a0d873 100644 --- a/build/targets/codeanalysis/Packages.props +++ b/build/targets/codeanalysis/Packages.props @@ -1,6 +1,6 @@ - + From cff3035a4ab99d15bdf34869ee935e54aca2f529 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Wed, 19 Jun 2024 08:47:16 -0700 Subject: [PATCH 11/21] Enable warnings as errors (#91) - Updated `.editorconfig` to set policy on some duplicate warnings for TODO. Borrowed from #89 to make merging easier - Added suppressions for analyzers that seem to be misfiring. Tracked in #90 - Updated cases where nullability was incorrect (something could be passed as null but wasn't, something was marked as nullable but wasn't). Where possible added asserts so we can catch cases when they arise. - Removed `` from `Moq.Analyzers.csproj` - Added `` and `MSBuildTreatWarningsAsErrors` elements to `CodeAnalysis.props` --- .editorconfig | 4 ++- ...tureShouldMatchMockedMethodCodeFixTests.cs | 1 + .../Moq.Analyzers.Test.csproj | 1 - ...ignatureShouldMatchMockedMethodAnalyzer.cs | 15 +++++++++- ...SignatureShouldMatchMockedMethodCodeFix.cs | 6 ++-- ...ConstructorArgumentsShouldMatchAnalyzer.cs | 19 +++++++------ Source/Moq.Analyzers/Helpers.cs | 28 +++++++++++-------- ...ructorArgumentsForInterfaceMockAnalyzer.cs | 10 +++++-- build/targets/codeanalysis/CodeAnalysis.props | 2 ++ 9 files changed, 56 insertions(+), 30 deletions(-) diff --git a/.editorconfig b/.editorconfig index a403677d..012d9871 100644 --- a/.editorconfig +++ b/.editorconfig @@ -406,4 +406,6 @@ dotnet_diagnostic.CA2016.severity = error # S1135: Track uses of "TODO" tags dotnet_diagnostic.S1135.severity = suggestion # AV2318: Work-tracking TODO comment should be removed -dotnet_diagnostic.AV2318.severity = none \ No newline at end of file +dotnet_diagnostic.AV2318.severity = none +# MA0026: Fix TODO comment +dotnet_diagnostic.MA0026.severity = none \ No newline at end of file diff --git a/Source/Moq.Analyzers.Test/CallbackSignatureShouldMatchMockedMethodCodeFixTests.cs b/Source/Moq.Analyzers.Test/CallbackSignatureShouldMatchMockedMethodCodeFixTests.cs index af3280c0..23df15cc 100644 --- a/Source/Moq.Analyzers.Test/CallbackSignatureShouldMatchMockedMethodCodeFixTests.cs +++ b/Source/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 TestData() { return new object[][] diff --git a/Source/Moq.Analyzers.Test/Moq.Analyzers.Test.csproj b/Source/Moq.Analyzers.Test/Moq.Analyzers.Test.csproj index 92b40846..10e52888 100644 --- a/Source/Moq.Analyzers.Test/Moq.Analyzers.Test.csproj +++ b/Source/Moq.Analyzers.Test/Moq.Analyzers.Test.csproj @@ -7,7 +7,6 @@ - 1701;1702;SA1600;SA1402 diff --git a/Source/Moq.Analyzers/CallbackSignatureShouldMatchMockedMethodAnalyzer.cs b/Source/Moq.Analyzers/CallbackSignatureShouldMatchMockedMethodAnalyzer.cs index 1b21a798..35e486e7 100644 --- a/Source/Moq.Analyzers/CallbackSignatureShouldMatchMockedMethodAnalyzer.cs +++ b/Source/Moq.Analyzers/CallbackSignatureShouldMatchMockedMethodAnalyzer.cs @@ -1,3 +1,5 @@ +using System.Diagnostics; + namespace Moq.Analyzers; /// @@ -69,10 +71,21 @@ private static void Analyze(SyntaxNodeAnalysisContext context) { for (int argumentIndex = 0; argumentIndex < mockedMethodArguments.Count; argumentIndex++) { + TypeSyntax? lambdaParameterTypeSyntax = lambdaParameters[argumentIndex].Type; + Debug.Assert(lambdaParameterTypeSyntax != null, nameof(lambdaParameterTypeSyntax) + " != null"); + + // TODO: Don't know if continue or break is the right thing to do here +#pragma warning disable S2589 // Boolean expressions should not be gratuitous + if (lambdaParameterTypeSyntax is null) continue; +#pragma warning restore S2589 // Boolean expressions should not be gratuitous + + TypeInfo lambdaParameterType = context.SemanticModel.GetTypeInfo(lambdaParameterTypeSyntax, context.CancellationToken); + TypeInfo mockedMethodArgumentType = context.SemanticModel.GetTypeInfo(mockedMethodArguments[argumentIndex].Expression, context.CancellationToken); - TypeInfo lambdaParameterType = context.SemanticModel.GetTypeInfo(lambdaParameters[argumentIndex].Type, 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()); diff --git a/Source/Moq.Analyzers/CallbackSignatureShouldMatchMockedMethodCodeFix.cs b/Source/Moq.Analyzers/CallbackSignatureShouldMatchMockedMethodCodeFix.cs index 29ea2956..8f29e2e2 100644 --- a/Source/Moq.Analyzers/CallbackSignatureShouldMatchMockedMethodCodeFix.cs +++ b/Source/Moq.Analyzers/CallbackSignatureShouldMatchMockedMethodCodeFix.cs @@ -59,8 +59,6 @@ private async Task FixCallbackSignatureAsync(SyntaxNode root, Document { SemanticModel? semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); - Debug.Assert(semanticModel != null, nameof(semanticModel) + " != null"); - if (semanticModel == null) { return document; @@ -73,9 +71,9 @@ private async Task FixCallbackSignatureAsync(SyntaxNode root, Document InvocationExpressionSyntax? setupMethodInvocation = Helpers.FindSetupMethodFromCallbackInvocation(semanticModel, callbackInvocation, cancellationToken); Debug.Assert(setupMethodInvocation != null, nameof(setupMethodInvocation) + " != null"); - IMethodSymbol[]? matchingMockedMethods = Helpers.GetAllMatchingMockedMethodSymbolsFromSetupMethodInvocation(semanticModel, setupMethodInvocation).ToArray(); + IMethodSymbol[] matchingMockedMethods = Helpers.GetAllMatchingMockedMethodSymbolsFromSetupMethodInvocation(semanticModel, setupMethodInvocation).ToArray(); - if (matchingMockedMethods.Length != 1 || oldParameters == null) + if (matchingMockedMethods.Length != 1) { return document; } diff --git a/Source/Moq.Analyzers/ConstructorArgumentsShouldMatchAnalyzer.cs b/Source/Moq.Analyzers/ConstructorArgumentsShouldMatchAnalyzer.cs index 65695cae..d33cdb6f 100644 --- a/Source/Moq.Analyzers/ConstructorArgumentsShouldMatchAnalyzer.cs +++ b/Source/Moq.Analyzers/ConstructorArgumentsShouldMatchAnalyzer.cs @@ -36,6 +36,7 @@ public override void Initialize(AnalysisContext context) } [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; @@ -48,18 +49,17 @@ private static void Analyze(SyntaxNodeAnalysisContext context) // Full check that we are calling new Mock() IMethodSymbol? constructorSymbol = GetConstructorSymbol(context, objectCreation); - // Vararg parameter is the one that takes all arguments for mocked class constructor - IParameterSymbol? varArgsConstructorParameter = constructorSymbol?.Parameters.FirstOrDefault(parameterSymbol => parameterSymbol.IsParams); + Debug.Assert(constructorSymbol != null, nameof(constructorSymbol) + " != null"); - // Vararg parameter are not used, so there are no arguments for mocked class constructor - if (varArgsConstructorParameter == null) return; +#pragma warning disable S2589 // Boolean expressions should not be gratuitous + if (constructorSymbol is null) return; +#pragma warning restore S2589 // Boolean expressions should not be gratuitous - Debug.Assert(constructorSymbol != null, nameof(constructorSymbol) + " != null"); + // Vararg parameter is the one that takes all arguments for mocked class constructor + IParameterSymbol? varArgsConstructorParameter = constructorSymbol.Parameters.FirstOrDefault(parameterSymbol => parameterSymbol.IsParams); - if (constructorSymbol == null) - { - return; - } + // Vararg parameter are not used, so there are no arguments for mocked class constructor + if (varArgsConstructorParameter == null) return; int varArgsConstructorParameterIndex = constructorSymbol.Parameters.IndexOf(varArgsConstructorParameter); @@ -167,6 +167,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(), diff --git a/Source/Moq.Analyzers/Helpers.cs b/Source/Moq.Analyzers/Helpers.cs index 7b04bfcd..f3f024db 100644 --- a/Source/Moq.Analyzers/Helpers.cs +++ b/Source/Moq.Analyzers/Helpers.cs @@ -1,4 +1,5 @@ using System.Diagnostics; +using System.Linq.Expressions; namespace Moq.Analyzers; @@ -17,14 +18,12 @@ internal static bool IsCallbackOrReturnInvocation(SemanticModel semanticModel, I { MemberAccessExpressionSyntax? callbackOrReturnsMethod = callbackOrReturnsInvocation.Expression as MemberAccessExpressionSyntax; - Debug.Assert(callbackOrReturnsMethod != null, nameof(callbackOrReturnsMethod) + " != null"); - if (callbackOrReturnsMethod == null) { return false; } - string? methodName = callbackOrReturnsMethod.Name.ToString(); + string methodName = callbackOrReturnsMethod.Name.ToString(); // First fast check before walking semantic model if (!string.Equals(methodName, "Callback", StringComparison.Ordinal) @@ -67,24 +66,29 @@ internal static IEnumerable GetAllMatchingMockedMethodSymbolsFrom LambdaExpressionSyntax? setupLambdaArgument = setupMethodInvocation?.ArgumentList.Arguments[0].Expression as LambdaExpressionSyntax; InvocationExpressionSyntax? mockedMethodInvocation = setupLambdaArgument?.Body as InvocationExpressionSyntax; - return GetAllMatchingSymbols(semanticModel, mockedMethodInvocation); + return mockedMethodInvocation == null + ? [] + : GetAllMatchingSymbols(semanticModel, 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 IEnumerable GetAllMatchingSymbols(SemanticModel semanticModel, ExpressionSyntax? expression) + internal static IEnumerable GetAllMatchingSymbols(SemanticModel semanticModel, ExpressionSyntax expression) where T : class { - if (expression == null) - { - return Enumerable.Empty(); - } - List matchingSymbols = new(); SymbolInfo symbolInfo = semanticModel.GetSymbolInfo(expression); if (symbolInfo is { CandidateReason: CandidateReason.None, Symbol: T }) { - matchingSymbols.Add(symbolInfo.Symbol as 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 } else if (symbolInfo.CandidateReason == CandidateReason.OverloadResolutionFailure) { @@ -92,7 +96,7 @@ internal static IEnumerable GetAllMatchingSymbols(SemanticModel semanticMo } else { - throw new NotSupportedException("Symbol not supported."); + return matchingSymbols; } return matchingSymbols; diff --git a/Source/Moq.Analyzers/NoConstructorArgumentsForInterfaceMockAnalyzer.cs b/Source/Moq.Analyzers/NoConstructorArgumentsForInterfaceMockAnalyzer.cs index e0dd7528..32d2bfaa 100644 --- a/Source/Moq.Analyzers/NoConstructorArgumentsForInterfaceMockAnalyzer.cs +++ b/Source/Moq.Analyzers/NoConstructorArgumentsForInterfaceMockAnalyzer.cs @@ -54,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(), @@ -64,7 +70,7 @@ private static void Analyze(SyntaxNodeAnalysisContext context) return; } - if (constructorSymbol.Parameters == null || constructorSymbol.Parameters.Length == 0) return; + if (constructorSymbol.Parameters.Length == 0) return; if (!constructorSymbol.Parameters.Any(parameterSymbol => parameterSymbol.IsParams)) return; // Find mocked type diff --git a/build/targets/codeanalysis/CodeAnalysis.props b/build/targets/codeanalysis/CodeAnalysis.props index 34eb6a65..c095d019 100644 --- a/build/targets/codeanalysis/CodeAnalysis.props +++ b/build/targets/codeanalysis/CodeAnalysis.props @@ -5,6 +5,8 @@ preview 9999 true + true + true From 8f48c921a4b0b843724ca4350cbc57fde4a8482e Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Wed, 19 Jun 2024 10:59:47 -0700 Subject: [PATCH 12/21] Update main.yml to add merge_group (#105) --- .github/workflows/main.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 1e6e2be7..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 From 7fc2de5fbfec1b59f3bd529a766d9f9930f9bc03 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Fri, 21 Jun 2024 09:08:13 -0700 Subject: [PATCH 13/21] Add auto labelers for Issues and PRs (#106) Add automatic labeling for issues and PRs to cut down on administrative work. Will allow for more consistent reporting, especially when generating patch notes from PRs. --- .github/labeler.yml | 17 ++++++++++ .github/workflows/label-issues.yml | 50 ++++++++++++++++++++++++++++++ .github/workflows/label-pr.yml | 21 +++++++++++++ 3 files changed, 88 insertions(+) create mode 100644 .github/labeler.yml create mode 100644 .github/workflows/label-issues.yml create mode 100644 .github/workflows/label-pr.yml diff --git a/.github/labeler.yml b/.github/labeler.yml new file mode 100644 index 00000000..de9627c3 --- /dev/null +++ b/.github/labeler.yml @@ -0,0 +1,17 @@ +# Add 'build' label to any change in the 'build' directory +build: + - build/**/* + +# Add 'dependencies' label to any change in one of the packages files +dependencies: + - build/**/Packages.props + - Directory.Packages.props + +# Add '.NET' label to any change to a '.cs' file under Moq.Analyzers +.NET + - 'Source/Moq.Analyzers/**/*.cs' + +# Add 'documentation' label to any change within the 'docs' directory or any '.md' file +documentation: + - docs/**/* + - '**/*.md' \ No newline at end of file diff --git a/.github/workflows/label-issues.yml b/.github/workflows/label-issues.yml new file mode 100644 index 00000000..c5529ec6 --- /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@v6 + 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..b4823847 --- /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@v4 + with: + repo-token: "${{ secrets.GH_ACTIONS_PR_WRITE }}" \ No newline at end of file From 926c048a0625646991049dd54cfba1aaa1141b2a Mon Sep 17 00:00:00 2001 From: Matt Kotsenas Date: Fri, 21 Jun 2024 13:00:22 -0400 Subject: [PATCH 14/21] Move code under src/ and tests/ directories (#107) I tried to avoid making this change, but the upcoming Benchmarks PR needs to share an .editorconfig with the unit tests project, so reorganizing the source so that: 1. We follow the common pattern of `src/` and `tests/` 2. Test projects share a common ancestor --- Moq.Analyzers.sln | 4 ++-- build/targets/codeanalysis/CodeAnalysis.props | 2 +- {Source => build/targets/codeanalysis}/stylecop.json | 0 {Source => src}/Moq.Analyzers/AnalyzerReleases.Shipped.md | 0 {Source => src}/Moq.Analyzers/AnalyzerReleases.Unshipped.md | 0 .../Moq.Analyzers/AsShouldBeUsedOnlyForInterfaceAnalyzer.cs | 0 .../CallbackSignatureShouldMatchMockedMethodAnalyzer.cs | 0 .../CallbackSignatureShouldMatchMockedMethodCodeFix.cs | 0 .../Moq.Analyzers/ConstructorArgumentsShouldMatchAnalyzer.cs | 0 {Source => src}/Moq.Analyzers/DiagnosticCategory.cs | 0 .../Moq.Analyzers}/GlobalSuppressions.cs | 0 {Source => src}/Moq.Analyzers/GlobalUsings.cs | 0 {Source => src}/Moq.Analyzers/Helpers.cs | 0 {Source => src}/Moq.Analyzers/Moq.Analyzers.csproj | 0 {Source => src}/Moq.Analyzers/MoqAsMethodDescriptor.cs | 0 {Source => src}/Moq.Analyzers/MoqMethodDescriptorBase.cs | 0 {Source => src}/Moq.Analyzers/MoqSetupMethodDescriptor.cs | 0 .../NoConstructorArgumentsForInterfaceMockAnalyzer.cs | 0 .../Moq.Analyzers/NoMethodsInPropertySetupAnalyzer.cs | 0 {Source => src}/Moq.Analyzers/NoSealedClassMocksAnalyzer.cs | 0 {Source => src}/Moq.Analyzers/Resources.Designer.cs | 0 {Source => src}/Moq.Analyzers/Resources.resx | 0 .../SetupShouldBeUsedOnlyForOverridableMembersAnalyzer.cs | 0 .../Moq.Analyzers/SetupShouldNotIncludeAsyncResultAnalyzer.cs | 0 {Source => src}/Moq.Analyzers/SyntaxExtensions.cs | 0 {Source => src}/Moq.Analyzers/tools/install.ps1 | 0 {Source => src}/Moq.Analyzers/tools/uninstall.ps1 | 0 {Source => tests}/Moq.Analyzers.Test/.editorconfig | 0 .../Moq.Analyzers.Test/AsAcceptOnlyInterfaceAnalyzerTests.cs | 0 .../CallbackSignatureShouldMatchMockedMethodCodeFixTests.cs | 0 .../ConstructorArgumentsShouldMatchAnalyzerTests.cs | 0 .../Moq.Analyzers.Test}/GlobalSuppressions.cs | 0 {Source => tests}/Moq.Analyzers.Test/GlobalUsings.cs | 0 .../Moq.Analyzers.Test/Helpers/AnalyzerVerifier.cs | 0 .../Moq.Analyzers.Test/Helpers/CodeFixVerifier.cs | 0 .../Moq.Analyzers.Test/Helpers/ReferenceAssemblyCatalog.cs | 0 {Source => tests}/Moq.Analyzers.Test/Helpers/Test.cs | 0 .../Moq.Analyzers.Test/Helpers/TestDataExtensions.cs | 0 {Source => tests}/Moq.Analyzers.Test/ModuleInitializer.cs | 0 .../Moq.Analyzers.Test/Moq.Analyzers.Test.csproj | 2 +- .../NoConstructorArgumentsForInterfaceMockAnalyzerTests.cs | 0 .../NoMethodsInPropertySetupAnalyzerTests.cs | 0 .../Moq.Analyzers.Test/NoSealedClassMocksAnalyzerTests.cs | 0 .../PackageTests.Baseline#contents.verified.txt | 0 .../PackageTests.Baseline#manifest.verified.nuspec | 0 {Source => tests}/Moq.Analyzers.Test/PackageTests.cs | 0 ...SetupShouldBeUsedOnlyForOverridableMembersAnalyzerTests.cs | 0 .../SetupShouldNotIncludeAsyncResultAnalyzerTests.cs | 0 48 files changed, 4 insertions(+), 4 deletions(-) rename {Source => build/targets/codeanalysis}/stylecop.json (100%) rename {Source => src}/Moq.Analyzers/AnalyzerReleases.Shipped.md (100%) rename {Source => src}/Moq.Analyzers/AnalyzerReleases.Unshipped.md (100%) rename {Source => src}/Moq.Analyzers/AsShouldBeUsedOnlyForInterfaceAnalyzer.cs (100%) rename {Source => src}/Moq.Analyzers/CallbackSignatureShouldMatchMockedMethodAnalyzer.cs (100%) rename {Source => src}/Moq.Analyzers/CallbackSignatureShouldMatchMockedMethodCodeFix.cs (100%) rename {Source => src}/Moq.Analyzers/ConstructorArgumentsShouldMatchAnalyzer.cs (100%) rename {Source => src}/Moq.Analyzers/DiagnosticCategory.cs (100%) rename {Source/Moq.Analyzers.Test => src/Moq.Analyzers}/GlobalSuppressions.cs (100%) rename {Source => src}/Moq.Analyzers/GlobalUsings.cs (100%) rename {Source => src}/Moq.Analyzers/Helpers.cs (100%) rename {Source => src}/Moq.Analyzers/Moq.Analyzers.csproj (100%) rename {Source => src}/Moq.Analyzers/MoqAsMethodDescriptor.cs (100%) rename {Source => src}/Moq.Analyzers/MoqMethodDescriptorBase.cs (100%) rename {Source => src}/Moq.Analyzers/MoqSetupMethodDescriptor.cs (100%) rename {Source => src}/Moq.Analyzers/NoConstructorArgumentsForInterfaceMockAnalyzer.cs (100%) rename {Source => src}/Moq.Analyzers/NoMethodsInPropertySetupAnalyzer.cs (100%) rename {Source => src}/Moq.Analyzers/NoSealedClassMocksAnalyzer.cs (100%) rename {Source => src}/Moq.Analyzers/Resources.Designer.cs (100%) rename {Source => src}/Moq.Analyzers/Resources.resx (100%) rename {Source => src}/Moq.Analyzers/SetupShouldBeUsedOnlyForOverridableMembersAnalyzer.cs (100%) rename {Source => src}/Moq.Analyzers/SetupShouldNotIncludeAsyncResultAnalyzer.cs (100%) rename {Source => src}/Moq.Analyzers/SyntaxExtensions.cs (100%) rename {Source => src}/Moq.Analyzers/tools/install.ps1 (100%) rename {Source => src}/Moq.Analyzers/tools/uninstall.ps1 (100%) rename {Source => tests}/Moq.Analyzers.Test/.editorconfig (100%) rename {Source => tests}/Moq.Analyzers.Test/AsAcceptOnlyInterfaceAnalyzerTests.cs (100%) rename {Source => tests}/Moq.Analyzers.Test/CallbackSignatureShouldMatchMockedMethodCodeFixTests.cs (100%) rename {Source => tests}/Moq.Analyzers.Test/ConstructorArgumentsShouldMatchAnalyzerTests.cs (100%) rename {Source/Moq.Analyzers => tests/Moq.Analyzers.Test}/GlobalSuppressions.cs (100%) rename {Source => tests}/Moq.Analyzers.Test/GlobalUsings.cs (100%) rename {Source => tests}/Moq.Analyzers.Test/Helpers/AnalyzerVerifier.cs (100%) rename {Source => tests}/Moq.Analyzers.Test/Helpers/CodeFixVerifier.cs (100%) rename {Source => tests}/Moq.Analyzers.Test/Helpers/ReferenceAssemblyCatalog.cs (100%) rename {Source => tests}/Moq.Analyzers.Test/Helpers/Test.cs (100%) rename {Source => tests}/Moq.Analyzers.Test/Helpers/TestDataExtensions.cs (100%) rename {Source => tests}/Moq.Analyzers.Test/ModuleInitializer.cs (100%) rename {Source => tests}/Moq.Analyzers.Test/Moq.Analyzers.Test.csproj (89%) rename {Source => tests}/Moq.Analyzers.Test/NoConstructorArgumentsForInterfaceMockAnalyzerTests.cs (100%) rename {Source => tests}/Moq.Analyzers.Test/NoMethodsInPropertySetupAnalyzerTests.cs (100%) rename {Source => tests}/Moq.Analyzers.Test/NoSealedClassMocksAnalyzerTests.cs (100%) rename {Source => tests}/Moq.Analyzers.Test/PackageTests.Baseline#contents.verified.txt (100%) rename {Source => tests}/Moq.Analyzers.Test/PackageTests.Baseline#manifest.verified.nuspec (100%) rename {Source => tests}/Moq.Analyzers.Test/PackageTests.cs (100%) rename {Source => tests}/Moq.Analyzers.Test/SetupShouldBeUsedOnlyForOverridableMembersAnalyzerTests.cs (100%) rename {Source => tests}/Moq.Analyzers.Test/SetupShouldNotIncludeAsyncResultAnalyzerTests.cs (100%) diff --git a/Moq.Analyzers.sln b/Moq.Analyzers.sln index 8d30b87d..2e63ae25 100644 --- a/Moq.Analyzers.sln +++ b/Moq.Analyzers.sln @@ -3,9 +3,9 @@ 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 Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/build/targets/codeanalysis/CodeAnalysis.props b/build/targets/codeanalysis/CodeAnalysis.props index c095d019..d959a096 100644 --- a/build/targets/codeanalysis/CodeAnalysis.props +++ b/build/targets/codeanalysis/CodeAnalysis.props @@ -10,7 +10,7 @@ - + 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/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 100% rename from Source/Moq.Analyzers/AsShouldBeUsedOnlyForInterfaceAnalyzer.cs rename to src/Moq.Analyzers/AsShouldBeUsedOnlyForInterfaceAnalyzer.cs diff --git a/Source/Moq.Analyzers/CallbackSignatureShouldMatchMockedMethodAnalyzer.cs b/src/Moq.Analyzers/CallbackSignatureShouldMatchMockedMethodAnalyzer.cs similarity index 100% rename from Source/Moq.Analyzers/CallbackSignatureShouldMatchMockedMethodAnalyzer.cs rename to src/Moq.Analyzers/CallbackSignatureShouldMatchMockedMethodAnalyzer.cs diff --git a/Source/Moq.Analyzers/CallbackSignatureShouldMatchMockedMethodCodeFix.cs b/src/Moq.Analyzers/CallbackSignatureShouldMatchMockedMethodCodeFix.cs similarity index 100% rename from Source/Moq.Analyzers/CallbackSignatureShouldMatchMockedMethodCodeFix.cs rename to src/Moq.Analyzers/CallbackSignatureShouldMatchMockedMethodCodeFix.cs diff --git a/Source/Moq.Analyzers/ConstructorArgumentsShouldMatchAnalyzer.cs b/src/Moq.Analyzers/ConstructorArgumentsShouldMatchAnalyzer.cs similarity index 100% rename from Source/Moq.Analyzers/ConstructorArgumentsShouldMatchAnalyzer.cs rename to src/Moq.Analyzers/ConstructorArgumentsShouldMatchAnalyzer.cs 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/Source/Moq.Analyzers/Helpers.cs b/src/Moq.Analyzers/Helpers.cs similarity index 100% rename from Source/Moq.Analyzers/Helpers.cs rename to src/Moq.Analyzers/Helpers.cs 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 100% rename from Source/Moq.Analyzers/MoqAsMethodDescriptor.cs rename to src/Moq.Analyzers/MoqAsMethodDescriptor.cs 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 100% rename from Source/Moq.Analyzers/MoqSetupMethodDescriptor.cs rename to src/Moq.Analyzers/MoqSetupMethodDescriptor.cs diff --git a/Source/Moq.Analyzers/NoConstructorArgumentsForInterfaceMockAnalyzer.cs b/src/Moq.Analyzers/NoConstructorArgumentsForInterfaceMockAnalyzer.cs similarity index 100% rename from Source/Moq.Analyzers/NoConstructorArgumentsForInterfaceMockAnalyzer.cs rename to src/Moq.Analyzers/NoConstructorArgumentsForInterfaceMockAnalyzer.cs diff --git a/Source/Moq.Analyzers/NoMethodsInPropertySetupAnalyzer.cs b/src/Moq.Analyzers/NoMethodsInPropertySetupAnalyzer.cs similarity index 100% rename from Source/Moq.Analyzers/NoMethodsInPropertySetupAnalyzer.cs rename to src/Moq.Analyzers/NoMethodsInPropertySetupAnalyzer.cs diff --git a/Source/Moq.Analyzers/NoSealedClassMocksAnalyzer.cs b/src/Moq.Analyzers/NoSealedClassMocksAnalyzer.cs similarity index 100% rename from Source/Moq.Analyzers/NoSealedClassMocksAnalyzer.cs rename to src/Moq.Analyzers/NoSealedClassMocksAnalyzer.cs 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/Source/Moq.Analyzers/SetupShouldBeUsedOnlyForOverridableMembersAnalyzer.cs b/src/Moq.Analyzers/SetupShouldBeUsedOnlyForOverridableMembersAnalyzer.cs similarity index 100% rename from Source/Moq.Analyzers/SetupShouldBeUsedOnlyForOverridableMembersAnalyzer.cs rename to src/Moq.Analyzers/SetupShouldBeUsedOnlyForOverridableMembersAnalyzer.cs diff --git a/Source/Moq.Analyzers/SetupShouldNotIncludeAsyncResultAnalyzer.cs b/src/Moq.Analyzers/SetupShouldNotIncludeAsyncResultAnalyzer.cs similarity index 100% rename from Source/Moq.Analyzers/SetupShouldNotIncludeAsyncResultAnalyzer.cs rename to src/Moq.Analyzers/SetupShouldNotIncludeAsyncResultAnalyzer.cs diff --git a/Source/Moq.Analyzers/SyntaxExtensions.cs b/src/Moq.Analyzers/SyntaxExtensions.cs similarity index 100% rename from Source/Moq.Analyzers/SyntaxExtensions.cs rename to src/Moq.Analyzers/SyntaxExtensions.cs 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/Moq.Analyzers.Test/.editorconfig similarity index 100% rename from Source/Moq.Analyzers.Test/.editorconfig rename to tests/Moq.Analyzers.Test/.editorconfig 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 100% rename from Source/Moq.Analyzers.Test/CallbackSignatureShouldMatchMockedMethodCodeFixTests.cs rename to tests/Moq.Analyzers.Test/CallbackSignatureShouldMatchMockedMethodCodeFixTests.cs diff --git a/Source/Moq.Analyzers.Test/ConstructorArgumentsShouldMatchAnalyzerTests.cs b/tests/Moq.Analyzers.Test/ConstructorArgumentsShouldMatchAnalyzerTests.cs similarity index 100% rename from Source/Moq.Analyzers.Test/ConstructorArgumentsShouldMatchAnalyzerTests.cs rename to tests/Moq.Analyzers.Test/ConstructorArgumentsShouldMatchAnalyzerTests.cs diff --git a/Source/Moq.Analyzers/GlobalSuppressions.cs b/tests/Moq.Analyzers.Test/GlobalSuppressions.cs similarity index 100% rename from Source/Moq.Analyzers/GlobalSuppressions.cs rename to tests/Moq.Analyzers.Test/GlobalSuppressions.cs diff --git a/Source/Moq.Analyzers.Test/GlobalUsings.cs b/tests/Moq.Analyzers.Test/GlobalUsings.cs similarity index 100% rename from Source/Moq.Analyzers.Test/GlobalUsings.cs rename to tests/Moq.Analyzers.Test/GlobalUsings.cs diff --git a/Source/Moq.Analyzers.Test/Helpers/AnalyzerVerifier.cs b/tests/Moq.Analyzers.Test/Helpers/AnalyzerVerifier.cs similarity index 100% rename from Source/Moq.Analyzers.Test/Helpers/AnalyzerVerifier.cs rename to tests/Moq.Analyzers.Test/Helpers/AnalyzerVerifier.cs diff --git a/Source/Moq.Analyzers.Test/Helpers/CodeFixVerifier.cs b/tests/Moq.Analyzers.Test/Helpers/CodeFixVerifier.cs similarity index 100% rename from Source/Moq.Analyzers.Test/Helpers/CodeFixVerifier.cs rename to tests/Moq.Analyzers.Test/Helpers/CodeFixVerifier.cs diff --git a/Source/Moq.Analyzers.Test/Helpers/ReferenceAssemblyCatalog.cs b/tests/Moq.Analyzers.Test/Helpers/ReferenceAssemblyCatalog.cs similarity index 100% rename from Source/Moq.Analyzers.Test/Helpers/ReferenceAssemblyCatalog.cs rename to tests/Moq.Analyzers.Test/Helpers/ReferenceAssemblyCatalog.cs diff --git a/Source/Moq.Analyzers.Test/Helpers/Test.cs b/tests/Moq.Analyzers.Test/Helpers/Test.cs similarity index 100% rename from Source/Moq.Analyzers.Test/Helpers/Test.cs rename to tests/Moq.Analyzers.Test/Helpers/Test.cs diff --git a/Source/Moq.Analyzers.Test/Helpers/TestDataExtensions.cs b/tests/Moq.Analyzers.Test/Helpers/TestDataExtensions.cs similarity index 100% rename from Source/Moq.Analyzers.Test/Helpers/TestDataExtensions.cs rename to tests/Moq.Analyzers.Test/Helpers/TestDataExtensions.cs diff --git a/Source/Moq.Analyzers.Test/ModuleInitializer.cs b/tests/Moq.Analyzers.Test/ModuleInitializer.cs similarity index 100% rename from Source/Moq.Analyzers.Test/ModuleInitializer.cs rename to tests/Moq.Analyzers.Test/ModuleInitializer.cs diff --git a/Source/Moq.Analyzers.Test/Moq.Analyzers.Test.csproj b/tests/Moq.Analyzers.Test/Moq.Analyzers.Test.csproj similarity index 89% rename from Source/Moq.Analyzers.Test/Moq.Analyzers.Test.csproj rename to tests/Moq.Analyzers.Test/Moq.Analyzers.Test.csproj index 10e52888..43af6d3e 100644 --- a/Source/Moq.Analyzers.Test/Moq.Analyzers.Test.csproj +++ b/tests/Moq.Analyzers.Test/Moq.Analyzers.Test.csproj @@ -23,7 +23,7 @@ - + \ 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/Source/Moq.Analyzers.Test/PackageTests.cs b/tests/Moq.Analyzers.Test/PackageTests.cs similarity index 100% rename from Source/Moq.Analyzers.Test/PackageTests.cs rename to tests/Moq.Analyzers.Test/PackageTests.cs 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 From b181344010edddf61409be22f0ffe0524191663f Mon Sep 17 00:00:00 2001 From: Matt Kotsenas Date: Fri, 21 Jun 2024 18:29:51 -0400 Subject: [PATCH 15/21] Add Moq.Analyzers.Benchmarks with a single sample benchmark (#109) Add the project `Moq.Analyzers.Benchmarks` with a sample benchmark for diagnostic Moq1300. This pattern is copied from https://github.com/dotnet/roslyn-analyzers/blob/f1115edce8633ebe03a86191bc05c6969ed9a821/src/PerformanceTests/Tests/Enabled/CSharp_CA1416.cs, but where possible I used the types exposed by the Microsoft.CodeAnalysis.*.Testing libraries instead. As part of the exercise, I filed https://github.com/dotnet/roslyn-sdk/issues/1165 to discuss if some of these helpers should move into the testing packages, and / or to document any existing profiling best practices. --- .editorconfig | 7 + Directory.Packages.props | 1 + Moq.Analyzers.sln | 6 + build/targets/codeanalysis/.globalconfig | 9 + build/targets/codeanalysis/CodeAnalysis.props | 1 + tests/{Moq.Analyzers.Test => }/.editorconfig | 4 +- tests/Moq.Analyzers.Benchmarks/Constants.cs | 6 + .../Helpers/AnalysisResultExtensions.cs | 21 +++ .../Helpers/AsyncLazy.cs | 46 +++++ .../BenchmarkCSharpCompilationCreator.cs | 26 +++ .../Helpers/CSharpCompilationCreator.cs | 35 ++++ .../Helpers/CompilationCreator.cs | 159 ++++++++++++++++++ .../Helpers/ExportProviderExtensions.cs | 89 ++++++++++ .../Moq.Analyzers.Benchmarks.csproj | 16 ++ .../Moq1300Benchmarks.cs | 84 +++++++++ tests/Moq.Analyzers.Benchmarks/Program.cs | 19 +++ 16 files changed, 528 insertions(+), 1 deletion(-) create mode 100644 build/targets/codeanalysis/.globalconfig rename tests/{Moq.Analyzers.Test => }/.editorconfig (82%) create mode 100644 tests/Moq.Analyzers.Benchmarks/Constants.cs create mode 100644 tests/Moq.Analyzers.Benchmarks/Helpers/AnalysisResultExtensions.cs create mode 100644 tests/Moq.Analyzers.Benchmarks/Helpers/AsyncLazy.cs create mode 100644 tests/Moq.Analyzers.Benchmarks/Helpers/BenchmarkCSharpCompilationCreator.cs create mode 100644 tests/Moq.Analyzers.Benchmarks/Helpers/CSharpCompilationCreator.cs create mode 100644 tests/Moq.Analyzers.Benchmarks/Helpers/CompilationCreator.cs create mode 100644 tests/Moq.Analyzers.Benchmarks/Helpers/ExportProviderExtensions.cs create mode 100644 tests/Moq.Analyzers.Benchmarks/Moq.Analyzers.Benchmarks.csproj create mode 100644 tests/Moq.Analyzers.Benchmarks/Moq1300Benchmarks.cs create mode 100644 tests/Moq.Analyzers.Benchmarks/Program.cs diff --git a/.editorconfig b/.editorconfig index 012d9871..f2384a03 100644 --- a/.editorconfig +++ b/.editorconfig @@ -242,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 @@ -398,6 +401,10 @@ dotnet_diagnostic.MA0040.severity = error # Async analyzer 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 diff --git a/Directory.Packages.props b/Directory.Packages.props index 847853e9..f474f00b 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -20,6 +20,7 @@ + diff --git a/Moq.Analyzers.sln b/Moq.Analyzers.sln index 2e63ae25..4442c49b 100644 --- a/Moq.Analyzers.sln +++ b/Moq.Analyzers.sln @@ -7,6 +7,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Moq.Analyzers", "src\Moq.An EndProject 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 Debug|Any CPU = Debug|Any CPU @@ -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/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 d959a096..a66e6a33 100644 --- a/build/targets/codeanalysis/CodeAnalysis.props +++ b/build/targets/codeanalysis/CodeAnalysis.props @@ -11,6 +11,7 @@ + diff --git a/tests/Moq.Analyzers.Test/.editorconfig b/tests/.editorconfig similarity index 82% rename from tests/Moq.Analyzers.Test/.editorconfig rename to tests/.editorconfig index d344cf31..576433c2 100644 --- a/tests/Moq.Analyzers.Test/.editorconfig +++ b/tests/.editorconfig @@ -14,5 +14,7 @@ dotnet_diagnostic.CS1591.severity = suggestion 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 \ No newline at end of file +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. +} From ba894757902287f489973f8b9b469275fe32201d Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Sat, 22 Jun 2024 07:37:57 -0700 Subject: [PATCH 16/21] Update labeler.yml path for source (#108) #107 moved the source folder to follow convention. Updating the labeler so PRs are correctly labeled --- .github/labeler.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/labeler.yml b/.github/labeler.yml index de9627c3..97a0e4ae 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -1,17 +1,17 @@ # Add 'build' label to any change in the 'build' directory build: - - build/**/* +- build/**/* # Add 'dependencies' label to any change in one of the packages files dependencies: - - build/**/Packages.props - - Directory.Packages.props +- build/**/Packages.props +- Directory.Packages.props # Add '.NET' label to any change to a '.cs' file under Moq.Analyzers .NET - - 'Source/Moq.Analyzers/**/*.cs' +- 'src/Moq.Analyzers/**/*.cs' # Add 'documentation' label to any change within the 'docs' directory or any '.md' file documentation: - - docs/**/* - - '**/*.md' \ No newline at end of file +- docs/**/* +- '**/*.md' From a8bea577aa02146a2f632a9924286ae94da30eac Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Jun 2024 09:33:51 -0700 Subject: [PATCH 17/21] Bump actions/labeler from 4 to 5 (#112) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [actions/labeler](https://github.com/actions/labeler) from 4 to 5.
Release notes

Sourced from actions/labeler's releases.

v5.0.0

What's Changed

This release contains the following breaking changes:

  1. The ability to apply labels based on the names of base and/or head branches was added (#186 and #54). The match object for changed files was expanded with new combinations in order to make it more intuitive and flexible (#423 and #101). As a result, the configuration file structure was significantly redesigned and is not compatible with the structure of the previous version. Please read the action documentation to find out how to adapt your configuration files for use with the new action version.

  2. The bug related to the sync-labels input was fixed (#112). Now the input value is read correctly.

  3. By default, dot input is set to true. Now, paths starting with a dot (e.g. .github) are matched by default.

  4. Version 5 of this action updated the runtime to Node.js 20. All scripts are now run with Node.js 20 instead of Node.js 16 and are affected by any breaking changes between Node.js 16 and 20.

For more information, please read the action documentation.

New Contributors

Full Changelog: https://github.com/actions/labeler/compare/v4...v5.0.0

v5.0.0-beta.1

What's Changed

In scope of this beta release, the structure of the configuration file (.github/labeler.yml) was changed from

LabelName:
- any:
  - changed-files: ['list', 'of', 'globs']
  - base-branch: ['list', 'of', 'regexps']
  - head-branch: ['list', 'of', 'regexps']
- all:
  - changed-files: ['list', 'of', 'globs']
  - base-branch: ['list', 'of', 'regexps']
  - head-branch: ['list', 'of', 'regexps']

to

LabelName:
- any:
  - changed-files:
    - AnyGlobToAnyFile: ['list', 'of', 'globs']
    - AnyGlobToAllFiles: ['list', 'of', 'globs']
    - AllGlobsToAnyFile: ['list', 'of', 'globs']
    - AllGlobsToAllFiles: ['list', 'of', 'globs']
  - base-branch: ['list', 'of', 'regexps']
  - head-branch: ['list', 'of', 'regexps']
- all:
  - changed-files:
    - AnyGlobToAnyFile: ['list', 'of', 'globs']
    - AnyGlobToAllFiles: ['list', 'of', 'globs']
    - AllGlobsToAnyFile: ['list', 'of', 'globs']
</tr></table>

... (truncated)

Commits
  • 8558fd7 Merge pull request #709 from actions/v5.0.0-beta
  • 000ca75 Merge pull request #700 from MaksimZhukov/apply-suggestions-and-update-docume...
  • cb66c2f Update dist
  • 9181355 Apply suggestions for the beta vesrion and update the documentation
  • efe4c1c Merge pull request #699 from MaksimZhukov/update-node-runtime-and-dependencies
  • c0957ad Run Prettier
  • 8dc8d18 Update Node.js version in reusable workflows
  • d0d0bbe Update documentation
  • 1375c42 5.0.0
  • ab7411e Change version of Node.js runtime to node20
  • Additional commits viewable in compare view

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/labeler&package-manager=github_actions&previous-version=4&new-version=5)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/label-pr.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/label-pr.yml b/.github/workflows/label-pr.yml index b4823847..a516e8b0 100644 --- a/.github/workflows/label-pr.yml +++ b/.github/workflows/label-pr.yml @@ -16,6 +16,6 @@ jobs: pull-requests: write steps: - - uses: actions/labeler@v4 + - uses: actions/labeler@v5 with: repo-token: "${{ secrets.GH_ACTIONS_PR_WRITE }}" \ No newline at end of file From 12e9e315e153212b72594781c99319b18c545a29 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Mon, 24 Jun 2024 10:13:45 -0700 Subject: [PATCH 18/21] Update labeler.yml for v5 (#114) Update labeler action to v5 configuration https://github.com/actions/labeler/tree/v5 --- .github/labeler.yml | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/.github/labeler.yml b/.github/labeler.yml index 97a0e4ae..b68e26c4 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -1,17 +1,31 @@ # Add 'build' label to any change in the 'build' directory build: -- build/**/* +- changed-files: + - any-glob-to-any-file: 'build/**/*' # Add 'dependencies' label to any change in one of the packages files dependencies: -- build/**/Packages.props -- Directory.Packages.props +- changed-files: + - any-glob-to-any-file: ['build/**/Packages.props', 'Directory.Packages.props'] # Add '.NET' label to any change to a '.cs' file under Moq.Analyzers .NET -- 'src/Moq.Analyzers/**/*.cs' +- changed-files: + - any-glob-to-any-file: ['src/Moq.Analyzers/**/*.cs'] # Add 'documentation' label to any change within the 'docs' directory or any '.md' file documentation: -- docs/**/* -- '**/*.md' +- 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' From 3e36ac620969caed30d9bf3482c56dc708db3ee9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Jun 2024 11:44:56 -0700 Subject: [PATCH 19/21] Bump actions/github-script from 6 to 7 (#111) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [actions/github-script](https://github.com/actions/github-script) from 6 to 7.
Release notes

Sourced from actions/github-script's releases.

v7.0.0

What's Changed

New Contributors

Full Changelog: https://github.com/actions/github-script/compare/v6.4.1...v7.0.0

v6.4.1

What's Changed

New Contributors

Full Changelog: https://github.com/actions/github-script/compare/v6.4.0...v6.4.1

v6.4.0

What's Changed

New Contributors

Full Changelog: https://github.com/actions/github-script/compare/v6.3.3...v6.4.0

v6.3.3

What's Changed

New Contributors

Full Changelog: https://github.com/actions/github-script/compare/v6.3.2...v6.3.3

v6.3.2

What's Changed

... (truncated)

Commits
  • 60a0d83 Merge pull request #440 from actions/joshmgross/v7.0.1
  • b7fb200 Update version to 7.0.1
  • 12e22ed Merge pull request #439 from actions/joshmgross/avoid-setting-base-url
  • d319f8f Avoid setting baseUrl to undefined when input is not provided
  • e69ef54 Merge pull request #425 from actions/joshmgross/node-20
  • ee0914b Update licenses
  • d6fc56f Use @types/node for Node 20
  • 384d6cf Fix quotations in tests
  • 8472492 Only validate GraphQL previews
  • 84903f5 Remove node-fetch from type
  • Additional commits viewable in compare view

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/github-script&package-manager=github_actions&previous-version=6&new-version=7)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/label-issues.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/label-issues.yml b/.github/workflows/label-issues.yml index c5529ec6..b4eb5292 100644 --- a/.github/workflows/label-issues.yml +++ b/.github/workflows/label-issues.yml @@ -15,7 +15,7 @@ jobs: permissions: issues: write steps: - - uses: actions/github-script@v6 + - uses: actions/github-script@v7 with: github-token: ${{ secrets.GH_ACTIONS_PR_WRITE }} script: | From b911035f09520da303df155203af2d08cc067b35 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Mon, 24 Jun 2024 11:47:25 -0700 Subject: [PATCH 20/21] Refactor code to remove analyzer warnings (#113) --- ...ignatureShouldMatchMockedMethodAnalyzer.cs | 15 +-- ...SignatureShouldMatchMockedMethodCodeFix.cs | 12 +- ...ConstructorArgumentsShouldMatchAnalyzer.cs | 113 ++++++++++++------ .../InvocationExpressionSyntaxExtensions.cs | 19 +++ ...xExtensions.cs => NameSyntaxExtensions.cs} | 4 +- ...ructorArgumentsForInterfaceMockAnalyzer.cs | 4 +- .../NoMethodsInPropertySetupAnalyzer.cs | 6 +- .../NoSealedClassMocksAnalyzer.cs | 4 +- ...{Helpers.cs => SemanticModelExtensions.cs} | 96 ++++++++------- ...BeUsedOnlyForOverridableMembersAnalyzer.cs | 8 +- ...etupShouldNotIncludeAsyncResultAnalyzer.cs | 10 +- 11 files changed, 170 insertions(+), 121 deletions(-) create mode 100644 src/Moq.Analyzers/InvocationExpressionSyntaxExtensions.cs rename src/Moq.Analyzers/{SyntaxExtensions.cs => NameSyntaxExtensions.cs} (92%) rename src/Moq.Analyzers/{Helpers.cs => SemanticModelExtensions.cs} (56%) diff --git a/src/Moq.Analyzers/CallbackSignatureShouldMatchMockedMethodAnalyzer.cs b/src/Moq.Analyzers/CallbackSignatureShouldMatchMockedMethodAnalyzer.cs index 35e486e7..8760a9d2 100644 --- a/src/Moq.Analyzers/CallbackSignatureShouldMatchMockedMethodAnalyzer.cs +++ b/src/Moq.Analyzers/CallbackSignatureShouldMatchMockedMethodAnalyzer.cs @@ -38,14 +38,14 @@ public override void Initialize(AnalysisContext context) [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; @@ -56,15 +56,15 @@ 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 @@ -72,12 +72,9 @@ private static void Analyze(SyntaxNodeAnalysisContext context) for (int argumentIndex = 0; argumentIndex < mockedMethodArguments.Count; argumentIndex++) { TypeSyntax? lambdaParameterTypeSyntax = lambdaParameters[argumentIndex].Type; - Debug.Assert(lambdaParameterTypeSyntax != null, nameof(lambdaParameterTypeSyntax) + " != null"); // TODO: Don't know if continue or break is the right thing to do here -#pragma warning disable S2589 // Boolean expressions should not be gratuitous if (lambdaParameterTypeSyntax is null) continue; -#pragma warning restore S2589 // Boolean expressions should not be gratuitous TypeInfo lambdaParameterType = context.SemanticModel.GetTypeInfo(lambdaParameterTypeSyntax, context.CancellationToken); @@ -88,7 +85,7 @@ private static void Analyze(SyntaxNodeAnalysisContext context) 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/src/Moq.Analyzers/CallbackSignatureShouldMatchMockedMethodCodeFix.cs b/src/Moq.Analyzers/CallbackSignatureShouldMatchMockedMethodCodeFix.cs index 8f29e2e2..70a6eed7 100644 --- a/src/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. @@ -69,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) { return document; } - ParameterListSyntax? newParameters = SyntaxFactory.ParameterList(SyntaxFactory.SeparatedList(matchingMockedMethods[0].Parameters.Select( + ParameterListSyntax newParameters = SyntaxFactory.ParameterList(SyntaxFactory.SeparatedList(matchingMockedMethods[0].Parameters.Select( parameterSymbol => { - TypeSyntax? type = SyntaxFactory.ParseTypeName(parameterSymbol.Type.ToMinimalDisplayString(semanticModel, oldParameters.SpanStart)); + 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/src/Moq.Analyzers/ConstructorArgumentsShouldMatchAnalyzer.cs b/src/Moq.Analyzers/ConstructorArgumentsShouldMatchAnalyzer.cs index d33cdb6f..69922915 100644 --- a/src/Moq.Analyzers/ConstructorArgumentsShouldMatchAnalyzer.cs +++ b/src/Moq.Analyzers/ConstructorArgumentsShouldMatchAnalyzer.cs @@ -39,74 +39,101 @@ public override void Initialize(AnalysisContext context) [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); - Debug.Assert(constructorSymbol != null, nameof(constructorSymbol) + " != null"); - -#pragma warning disable S2589 // Boolean expressions should not be gratuitous - if (constructorSymbol is null) return; -#pragma warning restore S2589 // Boolean expressions should not be gratuitous + // 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 + // Vararg parameter is the one that takes all arguments for mocked class constructor 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; + if (varArgsConstructorParameter == null) + { + return; + } 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(varArgsConstructorParameterIndex == 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 constructorIndex = 0; constructorIndex < mockedTypeSymbol.Constructors.Length; constructorIndex++) + // 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[constructorIndex]; - 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); } } @@ -122,10 +149,12 @@ 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; } @@ -133,7 +162,7 @@ private static bool AreParametersMatching(ImmutableArray const // Check if each parameter type matches in order for (int constructorParameterIndex = 0; constructorParameterIndex < constructorParameters.Length; constructorParameterIndex++) { - if (!constructorParameters[constructorParameterIndex].Type.Equals(argumentTypes2[constructorParameterIndex], SymbolEqualityComparer.IncludeNullability)) + if (!constructorParameters[constructorParameterIndex].Type.Equals(argumentTypes[constructorParameterIndex], SymbolEqualityComparer.IncludeNullability)) { return false; } @@ -144,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; @@ -177,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/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/src/Moq.Analyzers/SyntaxExtensions.cs b/src/Moq.Analyzers/NameSyntaxExtensions.cs similarity index 92% rename from src/Moq.Analyzers/SyntaxExtensions.cs rename to src/Moq.Analyzers/NameSyntaxExtensions.cs index 9099f12e..45a5004a 100644 --- a/src/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/src/Moq.Analyzers/NoConstructorArgumentsForInterfaceMockAnalyzer.cs b/src/Moq.Analyzers/NoConstructorArgumentsForInterfaceMockAnalyzer.cs index 32d2bfaa..775cbf1d 100644 --- a/src/Moq.Analyzers/NoConstructorArgumentsForInterfaceMockAnalyzer.cs +++ b/src/Moq.Analyzers/NoConstructorArgumentsForInterfaceMockAnalyzer.cs @@ -38,7 +38,7 @@ public override void Initialize(AnalysisContext context) [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; @@ -84,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/src/Moq.Analyzers/NoMethodsInPropertySetupAnalyzer.cs b/src/Moq.Analyzers/NoMethodsInPropertySetupAnalyzer.cs index 71b012cf..0880481e 100644 --- a/src/Moq.Analyzers/NoMethodsInPropertySetupAnalyzer.cs +++ b/src/Moq.Analyzers/NoMethodsInPropertySetupAnalyzer.cs @@ -36,19 +36,19 @@ public override void Initialize(AnalysisContext context) [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/src/Moq.Analyzers/NoSealedClassMocksAnalyzer.cs b/src/Moq.Analyzers/NoSealedClassMocksAnalyzer.cs index c30338ce..4e5d244f 100644 --- a/src/Moq.Analyzers/NoSealedClassMocksAnalyzer.cs +++ b/src/Moq.Analyzers/NoSealedClassMocksAnalyzer.cs @@ -36,7 +36,7 @@ public override void Initialize(AnalysisContext context) [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; @@ -68,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/src/Moq.Analyzers/Helpers.cs b/src/Moq.Analyzers/SemanticModelExtensions.cs similarity index 56% rename from src/Moq.Analyzers/Helpers.cs rename to src/Moq.Analyzers/SemanticModelExtensions.cs index f3f024db..f6899ae1 100644 --- a/src/Moq.Analyzers/Helpers.cs +++ b/src/Moq.Analyzers/SemanticModelExtensions.cs @@ -1,20 +1,33 @@ using System.Diagnostics; -using System.Linq.Expressions; namespace Moq.Analyzers; -[System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "AV1708:Type name contains term that should be avoided", Justification = "Tracked in https://github.com/rjmurillo/moq.analyzers/issues/90")] -internal static class Helpers +/// +/// Extensions methods for . +/// +internal static class SemanticModelExtensions { private static readonly MoqMethodDescriptorBase MoqSetupMethodDescriptor = new MoqSetupMethodDescriptor(); - internal static bool IsMoqSetupMethod(SemanticModel semanticModel, MemberAccessExpressionSyntax method, CancellationToken cancellationToken) + internal static InvocationExpressionSyntax? FindSetupMethodFromCallbackInvocation(this SemanticModel semanticModel, ExpressionSyntax expression, CancellationToken cancellationToken) { - return MoqSetupMethodDescriptor.IsMatch(semanticModel, method, 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(SemanticModel semanticModel, InvocationExpressionSyntax callbackOrReturnsInvocation) + internal static bool IsCallbackOrReturnInvocation(this SemanticModel semanticModel, InvocationExpressionSyntax callbackOrReturnsInvocation) { MemberAccessExpressionSyntax? callbackOrReturnsMethod = callbackOrReturnsInvocation.Expression as MemberAccessExpressionSyntax; @@ -41,62 +54,47 @@ internal static bool IsCallbackOrReturnInvocation(SemanticModel semanticModel, I }; } - 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) + internal static bool IsMoqSetupMethod(this SemanticModel semanticModel, MemberAccessExpressionSyntax method, CancellationToken cancellationToken) { - LambdaExpressionSyntax? setupLambdaArgument = setupMethodInvocation?.ArgumentList.Arguments[0].Expression as LambdaExpressionSyntax; - InvocationExpressionSyntax? mockedMethodInvocation = setupLambdaArgument?.Body as InvocationExpressionSyntax; - - return mockedMethodInvocation == null - ? [] - : GetAllMatchingSymbols(semanticModel, mockedMethodInvocation); + 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")] - internal static IEnumerable GetAllMatchingSymbols(SemanticModel semanticModel, ExpressionSyntax expression) + private static List GetAllMatchingSymbols(this SemanticModel semanticModel, ExpressionSyntax expression) where T : class { List matchingSymbols = new(); SymbolInfo symbolInfo = semanticModel.GetSymbolInfo(expression); - if (symbolInfo is { CandidateReason: CandidateReason.None, Symbol: T }) + switch (symbolInfo) { - T? value = symbolInfo.Symbol as T; - Debug.Assert(value != null, "Value should not be null."); + 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); - } + if (value != default(T)) + { + matchingSymbols.Add(value); + } #pragma warning restore S2589 // Boolean expressions should not be gratuitous - } - else if (symbolInfo.CandidateReason == CandidateReason.OverloadResolutionFailure) - { - matchingSymbols.AddRange(symbolInfo.CandidateSymbols.OfType()); - } - else - { - return matchingSymbols; + break; + } + + default: + { + if (symbolInfo.CandidateReason == CandidateReason.OverloadResolutionFailure) + { + matchingSymbols.AddRange(symbolInfo.CandidateSymbols.OfType()); + } + else + { + return matchingSymbols; + } + + break; + } } return matchingSymbols; diff --git a/src/Moq.Analyzers/SetupShouldBeUsedOnlyForOverridableMembersAnalyzer.cs b/src/Moq.Analyzers/SetupShouldBeUsedOnlyForOverridableMembersAnalyzer.cs index b8a3638c..aaceb995 100644 --- a/src/Moq.Analyzers/SetupShouldBeUsedOnlyForOverridableMembersAnalyzer.cs +++ b/src/Moq.Analyzers/SetupShouldBeUsedOnlyForOverridableMembersAnalyzer.cs @@ -33,11 +33,11 @@ public override void Initialize(AnalysisContext context) [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 +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/src/Moq.Analyzers/SetupShouldNotIncludeAsyncResultAnalyzer.cs b/src/Moq.Analyzers/SetupShouldNotIncludeAsyncResultAnalyzer.cs index b44ea243..44f861b3 100644 --- a/src/Moq.Analyzers/SetupShouldNotIncludeAsyncResultAnalyzer.cs +++ b/src/Moq.Analyzers/SetupShouldNotIncludeAsyncResultAnalyzer.cs @@ -33,11 +33,11 @@ public override void Initialize(AnalysisContext context) [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; @@ -48,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); } } @@ -62,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) From 932cc3dcb5bc3a299a5a5835f7ca52891426db64 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Mon, 24 Jun 2024 12:04:28 -0700 Subject: [PATCH 21/21] Update labeler.yml to remove .net label, add github actions and analyzers label --- .github/labeler.yml | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/.github/labeler.yml b/.github/labeler.yml index b68e26c4..c2e29f9b 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -8,24 +8,29 @@ dependencies: - changed-files: - any-glob-to-any-file: ['build/**/Packages.props', 'Directory.Packages.props'] -# Add '.NET' label to any change to a '.cs' file under Moq.Analyzers -.NET -- changed-files: - - any-glob-to-any-file: ['src/Moq.Analyzers/**/*.cs'] - # Add 'documentation' label to any change within the 'docs' directory or any '.md' file documentation: - changed-files: - - any-glob-to-any-file: ['docs/**/*', '**/*.md'] + - 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 +# 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 +# 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']