diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/BoundAttributeDescriptorExtensions.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/BoundAttributeDescriptorExtensions.cs index 42a83b368c..9ea08e8537 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/BoundAttributeDescriptorExtensions.cs +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/BoundAttributeDescriptorExtensions.cs @@ -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) { diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentBindLoweringPass.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentBindLoweringPass.cs index c2d601dfc5..52162c62f8 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentBindLoweringPass.cs +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentBindLoweringPass.cs @@ -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(); - var parameterReferences = documentNode.FindDescendantReferences(); + var references = documentNode.FindDescendantReferences(); + var parameterReferences = documentNode.FindDescendantReferences(); var parents = new HashSet(); 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() + .OfType() .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; } } } } diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentComplexAttributeContentPass.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentComplexAttributeContentPass.cs index 3a6984f397..37d3d4ca2e 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentComplexAttributeContentPass.cs +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentComplexAttributeContentPass.cs @@ -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; } } } diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentDiagnosticFactory.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentDiagnosticFactory.cs index aa416b3da4..9303b730bd 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentDiagnosticFactory.cs +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentDiagnosticFactory.cs @@ -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, diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentEventHandlerLoweringPass.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentEventHandlerLoweringPass.cs index 3e7666a021..5e33dd1ad2 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentEventHandlerLoweringPass.cs +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentEventHandlerLoweringPass.cs @@ -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(); + var references = documentNode.FindDescendantReferences(); var parents = new HashSet(); 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() + .OfType() .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, 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 GetAttributeContent(TagHelperPropertyIntermediateNode node) + private static IReadOnlyList GetAttributeContent(TagHelperDirectiveAttributeIntermediateNode node) { var template = node.FindDescendantNodes().FirstOrDefault(); if (template != null) diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentKeyLoweringPass.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentKeyLoweringPass.cs index d4394a04f5..1787003101 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentKeyLoweringPass.cs +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentKeyLoweringPass.cs @@ -25,20 +25,20 @@ namespace Microsoft.AspNetCore.Razor.Language.Components return; } - var references = documentNode.FindDescendantReferences(); + var references = documentNode.FindDescendantReferences(); 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; diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentLoweringPass.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentLoweringPass.cs index 4b8da7a490..2954c6361c 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentLoweringPass.cs +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentLoweringPass.cs @@ -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); diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentReferenceCaptureLoweringPass.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentReferenceCaptureLoweringPass.cs index a962b96f72..b4d776aacf 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentReferenceCaptureLoweringPass.cs +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentReferenceCaptureLoweringPass.cs @@ -25,20 +25,20 @@ namespace Microsoft.AspNetCore.Razor.Language.Components return; } - var references = documentNode.FindDescendantReferences(); + var references = documentNode.FindDescendantReferences(); 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; diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentTemplateDiagnosticPass.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentTemplateDiagnosticPass.cs index b545237f4b..c6ae4e3aaf 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentTemplateDiagnosticPass.cs +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentTemplateDiagnosticPass.cs @@ -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)); } diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/DefaultRazorIntermediateNodeLoweringPhase.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/DefaultRazorIntermediateNodeLoweringPhase.cs index 05fd4b54e7..2e619dfb52 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/DefaultRazorIntermediateNodeLoweringPhase.cs +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/DefaultRazorIntermediateNodeLoweringPhase.cs @@ -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, diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Intermediate/ComponentAttributeIntermediateNode.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Intermediate/ComponentAttributeIntermediateNode.cs index 2d2766b7b3..98404ef88e 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Intermediate/ComponentAttributeIntermediateNode.cs +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Intermediate/ComponentAttributeIntermediateNode.cs @@ -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, 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; } diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Intermediate/IntermediateNodeVisitor.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Intermediate/IntermediateNodeVisitor.cs index e2e249ad2c..1489e23002 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Intermediate/IntermediateNodeVisitor.cs +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Intermediate/IntermediateNodeVisitor.cs @@ -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); } diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Intermediate/TagHelperDirectiveAttributeIntermediateNode.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Intermediate/TagHelperDirectiveAttributeIntermediateNode.cs new file mode 100644 index 0000000000..29ab32badd --- /dev/null +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Intermediate/TagHelperDirectiveAttributeIntermediateNode.cs @@ -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); + } + } +} diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Intermediate/TagHelperAttributeParameterIntermediateNode.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Intermediate/TagHelperDirectiveAttributeParameterIntermediateNode.cs similarity index 82% rename from src/Razor/Microsoft.AspNetCore.Razor.Language/src/Intermediate/TagHelperAttributeParameterIntermediateNode.cs rename to src/Razor/Microsoft.AspNetCore.Razor.Language/src/Intermediate/TagHelperDirectiveAttributeParameterIntermediateNode.cs index 06d03f5c3b..bb3c660085 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Intermediate/TagHelperAttributeParameterIntermediateNode.cs +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Intermediate/TagHelperDirectiveAttributeParameterIntermediateNode.cs @@ -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); } } diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Intermediate/TagHelperPropertyIntermediateNode.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Intermediate/TagHelperPropertyIntermediateNode.cs index 409c86014f..1a755fe6f9 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Intermediate/TagHelperPropertyIntermediateNode.cs +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Intermediate/TagHelperPropertyIntermediateNode.cs @@ -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); } diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/test/IntegrationTests/ComponentChildContentIntegrationTest.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/test/IntegrationTests/ComponentChildContentIntegrationTest.cs index 7a3c4c098d..ee4c92fcb7 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Language/test/IntegrationTests/ComponentChildContentIntegrationTest.cs +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/test/IntegrationTests/ComponentChildContentIntegrationTest.cs @@ -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(@" + + + +"); + + // 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()); + } } } diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/test/IntegrationTests/ComponentDiagnosticRazorIntegrationTest.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/test/IntegrationTests/ComponentDiagnosticRazorIntegrationTest.cs index 1a193ca547..5caa98fb92 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Language/test/IntegrationTests/ComponentDiagnosticRazorIntegrationTest.cs +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/test/IntegrationTests/ComponentDiagnosticRazorIntegrationTest.cs @@ -107,5 +107,23 @@ namespace Test Assert.Equal(0, item.Span.CharacterIndex); }); } + + [Fact] + public void DirectiveAttribute_ComplexContent_ReportsError() + { + // Arrange & Act + var generated = CompileToCSharp(@" + +@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()); + } } } diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common/Language/IntegrationTests/IntermediateNodeWriter.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common/Language/IntegrationTests/IntermediateNodeWriter.cs index 38f3e0e31f..6784ea7ab2 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common/Language/IntegrationTests/IntermediateNodeWriter.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common/Language/IntegrationTests/IntermediateNodeWriter.cs @@ -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)); }