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;
}
internal static bool IsDirectiveAttribute(this BoundAttributeDescriptor attribute)
public static bool IsDirectiveAttribute(this BoundAttributeDescriptor attribute)
{
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.
var references = documentNode.FindDescendantReferences<TagHelperPropertyIntermediateNode>();
var parameterReferences = documentNode.FindDescendantReferences<TagHelperAttributeParameterIntermediateNode>();
var references = documentNode.FindDescendantReferences<TagHelperDirectiveAttributeIntermediateNode>();
var parameterReferences = documentNode.FindDescendantReferences<TagHelperDirectiveAttributeParameterIntermediateNode>();
var parents = new HashSet<IntermediateNode>();
for (var i = 0; i < references.Count; i++)
@ -56,7 +56,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
{
var reference = references[i];
var parent = reference.Parent;
var node = (TagHelperPropertyIntermediateNode)reference.Node;
var node = (TagHelperDirectiveAttributeIntermediateNode)reference.Node;
if (!parent.Children.Contains(node))
{
@ -64,7 +64,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
continue;
}
if (node.TagHelper.IsBindTagHelper() && node.IsDirectiveAttribute)
if (node.TagHelper.IsBindTagHelper())
{
bindEntries[(parent, node.AttributeName)] = new BindEntry(reference);
}
@ -75,7 +75,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
{
var parameterReference = parameterReferences[i];
var parent = parameterReference.Parent;
var node = (TagHelperAttributeParameterIntermediateNode)parameterReference.Node;
var node = (TagHelperDirectiveAttributeParameterIntermediateNode)parameterReference.Node;
if (!parent.Children.Contains(node))
{
@ -83,7 +83,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
continue;
}
if (node.TagHelper.IsBindTagHelper() && node.IsDirectiveAttribute)
if (node.TagHelper.IsBindTagHelper())
{
// Check if this tag contains a corresponding non-parameterized bind node.
if (!bindEntries.TryGetValue((parent, node.AttributeNameWithoutParameter), out var entry))
@ -140,12 +140,12 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
TagHelperDescriptor tagHelper = null;
string attributeName = null;
var attribute = node.Children[i];
if (attribute is TagHelperPropertyIntermediateNode propertyAttribute)
if (attribute is TagHelperDirectiveAttributeIntermediateNode directiveAttribute)
{
attributeName = propertyAttribute.AttributeName;
tagHelper = propertyAttribute.TagHelper;
attributeName = directiveAttribute.AttributeName;
tagHelper = directiveAttribute.TagHelper;
}
else if (attribute is TagHelperAttributeParameterIntermediateNode parameterAttribute)
else if (attribute is TagHelperDirectiveAttributeParameterIntermediateNode parameterAttribute)
{
attributeName = parameterAttribute.AttributeName;
tagHelper = parameterAttribute.TagHelper;
@ -159,12 +159,12 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
TagHelperDescriptor duplicateTagHelper = null;
string duplicateAttributeName = null;
var duplicate = node.Children[j];
if (duplicate is TagHelperPropertyIntermediateNode duplicatePropertyAttribute)
if (duplicate is TagHelperDirectiveAttributeIntermediateNode duplicateDirectiveAttribute)
{
duplicateAttributeName = duplicatePropertyAttribute.AttributeName;
duplicateTagHelper = duplicatePropertyAttribute.TagHelper;
duplicateAttributeName = duplicateDirectiveAttribute.AttributeName;
duplicateTagHelper = duplicateDirectiveAttribute.TagHelper;
}
else if (duplicate is TagHelperAttributeParameterIntermediateNode duplicateParameterAttribute)
else if (duplicate is TagHelperDirectiveAttributeParameterIntermediateNode duplicateParameterAttribute)
{
duplicateAttributeName = duplicateParameterAttribute.AttributeName;
duplicateTagHelper = duplicateParameterAttribute.TagHelper;
@ -195,12 +195,12 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
TagHelperDescriptor duplicateTagHelper = null;
string duplicateAttributeName = null;
var duplicate = node.Children[j];
if (duplicate is TagHelperPropertyIntermediateNode duplicatePropertyAttribute)
if (duplicate is TagHelperDirectiveAttributeIntermediateNode duplicateDirectiveAttribute)
{
duplicateAttributeName = duplicatePropertyAttribute.AttributeName;
duplicateTagHelper = duplicatePropertyAttribute.TagHelper;
duplicateAttributeName = duplicateDirectiveAttribute.AttributeName;
duplicateTagHelper = duplicateDirectiveAttribute.TagHelper;
}
else if (duplicate is TagHelperAttributeParameterIntermediateNode duplicateParameterAttribute)
else if (duplicate is TagHelperDirectiveAttributeParameterIntermediateNode duplicateParameterAttribute)
{
duplicateAttributeName = duplicateParameterAttribute.AttributeName;
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.
var duplicates = node.Children
.OfType<TagHelperPropertyIntermediateNode>()
.OfType<TagHelperDirectiveAttributeIntermediateNode>()
.GroupBy(p => p.AttributeName)
.Where(g => g.Count() > 1);
@ -230,7 +230,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
{
node.Diagnostics.Add(ComponentDiagnosticFactory.CreateBindAttribute_Duplicates(
node.Source,
duplicate.Key,
duplicate.First().OriginalAttributeName,
duplicate.ToArray()));
foreach (var property in duplicate)
{
@ -338,7 +338,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
{
Annotations =
{
[ComponentMetadata.Common.OriginalAttributeName] = node.AttributeName,
[ComponentMetadata.Common.OriginalAttributeName] = node.OriginalAttributeName,
},
AttributeName = valueAttributeName,
Source = node.Source,
@ -362,7 +362,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
{
Annotations =
{
[ComponentMetadata.Common.OriginalAttributeName] = node.AttributeName,
[ComponentMetadata.Common.OriginalAttributeName] = node.OriginalAttributeName,
},
AttributeName = changeAttributeName,
Source = node.Source,
@ -385,7 +385,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
{
Annotations =
{
[ComponentMetadata.Common.OriginalAttributeName] = node.AttributeName,
[ComponentMetadata.Common.OriginalAttributeName] = node.OriginalAttributeName,
},
AttributeName = valueAttributeName,
BoundAttribute = valueAttribute, // Might be null if it doesn't match a component attribute
@ -405,7 +405,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
{
Annotations =
{
[ComponentMetadata.Common.OriginalAttributeName] = node.AttributeName,
[ComponentMetadata.Common.OriginalAttributeName] = node.OriginalAttributeName,
},
AttributeName = changeAttributeName,
BoundAttribute = changeAttribute, // Might be null if it doesn't match a component attribute
@ -430,7 +430,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
{
Annotations =
{
[ComponentMetadata.Common.OriginalAttributeName] = node.AttributeName,
[ComponentMetadata.Common.OriginalAttributeName] = node.OriginalAttributeName,
},
AttributeName = expressionAttributeName,
BoundAttribute = expressionAttribute,
@ -463,7 +463,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
valueAttributeName = null;
changeAttributeName = null;
if (!attributeName.StartsWith("@bind"))
if (!attributeName.StartsWith("bind"))
{
return false;
}
@ -473,7 +473,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
changeAttributeName = GetAttributeContent(bindEntry.BindEventNode)?.Content?.Trim('"');
}
if (attributeName == "@bind")
if (attributeName == "bind")
{
return true;
}
@ -591,7 +591,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
private bool TryGetFormatNode(
IntermediateNode node,
TagHelperPropertyIntermediateNode attributeNode,
TagHelperDirectiveAttributeIntermediateNode attributeNode,
string valueAttributeName,
out TagHelperPropertyIntermediateNode formatNode)
{
@ -807,16 +807,16 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
public BindEntry(IntermediateNodeReference bindNodeReference)
{
BindNodeReference = bindNodeReference;
BindNode = (TagHelperPropertyIntermediateNode)bindNodeReference.Node;
BindNode = (TagHelperDirectiveAttributeIntermediateNode)bindNodeReference.Node;
}
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 (TrySimplifyContent(propertyNode) && node.TagHelpers.Any(t => t.IsComponentTagHelper()))
if (!TrySimplifyContent(propertyNode) && node.TagHelpers.Any(t => t.IsComponentTagHelper()))
{
node.Diagnostics.Add(ComponentDiagnosticFactory.Create_UnsupportedComplexContent(
propertyNode,
@ -47,7 +47,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
}
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(
htmlNode,
@ -56,6 +56,17 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
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)
{
// This case can be hit for a 'string' attribute
return true;
return false;
}
else if (node.Children.Count == 1 &&
node.Children[0] is CSharpExpressionIntermediateNode cSharpNode &&
@ -87,25 +98,25 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
cSharpNode.Children.RemoveAt(0);
// We were able to simplify it, all good.
return false;
return true;
}
return true;
return false;
}
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 @{ ... }
// We don't support this.
return true;
return false;
}
else if (node.Children.Count > 1)
{
// 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(
$"{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);
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}",
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(
BindAttribute_Duplicates,
@ -152,7 +152,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
() => "The attribute '{0}' was matched by multiple event handlers attributes. Duplicates:{1}",
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(
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.
// Each usage will be represented by a tag helper property that is a descendant of either
// a component or element.
var references = documentNode.FindDescendantReferences<TagHelperPropertyIntermediateNode>();
var references = documentNode.FindDescendantReferences<TagHelperDirectiveAttributeIntermediateNode>();
var parents = new HashSet<IntermediateNode>();
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++)
{
var reference = references[i];
var node = (TagHelperPropertyIntermediateNode)reference.Node;
var node = (TagHelperDirectiveAttributeIntermediateNode)reference.Node;
if (!reference.Parent.Children.Contains(node))
{
@ -54,7 +54,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
continue;
}
if (node.TagHelper.IsEventHandlerTagHelper() && node.IsDirectiveAttribute)
if (node.TagHelper.IsEventHandlerTagHelper())
{
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.
var duplicates = parent.Children
.OfType<TagHelperPropertyIntermediateNode>()
.OfType<TagHelperDirectiveAttributeIntermediateNode>()
.Where(p => p.TagHelper?.IsEventHandlerTagHelper() ?? false)
.GroupBy(p => p.AttributeName)
.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);
if (original.Count == 0)
@ -148,19 +148,14 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
}
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)
{
var result = new HtmlAttributeIntermediateNode()
{
Annotations =
{
[ComponentMetadata.Common.OriginalAttributeName] = node.AttributeName,
[ComponentMetadata.Common.OriginalAttributeName] = node.OriginalAttributeName,
},
AttributeName = attributeName,
Source = node.Source,
@ -188,7 +183,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
{
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();
if (template != null)

View File

@ -25,20 +25,20 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
return;
}
var references = documentNode.FindDescendantReferences<TagHelperPropertyIntermediateNode>();
var references = documentNode.FindDescendantReferences<TagHelperDirectiveAttributeIntermediateNode>();
for (var i = 0; i < references.Count; 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));
}
}
}
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
// already be a diagnostic for empty values
@ -51,7 +51,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
return new SetKeyIntermediateNode(keyValueToken);
}
private IntermediateToken DetermineKeyValueToken(TagHelperPropertyIntermediateNode attributeNode)
private IntermediateToken DetermineKeyValueToken(TagHelperDirectiveAttributeIntermediateNode attributeNode)
{
IntermediateToken foundToken = null;

View File

@ -261,7 +261,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
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));
}
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.
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
{
// 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));
}
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)
{
_children.Add(node);
@ -490,6 +502,13 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
_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)
{
_children.Add(node);

View File

@ -25,20 +25,20 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
return;
}
var references = documentNode.FindDescendantReferences<TagHelperPropertyIntermediateNode>();
var references = documentNode.FindDescendantReferences<TagHelperDirectiveAttributeIntermediateNode>();
for (var i = 0; i < references.Count; 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));
}
}
}
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
// 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;

View File

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

View File

@ -1752,14 +1752,19 @@ namespace Microsoft.AspNetCore.Razor.Language
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;
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,
OriginalAttributeName = attributeName,
BoundAttributeParameter = associatedAttributeParameterDescriptor,
BoundAttribute = associatedAttributeDescriptor,
TagHelper = associatedDescriptor,
@ -1770,9 +1775,10 @@ namespace Microsoft.AspNetCore.Razor.Language
}
else
{
attributeNode = new TagHelperPropertyIntermediateNode()
attributeNode = new TagHelperDirectiveAttributeIntermediateNode()
{
AttributeName = attributeName,
AttributeName = actualAttributeName,
OriginalAttributeName = attributeName,
BoundAttribute = associatedAttributeDescriptor,
TagHelper = associatedDescriptor,
AttributeStructure = node.TagHelperAttributeInfo.AttributeStructure,
@ -1870,14 +1876,19 @@ namespace Microsoft.AspNetCore.Razor.Language
out var parameterMatch,
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;
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,
OriginalAttributeName = attributeName,
BoundAttributeParameter = associatedAttributeParameterDescriptor,
BoundAttribute = associatedAttributeDescriptor,
TagHelper = associatedDescriptor,
@ -1888,9 +1899,10 @@ namespace Microsoft.AspNetCore.Razor.Language
}
else
{
attributeNode = new TagHelperPropertyIntermediateNode()
attributeNode = new TagHelperDirectiveAttributeIntermediateNode()
{
AttributeName = attributeName,
AttributeName = actualAttributeName,
OriginalAttributeName = attributeName,
BoundAttribute = associatedAttributeDescriptor,
TagHelper = associatedDescriptor,
AttributeStructure = node.TagHelperAttributeInfo.AttributeStructure,

View File

@ -42,12 +42,6 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate
}
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;
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 string AttributeName { get; set; }

View File

@ -124,12 +124,17 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate
VisitDefault(node);
}
public virtual void VisitTagHelperDirectiveAttribute(TagHelperDirectiveAttributeIntermediateNode node)
{
VisitDefault(node);
}
public virtual void VisitTagHelperHtmlAttribute(TagHelperHtmlAttributeIntermediateNode node)
{
VisitDefault(node);
}
public virtual void VisitTagHelperAttributeParameter(TagHelperAttributeParameterIntermediateNode node)
public virtual void VisitTagHelperDirectiveAttributeParameter(TagHelperDirectiveAttributeParameterIntermediateNode 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
{
public sealed class TagHelperAttributeParameterIntermediateNode : IntermediateNode
public sealed class TagHelperDirectiveAttributeParameterIntermediateNode : IntermediateNode
{
public override IntermediateNodeCollection Children { get; } = new IntermediateNodeCollection();
@ -13,6 +13,8 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate
public string AttributeNameWithoutParameter { get; set; }
public string OriginalAttributeName { get; set; }
public AttributeStructure AttributeStructure { get; set; }
public BoundAttributeParameterDescriptor BoundAttributeParameter { get; set; }
@ -23,8 +25,6 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate
public bool IsIndexerNameMatch { get; set; }
public bool IsDirectiveAttribute => BoundAttribute?.IsDirectiveAttribute() ?? false;
public override void Accept(IntermediateNodeVisitor visitor)
{
if (visitor == null)
@ -32,7 +32,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate
throw new ArgumentNullException(nameof(visitor));
}
visitor.VisitTagHelperAttributeParameter(this);
visitor.VisitTagHelperDirectiveAttributeParameter(this);
}
public override void FormatNode(IntermediateNodeFormatter formatter)
@ -40,10 +40,10 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate
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(BoundAttributeParameter), BoundAttributeParameter?.DisplayName);
formatter.WriteProperty(nameof(IsDirectiveAttribute), IsDirectiveAttribute.ToString());
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 IsDirectiveAttribute => BoundAttribute?.IsDirectiveAttribute() ?? false;
public override void Accept(IntermediateNodeVisitor visitor)
{
if (visitor == null)
@ -38,7 +36,6 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate
formatter.WriteProperty(nameof(AttributeName), AttributeName);
formatter.WriteProperty(nameof(AttributeStructure), AttributeStructure.ToString());
formatter.WriteProperty(nameof(BoundAttribute), BoundAttribute?.DisplayName);
formatter.WriteProperty(nameof(IsDirectiveAttribute), IsDirectiveAttribute.ToString());
formatter.WriteProperty(nameof(IsIndexerNameMatch), IsIndexerNameMatch.ToString());
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.",
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);
});
}
[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));
}
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));
}