| File: CSharp\GenericTypeNameRewriter.cs | Web Access |
| Project: src\roslyn\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)); } } }