// 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; using System.Linq; using Microsoft.AspNetCore.Razor.Language.Extensions; using Xunit; namespace Microsoft.AspNetCore.Razor.Language.Legacy { public class CSharpDirectivesTest : CsHtmlCodeParserTestBase { [Fact] public void DirectiveDescriptor_FileScopedMultipleOccurring_CanHaveDuplicates() { // Arrange var descriptor = DirectiveDescriptor.CreateDirective( "custom", DirectiveKind.SingleLine, builder => { builder.Usage = DirectiveUsage.FileScopedMultipleOccurring; builder.AddTypeToken(); }); // Act & Assert ParseDocumentTest( @"@custom System.Text.Encoding.ASCIIEncoding @custom System.Text.Encoding.UTF8Encoding", new[] { descriptor }, new MarkupBlock( Factory.EmptyHtml(), new DirectiveBlock(new DirectiveChunkGenerator(descriptor), Factory.CodeTransition(), Factory.MetaCode("custom").Accepts(AcceptedCharactersInternal.None), Factory.Span(SpanKindInternal.Code, " ", markup: false).Accepts(AcceptedCharactersInternal.WhiteSpace), Factory.Span(SpanKindInternal.Code, "System.Text.Encoding.ASCIIEncoding", markup: false).AsDirectiveToken(descriptor.Tokens[0]), Factory.MetaCode(Environment.NewLine).Accepts(AcceptedCharactersInternal.WhiteSpace)), Factory.EmptyHtml(), new DirectiveBlock(new DirectiveChunkGenerator(descriptor), Factory.CodeTransition(), Factory.MetaCode("custom").Accepts(AcceptedCharactersInternal.None), Factory.Span(SpanKindInternal.Code, " ", markup: false).Accepts(AcceptedCharactersInternal.WhiteSpace), Factory.Span(SpanKindInternal.Code, "System.Text.Encoding.UTF8Encoding", markup: false).AsDirectiveToken(descriptor.Tokens[0])), Factory.EmptyHtml())); } [Fact] public void DirectiveDescriptor_FileScopedSinglyOccurring_ErrorsIfDuplicate() { // Arrange var descriptor = DirectiveDescriptor.CreateDirective( "custom", DirectiveKind.SingleLine, builder => { builder.Usage = DirectiveUsage.FileScopedSinglyOccurring; builder.AddTypeToken(); }); var chunkGenerator = new DirectiveChunkGenerator(descriptor); chunkGenerator.Diagnostics.Add( RazorDiagnostic.Create( new RazorError( Resources.FormatDuplicateDirective("custom"), 42 + Environment.NewLine.Length, 1, 0, 7))); // Act & Assert ParseDocumentTest( @"@custom System.Text.Encoding.ASCIIEncoding @custom System.Text.Encoding.UTF8Encoding", new[] { descriptor }, new MarkupBlock( Factory.EmptyHtml(), new DirectiveBlock(new DirectiveChunkGenerator(descriptor), Factory.CodeTransition(), Factory.MetaCode("custom").Accepts(AcceptedCharactersInternal.None), Factory.Span(SpanKindInternal.Code, " ", markup: false).Accepts(AcceptedCharactersInternal.WhiteSpace), Factory.Span(SpanKindInternal.Code, "System.Text.Encoding.ASCIIEncoding", markup: false).AsDirectiveToken(descriptor.Tokens[0]), Factory.MetaCode(Environment.NewLine).Accepts(AcceptedCharactersInternal.WhiteSpace)), Factory.EmptyHtml(), new DirectiveBlock(chunkGenerator, Factory.CodeTransition(), Factory.MetaCode("custom").Accepts(AcceptedCharactersInternal.None), Factory.Span(SpanKindInternal.Code, " ", markup: false).Accepts(AcceptedCharactersInternal.WhiteSpace), Factory.Span(SpanKindInternal.Code, "System.Text.Encoding.UTF8Encoding", markup: false).AsDirectiveToken(descriptor.Tokens[0])), Factory.EmptyHtml())); } [Theory] [InlineData(DirectiveUsage.FileScopedSinglyOccurring)] [InlineData(DirectiveUsage.FileScopedMultipleOccurring)] public void DirectiveDescriptor_FileScoped_CanBeBeneathOtherDirectives(DirectiveUsage directiveUsage) { // Arrange var customDescriptor = DirectiveDescriptor.CreateDirective( "custom", DirectiveKind.SingleLine, builder => { builder.Usage = directiveUsage; builder.AddTypeToken(); }); var somethingDescriptor = DirectiveDescriptor.CreateDirective( "something", DirectiveKind.SingleLine, builder => { builder.Usage = directiveUsage; builder.AddMemberToken(); }); // Act & Assert ParseDocumentTest( @"@custom System.Text.Encoding.ASCIIEncoding @something Else", new[] { customDescriptor, somethingDescriptor }, new MarkupBlock( Factory.EmptyHtml(), new DirectiveBlock(new DirectiveChunkGenerator(customDescriptor), Factory.CodeTransition(), Factory.MetaCode("custom").Accepts(AcceptedCharactersInternal.None), Factory.Span(SpanKindInternal.Code, " ", markup: false).Accepts(AcceptedCharactersInternal.WhiteSpace), Factory.Span(SpanKindInternal.Code, "System.Text.Encoding.ASCIIEncoding", markup: false).AsDirectiveToken(customDescriptor.Tokens[0]), Factory.MetaCode(Environment.NewLine).Accepts(AcceptedCharactersInternal.WhiteSpace)), Factory.EmptyHtml(), new DirectiveBlock(new DirectiveChunkGenerator(somethingDescriptor), Factory.CodeTransition(), Factory.MetaCode("something").Accepts(AcceptedCharactersInternal.None), Factory.Span(SpanKindInternal.Code, " ", markup: false).Accepts(AcceptedCharactersInternal.WhiteSpace), Factory.Span(SpanKindInternal.Code, "Else", markup: false).AsDirectiveToken(somethingDescriptor.Tokens[0])), Factory.EmptyHtml())); } [Theory] [InlineData(DirectiveUsage.FileScopedSinglyOccurring)] [InlineData(DirectiveUsage.FileScopedMultipleOccurring)] public void DirectiveDescriptor_FileScoped_CanBeBeneathOtherWhiteSpaceCommentsAndDirectives(DirectiveUsage directiveUsage) { // Arrange var customDescriptor = DirectiveDescriptor.CreateDirective( "custom", DirectiveKind.SingleLine, builder => { builder.Usage = directiveUsage; builder.AddTypeToken(); }); var somethingDescriptor = DirectiveDescriptor.CreateDirective( "something", DirectiveKind.SingleLine, builder => { builder.Usage = directiveUsage; builder.AddMemberToken(); }); // Act & Assert ParseDocumentTest( @"@* There are two directives beneath this *@ @custom System.Text.Encoding.ASCIIEncoding @something Else
This is extra
", new[] { customDescriptor, somethingDescriptor }, new MarkupBlock( Factory.EmptyHtml(), new CommentBlock( Factory.MarkupTransition(HtmlSymbolType.RazorCommentTransition).Accepts(AcceptedCharactersInternal.None), Factory.MetaMarkup("*", HtmlSymbolType.RazorCommentStar).Accepts(AcceptedCharactersInternal.None), Factory.Span(SpanKindInternal.Comment, new HtmlSymbol(" There are two directives beneath this ", HtmlSymbolType.RazorComment)).Accepts(AcceptedCharactersInternal.Any), Factory.MetaMarkup("*", HtmlSymbolType.RazorCommentStar).Accepts(AcceptedCharactersInternal.None), Factory.MarkupTransition(HtmlSymbolType.RazorCommentTransition).Accepts(AcceptedCharactersInternal.None)), Factory.Markup(Environment.NewLine), new DirectiveBlock(new DirectiveChunkGenerator(customDescriptor), Factory.CodeTransition(), Factory.MetaCode("custom").Accepts(AcceptedCharactersInternal.None), Factory.Span(SpanKindInternal.Code, " ", markup: false).Accepts(AcceptedCharactersInternal.WhiteSpace), Factory.Span(SpanKindInternal.Code, "System.Text.Encoding.ASCIIEncoding", markup: false).AsDirectiveToken(customDescriptor.Tokens[0]), Factory.MetaCode(Environment.NewLine).Accepts(AcceptedCharactersInternal.WhiteSpace)), Factory.Markup(Environment.NewLine), new DirectiveBlock(new DirectiveChunkGenerator(somethingDescriptor), Factory.CodeTransition(), Factory.MetaCode("something").Accepts(AcceptedCharactersInternal.None), Factory.Span(SpanKindInternal.Code, " ", markup: false).Accepts(AcceptedCharactersInternal.WhiteSpace), Factory.Span(SpanKindInternal.Code, "Else", markup: false).AsDirectiveToken(somethingDescriptor.Tokens[0]), Factory.MetaCode(Environment.NewLine).Accepts(AcceptedCharactersInternal.WhiteSpace)), Factory.Markup(Environment.NewLine), BlockFactory.MarkupTagBlock(""), Factory.Markup("This is extra"), BlockFactory.MarkupTagBlock("
"))); } [Fact] public void DirectiveDescriptor_TokensMustBeSeparatedBySpace() { // Arrange var descriptor = DirectiveDescriptor.CreateDirective( "custom", DirectiveKind.SingleLine, b => b.AddStringToken().AddStringToken()); var chunkGenerator = new DirectiveChunkGenerator(descriptor); chunkGenerator.Diagnostics.Add( RazorDiagnostic.Create( new RazorError( Resources.FormatDirectiveTokensMustBeSeparatedByWhitespace("custom"), 17, 0, 17, 9))); // Act & Assert ParseCodeBlockTest( "@custom \"string1\"\"string2\"", new[] { descriptor }, new DirectiveBlock(chunkGenerator, Factory.CodeTransition(), Factory.MetaCode("custom").Accepts(AcceptedCharactersInternal.None), Factory.Span(SpanKindInternal.Markup, " ", markup: false).Accepts(AcceptedCharactersInternal.WhiteSpace), Factory.Span(SpanKindInternal.Code, "\"string1\"", markup: false).AsDirectiveToken(descriptor.Tokens[0]))); } [Fact] public void DirectiveDescriptor_CanHandleEOFIncompleteNamespaceTokens() { // Arrange var descriptor = DirectiveDescriptor.CreateDirective( "custom", DirectiveKind.SingleLine, b => b.AddNamespaceToken()); var chunkGenerator = new DirectiveChunkGenerator(descriptor); chunkGenerator.Diagnostics.Add( RazorDiagnostic.Create( new RazorError( LegacyResources.FormatDirectiveExpectsNamespace("custom"), 8, 0, 8, 7))); // Act & Assert ParseCodeBlockTest( "@custom System.", new[] { descriptor }, new DirectiveBlock(chunkGenerator, Factory.CodeTransition(), Factory.MetaCode("custom").Accepts(AcceptedCharactersInternal.None), Factory.Span(SpanKindInternal.Code, " ", markup: false).Accepts(AcceptedCharactersInternal.WhiteSpace))); } [Fact] public void DirectiveDescriptor_CanHandleEOFInvalidNamespaceTokens() { // Arrange var descriptor = DirectiveDescriptor.CreateDirective( "custom", DirectiveKind.SingleLine, b => b.AddNamespaceToken()); var chunkGenerator = new DirectiveChunkGenerator(descriptor); chunkGenerator.Diagnostics.Add( RazorDiagnostic.Create( new RazorError( LegacyResources.FormatDirectiveExpectsNamespace("custom"), 8, 0, 8, 7))); // Act & Assert ParseCodeBlockTest( "@custom System<", new[] { descriptor }, new DirectiveBlock(chunkGenerator, Factory.CodeTransition(), Factory.MetaCode("custom").Accepts(AcceptedCharactersInternal.None), Factory.Span(SpanKindInternal.Code, " ", markup: false).Accepts(AcceptedCharactersInternal.WhiteSpace))); } [Fact] public void DirectiveDescriptor_CanHandleIncompleteNamespaceTokens() { // Arrange var descriptor = DirectiveDescriptor.CreateDirective( "custom", DirectiveKind.SingleLine, b => b.AddNamespaceToken()); var chunkGenerator = new DirectiveChunkGenerator(descriptor); chunkGenerator.Diagnostics.Add( RazorDiagnostic.Create( new RazorError( LegacyResources.FormatDirectiveExpectsNamespace("custom"), 8, 0, 8, 7))); // Act & Assert ParseCodeBlockTest( "@custom System." + Environment.NewLine, new[] { descriptor }, new DirectiveBlock(chunkGenerator, Factory.CodeTransition(), Factory.MetaCode("custom").Accepts(AcceptedCharactersInternal.None), Factory.Span(SpanKindInternal.Code, " ", markup: false).Accepts(AcceptedCharactersInternal.WhiteSpace))); } [Fact] public void DirectiveDescriptor_CanHandleInvalidNamespaceTokens() { // Arrange var descriptor = DirectiveDescriptor.CreateDirective( "custom", DirectiveKind.SingleLine, b => b.AddNamespaceToken()); var chunkGenerator = new DirectiveChunkGenerator(descriptor); chunkGenerator.Diagnostics.Add( RazorDiagnostic.Create( new RazorError( LegacyResources.FormatDirectiveExpectsNamespace("custom"), 8, 0, 8, 7))); // Act & Assert ParseCodeBlockTest( "@custom System<" + Environment.NewLine, new[] { descriptor }, new DirectiveBlock(chunkGenerator, Factory.CodeTransition(), Factory.MetaCode("custom").Accepts(AcceptedCharactersInternal.None), Factory.Span(SpanKindInternal.Code, " ", markup: false).Accepts(AcceptedCharactersInternal.WhiteSpace))); } [Fact] public void ExtensibleDirectiveDoesNotErorrIfNotAtStartOfLineBecauseOfWhitespace() { // Arrange var descriptor = DirectiveDescriptor.CreateDirective( "custom", DirectiveKind.SingleLine, b => b.AddTypeToken()); // Act & Assert ParseCodeBlockTest(Environment.NewLine + " @custom System.Text.Encoding.ASCIIEncoding", new[] { descriptor }, new DirectiveBlock( new DirectiveChunkGenerator(descriptor), Factory.Code(Environment.NewLine + " ").AsStatement(), Factory.CodeTransition(), Factory.MetaCode("custom").Accepts(AcceptedCharactersInternal.None), Factory.Span(SpanKindInternal.Code, " ", markup: false).Accepts(AcceptedCharactersInternal.WhiteSpace), Factory.Span(SpanKindInternal.Code, "System.Text.Encoding.ASCIIEncoding", markup: false).AsDirectiveToken(descriptor.Tokens[0]))); } [Fact] public void BuiltInDirectiveDoesNotErorrIfNotAtStartOfLineBecauseOfWhitespace() { // Act & Assert ParseCodeBlockTest(Environment.NewLine + " @addTagHelper \"*, Foo\"", Enumerable.EmptyF{o}o
}", new[] { descriptor }, new DirectiveBlock( new DirectiveChunkGenerator(descriptor), Factory.CodeTransition(), Factory.MetaCode("custom").Accepts(AcceptedCharactersInternal.None), Factory.Span(SpanKindInternal.Markup, " ", markup: false).Accepts(AcceptedCharactersInternal.WhiteSpace), Factory.Span(SpanKindInternal.Code, "\"Header\"", markup: false).AsDirectiveToken(descriptor.Tokens[0]), Factory.Span(SpanKindInternal.Markup, " ", markup: false).Accepts(AcceptedCharactersInternal.AllWhiteSpace), Factory.MetaCode("{") .AutoCompleteWith(null, atEndOfSpan: true) .Accepts(AcceptedCharactersInternal.None), new MarkupBlock( Factory.Markup(" "), new MarkupTagBlock( Factory.Markup("")), Factory.Markup("F", "{", "o", "}", "o"), new MarkupTagBlock( Factory.Markup("
")), Factory.Markup(" ")), Factory.MetaCode("}").Accepts(AcceptedCharactersInternal.None))); } [Fact] public void DirectiveDescriptor_UnderstandsCodeBlocks() { // Arrange var descriptor = DirectiveDescriptor.CreateDirective( "custom", DirectiveKind.CodeBlock, b => b.AddStringToken()); // Act & Assert ParseCodeBlockTest( "@custom \"Name\" { foo(); bar(); }", new[] { descriptor }, new DirectiveBlock( new DirectiveChunkGenerator(descriptor), Factory.CodeTransition(), Factory.MetaCode("custom").Accepts(AcceptedCharactersInternal.None), Factory.Span(SpanKindInternal.Markup, " ", markup: false).Accepts(AcceptedCharactersInternal.WhiteSpace), Factory.Span(SpanKindInternal.Code, "\"Name\"", markup: false).AsDirectiveToken(descriptor.Tokens[0]), Factory.Span(SpanKindInternal.Markup, " ", markup: false).Accepts(AcceptedCharactersInternal.AllWhiteSpace), Factory.MetaCode("{") .AutoCompleteWith(null, atEndOfSpan: true) .Accepts(AcceptedCharactersInternal.None), Factory.Code(" foo(); bar(); ").AsStatement(), Factory.MetaCode("}").Accepts(AcceptedCharactersInternal.None))); } [Fact] public void DirectiveDescriptor_AllowsWhiteSpaceAroundTokens() { // Arrange var descriptor = DirectiveDescriptor.CreateDirective( "custom", DirectiveKind.SingleLine, b => b.AddTypeToken().AddMemberToken()); // Act & Assert ParseCodeBlockTest( "@custom System.Text.Encoding.ASCIIEncoding Some_Member ", new[] { descriptor }, new DirectiveBlock( new DirectiveChunkGenerator(descriptor), Factory.CodeTransition(), Factory.MetaCode("custom").Accepts(AcceptedCharactersInternal.None), Factory.Span(SpanKindInternal.Code, " ", markup: false).Accepts(AcceptedCharactersInternal.WhiteSpace), Factory.Span(SpanKindInternal.Code, "System.Text.Encoding.ASCIIEncoding", markup: false).AsDirectiveToken(descriptor.Tokens[0]), Factory.Span(SpanKindInternal.Code, " ", markup: false).Accepts(AcceptedCharactersInternal.WhiteSpace), Factory.Span(SpanKindInternal.Code, "Some_Member", markup: false).AsDirectiveToken(descriptor.Tokens[1]), Factory.MetaCode(" ").Accepts(AcceptedCharactersInternal.WhiteSpace))); } [Fact] public void DirectiveDescriptor_ErrorsForInvalidMemberTokens() { // Arrange var descriptor = DirectiveDescriptor.CreateDirective( "custom", DirectiveKind.SingleLine, b => b.AddMemberToken()); var chunkGenerator = new DirectiveChunkGenerator(descriptor); chunkGenerator.Diagnostics.Add( RazorDiagnostic.Create( new RazorError( LegacyResources.FormatDirectiveExpectsIdentifier("custom"), new SourceLocation(8, 0, 8), length: 1))); // Act & Assert ParseCodeBlockTest( "@custom -Some_Member", new[] { descriptor }, new DirectiveBlock(chunkGenerator, Factory.CodeTransition(), Factory.MetaCode("custom").Accepts(AcceptedCharactersInternal.None), Factory.Span(SpanKindInternal.Code, " ", markup: false).Accepts(AcceptedCharactersInternal.WhiteSpace))); } [Fact] public void DirectiveDescriptor_NoErrorsSemicolonAfterDirective() { // Arrange var descriptor = DirectiveDescriptor.CreateDirective( "custom", DirectiveKind.SingleLine, b => b.AddStringToken()); // Act & Assert ParseCodeBlockTest( "@custom \"hello\" ; ", new[] { descriptor }, new DirectiveBlock( new DirectiveChunkGenerator(descriptor), Factory.CodeTransition(), Factory.MetaCode("custom").Accepts(AcceptedCharactersInternal.None), Factory.Span(SpanKindInternal.Markup, " ", markup: false).Accepts(AcceptedCharactersInternal.WhiteSpace), Factory.Span(SpanKindInternal.Code, "\"hello\"", markup: false).AsDirectiveToken(descriptor.Tokens[0]), Factory.MetaCode(" ; ").Accepts(AcceptedCharactersInternal.WhiteSpace))); } [Fact] public void DirectiveDescriptor_ErrorsExtraContentAfterDirective() { // Arrange var descriptor = DirectiveDescriptor.CreateDirective( "custom", DirectiveKind.SingleLine, b => b.AddStringToken()); var chunkGenerator = new DirectiveChunkGenerator(descriptor); chunkGenerator.Diagnostics.Add( RazorDiagnostic.Create( new RazorError( LegacyResources.FormatUnexpectedDirectiveLiteral("custom", "line break"), new SourceLocation(16, 0, 16), length: 7))); // Act & Assert ParseCodeBlockTest( "@custom \"hello\" \"world\"", new[] { descriptor }, new DirectiveBlock(chunkGenerator, Factory.CodeTransition(), Factory.MetaCode("custom").Accepts(AcceptedCharactersInternal.None), Factory.Span(SpanKindInternal.Markup, " ", markup: false).Accepts(AcceptedCharactersInternal.WhiteSpace), Factory.Span(SpanKindInternal.Code, "\"hello\"", markup: false).AsDirectiveToken(descriptor.Tokens[0]), Factory.MetaCode(" ").Accepts(AcceptedCharactersInternal.WhiteSpace))); } [Fact] public void DirectiveDescriptor_ErrorsWhenExtraContentBeforeBlockStart() { // Arrange var descriptor = DirectiveDescriptor.CreateDirective( "custom", DirectiveKind.CodeBlock, b => b.AddStringToken()); var chunkGenerator = new DirectiveChunkGenerator(descriptor); chunkGenerator.Diagnostics.Add( RazorDiagnostic.Create( new RazorError( LegacyResources.FormatUnexpectedDirectiveLiteral("custom", "{"), new SourceLocation(16, 0, 16), length: 5))); // Act & Assert ParseCodeBlockTest( "@custom \"Hello\" World { foo(); bar(); }", new[] { descriptor }, new DirectiveBlock(chunkGenerator, Factory.CodeTransition(), Factory.MetaCode("custom").Accepts(AcceptedCharactersInternal.None), Factory.Span(SpanKindInternal.Markup, " ", markup: false).Accepts(AcceptedCharactersInternal.WhiteSpace), Factory.Span(SpanKindInternal.Code, "\"Hello\"", markup: false).AsDirectiveToken(descriptor.Tokens[0]), Factory.Span(SpanKindInternal.Markup, " ", markup: false).Accepts(AcceptedCharactersInternal.AllWhiteSpace))); } [Fact] public void DirectiveDescriptor_ErrorsWhenEOFBeforeDirectiveBlockStart() { // Arrange var descriptor = DirectiveDescriptor.CreateDirective( "custom", DirectiveKind.CodeBlock, b => b.AddStringToken()); var chunkGenerator = new DirectiveChunkGenerator(descriptor); chunkGenerator.Diagnostics.Add( RazorDiagnostic.Create( new RazorError( LegacyResources.FormatUnexpectedEOFAfterDirective("custom", "{"), new SourceLocation(15, 0, 15), length: 1))); // Act & Assert ParseCodeBlockTest( "@custom \"Hello\"", new[] { descriptor }, new DirectiveBlock(chunkGenerator, Factory.CodeTransition(), Factory.MetaCode("custom").Accepts(AcceptedCharactersInternal.None), Factory.Span(SpanKindInternal.Markup, " ", markup: false).Accepts(AcceptedCharactersInternal.WhiteSpace), Factory.Span(SpanKindInternal.Code, "\"Hello\"", markup: false).AsDirectiveToken(descriptor.Tokens[0]))); } [Fact] public void DirectiveDescriptor_ErrorsWhenMissingEndBrace() { // Arrange var descriptor = DirectiveDescriptor.CreateDirective( "custom", DirectiveKind.CodeBlock, b => b.AddStringToken()); var chunkGenerator = new DirectiveChunkGenerator(descriptor); chunkGenerator.Diagnostics.Add( RazorDiagnostic.Create( new RazorError( LegacyResources.FormatParseError_Expected_EndOfBlock_Before_EOF("custom", "}", "{"), new SourceLocation(16, 0, 16), length: 1))); // Act & Assert ParseCodeBlockTest( "@custom \"Hello\" {", new[] { descriptor }, new DirectiveBlock(chunkGenerator, Factory.CodeTransition(), Factory.MetaCode("custom").Accepts(AcceptedCharactersInternal.None), Factory.Span(SpanKindInternal.Markup, " ", markup: false).Accepts(AcceptedCharactersInternal.WhiteSpace), Factory.Span(SpanKindInternal.Code, "\"Hello\"", markup: false).AsDirectiveToken(descriptor.Tokens[0]), Factory.Span(SpanKindInternal.Markup, " ", markup: false).Accepts(AcceptedCharactersInternal.AllWhiteSpace), Factory.MetaCode("{") .AutoCompleteWith("}", atEndOfSpan: true) .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.OfTypeF{o}o
}", new[] { SectionDirective.Directive, }, new DirectiveBlock(new DirectiveChunkGenerator(SectionDirective.Directive), Factory.CodeTransition(), Factory.MetaCode("section").Accepts(AcceptedCharactersInternal.None), Factory.Span(SpanKindInternal.Code, " ", CSharpSymbolType.WhiteSpace).Accepts(AcceptedCharactersInternal.WhiteSpace), Factory.Span(SpanKindInternal.Code, "Header", CSharpSymbolType.Identifier) .AsDirectiveToken(SectionDirective.Directive.Tokens.First()), Factory.Span(SpanKindInternal.Markup, " ", CSharpSymbolType.WhiteSpace).Accepts(AcceptedCharactersInternal.AllWhiteSpace), Factory.MetaCode("{").AutoCompleteWith(null, atEndOfSpan: true).Accepts(AcceptedCharactersInternal.None), new MarkupBlock( Factory.Markup(" "), new MarkupTagBlock( Factory.Markup("")), Factory.Markup("F", "{", "o", "}", "o"), new MarkupTagBlock( Factory.Markup("
")), Factory.Markup(" ")), Factory.MetaCode("}") .Accepts(AcceptedCharactersInternal.None))); } [Fact] public void OptionalDirectiveTokens_AreSkipped() { // Arrange var descriptor = DirectiveDescriptor.CreateDirective( "custom", DirectiveKind.SingleLine, b => b.AddOptionalStringToken()); // Act & Assert ParseCodeBlockTest( "@custom ", new[] { descriptor }, new DirectiveBlock( new DirectiveChunkGenerator(descriptor), Factory.CodeTransition(), Factory.MetaCode("custom").Accepts(AcceptedCharactersInternal.None), Factory.Span(SpanKindInternal.Markup, " ", markup: false).Accepts(AcceptedCharactersInternal.WhiteSpace))); } [Fact] public void OptionalDirectiveTokens_WithSimpleTokens_AreParsed() { // Arrange var descriptor = DirectiveDescriptor.CreateDirective( "custom", DirectiveKind.SingleLine, b => b.AddOptionalStringToken()); // Act & Assert ParseCodeBlockTest( "@custom \"simple-value\"", new[] { descriptor }, new DirectiveBlock( new DirectiveChunkGenerator(descriptor), Factory.CodeTransition(), Factory.MetaCode("custom").Accepts(AcceptedCharactersInternal.None), Factory.Span(SpanKindInternal.Markup, " ", markup: false).Accepts(AcceptedCharactersInternal.WhiteSpace), Factory.Span(SpanKindInternal.Code, "\"simple-value\"", markup: false) .AsDirectiveToken(descriptor.Tokens[0]))); } [Fact] public void OptionalDirectiveTokens_WithBraces_AreParsed() { // Arrange var descriptor = DirectiveDescriptor.CreateDirective( "custom", DirectiveKind.SingleLine, b => b.AddOptionalStringToken()); // Act & Assert ParseCodeBlockTest( "@custom \"{formaction}?/{id}?\"", new[] { descriptor }, new DirectiveBlock( new DirectiveChunkGenerator(descriptor), Factory.CodeTransition(), Factory.MetaCode("custom").Accepts(AcceptedCharactersInternal.None), Factory.Span(SpanKindInternal.Markup, " ", markup: false).Accepts(AcceptedCharactersInternal.WhiteSpace), Factory.Span(SpanKindInternal.Code, "\"{formaction}?/{id}?\"", markup: false) .AsDirectiveToken(descriptor.Tokens[0]))); } [Fact] public void OptionalDirectiveTokens_WithMultipleOptionalTokens_AreParsed() { // Arrange var descriptor = DirectiveDescriptor.CreateDirective( "custom", DirectiveKind.SingleLine, b => b.AddOptionalStringToken().AddOptionalTypeToken()); // Act & Assert ParseCodeBlockTest( "@custom \"{formaction}?/{id}?\" System.String", new[] { descriptor }, new DirectiveBlock( new DirectiveChunkGenerator(descriptor), Factory.CodeTransition(), Factory.MetaCode("custom").Accepts(AcceptedCharactersInternal.None), Factory.Span(SpanKindInternal.Markup, " ", markup: false).Accepts(AcceptedCharactersInternal.WhiteSpace), Factory.Span(SpanKindInternal.Code, "\"{formaction}?/{id}?\"", markup: false).AsDirectiveToken(descriptor.Tokens[0]), Factory.Span(SpanKindInternal.Code, " ", markup: false).Accepts(AcceptedCharactersInternal.WhiteSpace), Factory.Span(SpanKindInternal.Code, "System.String", markup: false).AsDirectiveToken(descriptor.Tokens.Last()))); } [Fact] public void OptionalMemberTokens_WithMissingMember_IsParsed() { // Arrange var descriptor = DirectiveDescriptor.CreateDirective( "TestDirective", DirectiveKind.SingleLine, b => b.AddOptionalMemberToken().AddOptionalStringToken()); // Act & Assert ParseCodeBlockTest( "@TestDirective ", new[] { descriptor }, new DirectiveBlock( new DirectiveChunkGenerator(descriptor), Factory.CodeTransition(), Factory.MetaCode("TestDirective").Accepts(AcceptedCharactersInternal.None), Factory.Span(SpanKindInternal.Code, " ", markup: false).Accepts(AcceptedCharactersInternal.WhiteSpace))); } [Fact] public void OptionalMemberTokens_WithMemberSpecified_IsParsed() { // Arrange var descriptor = DirectiveDescriptor.CreateDirective( "TestDirective", DirectiveKind.SingleLine, b => b.AddOptionalMemberToken().AddOptionalStringToken()); // Act & Assert ParseCodeBlockTest( "@TestDirective PropertyName", new[] { descriptor }, new DirectiveBlock( new DirectiveChunkGenerator(descriptor), Factory.CodeTransition(), Factory.MetaCode("TestDirective").Accepts(AcceptedCharactersInternal.None), Factory.Span(SpanKindInternal.Code, " ", markup: false).Accepts(AcceptedCharactersInternal.WhiteSpace), Factory.Span(SpanKindInternal.Code, "PropertyName", markup: false).AsDirectiveToken(descriptor.Tokens[0]))); } [Fact] public void Directives_CanUseReservedWord_Class() { // Arrange var descriptor = DirectiveDescriptor.CreateDirective( "class", DirectiveKind.SingleLine); // Act & Assert ParseCodeBlockTest( "@class", new[] { descriptor }, new DirectiveBlock( new DirectiveChunkGenerator(descriptor), Factory.CodeTransition(), Factory.MetaCode("class").Accepts(AcceptedCharactersInternal.None))); } [Fact] public void Directives_CanUseReservedWord_Namespace() { // Arrange var descriptor = DirectiveDescriptor.CreateDirective( "namespace", DirectiveKind.SingleLine); // Act & Assert ParseCodeBlockTest( "@namespace", new[] { descriptor }, new DirectiveBlock( new DirectiveChunkGenerator(descriptor), Factory.CodeTransition(), Factory.MetaCode("namespace").Accepts(AcceptedCharactersInternal.None))); } internal virtual void ParseCodeBlockTest( string document, IEnumerable