File: Language\Components\ComponentLoweringPass.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;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using Microsoft.AspNetCore.Razor.Language.Intermediate;
using Microsoft.AspNetCore.Razor.PooledObjects;
 
namespace Microsoft.AspNetCore.Razor.Language.Components;
 
internal sealed class ComponentLoweringPass : ComponentIntermediateNodePassBase, IRazorOptimizationPass
{
    // This pass runs earlier than our other passes that 'lower' specific kinds of attributes.
    public override int Order => 0;
 
    protected override void ExecuteCore(
        RazorCodeDocument codeDocument,
        DocumentIntermediateNode documentNode,
        CancellationToken cancellationToken)
    {
        if (!IsComponentDocument(documentNode))
        {
            return;
        }
 
        var @namespace = documentNode.FindPrimaryNamespace();
        var @class = documentNode.FindPrimaryClass();
        if (@namespace == null || @class == null)
        {
            // Nothing to do, bail. We can't function without the standard structure.
            return;
        }
 
        // For each component *usage* we need to rewrite the tag helper node to map to the relevant component
        // APIs.
        var usings = documentNode.FindDescendantNodes<UsingDirectiveIntermediateNode>();
        var references = documentNode.FindDescendantReferences<TagHelperIntermediateNode>();
 
        foreach (var reference in references)
        {
            var node = reference.Node;
            if (node.TagHelpers.Any(t => t.Kind == TagHelperKind.ChildContent))
            {
                // This is a child content tag helper. This will be rewritten when we visit its parent.
                continue;
            }
 
            // The element didn't match any child content descriptors. Look for any matching component descriptors.
            var count = 0;
            foreach (var tagHelper in node.TagHelpers)
            {
                if (tagHelper.Kind == TagHelperKind.Component)
                {
                    // Count component tag helpers
                    count++;
                }
            }
 
            if (count == 1)
            {
                reference.Replace(RewriteAsComponent(node, node.TagHelpers.Single(n => n.Kind == TagHelperKind.Component)));
            }
            else if (count > 1)
            {
                var tagHelper = GetTagHelperOrAddDiagnostic(node, usings);
                if (tagHelper == null)
                {
                    break;
                }
 
                reference.Replace(RewriteAsComponent(node, tagHelper));
            }
            else
            {
                reference.Replace(RewriteAsElement(node));
            }
        }
 
        // We have several tag helpers that are candidates for the same tag node. We need to disambiguate which tag helper applies to this tag
        // since we now fully qualify the tag helper when we emit the code, and we can't rely on the compiler to error out when there are ambiguous
        // tag helpers.
        // We look based on the set of usings if there is only a single component for that can be applied to that tag or if we detect more than one
        // we add a diagnostic and return null
        static TagHelperDescriptor GetTagHelperOrAddDiagnostic(TagHelperIntermediateNode node, IReadOnlyList<UsingDirectiveIntermediateNode> usings)
        {
            TagHelperDescriptor candidate = null;
            List<TagHelperDescriptor> matched = null;
            foreach (var tagHelper in node.TagHelpers)
            {
                if (tagHelper.Kind != TagHelperKind.Component)
                {
                    continue;
                }
 
                for (var j = 0; j < usings.Count; j++)
                {
                    var usingNamespace = usings[j].Content;
                    if (string.Equals(tagHelper.TypeNamespace, usingNamespace, StringComparison.Ordinal))
                    {
                        if (candidate == null)
                        {
                            candidate = tagHelper;
                        }
                        else
                        {
                            matched ??= new();
                            matched.Add(tagHelper);
                        }
                    }
                }
            }
 
            if (matched != null)
            {
                // Insert candidate at the beginning to maintain the original order
                matched.Insert(0, candidate);
 
                // Before reporting an ambiguity error, try to disambiguate based on whether
                // type parameters are provided. This handles the case where both a generic
                // and non-generic version of a component exist with the same name.
                var resolvedCandidate = TryDisambiguateByTypeParameters(node, matched);
                if (resolvedCandidate != null)
                {
                    return resolvedCandidate;
                }
 
                // Iterate over existing diagnostics to avoid adding multiple diagnostics when we find an ambiguous tag.
                foreach (var diagnostic in node.Diagnostics)
                {
                    if (diagnostic.Id == ComponentDiagnosticFactory.MultipleComponents.Id ||
                        diagnostic.Id == ComponentDiagnosticFactory.AmbiguousComponentSelection.Id)
                    {
                        return null;
                    }
                }
 
                node.AddDiagnostic(ComponentDiagnosticFactory.Create_MultipleComponents(node.Source, node.TagName, matched));
 
                return null;
            }
 
            return candidate;
        }
 
        // Try to disambiguate between multiple components with the same name by checking if
        // type parameters are provided. The logic handles:
        // 1. One generic and one non-generic component
        // 2. Multiple generic components with different type parameter counts
        //
        // Disambiguation is based on matching the provided type parameters in the markup
        // with the type parameters defined by each candidate component.
        static TagHelperDescriptor TryDisambiguateByTypeParameters(TagHelperIntermediateNode node, List<TagHelperDescriptor> candidates)
        {
            // Separate candidates into generic and non-generic
            var genericCandidates = candidates.Where(c => c.IsGenericTypedComponent()).ToList();
            var nonGenericCandidates = candidates.Where(c => !c.IsGenericTypedComponent()).ToList();
 
            // Get all type parameter attributes provided in the markup
            using var providedTypeParameters = GetProvidedTypeParameters(node);
 
            // If no type parameters are provided
            if (providedTypeParameters.Count == 0)
            {
                // If there's exactly one non-generic component, prefer it
                if (nonGenericCandidates.Count == 1 && genericCandidates.Count >= 1)
                {
                    var nonGenericComponent = nonGenericCandidates[0];
                    
                    // Check for ambiguity with any generic component
                    foreach (var genericComponent in genericCandidates)
                    {
                        if (HasAmbiguousParameters(node, genericComponent, nonGenericComponent))
                        {
                            // Report an ambiguity error and return null
                            node.AddDiagnostic(ComponentDiagnosticFactory.Create_AmbiguousComponentSelection(
                                node.Source, 
                                node.TagName, 
                                genericComponent, 
                                nonGenericComponent));
                            return null;
                        }
                    }
                    
                    // No ambiguity with any generic variant, use the non-generic component
                    return nonGenericComponent;
                }
                
                // Can't disambiguate - either no non-generic or multiple non-generic components
                return null;
            }
 
            // Type parameters are provided - find the generic component that matches
            TagHelperDescriptor bestMatch = null;
            var providedTypeParametersArray = providedTypeParameters.ToArray();
            
            foreach (var candidate in genericCandidates)
            {
                using var candidateTypeParams = GetTypeParameterNames(candidate);
                
                // Check if all provided type parameters exist in this candidate's type parameters
                var allProvidedMatch = true;
                foreach (var provided in providedTypeParametersArray)
                {
                    if (!candidateTypeParams.Contains(provided))
                    {
                        allProvidedMatch = false;
                        break;
                    }
                }
                
                if (!allProvidedMatch)
                {
                    continue;
                }
                
                // All provided type parameters match this candidate
                // Check if this is a complete match (all type parameters provided)
                if (providedTypeParameters.Count == candidateTypeParams.Count)
                {
                    // Exact match - this is the component to use
                    return candidate;
                }
                
                // Partial match - could be this component (type inference will handle the rest)
                // Keep track of it as a potential match
                if (bestMatch == null)
                {
                    bestMatch = candidate;
                }
                else
                {
                    // Multiple components match the provided type parameters - ambiguous
                    return null;
                }
            }
 
            // Return the best match if we found one, otherwise null (ambiguous or no match)
            return bestMatch;
        }
 
        // Get all type parameter names provided in the markup
        static PooledHashSet<string> GetProvidedTypeParameters(TagHelperIntermediateNode node)
        {
            var result = new PooledHashSet<string>(StringComparer.Ordinal);
            
            foreach (var child in node.Children)
            {
                if (child is TagHelperPropertyIntermediateNode property)
                {
                    // Check if this property matches any type parameter from any candidate
                    // We'll check this by seeing if it's a type parameter for any of the tag helpers
                    foreach (var tagHelper in node.TagHelpers)
                    {
                        if (tagHelper.IsGenericTypedComponent())
                        {
                            foreach (var typeParam in tagHelper.GetTypeParameters())
                            {
                                if (string.Equals(property.AttributeName, typeParam.Name, StringComparison.Ordinal))
                                {
                                    result.Add(property.AttributeName);
                                    break;
                                }
                            }
                        }
                    }
                }
            }
            
            return result;
        }
 
        // Get the set of type parameter names for a component
        static PooledHashSet<string> GetTypeParameterNames(TagHelperDescriptor component)
        {
            var result = new PooledHashSet<string>(StringComparer.Ordinal);
            foreach (var typeParam in component.GetTypeParameters())
            {
                result.Add(typeParam.Name);
            }
            return result;
        }
 
        // Check if both components have parameters with the same names as those used in the markup,
        // which would make selection ambiguous when no type parameters are provided
        static bool HasAmbiguousParameters(TagHelperIntermediateNode node, TagHelperDescriptor genericComponent, TagHelperDescriptor nonGenericComponent)
        {
            // Get all the attribute names used in the markup (excluding type parameters)
            using var typeParameterNames = GetTypeParameterNames(genericComponent);
 
            using var markupAttributeNames = new PooledHashSet<string>(StringComparer.OrdinalIgnoreCase);
            foreach (var child in node.Children)
            {
                if (child is TagHelperPropertyIntermediateNode property)
                {
                    // Skip type parameters
                    if (!typeParameterNames.Contains(property.AttributeName))
                    {
                        markupAttributeNames.Add(property.AttributeName);
                    }
                }
            }
 
            // If no non-type-parameter attributes are used, there's no ambiguity
            if (markupAttributeNames.Count == 0)
            {
                return false;
            }
 
            // Check if both components have bound attributes with the same names
            using var genericParamNames = new PooledHashSet<string>(StringComparer.OrdinalIgnoreCase);
            foreach (var attr in genericComponent.BoundAttributes)
            {
                if (!attr.IsTypeParameterProperty())
                {
                    genericParamNames.Add(attr.Name);
                }
            }
 
            using var nonGenericParamNames = new PooledHashSet<string>(StringComparer.OrdinalIgnoreCase);
            foreach (var attr in nonGenericComponent.BoundAttributes)
            {
                nonGenericParamNames.Add(attr.Name);
            }
 
            // Check if any of the used attributes exist in both components
            var markupAttributesArray = markupAttributeNames.ToArray();
            foreach (var attrName in markupAttributesArray)
            {
                if (genericParamNames.Contains(attrName) && nonGenericParamNames.Contains(attrName))
                {
                    // Found a parameter that exists in both components - this is ambiguous
                    return true;
                }
            }
 
            return false;
        }
    }
 
    private static ComponentIntermediateNode RewriteAsComponent(TagHelperIntermediateNode node, TagHelperDescriptor tagHelper)
    {
        Debug.Assert(node.StartTagSpan.HasValue, "Component tags should always have a start tag span.");
        var component = new ComponentIntermediateNode()
        {
            Component = tagHelper,
            Source = node.Source,
            TagName = node.TagName,
            TypeName = tagHelper.TypeName,
            StartTagSpan = node.StartTagSpan.AssumeNotNull(),
        };
 
        component.AddDiagnosticsFromNode(node);
 
        var visitor = new ComponentRewriteVisitor(component);
        visitor.Visit(node);
 
        // Fixup the parameter names of child content elements. We can't do this during the rewrite
        // because we see the nodes in the wrong order.
        foreach (var childContent in component.ChildContents)
        {
            childContent.ParameterName ??= component.ChildContentParameterName ?? ComponentHelpers.ChildContent.DefaultParameterName;
        }
 
        ValidateRequiredAttributes(node, tagHelper, component);
 
        return component;
    }
 
    private static void ValidateRequiredAttributes(TagHelperIntermediateNode node, TagHelperDescriptor tagHelper, ComponentIntermediateNode intermediateNode)
    {
        if (intermediateNode.Children.Any(static c => c is TagHelperDirectiveAttributeIntermediateNode node && (node.TagHelper?.Kind == TagHelperKind.Splat)))
        {
            // If there are any splat attributes, assume the user may have provided all values.
            // This pass runs earlier than ComponentSplatLoweringPass, so we cannot rely on the presence of SplatIntermediateNode to make this check.
            return;
        }
 
        foreach (var requiredAttribute in tagHelper.EditorRequiredAttributes)
        {
            if (!IsPresentAsAttribute(requiredAttribute.Name, intermediateNode))
            {
                intermediateNode.AddDiagnostic(
                  RazorDiagnosticFactory.CreateComponent_EditorRequiredParameterNotSpecified(
                      node.Source,
                      intermediateNode.TagName,
                      requiredAttribute.Name));
            }
        }
 
        static bool IsPresentAsAttribute(string attributeName, ComponentIntermediateNode intermediateNode)
        {
            foreach (var child in intermediateNode.Children)
            {
                if (child is ComponentAttributeIntermediateNode attributeNode && attributeName == attributeNode.AttributeName)
                {
                    return true;
                }
                if (child is ComponentChildContentIntermediateNode childContent && attributeName == childContent.AttributeName)
                {
                    return true;
                }
                const string bindPrefix = "@bind-";
                if (child is TagHelperDirectiveAttributeIntermediateNode { OriginalAttributeName: { } originalAttributeName } &&
                    originalAttributeName.StartsWith(bindPrefix, StringComparison.Ordinal) &&
                    EqualsWithOptionalChangedOrExpressionSuffix(originalAttributeName.AsSpan(start: bindPrefix.Length), attributeName))
                {
                    return true;
                }
                if (child is TagHelperDirectiveAttributeParameterIntermediateNode { OriginalAttributeName: { } originalName, AttributeNameWithoutParameter: { } nameWithoutParameter } &&
                    originalName.StartsWith(bindPrefix, StringComparison.Ordinal) &&
                    EqualsWithOptionalChangedOrExpressionSuffix(nameWithoutParameter.AsSpan(start: bindPrefix.Length - 1), attributeName))
                {
                    // `@bind-Value:get` or `@bind-Value:set` is specified.
                    return true;
                }
            }
 
            return false;
        }
 
        // True if `requiredName` is equal to `specifiedName` or to `specifiedName + "Changed"` or to `specifiedName + "Expression"`.
        static bool EqualsWithOptionalChangedOrExpressionSuffix(ReadOnlySpan<char> specifiedName, string requiredName)
        {
            var requiredNameSpan = requiredName.AsSpan();
            return EqualsWithSuffix(specifiedName, requiredNameSpan, "Changed") ||
                EqualsWithSuffix(specifiedName, requiredNameSpan, "Expression") ||
                specifiedName.Equals(requiredNameSpan, StringComparison.Ordinal);
        }
 
        // True if `requiredName` is equal to `specifiedName + suffix`.
        static bool EqualsWithSuffix(ReadOnlySpan<char> specifiedName, ReadOnlySpan<char> requiredName, string suffix)
        {
            return requiredName.EndsWith(suffix.AsSpan(), StringComparison.Ordinal) &&
                specifiedName.Equals(requiredName[..^suffix.Length], StringComparison.Ordinal);
        }
    }
 
    private static MarkupElementIntermediateNode RewriteAsElement(TagHelperIntermediateNode node)
    {
        var result = new MarkupElementIntermediateNode()
        {
            Source = node.Source,
            TagName = node.TagName,
        };
 
        result.AddDiagnosticsFromNode(node);
 
        var visitor = new ElementRewriteVisitor(result.Children);
        visitor.Visit(node);
 
        return result;
    }
 
    private class ComponentRewriteVisitor : IntermediateNodeWalker
    {
        private readonly ComponentIntermediateNode _component;
        private readonly IntermediateNodeCollection _children;
 
        public ComponentRewriteVisitor(ComponentIntermediateNode component)
        {
            _component = component;
            _children = component.Children;
        }
 
        public override void VisitTagHelper(TagHelperIntermediateNode node)
        {
            // Visit children, we're replacing this node.
            base.VisitDefault(node);
        }
 
        public override void VisitTagHelperBody(TagHelperBodyIntermediateNode node)
        {
            // Wrap the component's children in a ChildContent node if we have some significant
            // content.
            if (node.Children.Count == 0)
            {
                return;
            }
 
            // If we get a single HTML content node containing only whitespace,
            // then this is probably a tag that looks like '<MyComponent>  </MyComponent>
            //
            // We don't want to create a child content for this case, because it can conflict
            // with a child content that's set via an attribute. We don't want the formatting
            // of insignificant whitespace to be annoying when setting attributes directly.
            if (node.Children.Count == 1 && IsIgnorableWhitespace(node.Children[0]))
            {
                return;
            }
 
            // From here we fork and behave differently based on whether the component's child content is
            // implicit or explicit.
            //
            // Explicit child content will look like: <MyComponent><ChildContent><div>...</div></ChildContent></MyComponent>
            // compared with implicit: <MyComponent><div></div></MyComponent>
            //
            // Using implicit child content:
            // 1. All content is grouped into a single child content lambda, and assigned to the property 'ChildContent'
            //
            // Using explicit child content:
            // 1. All content must be contained within 'child content' elements that are direct children
            // 2. Whitespace outside of 'child content' elements will be ignored (not an error)
            // 3. Non-whitespace outside of 'child content' elements will cause an error
            // 4. All 'child content' elements must match parameters on the component (exception for ChildContent,
            //    which is always allowed.
            // 5. Each 'child content' element will generate its own lambda, and be assigned to the property
            //    that matches the element name.
            if (!node.Children.OfType<TagHelperIntermediateNode>().Any(t => t.TagHelpers.Any(th => th.Kind == TagHelperKind.ChildContent)))
            {
                // This node has implicit child content. It may or may not have an attribute that matches.
                var attribute = _component.Component.BoundAttributes
                    .Where(a => string.Equals(a.Name, ComponentsApi.RenderTreeBuilder.ChildContent, StringComparison.Ordinal))
                    .FirstOrDefault();
                _children.Add(RewriteChildContent(attribute, node.Source, node.Children));
                return;
            }
 
            // OK this node has explicit child content, we can rewrite it by visiting each node
            // in sequence, since we:
            // a) need to rewrite each child content element
            // b) any significant content outside of a child content is an error
            for (var i = 0; i < node.Children.Count; i++)
            {
                var child = node.Children[i];
                if (IsIgnorableWhitespace(child))
                {
                    continue;
                }
 
                if (child is TagHelperIntermediateNode tagHelperNode &&
                    tagHelperNode.TagHelpers.Any(th => th.Kind == TagHelperKind.ChildContent))
                {
                    // This is a child content element
                    var attribute = _component.Component.BoundAttributes
                        .Where(a => string.Equals(a.Name, tagHelperNode.TagName, StringComparison.Ordinal))
                        .FirstOrDefault();
                    var rewrittenChildContent = RewriteChildContent(attribute, child.Source, child.Children);
                    // Transfer diagnostics from the TagHelperIntermediateNode to the rewritten child content.
                    // The resolution phase may have placed diagnostics on the tag helper node that need
                    // to survive rewriting into a ComponentChildContentIntermediateNode.
                    rewrittenChildContent.AddDiagnosticsFromNode(tagHelperNode);
                    _children.Add(rewrittenChildContent);
                    continue;
                }
 
                // If we get here then this is significant content inside a component with explicit child content.
                child.AddDiagnostic(ComponentDiagnosticFactory.Create_ChildContentMixedWithExplicitChildContent(child.Source, _component));
                _children.Add(child);
            }
 
            bool IsIgnorableWhitespace(IntermediateNode n)
            {
                if (n is HtmlContentIntermediateNode html &&
                    html.Children.Count == 1 &&
                    html.Children[0] is IntermediateToken token &&
                    string.IsNullOrWhiteSpace(token.Content))
                {
                    return true;
                }
 
                return false;
            }
        }
 
        private ComponentChildContentIntermediateNode RewriteChildContent(BoundAttributeDescriptor attribute, SourceSpan? source, IntermediateNodeCollection children)
        {
            var childContent = new ComponentChildContentIntermediateNode()
            {
                BoundAttribute = attribute,
                Source = source,
                TypeName = attribute?.TypeName ?? ComponentsApi.RenderFragment.FullTypeName,
            };
 
            // There are two cases here:
            // 1. Implicit child content - the children will be non-taghelper nodes, just accept them
            // 2. Explicit child content - the children will be various tag helper nodes, that need special processing.
            for (var i = 0; i < children.Count; i++)
            {
                var child = children[i];
                if (child is TagHelperBodyIntermediateNode body)
                {
                    // The body is all of the content we want to render, the rest of the children will
                    // be the attributes.
                    for (var j = 0; j < body.Children.Count; j++)
                    {
                        childContent.Children.Add(body.Children[j]);
                    }
                }
                else if (child is TagHelperPropertyIntermediateNode property)
                {
                    if (property.BoundAttribute.IsChildContentParameterNameProperty())
                    {
                        // Check for each child content with a parameter name, that the parameter name is specified
                        // with literal text. For instance, the following is not allowed and should generate a diagnostic.
                        //
                        // <MyComponent><ChildContent Context="@Foo()">...</ChildContent></MyComponent>
                        if (TryGetAttributeStringContent(property, out var parameterName))
                        {
                            childContent.ParameterName = parameterName;
                            continue;
                        }
 
                        // The parameter name is invalid.
                        childContent.AddDiagnostic(ComponentDiagnosticFactory.Create_ChildContentHasInvalidParameter(property.Source, property.AttributeName, attribute.Name));
                        continue;
                    }
 
                    // This is an unrecognized tag helper bound attribute. This will practically never happen unless the child content descriptor was misconfigured.
                    childContent.AddDiagnostic(ComponentDiagnosticFactory.Create_ChildContentHasInvalidAttribute(property.Source, property.AttributeName, attribute.Name));
                }
                else if (child is TagHelperHtmlAttributeIntermediateNode a)
                {
                    // This is an HTML attribute on a child content.
                    childContent.AddDiagnostic(ComponentDiagnosticFactory.Create_ChildContentHasInvalidAttribute(a.Source, a.AttributeName, attribute.Name));
                }
                else if (child is TagHelperDirectiveAttributeIntermediateNode directiveAttribute)
                {
                    // We don't support directive attributes inside child content, this is possible if you try to do something like put '@ref' on a child content.
                    childContent.AddDiagnostic(ComponentDiagnosticFactory.Create_ChildContentHasInvalidAttribute(directiveAttribute.Source, directiveAttribute.OriginalAttributeName, attribute.Name));
                }
                else
                {
                    // This is some other kind of node (likely an implicit child content)
                    childContent.Children.Add(child);
                }
            }
 
            return childContent;
        }
 
        private bool TryGetAttributeStringContent(TagHelperPropertyIntermediateNode property, out string content)
        {
            // The success path looks like - a single HTML Attribute Value node with tokens
            if (property.Children.Count == 1 &&
                property.Children[0] is HtmlContentIntermediateNode html)
            {
                content = string.Join(string.Empty, html.Children.OfType<IntermediateToken>().Select(n => n.Content));
                return true;
            }
 
            content = null;
            return false;
        }
 
        public override void VisitTagHelperHtmlAttribute(TagHelperHtmlAttributeIntermediateNode node)
        {
            var attribute = new ComponentAttributeIntermediateNode(node);
            _children.Add(attribute);
 
            // Since we don't support complex content, we can rewrite the inside of this
            // node to the rather simpler form that property nodes usually have.
            for (var i = 0; i < attribute.Children.Count; i++)
            {
                if (attribute.Children[i] is HtmlAttributeValueIntermediateNode htmlValue)
                {
                    var newNode = new HtmlContentIntermediateNode()
                    {
                        Source = htmlValue.Source,
                    };
                    for (var j = 0; j < htmlValue.Children.Count; j++)
                    {
                        newNode.Children.Add(htmlValue.Children[j]);
                    }
 
                    attribute.Children[i] = newNode;
                }
                else if (attribute.Children[i] is CSharpExpressionAttributeValueIntermediateNode expressionValue)
                {
                    var newNode = new CSharpExpressionIntermediateNode()
                    {
                        Source = expressionValue.Source,
                    };
                    for (var j = 0; j < expressionValue.Children.Count; j++)
                    {
                        newNode.Children.Add(expressionValue.Children[j]);
                    }
 
                    attribute.Children[i] = newNode;
                }
                else if (attribute.Children[i] is CSharpCodeAttributeValueIntermediateNode codeValue)
                {
                    var newNode = new CSharpExpressionIntermediateNode()
                    {
                        Source = codeValue.Source,
                    };
                    for (var j = 0; j < codeValue.Children.Count; j++)
                    {
                        newNode.Children.Add(codeValue.Children[j]);
                    }
 
                    attribute.Children[i] = newNode;
                }
            }
        }
 
        public override void VisitTagHelperProperty(TagHelperPropertyIntermediateNode node)
        {
            // Each 'tag helper property' belongs to a specific tag helper. We want to handle
            // the cases for components, but leave others alone. This allows our other passes
            // to handle those cases.
            if (node.TagHelper.Kind != TagHelperKind.Component)
            {
                _children.Add(node);
                return;
            }
 
            // Another special case here - this might be a type argument. These don't represent 'real' parameters
            // that get passed to the component, it needs special code generation support.
            if (node.TagHelper.IsGenericTypedComponent() && node.BoundAttribute.IsTypeParameterProperty())
            {
                _children.Add(new ComponentTypeArgumentIntermediateNode(node));
                return;
            }
 
            // Another special case here -- this might be a 'Context' parameter, which specifies the name
            // for lambda parameter for parameterized child content
            if (node.BoundAttribute.IsChildContentParameterNameProperty())
            {
                // Check for each child content with a parameter name, that the parameter name is specified
                // with literal text. For instance, the following is not allowed and should generate a diagnostic.
                //
                // <MyComponent Context="@Foo()">...</MyComponent>
                if (TryGetAttributeStringContent(node, out var parameterName))
                {
                    _component.ChildContentParameterName = parameterName;
                    return;
                }
 
                // The parameter name is invalid.
                _component.AddDiagnostic(ComponentDiagnosticFactory.Create_ChildContentHasInvalidParameterOnComponent(node.Source, node.AttributeName, _component.TagName));
                return;
            }
 
            _children.Add(new ComponentAttributeIntermediateNode(node));
        }
 
        public override void VisitTagHelperDirectiveAttribute(TagHelperDirectiveAttributeIntermediateNode node)
        {
            // We don't want to do anything special with directive attributes here.
            // Let their corresponding lowering pass take care of processing them.
            _children.Add(node);
        }
 
        public override void VisitDefault(IntermediateNode node)
        {
            _children.Add(node);
        }
    }
 
    private class ElementRewriteVisitor : IntermediateNodeWalker
    {
        private readonly IntermediateNodeCollection _children;
 
        public ElementRewriteVisitor(IntermediateNodeCollection children)
        {
            _children = children;
        }
 
        public override void VisitTagHelper(TagHelperIntermediateNode node)
        {
            // Visit children, we're replacing this node.
            for (var i = 0; i < node.Children.Count; i++)
            {
                Visit(node.Children[i]);
            }
        }
 
        public override void VisitTagHelperBody(TagHelperBodyIntermediateNode node)
        {
            for (var i = 0; i < node.Children.Count; i++)
            {
                _children.Add(node.Children[i]);
            }
        }
 
        public override void VisitTagHelperHtmlAttribute(TagHelperHtmlAttributeIntermediateNode node)
        {
            var attribute = new HtmlAttributeIntermediateNode()
            {
                AttributeName = node.AttributeName,
                Source = node.Source,
            };
 
            _children.Add(attribute);
 
            attribute.AddDiagnosticsFromNode(node);
 
            switch (node.AttributeStructure)
            {
                case AttributeStructure.Minimized:
 
                    attribute.Prefix = node.AttributeName;
                    attribute.Suffix = string.Empty;
                    break;
 
                case AttributeStructure.NoQuotes:
                case AttributeStructure.SingleQuotes:
                case AttributeStructure.DoubleQuotes:
 
                    // We're ignoring attribute structure here for simplicity, it doesn't effect us.
                    attribute.Prefix = node.AttributeName + "=\"";
                    attribute.Suffix = "\"";
 
                    for (var i = 0; i < node.Children.Count; i++)
                    {
                        attribute.Children.Add(RewriteAttributeContent(node.Children[i]));
                    }
 
                    break;
            }
 
            IntermediateNode RewriteAttributeContent(IntermediateNode content)
            {
                if (content is HtmlContentIntermediateNode html)
                {
                    var value = new HtmlAttributeValueIntermediateNode()
                    {
                        Source = content.Source,
                    };
 
                    for (var i = 0; i < html.Children.Count; i++)
                    {
                        value.Children.Add(html.Children[i]);
                    }
 
 
                    value.AddDiagnosticsFromNode(html);
 
                    return value;
                }
 
 
                return content;
            }
        }
 
        public override void VisitTagHelperProperty(TagHelperPropertyIntermediateNode node)
        {
            // Each 'tag helper property' belongs to a specific tag helper. We want to handle
            // the cases for components, but leave others alone. This allows our other passes
            // to handle those cases.
            _children.Add(node.TagHelper.Kind == TagHelperKind.Component ? new ComponentAttributeIntermediateNode(node) : node);
        }
 
        public override void VisitTagHelperDirectiveAttribute(TagHelperDirectiveAttributeIntermediateNode node)
        {
            // We don't want to do anything special with directive attributes here.
            // Let their corresponding lowering pass take care of processing them.
            _children.Add(node);
        }
 
        public override void VisitDefault(IntermediateNode node)
        {
            _children.Add(node);
        }
    }
}