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
91 changes: 64 additions & 27 deletions src/Common/EventSyntaxExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -211,42 +211,79 @@ private static bool TryGetEventMethodArgumentsInternal(
return true;
}

/// <summary>
/// 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.
/// </summary>
/// <param name="eventType">The event delegate type to analyze.</param>
/// <param name="knownSymbols">Known symbols for enhanced type checking and recognition.</param>
/// <returns>
/// An array of parameter types expected by the event delegate:
/// - For Action delegates: Returns all generic type arguments
/// - For EventHandler&lt;T&gt; delegates: Returns the single generic argument T
/// - For custom delegates: Returns parameters from the Invoke method
/// - For non-delegate types: Returns empty array.
/// </returns>
private static ITypeSymbol[] GetEventParameterTypesInternal(ITypeSymbol eventType, KnownSymbols? knownSymbols)
{
// For delegates like Action<T>, we need to get the generic type arguments
if (eventType is INamedTypeSymbol namedType)
if (eventType is not INamedTypeSymbol namedType)
{
// Handle Action delegates
if (knownSymbols != null && namedType.IsActionDelegate(knownSymbols))
{
return namedType.TypeArguments.ToArray();
}
return [];
}

if (knownSymbols == null && IsActionDelegate(namedType))
{
return namedType.TypeArguments.ToArray();
}
// Try different delegate type handlers in order of specificity
ITypeSymbol[]? parameterTypes = TryGetActionDelegateParameters(namedType, knownSymbols) ??
TryGetEventHandlerDelegateParameters(namedType, knownSymbols) ??
TryGetCustomDelegateParameters(namedType);

// Handle EventHandler<T> - expects single argument of type T (not the sender/args pattern)
if (knownSymbols != null && namedType.IsEventHandlerDelegate(knownSymbols))
{
return [namedType.TypeArguments[0]];
}
return parameterTypes ?? [];
}

if (knownSymbols == null && IsEventHandlerDelegate(namedType))
{
return [namedType.TypeArguments[0]];
}
/// <summary>
/// Attempts to get parameter types from Action delegate types.
/// </summary>
/// <param name="namedType">The named type symbol to check.</param>
/// <param name="knownSymbols">Optional known symbols for enhanced type checking.</param>
/// <returns>Parameter types if this is an Action delegate; otherwise null.</returns>
private static ITypeSymbol[]? TryGetActionDelegateParameters(INamedTypeSymbol namedType, KnownSymbols? knownSymbols)
{
bool isActionDelegate = knownSymbols != null
? namedType.IsActionDelegate(knownSymbols)
: IsActionDelegate(namedType);

// Handle custom delegates by getting the Invoke method parameters
IMethodSymbol? invokeMethod = namedType.DelegateInvokeMethod;
if (invokeMethod != null)
{
return invokeMethod.Parameters.Select(p => p.Type).ToArray();
}
return isActionDelegate ? namedType.TypeArguments.ToArray() : null;
}

/// <summary>
/// Attempts to get parameter types from EventHandler delegate types.
/// </summary>
/// <param name="namedType">The named type symbol to check.</param>
/// <param name="knownSymbols">Optional known symbols for enhanced type checking.</param>
/// <returns>Parameter types if this is an EventHandler delegate; otherwise null.</returns>
private static ITypeSymbol[]? TryGetEventHandlerDelegateParameters(INamedTypeSymbol namedType, KnownSymbols? knownSymbols)
{
bool isEventHandlerDelegate = knownSymbols != null
? namedType.IsEventHandlerDelegate(knownSymbols)
: IsEventHandlerDelegate(namedType);

if (isEventHandlerDelegate && namedType.TypeArguments.Length > 0)
{
return [namedType.TypeArguments[0]];
}

return [];
return null;
}

/// <summary>
/// Attempts to get parameter types from custom delegate types using the Invoke method.
/// </summary>
/// <param name="namedType">The named type symbol to check.</param>
/// <returns>Parameter types if this has a delegate Invoke method; otherwise null.</returns>
private static ITypeSymbol[]? TryGetCustomDelegateParameters(INamedTypeSymbol namedType)
{
IMethodSymbol? invokeMethod = namedType.DelegateInvokeMethod;
return invokeMethod?.Parameters.Select(p => p.Type).ToArray();
}

private static bool IsActionDelegate(INamedTypeSymbol namedType)
Expand Down
99 changes: 80 additions & 19 deletions src/Common/ISymbolExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -207,38 +207,99 @@
/// </summary>
/// <param name="symbol">The symbol to check.</param>
/// <param name="knownSymbols">The known symbols for type checking.</param>
/// <returns>True if the symbol is a Raises or RaisesAsync method from Moq.Language.IRaiseable or IRaiseableAsync; otherwise false.</returns>
/// <returns>True if the symbol is a Raises or RaisesAsync method from Moq.Language interfaces; otherwise false.</returns>
internal static bool IsMoqRaisesMethod(this ISymbol symbol, MoqKnownSymbols knownSymbols)
{
if (symbol is not IMethodSymbol methodSymbol)
{
return false;
}

// Check if this method symbol matches any of the known Raises methods
// Try the ICallback and IReturns interfaces which are more likely to contain Raises
bool symbolBasedResult = symbol.IsInstanceOf(knownSymbols.ICallbackRaises) ||
symbol.IsInstanceOf(knownSymbols.ICallback1Raises) ||
symbol.IsInstanceOf(knownSymbols.ICallback2Raises) ||
symbol.IsInstanceOf(knownSymbols.IReturnsRaises) ||
symbol.IsInstanceOf(knownSymbols.IReturns1Raises) ||
symbol.IsInstanceOf(knownSymbols.IReturns2Raises) ||
symbol.IsInstanceOf(knownSymbols.IRaiseableRaises) ||
symbol.IsInstanceOf(knownSymbols.IRaiseableAsyncRaisesAsync);

if (symbolBasedResult)
// Primary: Use symbol-based detection for known Moq interfaces
if (IsKnownMoqRaisesMethod(symbol, knownSymbols))
{
return true;
}

// Fallback: Check if it's a Raises/RaisesAsync method on any Moq.Language interface
// This provides compatibility until the correct interface names are identified
string? containingTypeName = methodSymbol.ContainingType?.ToDisplayString();
// TODO: Remove this fallback once symbol-based detection is complete

Check notice

Code scanning / devskim

A "TODO" or similar was left in source code, possibly indicating incomplete functionality Note

Suspicious comment
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@coderabbitai Log an issue for this TODO

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

I'll create a GitHub issue for this TODO comment about removing the fallback method once symbol-based detection is complete.

@rjmurillo I've created a GitHub issue to track the removal of the string-based fallback method in IsMoqRaisesMethod once the symbol-based detection is comprehensive. The issue includes the context, location details, and references back to this PR and comment.


🏁 Script executed:

#!/bin/bash
# Create a GitHub issue for the TODO comment about removing fallback method
gh issue create \
  --title "Remove string-based fallback in IsMoqRaisesMethod once symbol-based detection is comprehensive" \
  --body "## Description

A TODO comment in \`src/Common/ISymbolExtensions.cs\` indicates that a string-based fallback method should be removed once symbol-based detection for Moq Raises methods is complete.

## Location
- **File**: \`src/Common/ISymbolExtensions.cs\`
- **Method**: \`IsMoqRaisesMethod\`
- **Line**: ~224

## Current Implementation
The method currently uses both:
1. Primary symbol-based detection via \`IsKnownMoqRaisesMethod\`
2. Fallback string-based detection via \`IsLikelyMoqRaisesMethodByName\`

## Task
Remove the fallback method \`IsLikelyMoqRaisesMethodByName\` once the symbol-based detection is verified to handle all Moq Raises method scenarios comprehensively.

## Context
This was flagged during security scanning as a TODO that may indicate incomplete functionality.

## References
- PR: https://github.com/rjmurillo/moq.analyzers/pull/633
- Comment: https://github.com/rjmurillo/moq.analyzers/pull/633#discussion_r2233694979

Requested by: @rjmurillo" \
  --assignee rjmurillo

Length of output: 1169


Perfect! I've successfully created GitHub issue #637 for the TODO comment. The issue has been created with:

Issue URL: #637

Details included:

  • Clear description of the task: removing the string-based fallback method once symbol-based detection is comprehensive
  • Specific location information (src/Common/ISymbolExtensions.cs, IsMoqRaisesMethod method)
  • Context explaining this is technical debt flagged by security scanning
  • References back to this PR and comment thread
  • Assignment to you as requested

The issue is now tracked and ready for future work to clean up the fallback method IsLikelyMoqRaisesMethodByName once the symbol-based approach via IsKnownMoqRaisesMethod is verified to handle all scenarios.

🐰✨
Creating organized tracking for future improvements!


🧬 Code Graph Analysis Results

Relevant code snippets for understanding src/Common/ISymbolExtensions.cs and the user comment about the TODO:


File: src/Common/ISymbolExtensions.cs (lines 1-230)

The entire file content is relevant as it defines extension methods on ISymbol related to Moq symbols and their identification, including the method containing the TODO comment:

  • IsMoqRaisesMethod(this ISymbol symbol, MoqKnownSymbols knownSymbols) method (lines ~140-180) contains the TODO comment:
internal static bool IsMoqRaisesMethod(this ISymbol symbol, MoqKnownSymbols knownSymbols)
{
    if (symbol is not IMethodSymbol methodSymbol)
    {
        return false;
    }

    // Primary: Use symbol-based detection for known Moq interfaces
    if (IsKnownMoqRaisesMethod(symbol, knownSymbols))
    {
        return true;
    }

    // TODO: Remove this fallback once symbol-based detection is complete
    // This is a temporary safety net for cases where symbol resolution fails
    // but should be replaced with comprehensive symbol-based approach
    return IsLikelyMoqRaisesMethodByName(methodSymbol);
}

This method checks if a symbol is a Moq Raises method, first by symbol-based detection, then by a fallback name-based heuristic (marked with a TODO to remove later).

  • The helper methods used by IsMoqRaisesMethod are also relevant:
private static bool IsKnownMoqRaisesMethod(ISymbol symbol, MoqKnownSymbols knownSymbols)
{
    return IsCallbackRaisesMethod(symbol, knownSymbols) ||
           IsReturnsRaisesMethod(symbol, knownSymbols) ||
           IsRaiseableMethod(symbol, knownSymbols);
}

private static bool IsCallbackRaisesMethod(ISymbol symbol, MoqKnownSymbols knownSymbols)
{
    return symbol.IsInstanceOf(knownSymbols.ICallbackRaises) ||
           symbol.IsInstanceOf(knownSymbols.ICallback1Raises) ||
           symbol.IsInstanceOf(knownSymbols.ICallback2Raises);
}

private static bool IsReturnsRaisesMethod(ISymbol symbol, MoqKnownSymbols knownSymbols)
{
    return symbol.IsInstanceOf(knownSymbols.IReturnsRaises) ||
           symbol.IsInstanceOf(knownSymbols.IReturns1Raises) ||
           symbol.IsInstanceOf(knownSymbols.IReturns2Raises);
}

private static bool IsRaiseableMethod(ISymbol symbol, MoqKnownSymbols knownSymbols)
{
    return symbol.IsInstanceOf(knownSymbols.IRaiseableRaises) ||
           symbol.IsInstanceOf(knownSymbols.IRaiseableAsyncRaisesAsync);
}

private static bool IsLikelyMoqRaisesMethodByName(IMethodSymbol methodSymbol)
{
    string methodName = methodSymbol.Name;

    // Only match exact "Raises" or "RaisesAsync" method names
    if (!string.Equals(methodName, "Raises", StringComparison.Ordinal) &&
        !string.Equals(methodName, "RaisesAsync", StringComparison.Ordinal))
    {
        return false;
    }

    // Must be in a type that contains "Moq" in its namespace to reduce false positives
    string? containingTypeNamespace = methodSymbol.ContainingType?.ContainingNamespace?.ToDisplayString();
    return containingTypeNamespace?.StartsWith("Moq", StringComparison.Ordinal) == true;
}

These methods implement the logic for identifying Moq Raises methods by checking symbol equality against known Moq interface methods or by a fallback heuristic based on method name and namespace.


File: src/Common/WellKnown/MoqKnownSymbols.cs (lines 6-286)

This file defines the MoqKnownSymbols class which provides cached references to known Moq symbols used in ISymbolExtensions for symbol equality checks.

  • It contains properties like:
internal ImmutableArray<IMethodSymbol> ICallbackRaises => ICallback?.GetMembers("Raises").OfType<IMethodSymbol>().ToImmutableArray() ?? ImmutableArray<IMethodSymbol>.Empty;
internal ImmutableArray<IMethodSymbol> ICallback1Raises => ICallback1?.GetMembers("Raises").OfType<IMethodSymbol>().ToImmutableArray() ?? ImmutableArray<IMethodSymbol>.Empty;
internal ImmutableArray<IMethodSymbol> ICallback2Raises => ICallback2?.GetMembers("Raises").OfType<IMethodSymbol>().ToImmutableArray() ?? ImmutableArray<IMethodSymbol>.Empty;

internal ImmutableArray<IMethodSymbol> IReturnsRaises => IReturns?.GetMembers("Raises").OfType<IMethodSymbol>().ToImmutableArray() ?? ImmutableArray<IMethodSymbol>.Empty;
internal ImmutableArray<IMethodSymbol> IReturns1Raises => IReturns1?.GetMembers("Raises").OfType<IMethodSymbol>().ToImmutableArray() ?? ImmutableArray<IMethodSymbol>.Empty;
internal ImmutableArray<IMethodSymbol> IReturns2Raises => IReturns2?.GetMembers("Raises").OfType<IMethodSymbol>().ToImmutableArray() ?? ImmutableArray<IMethodSymbol>.Empty;

internal ImmutableArray<IMethodSymbol> IRaiseableRaises => IRaiseable?.GetMembers("Raises").OfType<IMethodSymbol>().ToImmutableArray() ?? ImmutableArray<IMethodSymbol>.Empty;
internal ImmutableArray<IMethodSymbol> IRaiseableAsyncRaisesAsync => IRaiseableAsync?.GetMembers("RaisesAsync").OfType<IMethodSymbol>().ToImmutableArray() ?? ImmutableArray<IMethodSymbol>.Empty;

These are the known Moq interface methods used by ISymbolExtensions to identify Raises methods.


File: src/Common/MoqVerificationHelpers.cs (lines 34-70)

This file contains helper methods to extract symbols from Moq verification expressions, which may be relevant for understanding how symbols are used in the context of Moq analyzers and fixes:

public static ISymbol? ExtractPropertyFromVerifySetLambda(IAnonymousFunctionOperation lambdaOperation)
{
    foreach (IOperation op in lambdaOperation.Body.Operations)
    {
        if (op is IExpressionStatementOperation exprStmt)
        {
            IAssignmentOperation? assignOp = exprStmt.Operation as IAssignmentOperation
                ?? exprStmt.Operation as ISimpleAssignmentOperation;

            if (assignOp?.Target is IPropertyReferenceOperation propRef)
            {
                return propRef.Property;
            }
        }
    }

    return null;
}

public static ISymbol? TryGetMockedMemberSymbol(IInvocationOperation moqInvocation)
{
    if (moqInvocation.Arguments.Length == 0)
    {
        return null;
    }

    // For code fix, we only need to handle the simple case (first argument)
    // since the analyzer already validates the more complex scenarios
    IAnonymousFunctionOperation? lambdaOperation = ExtractLambdaFromArgument(moqInvocation.Arguments[0].Value);

    return lambdaOperation?.Body.GetReferencedMemberSymbolFromLambda();
}

File: src/Common/IOperationExtensions.cs (lines 47-57 and 130-138)

These extension methods help extract symbols from operation trees, which is useful in Moq analyzers and code fixes:

internal static ISymbol? GetReferencedMemberSymbolFromLambda(this IOperation? bodyOperation)
{
    if (bodyOperation is IBlockOperation { Operations.Length: 1 } blockOperation)
    {
        // If it's a block lambda (example: => { return x.Property; })
        return blockOperation.Operations[0].GetSymbolFromOperation();
    }

    // If it's an expression lambda (example: => x.Property or => x.Method(...))
    return bodyOperation.GetSymbolFromOperation();
}

private static ISymbol? GetSymbolFromOperation(this IOperation? operation)
    => IOperationExtensions.TraverseOperation<ISymbol>(operation, static op => op switch
    {
        IPropertyReferenceOperation propertyRef => propertyRef.Property,
        IInvocationOperation methodOp => methodOp.TargetMethod,
        IEventReferenceOperation eventRef => eventRef.Event,
        IFieldReferenceOperation fieldRef => fieldRef.Field,
        _ => null,
    });

Summary

  • The TODO comment is in ISymbolExtensions.cs inside the IsMoqRaisesMethod method, which currently uses a fallback heuristic by method name and namespace to detect Moq Raises methods when symbol-based detection fails.
  • The relevant known Moq symbols used for symbol-based detection are defined in MoqKnownSymbols.cs.
  • Helper methods in MoqVerificationHelpers.cs and IOperationExtensions.cs provide context on how symbols are extracted and used in Moq analyzers and code fixes.

These snippets provide the necessary context to understand the TODO and the related symbol detection logic.


🧠 Learnings used
Learnt from: rjmurillo
PR: rjmurillo/moq.analyzers#633
File: src/Common/ISymbolExtensions.cs:305-305
Timestamp: 2025-07-26T22:05:51.788Z
Learning: String-based method name detection should be avoided in favor of the MoqKnownSymbols pattern for consistency, type safety, and robustness in the Moq.Analyzers codebase. Methods like IsRaisesMethodByName that use string.Equals for method name checking go against established patterns.

Learnt from: rjmurillo
PR: rjmurillo/moq.analyzers#580
File: tests/Moq.Analyzers.Test/CallbackSignatureShouldMatchMockedMethodAnalyzerTests.cs:116-116
Timestamp: 2025-07-26T15:57:15.819Z
Learning: The CallbackSignatureShouldMatchMockedMethodAnalyzer (Moq1100) currently has a gap where it does not validate generic .Callback<T>() type parameters against the mocked method signature. This can lead to runtime errors when generic callback types don't match the expected method parameters, documented in CallbackSignatureShouldMatchMockedMethodAnalyzerTests.GenericCallbackValidation_CurrentLimitation_IsDocumented(). According to Moq documentation, .Callback<T> should provide compile-time type safety where T must match the argument type of the mocked method.

Learnt from: rjmurillo
PR: rjmurillo/moq.analyzers#580
File: tests/Moq.Analyzers.Test/CallbackSignatureShouldMatchMockedMethodAnalyzerTests.cs:116-116
Timestamp: 2025-07-26T15:57:15.819Z
Learning: The CallbackSignatureShouldMatchMockedMethodAnalyzer (Moq1100) currently has a gap where it does not validate generic .Callback<T>() type parameters against the mocked method signature. This can lead to runtime errors when generic callback types don't match the expected method parameters, documented in CallbackSignatureShouldMatchMockedMethodAnalyzerTests.GenericCallbackValidation_CurrentLimitation_IsDocumented().

Learnt from: CR
PR: rjmurillo/moq.analyzers#0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-06-25T12:42:47.111Z
Learning: If a Moq feature has known limitations or edge cases (e.g., explicit interface implementation, event setup), document this in the test or analyzer code.

Learnt from: CR
PR: rjmurillo/moq.analyzers#0
File: CONTRIBUTING.md:0-0
Timestamp: 2025-07-27T02:45:02.136Z
Learning: Applies to tests/**/*.cs : In code fix tests, the code that triggers the analyzer MUST be wrapped with the diagnostic marker syntax (e.g., {|Moq1210:...|}).

Learnt from: rjmurillo
PR: rjmurillo/moq.analyzers#0
File: :0-0
Timestamp: 2025-07-22T17:25:24.857Z
Learning: Applies to {src/Analyzers/**/*.cs,tests/**/*.cs}: Moq protected member APIs that need analyzer validation include: Setup, SetupSequence, SetupGet<T>, SetupSet<T>, Verify, VerifyGet<T>, VerifySet<T>, and As<T>() for interface mapping. All protected members must be virtual or abstract to be mockable.

Learnt from: rjmurillo
PR: rjmurillo/moq.analyzers#0
File: :0-0
Timestamp: 2025-07-22T17:25:24.857Z
Learning: Applies to {src/Analyzers/**/*.cs,tests/**/*.cs}: Moq protected member APIs that need analyzer validation include: Setup, SetupSequence, SetupGet<T>, SetupSet<T>, Verify, VerifyGet<T>, VerifySet<T>, and As<T>() for interface mapping. All protected members must be virtual or abstract to be mockable.

Learnt from: CR
PR: rjmurillo/moq.analyzers#0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-07-27T02:44:39.596Z
Learning: Applies to tests/Moq.Analyzers.Test/**/*.cs : Add or update unit tests in tests/Moq.Analyzers.Test/ for every analyzer or code fix change

Learnt from: CR
PR: rjmurillo/moq.analyzers#0
File: CONTRIBUTING.md:0-0
Timestamp: 2025-07-27T02:45:02.136Z
Learning: Applies to tests/Moq.Analyzers.Benchmarks/**/*.cs : Performance-sensitive changes require benchmark validation. Run performance tests locally before submitting PRs and include performance regression analysis in PR description.

Learnt from: CR
PR: rjmurillo/moq.analyzers#0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-06-25T12:42:47.110Z
Learning: Do not include tests for features/APIs that do not exist in the targeted Moq version.

Learnt from: CR
PR: rjmurillo/moq.analyzers#0
File: CONTRIBUTING.md:0-0
Timestamp: 2025-07-27T02:45:02.136Z
Learning: If your PR changes or adds analyzer tests, include a note in the PR description about which Moq versions are targeted and how test data is grouped accordingly.

// This is a temporary safety net for cases where symbol resolution fails
// but should be replaced with comprehensive symbol-based approach
return IsLikelyMoqRaisesMethodByName(methodSymbol);
}

/// <summary>
/// Checks if the symbol matches any of the known Moq Raises method symbols.
/// This method handles all supported Moq interfaces that provide Raises functionality.
/// </summary>
/// <param name="symbol">The symbol to check.</param>
/// <param name="knownSymbols">The known symbols for type checking.</param>
/// <returns>True if the symbol matches a known Moq Raises method; otherwise false.</returns>
private static bool IsKnownMoqRaisesMethod(ISymbol symbol, MoqKnownSymbols knownSymbols)
{
return IsCallbackRaisesMethod(symbol, knownSymbols) ||
IsReturnsRaisesMethod(symbol, knownSymbols) ||
IsRaiseableMethod(symbol, knownSymbols);
}

/// <summary>
/// Checks if the symbol is a Raises method from ICallback interfaces.
/// </summary>
/// <param name="symbol">The symbol to check.</param>
/// <param name="knownSymbols">The known symbols for type checking.</param>
/// <returns>True if the symbol is a callback Raises method; otherwise false.</returns>
private static bool IsCallbackRaisesMethod(ISymbol symbol, MoqKnownSymbols knownSymbols)
{
return symbol.IsInstanceOf(knownSymbols.ICallbackRaises) ||
symbol.IsInstanceOf(knownSymbols.ICallback1Raises) ||
symbol.IsInstanceOf(knownSymbols.ICallback2Raises);
}

/// <summary>
/// Checks if the symbol is a Raises method from IReturns interfaces.
/// </summary>
/// <param name="symbol">The symbol to check.</param>
/// <param name="knownSymbols">The known symbols for type checking.</param>
/// <returns>True if the symbol is a returns Raises method; otherwise false.</returns>
private static bool IsReturnsRaisesMethod(ISymbol symbol, MoqKnownSymbols knownSymbols)
{
return symbol.IsInstanceOf(knownSymbols.IReturnsRaises) ||
symbol.IsInstanceOf(knownSymbols.IReturns1Raises) ||
symbol.IsInstanceOf(knownSymbols.IReturns2Raises);
}

/// <summary>
/// Checks if the symbol is a Raises method from IRaiseable interfaces.
/// </summary>
/// <param name="symbol">The symbol to check.</param>
/// <param name="knownSymbols">The known symbols for type checking.</param>
/// <returns>True if the symbol is a raiseable method; otherwise false.</returns>
private static bool IsRaiseableMethod(ISymbol symbol, MoqKnownSymbols knownSymbols)
{
return symbol.IsInstanceOf(knownSymbols.IRaiseableRaises) ||
symbol.IsInstanceOf(knownSymbols.IRaiseableAsyncRaisesAsync);
}

/// <summary>
/// TEMPORARY: Conservative fallback for Moq Raises method detection.
/// This should be removed once symbol-based detection is comprehensive.
/// Only matches methods that are clearly Moq Raises methods.
/// </summary>
/// <param name="methodSymbol">The method symbol to check.</param>
/// <returns>True if this is likely a Moq Raises method; otherwise false.</returns>
private static bool IsLikelyMoqRaisesMethodByName(IMethodSymbol methodSymbol)
{
string methodName = methodSymbol.Name;

return (string.Equals(methodName, "Raises", StringComparison.Ordinal) ||
string.Equals(methodName, "RaisesAsync", StringComparison.Ordinal)) &&
containingTypeName?.Contains("Moq.Language", StringComparison.Ordinal) == true;
// Only match exact "Raises" or "RaisesAsync" method names
if (!string.Equals(methodName, "Raises", StringComparison.Ordinal) &&
!string.Equals(methodName, "RaisesAsync", StringComparison.Ordinal))
{
return false;
}

// Must be in a type that contains "Moq" in its namespace to reduce false positives
string? containingTypeNamespace = methodSymbol.ContainingType?.ContainingNamespace?.ToDisplayString();
return containingTypeNamespace?.StartsWith("Moq", StringComparison.Ordinal) == true;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Moq Raises Method Detection Namespace Issue

The fallback logic for Moq Raises method detection was altered. It changed from checking if the containing type name contained "Moq.Language" to checking if the containing namespace starts with "Moq". This introduces both false positives (matching methods in any namespace starting with "Moq", not just Moq.Language interfaces) and false negatives (missing methods where "Moq.Language" is in the type name but the namespace doesn't start with "Moq").

Locations (1)

Fix in CursorFix in Web

}

/// <summary>
Expand Down
Loading