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
24 changes: 23 additions & 1 deletion .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -82,13 +82,17 @@ 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

# 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]
Expand Down Expand Up @@ -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
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
4 changes: 4 additions & 0 deletions Source/Moq.Analyzers.Test/.editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ public static IEnumerable<object[]> TestData()
["""new Mock<AbstractGenericClassDefaultCtor<object>>{|Moq1002:(42)|};"""],
["""new Mock<AbstractGenericClassDefaultCtor<object>>();"""],
["""new Mock<AbstractGenericClassDefaultCtor<object>>(MockBehavior.Default);"""],

// TODO: "I think this _should_ fail, but currently passes. Tracked by #55."
// ["""new Mock<AbstractClassWithCtor>();"""],
["""new Mock<AbstractClassWithCtor>{|Moq1002:("42")|};"""],
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 @@ -23,7 +23,6 @@
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.CodeFix.Testing" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" />
<PackageReference Include="Verify.Nupkg" />
<PackageReference Include="Verify.Xunit" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Moq.Analyzers\Moq.Analyzers.csproj" AddPackageAsOutput="true" />
Expand Down
15 changes: 5 additions & 10 deletions Source/Moq.Analyzers.Test/PackageTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<Document> FixCallbackSignatureAsync(SyntaxNode root, Document document, ParameterListSyntax? oldParameters, CancellationToken cancellationToken)
{
SemanticModel? semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);
Expand All @@ -80,10 +81,10 @@ private async Task<Document> 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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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)
{
Expand All @@ -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
Expand Down Expand Up @@ -130,9 +131,9 @@ private static bool AreParametersMatching(ImmutableArray<IParameterSymbol> 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;
}
Expand Down
32 changes: 21 additions & 11 deletions Source/Moq.Analyzers/Helpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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;
Expand Down Expand Up @@ -68,21 +70,29 @@ internal static IEnumerable<IMethodSymbol> GetAllMatchingMockedMethodSymbolsFrom
return 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)
where T : class
{
List<T>? matchingSymbols = new List<T>();
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<T>());
}
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);
}
else if (symbolInfo.CandidateReason == CandidateReason.OverloadResolutionFailure)
{
matchingSymbols.AddRange(symbolInfo.CandidateSymbols.OfType<T>());
}
else
{
throw new NotSupportedException("Symbol not supported.");
}

return matchingSymbols;
Expand Down
1 change: 1 addition & 0 deletions Source/Moq.Analyzers/MoqAsMethodDescriptor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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()))
Expand Down
1 change: 1 addition & 0 deletions Source/Moq.Analyzers/MoqSetupMethodDescriptor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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()))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<TypeSyntax> typeArguments = genericName.TypeArgumentList.Arguments;
Expand Down
1 change: 1 addition & 0 deletions Source/Moq.Analyzers/NoMethodsInPropertySetupAnalyzer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions Source/Moq.Analyzers/NoSealedClassMocksAnalyzer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Loading