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
4 changes: 3 additions & 1 deletion .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -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
dotnet_diagnostic.AV2318.severity = none
# MA0026: Fix TODO comment
dotnet_diagnostic.MA0026.severity = none
Original file line number Diff line number Diff line change
Expand Up @@ -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<object[]> TestData()
{
return new object[][]
Expand Down
1 change: 0 additions & 1 deletion Source/Moq.Analyzers.Test/Moq.Analyzers.Test.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<NoWarn>1701;1702;SA1600;SA1402</NoWarn>
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using System.Diagnostics;

namespace Moq.Analyzers;

/// <summary>
Expand Down Expand Up @@ -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());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,6 @@ private async Task<Document> FixCallbackSignatureAsync(SyntaxNode root, Document
{
SemanticModel? semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);

Debug.Assert(semanticModel != null, nameof(semanticModel) + " != null");

if (semanticModel == null)
{
return document;
Expand All @@ -73,9 +71,9 @@ private async Task<Document> 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;
}
Expand Down
19 changes: 10 additions & 9 deletions Source/Moq.Analyzers/ConstructorArgumentsShouldMatchAnalyzer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -48,18 +49,17 @@ private static void Analyze(SyntaxNodeAnalysisContext context)
// Full check that we are calling new Mock<T>()
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);

Expand Down Expand Up @@ -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(),
Expand Down
28 changes: 16 additions & 12 deletions Source/Moq.Analyzers/Helpers.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Diagnostics;
using System.Linq.Expressions;

namespace Moq.Analyzers;

Expand All @@ -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)
Expand Down Expand Up @@ -67,32 +66,37 @@ internal static IEnumerable<IMethodSymbol> GetAllMatchingMockedMethodSymbolsFrom
LambdaExpressionSyntax? setupLambdaArgument = setupMethodInvocation?.ArgumentList.Arguments[0].Expression as LambdaExpressionSyntax;
InvocationExpressionSyntax? mockedMethodInvocation = setupLambdaArgument?.Body as InvocationExpressionSyntax;

return GetAllMatchingSymbols<IMethodSymbol>(semanticModel, mockedMethodInvocation);
return mockedMethodInvocation == null
? []
: GetAllMatchingSymbols<IMethodSymbol>(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<T> GetAllMatchingSymbols<T>(SemanticModel semanticModel, ExpressionSyntax? expression)
internal static IEnumerable<T> GetAllMatchingSymbols<T>(SemanticModel semanticModel, ExpressionSyntax expression)
where T : class
{
if (expression == null)
{
return Enumerable.Empty<T>();
}

List<T> 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)
{
matchingSymbols.AddRange(symbolInfo.CandidateSymbols.OfType<T>());
}
else
{
throw new NotSupportedException("Symbol not supported.");
return matchingSymbols;
}

return matchingSymbols;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand All @@ -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
Expand Down
2 changes: 2 additions & 0 deletions build/targets/codeanalysis/CodeAnalysis.props
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
<AnalysisMode>preview</AnalysisMode>
<WarningLevel>9999</WarningLevel>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<TreatWarningsAsErrors Condition=" '$(ContinuousIntegrationBuild)' == 'true' ">true</TreatWarningsAsErrors>
<MSBuildTreatWarningsAsErrors Condition=" '$(ContinuousIntegrationBuild)' == 'true' ">true</MSBuildTreatWarningsAsErrors>
</PropertyGroup>

<ItemGroup>
Expand Down