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
This commit is contained in:
N. Taylor Mullen 2015-10-30 17:13:48 -07:00
parent 02d6b00d8f
commit 013f3a27af
9 changed files with 100 additions and 173 deletions

View File

@ -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
{
/// <summary>
/// A <see cref="SpanChunkGenerator"/> responsible for generating <see cref="AddTagHelperChunk"/>s and
/// <see cref="RemoveTagHelperChunk"/>s.
/// </summary>
public class AddOrRemoveTagHelperChunkGenerator : SpanChunkGenerator
{
/// <summary>
/// Instantiates a new <see cref="AddOrRemoveTagHelperChunkGenerator"/>.
/// </summary>
/// <param name="lookupText">
/// Text used to look up <see cref="Compilation.TagHelpers.TagHelperDescriptor"/>s that should be added or removed.
/// </param>
public AddOrRemoveTagHelperChunkGenerator(bool removeTagHelperDescriptors, string lookupText)
{
RemoveTagHelperDescriptors = removeTagHelperDescriptors;
LookupText = lookupText;
}
/// <summary>
/// Gets the text used to look up <see cref="Compilation.TagHelpers.TagHelperDescriptor"/>s that should be added to or
/// removed from the Razor page.
/// </summary>
public string LookupText { get; }
/// <summary>
/// Whether we want to remove <see cref="Compilation.TagHelpers.TagHelperDescriptor"/>s from the Razor page.
/// </summary>
/// <remarks>If <c>true</c> <see cref="GenerateChunk"/> generates <see cref="AddTagHelperChunk"/>s,
/// <see cref="RemoveTagHelperChunk"/>s otherwise.</remarks>
public bool RemoveTagHelperDescriptors { get; }
/// <summary>
/// Generates <see cref="AddTagHelperChunk"/>s if <see cref="RemoveTagHelperDescriptors"/> is
/// <c>true</c>, otherwise <see cref="RemoveTagHelperChunk"/>s are generated.
/// </summary>
/// <param name="target">
/// The <see cref="Span"/> responsible for this <see cref="AddOrRemoveTagHelperChunkGenerator"/>.
/// </param>
/// <param name="context">A <see cref="ChunkGeneratorContext"/> instance that contains information about
/// the current chunk generation process.</param>
public override void GenerateChunk(Span target, ChunkGeneratorContext context)
{
if (RemoveTagHelperDescriptors)
{
context.ChunkTreeBuilder.AddRemoveTagHelperChunk(LookupText, target);
}
else
{
context.ChunkTreeBuilder.AddAddTagHelperChunk(LookupText, target);
}
}
}
}

View File

@ -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
{
/// <summary>
/// A <see cref="SpanChunkGenerator"/> responsible for generating <see cref="AddTagHelperChunk"/>s.
/// </summary>
public class AddTagHelperChunkGenerator : SpanChunkGenerator
{
/// <summary>
/// Generates <see cref="AddTagHelperChunk"/>s.
/// </summary>
/// <param name="target">
/// The <see cref="Span"/> responsible for this <see cref="AddTagHelperChunkGenerator"/>.
/// </param>
/// <param name="context">A <see cref="ChunkGeneratorContext"/> instance that contains information about
/// the current chunk generation process.</param>
public override void GenerateChunk(Span target, ChunkGeneratorContext context)
{
var lookupText = target.Content.Trim();
context.ChunkTreeBuilder.AddAddTagHelperChunk(lookupText, target);
}
}
}

View File

@ -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
{
/// <summary>
/// A <see cref="SpanChunkGenerator"/> responsible for generating <see cref="RemoveTagHelperChunk"/>s.
/// </summary>
public class RemoveTagHelperChunkGenerator : SpanChunkGenerator
{
/// <summary>
/// Generates <see cref="RemoveTagHelperChunk"/>s.
/// </summary>
/// <param name="target">
/// The <see cref="Span"/> responsible for this <see cref="RemoveTagHelperChunkGenerator"/>.
/// </param>
/// <param name="context">A <see cref="ChunkGeneratorContext"/> instance that contains information about
/// the current chunk generation process.</param>
public override void GenerateChunk(Span target, ChunkGeneratorContext context)
{
var lookupText = target.Content.Trim();
context.ChunkTreeBuilder.AddRemoveTagHelperChunk(lookupText, target);
}
}
}

View File

@ -11,22 +11,6 @@ namespace Microsoft.AspNet.Razor.Chunks.Generators
/// </summary>
public class TagHelperPrefixDirectiveChunkGenerator : SpanChunkGenerator
{
/// <summary>
/// Instantiates a new <see cref="TagHelperPrefixDirectiveChunkGenerator"/>.
/// </summary>
/// <param name="prefix">
/// Text used as a required prefix when matching HTML.
/// </param>
public TagHelperPrefixDirectiveChunkGenerator(string prefix)
{
Prefix = prefix;
}
/// <summary>
/// Text used as a required prefix when matching HTML.
/// </summary>
public string Prefix { get; }
/// <summary>
/// Generates <see cref="TagHelperPrefixDirectiveChunk"/>s.
/// </summary>
@ -37,7 +21,9 @@ namespace Microsoft.AspNet.Razor.Chunks.Generators
/// the current chunk generation process.</param>
public override void GenerateChunk(Span target, ChunkGeneratorContext context)
{
context.ChunkTreeBuilder.AddTagHelperPrefixDirectiveChunk(Prefix, target);
var prefix = target.Content.Trim();
context.ChunkTreeBuilder.AddTagHelperPrefixDirectiveChunk(prefix, target);
}
}
}

View File

@ -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("\";");
}
}
}

View File

@ -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<string, ISpanChunkGenerator> 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);

View File

@ -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);
}
}
}

View File

@ -1302,22 +1302,6 @@ namespace Microsoft.AspNet.Razor
return string.Format(CultureInfo.CurrentCulture, GetString("ParseError_DirectiveMustHaveValue"), p0);
}
/// <summary>
/// Directive '{0}'s value must be surrounded in double quotes.
/// </summary>
internal static string ParseError_DirectiveMustBeSurroundedByQuotes
{
get { return GetString("ParseError_DirectiveMustBeSurroundedByQuotes"); }
}
/// <summary>
/// Directive '{0}'s value must be surrounded in double quotes.
/// </summary>
internal static string FormatParseError_DirectiveMustBeSurroundedByQuotes(object p0)
{
return string.Format(CultureInfo.CurrentCulture, GetString("ParseError_DirectiveMustBeSurroundedByQuotes"), p0);
}
/// <summary>
/// Found a malformed '{0}' tag helper. Tag helpers must have a start and end tag or be self closing.
/// </summary>

View File

@ -378,9 +378,6 @@ Instead, wrap the contents of the block in "{{}}":
<data name="ParseError_DirectiveMustHaveValue" xml:space="preserve">
<value>Directive '{0}' must have a value.</value>
</data>
<data name="ParseError_DirectiveMustBeSurroundedByQuotes" xml:space="preserve">
<value>Directive '{0}'s value must be surrounded in double quotes.</value>
</data>
<data name="TagHelpersParseTreeRewriter_FoundMalformedTagHelper" xml:space="preserve">
<value>Found a malformed '{0}' tag helper. Tag helpers must have a start and end tag or be self closing.</value>
</data>