File: Language\Components\ComponentMarkupDiagnosticPass.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;
using System.Collections.Generic;
using System.Threading;
using Microsoft.AspNetCore.Razor.Language.Intermediate;
 
namespace Microsoft.AspNetCore.Razor.Language.Components;
 
internal sealed class ComponentMarkupDiagnosticPass : ComponentIntermediateNodePassBase, IRazorOptimizationPass
{
    public const int DefaultOrder = 10000;
 
    public override int Order => DefaultOrder;
 
    protected override void ExecuteCore(
        RazorCodeDocument codeDocument,
        DocumentIntermediateNode documentNode,
        CancellationToken cancellationToken)
    {
        var visitor = new Visitor();
        visitor.Visit(documentNode);
    }
 
    private class Visitor : IntermediateNodeWalker
    {
        private readonly Dictionary<string, (string name, IntermediateNode node)> _attributes = new(StringComparer.OrdinalIgnoreCase);
 
        public override void VisitMarkupElement(MarkupElementIntermediateNode node)
        {
            foreach (var child in node.Children)
            {
                if (child is HtmlAttributeIntermediateNode attribute && attribute.AttributeName != null)
                {
                    if (_attributes.TryGetValue(attribute.AttributeName, out var other))
                    {
                        var otherAttribute = (HtmlAttributeIntermediateNode)other.node;
 
                        // As a special case we want to point it out explicitly where a directive or other construct
                        // has emitted an attribute that causes a conflict. We're already looking at the lowered version
                        // of this construct, so it's easy to detect. We just need the original name to report the issue.
                        //
                        // Example: `bind-value` will set `value` and `onchange`.
                        var originalAttributeName = attribute.OriginalAttributeName ?? otherAttribute.OriginalAttributeName;
 
                        if (originalAttributeName != null)
                        {
                            otherAttribute.AddDiagnostic(ComponentDiagnosticFactory.Create_DuplicateMarkupAttributeDirective(
                                other.name,
                                originalAttributeName,
                                otherAttribute.Source ?? node.Source));
                        }
                        else
                        {
                            // This is a conflict in the code the user wrote.
                            otherAttribute.AddDiagnostic(ComponentDiagnosticFactory.Create_DuplicateMarkupAttribute(
                                other.name,
                                otherAttribute.Source ?? node.Source));
                        }
                    }
 
                    // Replace the attribute we were previously tracking. Then if you have three, the two on the left will have
                    // diagnostics.
                    _attributes[attribute.AttributeName] = (attribute.AttributeName, attribute);
                }
            }
 
            _attributes.Clear();
            base.VisitMarkupElement(node);
        }
 
        public override void VisitComponent(ComponentIntermediateNode node)
        {
            foreach (var child in node.Children)
            {
                // Note that we don't handle ChildContent cases here. Those have their own pass for diagnostics.
                if (child is ComponentAttributeIntermediateNode attribute && attribute.AttributeName != null)
                {
                    if (_attributes.TryGetValue(attribute.AttributeName, out var other))
                    {
                        var otherAttribute = (ComponentAttributeIntermediateNode)other.node;
 
                        // As a special case we want to point it out explicitly where a directive or other construct
                        // has emitted an attribute that causes a conflict. We're already looking at the lowered version
                        // of this construct, so it's easy to detect. We just need the original name to report the issue.
                        //
                        // Example: `bind-Value` will set `Value` and `ValueChanged`.
                        var originalAttributeName = attribute.OriginalAttributeName ?? otherAttribute.OriginalAttributeName;
 
                        if (originalAttributeName != null)
                        {
                            other.node.AddDiagnostic(ComponentDiagnosticFactory.Create_DuplicateComponentParameterDirective(
                                other.name,
                                originalAttributeName,
                                other.node.Source ?? node.Source));
                        }
                        else
                        {
                            // This is a conflict in the code the user wrote.
                            other.node.AddDiagnostic(ComponentDiagnosticFactory.Create_DuplicateComponentParameter(
                                other.name,
                                other.node.Source ?? node.Source));
                        }
                    }
 
                    // Replace the attribute we were previously tracking. Then if you have three, the two on the left will have
                    // diagnostics.
                    _attributes[attribute.AttributeName] = (attribute.AttributeName, attribute);
                }
            }
 
            _attributes.Clear();
            base.VisitComponent(node);
        }
    }
}