diff --git a/src/Common/EventSyntaxExtensions.cs b/src/Common/EventSyntaxExtensions.cs
index 4549b8ee6..368a028cc 100644
--- a/src/Common/EventSyntaxExtensions.cs
+++ b/src/Common/EventSyntaxExtensions.cs
@@ -211,42 +211,79 @@ 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)
{
- // For delegates like Action, 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 - 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]];
- }
+ ///
+ /// Attempts to get parameter types from Action delegate types.
+ ///
+ /// The named type symbol to check.
+ /// Optional known symbols for enhanced type checking.
+ /// Parameter types if this is an Action delegate; otherwise null.
+ 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;
+ }
+
+ ///
+ /// Attempts to get parameter types from EventHandler delegate types.
+ ///
+ /// The named type symbol to check.
+ /// Optional known symbols for enhanced type checking.
+ /// Parameter types if this is an EventHandler delegate; otherwise null.
+ 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;
+ }
+
+ ///
+ /// Attempts to get parameter types from custom delegate types using the Invoke method.
+ ///
+ /// The named type symbol to check.
+ /// Parameter types if this has a delegate Invoke method; otherwise null.
+ private static ITypeSymbol[]? TryGetCustomDelegateParameters(INamedTypeSymbol namedType)
+ {
+ IMethodSymbol? invokeMethod = namedType.DelegateInvokeMethod;
+ return invokeMethod?.Parameters.Select(p => p.Type).ToArray();
}
private static bool IsActionDelegate(INamedTypeSymbol namedType)
diff --git a/src/Common/ISymbolExtensions.cs b/src/Common/ISymbolExtensions.cs
index 18d5dc2d8..dcb33a537 100644
--- a/src/Common/ISymbolExtensions.cs
+++ b/src/Common/ISymbolExtensions.cs
@@ -207,7 +207,7 @@ internal static bool IsMoqCallbackMethod(this ISymbol symbol, MoqKnownSymbols kn
///
/// The symbol to check.
/// The known symbols for type checking.
- /// True if the symbol is a Raises or RaisesAsync method from Moq.Language.IRaiseable or IRaiseableAsync; otherwise false.
+ /// True if the symbol is a Raises or RaisesAsync method from Moq.Language interfaces; otherwise false.
internal static bool IsMoqRaisesMethod(this ISymbol symbol, MoqKnownSymbols knownSymbols)
{
if (symbol is not IMethodSymbol methodSymbol)
@@ -215,30 +215,91 @@ internal static bool IsMoqRaisesMethod(this ISymbol symbol, MoqKnownSymbols know
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
+ // This is a temporary safety net for cases where symbol resolution fails
+ // but should be replaced with comprehensive symbol-based approach
+ return IsLikelyMoqRaisesMethodByName(methodSymbol);
+ }
+
+ ///
+ /// Checks if the symbol matches any of the known Moq Raises method symbols.
+ /// This method handles all supported Moq interfaces that provide Raises functionality.
+ ///
+ /// The symbol to check.
+ /// The known symbols for type checking.
+ /// True if the symbol matches a known Moq Raises method; otherwise false.
+ private static bool IsKnownMoqRaisesMethod(ISymbol symbol, MoqKnownSymbols knownSymbols)
+ {
+ return IsCallbackRaisesMethod(symbol, knownSymbols) ||
+ IsReturnsRaisesMethod(symbol, knownSymbols) ||
+ IsRaiseableMethod(symbol, knownSymbols);
+ }
+
+ ///
+ /// Checks if the symbol is a Raises method from ICallback interfaces.
+ ///
+ /// The symbol to check.
+ /// The known symbols for type checking.
+ /// True if the symbol is a callback Raises method; otherwise false.
+ private static bool IsCallbackRaisesMethod(ISymbol symbol, MoqKnownSymbols knownSymbols)
+ {
+ return symbol.IsInstanceOf(knownSymbols.ICallbackRaises) ||
+ symbol.IsInstanceOf(knownSymbols.ICallback1Raises) ||
+ symbol.IsInstanceOf(knownSymbols.ICallback2Raises);
+ }
+
+ ///
+ /// Checks if the symbol is a Raises method from IReturns interfaces.
+ ///
+ /// The symbol to check.
+ /// The known symbols for type checking.
+ /// True if the symbol is a returns Raises method; otherwise false.
+ private static bool IsReturnsRaisesMethod(ISymbol symbol, MoqKnownSymbols knownSymbols)
+ {
+ return symbol.IsInstanceOf(knownSymbols.IReturnsRaises) ||
+ symbol.IsInstanceOf(knownSymbols.IReturns1Raises) ||
+ symbol.IsInstanceOf(knownSymbols.IReturns2Raises);
+ }
+
+ ///
+ /// Checks if the symbol is a Raises method from IRaiseable interfaces.
+ ///
+ /// The symbol to check.
+ /// The known symbols for type checking.
+ /// True if the symbol is a raiseable method; otherwise false.
+ private static bool IsRaiseableMethod(ISymbol symbol, MoqKnownSymbols knownSymbols)
+ {
+ return symbol.IsInstanceOf(knownSymbols.IRaiseableRaises) ||
+ symbol.IsInstanceOf(knownSymbols.IRaiseableAsyncRaisesAsync);
+ }
+
+ ///
+ /// 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.
+ ///
+ /// The method symbol to check.
+ /// True if this is likely a Moq Raises method; otherwise false.
+ 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;
}
///