File: Language\Components\ComponentComplexAttributeContentPass.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.Linq;
using System.Threading;
using Microsoft.AspNetCore.Razor.Language.Intermediate;
 
namespace Microsoft.AspNetCore.Razor.Language.Components;
 
// We don't support 'complex' content for components (mixed C# and markup) right now.
// It's not clear yet if components will have a good scenario to use these constructs.
//
// This is where a lot of the complexity in the Razor/TagHelpers model creeps in and we
// might be able to avoid it if these features aren't needed.
internal sealed class ComponentComplexAttributeContentPass : ComponentIntermediateNodePassBase, IRazorOptimizationPass
{
    // Run before other Component passes
    public override int Order => -1000;
 
    protected override void ExecuteCore(
        RazorCodeDocument codeDocument,
        DocumentIntermediateNode documentNode,
        CancellationToken cancellationToken)
    {
        if (!IsComponentDocument(documentNode))
        {
            return;
        }
 
        foreach (var node in documentNode.FindDescendantNodes<TagHelperIntermediateNode>())
        {
            ProcessAttributes(node);
        }
    }
 
    private void ProcessAttributes(TagHelperIntermediateNode node)
    {
        for (var i = node.Children.Count - 1; i >= 0; i--)
        {
            if (node.Children[i] is TagHelperPropertyIntermediateNode propertyNode &&
                node.TagHelpers.Any(t => t.Kind == TagHelperKind.Component))
            {
                ProcessAttribute(node, propertyNode, propertyNode.AttributeName);
            }
            else if (node.Children[i] is TagHelperHtmlAttributeIntermediateNode htmlNode &&
                node.TagHelpers.Any(t => t.Kind == TagHelperKind.Component))
            {
                ProcessAttribute(node, htmlNode, htmlNode.AttributeName);
            }
            else if (node.Children[i] is TagHelperDirectiveAttributeIntermediateNode directiveAttributeNode)
            {
                ProcessAttribute(node, directiveAttributeNode, directiveAttributeNode.OriginalAttributeName);
            }
        }
    }
 
    private static void ProcessAttribute(IntermediateNode parent, IntermediateNode node, string attributeName)
    {
        var issueDiagnostic = false;
 
        if (node.Children is [HtmlAttributeIntermediateNode { Children.Count: > 1 }])
        {
            // This case can be hit for a 'string' attribute
            issueDiagnostic = true;
        }
        else if (node.Children is [CSharpExpressionIntermediateNode { Children.Count: > 1 } cSharpNode])
        {
            // This case can be hit when the attribute has an explicit @ inside, which
            // 'escapes' any special sugar we provide for codegen.
            //
            // There's a special case here for explicit expressions. See https://github.com/aspnet/Razor/issues/2203
            // handling this case as a tactical matter since it's important for lambdas.
            if (cSharpNode.Children is [IntermediateToken { Content: "(" }, _, IntermediateToken { Content: ")" }])
            {
                cSharpNode.Children.RemoveAt(2);
                cSharpNode.Children.RemoveAt(0);
            }
            else
            {
                issueDiagnostic = true;
            }
        }
        else if (node.Children is [CSharpCodeIntermediateNode])
        {
            // This is the case when an attribute contains a code block @{ ... }
            // We don't support this.
            issueDiagnostic = true;
        }
        else if (node.Children.Count > 1)
        {
            // This is the common case for 'mixed' content
            issueDiagnostic = true;
        }
 
        if (issueDiagnostic)
        {
            node.AddDiagnostic(ComponentDiagnosticFactory.Create_UnsupportedComplexContent(
                node,
                attributeName));
        }
    }
}