From 013f3a27afc6ecdc58a8c246a67cbc5b2c531ce3 Mon Sep 17 00:00:00 2001 From: "N. Taylor Mullen" Date: Fri, 30 Oct 2015 17:13:48 -0700 Subject: [PATCH] Remove quote requirement for `TagHelper` directives. - `@addTagHelper`, `@removeTagHelper` and `@tagHelperPrefix` values are all written without quotes now. - Updated the `ChunkGenerator`s to be in-line with other `ChunkGenerator`s in the system now that they don't have vastly different values than what their corresponding `Span`s have. - Split `AddOrRemoveTagHelperChunkGenerator` into two smaller `ChunkGenerator`s to stay consistent with other generators. - Removed error cases introduced by having quotes around directives. - Updated code generation logic to not generate line pragmas (they should never have been generated to start with) and more specifically only map to the inner string that's generated. Without this change directive mappings would be offeset by a generated quote. #561 --- .../AddOrRemoveTagHelperChunkGenerator.cs | 60 ------------------ .../Generators/AddTagHelperChunkGenerator.cs | 28 +++++++++ .../RemoveTagHelperChunkGenerator.cs | 28 +++++++++ .../TagHelperPrefixDirectiveChunkGenerator.cs | 20 +----- .../Visitors/CSharpDesignTimeCodeVisitor.cs | 15 ++--- .../Parser/CSharpCodeParser.Directives.cs | 42 ++----------- .../TagHelperDirectiveSpanVisitor.cs | 61 ++++++++----------- .../Properties/RazorResources.Designer.cs | 16 ----- .../RazorResources.resx | 3 - 9 files changed, 100 insertions(+), 173 deletions(-) delete mode 100644 src/Microsoft.AspNet.Razor/Chunks/Generators/AddOrRemoveTagHelperChunkGenerator.cs create mode 100644 src/Microsoft.AspNet.Razor/Chunks/Generators/AddTagHelperChunkGenerator.cs create mode 100644 src/Microsoft.AspNet.Razor/Chunks/Generators/RemoveTagHelperChunkGenerator.cs diff --git a/src/Microsoft.AspNet.Razor/Chunks/Generators/AddOrRemoveTagHelperChunkGenerator.cs b/src/Microsoft.AspNet.Razor/Chunks/Generators/AddOrRemoveTagHelperChunkGenerator.cs deleted file mode 100644 index bfc64a4ebf..0000000000 --- a/src/Microsoft.AspNet.Razor/Chunks/Generators/AddOrRemoveTagHelperChunkGenerator.cs +++ /dev/null @@ -1,60 +0,0 @@ -// 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 Microsoft.AspNet.Razor.Parser.SyntaxTree; - -namespace Microsoft.AspNet.Razor.Chunks.Generators -{ - /// - /// A responsible for generating s and - /// s. - /// - public class AddOrRemoveTagHelperChunkGenerator : SpanChunkGenerator - { - /// - /// Instantiates a new . - /// - /// - /// Text used to look up s that should be added or removed. - /// - public AddOrRemoveTagHelperChunkGenerator(bool removeTagHelperDescriptors, string lookupText) - { - RemoveTagHelperDescriptors = removeTagHelperDescriptors; - LookupText = lookupText; - } - - /// - /// Gets the text used to look up s that should be added to or - /// removed from the Razor page. - /// - public string LookupText { get; } - - /// - /// Whether we want to remove s from the Razor page. - /// - /// If true generates s, - /// s otherwise. - public bool RemoveTagHelperDescriptors { get; } - - /// - /// Generates s if is - /// true, otherwise s are generated. - /// - /// - /// The responsible for this . - /// - /// A instance that contains information about - /// the current chunk generation process. - public override void GenerateChunk(Span target, ChunkGeneratorContext context) - { - if (RemoveTagHelperDescriptors) - { - context.ChunkTreeBuilder.AddRemoveTagHelperChunk(LookupText, target); - } - else - { - context.ChunkTreeBuilder.AddAddTagHelperChunk(LookupText, target); - } - } - } -} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Razor/Chunks/Generators/AddTagHelperChunkGenerator.cs b/src/Microsoft.AspNet.Razor/Chunks/Generators/AddTagHelperChunkGenerator.cs new file mode 100644 index 0000000000..51b2f98b9b --- /dev/null +++ b/src/Microsoft.AspNet.Razor/Chunks/Generators/AddTagHelperChunkGenerator.cs @@ -0,0 +1,28 @@ +// 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 Microsoft.AspNet.Razor.Parser.SyntaxTree; + +namespace Microsoft.AspNet.Razor.Chunks.Generators +{ + /// + /// A responsible for generating s. + /// + public class AddTagHelperChunkGenerator : SpanChunkGenerator + { + /// + /// Generates s. + /// + /// + /// The responsible for this . + /// + /// A instance that contains information about + /// the current chunk generation process. + public override void GenerateChunk(Span target, ChunkGeneratorContext context) + { + var lookupText = target.Content.Trim(); + + context.ChunkTreeBuilder.AddAddTagHelperChunk(lookupText, target); + } + } +} diff --git a/src/Microsoft.AspNet.Razor/Chunks/Generators/RemoveTagHelperChunkGenerator.cs b/src/Microsoft.AspNet.Razor/Chunks/Generators/RemoveTagHelperChunkGenerator.cs new file mode 100644 index 0000000000..280bb55747 --- /dev/null +++ b/src/Microsoft.AspNet.Razor/Chunks/Generators/RemoveTagHelperChunkGenerator.cs @@ -0,0 +1,28 @@ +// 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 Microsoft.AspNet.Razor.Parser.SyntaxTree; + +namespace Microsoft.AspNet.Razor.Chunks.Generators +{ + /// + /// A responsible for generating s. + /// + public class RemoveTagHelperChunkGenerator : SpanChunkGenerator + { + /// + /// Generates s. + /// + /// + /// The responsible for this . + /// + /// A instance that contains information about + /// the current chunk generation process. + public override void GenerateChunk(Span target, ChunkGeneratorContext context) + { + var lookupText = target.Content.Trim(); + + context.ChunkTreeBuilder.AddRemoveTagHelperChunk(lookupText, target); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Razor/Chunks/Generators/TagHelperPrefixDirectiveChunkGenerator.cs b/src/Microsoft.AspNet.Razor/Chunks/Generators/TagHelperPrefixDirectiveChunkGenerator.cs index dd00b86540..32b98188f4 100644 --- a/src/Microsoft.AspNet.Razor/Chunks/Generators/TagHelperPrefixDirectiveChunkGenerator.cs +++ b/src/Microsoft.AspNet.Razor/Chunks/Generators/TagHelperPrefixDirectiveChunkGenerator.cs @@ -11,22 +11,6 @@ namespace Microsoft.AspNet.Razor.Chunks.Generators /// public class TagHelperPrefixDirectiveChunkGenerator : SpanChunkGenerator { - /// - /// Instantiates a new . - /// - /// - /// Text used as a required prefix when matching HTML. - /// - public TagHelperPrefixDirectiveChunkGenerator(string prefix) - { - Prefix = prefix; - } - - /// - /// Text used as a required prefix when matching HTML. - /// - public string Prefix { get; } - /// /// Generates s. /// @@ -37,7 +21,9 @@ namespace Microsoft.AspNet.Razor.Chunks.Generators /// the current chunk generation process. public override void GenerateChunk(Span target, ChunkGeneratorContext context) { - context.ChunkTreeBuilder.AddTagHelperPrefixDirectiveChunk(Prefix, target); + var prefix = target.Content.Trim(); + + context.ChunkTreeBuilder.AddTagHelperPrefixDirectiveChunk(prefix, target); } } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Razor/CodeGenerators/Visitors/CSharpDesignTimeCodeVisitor.cs b/src/Microsoft.AspNet.Razor/CodeGenerators/Visitors/CSharpDesignTimeCodeVisitor.cs index 3b05fdbc0d..3d533dc097 100644 --- a/src/Microsoft.AspNet.Razor/CodeGenerators/Visitors/CSharpDesignTimeCodeVisitor.cs +++ b/src/Microsoft.AspNet.Razor/CodeGenerators/Visitors/CSharpDesignTimeCodeVisitor.cs @@ -98,15 +98,16 @@ namespace Microsoft.AspNet.Razor.CodeGenerators.Visitors Writer.WriteVariableDeclaration("string", TagHelperDirectiveSyntaxHelper, "null"); } - Writer.WriteStartAssignment(TagHelperDirectiveSyntaxHelper); + Writer + .WriteStartAssignment(TagHelperDirectiveSyntaxHelper) + .Write("\""); - // The parsing mechanism for a TagHelper directive chunk (CSharpCodeParser.TagHelperDirective()) - // removes quotes that surround the text. - CSharpCodeVisitor.CreateExpressionCodeMapping( - string.Format(CultureInfo.InvariantCulture, "\"{0}\"", text), - chunk); + using (new CSharpLineMappingWriter(Writer, chunk.Start, text.Length)) + { + Writer.Write(text); + } - Writer.WriteLine(";"); + Writer.WriteLine("\";"); } } } diff --git a/src/Microsoft.AspNet.Razor/Parser/CSharpCodeParser.Directives.cs b/src/Microsoft.AspNet.Razor/Parser/CSharpCodeParser.Directives.cs index c241e160d3..5db4ea0ec5 100644 --- a/src/Microsoft.AspNet.Razor/Parser/CSharpCodeParser.Directives.cs +++ b/src/Microsoft.AspNet.Razor/Parser/CSharpCodeParser.Directives.cs @@ -26,23 +26,17 @@ namespace Microsoft.AspNet.Razor.Parser { TagHelperDirective( SyntaxConstants.CSharp.TagHelperPrefixKeyword, - prefix => new TagHelperPrefixDirectiveChunkGenerator(prefix)); + new TagHelperPrefixDirectiveChunkGenerator()); } protected virtual void AddTagHelperDirective() { - TagHelperDirective( - SyntaxConstants.CSharp.AddTagHelperKeyword, - lookupText => - new AddOrRemoveTagHelperChunkGenerator(removeTagHelperDescriptors: false, lookupText: lookupText)); + TagHelperDirective(SyntaxConstants.CSharp.AddTagHelperKeyword, new AddTagHelperChunkGenerator()); } protected virtual void RemoveTagHelperDirective() { - TagHelperDirective( - SyntaxConstants.CSharp.RemoveTagHelperKeyword, - lookupText => - new AddOrRemoveTagHelperChunkGenerator(removeTagHelperDescriptors: true, lookupText: lookupText)); + TagHelperDirective(SyntaxConstants.CSharp.RemoveTagHelperKeyword, new RemoveTagHelperChunkGenerator()); } protected virtual void SectionDirective() @@ -289,7 +283,7 @@ namespace Microsoft.AspNet.Razor.Parser Output(SpanKind.Code, AcceptedCharacters.AnyExceptNewline); } - private void TagHelperDirective(string keyword, Func buildChunkGenerator) + private void TagHelperDirective(string keyword, ISpanChunkGenerator chunkGenerator) { AssertDirective(keyword); var keywordStartLocation = CurrentLocation; @@ -324,34 +318,10 @@ namespace Microsoft.AspNet.Razor.Parser // Parse to the end of the line. Essentially accepts anything until end of line, comments, invalid code // etc. AcceptUntil(CSharpSymbolType.NewLine); - - // Pull out the value minus the spaces at the end - var rawValue = Span.GetContent().Value.TrimEnd(); - var startsWithQuote = rawValue.StartsWith("\"", StringComparison.OrdinalIgnoreCase); - - // If the value starts with a quote then we should generate appropriate C# code to colorize the value. - if (startsWithQuote) - { - // Set up chunk generation - // The generated chunk of this chunk generator is picked up by CSharpDesignTimeHelpersVisitor which - // renders the C# to colorize the user provided value. We trim the quotes around the user's value - // so when we render the code we can project the users value into double quotes to not invoke C# - // IntelliSense. - Span.ChunkGenerator = buildChunkGenerator(rawValue.Trim('"')); - } - - // We expect the directive to be surrounded in quotes. - // The format for tag helper directives are: @directivename "SomeValue" - if (!startsWithQuote || - !rawValue.EndsWith("\"", StringComparison.OrdinalIgnoreCase)) - { - Context.OnError( - startLocation, - RazorResources.FormatParseError_DirectiveMustBeSurroundedByQuotes(keyword), - rawValue.Length); - } } + Span.ChunkGenerator = chunkGenerator; + // Output the span and finish the block CompleteBlock(); Output(SpanKind.Code, AcceptedCharacters.AnyExceptNewline); diff --git a/src/Microsoft.AspNet.Razor/Parser/TagHelpers/TagHelperDirectiveSpanVisitor.cs b/src/Microsoft.AspNet.Razor/Parser/TagHelpers/TagHelperDirectiveSpanVisitor.cs index ad7cee68a1..0289483c9b 100644 --- a/src/Microsoft.AspNet.Razor/Parser/TagHelpers/TagHelperDirectiveSpanVisitor.cs +++ b/src/Microsoft.AspNet.Razor/Parser/TagHelpers/TagHelperDirectiveSpanVisitor.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using Microsoft.AspNet.Razor.Chunks.Generators; using Microsoft.AspNet.Razor.Parser.SyntaxTree; using Microsoft.AspNet.Razor.Compilation.TagHelpers; +using System.Diagnostics; namespace Microsoft.AspNet.Razor.Parser.TagHelpers { @@ -82,50 +83,42 @@ namespace Microsoft.AspNet.Razor.Parser.TagHelpers public override void VisitSpan(Span span) { - // We're only interested in spans with an AddOrRemoveTagHelperChunkGenerator. - - if (span.ChunkGenerator is AddOrRemoveTagHelperChunkGenerator) + if (span == null) { - var chunkGenerator = (AddOrRemoveTagHelperChunkGenerator)span.ChunkGenerator; + throw new ArgumentNullException(nameof(span)); + } - var directive = - chunkGenerator.RemoveTagHelperDescriptors ? - TagHelperDirectiveType.RemoveTagHelper : - TagHelperDirectiveType.AddTagHelper; - var textLocation = GetSubTextSourceLocation(span, chunkGenerator.LookupText); - - var directiveDescriptor = new TagHelperDirectiveDescriptor - { - DirectiveText = chunkGenerator.LookupText, - Location = textLocation, - DirectiveType = directive - }; - - _directiveDescriptors.Add(directiveDescriptor); + TagHelperDirectiveType directiveType; + if (span.ChunkGenerator is AddTagHelperChunkGenerator) + { + directiveType = TagHelperDirectiveType.AddTagHelper; + } + else if (span.ChunkGenerator is RemoveTagHelperChunkGenerator) + { + directiveType = TagHelperDirectiveType.RemoveTagHelper; } else if (span.ChunkGenerator is TagHelperPrefixDirectiveChunkGenerator) { - var chunkGenerator = (TagHelperPrefixDirectiveChunkGenerator)span.ChunkGenerator; - var textLocation = GetSubTextSourceLocation(span, chunkGenerator.Prefix); - - var directiveDescriptor = new TagHelperDirectiveDescriptor - { - DirectiveText = chunkGenerator.Prefix, - Location = textLocation, - DirectiveType = TagHelperDirectiveType.TagHelperPrefix - }; - - _directiveDescriptors.Add(directiveDescriptor); + directiveType = TagHelperDirectiveType.TagHelperPrefix; + } + else + { + // Not a chunk generator that we're interested in. + return; } - } - private static SourceLocation GetSubTextSourceLocation(Span span, string text) - { - var startOffset = span.Content.IndexOf(text); + var directiveText = span.Content.Trim(); + var startOffset = span.Content.IndexOf(directiveText); var offsetContent = span.Content.Substring(0, startOffset); var offsetTextLocation = SourceLocation.Advance(span.Start, offsetContent); + var directiveDescriptor = new TagHelperDirectiveDescriptor + { + DirectiveText = directiveText, + Location = offsetTextLocation, + DirectiveType = directiveType + }; - return offsetTextLocation; + _directiveDescriptors.Add(directiveDescriptor); } } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Razor/Properties/RazorResources.Designer.cs b/src/Microsoft.AspNet.Razor/Properties/RazorResources.Designer.cs index 317c929956..81b3aa245b 100644 --- a/src/Microsoft.AspNet.Razor/Properties/RazorResources.Designer.cs +++ b/src/Microsoft.AspNet.Razor/Properties/RazorResources.Designer.cs @@ -1302,22 +1302,6 @@ namespace Microsoft.AspNet.Razor return string.Format(CultureInfo.CurrentCulture, GetString("ParseError_DirectiveMustHaveValue"), p0); } - /// - /// Directive '{0}'s value must be surrounded in double quotes. - /// - internal static string ParseError_DirectiveMustBeSurroundedByQuotes - { - get { return GetString("ParseError_DirectiveMustBeSurroundedByQuotes"); } - } - - /// - /// Directive '{0}'s value must be surrounded in double quotes. - /// - internal static string FormatParseError_DirectiveMustBeSurroundedByQuotes(object p0) - { - return string.Format(CultureInfo.CurrentCulture, GetString("ParseError_DirectiveMustBeSurroundedByQuotes"), p0); - } - /// /// Found a malformed '{0}' tag helper. Tag helpers must have a start and end tag or be self closing. /// diff --git a/src/Microsoft.AspNet.Razor/RazorResources.resx b/src/Microsoft.AspNet.Razor/RazorResources.resx index a68e8bea39..841012e31f 100644 --- a/src/Microsoft.AspNet.Razor/RazorResources.resx +++ b/src/Microsoft.AspNet.Razor/RazorResources.resx @@ -378,9 +378,6 @@ Instead, wrap the contents of the block in "{{}}": Directive '{0}' must have a value. - - Directive '{0}'s value must be surrounded in double quotes. - Found a malformed '{0}' tag helper. Tag helpers must have a start and end tag or be self closing.