File: CSharp\GlobalQualifiedTypeNameRewriter.cs
Web Access
Project: src\src\Razor\src\Compiler\Microsoft.CodeAnalysis.Razor.Compiler\src\Microsoft.CodeAnalysis.Razor.Compiler.csproj (Microsoft.CodeAnalysis.Razor.Compiler)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
#nullable disable
 
using System.Collections.Generic;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.AspNetCore.Razor.Language.Intermediate;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
 
namespace Microsoft.CodeAnalysis.Razor;
 
// Rewrites type names to use the 'global::' prefix for identifiers.
//
// This is useful when we're generating code in a different namespace than
// what the user code lives in. When we synthesize a namespace it's easy to have
// clashes.
internal class GlobalQualifiedTypeNameRewriter : TypeNameRewriter
{
    // List of names to ignore.
    //
    // NOTE: this is the list of type parameters defined on the component.
    private readonly HashSet<string> _ignore;
 
    public GlobalQualifiedTypeNameRewriter(ICollection<string> ignore)
    {
        _ignore = new HashSet<string>(ignore);
    }
 
    public override string Rewrite(string typeName)
    {
        var parsed = SyntaxFactory.ParseTypeName(typeName);
        var rewritten = (TypeSyntax)new Visitor(_ignore).Visit(parsed);
        return rewritten.ToFullString();
    }
 
    public override void RewriteComponentTypeName(ComponentIntermediateNode node)
    {
        node.TypeName = Rewrite(node.TypeName);
    }
 
    private class Visitor : CSharpSyntaxRewriter
    {
        private readonly HashSet<string> _ignore;
 
        public Visitor(HashSet<string> ignore)
        {
            _ignore = ignore;
        }
 
        public override SyntaxNode Visit(SyntaxNode node)
        {
            return base.Visit(node);
        }
 
        public override SyntaxNode VisitQualifiedName(QualifiedNameSyntax node)
        {
            if (node.Parent is QualifiedNameSyntax)
            {
                return base.VisitQualifiedName(node);
            }
 
            // Need to rewrite postorder so we can rewrite the names of generic type arguments.
            node = (QualifiedNameSyntax)base.VisitQualifiedName(node);
 
            // Rewriting these is complicated, best to just tostring and parse again.
            return SyntaxFactory.ParseTypeName(IsGloballyQualified(node) ? node.ToString() : "global::" + node.ToString())
                .WithTriviaFrom(node);
 
            static bool IsGloballyQualified(QualifiedNameSyntax node)
            {
                var candidate = node;
                while (candidate != null)
                {
                    if (candidate.Left is AliasQualifiedNameSyntax)
                    {
                        return true;
                    }
                    candidate = candidate.Left as QualifiedNameSyntax;
                }
                return false;
            }
        }
 
        public override SyntaxNode VisitIdentifierName(IdentifierNameSyntax node)
        {
            if (_ignore.Contains(node.ToString()))
            {
                return node;
            }
 
            if (node.Parent != null)
            {
                return node;
            }
 
            return SyntaxFactory.AliasQualifiedName(SyntaxFactory.IdentifierName(SyntaxFactory.Token(CSharp.SyntaxKind.GlobalKeyword)), node);
        }
    }
}