diff --git a/src/Analyzers/MockBehaviorDiagnosticAnalyzerBase.cs b/src/Analyzers/MockBehaviorDiagnosticAnalyzerBase.cs
index 52a6f8555..4bc4a337c 100644
--- a/src/Analyzers/MockBehaviorDiagnosticAnalyzerBase.cs
+++ b/src/Analyzers/MockBehaviorDiagnosticAnalyzerBase.cs
@@ -1,4 +1,4 @@
-using Microsoft.CodeAnalysis.Operations;
+using Microsoft.CodeAnalysis.Operations;
namespace Moq.Analyzers;
@@ -20,6 +20,147 @@ public override void Initialize(AnalysisContext context)
context.RegisterCompilationStartAction(RegisterCompilationStartAction);
}
+ ///
+ /// Extracts the mocked type name from the operation for use in diagnostic messages.
+ ///
+ /// The operation being analyzed.
+ /// The target method symbol.
+ /// The display name of the mocked type.
+ internal virtual string GetMockedTypeName(IOperation operation, IMethodSymbol target)
+ {
+ // For object creation (new Mock), get the type argument from the Mock type
+ if (operation is IObjectCreationOperation objectCreation
+ && objectCreation.Type is INamedTypeSymbol namedType
+ && namedType.TypeArguments.Length > 0)
+ {
+ return namedType.TypeArguments[0].ToDisplayString();
+ }
+
+ // For method invocation (Mock.Of), get the type argument from the method
+ if (operation is IInvocationOperation invocation && invocation.TargetMethod.TypeArguments.Length > 0)
+ {
+ return invocation.TargetMethod.TypeArguments[0].ToDisplayString();
+ }
+
+ // Try the containing type's type arguments (e.g. Mock.ctor)
+ if (target.ContainingType?.TypeArguments.Length > 0)
+ {
+ return target.ContainingType.TypeArguments[0].ToDisplayString();
+ }
+
+ return "T";
+ }
+
+ ///
+ /// Attempts to report a diagnostic for a MockBehavior parameter issue.
+ ///
+ /// The operation analysis context.
+ /// The method to check for MockBehavior parameter.
+ /// The known Moq symbols.
+ /// The diagnostic rule to report.
+ /// The type of edit for the code fix.
+ /// True if a diagnostic was reported; otherwise, false.
+ internal bool TryReportMockBehaviorDiagnostic(
+ OperationAnalysisContext context,
+ IMethodSymbol method,
+ MoqKnownSymbols knownSymbols,
+ DiagnosticDescriptor rule,
+ DiagnosticEditProperties.EditType editType)
+ {
+ if (!method.TryGetParameterOfType(knownSymbols.MockBehavior!, out IParameterSymbol? parameterMatch, cancellationToken: context.CancellationToken))
+ {
+ return false;
+ }
+
+ ImmutableDictionary properties = new DiagnosticEditProperties
+ {
+ TypeOfEdit = editType,
+ EditPosition = parameterMatch.Ordinal,
+ }.ToImmutableDictionary();
+
+ context.ReportDiagnostic(context.Operation.CreateDiagnostic(rule, properties));
+ return true;
+ }
+
+ ///
+ /// Attempts to report a diagnostic for a MockBehavior parameter issue, with the mocked type name.
+ ///
+ /// The operation analysis context.
+ /// The method to check for MockBehavior parameter.
+ /// The known Moq symbols.
+ /// The diagnostic rule to report.
+ /// The type of edit for the code fix.
+ /// The mocked type name to format into the diagnostic message.
+ /// True if a diagnostic was reported; otherwise, false.
+ internal bool TryReportMockBehaviorDiagnostic(
+ OperationAnalysisContext context,
+ IMethodSymbol method,
+ MoqKnownSymbols knownSymbols,
+ DiagnosticDescriptor rule,
+ DiagnosticEditProperties.EditType editType,
+ string mockedTypeName)
+ {
+ if (!method.TryGetParameterOfType(knownSymbols.MockBehavior!, out IParameterSymbol? parameterMatch, cancellationToken: context.CancellationToken))
+ {
+ return false;
+ }
+
+ ImmutableDictionary properties = new DiagnosticEditProperties
+ {
+ TypeOfEdit = editType,
+ EditPosition = parameterMatch.Ordinal,
+ }.ToImmutableDictionary();
+
+ context.ReportDiagnostic(context.Operation.CreateDiagnostic(rule, properties, mockedTypeName));
+ return true;
+ }
+
+ ///
+ /// Attempts to handle missing MockBehavior parameter by checking for overloads that accept it.
+ ///
+ /// The operation analysis context.
+ /// The MockBehavior parameter (should be null to trigger overload check).
+ /// The target method to check for overloads.
+ /// The known Moq symbols.
+ /// The diagnostic rule to report.
+ /// True if a diagnostic was reported; otherwise, false.
+ internal bool TryHandleMissingMockBehaviorParameter(
+ OperationAnalysisContext context,
+ IParameterSymbol? mockParameter,
+ IMethodSymbol target,
+ MoqKnownSymbols knownSymbols,
+ DiagnosticDescriptor rule)
+ {
+ // If the target method doesn't have a MockBehavior parameter, check if there's an overload that does
+ return mockParameter is null
+ && target.TryGetOverloadWithParameterOfType(knownSymbols.MockBehavior!, out IMethodSymbol? methodMatch, out _, cancellationToken: context.CancellationToken)
+ && TryReportMockBehaviorDiagnostic(context, methodMatch, knownSymbols, rule, DiagnosticEditProperties.EditType.Insert);
+ }
+
+ ///
+ /// Attempts to handle missing MockBehavior parameter by checking for overloads that accept it,
+ /// with the mocked type name.
+ ///
+ /// The operation analysis context.
+ /// The MockBehavior parameter (should be null to trigger overload check).
+ /// The target method to check for overloads.
+ /// The known Moq symbols.
+ /// The diagnostic rule to report.
+ /// The mocked type name to format into the diagnostic message.
+ /// True if a diagnostic was reported; otherwise, false.
+ internal bool TryHandleMissingMockBehaviorParameter(
+ OperationAnalysisContext context,
+ IParameterSymbol? mockParameter,
+ IMethodSymbol target,
+ MoqKnownSymbols knownSymbols,
+ DiagnosticDescriptor rule,
+ string mockedTypeName)
+ {
+ return mockParameter is null
+ && target.TryGetOverloadWithParameterOfType(knownSymbols.MockBehavior!, out IMethodSymbol? methodMatch, out _, cancellationToken: context.CancellationToken)
+ && TryReportMockBehaviorDiagnostic(context, methodMatch, knownSymbols, rule, DiagnosticEditProperties.EditType.Insert, mockedTypeName);
+ }
+
private protected abstract void AnalyzeCore(OperationAnalysisContext context, IMethodSymbol target, ImmutableArray arguments, MoqKnownSymbols knownSymbols);
private void RegisterCompilationStartAction(CompilationStartAnalysisContext context)
diff --git a/src/Analyzers/RaiseEventArgumentsShouldMatchEventSignatureAnalyzer.cs b/src/Analyzers/RaiseEventArgumentsShouldMatchEventSignatureAnalyzer.cs
index 9a70aaad8..8024008c9 100644
--- a/src/Analyzers/RaiseEventArgumentsShouldMatchEventSignatureAnalyzer.cs
+++ b/src/Analyzers/RaiseEventArgumentsShouldMatchEventSignatureAnalyzer.cs
@@ -82,7 +82,7 @@ private static void Analyze(SyntaxNodeAnalysisContext context, MoqKnownSymbols k
context.SemanticModel.TryGetEventNameFromLambdaSelector(eventSelector, out eventName);
}
- ValidateArgumentTypesWithEventName(context, eventArguments, expectedParameterTypes, invocation, eventName ?? "event");
+ context.ValidateEventArgumentTypes(eventArguments, expectedParameterTypes, invocation, Rule, eventName ?? "event");
}
private static bool TryGetRaiseMethodArguments(
@@ -105,43 +105,6 @@ private static bool TryGetRaiseMethodArguments(
knownSymbols);
}
- private static void ValidateArgumentTypesWithEventName(SyntaxNodeAnalysisContext context, ArgumentSyntax[] eventArguments, ITypeSymbol[] expectedParameterTypes, InvocationExpressionSyntax invocation, string eventName)
- {
- if (eventArguments.Length != expectedParameterTypes.Length)
- {
- Location location;
- if (eventArguments.Length < expectedParameterTypes.Length)
- {
- // Too few arguments: report on the invocation
- location = invocation.GetLocation();
- }
- else
- {
- // Too many arguments: report on the first extra argument
- location = eventArguments[expectedParameterTypes.Length].GetLocation();
- }
-
- Diagnostic diagnostic = location.CreateDiagnostic(Rule, eventName);
- context.ReportDiagnostic(diagnostic);
- return;
- }
-
- // Check each argument type matches the expected parameter type
- for (int i = 0; i < eventArguments.Length; i++)
- {
- TypeInfo argumentTypeInfo = context.SemanticModel.GetTypeInfo(eventArguments[i].Expression, context.CancellationToken);
- ITypeSymbol? argumentType = argumentTypeInfo.Type;
- ITypeSymbol expectedType = expectedParameterTypes[i];
-
- if (argumentType != null && !context.SemanticModel.HasConversion(argumentType, expectedType))
- {
- // Report on the specific argument with the wrong type
- Diagnostic diagnostic = eventArguments[i].GetLocation().CreateDiagnostic(Rule, eventName);
- context.ReportDiagnostic(diagnostic);
- }
- }
- }
-
private static bool IsRaiseMethodCall(SemanticModel semanticModel, InvocationExpressionSyntax invocation, MoqKnownSymbols knownSymbols)
{
SymbolInfo symbolInfo = semanticModel.GetSymbolInfo(invocation);
diff --git a/src/Analyzers/RaisesEventArgumentsShouldMatchEventSignatureAnalyzer.cs b/src/Analyzers/RaisesEventArgumentsShouldMatchEventSignatureAnalyzer.cs
index 09d6ad2f0..738bbd8b0 100644
--- a/src/Analyzers/RaisesEventArgumentsShouldMatchEventSignatureAnalyzer.cs
+++ b/src/Analyzers/RaisesEventArgumentsShouldMatchEventSignatureAnalyzer.cs
@@ -77,10 +77,15 @@ private static void Analyze(SyntaxNodeAnalysisContext context, MoqKnownSymbols k
return;
}
- // Extract event name from the lambda selector (first argument)
- string eventName = TryGetEventNameFromLambdaSelector(invocation, context.SemanticModel) ?? "event";
+ // Extract event name from the first argument (event selector lambda)
+ string? eventName = null;
+ if (invocation.ArgumentList.Arguments.Count > 0)
+ {
+ ExpressionSyntax eventSelector = invocation.ArgumentList.Arguments[0].Expression;
+ context.SemanticModel.TryGetEventNameFromLambdaSelector(eventSelector, out eventName);
+ }
- EventSyntaxExtensions.ValidateEventArgumentTypes(context, eventArguments, expectedParameterTypes, invocation, Rule, eventName);
+ context.ValidateEventArgumentTypes(eventArguments, expectedParameterTypes, invocation, Rule, eventName ?? "event");
}
private static bool TryGetRaisesMethodArguments(InvocationExpressionSyntax invocation, SemanticModel semanticModel, out ArgumentSyntax[] eventArguments, out ITypeSymbol[] expectedParameterTypes)
@@ -96,50 +101,4 @@ private static bool TryGetRaisesMethodArguments(InvocationExpressionSyntax invoc
return (success, eventType);
});
}
-
- ///
- /// Extracts the event name from a lambda selector of the form: x => x.EventName += null.
- ///
- /// The method invocation containing the lambda selector.
- /// The semantic model.
- /// The event name if found; otherwise null.
- private static string? TryGetEventNameFromLambdaSelector(InvocationExpressionSyntax invocation, SemanticModel semanticModel)
- {
- // Get the first argument which should be the lambda selector
- SeparatedSyntaxList arguments = invocation.ArgumentList.Arguments;
- if (arguments.Count < 1)
- {
- return null;
- }
-
- ExpressionSyntax eventSelector = arguments[0].Expression;
-
- // The event selector should be a lambda like: p => p.EventName += null
- if (eventSelector is not LambdaExpressionSyntax lambda)
- {
- return null;
- }
-
- // The body should be an assignment expression with += operator
- if (lambda.Body is not AssignmentExpressionSyntax assignment ||
- !assignment.OperatorToken.IsKind(SyntaxKind.PlusEqualsToken))
- {
- return null;
- }
-
- // The left side should be a member access to the event
- if (assignment.Left is not MemberAccessExpressionSyntax memberAccess)
- {
- return null;
- }
-
- // Get the symbol for the event
- SymbolInfo symbolInfo = semanticModel.GetSymbolInfo(memberAccess);
- if (symbolInfo.Symbol is IEventSymbol eventSymbol)
- {
- return eventSymbol.ToDisplayString();
- }
-
- return null;
- }
}
diff --git a/src/Analyzers/SetExplicitMockBehaviorAnalyzer.cs b/src/Analyzers/SetExplicitMockBehaviorAnalyzer.cs
index 6d5071479..06435c769 100644
--- a/src/Analyzers/SetExplicitMockBehaviorAnalyzer.cs
+++ b/src/Analyzers/SetExplicitMockBehaviorAnalyzer.cs
@@ -31,16 +31,14 @@ public class SetExplicitMockBehaviorAnalyzer : MockBehaviorDiagnosticAnalyzerBas
private protected override void AnalyzeCore(OperationAnalysisContext context, IMethodSymbol target, ImmutableArray arguments, MoqKnownSymbols knownSymbols)
{
// Extract the type name for the diagnostic message
- string typeName = GetMockedTypeName(context, target);
+ string typeName = GetMockedTypeName(context.Operation, target);
// Check if the target method has a parameter of type MockBehavior
IParameterSymbol? mockParameter = target.Parameters.DefaultIfNotSingle(parameter => parameter.Type.IsInstanceOf(knownSymbols.MockBehavior));
// If the target method doesn't have a MockBehavior parameter, check if there's an overload that does
- if (mockParameter is null && target.TryGetOverloadWithParameterOfType(knownSymbols.MockBehavior!, out IMethodSymbol? methodMatch, out _, cancellationToken: context.CancellationToken))
+ if (TryHandleMissingMockBehaviorParameter(context, mockParameter, target, knownSymbols, Rule, typeName))
{
- // Using a method that doesn't accept a MockBehavior parameter, however there's an overload that does
- ReportDiagnosticWithTypeName(context, methodMatch, typeName, knownSymbols, DiagnosticEditProperties.EditType.Insert);
return;
}
@@ -49,7 +47,7 @@ private protected override void AnalyzeCore(OperationAnalysisContext context, IM
// Is the behavior set via a default value?
if (mockArgument?.ArgumentKind == ArgumentKind.DefaultValue && mockArgument.Value.WalkDownConversion().ConstantValue.Value == knownSymbols.MockBehaviorDefault?.ConstantValue)
{
- ReportDiagnosticWithTypeName(context, target, typeName, knownSymbols, DiagnosticEditProperties.EditType.Insert);
+ TryReportMockBehaviorDiagnostic(context, target, knownSymbols, Rule, DiagnosticEditProperties.EditType.Insert, typeName);
}
// NOTE: This logic can't handle indirection (e.g. var x = MockBehavior.Default; new Mock(x);). We can't use the constant value either,
@@ -58,52 +56,7 @@ private protected override void AnalyzeCore(OperationAnalysisContext context, IM
// The operation specifies a MockBehavior; is it MockBehavior.Default?
if (mockArgument?.DescendantsAndSelf().OfType().Any(argument => argument.Member.IsInstanceOf(knownSymbols.MockBehaviorDefault)) == true)
{
- ReportDiagnosticWithTypeName(context, target, typeName, knownSymbols, DiagnosticEditProperties.EditType.Replace);
+ TryReportMockBehaviorDiagnostic(context, target, knownSymbols, Rule, DiagnosticEditProperties.EditType.Replace, typeName);
}
}
-
- private static string GetMockedTypeName(OperationAnalysisContext context, IMethodSymbol target)
- {
- // For object creation (new Mock), get the type argument from the Mock type
- if (context.Operation is IObjectCreationOperation objectCreation && objectCreation.Type is INamedTypeSymbol namedType && namedType.TypeArguments.Length > 0)
- {
- return namedType.TypeArguments[0].ToDisplayString();
- }
-
- // For method invocation (MockRepository.Of), get the type argument from the method
- if (context.Operation is IInvocationOperation invocation && invocation.TargetMethod.TypeArguments.Length > 0)
- {
- return invocation.TargetMethod.TypeArguments[0].ToDisplayString();
- }
-
- // If we can't determine the type, try to get it from the target method if it's generic
- if (target.ContainingType?.TypeArguments.Length > 0)
- {
- return target.ContainingType.TypeArguments[0].ToDisplayString();
- }
-
- // Fallback to a generic name
- return "T";
- }
-
- private void ReportDiagnosticWithTypeName(
- OperationAnalysisContext context,
- IMethodSymbol method,
- string typeName,
- MoqKnownSymbols knownSymbols,
- DiagnosticEditProperties.EditType editType)
- {
- if (!method.TryGetParameterOfType(knownSymbols.MockBehavior!, out IParameterSymbol? parameterMatch, cancellationToken: context.CancellationToken))
- {
- return;
- }
-
- ImmutableDictionary properties = new DiagnosticEditProperties
- {
- TypeOfEdit = editType,
- EditPosition = parameterMatch.Ordinal,
- }.ToImmutableDictionary();
-
- context.ReportDiagnostic(context.Operation.CreateDiagnostic(Rule, properties, typeName));
- }
}
diff --git a/src/Analyzers/SetStrictMockBehaviorAnalyzer.cs b/src/Analyzers/SetStrictMockBehaviorAnalyzer.cs
index b5e6d26e1..af872c294 100644
--- a/src/Analyzers/SetStrictMockBehaviorAnalyzer.cs
+++ b/src/Analyzers/SetStrictMockBehaviorAnalyzer.cs
@@ -26,6 +26,30 @@ public class SetStrictMockBehaviorAnalyzer : MockBehaviorDiagnosticAnalyzerBase
///
public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule);
+ ///
+ ///
+ /// The original strict analyzer resolved the mocked type name from 's
+ /// type arguments (not the invocation's) and fell back to "Unknown" instead of "T".
+ ///
+ internal override string GetMockedTypeName(IOperation operation, IMethodSymbol target)
+ {
+ // For object creation (new Mock), get the type argument from the Mock type
+ if (operation is IObjectCreationOperation objectCreation
+ && objectCreation.Type is INamedTypeSymbol namedType
+ && namedType.TypeArguments.Length > 0)
+ {
+ return namedType.TypeArguments[0].ToDisplayString();
+ }
+
+ // For any other case, use the target method's type arguments
+ if (target.TypeArguments.Length > 0)
+ {
+ return target.TypeArguments[0].ToDisplayString();
+ }
+
+ return "Unknown";
+ }
+
///
[SuppressMessage("Design", "MA0051:Method is too long", Justification = "Should be fixed. Ignoring for now to avoid additional churn as part of larger refactor.")]
private protected override void AnalyzeCore(OperationAnalysisContext context, IMethodSymbol target, ImmutableArray arguments, MoqKnownSymbols knownSymbols)
@@ -37,7 +61,7 @@ private protected override void AnalyzeCore(OperationAnalysisContext context, IM
IParameterSymbol? mockParameter = target.Parameters.DefaultIfNotSingle(parameter => parameter.Type.IsInstanceOf(knownSymbols.MockBehavior));
// If the target method doesn't have a MockBehavior parameter, check if there's an overload that does
- if (TryHandleMissingMockBehaviorParameter(context, mockParameter, target, knownSymbols, mockedTypeName))
+ if (TryHandleMissingMockBehaviorParameter(context, mockParameter, target, knownSymbols, Rule, mockedTypeName))
{
// Using a method that doesn't accept a MockBehavior parameter, however there's an overload that does
return;
@@ -47,7 +71,7 @@ private protected override void AnalyzeCore(OperationAnalysisContext context, IM
// Is the behavior set via a default value?
if (mockArgument?.ArgumentKind == ArgumentKind.DefaultValue && mockArgument.Value.WalkDownConversion().ConstantValue.Value == knownSymbols.MockBehaviorDefault?.ConstantValue
- && TryReportStrictMockBehaviorDiagnostic(context, target, knownSymbols, mockedTypeName, DiagnosticEditProperties.EditType.Insert))
+ && TryReportMockBehaviorDiagnostic(context, target, knownSymbols, Rule, DiagnosticEditProperties.EditType.Insert, mockedTypeName))
{
return;
}
@@ -58,85 +82,7 @@ private protected override void AnalyzeCore(OperationAnalysisContext context, IM
if (mockArgument?.Value.WalkDownConversion().ConstantValue.Value != knownSymbols.MockBehaviorStrict?.ConstantValue
&& mockArgument?.DescendantsAndSelf().OfType().Any(argument => argument.Member.IsInstanceOf(knownSymbols.MockBehaviorStrict)) != true)
{
- TryReportStrictMockBehaviorDiagnostic(context, target, knownSymbols, mockedTypeName, DiagnosticEditProperties.EditType.Replace);
+ TryReportMockBehaviorDiagnostic(context, target, knownSymbols, Rule, DiagnosticEditProperties.EditType.Replace, mockedTypeName);
}
}
-
- ///
- /// Extracts the mocked type name from the operation.
- ///
- /// The operation being analyzed.
- /// The target method symbol.
- /// The name of the mocked type, or "Unknown" if it cannot be determined.
- private static string GetMockedTypeName(IOperation operation, IMethodSymbol target)
- {
- // For object creation like new Mock()
- if (operation is IObjectCreationOperation objectCreation
- && objectCreation.Type is INamedTypeSymbol namedType
- && namedType.TypeArguments.Length > 0)
- {
- return namedType.TypeArguments[0].ToDisplayString();
- }
-
- // For method invocation like Mock.Of()
- if (operation is IInvocationOperation && target.TypeArguments.Length > 0)
- {
- return target.TypeArguments[0].ToDisplayString();
- }
-
- return "Unknown";
- }
-
- ///
- /// Attempts to report a strict mock behavior diagnostic with the mocked type name.
- ///
- /// The operation analysis context.
- /// The method to check for MockBehavior parameter.
- /// The known Moq symbols.
- /// The name of the mocked type.
- /// The type of edit for the code fix.
- /// True if a diagnostic was reported; otherwise, false.
- private bool TryReportStrictMockBehaviorDiagnostic(
- OperationAnalysisContext context,
- IMethodSymbol method,
- MoqKnownSymbols knownSymbols,
- string mockedTypeName,
- DiagnosticEditProperties.EditType editType)
- {
- if (!method.TryGetParameterOfType(knownSymbols.MockBehavior!, out IParameterSymbol? parameterMatch, cancellationToken: context.CancellationToken))
- {
- return false;
- }
-
- ImmutableDictionary properties = new DiagnosticEditProperties
- {
- TypeOfEdit = editType,
- EditPosition = parameterMatch.Ordinal,
- }.ToImmutableDictionary();
-
- context.ReportDiagnostic(context.Operation.CreateDiagnostic(Rule, properties, mockedTypeName));
- return true;
- }
-
- ///
- /// Attempts to handle missing MockBehavior parameter by checking for overloads that accept it.
- ///
- /// The operation analysis context.
- /// The MockBehavior parameter (should be null to trigger overload check).
- /// The target method to check for overloads.
- /// The known Moq symbols.
- /// The name of the mocked type.
- /// True if a diagnostic was reported; otherwise, false.
- private bool TryHandleMissingMockBehaviorParameter(
- OperationAnalysisContext context,
- IParameterSymbol? mockParameter,
- IMethodSymbol target,
- MoqKnownSymbols knownSymbols,
- string mockedTypeName)
- {
- // If the target method doesn't have a MockBehavior parameter, check if there's an overload that does
- return mockParameter is null
- && target.TryGetOverloadWithParameterOfType(knownSymbols.MockBehavior!, out IMethodSymbol? methodMatch, out _, cancellationToken: context.CancellationToken)
- && TryReportStrictMockBehaviorDiagnostic(context, methodMatch, knownSymbols, mockedTypeName, DiagnosticEditProperties.EditType.Insert);
- }
}
diff --git a/src/Analyzers/SetupSequenceShouldBeUsedOnlyForOverridableMembersAnalyzer.cs b/src/Analyzers/SetupSequenceShouldBeUsedOnlyForOverridableMembersAnalyzer.cs
index 085bd44e2..f7de28c56 100644
--- a/src/Analyzers/SetupSequenceShouldBeUsedOnlyForOverridableMembersAnalyzer.cs
+++ b/src/Analyzers/SetupSequenceShouldBeUsedOnlyForOverridableMembersAnalyzer.cs
@@ -1,4 +1,3 @@
-using System.Diagnostics.CodeAnalysis;
using Microsoft.CodeAnalysis.Operations;
namespace Moq.Analyzers;
@@ -48,7 +47,6 @@ private static void RegisterCompilationStartAction(CompilationStartAnalysisConte
OperationKind.Invocation);
}
- [SuppressMessage("Design", "MA0051:Method is too long", Justification = "Should be fixed. Ignoring for now to avoid additional churn as part of larger refactor.")]
private static void AnalyzeInvocation(OperationAnalysisContext context, MoqKnownSymbols knownSymbols)
{
if (context.Operation is not IInvocationOperation invocationOperation)
@@ -56,86 +54,24 @@ private static void AnalyzeInvocation(OperationAnalysisContext context, MoqKnown
return;
}
- IMethodSymbol targetMethod = invocationOperation.TargetMethod;
-
- // 1. Check if the invoked method is a Moq SetupSequence method
- if (!targetMethod.IsMoqSetupSequenceMethod(knownSymbols))
+ if (!invocationOperation.TargetMethod.IsMoqSetupSequenceMethod(knownSymbols))
{
return;
}
- // 2. Attempt to locate the member reference from the SetupSequence expression argument.
- // Typically, Moq SetupSequence calls have a single lambda argument like x => x.SomeMember.
- // We'll extract that member reference or invocation to see whether it is overridable.
ISymbol? mockedMemberSymbol = MoqVerificationHelpers.TryGetMockedMemberSymbol(invocationOperation);
- if (mockedMemberSymbol == null)
- {
- return;
- }
-
- // 3. Skip if the symbol is part of an interface, those are always "overridable".
- if (mockedMemberSymbol.ContainingType?.TypeKind == TypeKind.Interface)
+ if (mockedMemberSymbol is null
+ || mockedMemberSymbol.ContainingType?.TypeKind == TypeKind.Interface
+ || mockedMemberSymbol.IsOverridableOrAllowedMockMember(knownSymbols))
{
return;
}
- // 4. Check if symbol is a property or method, and if it is overridable or is returning a Task (which Moq allows).
- if (IsOverridableOrTaskResultMember(mockedMemberSymbol, knownSymbols))
- {
- return;
- }
-
- // 5. If we reach here, the member is neither overridable nor allowed by Moq
- // So we report the diagnostic.
- //
- // Try to get the specific member syntax for a more precise diagnostic location
+ // Use the specific member syntax for a more precise diagnostic location when available
SyntaxNode? memberSyntax = MoqVerificationHelpers.TryGetMockedMemberSyntax(invocationOperation);
Location diagnosticLocation = memberSyntax?.GetLocation() ?? invocationOperation.Syntax.GetLocation();
Diagnostic diagnostic = diagnosticLocation.CreateDiagnostic(Rule, mockedMemberSymbol.ToDisplayString());
context.ReportDiagnostic(diagnostic);
}
-
- ///
- /// Determines whether a member symbol is either overridable or represents a / Result property
- /// that Moq allows to be setup even if the underlying property is not overridable.
- ///
- /// The mocked member symbol.
- /// A instance for resolving well-known types.
- ///
- /// Returns when the member is overridable or is a / Result property; otherwise .
- ///
- private static bool IsOverridableOrTaskResultMember(ISymbol mockedMemberSymbol, MoqKnownSymbols knownSymbols)
- {
- switch (mockedMemberSymbol)
- {
- case IPropertySymbol propertySymbol:
- // Check if the property is Task.Result and skip diagnostic if it is
- if (propertySymbol.IsTaskOrValueResultProperty(knownSymbols))
- {
- return true;
- }
-
- if (propertySymbol.IsOverridable())
- {
- return true;
- }
-
- break;
-
- case IMethodSymbol methodSymbol:
- if (methodSymbol.IsOverridable())
- {
- return true;
- }
-
- break;
-
- default:
- // If it's not a property or method, it's not overridable
- return false;
- }
-
- return false;
- }
}
diff --git a/src/Analyzers/SetupShouldBeUsedOnlyForOverridableMembersAnalyzer.cs b/src/Analyzers/SetupShouldBeUsedOnlyForOverridableMembersAnalyzer.cs
index 9e33955df..8c75f4920 100644
--- a/src/Analyzers/SetupShouldBeUsedOnlyForOverridableMembersAnalyzer.cs
+++ b/src/Analyzers/SetupShouldBeUsedOnlyForOverridableMembersAnalyzer.cs
@@ -90,7 +90,7 @@ private static bool IsSetupOnNonOverridableMember(
ISymbol? candidate = MoqVerificationHelpers.TryGetMockedMemberSymbol(invocationOperation);
if (candidate is null
|| candidate.ContainingType?.TypeKind == TypeKind.Interface
- || IsPropertyOrMethod(candidate, knownSymbols))
+ || candidate.IsOverridableOrAllowedMockMember(knownSymbols))
{
return false;
}
@@ -98,49 +98,4 @@ private static bool IsSetupOnNonOverridableMember(
mockedMemberSymbol = candidate;
return true;
}
-
- ///
- /// Determines whether a property or method is either
- /// , , , or
- /// - OR -
- /// if the is overridable.
- ///
- /// The mocked member symbol.
- /// A instance for resolving well-known types.
- ///
- /// Returns when the diagnostic should not be triggered; otherwise .
- ///
- private static bool IsPropertyOrMethod(ISymbol mockedMemberSymbol, MoqKnownSymbols knownSymbols)
- {
- switch (mockedMemberSymbol)
- {
- case IPropertySymbol propertySymbol:
- // Check if the property is Task.Result and skip diagnostic if it is
- if (propertySymbol.IsTaskOrValueResultProperty(knownSymbols))
- {
- return true;
- }
-
- if (propertySymbol.IsOverridable())
- {
- return true;
- }
-
- break;
-
- case IMethodSymbol methodSymbol:
- if (methodSymbol.IsOverridable())
- {
- return true;
- }
-
- break;
-
- default:
- // If it's not a property or method, it's not overridable
- return false;
- }
-
- return false;
- }
}
diff --git a/src/Analyzers/VerifyShouldBeUsedOnlyForOverridableMembersAnalyzer.cs b/src/Analyzers/VerifyShouldBeUsedOnlyForOverridableMembersAnalyzer.cs
index 2a4459589..5d42606a3 100644
--- a/src/Analyzers/VerifyShouldBeUsedOnlyForOverridableMembersAnalyzer.cs
+++ b/src/Analyzers/VerifyShouldBeUsedOnlyForOverridableMembersAnalyzer.cs
@@ -112,7 +112,7 @@ private static bool IsMemberAllowedForVerification(ISymbol mockedMemberSymbol, I
return mockedMemberSymbol is IPropertySymbol propertySymbol && propertySymbol.IsOverridable();
}
- return IsAllowedMockMember(mockedMemberSymbol, knownSymbols);
+ return mockedMemberSymbol.IsOverridableOrAllowedMockMember(knownSymbols);
}
private static void ReportDiagnostic(OperationAnalysisContext context, IInvocationOperation invocationOperation, ISymbol mockedMemberSymbol)
@@ -120,28 +120,4 @@ private static void ReportDiagnostic(OperationAnalysisContext context, IInvocati
Diagnostic diagnostic = invocationOperation.Syntax.CreateDiagnostic(Rule, mockedMemberSymbol.Name);
context.ReportDiagnostic(diagnostic);
}
-
- ///
- /// Determines whether a member can be mocked.
- ///
- /// The mocked member symbol.
- /// The known symbols.
- ///
- /// Returns when the diagnostic should not be triggered; otherwise .
- ///
- private static bool IsAllowedMockMember(ISymbol mockedMemberSymbol, MoqKnownSymbols knownSymbols)
- {
- switch (mockedMemberSymbol)
- {
- case IPropertySymbol propertySymbol:
- return propertySymbol.IsOverridable() || propertySymbol.IsTaskOrValueResultProperty(knownSymbols);
-
- case IMethodSymbol methodSymbol:
- return methodSymbol.IsOverridable();
-
- default:
- // If it's not a property or method, it can't be mocked. This includes fields and events.
- return false;
- }
- }
}
diff --git a/src/Common/EventSyntaxExtensions.cs b/src/Common/EventSyntaxExtensions.cs
index 5162b8ec5..d7d7df516 100644
--- a/src/Common/EventSyntaxExtensions.cs
+++ b/src/Common/EventSyntaxExtensions.cs
@@ -5,24 +5,6 @@ namespace Moq.Analyzers.Common;
///
internal static class EventSyntaxExtensions
{
- ///
- /// Validates that event arguments match the expected parameter types.
- ///
- /// The analysis context.
- /// The event arguments to validate.
- /// The expected parameter types.
- /// The invocation expression for error reporting.
- /// The diagnostic rule to report.
- internal static void ValidateEventArgumentTypes(
- SyntaxNodeAnalysisContext context,
- ArgumentSyntax[] eventArguments,
- ITypeSymbol[] expectedParameterTypes,
- InvocationExpressionSyntax invocation,
- DiagnosticDescriptor rule)
- {
- ValidateEventArgumentTypes(context, eventArguments, expectedParameterTypes, invocation, rule, null);
- }
-
///
/// Validates that event arguments match the expected parameter types.
///
@@ -33,12 +15,12 @@ internal static void ValidateEventArgumentTypes(
/// The diagnostic rule to report.
/// The event name to include in diagnostic messages.
internal static void ValidateEventArgumentTypes(
- SyntaxNodeAnalysisContext context,
+ this SyntaxNodeAnalysisContext context,
ArgumentSyntax[] eventArguments,
ITypeSymbol[] expectedParameterTypes,
InvocationExpressionSyntax invocation,
DiagnosticDescriptor rule,
- string? eventName)
+ string? eventName = null)
{
if (eventArguments.Length != expectedParameterTypes.Length)
{
@@ -79,44 +61,24 @@ internal static void ValidateEventArgumentTypes(
}
}
- ///
- /// Gets the parameter types for a given event delegate type.
- ///
- /// The event delegate type.
- /// An array of parameter types expected by the event delegate.
- internal static ITypeSymbol[] GetEventParameterTypes(ITypeSymbol eventType)
- {
- return GetEventParameterTypesInternal(eventType, null);
- }
-
///
/// Gets the parameter types for a given event delegate type.
///
/// The event delegate type.
/// Known symbols for type checking.
/// An array of parameter types expected by the event delegate.
- internal static ITypeSymbol[] GetEventParameterTypes(ITypeSymbol eventType, KnownSymbols knownSymbols)
+ internal static ITypeSymbol[] GetEventParameterTypes(ITypeSymbol eventType, KnownSymbols? knownSymbols = null)
{
- return GetEventParameterTypesInternal(eventType, knownSymbols);
- }
+ if (eventType is not INamedTypeSymbol namedType)
+ {
+ return [];
+ }
- ///
- /// Extracts arguments from an event method invocation.
- ///
- /// The method invocation.
- /// The semantic model.
- /// The extracted event arguments.
- /// The expected parameter types.
- /// Function to extract event type from the event selector.
- /// if arguments were successfully extracted; otherwise, .
- internal static bool TryGetEventMethodArguments(
- InvocationExpressionSyntax invocation,
- SemanticModel semanticModel,
- out ArgumentSyntax[] eventArguments,
- out ITypeSymbol[] expectedParameterTypes,
- Func eventTypeExtractor)
- {
- return TryGetEventMethodArgumentsInternal(invocation, semanticModel, out eventArguments, out expectedParameterTypes, eventTypeExtractor, null);
+ ITypeSymbol[]? parameterTypes = TryGetActionDelegateParameters(namedType, knownSymbols) ??
+ TryGetEventHandlerDelegateParameters(namedType, knownSymbols) ??
+ TryGetCustomDelegateParameters(namedType);
+
+ return parameterTypes ?? [];
}
///
@@ -135,32 +97,18 @@ internal static bool TryGetEventMethodArguments(
out ArgumentSyntax[] eventArguments,
out ITypeSymbol[] expectedParameterTypes,
Func eventTypeExtractor,
- KnownSymbols knownSymbols)
- {
- return TryGetEventMethodArgumentsInternal(invocation, semanticModel, out eventArguments, out expectedParameterTypes, eventTypeExtractor, knownSymbols);
- }
-
- private static bool TryGetEventMethodArgumentsInternal(
- InvocationExpressionSyntax invocation,
- SemanticModel semanticModel,
- out ArgumentSyntax[] eventArguments,
- out ITypeSymbol[] expectedParameterTypes,
- Func eventTypeExtractor,
- KnownSymbols? knownSymbols)
+ KnownSymbols? knownSymbols = null)
{
eventArguments = [];
expectedParameterTypes = [];
- // Get the arguments to the method
SeparatedSyntaxList arguments = invocation.ArgumentList.Arguments;
- // Method should have at least 1 argument (the event selector)
if (arguments.Count < 1)
{
return false;
}
- // First argument should be a lambda that selects the event
ExpressionSyntax eventSelector = arguments[0].Expression;
(bool success, ITypeSymbol? eventType) = eventTypeExtractor(semanticModel, eventSelector);
if (!success || eventType == null)
@@ -168,10 +116,8 @@ private static bool TryGetEventMethodArgumentsInternal(
return false;
}
- // Get expected parameter types from the event delegate
- expectedParameterTypes = knownSymbols != null ? GetEventParameterTypes(eventType, knownSymbols) : GetEventParameterTypes(eventType);
+ expectedParameterTypes = GetEventParameterTypes(eventType, knownSymbols);
- // The remaining arguments should match the event parameter types
if (arguments.Count <= 1)
{
eventArguments = [];
@@ -188,35 +134,6 @@ private static bool TryGetEventMethodArgumentsInternal(
return true;
}
- ///
- /// Gets the parameter types for a given event delegate type.
- /// This method handles various delegate types including Action delegates, EventHandler delegates,
- /// and custom delegates by analyzing their structure and extracting parameter information.
- ///
- /// The event delegate type to analyze.
- /// Known symbols for enhanced type checking and recognition.
- ///
- /// An array of parameter types expected by the event delegate:
- /// - For Action delegates: Returns all generic type arguments
- /// - For EventHandler<T> delegates: Returns the single generic argument T
- /// - For custom delegates: Returns parameters from the Invoke method
- /// - For non-delegate types: Returns empty array.
- ///
- private static ITypeSymbol[] GetEventParameterTypesInternal(ITypeSymbol eventType, KnownSymbols? knownSymbols)
- {
- if (eventType is not INamedTypeSymbol namedType)
- {
- return [];
- }
-
- // Try different delegate type handlers in order of specificity
- ITypeSymbol[]? parameterTypes = TryGetActionDelegateParameters(namedType, knownSymbols) ??
- TryGetEventHandlerDelegateParameters(namedType, knownSymbols) ??
- TryGetCustomDelegateParameters(namedType);
-
- return parameterTypes ?? [];
- }
-
///
/// Attempts to get parameter types from Action delegate types.
///
diff --git a/src/Common/ISymbolExtensions.Moq.cs b/src/Common/ISymbolExtensions.Moq.cs
index 788a36567..d61764e1e 100644
--- a/src/Common/ISymbolExtensions.Moq.cs
+++ b/src/Common/ISymbolExtensions.Moq.cs
@@ -145,6 +145,29 @@ internal static bool IsMoqCallbackMethod(this ISymbol symbol, MoqKnownSymbols kn
symbol.IsInstanceOf(knownSymbols.ICallback2Callback);
}
+ ///
+ /// Determines whether a member symbol is either overridable or represents a
+ /// / Result property
+ /// that Moq allows to be set up even if the underlying property is not overridable.
+ ///
+ /// The mocked member symbol.
+ /// A instance for resolving well-known types.
+ ///
+ /// when the member is overridable or is a Task/ValueTask Result property;
+ /// otherwise .
+ ///
+ internal static bool IsOverridableOrAllowedMockMember(this ISymbol mockedMemberSymbol, MoqKnownSymbols knownSymbols)
+ {
+ return mockedMemberSymbol switch
+ {
+ IPropertySymbol propertySymbol =>
+ propertySymbol.IsOverridable() || propertySymbol.IsTaskOrValueResultProperty(knownSymbols),
+ IMethodSymbol methodSymbol =>
+ methodSymbol.IsOverridable(),
+ _ => false,
+ };
+ }
+
///
/// Determines whether a symbol is a Moq Raises method.
///
diff --git a/src/Common/ISymbolExtensions.cs b/src/Common/ISymbolExtensions.cs
index da0a5db72..5b681d1ef 100644
--- a/src/Common/ISymbolExtensions.cs
+++ b/src/Common/ISymbolExtensions.cs
@@ -1,4 +1,4 @@
-using System.Diagnostics.CodeAnalysis;
+using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
namespace Moq.Analyzers.Common;