diff --git a/src/Framework/AspNetCoreAnalyzers/src/Analyzers/Http/RequestDelegateReturnTypeAnalyzer.cs b/src/Framework/AspNetCoreAnalyzers/src/Analyzers/Http/RequestDelegateReturnTypeAnalyzer.cs index 9ce7c69a9ecb..a8b52ec826b1 100644 --- a/src/Framework/AspNetCoreAnalyzers/src/Analyzers/Http/RequestDelegateReturnTypeAnalyzer.cs +++ b/src/Framework/AspNetCoreAnalyzers/src/Analyzers/Http/RequestDelegateReturnTypeAnalyzer.cs @@ -55,6 +55,12 @@ public override void Initialize(AnalysisContext context) if (item is IReturnOperation returnOperation && returnOperation.ReturnedValue is { } returnedValue) { + // Skip return operations that belong to nested anonymous functions + if (IsReturnFromNestedAnonymousFunction(returnOperation, anonymousFunction)) + { + continue; + } + var resolvedOperation = WalkDownConversion(returnedValue); var returnType = resolvedOperation.Type; @@ -81,6 +87,32 @@ private static void AddDiagnosticWarning(OperationAnalysisContext context, Locat ((INamedTypeSymbol)returnType).TypeArguments[0].ToString())); } + private static bool IsReturnFromNestedAnonymousFunction(IReturnOperation returnOperation, IAnonymousFunctionOperation targetAnonymousFunction) + { + // Walk up the parent chain from the return operation to see if we encounter + // a nested anonymous function before reaching the target anonymous function + var current = returnOperation.Parent; + while (current != null) + { + if (ReferenceEquals(current, targetAnonymousFunction)) + { + // We reached the target anonymous function without finding a nested one + return false; + } + + if (current is IAnonymousFunctionOperation) + { + // We found a nested anonymous function before reaching the target + return true; + } + + current = current.Parent; + } + + // This shouldn't happen in valid code, but return true to be safe + return true; + } + private static IOperation WalkDownConversion(IOperation operation) { while (operation is IConversionOperation conversionOperation) diff --git a/src/Framework/AspNetCoreAnalyzers/test/Http/RequestDelegateReturnTypeAnalyzerTests.cs b/src/Framework/AspNetCoreAnalyzers/test/Http/RequestDelegateReturnTypeAnalyzerTests.cs index be5fe3adcea4..049e1e360df7 100644 --- a/src/Framework/AspNetCoreAnalyzers/test/Http/RequestDelegateReturnTypeAnalyzerTests.cs +++ b/src/Framework/AspNetCoreAnalyzers/test/Http/RequestDelegateReturnTypeAnalyzerTests.cs @@ -347,6 +347,42 @@ await VerifyCS.VerifyAnalyzerAsync(@" webApp.MapGet(""/"", HttpMethod); static Task HttpMethod(HttpContext context) => Task.CompletedTask; +"); + } + + [Fact] + public async Task AnonymousDelegate_RequestDelegate_ReturnType_InNestedAnonymousDelegate_DoesNotReportDiagnostics() + { + await VerifyCS.VerifyAnalyzerAsync(@" +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Builder; +var webApp = WebApplication.Create(); +webApp.Run((context) => +{ + Func> _ = () => Task.FromResult(DateTime.Now); + return Task.CompletedTask; +}); +"); + } + + [Fact] + public async Task AnonymousDelegate_RequestDelegate_ReturnType_InNestedLambdaPassedToMethod_DoesNotReportDiagnostics() + { + // This test simulates the real-world case from the issue where a lambda + // returning Task is passed to ThrowsAsync as Func> + await VerifyCS.VerifyAnalyzerAsync(@" +using System; +using System.IO; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Builder; +var webApp = WebApplication.Create(); +webApp.Run(async (context) => +{ + await SomeMethodThatTakesFunc(() => Task.FromResult(Stream.Null)); +}); + +static Task SomeMethodThatTakesFunc(Func> func) => Task.CompletedTask; "); } }

AltStyle によって変換されたページ (->オリジナル) /