File: Language\Components\ComponentTemplateDiagnosticPass.cs
Web Access
Project: src\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.Collections.Generic;
using System.Threading;
using Microsoft.AspNetCore.Razor.Language.Extensions;
using Microsoft.AspNetCore.Razor.Language.Intermediate;
using Microsoft.AspNetCore.Razor.PooledObjects;

namespace Microsoft.AspNetCore.Razor.Language.Components;

internal sealed class ComponentTemplateDiagnosticPass : ComponentIntermediateNodePassBase, IRazorOptimizationPass
{
    // Runs after components/eventhandlers/ref/bind. We need to check for templates in all of those
    // places.
    public override int Order => 150;

    protected override void ExecuteCore(
        RazorCodeDocument codeDocument,
        DocumentIntermediateNode documentNode,
        CancellationToken cancellationToken)
    {
        if (!IsComponentDocument(documentNode))
        {
            return;
        }

        using var _ = ListPool<IntermediateNodeReference>.GetPooledObject(out var candidates);

        var visitor = new Visitor(candidates);
        visitor.Visit(documentNode);

        foreach (var candidate in candidates)
        {
            var (node, parent) = candidate;

            parent.AddDiagnostic(ComponentDiagnosticFactory.Create_TemplateInvalidLocation(node.Source));

            // Remove the offending node since we don't know how to render it. This means that the user won't get C#
            // completion at this location, which is fine because it's inside an HTML attribute.
            candidate.Remove();
        }
    }

    private sealed class Visitor(List<IntermediateNodeReference> candidates)
        : IntermediateNodeWalker, IExtensionIntermediateNodeVisitor<TemplateIntermediateNode>
    {
        private readonly List<IntermediateNodeReference> _candidates = candidates;

        public void VisitExtension(TemplateIntermediateNode node)
        {
            // We found a template, let's check where it's located.
            foreach (var ancestor in Ancestors)
            {
                if (ancestor is HtmlAttributeIntermediateNode or // Inside markup attribute
                                ComponentAttributeIntermediateNode or // Inside component attribute
                                TagHelperPropertyIntermediateNode or // Inside malformed ref attribute
                                TagHelperDirectiveAttributeIntermediateNode) // Inside a directive attribute
                {
                    _candidates.Add(new IntermediateNodeReference(node, Parent.AssumeNotNull()));

                    // We found a candidate and can stop looking. There's no need to report multiple diagnostics for the same node.
                    break;
                }
            }
        }
    }
}