From fa6fde2b202e94f6daad198d156212d7efbca729 Mon Sep 17 00:00:00 2001 From: Ajay Bhargav Baaskaran Date: Mon, 21 Aug 2017 16:39:43 -0700 Subject: [PATCH] Moved TagHelper directive validation and parsing from TagHelperBinder to CSharpCodeParser --- .../DefaultRazorTagHelperBinderPhase.cs | 326 ++----- .../Legacy/AddTagHelperChunkGenerator.cs | 24 +- .../Legacy/CSharpCodeParser.cs | 156 +++- .../Legacy/RemoveTagHelperChunkGenerator.cs | 24 +- .../Legacy/TagHelperDirectiveDescriptor.cs | 48 -- .../TagHelperPrefixDirectiveChunkGenerator.cs | 9 +- .../DefaultRazorTagHelperBinderPhaseTest.cs | 793 ++++-------------- .../Legacy/CSharpDirectivesTest.cs | 437 +++++++++- ...pleteDirectives_DesignTime.diagnostics.txt | 7 + ...completeDirectives_Runtime.diagnostics.txt | 7 + .../Language/Legacy/TestSpanBuilder.cs | 22 +- 11 files changed, 864 insertions(+), 989 deletions(-) delete mode 100644 src/Microsoft.AspNetCore.Razor.Language/Legacy/TagHelperDirectiveDescriptor.cs diff --git a/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorTagHelperBinderPhase.cs b/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorTagHelperBinderPhase.cs index 41c4385d41..fe2d3649ed 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorTagHelperBinderPhase.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorTagHelperBinderPhase.cs @@ -10,11 +10,6 @@ namespace Microsoft.AspNetCore.Razor.Language { internal class DefaultRazorTagHelperBinderPhase : RazorEnginePhaseBase, IRazorTagHelperBinderPhase { - private static HashSet InvalidNonWhitespaceNameCharacters = new HashSet(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(); - 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(); 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 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 ProcessDirectives( - IReadOnlyList directives, - IReadOnlyList tagHelpers) - { - var matches = new HashSet(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 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 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 _tagHelpers; - public string TypePattern { get; set; } + public DirectiveVisitor(IReadOnlyList tagHelpers) + { + _tagHelpers = tagHelpers; + } - public SourceLocation AssemblyNameLocation { get; set; } - } + public string TagHelperPrefix { get; private set; } - private class DirectiveVisitor : ParserVisitor - { - public List Directives { get; } = new List(); + public HashSet Matches { get; } = new HashSet(); 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 diagnostics) + private bool AssemblyContainsTagHelpers(string assemblyName, IReadOnlyList 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; } } } diff --git a/src/Microsoft.AspNetCore.Razor.Language/Legacy/AddTagHelperChunkGenerator.cs b/src/Microsoft.AspNetCore.Razor.Language/Legacy/AddTagHelperChunkGenerator.cs index 92a43c2d50..8c9d98633b 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/Legacy/AddTagHelperChunkGenerator.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/Legacy/AddTagHelperChunkGenerator.cs @@ -10,14 +10,28 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy { internal class AddTagHelperChunkGenerator : SpanChunkGenerator { - public AddTagHelperChunkGenerator(string lookupText, List diagnostics) + public AddTagHelperChunkGenerator( + string lookupText, + string directiveText, + string typePattern, + string assemblyName, + List 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 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); } /// @@ -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; } diff --git a/src/Microsoft.AspNetCore.Razor.Language/Legacy/CSharpCodeParser.cs b/src/Microsoft.AspNetCore.Razor.Language/Legacy/CSharpCodeParser.cs index 5c961c4b93..2ae5a8f47e 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/Legacy/CSharpCodeParser.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/Legacy/CSharpCodeParser.cs @@ -10,6 +10,11 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy { internal class CSharpCodeParser : TokenizerBackedParser { + private static HashSet InvalidNonWhitespaceNameCharacters = new HashSet(new[] + { + '@', '!', '<', '/', '?', '[', '>', ']', '=', '"', '\'', '*' + }); + private static readonly Func 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 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 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 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; } + } } } diff --git a/src/Microsoft.AspNetCore.Razor.Language/Legacy/RemoveTagHelperChunkGenerator.cs b/src/Microsoft.AspNetCore.Razor.Language/Legacy/RemoveTagHelperChunkGenerator.cs index 21bc2978e9..09328bb69c 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/Legacy/RemoveTagHelperChunkGenerator.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/Legacy/RemoveTagHelperChunkGenerator.cs @@ -10,14 +10,28 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy { internal class RemoveTagHelperChunkGenerator : SpanChunkGenerator { - public RemoveTagHelperChunkGenerator(string lookupText, List diagnostics) + public RemoveTagHelperChunkGenerator( + string lookupText, + string directiveText, + string typePattern, + string assemblyName, + List 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 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); } /// @@ -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; } diff --git a/src/Microsoft.AspNetCore.Razor.Language/Legacy/TagHelperDirectiveDescriptor.cs b/src/Microsoft.AspNetCore.Razor.Language/Legacy/TagHelperDirectiveDescriptor.cs deleted file mode 100644 index d5cb428fe3..0000000000 --- a/src/Microsoft.AspNetCore.Razor.Language/Legacy/TagHelperDirectiveDescriptor.cs +++ /dev/null @@ -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 -{ - /// - /// Contains information needed to resolve s. - /// - internal class TagHelperDirectiveDescriptor - { - private string _directiveText; - - /// - /// A used to find tag helper s. - /// - public string DirectiveText - { - get - { - return _directiveText; - } - set - { - if (value == null) - { - throw new ArgumentNullException(nameof(value)); - } - - _directiveText = value; - } - } - - /// - /// The of the directive. - /// - public SourceLocation Location { get; set; } = SourceLocation.Zero; - - /// - /// The of this directive. - /// - public TagHelperDirectiveType DirectiveType { get; set; } - - public List Diagnostics { get; set; } - } -} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Razor.Language/Legacy/TagHelperPrefixDirectiveChunkGenerator.cs b/src/Microsoft.AspNetCore.Razor.Language/Legacy/TagHelperPrefixDirectiveChunkGenerator.cs index fae8d93ed8..bed1cadcc7 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/Legacy/TagHelperPrefixDirectiveChunkGenerator.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/Legacy/TagHelperPrefixDirectiveChunkGenerator.cs @@ -10,14 +10,17 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy { internal class TagHelperPrefixDirectiveChunkGenerator : SpanChunkGenerator { - public TagHelperPrefixDirectiveChunkGenerator(string prefix, List diagnostics) + public TagHelperPrefixDirectiveChunkGenerator(string prefix, string directiveText, List diagnostics) { Prefix = prefix; + DirectiveText = directiveText; Diagnostics = diagnostics; } public string Prefix { get; } + public string DirectiveText { get; } + public List 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); } /// @@ -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; } diff --git a/test/Microsoft.AspNetCore.Razor.Language.Test/DefaultRazorTagHelperBinderPhaseTest.cs b/test/Microsoft.AspNetCore.Razor.Language.Test/DefaultRazorTagHelperBinderPhaseTest.cs index 281c977fb8..968c12bde7 100644 --- a/test/Microsoft.AspNetCore.Razor.Language.Test/DefaultRazorTagHelperBinderPhaseTest.cs +++ b/test/Microsoft.AspNetCore.Razor.Language.Test/DefaultRazorTagHelperBinderPhaseTest.cs @@ -39,7 +39,6 @@ namespace Microsoft.AspNetCore.Razor.Language length: 1)) }; - var content = @" @addTagHelper """; @@ -85,7 +84,6 @@ namespace Microsoft.AspNetCore.Razor.Language length: 1)) }; - var content = @" @removeTagHelper """; @@ -131,7 +129,6 @@ namespace Microsoft.AspNetCore.Razor.Language length: 1)) }; - var content = @" @tagHelperPrefix """; @@ -459,259 +456,6 @@ namespace Microsoft.AspNetCore.Razor.Language Assert.Equal(new[] { initialError, expectedRewritingError }, outputTree.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 phase = new DefaultRazorTagHelperBinderPhase(); - - var directive = new TagHelperDirectiveDescriptor() - { - DirectiveText = text, - DirectiveType = TagHelperDirectiveType.AddTagHelper, - Location = SourceLocation.Zero, - Diagnostics = new List(), - }; - - var expected = new SourceLocation(assemblyLocation, 0, assemblyLocation); - - // Act - var result = phase.ParseAddOrRemoveDirective(directive); - - // Assert - Assert.Empty(directive.Diagnostics); - Assert.Equal("foo", result.TypePattern); - Assert.Equal("assemblyName", result.AssemblyName); - Assert.Equal(expected, result.AssemblyNameLocation); - } - - public static TheoryData InvalidTagHelperPrefixData - { - get - { - var directiveLocation1 = new SourceLocation(1, 2, 3); - var directiveLocation2 = new SourceLocation(4, 5, 6); - - var invalidTagHelperPrefixValueError = - "Invalid tag helper directive '{0}' value. '{1}' is not allowed in prefix '{2}'."; - - return new TheoryData, IEnumerable> - { - { - new[] - { - new TagHelperDirectiveDescriptor - { - DirectiveText = "th ", - Location = directiveLocation1, - DirectiveType = TagHelperDirectiveType.TagHelperPrefix, - Diagnostics = new List(), - }, - }, - new[] - { - new RazorError( - string.Format( - invalidTagHelperPrefixValueError, - SyntaxConstants.CSharp.TagHelperPrefixKeyword, - ' ', - "th "), - directiveLocation1, - length: 3) - } - }, - { - new[] - { - new TagHelperDirectiveDescriptor - { - DirectiveText = "th\t", - Location = directiveLocation1, - DirectiveType = TagHelperDirectiveType.TagHelperPrefix, - Diagnostics = new List(), - } - }, - new[] - { - new RazorError( - string.Format( - invalidTagHelperPrefixValueError, - SyntaxConstants.CSharp.TagHelperPrefixKeyword, - '\t', - "th\t"), - directiveLocation1, - length: 3) - } - }, - { - new[] - { - new TagHelperDirectiveDescriptor - { - DirectiveText = "th" + Environment.NewLine, - Location = directiveLocation1, - DirectiveType = TagHelperDirectiveType.TagHelperPrefix, - Diagnostics = new List(), - } - }, - new[] - { - new RazorError( - string.Format( - invalidTagHelperPrefixValueError, - SyntaxConstants.CSharp.TagHelperPrefixKeyword, - Environment.NewLine[0], - "th" + Environment.NewLine), - directiveLocation1, - length: 2 + Environment.NewLine.Length) - } - }, - { - new[] - { - new TagHelperDirectiveDescriptor - { - DirectiveText = " th ", - Location = directiveLocation1, - DirectiveType = TagHelperDirectiveType.TagHelperPrefix, - Diagnostics = new List(), - } - }, - new[] - { - new RazorError( - string.Format( - invalidTagHelperPrefixValueError, - SyntaxConstants.CSharp.TagHelperPrefixKeyword, - ' ', - " th "), - directiveLocation1, - length: 4) - } - }, - { - new[] - { - new TagHelperDirectiveDescriptor - { - DirectiveText = "@", - Location = directiveLocation1, - DirectiveType = TagHelperDirectiveType.TagHelperPrefix, - Diagnostics = new List(), - } - }, - new[] - { - new RazorError( - string.Format( - invalidTagHelperPrefixValueError, - SyntaxConstants.CSharp.TagHelperPrefixKeyword, - '@', - "@"), - directiveLocation1, - length: 1) - } - }, - { - new[] - { - new TagHelperDirectiveDescriptor - { - DirectiveText = "t@h", - Location = directiveLocation1, - DirectiveType = TagHelperDirectiveType.TagHelperPrefix, - Diagnostics = new List(), - } - }, - new[] - { - new RazorError( - string.Format( - invalidTagHelperPrefixValueError, - SyntaxConstants.CSharp.TagHelperPrefixKeyword, - '@', - "t@h"), - directiveLocation1, - length: 3) - } - }, - { - new[] - { - new TagHelperDirectiveDescriptor - { - DirectiveText = "!", - Location = directiveLocation1, - DirectiveType = TagHelperDirectiveType.TagHelperPrefix, - Diagnostics = new List(), - } - }, - new[] - { - new RazorError( - string.Format( - invalidTagHelperPrefixValueError, - SyntaxConstants.CSharp.TagHelperPrefixKeyword, - '!', - "!"), - directiveLocation1, - length: 1) - } - }, - { - new[] - { - new TagHelperDirectiveDescriptor - { - DirectiveText = "!th", - Location = directiveLocation1, - DirectiveType = TagHelperDirectiveType.TagHelperPrefix, - Diagnostics = new List(), - } - }, - new[] - { - new RazorError( - string.Format( - invalidTagHelperPrefixValueError, - SyntaxConstants.CSharp.TagHelperPrefixKeyword, - '!', - "!th"), - directiveLocation1, - length: 3) - } - }, - }; - } - } - - [Theory] - [MemberData(nameof(InvalidTagHelperPrefixData))] - public void IsValidTagHelperPrefix_ValidatesPrefix( - object directives, - object expectedErrors) - { - // Arrange - var expectedDiagnostics = ((IEnumerable)expectedErrors).Select(RazorDiagnostic.Create); - var tagHelperDirectives = (IEnumerable)directives; - var phase = new DefaultRazorTagHelperBinderPhase(); - - // Act - foreach (var directive in tagHelperDirectives) - { - Assert.False(phase.IsValidTagHelperPrefix(directive.DirectiveText, directive.Location, directive.Diagnostics)); - } - - // Assert - Assert.Equal(expectedDiagnostics, tagHelperDirectives.SelectMany(directive => directive.Diagnostics)); - } - private static string AssemblyA => "TestAssembly"; private static string AssemblyB => "AnotherAssembly"; @@ -755,87 +499,53 @@ namespace Microsoft.AspNetCore.Razor.Language { get { - // directiveDescriptors, expected prefix - return new TheoryData, string> + // source, expected prefix + return new TheoryData { { - new [] - { - CreateTagHelperDirectiveDescriptor("", TagHelperDirectiveType.TagHelperPrefix), - CreateTagHelperDirectiveDescriptor( - "Microsoft.AspNetCore.Razor.TagHelpers.ValidPlain*, " + AssemblyA, - TagHelperDirectiveType.AddTagHelper), - }, + $@" +@tagHelperPrefix """" +@addTagHelper Microsoft.AspNetCore.Razor.TagHelpers.ValidPlain*, TestAssembly", null }, { - new [] - { - CreateTagHelperDirectiveDescriptor("th:", TagHelperDirectiveType.TagHelperPrefix), - CreateTagHelperDirectiveDescriptor( - "Microsoft.AspNetCore.Razor.TagHelpers.ValidPlain*, " + AssemblyA, - TagHelperDirectiveType.AddTagHelper), - }, + $@" +@tagHelperPrefix th: +@addTagHelper Microsoft.AspNetCore.Razor.TagHelpers.ValidPlain*, {AssemblyA}", "th:" }, { - new [] - { - CreateTagHelperDirectiveDescriptor("*, " + AssemblyA, TagHelperDirectiveType.AddTagHelper), - CreateTagHelperDirectiveDescriptor("th:", TagHelperDirectiveType.TagHelperPrefix) - }, + $@" +@addTagHelper *, {AssemblyA} +@tagHelperPrefix th:", "th:" }, { - new [] - { - CreateTagHelperDirectiveDescriptor("th-", TagHelperDirectiveType.TagHelperPrefix), - CreateTagHelperDirectiveDescriptor( - "Microsoft.AspNetCore.Razor.TagHelpers.ValidPlain*, " + AssemblyA, - TagHelperDirectiveType.AddTagHelper), - CreateTagHelperDirectiveDescriptor( - "Microsoft.AspNetCore.Razor.TagHelpers.ValidInherited*, " + AssemblyA, - TagHelperDirectiveType.AddTagHelper) - }, + $@" +@tagHelperPrefix th- +@addTagHelper Microsoft.AspNetCore.Razor.TagHelpers.ValidPlain*, {AssemblyA} +@addTagHelper Microsoft.AspNetCore.Razor.TagHelpers.ValidInherited*, {AssemblyA}", "th-" }, { - new [] - { - CreateTagHelperDirectiveDescriptor("", TagHelperDirectiveType.TagHelperPrefix), - CreateTagHelperDirectiveDescriptor( - "Microsoft.AspNetCore.Razor.TagHelpers.ValidPlain*, " + AssemblyA, - TagHelperDirectiveType.AddTagHelper), - CreateTagHelperDirectiveDescriptor( - "Microsoft.AspNetCore.Razor.TagHelpers.ValidInherited*, " + AssemblyA, - TagHelperDirectiveType.AddTagHelper) - }, + $@" +@tagHelperPrefix +@addTagHelper Microsoft.AspNetCore.Razor.TagHelpers.ValidPlain*, {AssemblyA} +@addTagHelper Microsoft.AspNetCore.Razor.TagHelpers.ValidInherited*, {AssemblyA}", null }, { - new [] - { - CreateTagHelperDirectiveDescriptor("th", TagHelperDirectiveType.TagHelperPrefix), - CreateTagHelperDirectiveDescriptor( - "*, " + AssemblyA, - TagHelperDirectiveType.AddTagHelper), - CreateTagHelperDirectiveDescriptor( - "*, " + AssemblyB, - TagHelperDirectiveType.AddTagHelper), - }, + $@" +@tagHelperPrefix ""th"" +@addTagHelper *, {AssemblyA} +@addTagHelper *, {AssemblyB}", "th" }, { - new [] - { - CreateTagHelperDirectiveDescriptor( - "*, " + AssemblyA, - TagHelperDirectiveType.AddTagHelper), - CreateTagHelperDirectiveDescriptor("th:-", TagHelperDirectiveType.TagHelperPrefix), - CreateTagHelperDirectiveDescriptor( - "*, " + AssemblyB, - TagHelperDirectiveType.AddTagHelper), - }, + $@" +@addTagHelper *, {AssemblyA} +@tagHelperPrefix th:- +@addTagHelper *, {AssemblyB}", "th:-" }, }; @@ -844,193 +554,144 @@ namespace Microsoft.AspNetCore.Razor.Language [Theory] [MemberData(nameof(ProcessTagHelperPrefixData))] - public void ProcessTagHelperPrefix_ParsesPrefixFromDirectives( - object directiveDescriptors, + public void DirectiveVisitor_ExtractsPrefixFromSyntaxTree( + string source, string expectedPrefix) { // Arrange - var phase = new DefaultRazorTagHelperBinderPhase(); - var document = RazorCodeDocument.Create(new StringSourceDocument("Test content", encoding: Encoding.UTF8, filePath: "TestFile")); - var tagHelperDirectives = (IEnumerable)directiveDescriptors; + var sourceDocument = TestRazorSourceDocument.Create(source, fileName: "TestFile"); + var parser = new RazorParser(); + var syntaxTree = parser.Parse(sourceDocument); + var visitor = new DefaultRazorTagHelperBinderPhase.DirectiveVisitor(tagHelpers: new List()); // Act - var prefix = phase.ProcessTagHelperPrefix(((IEnumerable)directiveDescriptors).ToList(), document); + visitor.VisitBlock(syntaxTree.Root); // Assert - Assert.Empty(tagHelperDirectives.SelectMany(directive => directive.Diagnostics)); - Assert.Equal(expectedPrefix, prefix); + Assert.Equal(expectedPrefix, visitor.TagHelperPrefix); } - public static TheoryData ProcessDirectivesData + public static TheoryData ProcessTagHelperMatchesData { get { - return new TheoryData, // tagHelpers - IEnumerable, // directiveDescriptors - IEnumerable> // expectedDescriptors + // source, taghelpers, expected descriptors + return new TheoryData { { + $@" +@addTagHelper *, {AssemblyA}", new [] { Valid_PlainTagHelperDescriptor, }, - new [] - { - CreateTagHelperDirectiveDescriptor("*, " + AssemblyA, TagHelperDirectiveType.AddTagHelper) - }, new [] { Valid_PlainTagHelperDescriptor } }, { + $@" +@addTagHelper *, {AssemblyA} +@addTagHelper *, {AssemblyB}", new [] { Valid_PlainTagHelperDescriptor, String_TagHelperDescriptor }, - new [] - { - CreateTagHelperDirectiveDescriptor("*, " + AssemblyA, TagHelperDirectiveType.AddTagHelper), - CreateTagHelperDirectiveDescriptor("*, " + AssemblyB, TagHelperDirectiveType.AddTagHelper) - }, new [] { Valid_PlainTagHelperDescriptor, String_TagHelperDescriptor } }, { + $@" +@addTagHelper *, {AssemblyA} +@removeTagHelper *, {AssemblyB}", new [] { Valid_PlainTagHelperDescriptor, String_TagHelperDescriptor }, - new [] - { - CreateTagHelperDirectiveDescriptor("*, " + AssemblyA, TagHelperDirectiveType.AddTagHelper), - CreateTagHelperDirectiveDescriptor("*, " + AssemblyB, TagHelperDirectiveType.RemoveTagHelper) - }, new [] { Valid_PlainTagHelperDescriptor } }, { + $@" +@addTagHelper *, {AssemblyA} +@addTagHelper *, {AssemblyB} +@removeTagHelper *, {AssemblyA}", new [] { Valid_PlainTagHelperDescriptor, Valid_InheritedTagHelperDescriptor, String_TagHelperDescriptor }, - new [] - { - CreateTagHelperDirectiveDescriptor("*, " + AssemblyA, TagHelperDirectiveType.AddTagHelper), - CreateTagHelperDirectiveDescriptor("*, " + AssemblyB, TagHelperDirectiveType.AddTagHelper), - CreateTagHelperDirectiveDescriptor("*, " + AssemblyA, TagHelperDirectiveType.RemoveTagHelper) - }, new [] { String_TagHelperDescriptor } }, { + $@" +@addTagHelper {Valid_PlainTagHelperDescriptor.Name}, {AssemblyA} +@addTagHelper *, {AssemblyA}", new [] { Valid_PlainTagHelperDescriptor, Valid_InheritedTagHelperDescriptor, }, - new [] - { - CreateTagHelperDirectiveDescriptor( - Valid_PlainTagHelperDescriptor.Name + ", " + AssemblyA, - TagHelperDirectiveType.AddTagHelper), - CreateTagHelperDirectiveDescriptor("*, " + AssemblyA, TagHelperDirectiveType.AddTagHelper) - }, new [] { Valid_PlainTagHelperDescriptor, Valid_InheritedTagHelperDescriptor } }, { + $@" +@addTagHelper *, {AssemblyA} +@removeTagHelper {Valid_PlainTagHelperDescriptor.Name}, {AssemblyA}", new [] { Valid_PlainTagHelperDescriptor, Valid_InheritedTagHelperDescriptor, }, - new [] - { - CreateTagHelperDirectiveDescriptor("*, " + AssemblyA, TagHelperDirectiveType.AddTagHelper), - CreateTagHelperDirectiveDescriptor( - Valid_PlainTagHelperDescriptor.Name + ", " + AssemblyA, - TagHelperDirectiveType.RemoveTagHelper) - }, new [] { Valid_InheritedTagHelperDescriptor } }, { + $@" +@addTagHelper *, {AssemblyA} +@removeTagHelper *, {AssemblyA} +@addTagHelper *, {AssemblyA}", new [] { Valid_PlainTagHelperDescriptor, Valid_InheritedTagHelperDescriptor, }, - new [] - { - CreateTagHelperDirectiveDescriptor("*, " + AssemblyA, TagHelperDirectiveType.AddTagHelper), - CreateTagHelperDirectiveDescriptor( - Valid_PlainTagHelperDescriptor.Name + ", " + AssemblyA, - TagHelperDirectiveType.RemoveTagHelper), - CreateTagHelperDirectiveDescriptor("*, " + AssemblyA, TagHelperDirectiveType.AddTagHelper) - }, new [] { Valid_InheritedTagHelperDescriptor, Valid_PlainTagHelperDescriptor } }, { + $@" +@addTagHelper *, {AssemblyA} +@addTagHelper *, {AssemblyA}", new [] { Valid_PlainTagHelperDescriptor, Valid_InheritedTagHelperDescriptor, }, - new [] - { - CreateTagHelperDirectiveDescriptor("*, " + AssemblyA, TagHelperDirectiveType.AddTagHelper), - CreateTagHelperDirectiveDescriptor("*, " + AssemblyA, TagHelperDirectiveType.AddTagHelper), - }, new [] { Valid_InheritedTagHelperDescriptor, Valid_PlainTagHelperDescriptor } }, { + $@" +@addTagHelper Microsoft.AspNetCore.Razor.TagHelpers.ValidPlain*, {AssemblyA}", new [] { Valid_PlainTagHelperDescriptor, Valid_InheritedTagHelperDescriptor, }, - new [] - { - CreateTagHelperDirectiveDescriptor( - "Microsoft.AspNetCore.Razor.TagHelpers.ValidPlain*, " + AssemblyA, - TagHelperDirectiveType.AddTagHelper), - }, new [] { Valid_PlainTagHelperDescriptor } }, { + $@" +@addTagHelper Microsoft.AspNetCore.Razor.TagHelpers.*, {AssemblyA}", new [] { Valid_PlainTagHelperDescriptor, Valid_InheritedTagHelperDescriptor, }, - new [] - { - CreateTagHelperDirectiveDescriptor( - "Microsoft.AspNetCore.Razor.TagHelpers.*, " + AssemblyA, - TagHelperDirectiveType.AddTagHelper), - }, new [] { Valid_PlainTagHelperDescriptor, Valid_PlainTagHelperDescriptor } }, { + $@" +@addTagHelper *, {AssemblyA} +@removeTagHelper Microsoft.AspNetCore.Razor.TagHelpers.ValidP*, {AssemblyA} +@addTagHelper *, {AssemblyA}", new [] { Valid_PlainTagHelperDescriptor, Valid_InheritedTagHelperDescriptor, }, - new [] - { - CreateTagHelperDirectiveDescriptor("*, " + AssemblyA, TagHelperDirectiveType.AddTagHelper), - CreateTagHelperDirectiveDescriptor( - "Microsoft.AspNetCore.Razor.TagHelpers.ValidP*, " + AssemblyA, - TagHelperDirectiveType.RemoveTagHelper), - CreateTagHelperDirectiveDescriptor("*, " + AssemblyA, TagHelperDirectiveType.AddTagHelper) - }, new [] { Valid_InheritedTagHelperDescriptor, Valid_PlainTagHelperDescriptor } }, { + $@" +@addTagHelper *, {AssemblyA} +@removeTagHelper Str*, {AssemblyB}", new [] { Valid_PlainTagHelperDescriptor, String_TagHelperDescriptor, }, - new [] - { - CreateTagHelperDirectiveDescriptor("*, " + AssemblyA, TagHelperDirectiveType.AddTagHelper), - CreateTagHelperDirectiveDescriptor("Str*, " + AssemblyB, TagHelperDirectiveType.RemoveTagHelper) - }, new [] { Valid_PlainTagHelperDescriptor } }, { + $@" +@addTagHelper *, {AssemblyA} +@removeTagHelper *, {AssemblyB}", new [] { Valid_PlainTagHelperDescriptor, String_TagHelperDescriptor, }, - new [] - { - CreateTagHelperDirectiveDescriptor("*, " + AssemblyA, TagHelperDirectiveType.AddTagHelper), - CreateTagHelperDirectiveDescriptor("*, " + AssemblyB, TagHelperDirectiveType.RemoveTagHelper) - }, new [] { Valid_PlainTagHelperDescriptor } }, { + $@" +@addTagHelper *, {AssemblyA} +@addTagHelper System.{String_TagHelperDescriptor.Name}, {AssemblyB}", new [] { Valid_PlainTagHelperDescriptor, String_TagHelperDescriptor, }, - new [] - { - CreateTagHelperDirectiveDescriptor("*, " + AssemblyA, TagHelperDirectiveType.AddTagHelper), - CreateTagHelperDirectiveDescriptor("System." + String_TagHelperDescriptor.Name + ", " + AssemblyB, TagHelperDirectiveType.AddTagHelper) - }, new [] { Valid_PlainTagHelperDescriptor } }, { + $@" +@addTagHelper *, {AssemblyA} +@addTagHelper *, {AssemblyB} +@removeTagHelper Microsoft.*, {AssemblyA}", new [] { Valid_PlainTagHelperDescriptor, Valid_InheritedTagHelperDescriptor, String_TagHelperDescriptor }, - new [] - { - CreateTagHelperDirectiveDescriptor("*, " + AssemblyA, TagHelperDirectiveType.AddTagHelper), - CreateTagHelperDirectiveDescriptor("*, " + AssemblyB, TagHelperDirectiveType.AddTagHelper), - CreateTagHelperDirectiveDescriptor("Microsoft.*, " + AssemblyA, TagHelperDirectiveType.RemoveTagHelper) - }, new [] { String_TagHelperDescriptor } }, { + $@" +@addTagHelper *, {AssemblyA} +@addTagHelper *, {AssemblyB} +@removeTagHelper ?Microsoft*, {AssemblyA} +@removeTagHelper System.{String_TagHelperDescriptor.Name}, {AssemblyB}", new [] { Valid_PlainTagHelperDescriptor, Valid_InheritedTagHelperDescriptor, String_TagHelperDescriptor }, new [] - { - CreateTagHelperDirectiveDescriptor( - "*, " + AssemblyA, TagHelperDirectiveType.AddTagHelper), - CreateTagHelperDirectiveDescriptor( - "*, " + AssemblyB, TagHelperDirectiveType.AddTagHelper), - CreateTagHelperDirectiveDescriptor( - "?Microsoft*, " + AssemblyA, TagHelperDirectiveType.RemoveTagHelper), - CreateTagHelperDirectiveDescriptor( - "System." + String_TagHelperDescriptor.Name + ", " + AssemblyB, TagHelperDirectiveType.RemoveTagHelper) - }, - new [] { Valid_InheritedTagHelperDescriptor, Valid_PlainTagHelperDescriptor, @@ -1038,19 +699,13 @@ namespace Microsoft.AspNetCore.Razor.Language } }, { + $@" +@addTagHelper *, {AssemblyA} +@addTagHelper *, {AssemblyB} +@removeTagHelper TagHelper*, {AssemblyA} +@removeTagHelper System.{String_TagHelperDescriptor.Name}, {AssemblyB}", new [] { Valid_PlainTagHelperDescriptor, Valid_InheritedTagHelperDescriptor, String_TagHelperDescriptor }, new [] - { - CreateTagHelperDirectiveDescriptor( - "*, " + AssemblyA, TagHelperDirectiveType.AddTagHelper), - CreateTagHelperDirectiveDescriptor( - "*, " + AssemblyB, TagHelperDirectiveType.AddTagHelper), - CreateTagHelperDirectiveDescriptor( - "TagHelper*, " + AssemblyA, TagHelperDirectiveType.RemoveTagHelper), - CreateTagHelperDirectiveDescriptor( - "System." + String_TagHelperDescriptor.Name + ", " + AssemblyB, TagHelperDirectiveType.RemoveTagHelper) - }, - new [] { Valid_InheritedTagHelperDescriptor, Valid_PlainTagHelperDescriptor, @@ -1062,165 +717,145 @@ namespace Microsoft.AspNetCore.Razor.Language } [Theory] - [MemberData(nameof(ProcessDirectivesData))] - public void ProcessDirectives_FiltersTagHelpersByDirectives( + [MemberData(nameof(ProcessTagHelperMatchesData))] + public void DirectiveVisitor_FiltersTagHelpersByDirectives( + string source, object tagHelpers, - object directiveDescriptors, object expectedDescriptors) { // Arrange - var phase = new DefaultRazorTagHelperBinderPhase(); - var tagHelperDirectives = (IEnumerable)directiveDescriptors; - var expected = (IEnumerable)expectedDescriptors; + var expected = (TagHelperDescriptor[])expectedDescriptors; + var sourceDocument = TestRazorSourceDocument.Create(source, fileName: "TestFile"); + var parser = new RazorParser(); + var syntaxTree = parser.Parse(sourceDocument); + var visitor = new DefaultRazorTagHelperBinderPhase.DirectiveVisitor((TagHelperDescriptor[])tagHelpers); // Act - var results = phase.ProcessDirectives( - new List(tagHelperDirectives), - new List((IEnumerable)tagHelpers)); + visitor.VisitBlock(syntaxTree.Root); // Assert - Assert.Empty(tagHelperDirectives.SelectMany(directive => directive.Diagnostics)); - Assert.Equal(expected.Count(), results.Count()); + Assert.Equal(expected.Count(), visitor.Matches.Count()); foreach (var expectedDescriptor in expected) { - Assert.Contains(expectedDescriptor, results, TagHelperDescriptorComparer.Default); + Assert.Contains(expectedDescriptor, visitor.Matches, TagHelperDescriptorComparer.Default); } } - public static TheoryData ProcessDirectives_EmptyResultData + public static TheoryData ProcessTagHelperMatches_EmptyResultData { get { - return new TheoryData, IEnumerable> + // source, taghelpers + return new TheoryData> { { + $@" +@addTagHelper *, {AssemblyA} +@removeTagHelper *, {AssemblyA}", new TagHelperDescriptor[] { Valid_PlainTagHelperDescriptor, - }, - new [] - { - CreateTagHelperDirectiveDescriptor("*, " + AssemblyA, TagHelperDirectiveType.AddTagHelper), - CreateTagHelperDirectiveDescriptor("*, " + AssemblyA, TagHelperDirectiveType.RemoveTagHelper), } }, { + $@" +@addTagHelper *, {AssemblyA} +@removeTagHelper {Valid_PlainTagHelperDescriptor.Name}, {AssemblyA} +@removeTagHelper {Valid_InheritedTagHelperDescriptor.Name}, {AssemblyA}", new TagHelperDescriptor[] { Valid_PlainTagHelperDescriptor, Valid_InheritedTagHelperDescriptor, - }, - new [] - { - CreateTagHelperDirectiveDescriptor("*, " + AssemblyA, TagHelperDirectiveType.AddTagHelper), - CreateTagHelperDirectiveDescriptor(Valid_PlainTagHelperDescriptor.Name + ", " + AssemblyA, TagHelperDirectiveType.RemoveTagHelper), - CreateTagHelperDirectiveDescriptor(Valid_InheritedTagHelperDescriptor.Name + ", " + AssemblyA, TagHelperDirectiveType.RemoveTagHelper), } }, { + $@" +@addTagHelper *, {AssemblyA} +@addTagHelper *, {AssemblyB} +@removeTagHelper *, {AssemblyA} +@removeTagHelper *, {AssemblyB}", new TagHelperDescriptor[] { Valid_PlainTagHelperDescriptor, Valid_InheritedTagHelperDescriptor, String_TagHelperDescriptor, - }, - new [] - { - CreateTagHelperDirectiveDescriptor("*, " + AssemblyA, TagHelperDirectiveType.AddTagHelper), - CreateTagHelperDirectiveDescriptor("*, " + AssemblyB, TagHelperDirectiveType.AddTagHelper), - CreateTagHelperDirectiveDescriptor("*, " + AssemblyA, TagHelperDirectiveType.RemoveTagHelper), - CreateTagHelperDirectiveDescriptor("*, " + AssemblyB, TagHelperDirectiveType.RemoveTagHelper) } }, { + $@" +@addTagHelper *, {AssemblyA} +@addTagHelper *, {AssemblyB} +@removeTagHelper {Valid_PlainTagHelperDescriptor.Name}, {AssemblyA} +@removeTagHelper {Valid_InheritedTagHelperDescriptor.Name}, {AssemblyA} +@removeTagHelper {String_TagHelperDescriptor.Name}, {AssemblyB}", new TagHelperDescriptor[] { Valid_PlainTagHelperDescriptor, Valid_InheritedTagHelperDescriptor, String_TagHelperDescriptor, - }, - new [] - { - CreateTagHelperDirectiveDescriptor("*, " + AssemblyA, TagHelperDirectiveType.AddTagHelper), - CreateTagHelperDirectiveDescriptor("*, " + AssemblyB, TagHelperDirectiveType.AddTagHelper), - CreateTagHelperDirectiveDescriptor(Valid_PlainTagHelperDescriptor.Name + ", " + AssemblyA, TagHelperDirectiveType.RemoveTagHelper), - CreateTagHelperDirectiveDescriptor(Valid_InheritedTagHelperDescriptor.Name + ", " + AssemblyA, TagHelperDirectiveType.RemoveTagHelper), - CreateTagHelperDirectiveDescriptor(String_TagHelperDescriptor.Name + ", " + AssemblyB, TagHelperDirectiveType.RemoveTagHelper) } }, { - new TagHelperDescriptor[0], - new [] - { - CreateTagHelperDirectiveDescriptor("*, " + AssemblyA, TagHelperDirectiveType.RemoveTagHelper), - CreateTagHelperDirectiveDescriptor(Valid_PlainTagHelperDescriptor.Name + ", " + AssemblyA, TagHelperDirectiveType.RemoveTagHelper), - } + $@" +@removeTagHelper *, {AssemblyA} +@removeTagHelper {Valid_PlainTagHelperDescriptor.Name}, {AssemblyA}", + new TagHelperDescriptor[0] }, { + $@" +@addTagHelper *, {AssemblyA} +@removeTagHelper Mic*, {AssemblyA}", new TagHelperDescriptor[] { Valid_PlainTagHelperDescriptor, - }, - new [] - { - CreateTagHelperDirectiveDescriptor("*, " + AssemblyA, TagHelperDirectiveType.AddTagHelper), - CreateTagHelperDirectiveDescriptor("Mic*, " + AssemblyA, TagHelperDirectiveType.RemoveTagHelper), } }, { + $@" +@addTagHelper Mic*, {AssemblyA} +@removeTagHelper {Valid_PlainTagHelperDescriptor.Name}, {AssemblyA} +@removeTagHelper {Valid_InheritedTagHelperDescriptor.Name}, {AssemblyA}", new TagHelperDescriptor[] { Valid_PlainTagHelperDescriptor, Valid_InheritedTagHelperDescriptor - }, - new [] - { - CreateTagHelperDirectiveDescriptor("Mic*, " + AssemblyA, TagHelperDirectiveType.AddTagHelper), - CreateTagHelperDirectiveDescriptor(Valid_PlainTagHelperDescriptor.Name + ", " + AssemblyA, TagHelperDirectiveType.RemoveTagHelper), - CreateTagHelperDirectiveDescriptor(Valid_InheritedTagHelperDescriptor.Name + ", " + AssemblyA, TagHelperDirectiveType.RemoveTagHelper) } }, { + $@" +@addTagHelper Microsoft.*, {AssemblyA} +@addTagHelper System.*, {AssemblyB} +@removeTagHelper Microsoft.AspNetCore.Razor.TagHelpers*, {AssemblyA} +@removeTagHelper System.*, {AssemblyB}", new TagHelperDescriptor[] { Valid_PlainTagHelperDescriptor, Valid_InheritedTagHelperDescriptor, String_TagHelperDescriptor, - }, - new [] - { - CreateTagHelperDirectiveDescriptor("Microsoft.*, " + AssemblyA, TagHelperDirectiveType.AddTagHelper), - CreateTagHelperDirectiveDescriptor("System.*, " + AssemblyB, TagHelperDirectiveType.AddTagHelper), - CreateTagHelperDirectiveDescriptor("Microsoft.AspNetCore.Razor.TagHelpers*, " + AssemblyA, TagHelperDirectiveType.RemoveTagHelper), - CreateTagHelperDirectiveDescriptor("System.*, " + AssemblyB, TagHelperDirectiveType.RemoveTagHelper) } }, { + $@" +@addTagHelper ?icrosoft.*, {AssemblyA} +@addTagHelper ?ystem.*, {AssemblyB} +@removeTagHelper *?????r, {AssemblyA} +@removeTagHelper Sy??em.*, {AssemblyB}", new TagHelperDescriptor[] { Valid_PlainTagHelperDescriptor, Valid_InheritedTagHelperDescriptor, String_TagHelperDescriptor, - }, - new [] - { - CreateTagHelperDirectiveDescriptor("?icrosoft.*, " + AssemblyA, TagHelperDirectiveType.AddTagHelper), - CreateTagHelperDirectiveDescriptor("?ystem.*, " + AssemblyB, TagHelperDirectiveType.AddTagHelper), - CreateTagHelperDirectiveDescriptor("*?????r, " + AssemblyA, TagHelperDirectiveType.RemoveTagHelper), - CreateTagHelperDirectiveDescriptor("Sy??em.*, " + AssemblyB, TagHelperDirectiveType.RemoveTagHelper) } }, { + $@" +@addTagHelper ?i?crosoft.*, {AssemblyA} +@addTagHelper ??ystem.*, {AssemblyB}", new TagHelperDescriptor[] { Valid_PlainTagHelperDescriptor, Valid_InheritedTagHelperDescriptor, String_TagHelperDescriptor, - }, - new [] - { - CreateTagHelperDirectiveDescriptor("?i?crosoft.*, " + AssemblyA, TagHelperDirectiveType.AddTagHelper), - CreateTagHelperDirectiveDescriptor("??ystem.*, " + AssemblyB, TagHelperDirectiveType.AddTagHelper), } }, }; @@ -1228,119 +863,22 @@ namespace Microsoft.AspNetCore.Razor.Language } [Theory] - [MemberData(nameof(ProcessDirectives_EmptyResultData))] + [MemberData(nameof(ProcessTagHelperMatches_EmptyResultData))] public void ProcessDirectives_CanReturnEmptyDescriptorsBasedOnDirectiveDescriptors( - object tagHelpers, - object directiveDescriptors) + string source, + object tagHelpers) { // Arrange - var tagHelperDirectives = (IEnumerable)directiveDescriptors; - var phase = new DefaultRazorTagHelperBinderPhase(); + var sourceDocument = TestRazorSourceDocument.Create(source, fileName: "TestFile"); + var parser = new RazorParser(); + var syntaxTree = parser.Parse(sourceDocument); + var visitor = new DefaultRazorTagHelperBinderPhase.DirectiveVisitor((TagHelperDescriptor[])tagHelpers); // Act - var results = phase.ProcessDirectives( - new List(tagHelperDirectives), - new List((IEnumerable)tagHelpers)); + visitor.VisitBlock(syntaxTree.Root); // Assert - Assert.Empty(results); - } - - public static TheoryData ProcessDirectives_IgnoresSpacesData - { - get - { - var assemblyName = Valid_PlainTagHelperDescriptor.AssemblyName; - var typeName = Valid_PlainTagHelperDescriptor.Name; - return new TheoryData - { - $"{typeName},{assemblyName}", - $" {typeName},{assemblyName}", - $"{typeName} ,{assemblyName}", - $" {typeName} ,{assemblyName}", - $"{typeName}, {assemblyName}", - $"{typeName},{assemblyName} ", - $"{typeName}, {assemblyName} ", - $" {typeName}, {assemblyName} ", - $" {typeName} , {assemblyName} " - }; - } - } - - [Theory] - [MemberData(nameof(ProcessDirectives_IgnoresSpacesData))] - public void ProcessDirectives_IgnoresSpaces(string directiveText) - { - // Arrange - var phase = new DefaultRazorTagHelperBinderPhase(); - var directives = new[] - { - new TagHelperDirectiveDescriptor() - { - DirectiveText = directiveText, - DirectiveType = TagHelperDirectiveType.AddTagHelper, - Diagnostics = new List(), - } - }; - - // Act - var results = phase.ProcessDirectives( - directives, - new[] { Valid_PlainTagHelperDescriptor, Valid_InheritedTagHelperDescriptor }); - - // Assert - Assert.Empty(directives[0].Diagnostics); - - var single = Assert.Single(results); - Assert.Equal(Valid_PlainTagHelperDescriptor, single, TagHelperDescriptorComparer.Default); - } - - [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 DescriptorResolver_CreatesErrorIfInvalidLookupText_DoesNotThrow(string directiveText, int errorLength) - { - // Arrange - var phase = new DefaultRazorTagHelperBinderPhase(); - - var directive = new TagHelperDirectiveDescriptor() - { - DirectiveText = directiveText, - DirectiveType = TagHelperDirectiveType.AddTagHelper, - Location = new SourceLocation(1, 2, 3), - Diagnostics = new List(), - }; - - var expectedErrorMessage = string.Format( - "Invalid tag helper directive look up text '{0}'. The correct look up text " + - "format is: \"name, assemblyName\".", - directiveText); - - var expectedError = RazorDiagnostic.Create( - new RazorError( - expectedErrorMessage, - new SourceLocation(1, 2, 3), - errorLength)); - - - // Act - var result = phase.ParseAddOrRemoveDirective(directive); - - // Assert - Assert.Null(result); - - var error = Assert.Single(directive.Diagnostics); - Assert.Equal(expectedError, error); + Assert.Empty(visitor.Matches); } private static TagHelperDescriptor CreatePrefixedValidPlainDescriptor(string prefix) @@ -1358,19 +896,6 @@ namespace Microsoft.AspNetCore.Razor.Language return String_TagHelperDescriptor; } - private static TagHelperDirectiveDescriptor CreateTagHelperDirectiveDescriptor( - string directiveText, - TagHelperDirectiveType directiveType) - { - return new TagHelperDirectiveDescriptor - { - DirectiveText = directiveText, - Location = SourceLocation.Zero, - DirectiveType = directiveType, - Diagnostics = new List(), - }; - } - private static RazorSourceDocument CreateTestSourceDocument() { var content = diff --git a/test/Microsoft.AspNetCore.Razor.Language.Test/Legacy/CSharpDirectivesTest.cs b/test/Microsoft.AspNetCore.Razor.Language.Test/Legacy/CSharpDirectivesTest.cs index 2700e7f535..03142dbcd9 100644 --- a/test/Microsoft.AspNetCore.Razor.Language.Test/Legacy/CSharpDirectivesTest.cs +++ b/test/Microsoft.AspNetCore.Razor.Language.Test/Legacy/CSharpDirectivesTest.cs @@ -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> + { + { + "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)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(); + + // 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(); + 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(); + 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, diff --git a/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives_DesignTime.diagnostics.txt b/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives_DesignTime.diagnostics.txt index 193cdac5e8..b9c22ace19 100644 --- a/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives_DesignTime.diagnostics.txt +++ b/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives_DesignTime.diagnostics.txt @@ -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. diff --git a/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives_Runtime.diagnostics.txt b/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives_Runtime.diagnostics.txt index 193cdac5e8..b9c22ace19 100644 --- a/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives_Runtime.diagnostics.txt +++ b/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives_Runtime.diagnostics.txt @@ -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. diff --git a/test/Microsoft.AspNetCore.Razor.Test.Common/Language/Legacy/TestSpanBuilder.cs b/test/Microsoft.AspNetCore.Razor.Test.Common/Language/Legacy/TestSpanBuilder.cs index dc0e19f801..5b933b1749 100644 --- a/test/Microsoft.AspNetCore.Razor.Test.Common/Language/Legacy/TestSpanBuilder.cs +++ b/test/Microsoft.AspNetCore.Razor.Test.Common/Language/Legacy/TestSpanBuilder.cs @@ -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); }