Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions eng/Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@
<DotNetProjects Include="
$(RepoRoot)src\Framework\App.Ref\src\Microsoft.AspNetCore.App.Ref.csproj;
$(RepoRoot)src\Framework\App.Ref.Internal\src\Microsoft.AspNetCore.App.Ref.Internal.csproj;
$(RepoRoot)src\Framework\AspNetCoreAnalyzers\test\Microsoft.AspNetCore.App.Analyzers.Test.csproj;
$(RepoRoot)src\Framework\test\Microsoft.AspNetCore.App.UnitTests.csproj;
$(RepoRoot)src\Caching\**\*.*proj;
$(RepoRoot)src\DefaultBuilder\**\*.*proj;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
Expand All @@ -22,23 +23,17 @@ private static void DetectMisplacedLambdaAttribute(
// Hello();
// return "foo";
// }
IMethodSymbol? methodSymbol = null;

// () => Hello() has a single child which is a BlockOperation so we check to see if
// expression associated with that operation is an invocation expression
if (lambda.ChildOperations.FirstOrDefault().Syntax is InvocationExpressionSyntax innerInvocation)
// All lambdas have a single child which is a BlockOperation. We search ChildOperations for
// the invocation expression.
if (lambda.ChildOperations.Count != 1 || lambda.ChildOperations.FirstOrDefault() is not IBlockOperation blockOperation)
{
methodSymbol = lambda.Symbol;
}

if (lambda.ChildOperations.FirstOrDefault().ChildOperations.FirstOrDefault() is IReturnOperation returnOperation
&& returnOperation.ReturnedValue is IInvocationOperation returnedInvocation)
{
methodSymbol = returnedInvocation.TargetMethod;
Debug.Fail("Expected a single top-level BlockOperation for all lambdas.");
return;
}

// If no method definition was found for the lambda, then abort.
if (methodSymbol is null)
if (GetReturnedInvocation(blockOperation) is not IMethodSymbol methodSymbol)
{
return;
}
Expand Down Expand Up @@ -73,5 +68,38 @@ static bool IsInValidNamespace(INamespaceSymbol? @namespace)

return false;
}

static IMethodSymbol? GetReturnedInvocation(IBlockOperation blockOperation)
{
foreach (var op in blockOperation.ChildOperations.Reverse())
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we search the child operations in reverse here?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I figured it made sense to search from the end of the method for return operations instead of the start because I'd expect most returns to be nearer to the end. Multiple return operations are an edge case this analyzer never really accounted for anyway.

{
if (op is IReturnOperation returnStatement)
{
if (returnStatement.ReturnedValue is IInvocationOperation invocationReturn)
{
return invocationReturn.TargetMethod;
}

// Sometimes expression backed lambdas include an IReturnOperation with a null ReturnedValue
// right after the IExpressionStatementOperation whose Operation is the real ReturnedValue,
// so we keep looking backwards rather than returning null immediately.
}
else if (op is IExpressionStatementOperation expression)
{
if (expression.Operation is IInvocationOperation invocationExpression)
{
return invocationExpression.TargetMethod;
}

return null;
}
else
{
return null;
}
}

return null;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ public WebApplicationBuilderAnalyzerTest()
TrimAssemblyExtension(typeof(Microsoft.Extensions.Hosting.HostingHostBuilderExtensions).Assembly.Location),
TrimAssemblyExtension(typeof(Microsoft.AspNetCore.Builder.ConfigureHostBuilder).Assembly.Location),
TrimAssemblyExtension(typeof(Microsoft.AspNetCore.Builder.ConfigureWebHostBuilder).Assembly.Location),
TrimAssemblyExtension(typeof(Microsoft.AspNetCore.Builder.EndpointRoutingApplicationBuilderExtensions).Assembly.Location),
TrimAssemblyExtension(typeof(Microsoft.AspNetCore.Hosting.HostingAbstractionsWebHostBuilderExtensions).Assembly.Location),
TrimAssemblyExtension(typeof(Microsoft.Extensions.Logging.ILoggingBuilder).Assembly.Location),
TrimAssemblyExtension(typeof(Microsoft.Extensions.Logging.ConsoleLoggerExtensions).Assembly.Location),
Expand Down