| File: Language\Intermediate\ComponentTypeArgumentIntermediateNode.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. using System.Text; using Microsoft.AspNetCore.Razor.PooledObjects; namespace Microsoft.AspNetCore.Razor.Language.Intermediate; public sealed class ComponentTypeArgumentIntermediateNode( BoundAttributeDescriptor boundAttribute, CSharpIntermediateToken value) : IntermediateNode { public BoundAttributeDescriptor BoundAttribute { get; } = boundAttribute; public TagHelperDescriptor TagHelper => BoundAttribute.Parent; public string TypeParameterName => BoundAttribute.Name; public CSharpIntermediateToken Value { get; } = value; public override IntermediateNodeCollection Children { get; } = [value]; public ComponentTypeArgumentIntermediateNode(TagHelperPropertyIntermediateNode node) : this(node.BoundAttribute, GetValue(node)) { Source = node.Source; AddDiagnosticsFromNode(node); } private static CSharpIntermediateToken GetValue(TagHelperPropertyIntermediateNode node) => node.Children switch { [CSharpIntermediateToken t] => t, [CSharpExpressionIntermediateNode { Children: [CSharpIntermediateToken t] }] => t, // Handle the case where the value was lowered as HTML content (from the unresolved tag helper pipeline). [HtmlContentIntermediateNode { Children: [HtmlIntermediateToken t] }] => t.IsLazy ? IntermediateNodeFactory.CSharpToken( arg: t, contentFactory: static token => token.Content, source: t.Source) : new CSharpIntermediateToken(t.Content, t.Source), // Fallback: the value lowered to a more complex shape, e.g. literal text mixed // with one or more Razor expressions ("leading @x", "@x trailing", multiple // expressions, etc.). Concatenate the token content from all descendants into a // single CSharp token so downstream code generation can emit the user-authored // text as a type argument. The C# compiler will then produce a normal diagnostic // if the result is not a valid type, instead of the Razor compiler crashing. _ => MergeChildTokens(node), }; private static CSharpIntermediateToken MergeChildTokens(TagHelperPropertyIntermediateNode node) { using var _sb = StringBuilderPool.GetPooledObject(out var sb); SourceSpan? firstSpan = null; SourceSpan? lastSpan = null; AppendTokens(node, sb, ref firstSpan, ref lastSpan); SourceSpan? mergedSpan = null; if (firstSpan is { } first && lastSpan is { } last && first.AbsoluteIndex <= last.AbsoluteIndex) { mergedSpan = DefaultTagHelperResolutionPhase.MergeSourceSpans(first, last); } else { mergedSpan = firstSpan ?? lastSpan ?? node.Source; } return new CSharpIntermediateToken(sb.ToString(), mergedSpan); } private static void AppendTokens(IntermediateNode node, StringBuilder sb, ref SourceSpan? firstSpan, ref SourceSpan? lastSpan) { foreach (var child in node.Children) { if (child is IntermediateToken token) { sb.Append(token.Content); if (token.Source is { } s) { firstSpan ??= s; lastSpan = s; } } else { // Unwrap container nodes (CSharpExpressionIntermediateNode, HtmlContentIntermediateNode, etc.). AppendTokens(child, sb, ref firstSpan, ref lastSpan); } } } public override void Accept(IntermediateNodeVisitor visitor) => visitor.VisitComponentTypeArgument(this); public override void FormatNode(IntermediateNodeFormatter formatter) { formatter.WriteContent(TypeParameterName); formatter.WriteProperty(nameof(BoundAttribute), BoundAttribute.DisplayName); formatter.WriteProperty(nameof(TagHelper), TagHelper.DisplayName); } }