Moved TagHelper directive validation and parsing from TagHelperBinder to

CSharpCodeParser
This commit is contained in:
Ajay Bhargav Baaskaran 2017-08-21 16:39:43 -07:00
parent 9b6420dbfc
commit fa6fde2b20
11 changed files with 864 additions and 989 deletions

View File

@ -10,11 +10,6 @@ namespace Microsoft.AspNetCore.Razor.Language
{
internal class DefaultRazorTagHelperBinderPhase : RazorEnginePhaseBase, IRazorTagHelperBinderPhase
{
private static HashSet<char> InvalidNonWhitespaceNameCharacters = new HashSet<char>(new[]
{
'@', '!', '<', '/', '?', '[', '>', ']', '=', '"', '\'', '*'
});
protected override void ExecuteCore(RazorCodeDocument codeDocument)
{
var syntaxTree = codeDocument.GetSyntaxTree();
@ -31,7 +26,8 @@ namespace Microsoft.AspNetCore.Razor.Language
//
// The imports come logically before the main razor file and are in the order they
// should be processed.
var visitor = new DirectiveVisitor();
var descriptors = feature.GetDescriptors();
var visitor = new DirectiveVisitor(descriptors);
var imports = codeDocument.GetImportSyntaxTrees();
if (imports != null)
{
@ -44,14 +40,8 @@ namespace Microsoft.AspNetCore.Razor.Language
visitor.VisitBlock(syntaxTree.Root);
var errorList = new List<RazorDiagnostic>();
var descriptors = feature.GetDescriptors();
var directives = visitor.Directives;
descriptors = ProcessDirectives(directives, descriptors);
var tagHelperPrefix = ProcessTagHelperPrefix(directives, codeDocument);
var root = syntaxTree.Root;
var tagHelperPrefix = visitor.TagHelperPrefix;
descriptors = visitor.Matches.ToArray();
var context = TagHelperDocumentContext.Create(tagHelperPrefix, descriptors);
codeDocument.SetTagHelperContext(context);
@ -64,9 +54,11 @@ namespace Microsoft.AspNetCore.Razor.Language
var errorSink = new ErrorSink();
var rewriter = new TagHelperParseTreeRewriter(tagHelperPrefix, descriptors);
var root = syntaxTree.Root;
root = rewriter.Rewrite(root, errorSink);
// Temporary code while we're still using legacy diagnostics in the SyntaxTree.
var errorList = new List<RazorDiagnostic>();
errorList.AddRange(errorSink.Errors.Select(error => RazorDiagnostic.Create(error)));
errorList.AddRange(descriptors.SelectMany(d => d.GetAllDiagnostics()));
@ -77,205 +69,27 @@ namespace Microsoft.AspNetCore.Razor.Language
codeDocument.SetSyntaxTree(newSyntaxTree);
}
// Internal for testing
internal string ProcessTagHelperPrefix(List<TagHelperDirectiveDescriptor> directives, RazorCodeDocument codeDocument)
private static bool MatchesDirective(TagHelperDescriptor descriptor, string typePattern, string assemblyName)
{
// We only support a single prefix directive.
TagHelperDirectiveDescriptor prefixDirective = null;
for (var i = 0; i < directives.Count; i++)
{
if (directives[i].DirectiveType == TagHelperDirectiveType.TagHelperPrefix)
{
// We only expect to see a single one of these per file, but that's enforced at another level.
prefixDirective = directives[i];
}
}
var prefix = prefixDirective?.DirectiveText;
if (prefix != null && !IsValidTagHelperPrefix(prefix, prefixDirective.Location, prefixDirective.Diagnostics))
{
prefix = null;
}
if (!string.IsNullOrEmpty(prefix))
{
return prefixDirective.DirectiveText;
}
return null;
}
internal IReadOnlyList<TagHelperDescriptor> ProcessDirectives(
IReadOnlyList<TagHelperDirectiveDescriptor> directives,
IReadOnlyList<TagHelperDescriptor> tagHelpers)
{
var matches = new HashSet<TagHelperDescriptor>(TagHelperDescriptorComparer.Default);
for (var i = 0; i < directives.Count; i++)
{
var directive = directives[i];
ParsedDirective parsed;
switch (directive.DirectiveType)
{
case TagHelperDirectiveType.AddTagHelper:
parsed = ParseAddOrRemoveDirective(directive);
if (parsed == null)
{
// Skip this one, it's an error
break;
}
if (!AssemblyContainsTagHelpers(parsed.AssemblyName, tagHelpers))
{
// No tag helpers in the assembly.
break;
}
for (var j = 0; j < tagHelpers.Count; j++)
{
var tagHelper = tagHelpers[j];
if (MatchesDirective(tagHelper, parsed))
{
matches.Add(tagHelper);
}
}
break;
case TagHelperDirectiveType.RemoveTagHelper:
parsed = ParseAddOrRemoveDirective(directive);
if (parsed == null)
{
// Skip this one, it's an error
break;
}
if (!AssemblyContainsTagHelpers(parsed.AssemblyName, tagHelpers))
{
// No tag helpers in the assembly.
break;
}
for (var j = 0; j < tagHelpers.Count; j++)
{
var tagHelper = tagHelpers[j];
if (MatchesDirective(tagHelper, parsed))
{
matches.Remove(tagHelper);
}
}
break;
}
}
return matches.ToArray();
}
private bool AssemblyContainsTagHelpers(string assemblyName, IReadOnlyList<TagHelperDescriptor> tagHelpers)
{
for (var i = 0; i < tagHelpers.Count; i++)
{
if (string.Equals(tagHelpers[i].AssemblyName, assemblyName, StringComparison.Ordinal))
{
return true;
}
}
return false;
}
// Internal for testing
internal ParsedDirective ParseAddOrRemoveDirective(TagHelperDirectiveDescriptor directive)
{
var text = directive.DirectiveText;
var lookupStrings = text?.Split(new[] { ',' });
// Ensure that we have valid lookupStrings to work with. The valid format is "typeName, assemblyName"
if (lookupStrings == null ||
lookupStrings.Any(string.IsNullOrWhiteSpace) ||
lookupStrings.Length != 2)
{
directive.Diagnostics.Add(
RazorDiagnostic.Create(
new RazorError(
Resources.FormatInvalidTagHelperLookupText(text),
directive.Location,
Math.Max(text.Length, 1))));
return null;
}
var trimmedAssemblyName = lookupStrings[1].Trim();
// + 1 is for the comma separator in the lookup text.
var assemblyNameIndex =
lookupStrings[0].Length + 1 + lookupStrings[1].IndexOf(trimmedAssemblyName, StringComparison.Ordinal);
var assemblyNamePrefix = directive.DirectiveText.Substring(0, assemblyNameIndex);
var assemblyNameLocation = new SourceLocation(
directive.Location.FilePath,
directive.Location.AbsoluteIndex + assemblyNameIndex,
directive.Location.LineIndex,
directive.Location.CharacterIndex + assemblyNameIndex);
return new ParsedDirective
{
TypePattern = lookupStrings[0].Trim(),
AssemblyName = trimmedAssemblyName,
AssemblyNameLocation = assemblyNameLocation,
};
}
// Internal for testing
internal bool IsValidTagHelperPrefix(
string prefix,
SourceLocation directiveLocation,
List<RazorDiagnostic> diagnostics)
{
foreach (var character in prefix)
{
// Prefixes are correlated with tag names, tag names cannot have whitespace.
if (char.IsWhiteSpace(character) || InvalidNonWhitespaceNameCharacters.Contains(character))
{
diagnostics.Add(
RazorDiagnostic.Create(
new RazorError(
Resources.FormatInvalidTagHelperPrefixValue(SyntaxConstants.CSharp.TagHelperPrefixKeyword, character, prefix),
directiveLocation,
prefix.Length)));
return false;
}
}
return true;
}
private static bool MatchesDirective(TagHelperDescriptor descriptor, ParsedDirective lookupInfo)
{
if (!string.Equals(descriptor.AssemblyName, lookupInfo.AssemblyName, StringComparison.Ordinal))
if (!string.Equals(descriptor.AssemblyName, assemblyName, StringComparison.Ordinal))
{
return false;
}
if (lookupInfo.TypePattern.EndsWith("*", StringComparison.Ordinal))
if (typePattern.EndsWith("*", StringComparison.Ordinal))
{
if (lookupInfo.TypePattern.Length == 1)
if (typePattern.Length == 1)
{
// TypePattern is "*".
return true;
}
var lookupTypeName = lookupInfo.TypePattern.Substring(0, lookupInfo.TypePattern.Length - 1);
var lookupTypeName = typePattern.Substring(0, typePattern.Length - 1);
return descriptor.Name.StartsWith(lookupTypeName, StringComparison.Ordinal);
}
return string.Equals(descriptor.Name, lookupInfo.TypePattern, StringComparison.Ordinal);
return string.Equals(descriptor.Name, typePattern, StringComparison.Ordinal);
}
private static int GetErrorLength(string directiveText)
@ -295,81 +109,89 @@ namespace Microsoft.AspNetCore.Razor.Language
return combinedErrors;
}
internal class ParsedDirective
// Internal for testing.
internal class DirectiveVisitor : ParserVisitor
{
public string AssemblyName { get; set; }
private IReadOnlyList<TagHelperDescriptor> _tagHelpers;
public string TypePattern { get; set; }
public DirectiveVisitor(IReadOnlyList<TagHelperDescriptor> tagHelpers)
{
_tagHelpers = tagHelpers;
}
public SourceLocation AssemblyNameLocation { get; set; }
}
public string TagHelperPrefix { get; private set; }
private class DirectiveVisitor : ParserVisitor
{
public List<TagHelperDirectiveDescriptor> Directives { get; } = new List<TagHelperDirectiveDescriptor>();
public HashSet<TagHelperDescriptor> Matches { get; } = new HashSet<TagHelperDescriptor>();
public override void VisitAddTagHelperSpan(AddTagHelperChunkGenerator chunkGenerator, Span span)
{
var directive = CreateDirective(span, chunkGenerator.LookupText, TagHelperDirectiveType.AddTagHelper, chunkGenerator.Diagnostics);
Directives.Add(directive);
if (chunkGenerator.AssemblyName == null)
{
// Skip this one, it's an error
return;
}
if (!AssemblyContainsTagHelpers(chunkGenerator.AssemblyName, _tagHelpers))
{
// No tag helpers in the assembly.
return;
}
for (var i = 0; i < _tagHelpers.Count; i++)
{
var tagHelper = _tagHelpers[i];
if (MatchesDirective(tagHelper, chunkGenerator.TypePattern, chunkGenerator.AssemblyName))
{
Matches.Add(tagHelper);
}
}
}
public override void VisitRemoveTagHelperSpan(RemoveTagHelperChunkGenerator chunkGenerator, Span span)
{
var directive = CreateDirective(span, chunkGenerator.LookupText, TagHelperDirectiveType.RemoveTagHelper, chunkGenerator.Diagnostics);
Directives.Add(directive);
if (chunkGenerator.AssemblyName == null)
{
// Skip this one, it's an error
return;
}
if (!AssemblyContainsTagHelpers(chunkGenerator.AssemblyName, _tagHelpers))
{
// No tag helpers in the assembly.
return;
}
for (var i = 0; i < _tagHelpers.Count; i++)
{
var tagHelper = _tagHelpers[i];
if (MatchesDirective(tagHelper, chunkGenerator.TypePattern, chunkGenerator.AssemblyName))
{
Matches.Remove(tagHelper);
}
}
}
public override void VisitTagHelperPrefixDirectiveSpan(TagHelperPrefixDirectiveChunkGenerator chunkGenerator, Span span)
{
var directive = CreateDirective(span, chunkGenerator.Prefix, TagHelperDirectiveType.TagHelperPrefix, chunkGenerator.Diagnostics);
Directives.Add(directive);
if (!string.IsNullOrEmpty(chunkGenerator.DirectiveText))
{
// We only expect to see a single one of these per file, but that's enforced at another level.
TagHelperPrefix = chunkGenerator.DirectiveText;
}
}
private TagHelperDirectiveDescriptor CreateDirective(
Span span,
string directiveText,
TagHelperDirectiveType directiveType,
List<RazorDiagnostic> diagnostics)
private bool AssemblyContainsTagHelpers(string assemblyName, IReadOnlyList<TagHelperDescriptor> tagHelpers)
{
directiveText = directiveText.Trim();
if (directiveText.Length >= 2 &&
directiveText.StartsWith("\"", StringComparison.Ordinal) &&
directiveText.EndsWith("\"", StringComparison.Ordinal))
for (var i = 0; i < tagHelpers.Count; i++)
{
directiveText = directiveText.Substring(1, directiveText.Length - 2);
if (string.Equals(tagHelpers[i].AssemblyName, assemblyName, StringComparison.Ordinal))
{
return true;
}
}
// If this is the "string literal" form of a directive, we'll need to postprocess the location
// and content.
//
// Ex: @addTagHelper "*, Microsoft.AspNetCore.CoolLibrary"
// ^ ^
// Start End
var directiveStart = span.Start;
if (span.Symbols.Count == 1 && (span.Symbols[0] as CSharpSymbol)?.Type == CSharpSymbolType.StringLiteral)
{
var offset = span.Content.IndexOf(directiveText, StringComparison.Ordinal);
// This is safe because inside one of these directives all of the text needs to be on the
// same line.
var original = span.Start;
directiveStart = new SourceLocation(
original.FilePath,
original.AbsoluteIndex + offset,
original.LineIndex,
original.CharacterIndex + offset);
}
var directiveDescriptor = new TagHelperDirectiveDescriptor
{
DirectiveText = directiveText,
Location = directiveStart,
DirectiveType = directiveType,
Diagnostics = diagnostics,
};
return directiveDescriptor;
return false;
}
}
}

View File

@ -10,14 +10,28 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
{
internal class AddTagHelperChunkGenerator : SpanChunkGenerator
{
public AddTagHelperChunkGenerator(string lookupText, List<RazorDiagnostic> diagnostics)
public AddTagHelperChunkGenerator(
string lookupText,
string directiveText,
string typePattern,
string assemblyName,
List<RazorDiagnostic> diagnostics)
{
LookupText = lookupText;
DirectiveText = directiveText;
AssemblyName = assemblyName;
TypePattern = typePattern;
Diagnostics = diagnostics;
}
public string LookupText { get; }
public string DirectiveText { get; set; }
public string TypePattern { get; set; }
public string AssemblyName { get; set; }
public List<RazorDiagnostic> Diagnostics { get; }
public override void Accept(ParserVisitor visitor, Span span)
@ -31,7 +45,10 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
var other = obj as AddTagHelperChunkGenerator;
return base.Equals(other) &&
Enumerable.SequenceEqual(Diagnostics, other.Diagnostics) &&
string.Equals(LookupText, other.LookupText, StringComparison.Ordinal);
string.Equals(LookupText, other.LookupText, StringComparison.Ordinal) &&
string.Equals(DirectiveText, other.DirectiveText, StringComparison.Ordinal) &&
string.Equals(TypePattern, other.TypePattern, StringComparison.Ordinal) &&
string.Equals(AssemblyName, other.AssemblyName, StringComparison.Ordinal);
}
/// <inheritdoc />
@ -40,6 +57,9 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
var combiner = HashCodeCombiner.Start();
combiner.Add(base.GetHashCode());
combiner.Add(LookupText, StringComparer.Ordinal);
combiner.Add(DirectiveText, StringComparer.Ordinal);
combiner.Add(TypePattern, StringComparer.Ordinal);
combiner.Add(AssemblyName, StringComparer.Ordinal);
return combiner.CombinedHash;
}

View File

@ -10,6 +10,11 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
{
internal class CSharpCodeParser : TokenizerBackedParser<CSharpTokenizer, CSharpSymbol, CSharpSymbolType>
{
private static HashSet<char> InvalidNonWhitespaceNameCharacters = new HashSet<char>(new[]
{
'@', '!', '<', '/', '?', '[', '>', ']', '=', '"', '\'', '*'
});
private static readonly Func<CSharpSymbol, bool> IsValidStatementSpacingSymbol =
IsSpacingToken(includeNewLines: true, includeComments: true);
@ -1905,22 +1910,158 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
errors.Add(duplicateDiagnostic);
}
return new TagHelperPrefixDirectiveChunkGenerator(prefix, errors);
var parsedDirective = ParseDirective(prefix, Span.Start, TagHelperDirectiveType.TagHelperPrefix, errors);
return new TagHelperPrefixDirectiveChunkGenerator(
prefix,
parsedDirective.DirectiveText,
errors);
});
}
// Internal for testing.
internal void ValidateTagHelperPrefix(
string prefix,
SourceLocation directiveLocation,
List<RazorDiagnostic> diagnostics)
{
foreach (var character in prefix)
{
// Prefixes are correlated with tag names, tag names cannot have whitespace.
if (char.IsWhiteSpace(character) || InvalidNonWhitespaceNameCharacters.Contains(character))
{
diagnostics.Add(
RazorDiagnostic.Create(
new RazorError(
Resources.FormatInvalidTagHelperPrefixValue(SyntaxConstants.CSharp.TagHelperPrefixKeyword, character, prefix),
directiveLocation,
prefix.Length)));
return;
}
}
}
private ParsedDirective ParseDirective(
string directiveText,
SourceLocation directiveLocation,
TagHelperDirectiveType directiveType,
List<RazorDiagnostic> errors)
{
var offset = 0;
directiveText = directiveText.Trim();
if (directiveText.Length >= 2 &&
directiveText.StartsWith("\"", StringComparison.Ordinal) &&
directiveText.EndsWith("\"", StringComparison.Ordinal))
{
directiveText = directiveText.Substring(1, directiveText.Length - 2);
if (string.IsNullOrEmpty(directiveText))
{
offset = 1;
}
}
// If this is the "string literal" form of a directive, we'll need to postprocess the location
// and content.
//
// Ex: @addTagHelper "*, Microsoft.AspNetCore.CoolLibrary"
// ^ ^
// Start End
if (Span.Symbols.Count == 1 && (Span.Symbols[0] as CSharpSymbol)?.Type == CSharpSymbolType.StringLiteral)
{
offset += Span.Symbols[0].Content.IndexOf(directiveText, StringComparison.Ordinal);
// This is safe because inside one of these directives all of the text needs to be on the
// same line.
var original = directiveLocation;
directiveLocation = new SourceLocation(
original.FilePath,
original.AbsoluteIndex + offset,
original.LineIndex,
original.CharacterIndex + offset);
}
var parsedDirective = new ParsedDirective()
{
DirectiveText = directiveText
};
if (directiveType == TagHelperDirectiveType.TagHelperPrefix)
{
ValidateTagHelperPrefix(parsedDirective.DirectiveText, directiveLocation, errors);
return parsedDirective;
}
return ParseAddOrRemoveDirective(parsedDirective, directiveLocation, errors);
}
// Internal for testing.
internal ParsedDirective ParseAddOrRemoveDirective(ParsedDirective directive, SourceLocation directiveLocation, List<RazorDiagnostic> errors)
{
var text = directive.DirectiveText;
var lookupStrings = text?.Split(new[] { ',' });
// Ensure that we have valid lookupStrings to work with. The valid format is "typeName, assemblyName"
if (lookupStrings == null ||
lookupStrings.Any(string.IsNullOrWhiteSpace) ||
lookupStrings.Length != 2)
{
errors.Add(
RazorDiagnostic.Create(
new RazorError(
Resources.FormatInvalidTagHelperLookupText(text),
directiveLocation,
Math.Max(text.Length, 1))));
return directive;
}
var trimmedAssemblyName = lookupStrings[1].Trim();
// + 1 is for the comma separator in the lookup text.
var assemblyNameIndex =
lookupStrings[0].Length + 1 + lookupStrings[1].IndexOf(trimmedAssemblyName, StringComparison.Ordinal);
var assemblyNamePrefix = directive.DirectiveText.Substring(0, assemblyNameIndex);
directive.TypePattern = lookupStrings[0].Trim();
directive.AssemblyName = trimmedAssemblyName;
return directive;
}
protected virtual void AddTagHelperDirective()
{
TagHelperDirective(
SyntaxConstants.CSharp.AddTagHelperKeyword,
(lookupText, errors) => new AddTagHelperChunkGenerator(lookupText, errors));
(lookupText, errors) =>
{
var parsedDirective = ParseDirective(lookupText, Span.Start, TagHelperDirectiveType.AddTagHelper, errors);
return new AddTagHelperChunkGenerator(
lookupText,
parsedDirective.DirectiveText,
parsedDirective.TypePattern,
parsedDirective.AssemblyName,
errors);
});
}
protected virtual void RemoveTagHelperDirective()
{
TagHelperDirective(
SyntaxConstants.CSharp.RemoveTagHelperKeyword,
(lookupText, errors) => new RemoveTagHelperChunkGenerator(lookupText, errors));
(lookupText, errors) =>
{
var parsedDirective = ParseDirective(lookupText, Span.Start, TagHelperDirectiveType.RemoveTagHelper, errors);
return new RemoveTagHelperChunkGenerator(
lookupText,
parsedDirective.DirectiveText,
parsedDirective.TypePattern,
parsedDirective.AssemblyName,
errors);
});
}
[Conditional("DEBUG")]
@ -2093,5 +2234,14 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
return sym.Content;
}
}
internal class ParsedDirective
{
public string DirectiveText { get; set; }
public string AssemblyName { get; set; }
public string TypePattern { get; set; }
}
}
}

View File

@ -10,14 +10,28 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
{
internal class RemoveTagHelperChunkGenerator : SpanChunkGenerator
{
public RemoveTagHelperChunkGenerator(string lookupText, List<RazorDiagnostic> diagnostics)
public RemoveTagHelperChunkGenerator(
string lookupText,
string directiveText,
string typePattern,
string assemblyName,
List<RazorDiagnostic> diagnostics)
{
LookupText = lookupText;
DirectiveText = directiveText;
TypePattern = typePattern;
AssemblyName = assemblyName;
Diagnostics = diagnostics;
}
public string LookupText { get; }
public string DirectiveText { get; set; }
public string TypePattern { get; set; }
public string AssemblyName { get; set; }
public List<RazorDiagnostic> Diagnostics { get; }
public override void Accept(ParserVisitor visitor, Span span)
@ -31,7 +45,10 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
var other = obj as RemoveTagHelperChunkGenerator;
return base.Equals(other) &&
Enumerable.SequenceEqual(Diagnostics, other.Diagnostics) &&
string.Equals(LookupText, other.LookupText, StringComparison.Ordinal);
string.Equals(LookupText, other.LookupText, StringComparison.Ordinal) &&
string.Equals(DirectiveText, other.DirectiveText, StringComparison.Ordinal) &&
string.Equals(TypePattern, other.TypePattern, StringComparison.Ordinal) &&
string.Equals(AssemblyName, other.AssemblyName, StringComparison.Ordinal);
}
/// <inheritdoc />
@ -40,6 +57,9 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
var combiner = HashCodeCombiner.Start();
combiner.Add(base.GetHashCode());
combiner.Add(LookupText, StringComparer.Ordinal);
combiner.Add(DirectiveText, StringComparer.Ordinal);
combiner.Add(TypePattern, StringComparer.Ordinal);
combiner.Add(AssemblyName, StringComparer.Ordinal);
return combiner.CombinedHash;
}

View File

@ -1,48 +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 System;
using System.Collections.Generic;
namespace Microsoft.AspNetCore.Razor.Language.Legacy
{
/// <summary>
/// Contains information needed to resolve <see cref="TagHelperDescriptor"/>s.
/// </summary>
internal class TagHelperDirectiveDescriptor
{
private string _directiveText;
/// <summary>
/// A <see cref="string"/> used to find tag helper <see cref="System.Type"/>s.
/// </summary>
public string DirectiveText
{
get
{
return _directiveText;
}
set
{
if (value == null)
{
throw new ArgumentNullException(nameof(value));
}
_directiveText = value;
}
}
/// <summary>
/// The <see cref="SourceLocation"/> of the directive.
/// </summary>
public SourceLocation Location { get; set; } = SourceLocation.Zero;
/// <summary>
/// The <see cref="TagHelperDirectiveType"/> of this directive.
/// </summary>
public TagHelperDirectiveType DirectiveType { get; set; }
public List<RazorDiagnostic> Diagnostics { get; set; }
}
}

View File

@ -10,14 +10,17 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
{
internal class TagHelperPrefixDirectiveChunkGenerator : SpanChunkGenerator
{
public TagHelperPrefixDirectiveChunkGenerator(string prefix, List<RazorDiagnostic> diagnostics)
public TagHelperPrefixDirectiveChunkGenerator(string prefix, string directiveText, List<RazorDiagnostic> diagnostics)
{
Prefix = prefix;
DirectiveText = directiveText;
Diagnostics = diagnostics;
}
public string Prefix { get; }
public string DirectiveText { get; }
public List<RazorDiagnostic> Diagnostics { get; }
public override void Accept(ParserVisitor visitor, Span span)
@ -31,7 +34,8 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
var other = obj as TagHelperPrefixDirectiveChunkGenerator;
return base.Equals(other) &&
Enumerable.SequenceEqual(Diagnostics, other.Diagnostics) &&
string.Equals(Prefix, other.Prefix, StringComparison.Ordinal);
string.Equals(Prefix, other.Prefix, StringComparison.Ordinal) &&
string.Equals(DirectiveText, other.DirectiveText, StringComparison.Ordinal);
}
/// <inheritdoc />
@ -40,6 +44,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
var combiner = HashCodeCombiner.Start();
combiner.Add(base.GetHashCode());
combiner.Add(Prefix, StringComparer.Ordinal);
combiner.Add(DirectiveText, StringComparer.Ordinal);
return combiner.CombinedHash;
}

View File

@ -350,7 +350,11 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
Factory.MetaCode(SyntaxConstants.CSharp.AddTagHelperKeyword + " ")
.Accepts(AcceptedCharactersInternal.None),
Factory.Code("\"*, Foo\"")
.AsAddTagHelper("\"*, Foo\"")));
.AsAddTagHelper(
"\"*, Foo\"",
"*, Foo",
"*",
"Foo")));
}
[Fact]
@ -370,7 +374,10 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
.Accepts(AcceptedCharactersInternal.None),
Factory.Code("\"*, Foo\"")
.AsAddTagHelper(
"\"*, Foo\"",
"\"*, Foo\"",
"*, Foo",
"*",
"Foo",
new RazorError(Resources.FormatDirectiveMustAppearAtStartOfLine("addTagHelper"), new SourceLocation(4, 0, 4), 12))),
Factory.Code(Environment.NewLine).AsStatement(),
Factory.MetaCode("}").Accepts(AcceptedCharactersInternal.None)));
@ -1009,7 +1016,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
.MetaCode(SyntaxConstants.CSharp.TagHelperPrefixKeyword + " ")
.Accepts(AcceptedCharactersInternal.None),
Factory.Code("\"\"")
.AsTagHelperPrefixDirective("\"\"")));
.AsTagHelperPrefixDirective("\"\"", string.Empty)));
}
[Fact]
@ -1022,7 +1029,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
.MetaCode(SyntaxConstants.CSharp.TagHelperPrefixKeyword + " ")
.Accepts(AcceptedCharactersInternal.None),
Factory.Code("Foo")
.AsTagHelperPrefixDirective("Foo")));
.AsTagHelperPrefixDirective("Foo", "Foo")));
}
[Fact]
@ -1035,7 +1042,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
.MetaCode(SyntaxConstants.CSharp.TagHelperPrefixKeyword + " ")
.Accepts(AcceptedCharactersInternal.None),
Factory.Code("\"Foo\"")
.AsTagHelperPrefixDirective("\"Foo\"")));
.AsTagHelperPrefixDirective("\"Foo\"", "Foo")));
}
[Fact]
@ -1054,7 +1061,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
.MetaCode(SyntaxConstants.CSharp.TagHelperPrefixKeyword + " ")
.Accepts(AcceptedCharactersInternal.None),
Factory.EmptyCSharp()
.AsTagHelperPrefixDirective(string.Empty, expectedError)
.AsTagHelperPrefixDirective(string.Empty, string.Empty, expectedError)
.Accepts(AcceptedCharactersInternal.AnyExceptNewline)));
}
@ -1069,6 +1076,9 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
absoluteIndex: 17, lineIndex: 0, columnIndex: 17, length: 1),
new RazorError(
LegacyResources.FormatParseError_IncompleteQuotesAroundDirective(SyntaxConstants.CSharp.TagHelperPrefixKeyword),
absoluteIndex: 17, lineIndex: 0, columnIndex: 17, length: 4),
new RazorError(
Resources.FormatInvalidTagHelperPrefixValue(SyntaxConstants.CSharp.TagHelperPrefixKeyword, '"', "\"Foo"),
absoluteIndex: 17, lineIndex: 0, columnIndex: 17, length: 4)
};
@ -1080,7 +1090,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
.MetaCode(SyntaxConstants.CSharp.TagHelperPrefixKeyword + " ")
.Accepts(AcceptedCharactersInternal.None),
Factory.Code("\"Foo")
.AsTagHelperPrefixDirective("\"Foo", expectedErrors)));
.AsTagHelperPrefixDirective("\"Foo", "\"Foo", expectedErrors)));
}
[Fact]
@ -1094,6 +1104,9 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
absoluteIndex: 23, lineIndex: 0, columnIndex: 23, length: 1),
new RazorError(
LegacyResources.FormatParseError_IncompleteQuotesAroundDirective(SyntaxConstants.CSharp.TagHelperPrefixKeyword),
absoluteIndex: 17, lineIndex: 0, columnIndex: 17, length: 7),
new RazorError(
Resources.FormatInvalidTagHelperPrefixValue(SyntaxConstants.CSharp.TagHelperPrefixKeyword, ' ', "Foo \""),
absoluteIndex: 17, lineIndex: 0, columnIndex: 17, length: 7)
};
@ -1105,43 +1118,76 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
.MetaCode(SyntaxConstants.CSharp.TagHelperPrefixKeyword + " ")
.Accepts(AcceptedCharactersInternal.None),
Factory.Code("Foo \"")
.AsTagHelperPrefixDirective("Foo \"", expectedErrors)));
.AsTagHelperPrefixDirective("Foo \"", "Foo \"", expectedErrors)));
}
[Fact]
public void RemoveTagHelperDirective_NoValue_Succeeds()
public void RemoveTagHelperDirective_NoValue_Invalid()
{
var expectedErrors = new[]
{
new RazorError(
Resources.FormatInvalidTagHelperLookupText(string.Empty),
new SourceLocation(18, 0, 18),
length: 1)
};
ParseBlockTest("@removeTagHelper \"\"",
new DirectiveBlock(
Factory.CodeTransition(),
Factory.MetaCode(SyntaxConstants.CSharp.RemoveTagHelperKeyword + " ")
.Accepts(AcceptedCharactersInternal.None),
Factory.Code("\"\"")
.AsRemoveTagHelper("\"\"")));
.AsRemoveTagHelper(
"\"\"",
string.Empty,
legacyErrors: expectedErrors)));
}
[Fact]
public void RemoveTagHelperDirective_Succeeds()
public void RemoveTagHelperDirective_InvalidLookupText_AddsError()
{
var expectedErrors = new[]
{
new RazorError(
Resources.FormatInvalidTagHelperLookupText("Foo"),
new SourceLocation(17, 0, 17),
length: 3)
};
ParseBlockTest("@removeTagHelper Foo",
new DirectiveBlock(
Factory.CodeTransition(),
Factory.MetaCode(SyntaxConstants.CSharp.RemoveTagHelperKeyword + " ")
.Accepts(AcceptedCharactersInternal.None),
Factory.Code("Foo")
.AsRemoveTagHelper("Foo")));
.AsRemoveTagHelper(
"Foo",
"Foo",
legacyErrors: expectedErrors)));
}
[Fact]
public void RemoveTagHelperDirective_WithQuotes_Succeeds()
public void RemoveTagHelperDirective_WithQuotes_InvalidLookupText_AddsError()
{
var expectedErrors = new[]
{
new RazorError(
Resources.FormatInvalidTagHelperLookupText("Foo"),
new SourceLocation(18, 0, 18),
length: 3)
};
ParseBlockTest("@removeTagHelper \"Foo\"",
new DirectiveBlock(
Factory.CodeTransition(),
Factory.MetaCode(SyntaxConstants.CSharp.RemoveTagHelperKeyword + " ")
.Accepts(AcceptedCharactersInternal.None),
Factory.Code("\"Foo\"")
.AsRemoveTagHelper("\"Foo\"")));
.AsRemoveTagHelper(
"\"Foo\"",
"Foo",
legacyErrors: expectedErrors)));
}
[Fact]
@ -1153,7 +1199,11 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
Factory.MetaCode(SyntaxConstants.CSharp.RemoveTagHelperKeyword + " ")
.Accepts(AcceptedCharactersInternal.None),
Factory.Code("Foo, Bar ")
.AsRemoveTagHelper("Foo, Bar")
.AsRemoveTagHelper(
"Foo, Bar",
"Foo, Bar",
"Foo",
"Bar")
.Accepts(AcceptedCharactersInternal.AnyExceptNewline)));
}
@ -1161,9 +1211,16 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
public void RemoveTagHelperDirective_RequiresValue()
{
// Arrange
var expectedError = new RazorError(
LegacyResources.FormatParseError_DirectiveMustHaveValue(SyntaxConstants.CSharp.RemoveTagHelperKeyword),
absoluteIndex: 1, lineIndex: 0, columnIndex: 1, length: 15);
var expectedErrors = new[]
{
new RazorError(
LegacyResources.FormatParseError_DirectiveMustHaveValue(SyntaxConstants.CSharp.RemoveTagHelperKeyword),
absoluteIndex: 1, lineIndex: 0, columnIndex: 1, length: 15),
new RazorError(
Resources.FormatInvalidTagHelperLookupText(string.Empty),
new SourceLocation(17, 0, 17),
length: 1),
};
// Act & Assert
ParseBlockTest("@removeTagHelper ",
@ -1172,7 +1229,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
Factory.MetaCode(SyntaxConstants.CSharp.RemoveTagHelperKeyword + " ")
.Accepts(AcceptedCharactersInternal.None),
Factory.EmptyCSharp()
.AsRemoveTagHelper(string.Empty, expectedError)
.AsRemoveTagHelper(string.Empty, string.Empty, legacyErrors: expectedErrors)
.Accepts(AcceptedCharactersInternal.AnyExceptNewline)));
}
@ -1187,7 +1244,11 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
absoluteIndex: 17, lineIndex: 0, columnIndex: 17, length: 1),
new RazorError(
LegacyResources.FormatParseError_IncompleteQuotesAroundDirective(SyntaxConstants.CSharp.RemoveTagHelperKeyword),
absoluteIndex: 17, lineIndex: 0, columnIndex: 17, length: 4)
absoluteIndex: 17, lineIndex: 0, columnIndex: 17, length: 4),
new RazorError(
Resources.FormatInvalidTagHelperLookupText("\"Foo"),
new SourceLocation(17, 0, 17),
length: 4),
};
// Act & Assert
@ -1197,7 +1258,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
Factory.MetaCode(SyntaxConstants.CSharp.RemoveTagHelperKeyword + " ")
.Accepts(AcceptedCharactersInternal.None),
Factory.Code("\"Foo")
.AsRemoveTagHelper("\"Foo", expectedErrors)));
.AsRemoveTagHelper("\"Foo", "\"Foo", legacyErrors: expectedErrors)));
}
[Fact]
@ -1211,7 +1272,11 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
absoluteIndex: 20, lineIndex: 0, columnIndex: 20, length: 1),
new RazorError(
LegacyResources.FormatParseError_IncompleteQuotesAroundDirective(SyntaxConstants.CSharp.RemoveTagHelperKeyword),
absoluteIndex: 17, lineIndex: 0, columnIndex: 17, length: 4)
absoluteIndex: 17, lineIndex: 0, columnIndex: 17, length: 4),
new RazorError(
Resources.FormatInvalidTagHelperLookupText("Foo\""),
new SourceLocation(17, 0, 17),
length: 4),
};
// Act & Assert
@ -1221,48 +1286,81 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
Factory.MetaCode(SyntaxConstants.CSharp.RemoveTagHelperKeyword + " ")
.Accepts(AcceptedCharactersInternal.None),
Factory.Code("Foo\"")
.AsRemoveTagHelper("Foo\"", expectedErrors)
.AsRemoveTagHelper("Foo\"", "Foo\"", legacyErrors: expectedErrors)
.Accepts(AcceptedCharactersInternal.AnyExceptNewline)));
}
[Fact]
public void AddTagHelperDirective_NoValue_Succeeds()
public void AddTagHelperDirective_NoValue_Invalid()
{
var expectedErrors = new[]
{
new RazorError(
Resources.FormatInvalidTagHelperLookupText(string.Empty),
new SourceLocation(15, 0, 15),
length: 1)
};
ParseBlockTest("@addTagHelper \"\"",
new DirectiveBlock(
Factory.CodeTransition(),
Factory.MetaCode(SyntaxConstants.CSharp.AddTagHelperKeyword + " ")
.Accepts(AcceptedCharactersInternal.None),
Factory.Code("\"\"")
.AsAddTagHelper("\"\"")));
.AsAddTagHelper(
"\"\"",
string.Empty,
legacyErrors: expectedErrors)));
}
[Fact]
public void AddTagHelperDirective_Succeeds()
public void AddTagHelperDirective_InvalidLookupText_AddsError()
{
var expectedErrors = new[]
{
new RazorError(
Resources.FormatInvalidTagHelperLookupText("Foo"),
new SourceLocation(14, 0, 14),
length: 3)
};
ParseBlockTest("@addTagHelper Foo",
new DirectiveBlock(
Factory.CodeTransition(),
Factory.MetaCode(SyntaxConstants.CSharp.AddTagHelperKeyword + " ")
.Accepts(AcceptedCharactersInternal.None),
Factory.Code("Foo")
.AsAddTagHelper("Foo")));
.AsAddTagHelper(
"Foo",
"Foo",
legacyErrors: expectedErrors)));
}
[Fact]
public void AddTagHelperDirective_WithQuotes_Succeeds()
public void AddTagHelperDirective_WithQuotes_InvalidLookupText_AddsError()
{
var expectedErrors = new[]
{
new RazorError(
Resources.FormatInvalidTagHelperLookupText("Foo"),
new SourceLocation(15, 0, 15),
length: 3)
};
ParseBlockTest("@addTagHelper \"Foo\"",
new DirectiveBlock(
Factory.CodeTransition(),
Factory.MetaCode(SyntaxConstants.CSharp.AddTagHelperKeyword + " ")
.Accepts(AcceptedCharactersInternal.None),
Factory.Code("\"Foo\"")
.AsAddTagHelper("\"Foo\"")));
.AsAddTagHelper(
"\"Foo\"",
"Foo",
legacyErrors: expectedErrors)));
}
[Fact]
public void AddTagHelperDirectiveSupportsSpaces()
public void AddTagHelperDirective_SupportsSpaces()
{
ParseBlockTest("@addTagHelper Foo, Bar ",
new DirectiveBlock(
@ -1270,16 +1368,28 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
Factory.MetaCode(SyntaxConstants.CSharp.AddTagHelperKeyword + " ")
.Accepts(AcceptedCharactersInternal.None),
Factory.Code("Foo, Bar ")
.AsAddTagHelper("Foo, Bar")));
.AsAddTagHelper(
"Foo, Bar",
"Foo, Bar",
"Foo",
"Bar")
.Accepts(AcceptedCharactersInternal.AnyExceptNewline)));
}
[Fact]
public void AddTagHelperDirectiveRequiresValue()
public void AddTagHelperDirective_RequiresValue()
{
// Arrange
var expectedError = new RazorError(
LegacyResources.FormatParseError_DirectiveMustHaveValue(SyntaxConstants.CSharp.AddTagHelperKeyword),
absoluteIndex: 1, lineIndex: 0, columnIndex: 1, length: 12);
var expectedErrors = new[]
{
new RazorError(
LegacyResources.FormatParseError_DirectiveMustHaveValue(SyntaxConstants.CSharp.AddTagHelperKeyword),
absoluteIndex: 1, lineIndex: 0, columnIndex: 1, length: 12),
new RazorError(
Resources.FormatInvalidTagHelperLookupText(string.Empty),
new SourceLocation(14, 0, 14),
length: 1),
};
// Act & Assert
ParseBlockTest("@addTagHelper ",
@ -1288,7 +1398,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
Factory.MetaCode(SyntaxConstants.CSharp.AddTagHelperKeyword + " ")
.Accepts(AcceptedCharactersInternal.None),
Factory.EmptyCSharp()
.AsAddTagHelper(string.Empty, expectedError)
.AsAddTagHelper(string.Empty, string.Empty, legacyErrors: expectedErrors)
.Accepts(AcceptedCharactersInternal.AnyExceptNewline)));
}
@ -1303,7 +1413,11 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
absoluteIndex: 14, lineIndex: 0, columnIndex: 14, length: 1),
new RazorError(
LegacyResources.FormatParseError_IncompleteQuotesAroundDirective(SyntaxConstants.CSharp.AddTagHelperKeyword),
absoluteIndex: 14, lineIndex: 0, columnIndex: 14, length: 4)
absoluteIndex: 14, lineIndex: 0, columnIndex: 14, length: 4),
new RazorError(
Resources.FormatInvalidTagHelperLookupText("\"Foo"),
new SourceLocation(14, 0, 14),
length: 4),
};
// Act & Assert
@ -1313,7 +1427,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
Factory.MetaCode(SyntaxConstants.CSharp.AddTagHelperKeyword + " ")
.Accepts(AcceptedCharactersInternal.None),
Factory.Code("\"Foo")
.AsAddTagHelper("\"Foo", expectedErrors)));
.AsAddTagHelper("\"Foo", "\"Foo", legacyErrors: expectedErrors)));
}
[Fact]
@ -1327,7 +1441,11 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
absoluteIndex: 17, lineIndex: 0, columnIndex: 17, length: 1),
new RazorError(
LegacyResources.FormatParseError_IncompleteQuotesAroundDirective(SyntaxConstants.CSharp.AddTagHelperKeyword),
absoluteIndex: 14, lineIndex: 0, columnIndex: 14, length: 4)
absoluteIndex: 14, lineIndex: 0, columnIndex: 14, length: 4),
new RazorError(
Resources.FormatInvalidTagHelperLookupText("Foo\""),
new SourceLocation(14, 0, 14),
length: 4),
};
// Act & Assert
@ -1337,7 +1455,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
Factory.MetaCode(SyntaxConstants.CSharp.AddTagHelperKeyword + " ")
.Accepts(AcceptedCharactersInternal.None),
Factory.Code("Foo\"")
.AsAddTagHelper("Foo\"", expectedErrors)
.AsAddTagHelper("Foo\"", "Foo\"", legacyErrors: expectedErrors)
.Accepts(AcceptedCharactersInternal.AnyExceptNewline)));
}
@ -1611,6 +1729,245 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
Factory.MetaCode("namespace").Accepts(AcceptedCharactersInternal.None)));
}
public static TheoryData InvalidTagHelperPrefixData
{
get
{
var directiveLocation = new SourceLocation(1, 2, 3);
var invalidTagHelperPrefixValueError =
"Invalid tag helper directive '{0}' value. '{1}' is not allowed in prefix '{2}'.";
return new TheoryData<string, SourceLocation, IEnumerable<RazorError>>
{
{
"th ",
directiveLocation,
new[]
{
new RazorError(
string.Format(
invalidTagHelperPrefixValueError,
SyntaxConstants.CSharp.TagHelperPrefixKeyword,
' ',
"th "),
directiveLocation,
length: 3)
}
},
{
"th\t",
directiveLocation,
new[]
{
new RazorError(
string.Format(
invalidTagHelperPrefixValueError,
SyntaxConstants.CSharp.TagHelperPrefixKeyword,
'\t',
"th\t"),
directiveLocation,
length: 3)
}
},
{
"th" + Environment.NewLine,
directiveLocation,
new[]
{
new RazorError(
string.Format(
invalidTagHelperPrefixValueError,
SyntaxConstants.CSharp.TagHelperPrefixKeyword,
Environment.NewLine[0],
"th" + Environment.NewLine),
directiveLocation,
length: 2 + Environment.NewLine.Length)
}
},
{
" th ",
directiveLocation,
new[]
{
new RazorError(
string.Format(
invalidTagHelperPrefixValueError,
SyntaxConstants.CSharp.TagHelperPrefixKeyword,
' ',
" th "),
directiveLocation,
length: 4)
}
},
{
"@",
directiveLocation,
new[]
{
new RazorError(
string.Format(
invalidTagHelperPrefixValueError,
SyntaxConstants.CSharp.TagHelperPrefixKeyword,
'@',
"@"),
directiveLocation,
length: 1)
}
},
{
"t@h",
directiveLocation,
new[]
{
new RazorError(
string.Format(
invalidTagHelperPrefixValueError,
SyntaxConstants.CSharp.TagHelperPrefixKeyword,
'@',
"t@h"),
directiveLocation,
length: 3)
}
},
{
"!",
directiveLocation,
new[]
{
new RazorError(
string.Format(
invalidTagHelperPrefixValueError,
SyntaxConstants.CSharp.TagHelperPrefixKeyword,
'!',
"!"),
directiveLocation,
length: 1)
}
},
{
"!th",
directiveLocation,
new[]
{
new RazorError(
string.Format(
invalidTagHelperPrefixValueError,
SyntaxConstants.CSharp.TagHelperPrefixKeyword,
'!',
"!th"),
directiveLocation,
length: 3)
}
},
};
}
}
[Theory]
[MemberData(nameof(InvalidTagHelperPrefixData))]
public void ValidateTagHelperPrefix_ValidatesPrefix(
string directiveText,
SourceLocation directiveLocation,
object expectedErrors)
{
// Arrange
var expectedDiagnostics = ((IEnumerable<RazorError>)expectedErrors).Select(RazorDiagnostic.Create);
var source = TestRazorSourceDocument.Create();
var options = RazorParserOptions.CreateDefault();
var context = new ParserContext(source, options);
var parser = new CSharpCodeParser(context);
var diagnostics = new List<RazorDiagnostic>();
// Act
parser.ValidateTagHelperPrefix(directiveText, directiveLocation, diagnostics);
// Assert
Assert.Equal(expectedDiagnostics, diagnostics);
}
[Theory]
[InlineData("foo,assemblyName", 4)]
[InlineData("foo, assemblyName", 5)]
[InlineData(" foo, assemblyName", 8)]
[InlineData(" foo , assemblyName", 11)]
[InlineData("foo, assemblyName", 8)]
[InlineData(" foo , assemblyName ", 14)]
public void ParseAddOrRemoveDirective_CalculatesAssemblyLocationInLookupText(string text, int assemblyLocation)
{
// Arrange
var source = TestRazorSourceDocument.Create();
var options = RazorParserOptions.CreateDefault();
var context = new ParserContext(source, options);
var parser = new CSharpCodeParser(context);
var directive = new CSharpCodeParser.ParsedDirective()
{
DirectiveText = text,
};
var diagnostics = new List<RazorDiagnostic>();
var expected = new SourceLocation(assemblyLocation, 0, assemblyLocation);
// Act
var result = parser.ParseAddOrRemoveDirective(directive, SourceLocation.Zero, diagnostics);
// Assert
Assert.Empty(diagnostics);
Assert.Equal("foo", result.TypePattern);
Assert.Equal("assemblyName", result.AssemblyName);
}
[Theory]
[InlineData("", 1)]
[InlineData("*,", 2)]
[InlineData("?,", 2)]
[InlineData(",", 1)]
[InlineData(",,,", 3)]
[InlineData("First, ", 7)]
[InlineData("First , ", 8)]
[InlineData(" ,Second", 8)]
[InlineData(" , Second", 9)]
[InlineData("SomeType,", 9)]
[InlineData("SomeAssembly", 12)]
[InlineData("First,Second,Third", 18)]
public void ParseAddOrRemoveDirective_CreatesErrorIfInvalidLookupText_DoesNotThrow(string directiveText, int errorLength)
{
// Arrange
var source = TestRazorSourceDocument.Create();
var options = RazorParserOptions.CreateDefault();
var context = new ParserContext(source, options);
var parser = new CSharpCodeParser(context);
var expectedErrorMessage = string.Format(
"Invalid tag helper directive look up text '{0}'. The correct look up text " +
"format is: \"name, assemblyName\".",
directiveText);
var directive = new CSharpCodeParser.ParsedDirective()
{
DirectiveText = directiveText
};
var diagnostics = new List<RazorDiagnostic>();
var expectedError = RazorDiagnostic.Create(
new RazorError(
expectedErrorMessage,
new SourceLocation(1, 2, 3),
errorLength));
// Act
var result = parser.ParseAddOrRemoveDirective(directive, new SourceLocation(1, 2, 3), diagnostics);
// Assert
Assert.Same(directive, result);
var error = Assert.Single(diagnostics);
Assert.Equal(expectedError, error);
}
internal virtual void ParseCodeBlockTest(
string document,

View File

@ -1,14 +1,21 @@
TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml(3,2): Error RZ9999: Directive 'addTagHelper' must have a value.
TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml(3,14): Error RZ9999: Invalid tag helper directive look up text ''. The correct look up text format is: "name, assemblyName".
TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml(4,2): Error RZ9999: Directive 'addTagHelper' must have a value.
TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml(4,15): Error RZ9999: Invalid tag helper directive look up text ''. The correct look up text format is: "name, assemblyName".
TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml(5,15): Error RZ9999: Unterminated string literal. Strings that start with a quotation mark (") must be terminated before the end of the line. However, strings that start with @ and a quotation mark (@") can span multiple lines.
TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml(5,15): Error RZ9999: Invalid tag helper directive look up text '"'. The correct look up text format is: "name, assemblyName".
TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml(7,2): Error RZ9999: Directive 'removeTagHelper' must have a value.
TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml(7,17): Error RZ9999: Invalid tag helper directive look up text ''. The correct look up text format is: "name, assemblyName".
TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml(8,2): Error RZ9999: Directive 'removeTagHelper' must have a value.
TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml(8,18): Error RZ9999: Invalid tag helper directive look up text ''. The correct look up text format is: "name, assemblyName".
TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml(9,18): Error RZ9999: Unterminated string literal. Strings that start with a quotation mark (") must be terminated before the end of the line. However, strings that start with @ and a quotation mark (@") can span multiple lines.
TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml(9,18): Error RZ9999: Invalid tag helper directive look up text '"'. The correct look up text format is: "name, assemblyName".
TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml(11,2): Error RZ9999: Directive 'tagHelperPrefix' must have a value.
TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml(12,2): Error RZ9999: Directive 'tagHelperPrefix' must have a value.
TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml(12,1): Error RZ2001: The 'tagHelperPrefix' directive may only occur once per document.
TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml(13,18): Error RZ9999: Unterminated string literal. Strings that start with a quotation mark (") must be terminated before the end of the line. However, strings that start with @ and a quotation mark (@") can span multiple lines.
TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml(13,1): Error RZ2001: The 'tagHelperPrefix' directive may only occur once per document.
TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml(13,18): Error RZ9999: Invalid tag helper directive 'tagHelperPrefix' value. '"' is not allowed in prefix '"'.
TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml(15,10): Error RZ9999: The 'inherits' directive expects a type name.
TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml(16,1): Error RZ9999: The 'inherits' directive may only occur once per document.
TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml(16,11): Error RZ9999: The 'inherits' directive expects a type name.

View File

@ -1,14 +1,21 @@
TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml(3,2): Error RZ9999: Directive 'addTagHelper' must have a value.
TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml(3,14): Error RZ9999: Invalid tag helper directive look up text ''. The correct look up text format is: "name, assemblyName".
TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml(4,2): Error RZ9999: Directive 'addTagHelper' must have a value.
TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml(4,15): Error RZ9999: Invalid tag helper directive look up text ''. The correct look up text format is: "name, assemblyName".
TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml(5,15): Error RZ9999: Unterminated string literal. Strings that start with a quotation mark (") must be terminated before the end of the line. However, strings that start with @ and a quotation mark (@") can span multiple lines.
TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml(5,15): Error RZ9999: Invalid tag helper directive look up text '"'. The correct look up text format is: "name, assemblyName".
TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml(7,2): Error RZ9999: Directive 'removeTagHelper' must have a value.
TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml(7,17): Error RZ9999: Invalid tag helper directive look up text ''. The correct look up text format is: "name, assemblyName".
TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml(8,2): Error RZ9999: Directive 'removeTagHelper' must have a value.
TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml(8,18): Error RZ9999: Invalid tag helper directive look up text ''. The correct look up text format is: "name, assemblyName".
TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml(9,18): Error RZ9999: Unterminated string literal. Strings that start with a quotation mark (") must be terminated before the end of the line. However, strings that start with @ and a quotation mark (@") can span multiple lines.
TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml(9,18): Error RZ9999: Invalid tag helper directive look up text '"'. The correct look up text format is: "name, assemblyName".
TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml(11,2): Error RZ9999: Directive 'tagHelperPrefix' must have a value.
TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml(12,2): Error RZ9999: Directive 'tagHelperPrefix' must have a value.
TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml(12,1): Error RZ2001: The 'tagHelperPrefix' directive may only occur once per document.
TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml(13,18): Error RZ9999: Unterminated string literal. Strings that start with a quotation mark (") must be terminated before the end of the line. However, strings that start with @ and a quotation mark (@") can span multiple lines.
TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml(13,1): Error RZ2001: The 'tagHelperPrefix' directive may only occur once per document.
TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml(13,18): Error RZ9999: Invalid tag helper directive 'tagHelperPrefix' value. '"' is not allowed in prefix '"'.
TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml(15,10): Error RZ9999: The 'inherits' directive expects a type name.
TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml(16,1): Error RZ9999: The 'inherits' directive may only occur once per document.
TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml(16,11): Error RZ9999: The 'inherits' directive expects a type name.

View File

@ -334,27 +334,37 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
return _self.With(SpanChunkGenerator.Null);
}
public SpanConstructor AsAddTagHelper(string lookupText, params RazorError[] legacyErrors)
public SpanConstructor AsAddTagHelper(
string lookupText,
string directiveText,
string typePattern = null,
string assemblyName = null,
params RazorError[] legacyErrors)
{
var diagnostics = legacyErrors.Select(error => RazorDiagnostic.Create(error)).ToList();
return _self
.With(new AddTagHelperChunkGenerator(lookupText, diagnostics))
.With(new AddTagHelperChunkGenerator(lookupText, directiveText, typePattern, assemblyName, diagnostics))
.Accepts(AcceptedCharactersInternal.AnyExceptNewline);
}
public SpanConstructor AsRemoveTagHelper(string lookupText, params RazorError[] legacyErrors)
public SpanConstructor AsRemoveTagHelper(
string lookupText,
string directiveText,
string typePattern = null,
string assemblyName = null,
params RazorError[] legacyErrors)
{
var diagnostics = legacyErrors.Select(error => RazorDiagnostic.Create(error)).ToList();
return _self
.With(new RemoveTagHelperChunkGenerator(lookupText, diagnostics))
.With(new RemoveTagHelperChunkGenerator(lookupText, directiveText, typePattern, assemblyName, diagnostics))
.Accepts(AcceptedCharactersInternal.AnyExceptNewline);
}
public SpanConstructor AsTagHelperPrefixDirective(string prefix, params RazorError[] legacyErrors)
public SpanConstructor AsTagHelperPrefixDirective(string prefix, string directiveText, params RazorError[] legacyErrors)
{
var diagnostics = legacyErrors.Select(error => RazorDiagnostic.Create(error)).ToList();
return _self
.With(new TagHelperPrefixDirectiveChunkGenerator(prefix, diagnostics))
.With(new TagHelperPrefixDirectiveChunkGenerator(prefix, directiveText, diagnostics))
.Accepts(AcceptedCharactersInternal.AnyExceptNewline);
}