File: Language\Components\ComponentChildContentDiagnosticPass.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.
 
using System.Threading;
using Microsoft.AspNetCore.Razor.Language.Intermediate;
 
namespace Microsoft.AspNetCore.Razor.Language.Components;
 
internal sealed class ComponentChildContentDiagnosticPass : ComponentIntermediateNodePassBase, IRazorOptimizationPass
{
    // Runs after components/eventhandlers/ref/bind/templates. We want to validate every component
    // and it's usage of ChildContent.
    public override int Order => 160;
 
    protected override void ExecuteCore(
        RazorCodeDocument codeDocument,
        DocumentIntermediateNode documentNode,
        CancellationToken cancellationToken)
    {
        if (!IsComponentDocument(documentNode))
        {
            return;
        }
 
        var visitor = new Visitor();
        visitor.Visit(documentNode);
    }
 
    private sealed class Visitor : IntermediateNodeWalker
    {
        public override void VisitComponent(ComponentIntermediateNode node)
        {
            // Check for properties that are set by both element contents (body) and the attribute itself.
            foreach (var childContent in node.ChildContents)
            {
                foreach (var attribute in node.Attributes)
                {
                    if (attribute.AttributeName == childContent.AttributeName)
                    {
                        node.AddDiagnostic(ComponentDiagnosticFactory.Create_ChildContentSetByAttributeAndBody(
                            attribute.Source,
                            attribute.AttributeName));
                    }
                }
            }
 
            VisitDefault(node);
        }
 
        public override void VisitComponentChildContent(ComponentChildContentIntermediateNode node)
        {
            // Check that each child content has a unique parameter name within its scope. This is important
            // because the parameter name can be implicit, and it doesn't work well when nested.
            if (node.IsParameterized)
            {
                var ancestors = Ancestors;
                var parentComponent = (ComponentIntermediateNode)ancestors[0];
 
                // Skip the immediate parent component as we've already validated against it.
                // Loop to ancestors.Length - 1 because we're always checking pairs.
 
                for (var i = 1; i < ancestors.Length - 1; i++)
                {
                    if (ancestors[i] is ComponentChildContentIntermediateNode { IsParameterized: true } ancestor &&
                        ancestor.ParameterName == node.ParameterName &&
                        ancestors[i + 1] is ComponentIntermediateNode ancestorParentComponent)
                    {
                        // Duplicate name. We report an error because this will almost certainly also lead to an error
                        // from the C# compiler that's way less clear.
                        node.AddDiagnostic(ComponentDiagnosticFactory.Create_ChildContentRepeatedParameterName(
                            node.Source,
                            childContent1: node,
                            component1: parentComponent,
                            childContent2: ancestor,
                            component2: ancestorParentComponent));
                    }
                }
            }
 
            VisitDefault(node);
        }
    }
}