diff --git a/src/Analyzers/ConstructorArgumentsShouldMatchAnalyzer.cs b/src/Analyzers/ConstructorArgumentsShouldMatchAnalyzer.cs index ae93b7e..dc421d9 100644 --- a/src/Analyzers/ConstructorArgumentsShouldMatchAnalyzer.cs +++ b/src/Analyzers/ConstructorArgumentsShouldMatchAnalyzer.cs @@ -142,6 +142,8 @@ private static void VerifyInterfaceMockAttempt( } private static void AnalyzeCompilation(CompilationStartAnalysisContext context) + context.RegisterSyntaxNodeAction(context => AnalyzeObjectCreation(context, knownSymbols), SyntaxKind.ObjectCreationExpression); + context.RegisterSyntaxNodeAction(context => AnalyzeInvocationExpression(context, knownSymbols), SyntaxKind.InvocationExpression); { context.CancellationToken.ThrowIfCancellationRequested(); @@ -165,8 +167,6 @@ private static void AnalyzeCompilation(CompilationStartAnalysisContext context) } // These are for classes - context.RegisterSyntaxNodeAction(context => AnalyzeNewObject(context, knownSymbols), SyntaxKind.ObjectCreationExpression); - context.RegisterSyntaxNodeAction(context => AnalyzeInstanceCall(context, knownSymbols), SyntaxKind.InvocationExpression); } private static void AnalyzeInstanceCall(SyntaxNodeAnalysisContext context, MoqKnownSymbols knownSymbols) @@ -411,6 +411,11 @@ private static void VerifyMockAttempt( } } + private static void VerifyClassMockAttempt( + SyntaxNodeAnalysisContext context, + ITypeSymbol mockedClass, + ArgumentListSyntax? argumentList, + ArgumentSyntax[] arguments) private static void VerifyClassMockAttempt( SyntaxNodeAnalysisContext context, ITypeSymbol mockedClass, @@ -423,6 +428,13 @@ private static void VerifyClassMockAttempt( .Where(methodSymbol => methodSymbol.IsConstructor()) .ToArray(); + // Check if the first argument is a Func + if (arguments.Length > 0 && IsFactoryMethodArgument(context, arguments[0])) + { + // If it's a Func, we don't need to check constructor arguments + return; + } + // Bail out early if there are no arguments on constructors or no constructors at all (bool IsEmpty, Location Location) constructorIsEmpty = ConstructorIsEmpty(constructors, argumentList, context); if (constructorIsEmpty.IsEmpty) @@ -439,4 +451,36 @@ private static void VerifyClassMockAttempt( context.ReportDiagnostic(diagnostic); } } -} + + private static bool IsFactoryMethodArgument(SyntaxNodeAnalysisContext context, ArgumentSyntax argument) + { + var symbolInfo = context.SemanticModel.GetSymbolInfo(argument.Expression); + if (symbolInfo.Symbol is IMethodSymbol methodSymbol) + { + var containingType = methodSymbol.ContainingType; + return containingType != null && containingType.Name == "Func" && containingType.TypeArguments.Length == 1; + } + return false; + } + { + IMethodSymbol[] constructors = mockedClass + .GetMembers() + .OfType() + .Where(methodSymbol => methodSymbol.IsConstructor()) + .ToArray(); + + // Bail out early if there are no arguments on constructors or no constructors at all + (bool IsEmpty, Location Location) constructorIsEmpty = ConstructorIsEmpty(constructors, argumentList, context); + if (constructorIsEmpty.IsEmpty) + { + Diagnostic diagnostic = constructorIsEmpty.Location.CreateDiagnostic(ClassMustHaveMatchingConstructor, argumentList); + context.ReportDiagnostic(diagnostic); + return; + } + + // We have constructors, now we need to check if the arguments match any of them + if (!AnyConstructorsFound(constructors, arguments, context)) + { + Diagnostic diagnostic = constructorIsEmpty.Location.CreateDiagnostic(ClassMustHaveMatchingConstructor, argumentList); + context.ReportDiagnostic(diagnostic); + }