Use an explicit intermediate node for directive attributes (dotnet/aspnetcore-tooling#638)

* Use an explicit intermediate node for directive attributes

* More cleanup
\n\nCommit migrated from f8d7f4cc3b
This commit is contained in:
Ajay Bhargav Baaskaran 2019-06-03 13:54:28 -07:00 committed by GitHub
parent 6be2d3dfcd
commit c588d53a42
18 changed files with 249 additions and 97 deletions

View File

@ -51,7 +51,7 @@ namespace Microsoft.AspNetCore.Razor.Language
return isIndexerNameMatch && attribute.IsIndexerBooleanProperty; return isIndexerNameMatch && attribute.IsIndexerBooleanProperty;
} }
internal static bool IsDirectiveAttribute(this BoundAttributeDescriptor attribute) public static bool IsDirectiveAttribute(this BoundAttributeDescriptor attribute)
{ {
if (attribute == null) if (attribute == null)
{ {

View File

@ -29,8 +29,8 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
} }
// For each @bind *usage* we need to rewrite the tag helper node to map to basic constructs. // For each @bind *usage* we need to rewrite the tag helper node to map to basic constructs.
var references = documentNode.FindDescendantReferences<TagHelperPropertyIntermediateNode>(); var references = documentNode.FindDescendantReferences<TagHelperDirectiveAttributeIntermediateNode>();
var parameterReferences = documentNode.FindDescendantReferences<TagHelperAttributeParameterIntermediateNode>(); var parameterReferences = documentNode.FindDescendantReferences<TagHelperDirectiveAttributeParameterIntermediateNode>();
var parents = new HashSet<IntermediateNode>(); var parents = new HashSet<IntermediateNode>();
for (var i = 0; i < references.Count; i++) for (var i = 0; i < references.Count; i++)
@ -56,7 +56,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
{ {
var reference = references[i]; var reference = references[i];
var parent = reference.Parent; var parent = reference.Parent;
var node = (TagHelperPropertyIntermediateNode)reference.Node; var node = (TagHelperDirectiveAttributeIntermediateNode)reference.Node;
if (!parent.Children.Contains(node)) if (!parent.Children.Contains(node))
{ {
@ -64,7 +64,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
continue; continue;
} }
if (node.TagHelper.IsBindTagHelper() && node.IsDirectiveAttribute) if (node.TagHelper.IsBindTagHelper())
{ {
bindEntries[(parent, node.AttributeName)] = new BindEntry(reference); bindEntries[(parent, node.AttributeName)] = new BindEntry(reference);
} }
@ -75,7 +75,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
{ {
var parameterReference = parameterReferences[i]; var parameterReference = parameterReferences[i];
var parent = parameterReference.Parent; var parent = parameterReference.Parent;
var node = (TagHelperAttributeParameterIntermediateNode)parameterReference.Node; var node = (TagHelperDirectiveAttributeParameterIntermediateNode)parameterReference.Node;
if (!parent.Children.Contains(node)) if (!parent.Children.Contains(node))
{ {
@ -83,7 +83,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
continue; continue;
} }
if (node.TagHelper.IsBindTagHelper() && node.IsDirectiveAttribute) if (node.TagHelper.IsBindTagHelper())
{ {
// Check if this tag contains a corresponding non-parameterized bind node. // Check if this tag contains a corresponding non-parameterized bind node.
if (!bindEntries.TryGetValue((parent, node.AttributeNameWithoutParameter), out var entry)) if (!bindEntries.TryGetValue((parent, node.AttributeNameWithoutParameter), out var entry))
@ -140,12 +140,12 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
TagHelperDescriptor tagHelper = null; TagHelperDescriptor tagHelper = null;
string attributeName = null; string attributeName = null;
var attribute = node.Children[i]; var attribute = node.Children[i];
if (attribute is TagHelperPropertyIntermediateNode propertyAttribute) if (attribute is TagHelperDirectiveAttributeIntermediateNode directiveAttribute)
{ {
attributeName = propertyAttribute.AttributeName; attributeName = directiveAttribute.AttributeName;
tagHelper = propertyAttribute.TagHelper; tagHelper = directiveAttribute.TagHelper;
} }
else if (attribute is TagHelperAttributeParameterIntermediateNode parameterAttribute) else if (attribute is TagHelperDirectiveAttributeParameterIntermediateNode parameterAttribute)
{ {
attributeName = parameterAttribute.AttributeName; attributeName = parameterAttribute.AttributeName;
tagHelper = parameterAttribute.TagHelper; tagHelper = parameterAttribute.TagHelper;
@ -159,12 +159,12 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
TagHelperDescriptor duplicateTagHelper = null; TagHelperDescriptor duplicateTagHelper = null;
string duplicateAttributeName = null; string duplicateAttributeName = null;
var duplicate = node.Children[j]; var duplicate = node.Children[j];
if (duplicate is TagHelperPropertyIntermediateNode duplicatePropertyAttribute) if (duplicate is TagHelperDirectiveAttributeIntermediateNode duplicateDirectiveAttribute)
{ {
duplicateAttributeName = duplicatePropertyAttribute.AttributeName; duplicateAttributeName = duplicateDirectiveAttribute.AttributeName;
duplicateTagHelper = duplicatePropertyAttribute.TagHelper; duplicateTagHelper = duplicateDirectiveAttribute.TagHelper;
} }
else if (duplicate is TagHelperAttributeParameterIntermediateNode duplicateParameterAttribute) else if (duplicate is TagHelperDirectiveAttributeParameterIntermediateNode duplicateParameterAttribute)
{ {
duplicateAttributeName = duplicateParameterAttribute.AttributeName; duplicateAttributeName = duplicateParameterAttribute.AttributeName;
duplicateTagHelper = duplicateParameterAttribute.TagHelper; duplicateTagHelper = duplicateParameterAttribute.TagHelper;
@ -195,12 +195,12 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
TagHelperDescriptor duplicateTagHelper = null; TagHelperDescriptor duplicateTagHelper = null;
string duplicateAttributeName = null; string duplicateAttributeName = null;
var duplicate = node.Children[j]; var duplicate = node.Children[j];
if (duplicate is TagHelperPropertyIntermediateNode duplicatePropertyAttribute) if (duplicate is TagHelperDirectiveAttributeIntermediateNode duplicateDirectiveAttribute)
{ {
duplicateAttributeName = duplicatePropertyAttribute.AttributeName; duplicateAttributeName = duplicateDirectiveAttribute.AttributeName;
duplicateTagHelper = duplicatePropertyAttribute.TagHelper; duplicateTagHelper = duplicateDirectiveAttribute.TagHelper;
} }
else if (duplicate is TagHelperAttributeParameterIntermediateNode duplicateParameterAttribute) else if (duplicate is TagHelperDirectiveAttributeParameterIntermediateNode duplicateParameterAttribute)
{ {
duplicateAttributeName = duplicateParameterAttribute.AttributeName; duplicateAttributeName = duplicateParameterAttribute.AttributeName;
duplicateTagHelper = duplicateParameterAttribute.TagHelper; duplicateTagHelper = duplicateParameterAttribute.TagHelper;
@ -222,7 +222,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
// If we still have duplicates at this point then they are genuine conflicts. // If we still have duplicates at this point then they are genuine conflicts.
var duplicates = node.Children var duplicates = node.Children
.OfType<TagHelperPropertyIntermediateNode>() .OfType<TagHelperDirectiveAttributeIntermediateNode>()
.GroupBy(p => p.AttributeName) .GroupBy(p => p.AttributeName)
.Where(g => g.Count() > 1); .Where(g => g.Count() > 1);
@ -230,7 +230,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
{ {
node.Diagnostics.Add(ComponentDiagnosticFactory.CreateBindAttribute_Duplicates( node.Diagnostics.Add(ComponentDiagnosticFactory.CreateBindAttribute_Duplicates(
node.Source, node.Source,
duplicate.Key, duplicate.First().OriginalAttributeName,
duplicate.ToArray())); duplicate.ToArray()));
foreach (var property in duplicate) foreach (var property in duplicate)
{ {
@ -338,7 +338,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
{ {
Annotations = Annotations =
{ {
[ComponentMetadata.Common.OriginalAttributeName] = node.AttributeName, [ComponentMetadata.Common.OriginalAttributeName] = node.OriginalAttributeName,
}, },
AttributeName = valueAttributeName, AttributeName = valueAttributeName,
Source = node.Source, Source = node.Source,
@ -362,7 +362,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
{ {
Annotations = Annotations =
{ {
[ComponentMetadata.Common.OriginalAttributeName] = node.AttributeName, [ComponentMetadata.Common.OriginalAttributeName] = node.OriginalAttributeName,
}, },
AttributeName = changeAttributeName, AttributeName = changeAttributeName,
Source = node.Source, Source = node.Source,
@ -385,7 +385,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
{ {
Annotations = Annotations =
{ {
[ComponentMetadata.Common.OriginalAttributeName] = node.AttributeName, [ComponentMetadata.Common.OriginalAttributeName] = node.OriginalAttributeName,
}, },
AttributeName = valueAttributeName, AttributeName = valueAttributeName,
BoundAttribute = valueAttribute, // Might be null if it doesn't match a component attribute BoundAttribute = valueAttribute, // Might be null if it doesn't match a component attribute
@ -405,7 +405,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
{ {
Annotations = Annotations =
{ {
[ComponentMetadata.Common.OriginalAttributeName] = node.AttributeName, [ComponentMetadata.Common.OriginalAttributeName] = node.OriginalAttributeName,
}, },
AttributeName = changeAttributeName, AttributeName = changeAttributeName,
BoundAttribute = changeAttribute, // Might be null if it doesn't match a component attribute BoundAttribute = changeAttribute, // Might be null if it doesn't match a component attribute
@ -430,7 +430,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
{ {
Annotations = Annotations =
{ {
[ComponentMetadata.Common.OriginalAttributeName] = node.AttributeName, [ComponentMetadata.Common.OriginalAttributeName] = node.OriginalAttributeName,
}, },
AttributeName = expressionAttributeName, AttributeName = expressionAttributeName,
BoundAttribute = expressionAttribute, BoundAttribute = expressionAttribute,
@ -463,7 +463,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
valueAttributeName = null; valueAttributeName = null;
changeAttributeName = null; changeAttributeName = null;
if (!attributeName.StartsWith("@bind")) if (!attributeName.StartsWith("bind"))
{ {
return false; return false;
} }
@ -473,7 +473,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
changeAttributeName = GetAttributeContent(bindEntry.BindEventNode)?.Content?.Trim('"'); changeAttributeName = GetAttributeContent(bindEntry.BindEventNode)?.Content?.Trim('"');
} }
if (attributeName == "@bind") if (attributeName == "bind")
{ {
return true; return true;
} }
@ -591,7 +591,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
private bool TryGetFormatNode( private bool TryGetFormatNode(
IntermediateNode node, IntermediateNode node,
TagHelperPropertyIntermediateNode attributeNode, TagHelperDirectiveAttributeIntermediateNode attributeNode,
string valueAttributeName, string valueAttributeName,
out TagHelperPropertyIntermediateNode formatNode) out TagHelperPropertyIntermediateNode formatNode)
{ {
@ -807,16 +807,16 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
public BindEntry(IntermediateNodeReference bindNodeReference) public BindEntry(IntermediateNodeReference bindNodeReference)
{ {
BindNodeReference = bindNodeReference; BindNodeReference = bindNodeReference;
BindNode = (TagHelperPropertyIntermediateNode)bindNodeReference.Node; BindNode = (TagHelperDirectiveAttributeIntermediateNode)bindNodeReference.Node;
} }
public IntermediateNodeReference BindNodeReference { get; } public IntermediateNodeReference BindNodeReference { get; }
public TagHelperPropertyIntermediateNode BindNode { get; } public TagHelperDirectiveAttributeIntermediateNode BindNode { get; }
public TagHelperAttributeParameterIntermediateNode BindEventNode { get; set; } public TagHelperDirectiveAttributeParameterIntermediateNode BindEventNode { get; set; }
public TagHelperAttributeParameterIntermediateNode BindFormatNode { get; set; } public TagHelperDirectiveAttributeParameterIntermediateNode BindFormatNode { get; set; }
} }
} }
} }

View File

@ -36,7 +36,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
{ {
if (node.Children[i] is TagHelperPropertyIntermediateNode propertyNode) if (node.Children[i] is TagHelperPropertyIntermediateNode propertyNode)
{ {
if (TrySimplifyContent(propertyNode) && node.TagHelpers.Any(t => t.IsComponentTagHelper())) if (!TrySimplifyContent(propertyNode) && node.TagHelpers.Any(t => t.IsComponentTagHelper()))
{ {
node.Diagnostics.Add(ComponentDiagnosticFactory.Create_UnsupportedComplexContent( node.Diagnostics.Add(ComponentDiagnosticFactory.Create_UnsupportedComplexContent(
propertyNode, propertyNode,
@ -47,7 +47,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
} }
else if (node.Children[i] is TagHelperHtmlAttributeIntermediateNode htmlNode) else if (node.Children[i] is TagHelperHtmlAttributeIntermediateNode htmlNode)
{ {
if (TrySimplifyContent(htmlNode) && node.TagHelpers.Any(t => t.IsComponentTagHelper())) if (!TrySimplifyContent(htmlNode) && node.TagHelpers.Any(t => t.IsComponentTagHelper()))
{ {
node.Diagnostics.Add(ComponentDiagnosticFactory.Create_UnsupportedComplexContent( node.Diagnostics.Add(ComponentDiagnosticFactory.Create_UnsupportedComplexContent(
htmlNode, htmlNode,
@ -56,6 +56,17 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
continue; continue;
} }
} }
else if (node.Children[i] is TagHelperDirectiveAttributeIntermediateNode directiveAttributeNode)
{
if (!TrySimplifyContent(directiveAttributeNode))
{
node.Diagnostics.Add(ComponentDiagnosticFactory.Create_UnsupportedComplexContent(
directiveAttributeNode,
directiveAttributeNode.OriginalAttributeName));
node.Children.RemoveAt(i);
continue;
}
}
} }
} }
@ -66,7 +77,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
htmlNode.Children.Count > 1) htmlNode.Children.Count > 1)
{ {
// This case can be hit for a 'string' attribute // This case can be hit for a 'string' attribute
return true; return false;
} }
else if (node.Children.Count == 1 && else if (node.Children.Count == 1 &&
node.Children[0] is CSharpExpressionIntermediateNode cSharpNode && node.Children[0] is CSharpExpressionIntermediateNode cSharpNode &&
@ -87,25 +98,25 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
cSharpNode.Children.RemoveAt(0); cSharpNode.Children.RemoveAt(0);
// We were able to simplify it, all good. // We were able to simplify it, all good.
return false; return true;
} }
return true; return false;
} }
else if (node.Children.Count == 1 && else if (node.Children.Count == 1 &&
node.Children[0] is CSharpCodeIntermediateNode cSharpCodeNode) node.Children[0] is CSharpCodeIntermediateNode)
{ {
// This is the case when an attribute contains a code block @{ ... } // This is the case when an attribute contains a code block @{ ... }
// We don't support this. // We don't support this.
return true; return false;
} }
else if (node.Children.Count > 1) else if (node.Children.Count > 1)
{ {
// This is the common case for 'mixed' content // This is the common case for 'mixed' content
return true; return false;
} }
return false; return true;
} }
} }
} }

View File

@ -95,7 +95,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
public static readonly RazorDiagnosticDescriptor UnsupportedComplexContent = new RazorDiagnosticDescriptor( public static readonly RazorDiagnosticDescriptor UnsupportedComplexContent = new RazorDiagnosticDescriptor(
$"{DiagnosticPrefix}9986", $"{DiagnosticPrefix}9986",
() => "Component attributes do not support complex content (mixed C# and markup). Attribute: '{0}', text '{1}'", () => "Component attributes do not support complex content (mixed C# and markup). Attribute: '{0}', text: '{1}'",
RazorDiagnosticSeverity.Error); RazorDiagnosticSeverity.Error);
public static RazorDiagnostic Create_UnsupportedComplexContent(IntermediateNode node, string attributeName) public static RazorDiagnostic Create_UnsupportedComplexContent(IntermediateNode node, string attributeName)
@ -136,7 +136,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
() => "The attribute '{0}' was matched by multiple bind attributes. Duplicates:{1}", () => "The attribute '{0}' was matched by multiple bind attributes. Duplicates:{1}",
RazorDiagnosticSeverity.Error); RazorDiagnosticSeverity.Error);
public static RazorDiagnostic CreateBindAttribute_Duplicates(SourceSpan? source, string attribute, TagHelperPropertyIntermediateNode[] attributes) public static RazorDiagnostic CreateBindAttribute_Duplicates(SourceSpan? source, string attribute, TagHelperDirectiveAttributeIntermediateNode[] attributes)
{ {
var diagnostic = RazorDiagnostic.Create( var diagnostic = RazorDiagnostic.Create(
BindAttribute_Duplicates, BindAttribute_Duplicates,
@ -152,7 +152,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
() => "The attribute '{0}' was matched by multiple event handlers attributes. Duplicates:{1}", () => "The attribute '{0}' was matched by multiple event handlers attributes. Duplicates:{1}",
RazorDiagnosticSeverity.Error); RazorDiagnosticSeverity.Error);
public static RazorDiagnostic CreateEventHandler_Duplicates(SourceSpan? source, string attribute, TagHelperPropertyIntermediateNode[] attributes) public static RazorDiagnostic CreateEventHandler_Duplicates(SourceSpan? source, string attribute, TagHelperDirectiveAttributeIntermediateNode[] attributes)
{ {
var diagnostic = RazorDiagnostic.Create( var diagnostic = RazorDiagnostic.Create(
EventHandler_Duplicates, EventHandler_Duplicates,

View File

@ -30,7 +30,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
// For each event handler *usage* we need to rewrite the tag helper node to map to basic constructs. // For each event handler *usage* we need to rewrite the tag helper node to map to basic constructs.
// Each usage will be represented by a tag helper property that is a descendant of either // Each usage will be represented by a tag helper property that is a descendant of either
// a component or element. // a component or element.
var references = documentNode.FindDescendantReferences<TagHelperPropertyIntermediateNode>(); var references = documentNode.FindDescendantReferences<TagHelperDirectiveAttributeIntermediateNode>();
var parents = new HashSet<IntermediateNode>(); var parents = new HashSet<IntermediateNode>();
for (var i = 0; i < references.Count; i++) for (var i = 0; i < references.Count; i++)
@ -46,7 +46,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
for (var i = 0; i < references.Count; i++) for (var i = 0; i < references.Count; i++)
{ {
var reference = references[i]; var reference = references[i];
var node = (TagHelperPropertyIntermediateNode)reference.Node; var node = (TagHelperDirectiveAttributeIntermediateNode)reference.Node;
if (!reference.Parent.Children.Contains(node)) if (!reference.Parent.Children.Contains(node))
{ {
@ -54,7 +54,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
continue; continue;
} }
if (node.TagHelper.IsEventHandlerTagHelper() && node.IsDirectiveAttribute) if (node.TagHelper.IsEventHandlerTagHelper())
{ {
reference.Replace(RewriteUsage(reference.Parent, node)); reference.Replace(RewriteUsage(reference.Parent, node));
} }
@ -93,7 +93,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
// If we still have duplicates at this point then they are genuine conflicts. // If we still have duplicates at this point then they are genuine conflicts.
var duplicates = parent.Children var duplicates = parent.Children
.OfType<TagHelperPropertyIntermediateNode>() .OfType<TagHelperDirectiveAttributeIntermediateNode>()
.Where(p => p.TagHelper?.IsEventHandlerTagHelper() ?? false) .Where(p => p.TagHelper?.IsEventHandlerTagHelper() ?? false)
.GroupBy(p => p.AttributeName) .GroupBy(p => p.AttributeName)
.Where(g => g.Count() > 1); .Where(g => g.Count() > 1);
@ -111,7 +111,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
} }
} }
private IntermediateNode RewriteUsage(IntermediateNode parent, TagHelperPropertyIntermediateNode node) private IntermediateNode RewriteUsage(IntermediateNode parent, TagHelperDirectiveAttributeIntermediateNode node)
{ {
var original = GetAttributeContent(node); var original = GetAttributeContent(node);
if (original.Count == 0) if (original.Count == 0)
@ -148,19 +148,14 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
} }
var attributeName = node.AttributeName; var attributeName = node.AttributeName;
if (node.IsDirectiveAttribute && attributeName.StartsWith("@"))
{
// Directive attributes start with a "@" but we don't want that to be included in the output attribute name.
// E.g, <input @onclick="..." /> should result in the creation of 'onclick' attribute.
attributeName = attributeName.Substring(1);
}
if (parent is MarkupElementIntermediateNode) if (parent is MarkupElementIntermediateNode)
{ {
var result = new HtmlAttributeIntermediateNode() var result = new HtmlAttributeIntermediateNode()
{ {
Annotations = Annotations =
{ {
[ComponentMetadata.Common.OriginalAttributeName] = node.AttributeName, [ComponentMetadata.Common.OriginalAttributeName] = node.OriginalAttributeName,
}, },
AttributeName = attributeName, AttributeName = attributeName,
Source = node.Source, Source = node.Source,
@ -188,7 +183,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
{ {
Annotations = Annotations =
{ {
[ComponentMetadata.Common.OriginalAttributeName] = node.AttributeName, [ComponentMetadata.Common.OriginalAttributeName] = node.OriginalAttributeName,
}, },
}; };
@ -203,7 +198,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
} }
} }
private static IReadOnlyList<IntermediateToken> GetAttributeContent(TagHelperPropertyIntermediateNode node) private static IReadOnlyList<IntermediateToken> GetAttributeContent(TagHelperDirectiveAttributeIntermediateNode node)
{ {
var template = node.FindDescendantNodes<TemplateIntermediateNode>().FirstOrDefault(); var template = node.FindDescendantNodes<TemplateIntermediateNode>().FirstOrDefault();
if (template != null) if (template != null)

View File

@ -25,20 +25,20 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
return; return;
} }
var references = documentNode.FindDescendantReferences<TagHelperPropertyIntermediateNode>(); var references = documentNode.FindDescendantReferences<TagHelperDirectiveAttributeIntermediateNode>();
for (var i = 0; i < references.Count; i++) for (var i = 0; i < references.Count; i++)
{ {
var reference = references[i]; var reference = references[i];
var node = (TagHelperPropertyIntermediateNode)reference.Node; var node = (TagHelperDirectiveAttributeIntermediateNode)reference.Node;
if (node.TagHelper.IsKeyTagHelper() && node.IsDirectiveAttribute) if (node.TagHelper.IsKeyTagHelper())
{ {
reference.Replace(RewriteUsage(reference.Parent, node)); reference.Replace(RewriteUsage(reference.Parent, node));
} }
} }
} }
private IntermediateNode RewriteUsage(IntermediateNode parent, TagHelperPropertyIntermediateNode node) private IntermediateNode RewriteUsage(IntermediateNode parent, TagHelperDirectiveAttributeIntermediateNode node)
{ {
// If we can't get a nonempty attribute value, do nothing because there will // If we can't get a nonempty attribute value, do nothing because there will
// already be a diagnostic for empty values // already be a diagnostic for empty values
@ -51,7 +51,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
return new SetKeyIntermediateNode(keyValueToken); return new SetKeyIntermediateNode(keyValueToken);
} }
private IntermediateToken DetermineKeyValueToken(TagHelperPropertyIntermediateNode attributeNode) private IntermediateToken DetermineKeyValueToken(TagHelperDirectiveAttributeIntermediateNode attributeNode)
{ {
IntermediateToken foundToken = null; IntermediateToken foundToken = null;

View File

@ -261,7 +261,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
continue; continue;
} }
// This is an unrecognized attribute, this is possible if you try to do something like put 'ref' on a child content. // This is an unrecognized tag helper bound attribute. This will practically never happen unless the child content descriptor was misconfigured.
childContent.Diagnostics.Add(ComponentDiagnosticFactory.Create_ChildContentHasInvalidAttribute(property.Source, property.AttributeName, attribute.Name)); childContent.Diagnostics.Add(ComponentDiagnosticFactory.Create_ChildContentHasInvalidAttribute(property.Source, property.AttributeName, attribute.Name));
} }
else if (child is TagHelperHtmlAttributeIntermediateNode a) else if (child is TagHelperHtmlAttributeIntermediateNode a)
@ -269,6 +269,11 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
// This is an HTML attribute on a child content. // This is an HTML attribute on a child content.
childContent.Diagnostics.Add(ComponentDiagnosticFactory.Create_ChildContentHasInvalidAttribute(a.Source, a.AttributeName, attribute.Name)); childContent.Diagnostics.Add(ComponentDiagnosticFactory.Create_ChildContentHasInvalidAttribute(a.Source, a.AttributeName, attribute.Name));
} }
else if (child is TagHelperDirectiveAttributeIntermediateNode directiveAttribute)
{
// We don't support directive attributes inside child content, this is possible if you try to do something like put '@ref' on a child content.
childContent.Diagnostics.Add(ComponentDiagnosticFactory.Create_ChildContentHasInvalidAttribute(directiveAttribute.Source, directiveAttribute.OriginalAttributeName, attribute.Name));
}
else else
{ {
// This is some other kind of node (likely an implicit child content) // This is some other kind of node (likely an implicit child content)
@ -385,6 +390,13 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
_children.Add(new ComponentAttributeIntermediateNode(node)); _children.Add(new ComponentAttributeIntermediateNode(node));
} }
public override void VisitTagHelperDirectiveAttribute(TagHelperDirectiveAttributeIntermediateNode node)
{
// We don't want to do anything special with directive attributes here.
// Let their corresponding lowering pass take care of processing them.
_children.Add(node);
}
public override void VisitDefault(IntermediateNode node) public override void VisitDefault(IntermediateNode node)
{ {
_children.Add(node); _children.Add(node);
@ -490,6 +502,13 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
_children.Add(node.TagHelper.IsComponentTagHelper() ? (IntermediateNode)new ComponentAttributeIntermediateNode(node) : node); _children.Add(node.TagHelper.IsComponentTagHelper() ? (IntermediateNode)new ComponentAttributeIntermediateNode(node) : node);
} }
public override void VisitTagHelperDirectiveAttribute(TagHelperDirectiveAttributeIntermediateNode node)
{
// We don't want to do anything special with directive attributes here.
// Let their corresponding lowering pass take care of processing them.
_children.Add(node);
}
public override void VisitDefault(IntermediateNode node) public override void VisitDefault(IntermediateNode node)
{ {
_children.Add(node); _children.Add(node);

View File

@ -25,20 +25,20 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
return; return;
} }
var references = documentNode.FindDescendantReferences<TagHelperPropertyIntermediateNode>(); var references = documentNode.FindDescendantReferences<TagHelperDirectiveAttributeIntermediateNode>();
for (var i = 0; i < references.Count; i++) for (var i = 0; i < references.Count; i++)
{ {
var reference = references[i]; var reference = references[i];
var node = (TagHelperPropertyIntermediateNode)reference.Node; var node = (TagHelperDirectiveAttributeIntermediateNode)reference.Node;
if (node.TagHelper.IsRefTagHelper() && node.IsDirectiveAttribute) if (node.TagHelper.IsRefTagHelper())
{ {
reference.Replace(RewriteUsage(@class, reference.Parent, node)); reference.Replace(RewriteUsage(@class, reference.Parent, node));
} }
} }
} }
private IntermediateNode RewriteUsage(ClassDeclarationIntermediateNode classNode, IntermediateNode parent, TagHelperPropertyIntermediateNode node) private IntermediateNode RewriteUsage(ClassDeclarationIntermediateNode classNode, IntermediateNode parent, TagHelperDirectiveAttributeIntermediateNode node)
{ {
// If we can't get a nonempty attribute name, do nothing because there will // If we can't get a nonempty attribute name, do nothing because there will
// already be a diagnostic for empty values // already be a diagnostic for empty values
@ -61,7 +61,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
} }
} }
private IntermediateToken DetermineIdentifierToken(TagHelperPropertyIntermediateNode attributeNode) private IntermediateToken DetermineIdentifierToken(TagHelperDirectiveAttributeIntermediateNode attributeNode)
{ {
IntermediateToken foundToken = null; IntermediateToken foundToken = null;

View File

@ -53,7 +53,10 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
ancestor is ComponentAttributeIntermediateNode || ancestor is ComponentAttributeIntermediateNode ||
// Inside malformed ref attribute // Inside malformed ref attribute
ancestor is TagHelperPropertyIntermediateNode) ancestor is TagHelperPropertyIntermediateNode ||
// Inside a directive attribute
ancestor is TagHelperDirectiveAttributeIntermediateNode)
{ {
Candidates.Add(new IntermediateNodeReference(Parent, node)); Candidates.Add(new IntermediateNodeReference(Parent, node));
} }

View File

@ -1752,14 +1752,19 @@ namespace Microsoft.AspNetCore.Razor.Language
return; return;
} }
// Directive attributes should start with '@' unless the descriptors are misconfigured.
// In that case, we would have already logged an error.
var actualAttributeName = attributeName.StartsWith("@") ? attributeName.Substring(1) : attributeName;
IntermediateNode attributeNode; IntermediateNode attributeNode;
if (parameterMatch && if (parameterMatch &&
TagHelperMatchingConventions.TryGetBoundAttributeParameter(attributeName, out var attributeNameWithoutParameter, out _)) TagHelperMatchingConventions.TryGetBoundAttributeParameter(actualAttributeName, out var attributeNameWithoutParameter, out _))
{ {
attributeNode = new TagHelperAttributeParameterIntermediateNode() attributeNode = new TagHelperDirectiveAttributeParameterIntermediateNode()
{ {
AttributeName = attributeName, AttributeName = actualAttributeName,
AttributeNameWithoutParameter = attributeNameWithoutParameter, AttributeNameWithoutParameter = attributeNameWithoutParameter,
OriginalAttributeName = attributeName,
BoundAttributeParameter = associatedAttributeParameterDescriptor, BoundAttributeParameter = associatedAttributeParameterDescriptor,
BoundAttribute = associatedAttributeDescriptor, BoundAttribute = associatedAttributeDescriptor,
TagHelper = associatedDescriptor, TagHelper = associatedDescriptor,
@ -1770,9 +1775,10 @@ namespace Microsoft.AspNetCore.Razor.Language
} }
else else
{ {
attributeNode = new TagHelperPropertyIntermediateNode() attributeNode = new TagHelperDirectiveAttributeIntermediateNode()
{ {
AttributeName = attributeName, AttributeName = actualAttributeName,
OriginalAttributeName = attributeName,
BoundAttribute = associatedAttributeDescriptor, BoundAttribute = associatedAttributeDescriptor,
TagHelper = associatedDescriptor, TagHelper = associatedDescriptor,
AttributeStructure = node.TagHelperAttributeInfo.AttributeStructure, AttributeStructure = node.TagHelperAttributeInfo.AttributeStructure,
@ -1870,14 +1876,19 @@ namespace Microsoft.AspNetCore.Razor.Language
out var parameterMatch, out var parameterMatch,
out var associatedAttributeParameterDescriptor)) out var associatedAttributeParameterDescriptor))
{ {
// Directive attributes should start with '@' unless the descriptors are misconfigured.
// In that case, we would have already logged an error.
var actualAttributeName = attributeName.StartsWith("@") ? attributeName.Substring(1) : attributeName;
IntermediateNode attributeNode; IntermediateNode attributeNode;
if (parameterMatch && if (parameterMatch &&
TagHelperMatchingConventions.TryGetBoundAttributeParameter(attributeName, out var attributeNameWithoutParameter, out _)) TagHelperMatchingConventions.TryGetBoundAttributeParameter(actualAttributeName, out var attributeNameWithoutParameter, out _))
{ {
attributeNode = new TagHelperAttributeParameterIntermediateNode() attributeNode = new TagHelperDirectiveAttributeParameterIntermediateNode()
{ {
AttributeName = attributeName, AttributeName = actualAttributeName,
AttributeNameWithoutParameter = attributeNameWithoutParameter, AttributeNameWithoutParameter = attributeNameWithoutParameter,
OriginalAttributeName = attributeName,
BoundAttributeParameter = associatedAttributeParameterDescriptor, BoundAttributeParameter = associatedAttributeParameterDescriptor,
BoundAttribute = associatedAttributeDescriptor, BoundAttribute = associatedAttributeDescriptor,
TagHelper = associatedDescriptor, TagHelper = associatedDescriptor,
@ -1888,9 +1899,10 @@ namespace Microsoft.AspNetCore.Razor.Language
} }
else else
{ {
attributeNode = new TagHelperPropertyIntermediateNode() attributeNode = new TagHelperDirectiveAttributeIntermediateNode()
{ {
AttributeName = attributeName, AttributeName = actualAttributeName,
OriginalAttributeName = attributeName,
BoundAttribute = associatedAttributeDescriptor, BoundAttribute = associatedAttributeDescriptor,
TagHelper = associatedDescriptor, TagHelper = associatedDescriptor,
AttributeStructure = node.TagHelperAttributeInfo.AttributeStructure, AttributeStructure = node.TagHelperAttributeInfo.AttributeStructure,

View File

@ -42,12 +42,6 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate
} }
var attributeName = propertyNode.AttributeName; var attributeName = propertyNode.AttributeName;
if (propertyNode.IsDirectiveAttribute && attributeName.StartsWith("@"))
{
// Directive attributes start with a "@" but we don't want that to be included in the output attribute name.
// E.g, <input @onclick="..." /> should result in the creation of 'onclick' attribute.
attributeName = attributeName.Substring(1);
}
AttributeName = attributeName; AttributeName = attributeName;
AttributeStructure = propertyNode.AttributeStructure; AttributeStructure = propertyNode.AttributeStructure;
@ -68,6 +62,32 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate
} }
} }
public ComponentAttributeIntermediateNode(TagHelperDirectiveAttributeIntermediateNode directiveAttributeNode)
{
if (directiveAttributeNode == null)
{
throw new ArgumentNullException(nameof(directiveAttributeNode));
}
AttributeName = directiveAttributeNode.AttributeName;
AttributeStructure = directiveAttributeNode.AttributeStructure;
BoundAttribute = directiveAttributeNode.BoundAttribute;
PropertyName = directiveAttributeNode.BoundAttribute.GetPropertyName();
Source = directiveAttributeNode.Source;
TagHelper = directiveAttributeNode.TagHelper;
TypeName = directiveAttributeNode.BoundAttribute.IsWeaklyTyped() ? null : directiveAttributeNode.BoundAttribute.TypeName;
for (var i = 0; i < directiveAttributeNode.Children.Count; i++)
{
Children.Add(directiveAttributeNode.Children[i]);
}
for (var i = 0; i < directiveAttributeNode.Diagnostics.Count; i++)
{
Diagnostics.Add(directiveAttributeNode.Diagnostics[i]);
}
}
public override IntermediateNodeCollection Children { get; } = new IntermediateNodeCollection(); public override IntermediateNodeCollection Children { get; } = new IntermediateNodeCollection();
public string AttributeName { get; set; } public string AttributeName { get; set; }

View File

@ -124,12 +124,17 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate
VisitDefault(node); VisitDefault(node);
} }
public virtual void VisitTagHelperDirectiveAttribute(TagHelperDirectiveAttributeIntermediateNode node)
{
VisitDefault(node);
}
public virtual void VisitTagHelperHtmlAttribute(TagHelperHtmlAttributeIntermediateNode node) public virtual void VisitTagHelperHtmlAttribute(TagHelperHtmlAttributeIntermediateNode node)
{ {
VisitDefault(node); VisitDefault(node);
} }
public virtual void VisitTagHelperAttributeParameter(TagHelperAttributeParameterIntermediateNode node) public virtual void VisitTagHelperDirectiveAttributeParameter(TagHelperDirectiveAttributeParameterIntermediateNode node)
{ {
VisitDefault(node); VisitDefault(node);
} }

View File

@ -0,0 +1,46 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
namespace Microsoft.AspNetCore.Razor.Language.Intermediate
{
public sealed class TagHelperDirectiveAttributeIntermediateNode : IntermediateNode
{
public override IntermediateNodeCollection Children { get; } = new IntermediateNodeCollection();
public string AttributeName { get; set; }
public string OriginalAttributeName { get; set; }
public AttributeStructure AttributeStructure { get; set; }
public BoundAttributeDescriptor BoundAttribute { get; set; }
public TagHelperDescriptor TagHelper { get; set; }
public bool IsIndexerNameMatch { get; set; }
public override void Accept(IntermediateNodeVisitor visitor)
{
if (visitor == null)
{
throw new ArgumentNullException(nameof(visitor));
}
visitor.VisitTagHelperDirectiveAttribute(this);
}
public override void FormatNode(IntermediateNodeFormatter formatter)
{
formatter.WriteContent(AttributeName);
formatter.WriteProperty(nameof(AttributeName), AttributeName);
formatter.WriteProperty(nameof(OriginalAttributeName), OriginalAttributeName);
formatter.WriteProperty(nameof(AttributeStructure), AttributeStructure.ToString());
formatter.WriteProperty(nameof(BoundAttribute), BoundAttribute?.DisplayName);
formatter.WriteProperty(nameof(IsIndexerNameMatch), IsIndexerNameMatch.ToString());
formatter.WriteProperty(nameof(TagHelper), TagHelper?.DisplayName);
}
}
}

View File

@ -5,7 +5,7 @@ using System;
namespace Microsoft.AspNetCore.Razor.Language.Intermediate namespace Microsoft.AspNetCore.Razor.Language.Intermediate
{ {
public sealed class TagHelperAttributeParameterIntermediateNode : IntermediateNode public sealed class TagHelperDirectiveAttributeParameterIntermediateNode : IntermediateNode
{ {
public override IntermediateNodeCollection Children { get; } = new IntermediateNodeCollection(); public override IntermediateNodeCollection Children { get; } = new IntermediateNodeCollection();
@ -13,6 +13,8 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate
public string AttributeNameWithoutParameter { get; set; } public string AttributeNameWithoutParameter { get; set; }
public string OriginalAttributeName { get; set; }
public AttributeStructure AttributeStructure { get; set; } public AttributeStructure AttributeStructure { get; set; }
public BoundAttributeParameterDescriptor BoundAttributeParameter { get; set; } public BoundAttributeParameterDescriptor BoundAttributeParameter { get; set; }
@ -23,8 +25,6 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate
public bool IsIndexerNameMatch { get; set; } public bool IsIndexerNameMatch { get; set; }
public bool IsDirectiveAttribute => BoundAttribute?.IsDirectiveAttribute() ?? false;
public override void Accept(IntermediateNodeVisitor visitor) public override void Accept(IntermediateNodeVisitor visitor)
{ {
if (visitor == null) if (visitor == null)
@ -32,7 +32,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate
throw new ArgumentNullException(nameof(visitor)); throw new ArgumentNullException(nameof(visitor));
} }
visitor.VisitTagHelperAttributeParameter(this); visitor.VisitTagHelperDirectiveAttributeParameter(this);
} }
public override void FormatNode(IntermediateNodeFormatter formatter) public override void FormatNode(IntermediateNodeFormatter formatter)
@ -40,10 +40,10 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate
formatter.WriteContent(AttributeName); formatter.WriteContent(AttributeName);
formatter.WriteProperty(nameof(AttributeName), AttributeName); formatter.WriteProperty(nameof(AttributeName), AttributeName);
formatter.WriteProperty(nameof(OriginalAttributeName), OriginalAttributeName);
formatter.WriteProperty(nameof(AttributeStructure), AttributeStructure.ToString()); formatter.WriteProperty(nameof(AttributeStructure), AttributeStructure.ToString());
formatter.WriteProperty(nameof(BoundAttribute), BoundAttribute?.DisplayName); formatter.WriteProperty(nameof(BoundAttribute), BoundAttribute?.DisplayName);
formatter.WriteProperty(nameof(BoundAttributeParameter), BoundAttributeParameter?.DisplayName); formatter.WriteProperty(nameof(BoundAttributeParameter), BoundAttributeParameter?.DisplayName);
formatter.WriteProperty(nameof(IsDirectiveAttribute), IsDirectiveAttribute.ToString());
formatter.WriteProperty(nameof(TagHelper), TagHelper?.DisplayName); formatter.WriteProperty(nameof(TagHelper), TagHelper?.DisplayName);
} }
} }

View File

@ -19,8 +19,6 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate
public bool IsIndexerNameMatch { get; set; } public bool IsIndexerNameMatch { get; set; }
public bool IsDirectiveAttribute => BoundAttribute?.IsDirectiveAttribute() ?? false;
public override void Accept(IntermediateNodeVisitor visitor) public override void Accept(IntermediateNodeVisitor visitor)
{ {
if (visitor == null) if (visitor == null)
@ -38,7 +36,6 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate
formatter.WriteProperty(nameof(AttributeName), AttributeName); formatter.WriteProperty(nameof(AttributeName), AttributeName);
formatter.WriteProperty(nameof(AttributeStructure), AttributeStructure.ToString()); formatter.WriteProperty(nameof(AttributeStructure), AttributeStructure.ToString());
formatter.WriteProperty(nameof(BoundAttribute), BoundAttribute?.DisplayName); formatter.WriteProperty(nameof(BoundAttribute), BoundAttribute?.DisplayName);
formatter.WriteProperty(nameof(IsDirectiveAttribute), IsDirectiveAttribute.ToString());
formatter.WriteProperty(nameof(IsIndexerNameMatch), IsIndexerNameMatch.ToString()); formatter.WriteProperty(nameof(IsIndexerNameMatch), IsIndexerNameMatch.ToString());
formatter.WriteProperty(nameof(TagHelper), TagHelper?.DisplayName); formatter.WriteProperty(nameof(TagHelper), TagHelper?.DisplayName);
} }

View File

@ -245,5 +245,26 @@ Some Content
"Invalid parameter name. The parameter name attribute 'Context' on component 'RenderChildContentString' can only include literal text.", "Invalid parameter name. The parameter name attribute 'Context' on component 'RenderChildContentString' can only include literal text.",
diagnostic.GetMessage()); diagnostic.GetMessage());
} }
[Fact]
public void ChildContent_ExplicitChildContent_ContainsDirectiveAttribute_ProducesDiagnostic()
{
// Arrange
AdditionalSyntaxTrees.Add(RenderChildContentStringComponent);
// Act
var generated = CompileToCSharp(@"
<RenderChildContentString>
<ChildContent Context=""items"" @key=""Hello"">
</ChildContent>
</RenderChildContentString>");
// Assert
var diagnostic = Assert.Single(generated.Diagnostics);
Assert.Same(ComponentDiagnosticFactory.ChildContentHasInvalidAttribute.Id, diagnostic.Id);
Assert.Equal(
"Unrecognized attribute '@key' on child content element 'ChildContent'.",
diagnostic.GetMessage());
}
} }
} }

View File

@ -107,5 +107,23 @@ namespace Test
Assert.Equal(0, item.Span.CharacterIndex); Assert.Equal(0, item.Span.CharacterIndex);
}); });
} }
[Fact]
public void DirectiveAttribute_ComplexContent_ReportsError()
{
// Arrange & Act
var generated = CompileToCSharp(@"
<input type=""text"" @key=""Foo @Text"" />
@functions {
public string Text { get; set; } = ""text"";
}");
// Assert
var diagnostic = Assert.Single(generated.Diagnostics);
Assert.Equal("RZ9986", diagnostic.Id);
Assert.Equal(
"Component attributes do not support complex content (mixed C# and markup). Attribute: '@key', text: 'Foo @Text'",
diagnostic.GetMessage());
}
} }
} }

View File

@ -128,7 +128,12 @@ namespace Microsoft.AspNetCore.Razor.Language.IntegrationTests
WriteContentNode(node, node.AttributeName, string.Format("HtmlAttributeValueStyle.{0}", node.AttributeStructure)); WriteContentNode(node, node.AttributeName, string.Format("HtmlAttributeValueStyle.{0}", node.AttributeStructure));
} }
public override void VisitTagHelperAttributeParameter(TagHelperAttributeParameterIntermediateNode node) public override void VisitTagHelperDirectiveAttribute(TagHelperDirectiveAttributeIntermediateNode node)
{
WriteContentNode(node, node.AttributeName, node.BoundAttribute.DisplayName, string.Format("HtmlAttributeValueStyle.{0}", node.AttributeStructure));
}
public override void VisitTagHelperDirectiveAttributeParameter(TagHelperDirectiveAttributeParameterIntermediateNode node)
{ {
WriteContentNode(node, node.AttributeName, string.Format("HtmlAttributeValueStyle.{0}", node.AttributeStructure)); WriteContentNode(node, node.AttributeName, string.Format("HtmlAttributeValueStyle.{0}", node.AttributeStructure));
} }