diff --git a/src/Common/ISymbolExtensions.Moq.cs b/src/Common/ISymbolExtensions.Moq.cs new file mode 100644 index 000000000..788a36567 --- /dev/null +++ b/src/Common/ISymbolExtensions.Moq.cs @@ -0,0 +1,251 @@ +namespace Moq.Analyzers.Common; + +/// +/// Extension methods for detecting Moq-specific symbols (Setup, Verify, Returns, Callback, Raises, etc.). +/// +internal static partial class ISymbolExtensions +{ + /// + /// Determines whether a type symbol represents a Task or ValueTask type. + /// + /// The type symbol to check. + /// The known symbols from the compilation. + /// True if the type is Task, Task<T>, ValueTask, or ValueTask<T>; otherwise false. + internal static bool IsTaskOrValueTaskType(this ITypeSymbol typeSymbol, MoqKnownSymbols knownSymbols) + { + if (typeSymbol is not INamedTypeSymbol namedType) + { + return false; + } + + // Check for Task, Task, ValueTask, or ValueTask + INamedTypeSymbol originalDefinition = namedType.OriginalDefinition; + + return SymbolEqualityComparer.Default.Equals(originalDefinition, knownSymbols.Task) || + SymbolEqualityComparer.Default.Equals(originalDefinition, knownSymbols.Task1) || + SymbolEqualityComparer.Default.Equals(originalDefinition, knownSymbols.ValueTask) || + SymbolEqualityComparer.Default.Equals(originalDefinition, knownSymbols.ValueTask1); + } + + internal static bool IsTaskOrValueResultProperty(this ISymbol symbol, MoqKnownSymbols knownSymbols) + { + if (symbol is IPropertySymbol propertySymbol) + { + return IsGenericResultProperty(propertySymbol, knownSymbols.Task1) + || IsGenericResultProperty(propertySymbol, knownSymbols.ValueTask1); + } + + return false; + } + + internal static bool IsMoqSetupMethod(this ISymbol symbol, MoqKnownSymbols knownSymbols) + { + return symbol.IsInstanceOf(knownSymbols.Mock1Setup) && symbol is IMethodSymbol { IsGenericMethod: true }; + } + + internal static bool IsMoqSetupAddMethod(this ISymbol symbol, MoqKnownSymbols knownSymbols) + { + return symbol.IsInstanceOf(knownSymbols.Mock1SetupAdd); + } + + internal static bool IsMoqSetupRemoveMethod(this ISymbol symbol, MoqKnownSymbols knownSymbols) + { + return symbol.IsInstanceOf(knownSymbols.Mock1SetupRemove); + } + + internal static bool IsMoqEventSetupMethod(this ISymbol symbol, MoqKnownSymbols knownSymbols) + { + return symbol.IsMoqSetupAddMethod(knownSymbols) || symbol.IsMoqSetupRemoveMethod(knownSymbols); + } + + internal static bool IsMoqSetupSequenceMethod(this ISymbol symbol, MoqKnownSymbols knownSymbols) + { + return symbol.IsInstanceOf(knownSymbols.Mock1SetupSequence) && symbol is IMethodSymbol { IsGenericMethod: true }; + } + + internal static bool IsMoqVerificationMethod(this ISymbol symbol, MoqKnownSymbols knownSymbols) + { + return symbol.IsInstanceOf(knownSymbols.Mock1Verify) || + symbol.IsInstanceOf(knownSymbols.Mock1VerifyGet) || + symbol.IsInstanceOf(knownSymbols.Mock1VerifySet) || + symbol.IsInstanceOf(knownSymbols.Mock1VerifyNoOtherCalls); + } + + /// + /// Determines whether a symbol is a Moq Returns method. + /// + /// The symbol to check. + /// The known symbols for type checking. + /// True if the symbol is a Returns method from any Moq.Language.IReturns interface; otherwise false. + internal static bool IsMoqReturnsMethod(this ISymbol symbol, MoqKnownSymbols knownSymbols) + { + return symbol.IsInstanceOf(knownSymbols.IReturnsReturns) || + symbol.IsInstanceOf(knownSymbols.IReturns1Returns) || + symbol.IsInstanceOf(knownSymbols.IReturns2Returns); + } + + /// + /// Determines whether a symbol is a Moq Throws method. + /// + /// The symbol to check. + /// The known symbols for type checking. + /// True if the symbol is a Throws method from Moq.Language.IThrows; otherwise false. + internal static bool IsMoqThrowsMethod(this ISymbol symbol, MoqKnownSymbols knownSymbols) + { + return symbol.IsInstanceOf(knownSymbols.IThrowsThrows); + } + + /// + /// Determines whether a symbol is a Moq ReturnsAsync extension method. + /// + /// The symbol to check. + /// The known symbols for type checking. + /// True if the symbol is a ReturnsAsync method from Moq.ReturnsExtensions; otherwise false. + internal static bool IsMoqReturnsAsyncMethod(this ISymbol symbol, MoqKnownSymbols knownSymbols) + { + return symbol.IsInstanceOf(knownSymbols.ReturnsExtensionsReturnsAsync); + } + + /// + /// Determines whether a symbol is a Moq ThrowsAsync extension method. + /// + /// The symbol to check. + /// The known symbols for type checking. + /// True if the symbol is a ThrowsAsync method from Moq.ReturnsExtensions; otherwise false. + internal static bool IsMoqThrowsAsyncMethod(this ISymbol symbol, MoqKnownSymbols knownSymbols) + { + return symbol.IsInstanceOf(knownSymbols.ReturnsExtensionsThrowsAsync); + } + + /// + /// Determines whether a symbol is any Moq method that specifies a return value + /// (Returns, ReturnsAsync, Throws, or ThrowsAsync). + /// + /// The symbol to check. + /// The known symbols for type checking. + /// True if the symbol is a return value specification method; otherwise false. + internal static bool IsMoqReturnValueSpecificationMethod(this ISymbol symbol, MoqKnownSymbols knownSymbols) + { + return symbol.IsMoqReturnsMethod(knownSymbols) || + symbol.IsMoqThrowsMethod(knownSymbols) || + symbol.IsMoqReturnsAsyncMethod(knownSymbols) || + symbol.IsMoqThrowsAsyncMethod(knownSymbols); + } + + /// + /// Determines whether a symbol is a Moq Callback method. + /// + /// The symbol to check. + /// The known symbols for type checking. + /// True if the symbol is a Callback method from Moq.Language.ICallback; otherwise false. + internal static bool IsMoqCallbackMethod(this ISymbol symbol, MoqKnownSymbols knownSymbols) + { + return symbol.IsInstanceOf(knownSymbols.ICallbackCallback) || + symbol.IsInstanceOf(knownSymbols.ICallback1Callback) || + symbol.IsInstanceOf(knownSymbols.ICallback2Callback); + } + + /// + /// Determines whether a symbol is a Moq Raises method. + /// + /// The symbol to check. + /// The known symbols for type checking. + /// 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) + { + return false; + } + + return IsCallbackRaisesMethod(symbol, knownSymbols) || + IsReturnsRaisesMethod(symbol, knownSymbols) || + IsRaiseableMethod(symbol, knownSymbols) || + IsSetupRaisesMethod(symbol, knownSymbols) || + IsConcreteSetupPhraseRaisesMethod(symbol, knownSymbols); + } + + /// + /// Checks if the symbol is a Raises method from ISetup / ISetupPhrase interfaces. + /// + private static bool IsSetupRaisesMethod(ISymbol symbol, MoqKnownSymbols knownSymbols) + { + return symbol.IsInstanceOf(knownSymbols.ISetup1Raises) || + symbol.IsInstanceOf(knownSymbols.ISetupPhrase1Raises) || + symbol.IsInstanceOf(knownSymbols.ISetupGetter1Raises) || + symbol.IsInstanceOf(knownSymbols.ISetupSetter1Raises); + } + + /// + /// Checks if the symbol is a Raises method on concrete setup phrase types (Void/NonVoid). + /// + private static bool IsConcreteSetupPhraseRaisesMethod(ISymbol symbol, MoqKnownSymbols knownSymbols) + { + return symbol.IsInstanceOf(knownSymbols.VoidSetupPhrase1Raises) || + symbol.IsInstanceOf(knownSymbols.NonVoidSetupPhrase2Raises); + } + + /// + /// 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) || + symbol.IsInstanceOf(knownSymbols.ISetupGetterRaises) || + symbol.IsInstanceOf(knownSymbols.ISetupSetterRaises); + } + + /// + /// 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) || + symbol.IsInstanceOf(knownSymbols.IRaise1Raises) || + symbol.IsInstanceOf(knownSymbols.IRaise1RaisesAsync); + } + + /// + /// Checks if a property is the 'Result' property on or . + /// + private static bool IsGenericResultProperty(this ISymbol symbol, INamedTypeSymbol? genericType) + { + if (symbol is IPropertySymbol propertySymbol) + { + // Check if the property is named "Result" + if (!string.Equals(propertySymbol.Name, "Result", StringComparison.Ordinal)) + { + return false; + } + + return genericType != null && + + // If Task type cannot be found, we skip it + SymbolEqualityComparer.Default.Equals(propertySymbol.ContainingType.OriginalDefinition, genericType); + } + + return false; + } +} diff --git a/src/Common/ISymbolExtensions.cs b/src/Common/ISymbolExtensions.cs index 781d5096f..da0a5db72 100644 --- a/src/Common/ISymbolExtensions.cs +++ b/src/Common/ISymbolExtensions.cs @@ -3,9 +3,12 @@ namespace Moq.Analyzers.Common; -internal static class ISymbolExtensions +/// +/// General-purpose Roslyn symbol extension methods with no Moq-specific dependencies. +/// +internal static partial class ISymbolExtensions { - public static bool IsConstructor(this ISymbol symbol) + internal static bool IsConstructor(this ISymbol symbol) { return symbol.DeclaredAccessibility != Accessibility.Private && symbol is IMethodSymbol { MethodKind: MethodKind.Constructor } and { IsStatic: false }; @@ -28,7 +31,7 @@ public static bool IsConstructor(this ISymbol symbol) /// /// MyType{int}() is an instance of MyType{T}(). /// - public static bool IsInstanceOf(this ISymbol? symbol, TSymbol? other, SymbolEqualityComparer? symbolEqualityComparer = null) + internal static bool IsInstanceOf(this ISymbol? symbol, TSymbol? other, SymbolEqualityComparer? symbolEqualityComparer = null) where TSymbol : class, ISymbol { symbolEqualityComparer ??= SymbolEqualityComparer.Default; @@ -74,7 +77,7 @@ public static bool IsInstanceOf(this ISymbol? symbol, TSymbol? other, S /// The matching symbol if is an instance of any of . otherwise. /// /// The to use for equality. - public static bool IsInstanceOf(this ISymbol symbol, ImmutableArray others, [NotNullWhen(true)] out TSymbol? matchingSymbol, SymbolEqualityComparer? symbolEqualityComparer = null) + internal static bool IsInstanceOf(this ISymbol symbol, ImmutableArray others, [NotNullWhen(true)] out TSymbol? matchingSymbol, SymbolEqualityComparer? symbolEqualityComparer = null) where TSymbol : class, ISymbol { symbolEqualityComparer ??= SymbolEqualityComparer.Default; @@ -98,14 +101,14 @@ public static bool IsInstanceOf(this ISymbol symbol, ImmutableArray if matches any of others. /// /// The to use for equality. - public static bool IsInstanceOf(this ISymbol symbol, ImmutableArray others, SymbolEqualityComparer? symbolEqualityComparer = null) + internal static bool IsInstanceOf(this ISymbol symbol, ImmutableArray others, SymbolEqualityComparer? symbolEqualityComparer = null) where TSymbol : class, ISymbol { return symbol.IsInstanceOf(others, out _, symbolEqualityComparer); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool IsOverridable(this ISymbol symbol) + internal static bool IsOverridable(this ISymbol symbol) { return symbol switch { @@ -115,248 +118,4 @@ public static bool IsOverridable(this ISymbol symbol) (symbol.IsVirtual || symbol.IsAbstract || symbol is { IsOverride: true, IsSealed: false }), }; } - - public static bool IsTaskOrValueResultProperty(this ISymbol symbol, MoqKnownSymbols knownSymbols) - { - if (symbol is IPropertySymbol propertySymbol) - { - return IsGenericResultProperty(propertySymbol, knownSymbols.Task1) - || IsGenericResultProperty(propertySymbol, knownSymbols.ValueTask1); - } - - return false; - } - - /// - /// Determines whether a type symbol represents a Task or ValueTask type. - /// - /// The type symbol to check. - /// The known symbols from the compilation. - /// True if the type is Task, Task<T>, ValueTask, or ValueTask<T>; otherwise false. - public static bool IsTaskOrValueTaskType(this ITypeSymbol typeSymbol, MoqKnownSymbols knownSymbols) - { - if (typeSymbol is not INamedTypeSymbol namedType) - { - return false; - } - - // Check for Task, Task, ValueTask, or ValueTask - INamedTypeSymbol originalDefinition = namedType.OriginalDefinition; - - return SymbolEqualityComparer.Default.Equals(originalDefinition, knownSymbols.Task) || - SymbolEqualityComparer.Default.Equals(originalDefinition, knownSymbols.Task1) || - SymbolEqualityComparer.Default.Equals(originalDefinition, knownSymbols.ValueTask) || - SymbolEqualityComparer.Default.Equals(originalDefinition, knownSymbols.ValueTask1); - } - - internal static bool IsMoqSetupMethod(this ISymbol symbol, MoqKnownSymbols knownSymbols) - { - return symbol.IsInstanceOf(knownSymbols.Mock1Setup) && symbol is IMethodSymbol { IsGenericMethod: true }; - } - - internal static bool IsMoqSetupAddMethod(this ISymbol symbol, MoqKnownSymbols knownSymbols) - { - return symbol.IsInstanceOf(knownSymbols.Mock1SetupAdd); - } - - internal static bool IsMoqSetupRemoveMethod(this ISymbol symbol, MoqKnownSymbols knownSymbols) - { - return symbol.IsInstanceOf(knownSymbols.Mock1SetupRemove); - } - - internal static bool IsMoqEventSetupMethod(this ISymbol symbol, MoqKnownSymbols knownSymbols) - { - return symbol.IsMoqSetupAddMethod(knownSymbols) || symbol.IsMoqSetupRemoveMethod(knownSymbols); - } - - internal static bool IsMoqSetupSequenceMethod(this ISymbol symbol, MoqKnownSymbols knownSymbols) - { - return symbol.IsInstanceOf(knownSymbols.Mock1SetupSequence) && symbol is IMethodSymbol { IsGenericMethod: true }; - } - - internal static bool IsMoqVerificationMethod(this ISymbol symbol, MoqKnownSymbols knownSymbols) - { - return symbol.IsInstanceOf(knownSymbols.Mock1Verify) || - symbol.IsInstanceOf(knownSymbols.Mock1VerifyGet) || - symbol.IsInstanceOf(knownSymbols.Mock1VerifySet) || - symbol.IsInstanceOf(knownSymbols.Mock1VerifyNoOtherCalls); - } - - /// - /// Determines whether a symbol is a Moq Returns method. - /// - /// The symbol to check. - /// The known symbols for type checking. - /// True if the symbol is a Returns method from any Moq.Language.IReturns interface; otherwise false. - internal static bool IsMoqReturnsMethod(this ISymbol symbol, MoqKnownSymbols knownSymbols) - { - return symbol.IsInstanceOf(knownSymbols.IReturnsReturns) || - symbol.IsInstanceOf(knownSymbols.IReturns1Returns) || - symbol.IsInstanceOf(knownSymbols.IReturns2Returns); - } - - /// - /// Determines whether a symbol is a Moq Throws method. - /// - /// The symbol to check. - /// The known symbols for type checking. - /// True if the symbol is a Throws method from Moq.Language.IThrows; otherwise false. - internal static bool IsMoqThrowsMethod(this ISymbol symbol, MoqKnownSymbols knownSymbols) - { - return symbol.IsInstanceOf(knownSymbols.IThrowsThrows); - } - - /// - /// Determines whether a symbol is a Moq ReturnsAsync extension method. - /// - /// The symbol to check. - /// The known symbols for type checking. - /// True if the symbol is a ReturnsAsync method from Moq.ReturnsExtensions; otherwise false. - internal static bool IsMoqReturnsAsyncMethod(this ISymbol symbol, MoqKnownSymbols knownSymbols) - { - return symbol.IsInstanceOf(knownSymbols.ReturnsExtensionsReturnsAsync); - } - - /// - /// Determines whether a symbol is a Moq ThrowsAsync extension method. - /// - /// The symbol to check. - /// The known symbols for type checking. - /// True if the symbol is a ThrowsAsync method from Moq.ReturnsExtensions; otherwise false. - internal static bool IsMoqThrowsAsyncMethod(this ISymbol symbol, MoqKnownSymbols knownSymbols) - { - return symbol.IsInstanceOf(knownSymbols.ReturnsExtensionsThrowsAsync); - } - - /// - /// Determines whether a symbol is any Moq method that specifies a return value - /// (Returns, ReturnsAsync, Throws, or ThrowsAsync). - /// - /// The symbol to check. - /// The known symbols for type checking. - /// True if the symbol is a return value specification method; otherwise false. - internal static bool IsMoqReturnValueSpecificationMethod(this ISymbol symbol, MoqKnownSymbols knownSymbols) - { - return symbol.IsMoqReturnsMethod(knownSymbols) || - symbol.IsMoqThrowsMethod(knownSymbols) || - symbol.IsMoqReturnsAsyncMethod(knownSymbols) || - symbol.IsMoqThrowsAsyncMethod(knownSymbols); - } - - /// - /// Determines whether a symbol is a Moq Callback method. - /// - /// The symbol to check. - /// The known symbols for type checking. - /// True if the symbol is a Callback method from Moq.Language.ICallback; otherwise false. - internal static bool IsMoqCallbackMethod(this ISymbol symbol, MoqKnownSymbols knownSymbols) - { - return symbol.IsInstanceOf(knownSymbols.ICallbackCallback) || - symbol.IsInstanceOf(knownSymbols.ICallback1Callback) || - symbol.IsInstanceOf(knownSymbols.ICallback2Callback); - } - - /// - /// Determines whether a symbol is a Moq Raises method. - /// - /// The symbol to check. - /// The known symbols for type checking. - /// 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) - { - return false; - } - - return IsCallbackRaisesMethod(symbol, knownSymbols) || - IsReturnsRaisesMethod(symbol, knownSymbols) || - IsRaiseableMethod(symbol, knownSymbols) || - IsSetupRaisesMethod(symbol, knownSymbols) || - IsConcreteSetupPhraseRaisesMethod(symbol, knownSymbols); - } - - /// - /// Checks if the symbol is a Raises method from ISetup / ISetupPhrase interfaces. - /// - private static bool IsSetupRaisesMethod(ISymbol symbol, MoqKnownSymbols knownSymbols) - { - return symbol.IsInstanceOf(knownSymbols.ISetup1Raises) || - symbol.IsInstanceOf(knownSymbols.ISetupPhrase1Raises) || - symbol.IsInstanceOf(knownSymbols.ISetupGetter1Raises) || - symbol.IsInstanceOf(knownSymbols.ISetupSetter1Raises); - } - - /// - /// Checks if the symbol is a Raises method on concrete setup phrase types (Void/NonVoid). - /// - private static bool IsConcreteSetupPhraseRaisesMethod(ISymbol symbol, MoqKnownSymbols knownSymbols) - { - return symbol.IsInstanceOf(knownSymbols.VoidSetupPhrase1Raises) || - symbol.IsInstanceOf(knownSymbols.NonVoidSetupPhrase2Raises); - } - - /// - /// 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) || - symbol.IsInstanceOf(knownSymbols.ISetupGetterRaises) || - symbol.IsInstanceOf(knownSymbols.ISetupSetterRaises); - } - - /// - /// 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) || - symbol.IsInstanceOf(knownSymbols.IRaise1Raises) || - symbol.IsInstanceOf(knownSymbols.IRaise1RaisesAsync); - } - - /// - /// Checks if a property is the 'Result' property on or . - /// - private static bool IsGenericResultProperty(this ISymbol symbol, INamedTypeSymbol? genericType) - { - if (symbol is IPropertySymbol propertySymbol) - { - // Check if the property is named "Result" - if (!string.Equals(propertySymbol.Name, "Result", StringComparison.Ordinal)) - { - return false; - } - - return genericType != null && - - // If Task type cannot be found, we skip it - SymbolEqualityComparer.Default.Equals(propertySymbol.ContainingType.OriginalDefinition, genericType); - } - - return false; - } }