diff --git a/src/Microsoft.AspNetCore.Razor.Language/Legacy/TagHelperParseTreeRewriter.cs b/src/Microsoft.AspNetCore.Razor.Language/Legacy/TagHelperParseTreeRewriter.cs index 9bf9d2d77d..7d720a365b 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/Legacy/TagHelperParseTreeRewriter.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/Legacy/TagHelperParseTreeRewriter.cs @@ -44,7 +44,6 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy private readonly Stack _blockStack; private TagHelperBlockTracker _currentTagHelperTracker; private BlockBuilder _currentBlock; - private string _currentParentTagName; public TagHelperParseTreeRewriter(string tagHelperPrefix, IEnumerable descriptors) { @@ -56,6 +55,12 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy _htmlAttributeTracker = new List>(); } + private TagBlockTracker CurrentTracker => _trackerStack.Count > 0 ? _trackerStack.Peek() : null; + + private string CurrentParentTagName => CurrentTracker?.TagName; + + private bool CurrentParentIsTagHelper => CurrentTracker?.IsTagHelper ?? false; + public Block Rewrite(Block syntaxTree, ErrorSink errorSink) { RewriteTags(syntaxTree, errorSink, depth: 0); @@ -189,7 +194,11 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy // We're now in a start tag block, we first need to see if the tag block is a tag helper. var elementAttributes = GetAttributeNameValuePairs(tagBlock); - tagHelperBinding = _tagHelperBinder.GetBinding(tagName, elementAttributes, _currentParentTagName); + tagHelperBinding = _tagHelperBinder.GetBinding( + tagName, + elementAttributes, + CurrentParentTagName, + CurrentParentIsTagHelper); // If there aren't any TagHelperDescriptors registered then we aren't a TagHelper if (tagHelperBinding == null) @@ -257,7 +266,8 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy tagHelperBinding = _tagHelperBinder.GetBinding( tagName, attributes: Array.Empty>(), - parentTagName: _currentParentTagName); + parentTagName: CurrentParentTagName, + parentIsTagHelper: CurrentParentIsTagHelper); // If there are not TagHelperDescriptors associated with the end tag block that also have no // required attributes then it means we can't be a TagHelper, bail out. @@ -508,10 +518,20 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy return; } - var currentTracker = _trackerStack.Count > 0 ? _trackerStack.Peek() : null; + if (!HasAllowedChildren()) + { + return; + } - if (HasAllowedChildren() && - !_currentTagHelperTracker.AllowedChildren.Contains(tagName, StringComparer.OrdinalIgnoreCase)) + var tagHelperBinding = _tagHelperBinder.GetBinding( + tagName, + attributes: Array.Empty>(), + parentTagName: CurrentParentTagName, + parentIsTagHelper: CurrentParentIsTagHelper); + + // If we found a binding for the current tag, then it is a tag helper. Use the prefixed allowed children to compare. + var allowedChildren = tagHelperBinding != null ? _currentTagHelperTracker.PrefixedAllowedChildren : _currentTagHelperTracker.AllowedChildren; + if (!allowedChildren.Contains(tagName, StringComparer.OrdinalIgnoreCase)) { OnAllowedChildrenTagError(_currentTagHelperTracker, tagName, tagBlock, errorSink); } @@ -812,14 +832,12 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy private void PushTrackerStack(TagBlockTracker tracker) { - _currentParentTagName = tracker.TagName; _trackerStack.Push(tracker); } private TagBlockTracker PopTrackerStack() { var poppedTracker = _trackerStack.Pop(); - _currentParentTagName = _trackerStack.Count > 0 ? _trackerStack.Peek().TagName : null; return poppedTracker; } diff --git a/src/Microsoft.AspNetCore.Razor.Language/TagHelperBinder.cs b/src/Microsoft.AspNetCore.Razor.Language/TagHelperBinder.cs index 043fc3f73d..067f9d1304 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/TagHelperBinder.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/TagHelperBinder.cs @@ -39,12 +39,14 @@ namespace Microsoft.AspNetCore.Razor.Language /// retrieves catch-all s (descriptors that target every tag). /// Attributes on the HTML tag. /// The parent tag name of the given tag. + /// Is the parent tag of the given tag a tag helper. /// s that apply to the given HTML tag criteria. /// Will return null if no s are a match. public TagHelperBinding GetBinding( string tagName, IReadOnlyList> attributes, - string parentTagName) + string parentTagName, + bool parentIsTagHelper) { if (!string.IsNullOrEmpty(_tagHelperPrefix) && (tagName.Length <= _tagHelperPrefix.Length || @@ -74,11 +76,17 @@ namespace Microsoft.AspNetCore.Razor.Language } var tagNameWithoutPrefix = _tagHelperPrefix != null ? tagName.Substring(_tagHelperPrefix.Length) : tagName; + var parentTagNameWithoutPrefix = parentTagName; + if (_tagHelperPrefix != null && parentIsTagHelper) + { + parentTagNameWithoutPrefix = parentTagName.Substring(_tagHelperPrefix.Length); + } + Dictionary> applicableDescriptorMappings = null; foreach (var descriptor in descriptors) { var applicableRules = descriptor.TagMatchingRules.Where( - rule => TagHelperMatchingConventions.SatisfiesRule(tagNameWithoutPrefix, parentTagName, attributes, rule)); + rule => TagHelperMatchingConventions.SatisfiesRule(tagNameWithoutPrefix, parentTagNameWithoutPrefix, attributes, rule)); if (applicableRules.Any()) { diff --git a/src/Microsoft.AspNetCore.Razor.Language/TagHelperMatchingConventions.cs b/src/Microsoft.AspNetCore.Razor.Language/TagHelperMatchingConventions.cs index 6e6b2442de..1fb483c7ea 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/TagHelperMatchingConventions.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/TagHelperMatchingConventions.cs @@ -16,7 +16,7 @@ namespace Microsoft.AspNetCore.Razor.Language public static bool SatisfiesRule( string tagNameWithoutPrefix, - string parentTagName, + string parentTagNameWithoutPrefix, IEnumerable> tagAttributes, TagMatchingRuleDescriptor rule) { @@ -41,7 +41,7 @@ namespace Microsoft.AspNetCore.Razor.Language return false; } - var satisfiesParentTag = SatisfiesParentTag(parentTagName, rule); + var satisfiesParentTag = SatisfiesParentTag(parentTagNameWithoutPrefix, rule); if (!satisfiesParentTag) { return false; @@ -89,14 +89,14 @@ namespace Microsoft.AspNetCore.Razor.Language return true; } - public static bool SatisfiesParentTag(string parentTagName, TagMatchingRuleDescriptor rule) + public static bool SatisfiesParentTag(string parentTagNameWithoutPrefix, TagMatchingRuleDescriptor rule) { if (rule == null) { throw new ArgumentNullException(nameof(rule)); } - if (rule.ParentTag != null && !string.Equals(parentTagName, rule.ParentTag, StringComparison.OrdinalIgnoreCase)) + if (rule.ParentTag != null && !string.Equals(parentTagNameWithoutPrefix, rule.ParentTag, StringComparison.OrdinalIgnoreCase)) { return false; } diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/DefaultTagHelperCompletionService.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/DefaultTagHelperCompletionService.cs index a59548233b..09fd620fa8 100644 --- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/DefaultTagHelperCompletionService.cs +++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/DefaultTagHelperCompletionService.cs @@ -55,7 +55,13 @@ namespace Microsoft.CodeAnalysis.Razor var prefix = documentContext.Prefix ?? string.Empty; Debug.Assert(completionContext.CurrentTagName.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)); - var applicableTagHelperBinding = _tagHelperFactsService.GetTagHelperBinding(documentContext, completionContext.CurrentTagName, completionContext.Attributes, completionContext.CurrentParentTagName); + var applicableTagHelperBinding = _tagHelperFactsService.GetTagHelperBinding( + documentContext, + completionContext.CurrentTagName, + completionContext.Attributes, + completionContext.CurrentParentTagName, + parentIsTagHelper: false); + var applicableDescriptors = applicableTagHelperBinding?.Descriptors ?? Enumerable.Empty(); var unprefixedTagName = completionContext.CurrentTagName.Substring(prefix.Length); @@ -227,11 +233,13 @@ namespace Microsoft.CodeAnalysis.Razor } var prefix = completionContext.DocumentContext.Prefix ?? string.Empty; + var binding = _tagHelperFactsService.GetTagHelperBinding( completionContext.DocumentContext, completionContext.ContainingTagName, completionContext.Attributes, - completionContext.ContainingParentTagName); + completionContext.ContainingParentTagName, + completionContext.ContainingParentIsTagHelper); if (binding == null) { diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/DefaultTagHelperFactsService.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/DefaultTagHelperFactsService.cs index 5ea8295632..3b0943b055 100644 --- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/DefaultTagHelperFactsService.cs +++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/DefaultTagHelperFactsService.cs @@ -14,7 +14,8 @@ namespace Microsoft.CodeAnalysis.Razor TagHelperDocumentContext documentContext, string tagName, IEnumerable> attributes, - string parentTag) + string parentTag, + bool parentIsTagHelper) { if (documentContext == null) { @@ -39,7 +40,7 @@ namespace Microsoft.CodeAnalysis.Razor var prefix = documentContext.Prefix; var tagHelperBinder = new TagHelperBinder(prefix, descriptors); - var binding = tagHelperBinder.GetBinding(tagName, attributes.ToList(), parentTag); + var binding = tagHelperBinder.GetBinding(tagName, attributes.ToList(), parentTag, parentIsTagHelper); return binding; } diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ElementCompletionContext.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ElementCompletionContext.cs index c2d51e5d3e..614d5ac7f9 100644 --- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ElementCompletionContext.cs +++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ElementCompletionContext.cs @@ -15,6 +15,7 @@ namespace Microsoft.CodeAnalysis.Razor string containingTagName, IEnumerable> attributes, string containingParentTagName, + bool containingParentIsTagHelper, Func inHTMLSchema) { if (documentContext == null) @@ -37,6 +38,7 @@ namespace Microsoft.CodeAnalysis.Razor ContainingTagName = containingTagName; Attributes = attributes; ContainingParentTagName = containingParentTagName; + ContainingParentIsTagHelper = containingParentIsTagHelper; InHTMLSchema = inHTMLSchema; } @@ -50,6 +52,8 @@ namespace Microsoft.CodeAnalysis.Razor public string ContainingParentTagName { get; } + public bool ContainingParentIsTagHelper { get; } + public Func InHTMLSchema { get; } } } diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/TagHelperFactsService.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/TagHelperFactsService.cs index 0e569a419e..7022e2e36e 100644 --- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/TagHelperFactsService.cs +++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/TagHelperFactsService.cs @@ -9,7 +9,7 @@ namespace Microsoft.CodeAnalysis.Razor { public abstract class TagHelperFactsService : ILanguageService { - public abstract TagHelperBinding GetTagHelperBinding(TagHelperDocumentContext documentContext, string tagName, IEnumerable> attributes, string parentTag); + public abstract TagHelperBinding GetTagHelperBinding(TagHelperDocumentContext documentContext, string tagName, IEnumerable> attributes, string parentTag, bool parentIsTagHelper); public abstract IEnumerable GetBoundTagHelperAttributes(TagHelperDocumentContext documentContext, string attributeName, TagHelperBinding binding); diff --git a/src/Microsoft.VisualStudio.LanguageServices.Razor/Legacy/DefaultTagHelperCompletionService.cs b/src/Microsoft.VisualStudio.LanguageServices.Razor/Legacy/DefaultTagHelperCompletionService.cs index 15b0aea6aa..e81e01ccce 100644 --- a/src/Microsoft.VisualStudio.LanguageServices.Razor/Legacy/DefaultTagHelperCompletionService.cs +++ b/src/Microsoft.VisualStudio.LanguageServices.Razor/Legacy/DefaultTagHelperCompletionService.cs @@ -62,7 +62,12 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor var prefix = documentContext.Prefix ?? string.Empty; Debug.Assert(completionContext.CurrentTagName.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)); - var applicableTagHelperBinding = _tagHelperFactsService.GetTagHelperBinding(documentContext, completionContext.CurrentTagName, completionContext.Attributes, completionContext.CurrentParentTagName); + var applicableTagHelperBinding = _tagHelperFactsService.GetTagHelperBinding( + documentContext, + completionContext.CurrentTagName, + completionContext.Attributes, + completionContext.CurrentParentTagName); + var applicableDescriptors = applicableTagHelperBinding?.Descriptors ?? Enumerable.Empty(); var unprefixedTagName = completionContext.CurrentTagName.Substring(prefix.Length); diff --git a/src/Microsoft.VisualStudio.LanguageServices.Razor/Legacy/DefaultTagHelperFactsService.cs b/src/Microsoft.VisualStudio.LanguageServices.Razor/Legacy/DefaultTagHelperFactsService.cs index 8fa8a7a9b4..8e0b34b431 100644 --- a/src/Microsoft.VisualStudio.LanguageServices.Razor/Legacy/DefaultTagHelperFactsService.cs +++ b/src/Microsoft.VisualStudio.LanguageServices.Razor/Legacy/DefaultTagHelperFactsService.cs @@ -45,7 +45,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor var prefix = documentContext.Prefix; var tagHelperBinder = new TagHelperBinder(prefix, descriptors); - var binding = tagHelperBinder.GetBinding(tagName, attributes.ToList(), parentTag); + var binding = tagHelperBinder.GetBinding(tagName, attributes.ToList(), parentTag, parentIsTagHelper: false); return binding; } diff --git a/test/Microsoft.AspNetCore.Razor.Language.Test/Legacy/TagHelperParseTreeRewriterTest.cs b/test/Microsoft.AspNetCore.Razor.Language.Test/Legacy/TagHelperParseTreeRewriterTest.cs index a5afdfed28..7b027820aa 100644 --- a/test/Microsoft.AspNetCore.Razor.Language.Test/Legacy/TagHelperParseTreeRewriterTest.cs +++ b/test/Microsoft.AspNetCore.Razor.Language.Test/Legacy/TagHelperParseTreeRewriterTest.cs @@ -385,6 +385,99 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy tagHelperPrefix: "th:"); } + [Fact] + public void Rewrite_UnderstandsTagHelperPrefixAndAllowedChildrenAndRequireParent() + { + // Arrange + var documentContent = ""; + var expectedOutput = new MarkupBlock( + new MarkupTagHelperBlock("th:p", + new MarkupTagHelperBlock("th:strong"))); + var descriptors = new TagHelperDescriptor[] + { + TagHelperDescriptorBuilder.Create("PTagHelper", "SomeAssembly") + .TagMatchingRuleDescriptor(rule => rule.RequireTagName("p")) + .AllowChildTag("strong") + .Build(), + TagHelperDescriptorBuilder.Create("StrongTagHelper", "SomeAssembly") + .TagMatchingRuleDescriptor(rule => rule.RequireTagName("strong").RequireParentTag("p")) + .Build(), + }; + + // Act & Assert + EvaluateData( + descriptors, + documentContent, + expectedOutput, + expectedErrors: Enumerable.Empty(), + tagHelperPrefix: "th:"); + } + + [Fact] + public void Rewrite_InvalidStructure_UnderstandsTagHelperPrefixAndAllowedChildrenAndRequireParent() + { + // Arrange + var documentContent = ""; + var expectedOutput = new MarkupBlock( + new MarkupTagHelperBlock("th:p", + new MarkupTagBlock( + Factory.Markup("")))); + var descriptors = new TagHelperDescriptor[] + { + TagHelperDescriptorBuilder.Create("PTagHelper", "SomeAssembly") + .TagMatchingRuleDescriptor(rule => rule.RequireTagName("p")) + .AllowChildTag("strong") + .Build(), + TagHelperDescriptorBuilder.Create("StrongTagHelper", "SomeAssembly") + .TagMatchingRuleDescriptor(rule => rule.RequireTagName("strong").RequireParentTag("p")) + .Build(), + }; + var expectedErrors = new[] { + new RazorError( + LegacyResources.FormatTagHelpersParseTreeRewriter_FoundMalformedTagHelper("th:strong"), + absoluteIndex: 8, + lineIndex: 0, + columnIndex: 8, + length: 9), + }; + + // Act & Assert + EvaluateData( + descriptors, + documentContent, + expectedOutput, + expectedErrors: expectedErrors, + tagHelperPrefix: "th:"); + } + + [Fact] + public void Rewrite_NonTagHelperChild_UnderstandsTagHelperPrefixAndAllowedChildren() + { + // Arrange + var documentContent = ""; + var expectedOutput = new MarkupBlock( + new MarkupTagHelperBlock("th:p", + new MarkupTagBlock( + Factory.Markup("")), + new MarkupTagBlock( + Factory.Markup("")))); + var descriptors = new TagHelperDescriptor[] + { + TagHelperDescriptorBuilder.Create("PTagHelper", "SomeAssembly") + .TagMatchingRuleDescriptor(rule => rule.RequireTagName("p")) + .AllowChildTag("strong") + .Build(), + }; + + // Act & Assert + EvaluateData( + descriptors, + documentContent, + expectedOutput, + expectedErrors: Enumerable.Empty(), + tagHelperPrefix: "th:"); + } + public static TheoryData InvalidHtmlScriptBlockData { get diff --git a/test/Microsoft.AspNetCore.Razor.Language.Test/TagHelperBinderTest.cs b/test/Microsoft.AspNetCore.Razor.Language.Test/TagHelperBinderTest.cs index d05f37e85c..8530e7ebf9 100644 --- a/test/Microsoft.AspNetCore.Razor.Language.Test/TagHelperBinderTest.cs +++ b/test/Microsoft.AspNetCore.Razor.Language.Test/TagHelperBinderTest.cs @@ -28,7 +28,8 @@ namespace Microsoft.AspNetCore.Razor.Language var bindingResult = tagHelperBinder.GetBinding( tagName: "th:div", attributes: expectedAttributes, - parentTagName: "body"); + parentTagName: "body", + parentIsTagHelper: false); // Assert Assert.Equal(expectedDescriptors, bindingResult.Descriptors, TagHelperDescriptorComparer.CaseSensitive); @@ -109,7 +110,8 @@ namespace Microsoft.AspNetCore.Razor.Language var bindingResult = tagHelperBinder.GetBinding( tagName, attributes: Array.Empty>(), - parentTagName: parentTagName); + parentTagName: parentTagName, + parentIsTagHelper: false); // Assert Assert.Equal((IEnumerable)expectedDescriptors, bindingResult.Descriptors, TagHelperDescriptorComparer.CaseSensitive); @@ -272,7 +274,7 @@ namespace Microsoft.AspNetCore.Razor.Language var tagHelperBinder = new TagHelperBinder(null, (IReadOnlyList)availableDescriptors); // Act - var bindingResult = tagHelperBinder.GetBinding(tagName, providedAttributes, parentTagName: "p"); + var bindingResult = tagHelperBinder.GetBinding(tagName, providedAttributes, parentTagName: "p", parentIsTagHelper: false); // Assert Assert.Equal((IEnumerable)expectedDescriptors, bindingResult?.Descriptors, TagHelperDescriptorComparer.CaseSensitive); @@ -292,7 +294,8 @@ namespace Microsoft.AspNetCore.Razor.Language var bindingResult = tagHelperBinder.GetBinding( tagName: "th", attributes: Array.Empty>(), - parentTagName: "p"); + parentTagName: "p", + parentIsTagHelper: false); // Assert Assert.Null(bindingResult); @@ -312,11 +315,13 @@ namespace Microsoft.AspNetCore.Razor.Language var bindingResultDiv = tagHelperBinder.GetBinding( tagName: "th:div", attributes: Array.Empty>(), - parentTagName: "p"); + parentTagName: "p", + parentIsTagHelper: false); var bindingResultSpan = tagHelperBinder.GetBinding( tagName: "th:span", attributes: Array.Empty>(), - parentTagName: "p"); + parentTagName: "p", + parentIsTagHelper: false); // Assert var descriptor = Assert.Single(bindingResultDiv.Descriptors); @@ -339,7 +344,8 @@ namespace Microsoft.AspNetCore.Razor.Language var bindingResult = tagHelperBinder.GetBinding( tagName: "th:div", attributes: Array.Empty>(), - parentTagName: "p"); + parentTagName: "p", + parentIsTagHelper: false); // Assert var descriptor = Assert.Single(bindingResult.Descriptors); @@ -362,7 +368,8 @@ namespace Microsoft.AspNetCore.Razor.Language var bindingResult = tagHelperBinder.GetBinding( tagName: "div", attributes: Array.Empty>(), - parentTagName: "p"); + parentTagName: "p", + parentIsTagHelper: false); // Assert Assert.Null(bindingResult); @@ -385,7 +392,8 @@ namespace Microsoft.AspNetCore.Razor.Language var tagHelperBinding = tagHelperBinder.GetBinding( tagName: "foo", attributes: Array.Empty>(), - parentTagName: "p"); + parentTagName: "p", + parentIsTagHelper: false); // Assert Assert.Null(tagHelperBinding); @@ -411,11 +419,13 @@ namespace Microsoft.AspNetCore.Razor.Language var divBinding = tagHelperBinder.GetBinding( tagName: "div", attributes: Array.Empty>(), - parentTagName: "p"); + parentTagName: "p", + parentIsTagHelper: false); var spanBinding = tagHelperBinder.GetBinding( tagName: "span", attributes: Array.Empty>(), - parentTagName: "p"); + parentTagName: "p", + parentIsTagHelper: false); // Assert // For divs @@ -443,7 +453,8 @@ namespace Microsoft.AspNetCore.Razor.Language var bindingResult = tagHelperBinder.GetBinding( tagName: "div", attributes: Array.Empty>(), - parentTagName: "p"); + parentTagName: "p", + parentIsTagHelper: false); // Assert var descriptor = Assert.Single(bindingResult.Descriptors); @@ -470,7 +481,8 @@ namespace Microsoft.AspNetCore.Razor.Language var binding = tagHelperBinder.GetBinding( tagName: "div", attributes: Array.Empty>(), - parentTagName: "p"); + parentTagName: "p", + parentIsTagHelper: false); // Assert var boundDescriptor = Assert.Single(binding.Descriptors); @@ -479,5 +491,34 @@ namespace Microsoft.AspNetCore.Razor.Language var boundRule = Assert.Single(boundRules); Assert.Equal("div", boundRule.TagName); } + + [Fact] + public void GetBinding_PrefixedParent_ReturnsBinding() + { + // Arrange + var divDescriptor = TagHelperDescriptorBuilder.Create("foo1", "SomeAssembly") + .TagMatchingRuleDescriptor(rule => rule.RequireTagName("div").RequireParentTag("p")) + .Build(); + var pDescriptor = TagHelperDescriptorBuilder.Create("foo2", "SomeAssembly") + .TagMatchingRuleDescriptor(rule => rule.RequireTagName("p")) + .Build(); + var descriptors = new[] { divDescriptor, pDescriptor }; + var tagHelperBinder = new TagHelperBinder("th:", descriptors); + + // Act + var bindingResult = tagHelperBinder.GetBinding( + tagName: "th:div", + attributes: Array.Empty>(), + parentTagName: "th:p", + parentIsTagHelper: true); + + // Assert + var boundDescriptor = Assert.Single(bindingResult.Descriptors); + Assert.Same(divDescriptor, boundDescriptor); + var boundRules = bindingResult.GetBoundRules(boundDescriptor); + var boundRule = Assert.Single(boundRules); + Assert.Equal("div", boundRule.TagName); + Assert.Equal("p", boundRule.ParentTag); + } } } \ No newline at end of file diff --git a/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/DefaultTagHelperCompletionServiceTest.cs b/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/DefaultTagHelperCompletionServiceTest.cs index 3961d46743..8c79de0a5e 100644 --- a/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/DefaultTagHelperCompletionServiceTest.cs +++ b/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/DefaultTagHelperCompletionServiceTest.cs @@ -927,6 +927,7 @@ namespace Microsoft.CodeAnalysis.Razor IEnumerable existingCompletions, string containingTagName, string containingParentTagName = "body", + bool containingParentIsTagHelper = false, string tagHelperPrefix = "") { var documentContext = TagHelperDocumentContext.Create(tagHelperPrefix, descriptors); @@ -936,6 +937,7 @@ namespace Microsoft.CodeAnalysis.Razor containingTagName, attributes: Enumerable.Empty>(), containingParentTagName: containingParentTagName, + containingParentIsTagHelper: containingParentIsTagHelper, inHTMLSchema: (tag) => tag == "strong" || tag == "b" || tag == "bold" || tag == "li" || tag == "div"); return completionContext; diff --git a/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/DefaultTagHelperFactsServiceTest.cs b/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/DefaultTagHelperFactsServiceTest.cs index 712b815762..35af643181 100644 --- a/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/DefaultTagHelperFactsServiceTest.cs +++ b/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/DefaultTagHelperFactsServiceTest.cs @@ -27,7 +27,7 @@ namespace Microsoft.CodeAnalysis.Razor var service = new DefaultTagHelperFactsService(); // Act - var binding = service.GetTagHelperBinding(documentContext, "!a", Enumerable.Empty>(), parentTag: null); + var binding = service.GetTagHelperBinding(documentContext, "!a", Enumerable.Empty>(), parentTag: null, parentIsTagHelper: false); // Assert Assert.Null(binding); @@ -73,7 +73,7 @@ namespace Microsoft.CodeAnalysis.Razor }; // Act - var binding = service.GetTagHelperBinding(documentContext, "a", attributes, parentTag: "p"); + var binding = service.GetTagHelperBinding(documentContext, "a", attributes, parentTag: "p", parentIsTagHelper: false); // Assert var descriptor = Assert.Single(binding.Descriptors); @@ -109,7 +109,7 @@ namespace Microsoft.CodeAnalysis.Razor }; var documentContext = TagHelperDocumentContext.Create(string.Empty, documentDescriptors); var service = new DefaultTagHelperFactsService(); - var binding = service.GetTagHelperBinding(documentContext, "a", Enumerable.Empty>(), parentTag: null); + var binding = service.GetTagHelperBinding(documentContext, "a", Enumerable.Empty>(), parentTag: null, parentIsTagHelper: false); // Act var descriptors = service.GetBoundTagHelperAttributes(documentContext, "asp-route-something", binding); @@ -144,7 +144,7 @@ namespace Microsoft.CodeAnalysis.Razor }; var documentContext = TagHelperDocumentContext.Create(string.Empty, documentDescriptors); var service = new DefaultTagHelperFactsService(); - var binding = service.GetTagHelperBinding(documentContext, "input", Enumerable.Empty>(), parentTag: null); + var binding = service.GetTagHelperBinding(documentContext, "input", Enumerable.Empty>(), parentTag: null, parentIsTagHelper: false); // Act var descriptors = service.GetBoundTagHelperAttributes(documentContext, "asp-for", binding);