| File: Language\Intermediate\UnresolvedAttributeIntermediateNode.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.Diagnostics; namespace Microsoft.AspNetCore.Razor.Language.Intermediate; /// <summary> /// An intermediate node representing an attribute whose final form (plain HTML attribute /// vs tag helper bound attribute) is not yet determined. Pre-lowered fallback forms are /// stored so the resolution phase does not need syntax tree access. /// </summary> internal sealed class UnresolvedAttributeIntermediateNode : IntermediateNode { /// <summary>The attribute name (e.g., "Value", "@bind-Value", "@onclick").</summary> public string AttributeName { get; set; } = string.Empty; /// <summary>Whether this is a minimized attribute (no value).</summary> public bool IsMinimized { get; set; } /// <summary> /// Whether the parser treated the leading <c>@</c> as a Razor transition rather than an escaped /// literal. Semantic resolution determines whether the candidate binds to a directive attribute. /// </summary> public bool IsDirectiveAttributeCandidate { get; set; } /// <summary>The raw value content of the attribute (for all-literal values only).</summary> public string? ValueContent { get; set; } /// <summary> /// Source span covering the attribute value content (between quotes). /// Computed from syntax tree positions during lowering so the resolution phase /// can use it without needing syntax tree access. /// </summary> public SourceSpan? ValueSourceSpan { get; set; } /// <summary> /// The attribute structure (quote style). Pre-computed during initial lowering /// so the resolution phase does not need to inspect syntax. /// </summary> public AttributeStructure AttributeStructure { get; set; } /// <summary> /// Source span of the attribute name only (not the whole attribute). /// Pre-computed during initial lowering for tag helper property OriginalAttributeSpan. /// </summary> public SourceSpan? AttributeNameSpan { get; set; } /// <summary> /// Pre-lowered form of this attribute for when the element IS a tag helper but this /// attribute is unbound (doesn't match any tag helper property). The tag helper runtime /// represents unbound HTML attributes as <see cref="TagHelperHtmlAttributeIntermediateNode"/> /// wrapping a structured <see cref="HtmlAttributeIntermediateNode"/> with merged value tokens. /// Produced during lowering via <c>VisitAttributeValue</c>, which merges adjacent literal /// tokens into a single <see cref="HtmlContentIntermediateNode"/>. /// </summary> public IntermediateNode? AsTagHelperAttribute { get; set; } /// <summary> /// Pre-lowered form of this attribute for when the element is NOT a tag helper and needs /// to be unwrapped back to plain HTML markup. Plain HTML elements represent attributes with /// individual value tokens preserving the original parse structure. Produced during lowering /// via <c>LowerAttributeAsHtml</c>, which preserves individual tokens without merging. /// Used by <c>ConvertToMarkupElement</c> and <c>UnwrapElement</c> to restore the element /// to its non-tag-helper form. /// </summary> public IntermediateNode? AsMarkupAttribute { get; set; } /// <summary> /// The <see cref="HtmlAttributeIntermediateNode"/> child containing unresolved attribute /// value children. Set during lowering to avoid linear scans in the resolution phase. /// </summary> public HtmlAttributeIntermediateNode? HtmlAttributeNode { get; set; } public override IntermediateNodeCollection Children { get => field ??= []; } public override void Accept(IntermediateNodeVisitor visitor) { visitor.VisitDefault(this); } protected override IntermediateNode CloneNode() { var clone = new UnresolvedAttributeIntermediateNode { AttributeName = AttributeName, IsMinimized = IsMinimized, IsDirectiveAttributeCandidate = IsDirectiveAttributeCandidate, ValueContent = ValueContent, ValueSourceSpan = ValueSourceSpan, AttributeStructure = AttributeStructure, AttributeNameSpan = AttributeNameSpan, AsTagHelperAttribute = AsTagHelperAttribute?.Clone(), AsMarkupAttribute = AsMarkupAttribute?.Clone(), IsSynthesizedHelper = IsSynthesizedHelper, }; return clone; } internal override IntermediateNode Clone() { var clone = (UnresolvedAttributeIntermediateNode)base.Clone(); // HtmlAttributeNode aliases one of Children, so point the clone at its cloned child rather than // an independent copy. Cloning it separately would leave the clone's HtmlAttributeNode and its // Children entry as two divergent instances, so a phase that mutates one and walks the other // would see stale state. if (HtmlAttributeNode is { } htmlAttributeNode) { var index = Children.IndexOf(htmlAttributeNode); Debug.Assert(index >= 0, "HtmlAttributeNode is expected to be one of Children."); clone.HtmlAttributeNode = (HtmlAttributeIntermediateNode)clone.Children[index]; } return clone; } public override void FormatNode(IntermediateNodeFormatter formatter) { formatter.WriteContent(AttributeName); formatter.WriteProperty(nameof(AttributeName), AttributeName); formatter.WriteProperty(nameof(IsMinimized), IsMinimized.ToString()); } }