File: RouteEmbeddedLanguage\RoutePatternClassifier.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.Reflection;
using Microsoft.AspNetCore.Analyzers.Infrastructure.RoutePattern;
using Microsoft.AspNetCore.App.Analyzers.Infrastructure;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Classification;
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;
 
[ExportAspNetCoreEmbeddedLanguageClassifier(name: "Route", language: LanguageNames.CSharp)]
internal sealed class RoutePatternClassifier : IAspNetCoreEmbeddedLanguageClassifier
{
    public void RegisterClassifications(AspNetCoreEmbeddedLanguageClassificationContext context)
    {
        var routeUsageCache = RouteUsageCache.GetOrCreate(context.SemanticModel.Compilation);
        var routeUsage = routeUsageCache.Get(context.SyntaxToken, context.CancellationToken);
        if (routeUsage is null)
        {
            return;
        }
 
        var visitor = new Visitor(context);
        AddClassifications(routeUsage.RoutePattern.Root, visitor);
    }
 
    private static void AddClassifications(RoutePatternNode node, Visitor visitor)
    {
        node.Accept(visitor);
 
        foreach (var child in node)
        {
            if (child.IsNode)
            {
                AddClassifications(child.Node, visitor);
            }
        }
    }
 
    private sealed class Visitor : IRoutePatternNodeVisitor
    {
        public AspNetCoreEmbeddedLanguageClassificationContext _context;
 
        public Visitor(AspNetCoreEmbeddedLanguageClassificationContext context)
        {
            _context = context;
        }
 
        public void Visit(RoutePatternCompilationUnit node)
        {
            // Nothing to highlight.
        }
 
        public void Visit(RoutePatternSegmentNode node)
        {
            // Nothing to highlight.
        }
 
        public void Visit(RoutePatternReplacementNode node)
        {
            AddClassification(node.OpenBracketToken, ClassificationTypeNames.RegexCharacterClass);
            AddClassification(node.TextToken, ClassificationTypeNames.RegexCharacterClass);
            AddClassification(node.CloseBracketToken, ClassificationTypeNames.RegexCharacterClass);
        }
 
        public void Visit(RoutePatternParameterNode node)
        {
            AddClassification(node.OpenBraceToken, ClassificationTypeNames.RegexCharacterClass);
            AddClassification(node.CloseBraceToken, ClassificationTypeNames.RegexCharacterClass);
        }
 
        public void Visit(RoutePatternLiteralNode node)
        {
            // Nothing to highlight.
        }
 
        public void Visit(RoutePatternSegmentSeparatorNode node)
        {
            // Nothing to highlight.
        }
 
        public void Visit(RoutePatternOptionalSeparatorNode node)
        {
            // Nothing to highlight.
        }
 
        public void Visit(RoutePatternCatchAllParameterPartNode node)
        {
            AddClassification(node.AsteriskToken, ClassificationTypeNames.RegexAnchor);
        }
 
        public void Visit(RoutePatternNameParameterPartNode node)
        {
            // Parameter name should look like a regular parameter.
            // ClassificationTypeNames.ParameterName isn't working so temporarily reuse color from JSON syntax:
            // https://github.com/dotnet/roslyn/blob/e1ee2f544a7a4f8d536278bed4e180c54919276e/src/Workspaces/Core/Portable/Classification/ClassificationTypeNames.cs#L181
            //
            // TODO: Fix properly with https://github.com/dotnet/aspnetcore/issues/46207
            AddClassification(node.ParameterNameToken, "json - object");
        }
 
        public void Visit(RoutePatternPolicyParameterPartNode node)
        {
            AddClassification(node.ColonToken, ClassificationTypeNames.RegexCharacterClass);
        }
 
        public void Visit(RoutePatternPolicyFragmentEscapedNode node)
        {
            AddClassification(node.OpenParenToken, ClassificationTypeNames.RegexCharacterClass);
            AddClassification(node.CloseParenToken, ClassificationTypeNames.RegexCharacterClass);
        }
 
        public void Visit(RoutePatternPolicyFragment node)
        {
            AddClassification(node.ArgumentToken, ClassificationTypeNames.RegexGrouping);
        }
 
        public void Visit(RoutePatternOptionalParameterPartNode node)
        {
            AddClassification(node.QuestionMarkToken, ClassificationTypeNames.RegexAnchor);
        }
 
        public void Visit(RoutePatternDefaultValueParameterPartNode node)
        {
            AddClassification(node.EqualsToken, ClassificationTypeNames.RegexCharacterClass);
        }
 
        private void AddClassification(RoutePatternToken token, string typeName)
        {
            if (!token.IsMissing)
            {
                _context.AddClassification(typeName, token.GetSpan());
            }
        }
 
        private void ClassifyWholeNode(RoutePatternNode node, string typeName)
        {
            foreach (var child in node)
            {
                if (child.IsNode)
                {
                    ClassifyWholeNode(child.Node, typeName);
                }
                else
                {
                    AddClassification(child.Token, typeName);
                }
            }
        }
    }
 
    // IAspNetCoreEmbeddedLanguageClassifier 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(IAspNetCoreEmbeddedLanguageClassifier).Assembly;
    }
}