From 80ebb4a06838123599fa8132a850a141e9fc80b3 Mon Sep 17 00:00:00 2001 From: "N. Taylor Mullen" Date: Tue, 17 May 2016 16:45:17 -0700 Subject: [PATCH] Update `TagHelperDirectiveSpanVisitor` to properly capture TagHelper directive text. - Added a test with optional quotes for each of the `TagHelperDirectiveDescriptor`s, add, remove and prefix. #744 --- .../Generators/AddTagHelperChunkGenerator.cs | 15 ++- .../RemoveTagHelperChunkGenerator.cs | 15 ++- .../TagHelperPrefixDirectiveChunkGenerator.cs | 15 ++- .../TagHelperDirectiveSpanVisitor.cs | 17 ++- .../TagHelperDirectiveSpanVisitorTest.cs | 104 ++++++++++++++++++ 5 files changed, 144 insertions(+), 22 deletions(-) diff --git a/src/Microsoft.AspNetCore.Razor/Chunks/Generators/AddTagHelperChunkGenerator.cs b/src/Microsoft.AspNetCore.Razor/Chunks/Generators/AddTagHelperChunkGenerator.cs index 463e4424e0..17bac2b7b5 100644 --- a/src/Microsoft.AspNetCore.Razor/Chunks/Generators/AddTagHelperChunkGenerator.cs +++ b/src/Microsoft.AspNetCore.Razor/Chunks/Generators/AddTagHelperChunkGenerator.cs @@ -12,8 +12,6 @@ namespace Microsoft.AspNetCore.Razor.Chunks.Generators /// public class AddTagHelperChunkGenerator : SpanChunkGenerator { - private readonly string _lookupText; - /// /// Initializes a new instance of . /// @@ -22,9 +20,14 @@ namespace Microsoft.AspNetCore.Razor.Chunks.Generators /// public AddTagHelperChunkGenerator(string lookupText) { - _lookupText = lookupText; + LookupText = lookupText; } + /// + /// Gets the text used to look up s that should be added. + /// + public string LookupText { get; } + /// /// Generates s. /// @@ -35,7 +38,7 @@ namespace Microsoft.AspNetCore.Razor.Chunks.Generators /// the current chunk generation process. public override void GenerateChunk(Span target, ChunkGeneratorContext context) { - context.ChunkTreeBuilder.AddAddTagHelperChunk(_lookupText, target); + context.ChunkTreeBuilder.AddAddTagHelperChunk(LookupText, target); } /// @@ -43,7 +46,7 @@ namespace Microsoft.AspNetCore.Razor.Chunks.Generators { var other = obj as AddTagHelperChunkGenerator; return base.Equals(other) && - string.Equals(_lookupText, other._lookupText, StringComparison.Ordinal); + string.Equals(LookupText, other.LookupText, StringComparison.Ordinal); } /// @@ -51,7 +54,7 @@ namespace Microsoft.AspNetCore.Razor.Chunks.Generators { var combiner = HashCodeCombiner.Start(); combiner.Add(base.GetHashCode()); - combiner.Add(_lookupText, StringComparer.Ordinal); + combiner.Add(LookupText, StringComparer.Ordinal); return combiner.CombinedHash; } diff --git a/src/Microsoft.AspNetCore.Razor/Chunks/Generators/RemoveTagHelperChunkGenerator.cs b/src/Microsoft.AspNetCore.Razor/Chunks/Generators/RemoveTagHelperChunkGenerator.cs index c41dcd95e9..1c57cc6cb0 100644 --- a/src/Microsoft.AspNetCore.Razor/Chunks/Generators/RemoveTagHelperChunkGenerator.cs +++ b/src/Microsoft.AspNetCore.Razor/Chunks/Generators/RemoveTagHelperChunkGenerator.cs @@ -12,8 +12,6 @@ namespace Microsoft.AspNetCore.Razor.Chunks.Generators /// public class RemoveTagHelperChunkGenerator : SpanChunkGenerator { - private readonly string _lookupText; - /// /// Initializes a new instance of . /// @@ -22,9 +20,14 @@ namespace Microsoft.AspNetCore.Razor.Chunks.Generators /// public RemoveTagHelperChunkGenerator(string lookupText) { - _lookupText = lookupText; + LookupText = lookupText; } + /// + /// Text used to look up s that should be removed. + /// + public string LookupText { get; } + /// /// Generates s. /// @@ -35,7 +38,7 @@ namespace Microsoft.AspNetCore.Razor.Chunks.Generators /// the current chunk generation process. public override void GenerateChunk(Span target, ChunkGeneratorContext context) { - context.ChunkTreeBuilder.AddRemoveTagHelperChunk(_lookupText, target); + context.ChunkTreeBuilder.AddRemoveTagHelperChunk(LookupText, target); } /// @@ -43,7 +46,7 @@ namespace Microsoft.AspNetCore.Razor.Chunks.Generators { var other = obj as RemoveTagHelperChunkGenerator; return base.Equals(other) && - string.Equals(_lookupText, other._lookupText, StringComparison.Ordinal); + string.Equals(LookupText, other.LookupText, StringComparison.Ordinal); } /// @@ -51,7 +54,7 @@ namespace Microsoft.AspNetCore.Razor.Chunks.Generators { var combiner = HashCodeCombiner.Start(); combiner.Add(base.GetHashCode()); - combiner.Add(_lookupText, StringComparer.Ordinal); + combiner.Add(LookupText, StringComparer.Ordinal); return combiner.CombinedHash; } diff --git a/src/Microsoft.AspNetCore.Razor/Chunks/Generators/TagHelperPrefixDirectiveChunkGenerator.cs b/src/Microsoft.AspNetCore.Razor/Chunks/Generators/TagHelperPrefixDirectiveChunkGenerator.cs index ab59b1827d..d9fc198f6d 100644 --- a/src/Microsoft.AspNetCore.Razor/Chunks/Generators/TagHelperPrefixDirectiveChunkGenerator.cs +++ b/src/Microsoft.AspNetCore.Razor/Chunks/Generators/TagHelperPrefixDirectiveChunkGenerator.cs @@ -13,8 +13,6 @@ namespace Microsoft.AspNetCore.Razor.Chunks.Generators /// public class TagHelperPrefixDirectiveChunkGenerator : SpanChunkGenerator { - private readonly string _prefix; - /// /// Initializes a new instance of . /// @@ -23,9 +21,14 @@ namespace Microsoft.AspNetCore.Razor.Chunks.Generators /// public TagHelperPrefixDirectiveChunkGenerator(string prefix) { - _prefix = prefix; + Prefix = prefix; } + /// + /// Text used as a required prefix when matching HTML. + /// + public string Prefix { get; } + /// /// Generates s. /// @@ -36,7 +39,7 @@ namespace Microsoft.AspNetCore.Razor.Chunks.Generators /// the current chunk generation process. public override void GenerateChunk(Span target, ChunkGeneratorContext context) { - context.ChunkTreeBuilder.AddTagHelperPrefixDirectiveChunk(_prefix, target); + context.ChunkTreeBuilder.AddTagHelperPrefixDirectiveChunk(Prefix, target); } /// @@ -44,7 +47,7 @@ namespace Microsoft.AspNetCore.Razor.Chunks.Generators { var other = obj as TagHelperPrefixDirectiveChunkGenerator; return base.Equals(other) && - string.Equals(_prefix, other._prefix, StringComparison.Ordinal); + string.Equals(Prefix, other.Prefix, StringComparison.Ordinal); } /// @@ -52,7 +55,7 @@ namespace Microsoft.AspNetCore.Razor.Chunks.Generators { var combiner = HashCodeCombiner.Start(); combiner.Add(base.GetHashCode()); - combiner.Add(_prefix, StringComparer.Ordinal); + combiner.Add(Prefix, StringComparer.Ordinal); return combiner.CombinedHash; } diff --git a/src/Microsoft.AspNetCore.Razor/Parser/TagHelpers/TagHelperDirectiveSpanVisitor.cs b/src/Microsoft.AspNetCore.Razor/Parser/TagHelpers/TagHelperDirectiveSpanVisitor.cs index 294ef30368..b3bdb043f4 100644 --- a/src/Microsoft.AspNetCore.Razor/Parser/TagHelpers/TagHelperDirectiveSpanVisitor.cs +++ b/src/Microsoft.AspNetCore.Razor/Parser/TagHelpers/TagHelperDirectiveSpanVisitor.cs @@ -87,18 +87,27 @@ namespace Microsoft.AspNetCore.Razor.Parser.TagHelpers throw new ArgumentNullException(nameof(span)); } + string directiveText; TagHelperDirectiveType directiveType; - if (span.ChunkGenerator is AddTagHelperChunkGenerator) + + var addTagHelperChunkGenerator = span.ChunkGenerator as AddTagHelperChunkGenerator; + var removeTagHelperChunkGenerator = span.ChunkGenerator as RemoveTagHelperChunkGenerator; + var tagHelperPrefixChunkGenerator = span.ChunkGenerator as TagHelperPrefixDirectiveChunkGenerator; + + if (addTagHelperChunkGenerator != null) { directiveType = TagHelperDirectiveType.AddTagHelper; + directiveText = addTagHelperChunkGenerator.LookupText; } - else if (span.ChunkGenerator is RemoveTagHelperChunkGenerator) + else if (removeTagHelperChunkGenerator != null) { directiveType = TagHelperDirectiveType.RemoveTagHelper; + directiveText = removeTagHelperChunkGenerator.LookupText; } - else if (span.ChunkGenerator is TagHelperPrefixDirectiveChunkGenerator) + else if (tagHelperPrefixChunkGenerator != null) { directiveType = TagHelperDirectiveType.TagHelperPrefix; + directiveText = tagHelperPrefixChunkGenerator.Prefix; } else { @@ -106,7 +115,7 @@ namespace Microsoft.AspNetCore.Razor.Parser.TagHelpers return; } - var directiveText = span.Content.Trim(); + directiveText = directiveText.Trim(); var startOffset = span.Content.IndexOf(directiveText, StringComparison.Ordinal); var offsetContent = span.Content.Substring(0, startOffset); var offsetTextLocation = SourceLocation.Advance(span.Start, offsetContent); diff --git a/test/Microsoft.AspNetCore.Razor.Test/TagHelpers/TagHelperDirectiveSpanVisitorTest.cs b/test/Microsoft.AspNetCore.Razor.Test/TagHelpers/TagHelperDirectiveSpanVisitorTest.cs index e61f2d70a7..e18f75e464 100644 --- a/test/Microsoft.AspNetCore.Razor.Test/TagHelpers/TagHelperDirectiveSpanVisitorTest.cs +++ b/test/Microsoft.AspNetCore.Razor.Test/TagHelpers/TagHelperDirectiveSpanVisitorTest.cs @@ -8,6 +8,7 @@ using Microsoft.AspNetCore.Razor.Parser; using Microsoft.AspNetCore.Razor.Parser.SyntaxTree; using Microsoft.AspNetCore.Razor.Parser.TagHelpers; using Microsoft.AspNetCore.Razor.Test.Framework; +using Microsoft.AspNetCore.Razor.Tokenizer; using Microsoft.Extensions.Internal; using Moq; using Xunit; @@ -18,6 +19,109 @@ namespace Microsoft.AspNetCore.Razor.Compilation.TagHelpers { private static readonly SpanFactory Factory = SpanFactory.CreateCsHtml(); + public static TheoryData QuotedTagHelperDirectivesData + { + get + { + var factory = new SpanFactory + { + MarkupTokenizerFactory = doc => new HtmlTokenizer(doc), + CodeTokenizerFactory = doc => new CSharpTokenizer(doc), + }; + + // document, expectedDescriptors + return new TheoryData> + { + { + new MarkupBlock(factory.Code("\"*, someAssembly\"").AsAddTagHelper("*, someAssembly")), + new[] + { + new TagHelperDirectiveDescriptor + { + DirectiveText = "*, someAssembly", + DirectiveType = TagHelperDirectiveType.AddTagHelper + }, + } + }, + { + new MarkupBlock(factory.Code("\"*, someAssembly\"").AsRemoveTagHelper("*, someAssembly")), + new[] + { + new TagHelperDirectiveDescriptor + { + DirectiveText = "*, someAssembly", + DirectiveType = TagHelperDirectiveType.RemoveTagHelper + }, + } + }, + { + new MarkupBlock(factory.Code("\"th:\"").AsTagHelperPrefixDirective("th:")), + new[] + { + new TagHelperDirectiveDescriptor + { + DirectiveText = "th:", + DirectiveType = TagHelperDirectiveType.TagHelperPrefix + }, + } + }, + { + new MarkupBlock(factory.Code(" \"*, someAssembly \" ").AsAddTagHelper("*, someAssembly ")), + new[] + { + new TagHelperDirectiveDescriptor + { + DirectiveText = "*, someAssembly", + DirectiveType = TagHelperDirectiveType.AddTagHelper + }, + } + }, + { + new MarkupBlock(factory.Code(" \"*, someAssembly \" ").AsRemoveTagHelper("*, someAssembly ")), + new[] + { + new TagHelperDirectiveDescriptor + { + DirectiveText = "*, someAssembly", + DirectiveType = TagHelperDirectiveType.RemoveTagHelper + }, + } + }, + { + new MarkupBlock(factory.Code(" \" th :\"").AsTagHelperPrefixDirective(" th :")), + new[] + { + new TagHelperDirectiveDescriptor + { + DirectiveText = "th :", + DirectiveType = TagHelperDirectiveType.TagHelperPrefix + }, + } + }, + }; + } + } + + [Theory] + [MemberData(nameof(QuotedTagHelperDirectivesData))] + public void GetDescriptors_LocatesQuotedTagHelperDirectives_CreatesDirectiveDescriptors( + MarkupBlock document, + IEnumerable expectedDescriptors) + { + // Arrange + var resolver = new TestTagHelperDescriptorResolver(); + var tagHelperDirectiveSpanVisitor = new TagHelperDirectiveSpanVisitor(resolver, new ErrorSink()); + + // Act + tagHelperDirectiveSpanVisitor.GetDescriptors(document); + + // Assert + Assert.Equal( + expectedDescriptors, + resolver.DirectiveDescriptors, + TagHelperDirectiveDescriptorComparer.Default); + } + [Fact] public void GetDescriptors_InvokesResolveOnceForAllDirectives() {