// 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.CodeAnalysis; using Microsoft.AspNetCore.App.Analyzers.Infrastructure; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Operations; namespace Microsoft.AspNetCore.Http.RequestDelegateGenerator.StaticRouteHandlerModel; internal static class InvocationOperationExtensions { public static readonly string[] KnownMethods = { "MapGet", "MapPost", "MapPut", "MapDelete", "MapPatch", "Map", "MapMethods", "MapFallback" }; public static bool IsValidOperation(this IOperation? operation, WellKnownTypes wellKnownTypes, [NotNullWhen(true)] out IInvocationOperation? invocationOperation) { invocationOperation = null; if (operation is IInvocationOperation targetOperation && targetOperation.TargetMethod.ContainingNamespace is { Name: "Builder", ContainingNamespace: { Name: "AspNetCore", ContainingNamespace: { Name: "Microsoft", ContainingNamespace.IsGlobalNamespace: true } } } && targetOperation.TargetMethod.ContainingAssembly.Name is "Microsoft.AspNetCore.Routing" && targetOperation.TryGetRouteHandlerArgument(out var routeHandlerParameter) && routeHandlerParameter is { Parameter.Type: {} delegateType } && SymbolEqualityComparer.Default.Equals(delegateType, wellKnownTypes.Get(WellKnownTypeData.WellKnownType.System_Delegate))) { invocationOperation = targetOperation; return true; } return false; } public static bool TryGetRouteHandlerMethod(this IInvocationOperation invocation, SemanticModel semanticModel, [NotNullWhen(true)] out IMethodSymbol? method) { method = null; if (invocation.TryGetRouteHandlerArgument(out var argument)) { method = ResolveMethodFromOperation(argument, semanticModel); return method is not null; } return false; } public static bool TryGetRouteHandlerArgument(this IInvocationOperation invocation, [NotNullWhen(true)] out IArgumentOperation? argumentOperation) { argumentOperation = null; // Route handler argument is assumed to be the last parameter provided to // the Map methods. var routeHandlerArgumentOrdinal = invocation.Arguments.Length - 1; foreach (var argument in invocation.Arguments) { if (argument.Parameter?.Ordinal == routeHandlerArgumentOrdinal) { argumentOperation = argument; return true; } } return false; } public static bool TryGetMapMethodName(this SyntaxNode node, out string? methodName) { methodName = default; // Given an invocation like app.MapGet, app.Map, app.MapFallback, etc. get // the value of the Map method being access on the the WebApplication `app`. if (node is InvocationExpressionSyntax { Expression: MemberAccessExpressionSyntax { Name: { Identifier: { ValueText: var method } } } }) { methodName = method; return true; } return false; } private static IMethodSymbol? ResolveMethodFromOperation(IOperation operation, SemanticModel semanticModel) => operation switch { IArgumentOperation argument => ResolveMethodFromOperation(argument.Value, semanticModel), IConversionOperation conv => ResolveMethodFromOperation(conv.Operand, semanticModel), IDelegateCreationOperation del => ResolveMethodFromOperation(del.Target, semanticModel), IFieldReferenceOperation { Field.IsReadOnly: true } f when ResolveDeclarationOperation(f.Field, semanticModel) is IOperation op => ResolveMethodFromOperation(op, semanticModel), IAnonymousFunctionOperation anon => anon.Symbol, ILocalFunctionOperation local => local.Symbol, IMethodReferenceOperation method => method.Method, IParenthesizedOperation parenthesized => ResolveMethodFromOperation(parenthesized.Operand, semanticModel), _ => null }; private static IOperation? ResolveDeclarationOperation(ISymbol symbol, SemanticModel? semanticModel) { foreach (var syntaxReference in symbol.DeclaringSyntaxReferences) { var syn = syntaxReference.GetSyntax(); if (syn is VariableDeclaratorSyntax { Initializer: { Value: var expr } }) { // Use the correct semantic model based on the syntax tree var targetSemanticModel = semanticModel?.Compilation.GetSemanticModel(expr.SyntaxTree); var operation = targetSemanticModel?.GetOperation(expr); if (operation is not null) { return operation; } } } return null; } } |