Skip to content

Commit

Permalink
Updates the PosInfoMoq2000 rule to check the call to the static Mock.…
Browse files Browse the repository at this point in the history
…Verify() and Mock.VerifyAll() methods (#20).
  • Loading branch information
GillesTourreau committed Jun 13, 2024
1 parent 7ac0afb commit 5af9656
Show file tree
Hide file tree
Showing 4 changed files with 90 additions and 20 deletions.
35 changes: 28 additions & 7 deletions src/Moq.Analyzers/Analyzers/VerifyShouldBeCalledAnalyzer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ private static void Analyze(SyntaxNodeAnalysisContext context)
return;
}

var variableNameModel = context.SemanticModel.GetDeclaredSymbol(variableName);
var variableNameModel = context.SemanticModel.GetDeclaredSymbol(variableName, context.CancellationToken);

if (variableNameModel is null)
{
Expand All @@ -81,7 +81,7 @@ private static void Analyze(SyntaxNodeAnalysisContext context)
// Retrieve all method invocation expressions.
var invocationExpressions = parentMethod.DescendantNodes().OfType<InvocationExpressionSyntax>();

var verifyAllCalled = invocationExpressions.Any(expression => IsMockVerifyAllInvocation(expression, variableNameModel, moqSymbols, context.SemanticModel));
var verifyAllCalled = invocationExpressions.Any(expression => IsMockVerifyAllInvocation(expression, variableNameModel, moqSymbols, context.SemanticModel, context.CancellationToken));

if (!verifyAllCalled)
{
Expand All @@ -90,16 +90,15 @@ private static void Analyze(SyntaxNodeAnalysisContext context)
}
}

private static bool IsMockVerifyAllInvocation(InvocationExpressionSyntax invocation, ISymbol variableNameSymbol, MoqSymbols moqSymbols, SemanticModel semanticModel)
private static bool IsMockVerifyAllInvocation(InvocationExpressionSyntax invocation, ISymbol variableNameSymbol, MoqSymbols moqSymbols, SemanticModel semanticModel, CancellationToken cancellationToken)
{
if (invocation.Expression is not MemberAccessExpressionSyntax memberAccess)
{
return false;
}

// We check if a "VerifyAll()" method is called (currently we don't know if it is on the Mock object, it can be on other object type)
// but we try to use this condition here to stop quickly the analysis.
var verifyMethod = semanticModel.GetSymbolInfo(memberAccess);
// Check if the invocation expression is a Verify() / VerifyAll() methods.
var verifyMethod = semanticModel.GetSymbolInfo(memberAccess, cancellationToken);

if (verifyMethod.Symbol is null)
{
Expand All @@ -108,11 +107,33 @@ private static bool IsMockVerifyAllInvocation(InvocationExpressionSyntax invocat

if (!moqSymbols.IsVerifyMethod(verifyMethod.Symbol) && !moqSymbols.IsVerifyAllMethod(verifyMethod.Symbol))
{
if (!moqSymbols.IsVerifyStaticMethod(verifyMethod.Symbol) && !moqSymbols.IsVerifyAllStaticMethod(verifyMethod.Symbol))
{
return false;
}

// Special case, the static method Verify() or VerifyAll() has been called.
// In this case, iterate on each arguments of the method called and check if the variableNameSymbol has been passed.
foreach (var argument in invocation.ArgumentList.Arguments)
{
var argumentSymbol = semanticModel.GetSymbolInfo(argument.Expression, cancellationToken);

if (argumentSymbol.Symbol is null)
{
return false;
}

if (SymbolEqualityComparer.Default.Equals(argumentSymbol.Symbol, variableNameSymbol))
{
return true;
}
}

return false;
}

// Gets the variable name symbol.
var identifierSymbol = semanticModel.GetSymbolInfo(memberAccess.Expression);
var identifierSymbol = semanticModel.GetSymbolInfo(memberAccess.Expression, cancellationToken);

// If the variable name of .VerifyAll() does not match the variable, so the VerifyAll() was for other Mock instance.
if (!SymbolEqualityComparer.Default.Equals(identifierSymbol.Symbol, variableNameSymbol))
Expand Down
52 changes: 41 additions & 11 deletions src/Moq.Analyzers/MoqSymbols.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,38 +13,48 @@ internal sealed class MoqSymbols
{
private readonly INamedTypeSymbol mockBehaviorEnum;

private readonly INamedTypeSymbol mockClass;
private readonly INamedTypeSymbol mockGenericClass;

private readonly IReadOnlyList<IMethodSymbol> setupMethods;

private readonly IReadOnlyList<IMethodSymbol> setupProtectedMethods;

private readonly IReadOnlyList<IMethodSymbol> verifyMethods;

private readonly Lazy<IMethodSymbol> staticVerifyMethod;

private readonly Lazy<IMethodSymbol> staticVerifyAllMethod;

private readonly Lazy<IMethodSymbol> verifyAllMethod;

private readonly ISymbol mockBehaviorStrictField;

private readonly ISymbol isAnyTypeClass;

private readonly ISymbol asMethod;

private MoqSymbols(INamedTypeSymbol mockClass, INamedTypeSymbol mockBehaviorEnum, ISymbol isAnyTypeClass, INamedTypeSymbol protectedMockInterface)
private MoqSymbols(INamedTypeSymbol mockGenericClass, INamedTypeSymbol mockBehaviorEnum, ISymbol isAnyTypeClass, INamedTypeSymbol protectedMockInterface)
{
this.mockClass = mockClass;
this.mockGenericClass = mockGenericClass;
this.mockBehaviorEnum = mockBehaviorEnum;
this.isAnyTypeClass = isAnyTypeClass;

this.setupMethods = mockClass.GetMembers("Setup").OfType<IMethodSymbol>().ToArray();
this.setupMethods = mockGenericClass.GetMembers("Setup").OfType<IMethodSymbol>().ToArray();
this.mockBehaviorStrictField = mockBehaviorEnum.GetMembers("Strict").First();
this.setupProtectedMethods = protectedMockInterface.GetMembers("Setup").OfType<IMethodSymbol>().ToArray();
this.asMethod = mockClass.GetMembers("As").Single();
this.verifyMethods = mockClass.GetMembers("Verify").OfType<IMethodSymbol>().ToArray();
this.asMethod = mockGenericClass.GetMembers("As").Single();
this.verifyMethods = mockGenericClass.GetMembers("Verify").OfType<IMethodSymbol>().ToArray();

this.staticVerifyMethod = new Lazy<IMethodSymbol>(() => mockGenericClass.BaseType!.GetMembers("Verify").Where(m => m.IsStatic).OfType<IMethodSymbol>().Single());
this.staticVerifyAllMethod = new Lazy<IMethodSymbol>(() => mockGenericClass.BaseType!.GetMembers("VerifyAll").Where(m => m.IsStatic).OfType<IMethodSymbol>().Single());
this.verifyAllMethod = new Lazy<IMethodSymbol>(() => mockGenericClass.GetMembers("VerifyAll").OfType<IMethodSymbol>().Single());
}

public static MoqSymbols? FromCompilation(Compilation compilation)
{
var mockClass = compilation.GetTypeByMetadataName("Moq.Mock`1");
var mockGenericClass = compilation.GetTypeByMetadataName("Moq.Mock`1");

if (mockClass is null)
if (mockGenericClass is null)
{
return null;
}
Expand All @@ -70,7 +80,7 @@ private MoqSymbols(INamedTypeSymbol mockClass, INamedTypeSymbol mockBehaviorEnum
return null;
}

return new MoqSymbols(mockClass, mockBehaviorEnum, isAnyTypeClass, protectedMockInterface);
return new MoqSymbols(mockGenericClass, mockBehaviorEnum, isAnyTypeClass, protectedMockInterface);
}

public bool IsAnyType(ITypeSymbol symbol)
Expand All @@ -90,7 +100,7 @@ public bool IsMock(ISymbol? symbol)
return false;
}

if (!SymbolEqualityComparer.Default.Equals(symbol.OriginalDefinition, this.mockClass))
if (!SymbolEqualityComparer.Default.Equals(symbol.OriginalDefinition, this.mockGenericClass))
{
return false;
}
Expand Down Expand Up @@ -158,9 +168,29 @@ public bool IsVerifyMethod(ISymbol? symbol)
return false;
}

public bool IsVerifyStaticMethod(ISymbol symbol)
{
if (!SymbolEqualityComparer.Default.Equals(symbol, this.staticVerifyMethod.Value))
{
return false;
}

return true;
}

public bool IsVerifyAllMethod(ISymbol symbol)
{
if (symbol.Name != "VerifyAll")
if (!SymbolEqualityComparer.Default.Equals(symbol.OriginalDefinition, this.verifyAllMethod.Value))
{
return false;
}

return true;
}

public bool IsVerifyAllStaticMethod(ISymbol symbol)
{
if (!SymbolEqualityComparer.Default.Equals(symbol, this.staticVerifyAllMethod.Value))
{
return false;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@ public void TestMethod()
var mock2 = new Mock<I>();
var mock3 = new Mock<I>();
var mock4 = new Mock<I>();
var mock5 = new Mock<I>();
var mock6 = new Mock<I>();
var mock7 = new Mock<I>();
var mock8 = new Mock<I>();
var mock9 = new Mock<I>();
var mock10 = new Mock<I>();
new Mock<I>(); // No variable (ignored)
Expand All @@ -45,7 +51,13 @@ public void TestMethod()
mock2.Verify();
mock3.Verify(m => m.Method());
mock4.Verify(m => m.Method(), ""Foobar"");
}
Mock.VerifyAll(mock5, mock6);
Mock.Verify(mock7, mock8);
Mock<I>.VerifyAll(mock9);
Mock<I>.Verify(mock10);
}
}
public interface I
Expand Down
9 changes: 8 additions & 1 deletion tests/Moq.Analyzers.Tests/MoqLibrary.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ namespace Moq
using System;
using System.Linq.Expressions;
public class Mock<T>
public class Mock<T> : Mock
{
public Mock(MockBehavior _ = MockBehavior.Loose, params object[] args) { }
Expand All @@ -41,6 +41,13 @@ public void Verify<TResult>(Expression<Func<T, TResult>> _, string failedMessage
public object Property { get; set; }
}
public class Mock
{
public static void Verify(params Mock[] mocks) { }
public static void VerifyAll(params Mock[] mocks) { }
}
public enum MockBehavior { Strict, Loose }
public interface ISetup
Expand Down

0 comments on commit 5af9656

Please sign in to comment.