diff --git a/src/Microsoft.AspNetCore.Razor.Language/Legacy/CSharpCodeParser.cs b/src/Microsoft.AspNetCore.Razor.Language/Legacy/CSharpCodeParser.cs index bbefbd01c2..2b5eceda1e 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/Legacy/CSharpCodeParser.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/Legacy/CSharpCodeParser.cs @@ -1877,9 +1877,28 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy protected virtual void TagHelperPrefixDirective() { + RazorDiagnostic duplicateDiagnostic = null; + if (Context.SeenDirectives.Contains(SyntaxConstants.CSharp.TagHelperPrefixKeyword)) + { + // There wil always be at least 1 child because of the `@` transition. + var directiveStart = Context.Builder.CurrentBlock.Children.First().Start; + var errorLength = /* @ */ 1 + SyntaxConstants.CSharp.TagHelperPrefixKeyword.Length; + duplicateDiagnostic = RazorDiagnosticFactory.CreateParsing_DuplicateDirective( + SyntaxConstants.CSharp.TagHelperPrefixKeyword, + new SourceSpan(directiveStart, errorLength)); + } + TagHelperDirective( SyntaxConstants.CSharp.TagHelperPrefixKeyword, - (prefix, errors) => new TagHelperPrefixDirectiveChunkGenerator(prefix, errors)); + (prefix, errors) => + { + if (duplicateDiagnostic != null) + { + errors.Add(duplicateDiagnostic); + } + + return new TagHelperPrefixDirectiveChunkGenerator(prefix, errors); + }); } protected virtual void AddTagHelperDirective() diff --git a/src/Microsoft.AspNetCore.Razor.Language/RazorDiagnosticFactory.cs b/src/Microsoft.AspNetCore.Razor.Language/RazorDiagnosticFactory.cs index 52d7bb2997..80e5d6043c 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/RazorDiagnosticFactory.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/RazorDiagnosticFactory.cs @@ -45,6 +45,16 @@ namespace Microsoft.AspNetCore.Razor.Language return RazorDiagnostic.Create(CodeTarget_UnsupportedExtension, SourceSpan.Undefined, documentKind, extensionType.Name); } + internal static readonly RazorDiagnosticDescriptor Parsing_DuplicateDirective = + new RazorDiagnosticDescriptor( + $"{DiagnosticPrefix}2001", + () => Resources.DuplicateDirective, + RazorDiagnosticSeverity.Error); + public static RazorDiagnostic CreateParsing_DuplicateDirective(string directive, SourceSpan location) + { + return RazorDiagnostic.Create(Parsing_DuplicateDirective, location, directive); + } + #endregion #region TagHelper Errors diff --git a/test/Microsoft.AspNetCore.Razor.Language.Test/Legacy/CSharpDirectivesTest.cs b/test/Microsoft.AspNetCore.Razor.Language.Test/Legacy/CSharpDirectivesTest.cs index 1c1c9aa6a1..dc3da3bb61 100644 --- a/test/Microsoft.AspNetCore.Razor.Language.Test/Legacy/CSharpDirectivesTest.cs +++ b/test/Microsoft.AspNetCore.Razor.Language.Test/Legacy/CSharpDirectivesTest.cs @@ -1113,6 +1113,29 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy .Accepts(AcceptedCharactersInternal.None))); } + [Fact] + public void TagHelperPrefixDirective_DuplicatesCauseError() + { + // Arrange + var expectedDiagnostic = RazorDiagnosticFactory.CreateParsing_DuplicateDirective( + "tagHelperPrefix", + new SourceSpan(null, 22 + Environment.NewLine.Length, 1, 0, 16)); + + // Act + var document = ParseDocument( +@"@tagHelperPrefix ""th:"" +@tagHelperPrefix ""th""", + directives: null, + designTime: false); + + // Assert + var directive = document.Root.Children.OfType().Last(); + var erroredSpan = (Span)directive.Children.Last(); + var chunkGenerator = Assert.IsType(erroredSpan.ChunkGenerator); + var diagnostic = Assert.Single(chunkGenerator.Diagnostics); + Assert.Equal(expectedDiagnostic, diagnostic); + } + [Fact] public void TagHelperPrefixDirective_NoValueSucceeds() { 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 749ecb184e..193cdac5e8 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 @@ -6,7 +6,9 @@ TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cs 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(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(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 749ecb184e..193cdac5e8 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 @@ -6,7 +6,9 @@ TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cs 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(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(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.