@@ -106,6 +106,13 @@ private static bool IsFirstArgumentMockBehavior(SyntaxNodeAnalysisContext contex
106106 return IsExpressionMockBehavior ( context , knownSymbols , expression ) ;
107107 }
108108
109+ private static bool IsSecondArgumentMockBehavior ( SyntaxNodeAnalysisContext context , MoqKnownSymbols knownSymbols , ArgumentListSyntax ? argumentList )
110+ {
111+ ExpressionSyntax ? expression = argumentList ? . Arguments [ 1 ] . Expression ;
112+
113+ return IsExpressionMockBehavior ( context , knownSymbols , expression ) ;
114+ }
115+
109116 private static void VerifyDelegateMockAttempt (
110117 SyntaxNodeAnalysisContext context ,
111118 ArgumentListSyntax ? argumentList ,
@@ -267,10 +274,13 @@ private static void AnalyzeNewObject(SyntaxNodeAnalysisContext context, MoqKnown
267274 /// <param name="constructors">The constructors.</param>
268275 /// <param name="arguments">The arguments.</param>
269276 /// <param name="context">The context.</param>
270- /// <returns><c>true</c> if a suitable constructor was found; otherwise <c>false</c>. </returns>
277+ /// <returns>
278+ /// <see langword="true" /> if a suitable constructor was found; otherwise <see langword="false" />.
279+ /// If the construction method is a parenthesized lambda expression, <see langword="null" /> is returned.
280+ /// </returns>
271281 /// <remarks>Handles <see langword="params" /> and optional parameters.</remarks>
272282 [ SuppressMessage ( "Design" , "MA0051:Method is too long" , Justification = "This should be refactored; suppressing for now to enable TreatWarningsAsErrors in CI." ) ]
273- private static bool AnyConstructorsFound (
283+ private static bool ? AnyConstructorsFound (
274284 IMethodSymbol [ ] constructors ,
275285 ArgumentSyntax [ ] arguments ,
276286 SyntaxNodeAnalysisContext context )
@@ -348,6 +358,24 @@ private static bool AnyConstructorsFound(
348358 }
349359 }
350360
361+ // Special case for Lambda expression syntax
362+ // In Moq you can specify a Lambda expression that creates an instance
363+ // of the specified type
364+ // See https://github.com/devlooped/moq/blob/18dc7410ad4f993ce0edd809c5dfcaa3199f13ff/src/Moq/Mock%601.cs#L200
365+ //
366+ // The parenthesized lambda takes arguments as the first child node
367+ // which may be empty or have args defined as part of a closure.
368+ // Either way, we don't care about that, we only care that the
369+ // constructor is valid.
370+ //
371+ // Since this does not use reflection through Castle, an invalid
372+ // lambda here would cause the compiler to break, so no need to
373+ // do additional checks.
374+ if ( arguments . Length == 1 && arguments [ 0 ] . Expression . IsKind ( SyntaxKind . ParenthesizedLambdaExpression ) )
375+ {
376+ return null ;
377+ }
378+
351379 return false ;
352380 }
353381
@@ -386,10 +414,18 @@ private static void VerifyMockAttempt(
386414 ArgumentSyntax [ ] arguments = argumentList ? . Arguments . ToArray ( ) ?? [ ] ;
387415#pragma warning restore ECS0900 // Consider using an alternative implementation to avoid boxing and unboxing
388416
389- if ( hasMockBehavior && arguments . Length > 0 && IsFirstArgumentMockBehavior ( context , knownSymbols , argumentList ) )
417+ if ( hasMockBehavior && arguments . Length > 0 )
390418 {
391- // They passed a mock behavior as the first argument; ignore as Moq swallows it
392- arguments = arguments . RemoveAt ( 0 ) ;
419+ if ( arguments . Length >= 1 && IsFirstArgumentMockBehavior ( context , knownSymbols , argumentList ) )
420+ {
421+ // They passed a mock behavior as the first argument; ignore as Moq swallows it
422+ arguments = arguments . RemoveAt ( 0 ) ;
423+ }
424+ else if ( arguments . Length >= 2 && IsSecondArgumentMockBehavior ( context , knownSymbols , argumentList ) )
425+ {
426+ // They passed a mock behavior as the second argument; ignore as Moq swallows it
427+ arguments = arguments . RemoveAt ( 1 ) ;
428+ }
393429 }
394430
395431 switch ( mockedClass . TypeKind )
@@ -433,7 +469,9 @@ private static void VerifyClassMockAttempt(
433469 }
434470
435471 // We have constructors, now we need to check if the arguments match any of them
436- if ( ! AnyConstructorsFound ( constructors , arguments , context ) )
472+ // If the value is null it means we want to ignore and not create a diagnostic
473+ bool ? matchingCtorFound = AnyConstructorsFound ( constructors , arguments , context ) ;
474+ if ( matchingCtorFound . HasValue && ! matchingCtorFound . Value )
437475 {
438476 Diagnostic diagnostic = constructorIsEmpty . Location . CreateDiagnostic ( ClassMustHaveMatchingConstructor , argumentList ) ;
439477 context . ReportDiagnostic ( diagnostic ) ;
0 commit comments