File: CSharp\GenericTypeNameRewriter.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 System.Collections.Immutable;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.AspNetCore.Razor.Language.Intermediate;
using Microsoft.AspNetCore.Razor.Language.Legacy;
using Microsoft.AspNetCore.Razor.PooledObjects;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
 
namespace Microsoft.CodeAnalysis.Razor;
 
internal class GenericTypeNameRewriter : TypeNameRewriter
{
    private readonly Dictionary<string, ComponentTypeArgumentIntermediateNode> _bindings;
 
    public GenericTypeNameRewriter(Dictionary<string, ComponentTypeArgumentIntermediateNode> bindings)
    {
        _bindings = bindings;
    }
 
    public override string Rewrite(string typeName) => Rewrite(typeName, out _);
 
    public override void RewriteComponentTypeName(ComponentIntermediateNode node)
    {
        node.TypeName = Rewrite(node.TypeName, out var usedBindings);
        node.OrderedTypeArguments = usedBindings;
    }
 
    private string Rewrite(string typeName, out ImmutableArray<ComponentTypeArgumentIntermediateNode> usedTypeArguments)
    {
        using var _ = ArrayBuilderPool<ComponentTypeArgumentIntermediateNode>.GetPooledObject(out var builder);
 
        var parsed = SyntaxFactory.ParseTypeName(typeName);
        var rewritten = (TypeSyntax)new Visitor(_bindings, builder).Visit(parsed);
        usedTypeArguments = builder.ToImmutable();
        return rewritten.ToFullString();
    }
 
    private class Visitor : CSharpSyntaxRewriter
    {
        private readonly Dictionary<string, ComponentTypeArgumentIntermediateNode> _bindings;
        private readonly ImmutableArray<ComponentTypeArgumentIntermediateNode>.Builder _usedBindings;
 
        public Visitor(Dictionary<string, ComponentTypeArgumentIntermediateNode> bindings, ImmutableArray<ComponentTypeArgumentIntermediateNode>.Builder usedBindings)
        {
            _bindings = bindings;
            _usedBindings = usedBindings;
        }
 
        public override SyntaxNode Visit(SyntaxNode node)
        {
            // We can handle a single IdentifierNameSyntax at the top level (like 'TItem)
            // OR a GenericNameSyntax recursively (like `List<T>`)
            if (node is IdentifierNameSyntax identifier && !(identifier.Parent is QualifiedNameSyntax))
            {
                if (_bindings.TryGetValue(identifier.Identifier.Text, out var binding))
                {
                    _usedBindings.Add(binding);
 
                    // If we don't have a valid replacement, use object. This will make the code at least reasonable
                    // compared to leaving the type parameter in place.
                    //
                    // We add our own diagnostics for missing/invalid type parameters anyway.
                    var content = binding?.Value?.Content;
                    var replacement = !string.IsNullOrWhiteSpace(content) ? content : "object";
                    return identifier.Update(SyntaxFactory.Identifier(replacement).WithTriviaFrom(identifier.Identifier));
                }
            }
 
            return base.Visit(node);
        }
 
        public override SyntaxNode VisitGenericName(GenericNameSyntax node)
        {
            var args = node.TypeArgumentList.Arguments;
            for (var i = 0; i < args.Count; i++)
            {
                var typeArgument = args[i];
                args = args.Replace(typeArgument, (TypeSyntax)Visit(typeArgument));
            }
 
            return node.WithTypeArgumentList(node.TypeArgumentList.WithArguments(args));
        }
    }
}