Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 9 additions & 9 deletions Source/Moq.Analyzers/AsShouldBeUsedOnlyForInterfaceAnalyzer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Composition;
using System.Diagnostics;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;

Expand All @@ -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<ParameterListSyntax>().First();
ParameterListSyntax? badArgumentListSyntax = root.FindToken(diagnosticSpan.Start)
.Parent?
.AncestorsAndSelf()
.OfType<ParameterListSyntax>()
.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<Document> FixCallbackSignature(SyntaxNode root, Document document, ParameterListSyntax oldParameters, CancellationToken cancellationToken)
private async Task<Document> 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<AttributeListSyntax>), 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);
}
}
63 changes: 42 additions & 21 deletions Source/Moq.Analyzers/ConstructorArgumentsShouldMatchAnalyzer.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using System.Diagnostics;

namespace Moq.Analyzers;

[DiagnosticAnalyzer(LanguageNames.CSharp)]
Expand Down Expand Up @@ -36,22 +38,32 @@ 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
var mockedTypeSymbol = GetMockedSymbol(context, genericName);
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);
Expand All @@ -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;
}

Expand All @@ -105,7 +122,7 @@ private static bool AreParametersMatching(ImmutableArray<IParameterSymbol> 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;
}
Expand All @@ -114,7 +131,7 @@ private static bool AreParametersMatching(ImmutableArray<IParameterSymbol> const
return true;
}

private static GenericNameSyntax GetGenericNameSyntax(TypeSyntax typeSyntax)
private static GenericNameSyntax? GetGenericNameSyntax(TypeSyntax typeSyntax)
{
if (typeSyntax is GenericNameSyntax genericNameSyntax)
{
Expand All @@ -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<T>"
string.Equals(
constructorSymbol.ContainingType?.ConstructedFrom.ToDisplayString(),
"Moq.Mock<T>",
StringComparison.Ordinal)
? constructorSymbol
: null;
}
Expand Down
1 change: 0 additions & 1 deletion Source/Moq.Analyzers/Diagnostics.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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.";
Expand Down
Loading