|
// 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);
}
}
}
|