diff --git a/Source/Moq.Analyzers/AsShouldBeUsedOnlyForInterfaceAnalyzer.cs b/Source/Moq.Analyzers/AsShouldBeUsedOnlyForInterfaceAnalyzer.cs index f2ce8ad0..7b8a2289 100644 --- a/Source/Moq.Analyzers/AsShouldBeUsedOnlyForInterfaceAnalyzer.cs +++ b/Source/Moq.Analyzers/AsShouldBeUsedOnlyForInterfaceAnalyzer.cs @@ -24,17 +24,17 @@ private static void Analyze(SyntaxNodeAnalysisContext context) { var asInvocation = (InvocationExpressionSyntax)context.Node; - if (asInvocation.Expression is MemberAccessExpressionSyntax memberAccessExpression && Helpers.IsMoqAsMethod(context.SemanticModel, memberAccessExpression)) + if (asInvocation.Expression is MemberAccessExpressionSyntax memberAccessExpression + && Helpers.IsMoqAsMethod(context.SemanticModel, memberAccessExpression) + && memberAccessExpression.Name is GenericNameSyntax genericName + && genericName.TypeArgumentList.Arguments.Count == 1) { - if (memberAccessExpression.Name is GenericNameSyntax genericName && genericName.TypeArgumentList.Arguments.Count == 1) + var typeArgument = genericName.TypeArgumentList.Arguments[0]; + var symbolInfo = context.SemanticModel.GetSymbolInfo(typeArgument, context.CancellationToken); + if (symbolInfo.Symbol is ITypeSymbol typeSymbol && typeSymbol.TypeKind != TypeKind.Interface) { - var typeArgument = genericName.TypeArgumentList.Arguments[0]; - var symbolInfo = context.SemanticModel.GetSymbolInfo(typeArgument, context.CancellationToken); - if (symbolInfo.Symbol != null && symbolInfo.Symbol is ITypeSymbol typeSymbol && typeSymbol.TypeKind != TypeKind.Interface) - { - var diagnostic = Diagnostic.Create(Rule, typeArgument.GetLocation()); - context.ReportDiagnostic(diagnostic); - } + var diagnostic = Diagnostic.Create(Rule, typeArgument.GetLocation()); + context.ReportDiagnostic(diagnostic); } } } diff --git a/Source/Moq.Analyzers/CallbackSignatureShouldMatchMockedMethodAnalyzer.cs b/Source/Moq.Analyzers/CallbackSignatureShouldMatchMockedMethodAnalyzer.cs index eaaa0e28..8ceab124 100644 --- a/Source/Moq.Analyzers/CallbackSignatureShouldMatchMockedMethodAnalyzer.cs +++ b/Source/Moq.Analyzers/CallbackSignatureShouldMatchMockedMethodAnalyzer.cs @@ -60,9 +60,9 @@ private static void Analyze(SyntaxNodeAnalysisContext context) { var mockedMethodArgumentType = context.SemanticModel.GetTypeInfo(mockedMethodArguments[i].Expression, context.CancellationToken); var lambdaParameterType = context.SemanticModel.GetTypeInfo(lambdaParameters[i].Type, context.CancellationToken); - string mockedMethodTypeName = mockedMethodArgumentType.ConvertedType.ToString(); - string lambdaParameterTypeName = lambdaParameterType.ConvertedType.ToString(); - if (mockedMethodTypeName != lambdaParameterTypeName) + string? mockedMethodTypeName = mockedMethodArgumentType.ConvertedType?.ToString(); + string? lambdaParameterTypeName = lambdaParameterType.ConvertedType?.ToString(); + if (!string.Equals(mockedMethodTypeName, lambdaParameterTypeName, StringComparison.Ordinal)) { var diagnostic = Diagnostic.Create(Rule, callbackLambda.ParameterList.GetLocation()); context.ReportDiagnostic(diagnostic); diff --git a/Source/Moq.Analyzers/CallbackSignatureShouldMatchMockedMethodCodeFix.cs b/Source/Moq.Analyzers/CallbackSignatureShouldMatchMockedMethodCodeFix.cs index fc1a8e01..205806a2 100644 --- a/Source/Moq.Analyzers/CallbackSignatureShouldMatchMockedMethodCodeFix.cs +++ b/Source/Moq.Analyzers/CallbackSignatureShouldMatchMockedMethodCodeFix.cs @@ -1,4 +1,5 @@ using System.Composition; +using System.Diagnostics; using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CodeFixes; @@ -22,44 +23,63 @@ public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) { var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); + if (root == null) + { + return; + } + var diagnostic = context.Diagnostics.First(); var diagnosticSpan = diagnostic.Location.SourceSpan; // Find the type declaration identified by the diagnostic. - var badArgumentListSyntax = root.FindToken(diagnosticSpan.Start).Parent.AncestorsAndSelf().OfType().First(); + ParameterListSyntax? badArgumentListSyntax = root.FindToken(diagnosticSpan.Start) + .Parent? + .AncestorsAndSelf() + .OfType() + .First(); // Register a code action that will invoke the fix. context.RegisterCodeFix( CodeAction.Create( title: "Fix Moq callback signature", - createChangedDocument: c => FixCallbackSignature(root, context.Document, badArgumentListSyntax, c), + createChangedDocument: c => FixCallbackSignatureAsync(root, context.Document, badArgumentListSyntax, c), equivalenceKey: "Fix Moq callback signature"), diagnostic); } - private async Task FixCallbackSignature(SyntaxNode root, Document document, ParameterListSyntax oldParameters, CancellationToken cancellationToken) + private async Task FixCallbackSignatureAsync(SyntaxNode root, Document document, ParameterListSyntax? oldParameters, CancellationToken cancellationToken) { - var semanticModel = await document.GetSemanticModelAsync(cancellationToken); - var callbackInvocation = oldParameters?.Parent?.Parent?.Parent?.Parent as InvocationExpressionSyntax; - if (callbackInvocation != null) + var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); + + Debug.Assert(semanticModel != null, nameof(semanticModel) + " != null"); + + if (semanticModel == null) { - var setupMethodInvocation = Helpers.FindSetupMethodFromCallbackInvocation(semanticModel, callbackInvocation); - var matchingMockedMethods = Helpers.GetAllMatchingMockedMethodSymbolsFromSetupMethodInvocation(semanticModel, setupMethodInvocation).ToArray(); + return document; + } - if (matchingMockedMethods.Length == 1) - { - var newParameters = SyntaxFactory.ParameterList(SyntaxFactory.SeparatedList(matchingMockedMethods[0].Parameters.Select( - p => - { - var type = SyntaxFactory.ParseTypeName(p.Type.ToMinimalDisplayString(semanticModel, oldParameters.SpanStart)); - return SyntaxFactory.Parameter(default(SyntaxList), SyntaxFactory.TokenList(), type, SyntaxFactory.Identifier(p.Name), null); - }))); - - var newRoot = root.ReplaceNode(oldParameters, newParameters); - return document.WithSyntaxRoot(newRoot); - } + if (oldParameters?.Parent?.Parent?.Parent?.Parent is not InvocationExpressionSyntax callbackInvocation) + { + return document; } - return document; + var setupMethodInvocation = Helpers.FindSetupMethodFromCallbackInvocation(semanticModel, callbackInvocation); + Debug.Assert(setupMethodInvocation != null, nameof(setupMethodInvocation) + " != null"); + var matchingMockedMethods = Helpers.GetAllMatchingMockedMethodSymbolsFromSetupMethodInvocation(semanticModel, setupMethodInvocation).ToArray(); + + if (matchingMockedMethods.Length != 1 || oldParameters == null) + { + return document; + } + + var newParameters = SyntaxFactory.ParameterList(SyntaxFactory.SeparatedList(matchingMockedMethods[0].Parameters.Select( + p => + { + var type = SyntaxFactory.ParseTypeName(p.Type.ToMinimalDisplayString(semanticModel, oldParameters.SpanStart)); + return SyntaxFactory.Parameter(default, SyntaxFactory.TokenList(), type, SyntaxFactory.Identifier(p.Name), null); + }))); + + var newRoot = root.ReplaceNode(oldParameters, newParameters); + return document.WithSyntaxRoot(newRoot); } } diff --git a/Source/Moq.Analyzers/ConstructorArgumentsShouldMatchAnalyzer.cs b/Source/Moq.Analyzers/ConstructorArgumentsShouldMatchAnalyzer.cs index baa31cc2..ff6e0a63 100644 --- a/Source/Moq.Analyzers/ConstructorArgumentsShouldMatchAnalyzer.cs +++ b/Source/Moq.Analyzers/ConstructorArgumentsShouldMatchAnalyzer.cs @@ -1,3 +1,5 @@ +using System.Diagnostics; + namespace Moq.Analyzers; [DiagnosticAnalyzer(LanguageNames.CSharp)] @@ -36,10 +38,18 @@ private static void Analyze(SyntaxNodeAnalysisContext context) var constructorSymbol = GetConstructorSymbol(context, objectCreation); // Vararg parameter is the one that takes all arguments for mocked class constructor - var varArgsConstructorParameter = constructorSymbol.Parameters.FirstOrDefault(x => x.IsParams); + var varArgsConstructorParameter = constructorSymbol?.Parameters.FirstOrDefault(x => x.IsParams); // Vararg parameter are not used, so there are no arguments for mocked class constructor if (varArgsConstructorParameter == null) return; + + Debug.Assert(constructorSymbol != null, nameof(constructorSymbol) + " != null"); + + if (constructorSymbol == null) + { + return; + } + var varArgsConstructorParameterIdx = constructorSymbol.Parameters.IndexOf(varArgsConstructorParameter); // Find mocked type @@ -47,11 +57,13 @@ private static void Analyze(SyntaxNodeAnalysisContext context) if (mockedTypeSymbol == null) return; // Skip first argument if it is not vararg - typically it is MockingBehavior argument - var constructorArguments = objectCreation.ArgumentList.Arguments.Skip(varArgsConstructorParameterIdx == 0 ? 0 : 1).ToArray(); + var constructorArguments = objectCreation.ArgumentList?.Arguments.Skip(varArgsConstructorParameterIdx == 0 ? 0 : 1).ToArray(); if (!mockedTypeSymbol.IsAbstract) { - if (IsConstructorMismatch(context, objectCreation, genericName, constructorArguments)) + if (constructorArguments != null + && IsConstructorMismatch(context, objectCreation, genericName, constructorArguments) + && objectCreation.ArgumentList != null) { var diagnostic = Diagnostic.Create(Rule, objectCreation.ArgumentList.GetLocation()); context.ReportDiagnostic(diagnostic); @@ -64,33 +76,38 @@ private static void Analyze(SyntaxNodeAnalysisContext context) // The mocked symbol is abstract, so we need to check if the constructor arguments match the abstract class constructor // Extract types of arguments passed in the constructor call - var argumentTypes = constructorArguments - .Select(arg => context.SemanticModel.GetTypeInfo(arg.Expression, context.CancellationToken).Type) - .ToArray(); - - // Check all constructors of the abstract type - foreach (var constructor in mockedTypeSymbol.Constructors) + if (constructorArguments != null) { - if (AreParametersMatching(constructor.Parameters, argumentTypes)) + ITypeSymbol[] argumentTypes = constructorArguments + .Select(arg => context.SemanticModel.GetTypeInfo(arg.Expression, context.CancellationToken).Type) + .ToArray()!; + + // Check all constructors of the abstract type + for (int i = 0; i < mockedTypeSymbol.Constructors.Length; i++) { - return; // Found a matching constructor + IMethodSymbol constructor = mockedTypeSymbol.Constructors[i]; + if (AreParametersMatching(constructor.Parameters, argumentTypes)) + { + return; // Found a matching constructor + } } } - var diagnostic = Diagnostic.Create(Rule, objectCreation.ArgumentList.GetLocation()); + Debug.Assert(objectCreation.ArgumentList != null, "objectCreation.ArgumentList != null"); + + var diagnostic = Diagnostic.Create(Rule, objectCreation.ArgumentList?.GetLocation()); context.ReportDiagnostic(diagnostic); } } - private static INamedTypeSymbol GetMockedSymbol( + private static INamedTypeSymbol? GetMockedSymbol( SyntaxNodeAnalysisContext context, GenericNameSyntax genericName) { var typeArguments = genericName.TypeArgumentList.Arguments; - if (typeArguments == null || typeArguments.Count != 1) return null; + if (typeArguments.Count != 1) return null; var mockedTypeSymbolInfo = context.SemanticModel.GetSymbolInfo(typeArguments[0], context.CancellationToken); - var mockedTypeSymbol = mockedTypeSymbolInfo.Symbol as INamedTypeSymbol; - if (mockedTypeSymbol == null || mockedTypeSymbol.TypeKind != TypeKind.Class) return null; + if (mockedTypeSymbolInfo.Symbol is not INamedTypeSymbol { TypeKind: TypeKind.Class } mockedTypeSymbol) return null; return mockedTypeSymbol; } @@ -105,7 +122,7 @@ private static bool AreParametersMatching(ImmutableArray const // Check if each parameter type matches in order for (int i = 0; i < constructorParameters.Length; i++) { - if (!constructorParameters[i].Type.Equals(argumentTypes2[i])) + if (!constructorParameters[i].Type.Equals(argumentTypes2[i], SymbolEqualityComparer.IncludeNullability)) { return false; } @@ -114,7 +131,7 @@ private static bool AreParametersMatching(ImmutableArray const return true; } - private static GenericNameSyntax GetGenericNameSyntax(TypeSyntax typeSyntax) + private static GenericNameSyntax? GetGenericNameSyntax(TypeSyntax typeSyntax) { if (typeSyntax is GenericNameSyntax genericNameSyntax) { @@ -131,15 +148,19 @@ private static GenericNameSyntax GetGenericNameSyntax(TypeSyntax typeSyntax) private static bool IsMockGenericType(GenericNameSyntax genericName) { - return genericName?.Identifier.Text == "Mock" && genericName.TypeArgumentList.Arguments.Count == 1; + return string.Equals(genericName.Identifier.Text, "Mock", StringComparison.Ordinal) + && genericName.TypeArgumentList.Arguments.Count == 1; } - private static IMethodSymbol GetConstructorSymbol(SyntaxNodeAnalysisContext context, ObjectCreationExpressionSyntax objectCreation) + private static IMethodSymbol? GetConstructorSymbol(SyntaxNodeAnalysisContext context, ObjectCreationExpressionSyntax objectCreation) { var constructorSymbolInfo = context.SemanticModel.GetSymbolInfo(objectCreation, context.CancellationToken); var constructorSymbol = constructorSymbolInfo.Symbol as IMethodSymbol; return constructorSymbol?.MethodKind == MethodKind.Constructor && - constructorSymbol.ContainingType?.ConstructedFrom.ToDisplayString() == "Moq.Mock" + string.Equals( + constructorSymbol.ContainingType?.ConstructedFrom.ToDisplayString(), + "Moq.Mock", + StringComparison.Ordinal) ? constructorSymbol : null; } diff --git a/Source/Moq.Analyzers/Diagnostics.cs b/Source/Moq.Analyzers/Diagnostics.cs index f643c369..008f6f2c 100644 --- a/Source/Moq.Analyzers/Diagnostics.cs +++ b/Source/Moq.Analyzers/Diagnostics.cs @@ -24,7 +24,6 @@ internal static class Diagnostics internal const string NoMethodsInPropertySetupTitle = "Moq: Property setup used for a method"; internal const string NoMethodsInPropertySetupMessage = "SetupGet/SetupSet should be used for properties, not for methods."; - internal const string SetupShouldBeUsedOnlyForOverridableMembersId = "Moq1200"; internal const string SetupShouldBeUsedOnlyForOverridableMembersTitle = "Moq: Invalid setup parameter"; internal const string SetupShouldBeUsedOnlyForOverridableMembersMessage = "Setup should be used only for overridable members."; diff --git a/Source/Moq.Analyzers/Helpers.cs b/Source/Moq.Analyzers/Helpers.cs index 96f7ed87..347eeca0 100644 --- a/Source/Moq.Analyzers/Helpers.cs +++ b/Source/Moq.Analyzers/Helpers.cs @@ -1,84 +1,89 @@ -using System.Text.RegularExpressions; +using System.Diagnostics; +using System.Text.RegularExpressions; namespace Moq.Analyzers; internal static class Helpers { - private static MoqMethodDescriptor moqSetupMethodDescriptor = new MoqMethodDescriptor("Setup", new Regex("^Moq\\.Mock<.*>\\.Setup\\.*")); + private static readonly MoqMethodDescriptor MoqSetupMethodDescriptor = new("Setup", new Regex("^Moq\\.Mock<.*>\\.Setup\\.*")); - private static MoqMethodDescriptor moqAsMethodDescriptor = new MoqMethodDescriptor("As", new Regex("^Moq\\.Mock\\.As<\\.*"), isGeneric: true); + private static readonly MoqMethodDescriptor MoqAsMethodDescriptor = new("As", new Regex("^Moq\\.Mock\\.As<\\.*"), isGeneric: true); internal static bool IsMoqSetupMethod(SemanticModel semanticModel, MemberAccessExpressionSyntax method) { - return moqSetupMethodDescriptor.IsMoqMethod(semanticModel, method); + return MoqSetupMethodDescriptor.IsMoqMethod(semanticModel, method); } internal static bool IsMoqAsMethod(SemanticModel semanticModel, MemberAccessExpressionSyntax method) { - return moqAsMethodDescriptor.IsMoqMethod(semanticModel, method); + return MoqAsMethodDescriptor.IsMoqMethod(semanticModel, method); } internal static bool IsCallbackOrReturnInvocation(SemanticModel semanticModel, InvocationExpressionSyntax callbackOrReturnsInvocation) { var callbackOrReturnsMethod = callbackOrReturnsInvocation.Expression as MemberAccessExpressionSyntax; - var methodName = callbackOrReturnsMethod?.Name.ToString(); - // First fast check before walking semantic model - if (methodName != "Callback" && methodName != "Returns") + Debug.Assert(callbackOrReturnsMethod != null, nameof(callbackOrReturnsMethod) + " != null"); + + if (callbackOrReturnsMethod == null) { return false; } - var symbolInfo = semanticModel.GetSymbolInfo(callbackOrReturnsMethod); - if (symbolInfo.CandidateReason == CandidateReason.OverloadResolutionFailure) - { - return symbolInfo.CandidateSymbols.Any(s => IsCallbackOrReturnSymbol(s)); - } - else if (symbolInfo.CandidateReason == CandidateReason.None) + var methodName = callbackOrReturnsMethod.Name.ToString(); + + // First fast check before walking semantic model + if (!string.Equals(methodName, "Callback", StringComparison.Ordinal) + && !string.Equals(methodName, "Returns", StringComparison.Ordinal)) { - return IsCallbackOrReturnSymbol(symbolInfo.Symbol); + return false; } - return false; + var symbolInfo = semanticModel.GetSymbolInfo(callbackOrReturnsMethod); + return symbolInfo.CandidateReason switch + { + CandidateReason.OverloadResolutionFailure => symbolInfo.CandidateSymbols.Any(IsCallbackOrReturnSymbol), + CandidateReason.None => IsCallbackOrReturnSymbol(symbolInfo.Symbol), + _ => false, + }; } - internal static InvocationExpressionSyntax FindSetupMethodFromCallbackInvocation(SemanticModel semanticModel, ExpressionSyntax expression) + internal static InvocationExpressionSyntax? FindSetupMethodFromCallbackInvocation(SemanticModel semanticModel, ExpressionSyntax expression) { var invocation = expression as InvocationExpressionSyntax; - var method = invocation?.Expression as MemberAccessExpressionSyntax; - if (method == null) return null; + if (invocation?.Expression is not MemberAccessExpressionSyntax method) return null; if (IsMoqSetupMethod(semanticModel, method)) return invocation; return FindSetupMethodFromCallbackInvocation(semanticModel, method.Expression); } - internal static InvocationExpressionSyntax FindMockedMethodInvocationFromSetupMethod(InvocationExpressionSyntax setupInvocation) + internal static InvocationExpressionSyntax? FindMockedMethodInvocationFromSetupMethod(InvocationExpressionSyntax? setupInvocation) { - var setupLambdaArgument = setupInvocation?.ArgumentList.Arguments[0]?.Expression as LambdaExpressionSyntax; + var setupLambdaArgument = setupInvocation?.ArgumentList.Arguments[0].Expression as LambdaExpressionSyntax; return setupLambdaArgument?.Body as InvocationExpressionSyntax; } - internal static ExpressionSyntax FindMockedMemberExpressionFromSetupMethod(InvocationExpressionSyntax setupInvocation) + internal static ExpressionSyntax? FindMockedMemberExpressionFromSetupMethod(InvocationExpressionSyntax? setupInvocation) { - var setupLambdaArgument = setupInvocation?.ArgumentList.Arguments[0]?.Expression as LambdaExpressionSyntax; + var setupLambdaArgument = setupInvocation?.ArgumentList.Arguments[0].Expression as LambdaExpressionSyntax; return setupLambdaArgument?.Body as ExpressionSyntax; } - internal static IEnumerable GetAllMatchingMockedMethodSymbolsFromSetupMethodInvocation(SemanticModel semanticModel, InvocationExpressionSyntax setupMethodInvocation) + internal static IEnumerable GetAllMatchingMockedMethodSymbolsFromSetupMethodInvocation(SemanticModel semanticModel, InvocationExpressionSyntax? setupMethodInvocation) { - var setupLambdaArgument = setupMethodInvocation?.ArgumentList.Arguments[0]?.Expression as LambdaExpressionSyntax; + var setupLambdaArgument = setupMethodInvocation?.ArgumentList.Arguments[0].Expression as LambdaExpressionSyntax; var mockedMethodInvocation = setupLambdaArgument?.Body as InvocationExpressionSyntax; return GetAllMatchingSymbols(semanticModel, mockedMethodInvocation); } - internal static IEnumerable GetAllMatchingSymbols(SemanticModel semanticModel, ExpressionSyntax expression) + internal static IEnumerable GetAllMatchingSymbols(SemanticModel semanticModel, ExpressionSyntax? expression) where T : class { var matchingSymbols = new List(); if (expression != null) { var symbolInfo = semanticModel.GetSymbolInfo(expression); - if (symbolInfo.CandidateReason == CandidateReason.None && symbolInfo.Symbol is T) + if (symbolInfo is { CandidateReason: CandidateReason.None, Symbol: T }) { matchingSymbols.Add(symbolInfo.Symbol as T); } @@ -91,12 +96,12 @@ internal static IEnumerable GetAllMatchingSymbols(SemanticModel semanticMo return matchingSymbols; } - private static bool IsCallbackOrReturnSymbol(ISymbol symbol) + private static bool IsCallbackOrReturnSymbol(ISymbol? symbol) { // TODO: Check what is the best way to do such checks - var methodSymbol = symbol as IMethodSymbol; - if (methodSymbol == null) return false; + if (symbol is not IMethodSymbol methodSymbol) return false; var methodName = methodSymbol.ToString(); - return methodName.StartsWith("Moq.Language.ICallback") || methodName.StartsWith("Moq.Language.IReturns"); + return methodName.StartsWith("Moq.Language.ICallback", StringComparison.Ordinal) + || methodName.StartsWith("Moq.Language.IReturns", StringComparison.Ordinal); } } diff --git a/Source/Moq.Analyzers/Moq.Analyzers.csproj b/Source/Moq.Analyzers/Moq.Analyzers.csproj index 0f22d6aa..41cb041f 100644 --- a/Source/Moq.Analyzers/Moq.Analyzers.csproj +++ b/Source/Moq.Analyzers/Moq.Analyzers.csproj @@ -6,6 +6,7 @@ true false true + true diff --git a/Source/Moq.Analyzers/MoqMethodDescriptor.cs b/Source/Moq.Analyzers/MoqMethodDescriptor.cs index 560eae80..b3a457e4 100644 --- a/Source/Moq.Analyzers/MoqMethodDescriptor.cs +++ b/Source/Moq.Analyzers/MoqMethodDescriptor.cs @@ -1,14 +1,15 @@ -using System.Text.RegularExpressions; +using System.Diagnostics; +using System.Text.RegularExpressions; namespace Moq.Analyzers; internal class MoqMethodDescriptor { - private readonly bool isGeneric; + private readonly bool _isGeneric; public MoqMethodDescriptor(string shortMethodName, Regex fullMethodNamePattern, bool isGeneric = false) { - this.isGeneric = isGeneric; + _isGeneric = isGeneric; ShortMethodName = shortMethodName; FullMethodNamePattern = fullMethodNamePattern; } @@ -17,35 +18,41 @@ public MoqMethodDescriptor(string shortMethodName, Regex fullMethodNamePattern, private Regex FullMethodNamePattern { get; } - public bool IsMoqMethod(SemanticModel semanticModel, MemberAccessExpressionSyntax method) + public bool IsMoqMethod(SemanticModel semanticModel, MemberAccessExpressionSyntax? method) { var methodName = method?.Name.ToString(); + Debug.Assert(!string.IsNullOrEmpty(methodName), nameof(methodName) + " != null or empty"); + + if (string.IsNullOrEmpty(methodName)) return false; + // First fast check before walking semantic model - if (DoesShortMethodMatch(methodName) == false) return false; + if (!DoesShortMethodMatch(methodName!)) return false; - var symbolInfo = semanticModel.GetSymbolInfo(method); - if (symbolInfo.CandidateReason == CandidateReason.OverloadResolutionFailure) - { - return symbolInfo.CandidateSymbols.OfType() - .Any(s => this.FullMethodNamePattern.IsMatch(s.ToString())); - } - else if (symbolInfo.CandidateReason == CandidateReason.None) + Debug.Assert(method != null, nameof(method) + " != null"); + + if (method == null) { - // TODO: Replace regex with something more elegant - return symbolInfo.Symbol is IMethodSymbol && - this.FullMethodNamePattern.IsMatch(symbolInfo.Symbol.ToString()); + return false; } - return false; + var symbolInfo = semanticModel.GetSymbolInfo(method); + return symbolInfo.CandidateReason switch + { + CandidateReason.OverloadResolutionFailure => symbolInfo.CandidateSymbols.OfType().Any(s => FullMethodNamePattern.IsMatch(s.ToString())), + CandidateReason.None => symbolInfo.Symbol is IMethodSymbol && + FullMethodNamePattern.IsMatch(symbolInfo.Symbol.ToString()), + _ => false, + }; } private bool DoesShortMethodMatch(string methodName) { - if (isGeneric) + if (_isGeneric) { - return methodName.StartsWith($"{this.ShortMethodName}<"); + return methodName.StartsWith($"{ShortMethodName}<", StringComparison.Ordinal); } - return methodName == this.ShortMethodName; + + return string.Equals(methodName, ShortMethodName, StringComparison.Ordinal); } } diff --git a/Source/Moq.Analyzers/NoConstructorArgumentsForInterfaceMockAnalyzer.cs b/Source/Moq.Analyzers/NoConstructorArgumentsForInterfaceMockAnalyzer.cs index 8c46ef66..c5c81d40 100644 --- a/Source/Moq.Analyzers/NoConstructorArgumentsForInterfaceMockAnalyzer.cs +++ b/Source/Moq.Analyzers/NoConstructorArgumentsForInterfaceMockAnalyzer.cs @@ -1,3 +1,5 @@ +using System.Diagnostics; + namespace Moq.Analyzers; [DiagnosticAnalyzer(LanguageNames.CSharp)] @@ -28,38 +30,44 @@ private static void Analyze(SyntaxNodeAnalysisContext context) var objectCreation = (ObjectCreationExpressionSyntax)context.Node; // TODO Think how to make this piece more elegant while fast - GenericNameSyntax genericName = objectCreation.Type as GenericNameSyntax; - if (objectCreation.Type is QualifiedNameSyntax) + GenericNameSyntax? genericName = objectCreation.Type as GenericNameSyntax; + if (objectCreation.Type is QualifiedNameSyntax qualifiedName) { - var qualifiedName = objectCreation.Type as QualifiedNameSyntax; genericName = qualifiedName.Right as GenericNameSyntax; } if (genericName?.Identifier == null || genericName.TypeArgumentList == null) return; // Quick and dirty check - if (genericName.Identifier.ToFullString() != "Mock") return; + if (!string.Equals(genericName.Identifier.ToFullString(), "Mock", StringComparison.Ordinal)) return; // Full check var constructorSymbolInfo = context.SemanticModel.GetSymbolInfo(objectCreation, context.CancellationToken); - var constructorSymbol = constructorSymbolInfo.Symbol as IMethodSymbol; - if (constructorSymbol == null || 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 (constructorSymbol.ContainingType.ConstructedFrom.ToDisplayString() != "Moq.Mock") return; + if (!string.Equals( + constructorSymbol.ContainingType.ConstructedFrom.ToDisplayString(), + "Moq.Mock", + StringComparison.Ordinal)) + { + return; + } + if (constructorSymbol.Parameters == null || constructorSymbol.Parameters.Length == 0) return; if (!constructorSymbol.Parameters.Any(x => x.IsParams)) return; // Find mocked type var typeArguments = genericName.TypeArgumentList.Arguments; - if (typeArguments == null || typeArguments.Count != 1) return; + if (typeArguments.Count != 1) return; var symbolInfo = context.SemanticModel.GetSymbolInfo(typeArguments[0], context.CancellationToken); - var symbol = symbolInfo.Symbol as INamedTypeSymbol; - if (symbol == null) return; + if (symbolInfo.Symbol is not INamedTypeSymbol symbol) return; // Checked mocked type if (symbol.TypeKind == TypeKind.Interface) { - var diagnostic = Diagnostic.Create(Rule, objectCreation.ArgumentList.GetLocation()); + Debug.Assert(objectCreation.ArgumentList != null, "objectCreation.ArgumentList != null"); + + var diagnostic = Diagnostic.Create(Rule, objectCreation.ArgumentList?.GetLocation()); context.ReportDiagnostic(diagnostic); } } diff --git a/Source/Moq.Analyzers/NoMethodsInPropertySetupAnalyzer.cs b/Source/Moq.Analyzers/NoMethodsInPropertySetupAnalyzer.cs index 870eef1e..70d8a9fd 100644 --- a/Source/Moq.Analyzers/NoMethodsInPropertySetupAnalyzer.cs +++ b/Source/Moq.Analyzers/NoMethodsInPropertySetupAnalyzer.cs @@ -27,9 +27,9 @@ private static void Analyze(SyntaxNodeAnalysisContext context) { var setupGetOrSetInvocation = (InvocationExpressionSyntax)context.Node; - var setupGetOrSetMethod = setupGetOrSetInvocation.Expression as MemberAccessExpressionSyntax; - if (setupGetOrSetMethod == null) return; - if (setupGetOrSetMethod.Name.ToFullString() != "SetupGet" && setupGetOrSetMethod.Name.ToFullString() != "SetupSet") return; + 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; var mockedMethodCall = Helpers.FindMockedMethodInvocationFromSetupMethod(setupGetOrSetInvocation); if (mockedMethodCall == null) return; diff --git a/Source/Moq.Analyzers/NoSealedClassMocksAnalyzer.cs b/Source/Moq.Analyzers/NoSealedClassMocksAnalyzer.cs index 8e1f0cd1..436f826f 100644 --- a/Source/Moq.Analyzers/NoSealedClassMocksAnalyzer.cs +++ b/Source/Moq.Analyzers/NoSealedClassMocksAnalyzer.cs @@ -28,31 +28,31 @@ private static void Analyze(SyntaxNodeAnalysisContext context) var objectCreation = (ObjectCreationExpressionSyntax)context.Node; // TODO Think how to make this piece more elegant while fast - GenericNameSyntax genericName = objectCreation.Type as GenericNameSyntax; - if (objectCreation.Type is QualifiedNameSyntax) + GenericNameSyntax? genericName = objectCreation.Type as GenericNameSyntax; + if (objectCreation.Type is QualifiedNameSyntax qualifiedName) { - var qualifiedName = objectCreation.Type as QualifiedNameSyntax; genericName = qualifiedName.Right as GenericNameSyntax; } if (genericName?.Identifier == null || genericName.TypeArgumentList == null) return; // Quick and dirty check - if (genericName.Identifier.ToFullString() != "Mock") return; + if (!string.Equals(genericName.Identifier.ToFullString(), "Mock", StringComparison.Ordinal)) return; // Full check var constructorSymbolInfo = context.SemanticModel.GetSymbolInfo(objectCreation, context.CancellationToken); - var constructorSymbol = constructorSymbolInfo.Symbol as IMethodSymbol; - if (constructorSymbol == null || 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 (constructorSymbol.ContainingType.ConstructedFrom.ToDisplayString() != "Moq.Mock") return; + if (!string.Equals( + constructorSymbol.ContainingType.ConstructedFrom.ToDisplayString(), + "Moq.Mock", + StringComparison.Ordinal)) return; // Find mocked type var typeArguments = genericName.TypeArgumentList.Arguments; - if (typeArguments == null || typeArguments.Count != 1) return; + if (typeArguments.Count != 1) return; var symbolInfo = context.SemanticModel.GetSymbolInfo(typeArguments[0], context.CancellationToken); - var symbol = symbolInfo.Symbol as INamedTypeSymbol; - if (symbol == null) return; + if (symbolInfo.Symbol is not INamedTypeSymbol symbol) return; // Checked mocked type if (symbol.IsSealed && symbol.TypeKind != TypeKind.Delegate) diff --git a/Source/Moq.Analyzers/SetupShouldBeUsedOnlyForOverridableMembersAnalyzer.cs b/Source/Moq.Analyzers/SetupShouldBeUsedOnlyForOverridableMembersAnalyzer.cs index 44207e62..75cc2f79 100644 --- a/Source/Moq.Analyzers/SetupShouldBeUsedOnlyForOverridableMembersAnalyzer.cs +++ b/Source/Moq.Analyzers/SetupShouldBeUsedOnlyForOverridableMembersAnalyzer.cs @@ -33,19 +33,17 @@ private static void Analyze(SyntaxNodeAnalysisContext context) } var symbolInfo = context.SemanticModel.GetSymbolInfo(mockedMemberExpression, context.CancellationToken); - if (symbolInfo.Symbol is IPropertySymbol || symbolInfo.Symbol is IMethodSymbol) + if (symbolInfo.Symbol is IPropertySymbol or IMethodSymbol + && !IsMethodOverridable(symbolInfo.Symbol)) { - if (IsMethodOverridable(symbolInfo.Symbol) == false) - { - var diagnostic = Diagnostic.Create(Rule, mockedMemberExpression.GetLocation()); - context.ReportDiagnostic(diagnostic); - } + var diagnostic = Diagnostic.Create(Rule, mockedMemberExpression.GetLocation()); + context.ReportDiagnostic(diagnostic); } } } private static bool IsMethodOverridable(ISymbol methodSymbol) { - return methodSymbol.IsSealed == false && (methodSymbol.IsVirtual || methodSymbol.IsAbstract || methodSymbol.IsOverride); + return !methodSymbol.IsSealed && (methodSymbol.IsVirtual || methodSymbol.IsAbstract || methodSymbol.IsOverride); } } diff --git a/Source/Moq.Analyzers/SetupShouldNotIncludeAsyncResultAnalyzer.cs b/Source/Moq.Analyzers/SetupShouldNotIncludeAsyncResultAnalyzer.cs index 28d024ac..9bcfe052 100644 --- a/Source/Moq.Analyzers/SetupShouldNotIncludeAsyncResultAnalyzer.cs +++ b/Source/Moq.Analyzers/SetupShouldNotIncludeAsyncResultAnalyzer.cs @@ -33,31 +33,29 @@ private static void Analyze(SyntaxNodeAnalysisContext context) } var symbolInfo = context.SemanticModel.GetSymbolInfo(mockedMemberExpression, context.CancellationToken); - if (symbolInfo.Symbol is IPropertySymbol || symbolInfo.Symbol is IMethodSymbol) + if ((symbolInfo.Symbol is IPropertySymbol || symbolInfo.Symbol is IMethodSymbol) + && !IsMethodOverridable(symbolInfo.Symbol) + && IsMethodReturnTypeTask(symbolInfo.Symbol)) { - if (IsMethodOverridable(symbolInfo.Symbol) == false && - IsMethodReturnTypeTask(symbolInfo.Symbol) == true) - { - var diagnostic = Diagnostic.Create(Rule, mockedMemberExpression.GetLocation()); - context.ReportDiagnostic(diagnostic); - } + var diagnostic = Diagnostic.Create(Rule, mockedMemberExpression.GetLocation()); + context.ReportDiagnostic(diagnostic); } } } private static bool IsMethodOverridable(ISymbol methodSymbol) { - return methodSymbol.IsSealed == false && (methodSymbol.IsVirtual || methodSymbol.IsAbstract || methodSymbol.IsOverride); + return !methodSymbol.IsSealed + && (methodSymbol.IsVirtual || methodSymbol.IsAbstract || methodSymbol.IsOverride); } private static bool IsMethodReturnTypeTask(ISymbol methodSymbol) { var type = methodSymbol.ToDisplayString(); - return type != null && - (type == "System.Threading.Tasks.Task" || - type == "System.Threading.ValueTask" || - type.StartsWith("System.Threading.Tasks.Task<", StringComparison.Ordinal) || - type.StartsWith("System.Threading.Tasks.ValueTask<", StringComparison.Ordinal) && - type.EndsWith(".Result", StringComparison.Ordinal)); + 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) + || (type.StartsWith("System.Threading.Tasks.ValueTask<", StringComparison.Ordinal) + && type.EndsWith(".Result", StringComparison.Ordinal)); } }