File: RouteEmbeddedLanguage\Infrastructure\RoutePatternParametersDetector.cs
Web Access
Project: src\src\Framework\AspNetCoreAnalyzers\src\Analyzers\Microsoft.AspNetCore.App.Analyzers.csproj (Microsoft.AspNetCore.App.Analyzers)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System;
using System.Collections.Immutable;
using Microsoft.AspNetCore.App.Analyzers.Infrastructure;
using Microsoft.CodeAnalysis;
 
namespace Microsoft.AspNetCore.Analyzers.RouteEmbeddedLanguage.Infrastructure;
 
using WellKnownType = WellKnownTypeData.WellKnownType;
 
internal static class RoutePatternParametersDetector
{
    public static ImmutableArray<ParameterSymbol> ResolvedParameters(ISymbol symbol, WellKnownTypes wellKnownTypes)
    {
        return ResolvedParametersCore(symbol, topLevelSymbol: null, wellKnownTypes);
 
        static ImmutableArray<ParameterSymbol> ResolvedParametersCore(ISymbol symbol, ISymbol? topLevelSymbol, WellKnownTypes wellKnownTypes)
        {
            var resolvedParameterSymbols = ImmutableArray.CreateBuilder<ParameterSymbol>();
            var childSymbols = GetParameterSymbols(symbol);
 
            foreach (var child in childSymbols)
            {
                if (HasSpecialType(child, wellKnownTypes, RouteWellKnownTypes.ParameterSpecialTypes) || HasExplicitNonRouteAttribute(child, wellKnownTypes, RouteWellKnownTypes.NonRouteMetadataTypes))
                {
                    continue;
                }
                else if (child.HasAttribute(wellKnownTypes.Get(WellKnownType.Microsoft_AspNetCore_Http_AsParametersAttribute)))
                {
                    resolvedParameterSymbols.AddRange(ResolvedParametersCore(child.GetParameterType(), child, wellKnownTypes));
                }
                else
                {
                    var routeParameterName = ResolveRouteParameterName(child, wellKnownTypes);
                    resolvedParameterSymbols.Add(new ParameterSymbol(routeParameterName, child, topLevelSymbol));
                }
            }
            return resolvedParameterSymbols.ToImmutable();
        }
 
        static string ResolveRouteParameterName(ISymbol parameterSymbol, WellKnownTypes wellKnownTypes)
        {
            var fromRouteMetadata = wellKnownTypes.Get(WellKnownType.Microsoft_AspNetCore_Http_Metadata_IFromRouteMetadata);
            if (!parameterSymbol.TryGetAttributeImplementingInterface(fromRouteMetadata, out var attributeData))
            {
                return parameterSymbol.Name; // No route metadata attribute!
            }
 
            foreach (var namedArgument in attributeData.NamedArguments)
            {
                if (namedArgument.Key == "Name")
                {
                    var routeParameterNameConstant = namedArgument.Value;
                    var routeParameterName = (string)routeParameterNameConstant.Value!;
                    return routeParameterName; // Have attribute & name is specified.
                }
            }
 
            return parameterSymbol.Name; // We have the attribute, but name isn't specified!
        }
    }
 
    public static ImmutableArray<ISymbol> GetParameterSymbols(ISymbol symbol)
    {
        return symbol switch
        {
            ITypeSymbol typeSymbol => typeSymbol.GetMembers().OfType<IPropertySymbol>().ToImmutableArray().As<ISymbol>(),
            IMethodSymbol methodSymbol => methodSymbol.Parameters.As<ISymbol>(),
            _ => throw new InvalidOperationException("Unexpected symbol type: " + symbol)
        };
    }
 
    private static bool HasSpecialType(ISymbol child, WellKnownTypes wellKnownTypes, WellKnownType[] specialTypes)
    {
        if (child.GetParameterType() is not INamedTypeSymbol type)
        {
            return false;
        }
 
        return wellKnownTypes.IsType(type, specialTypes);
    }
 
    private static bool HasExplicitNonRouteAttribute(ISymbol child, WellKnownTypes wellKnownTypes, WellKnownType[] allNoneRouteMetadataTypes)
    {
        foreach (var attributeData in child.GetAttributes())
        {
            var attributeClass = attributeData.AttributeClass;
 
            if (attributeClass != null && wellKnownTypes.Implements(attributeClass, allNoneRouteMetadataTypes))
            {
                return true;
            }
        }
 
        return false;
    }
}