File: RouteEmbeddedLanguage\RoutePatternBraceMatcher.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.Reflection;
using System.Threading;
using Microsoft.AspNetCore.Analyzers.Infrastructure.RoutePattern;
using Microsoft.AspNetCore.Analyzers.Infrastructure.VirtualChars;
using Microsoft.AspNetCore.App.Analyzers.Infrastructure;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.ExternalAccess.AspNetCore.EmbeddedLanguages;
using RoutePatternToken = Microsoft.AspNetCore.Analyzers.Infrastructure.EmbeddedSyntax.EmbeddedSyntaxToken<Microsoft.AspNetCore.Analyzers.Infrastructure.RoutePattern.RoutePatternKind>;
 
namespace Microsoft.AspNetCore.Analyzers.RouteEmbeddedLanguage;
 
[ExportAspNetCoreEmbeddedLanguageBraceMatcher(name: "Route", language: LanguageNames.CSharp)]
internal class RoutePatternBraceMatcher : IAspNetCoreEmbeddedLanguageBraceMatcher
{
    public AspNetCoreBraceMatchingResult? FindBraces(SemanticModel semanticModel, SyntaxToken token, int position, CancellationToken cancellationToken)
    {
        var routeUsageCache = RouteUsageCache.GetOrCreate(semanticModel.Compilation);
        var routeUsage = routeUsageCache.Get(token, cancellationToken);
        if (routeUsage is null)
        {
            return null;
        }
 
        return GetMatchingBraces(routeUsage.RoutePattern, position);
    }
 
    private static AspNetCoreBraceMatchingResult? GetMatchingBraces(RoutePatternTree tree, int position)
    {
        var virtualChar = tree.Text.Find(position);
        if (virtualChar == null)
        {
            return null;
        }
 
        var ch = virtualChar.Value;
        return ch.Value switch
        {
            '{' or '}' => FindParameterBraces(tree, ch),
            '(' or ')' => FindPolicyParens(tree, ch),
            '[' or ']' => FindReplacementTokenBrackets(tree, ch),
            _ => null,
        };
    }
 
    private static AspNetCoreBraceMatchingResult? FindParameterBraces(RoutePatternTree tree, VirtualChar ch)
    {
        var node = FindParameterNode(tree.Root, ch);
        return node == null ? null : CreateResult(node.OpenBraceToken, node.CloseBraceToken);
    }
 
    private static AspNetCoreBraceMatchingResult? FindPolicyParens(RoutePatternTree tree, VirtualChar ch)
    {
        var node = FindPolicyFragmentEscapedNode(tree.Root, ch);
        return node == null ? null : CreateResult(node.OpenParenToken, node.CloseParenToken);
    }
 
    private static AspNetCoreBraceMatchingResult? FindReplacementTokenBrackets(RoutePatternTree tree, VirtualChar ch)
    {
        var node = FindReplacementNode(tree.Root, ch);
        return node == null ? null : CreateResult(node.OpenBracketToken, node.CloseBracketToken);
    }
 
    private static RoutePatternParameterNode? FindParameterNode(RoutePatternNode node, VirtualChar ch)
        => FindNode<RoutePatternParameterNode>(node, ch, (parameter, c) =>
                parameter.OpenBraceToken.VirtualChars.Contains(c) || parameter.CloseBraceToken.VirtualChars.Contains(c));
 
    private static RoutePatternPolicyFragmentEscapedNode? FindPolicyFragmentEscapedNode(RoutePatternNode node, VirtualChar ch)
        => FindNode<RoutePatternPolicyFragmentEscapedNode>(node, ch, (fragment, c) =>
                fragment.OpenParenToken.VirtualChars.Contains(c) || fragment.CloseParenToken.VirtualChars.Contains(c));
 
    private static RoutePatternReplacementNode? FindReplacementNode(RoutePatternNode node, VirtualChar ch)
        => FindNode<RoutePatternReplacementNode>(node, ch, (fragment, c) =>
                fragment.OpenBracketToken.VirtualChars.Contains(c) || fragment.CloseBracketToken.VirtualChars.Contains(c));
 
    private static TNode? FindNode<TNode>(RoutePatternNode node, VirtualChar ch, Func<TNode, VirtualChar, bool> predicate)
        where TNode : RoutePatternNode
    {
        if (node is TNode nodeMatch && predicate(nodeMatch, ch))
        {
            return nodeMatch;
        }
 
        foreach (var child in node)
        {
            if (child.IsNode)
            {
                var result = FindNode(child.Node, ch, predicate);
                if (result != null)
                {
                    return result;
                }
            }
        }
 
        return null;
    }
 
    private static AspNetCoreBraceMatchingResult? CreateResult(RoutePatternToken open, RoutePatternToken close)
        => open.IsMissing || close.IsMissing
            ? null
            : new AspNetCoreBraceMatchingResult(open.VirtualChars[0].Span, close.VirtualChars[0].Span);
 
    // IAspNetCoreEmbeddedLanguageBraceMatcher is internal and tests don't have access to it. Provide a way to get its assembly.
    // Just for unit tests. Don't use in production code.
    internal static class TestAccessor
    {
        public static Assembly ExternalAccessAssembly => typeof(IAspNetCoreEmbeddedLanguageBraceMatcher).Assembly;
    }
}