// 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.Span(SpanKindInternal.None, Environment.NewLine, markup: false).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(
RazorDiagnosticFactory.CreateParsing_DuplicateDirective(
new SourceSpan(new SourceLocation(42 + Environment.NewLine.Length, 1, 0), 7), "custom"));
// 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.Span(SpanKindInternal.None, Environment.NewLine, markup: false).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.Span(SpanKindInternal.None, Environment.NewLine, markup: false).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.Span(SpanKindInternal.None, Environment.NewLine, markup: false).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.Span(SpanKindInternal.None, Environment.NewLine, markup: false).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(
RazorDiagnosticFactory.CreateParsing_DirectiveTokensMustBeSeparatedByWhitespace(
new SourceSpan(new SourceLocation(17, 0, 17), 9), "custom"));
// 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(
RazorDiagnosticFactory.CreateParsing_DirectiveExpectsNamespace(
new SourceSpan(new SourceLocation(8, 0, 8), 7), "custom"));
// 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(
RazorDiagnosticFactory.CreateParsing_DirectiveExpectsNamespace(
new SourceSpan(new SourceLocation(8, 0, 8), 7), "custom"));
// 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(
RazorDiagnosticFactory.CreateParsing_DirectiveExpectsNamespace(
new SourceSpan(new SourceLocation(8, 0, 8), 7), "custom"));
// 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(
RazorDiagnosticFactory.CreateParsing_DirectiveExpectsNamespace(
new SourceSpan(new SourceLocation(8, 0, 8), 7), "custom"));
// 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.Empty(),
new DirectiveBlock(
Factory.Code(Environment.NewLine + " ").AsStatement(),
Factory.CodeTransition(),
Factory
.MetaCode(SyntaxConstants.CSharp.AddTagHelperKeyword)
.Accepts(AcceptedCharactersInternal.None),
Factory
.Span(SpanKindInternal.Markup, " ", markup: false)
.Accepts(AcceptedCharactersInternal.None),
Factory.Code("\"*, Foo\"")
.AsAddTagHelper(
"\"*, Foo\"",
"*, Foo",
"*",
"Foo")));
}
[Fact]
public void BuiltInDirectiveErorrsIfNotAtStartOfLine()
{
// Act & Assert
ParseCodeBlockTest("{ @addTagHelper \"*, Foo\"" + Environment.NewLine + "}",
Enumerable.Empty(),
new StatementBlock(
Factory.MetaCode("{").Accepts(AcceptedCharactersInternal.None),
Factory.Code(" ")
.AsStatement()
.AutoCompleteWith(autoCompleteString: null, atEndOfSpan: false),
new DirectiveBlock(
Factory.CodeTransition(),
Factory
.MetaCode(SyntaxConstants.CSharp.AddTagHelperKeyword)
.Accepts(AcceptedCharactersInternal.None),
Factory
.Span(SpanKindInternal.Markup, " ", markup: false)
.Accepts(AcceptedCharactersInternal.None),
Factory.Code("\"*, Foo\"")
.AsAddTagHelper(
"\"*, Foo\"",
"*, Foo",
"*",
"Foo",
RazorDiagnosticFactory.CreateParsing_DirectiveMustAppearAtStartOfLine(
new SourceSpan(new SourceLocation(4, 0, 4), 12), "addTagHelper"))),
Factory.Code(Environment.NewLine).AsStatement(),
Factory.MetaCode("}").Accepts(AcceptedCharactersInternal.None)));
}
[Fact]
public void ExtensibleDirectiveErorrsIfNotAtStartOfLine()
{
// Arrange
var descriptor = DirectiveDescriptor.CreateDirective(
"custom",
DirectiveKind.SingleLine,
b => b.AddTypeToken());
var chunkGenerator = new DirectiveChunkGenerator(descriptor);
chunkGenerator.Diagnostics.Add(
RazorDiagnosticFactory.CreateParsing_DirectiveMustAppearAtStartOfLine(
new SourceSpan(new SourceLocation(4, 0, 4), contentLength: 6), "custom"));
// Act & Assert
ParseCodeBlockTest(
"{ @custom System.Text.Encoding.ASCIIEncoding" + Environment.NewLine + "}",
new[] { descriptor },
new StatementBlock(
Factory.MetaCode("{").Accepts(AcceptedCharactersInternal.None),
Factory.Code(" ")
.AsStatement()
.AutoCompleteWith(autoCompleteString: null, atEndOfSpan: false),
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.ASCIIEncoding", markup: false).AsDirectiveToken(descriptor.Tokens[0]),
Factory.Span(SpanKindInternal.None, Environment.NewLine, markup: false).Accepts(AcceptedCharactersInternal.WhiteSpace)),
Factory.EmptyCSharp().AsStatement(),
Factory.MetaCode("}").Accepts(AcceptedCharactersInternal.None)));
}
[Fact]
public void DirectiveDescriptor_UnderstandsTypeTokens()
{
// Arrange
var descriptor = DirectiveDescriptor.CreateDirective(
"custom",
DirectiveKind.SingleLine,
b => b.AddTypeToken());
// Act & Assert
ParseCodeBlockTest(
"@custom System.Text.Encoding.ASCIIEncoding",
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])));
}
[Fact]
public void DirectiveDescriptor_UnderstandsMemberTokens()
{
// Arrange
var descriptor = DirectiveDescriptor.CreateDirective(
"custom",
DirectiveKind.SingleLine,
b => b.AddMemberToken());
// Act & Assert
ParseCodeBlockTest(
"@custom 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, "Some_Member", markup: false).AsDirectiveToken(descriptor.Tokens[0])));
}
[Fact]
public void Parser_ParsesNamespaceDirectiveToken_WithSingleSegment()
{
// Arrange
var descriptor = DirectiveDescriptor.CreateDirective(
"custom",
DirectiveKind.SingleLine,
b => b.AddNamespaceToken());
// Act & Assert
ParseCodeBlockTest(
"@custom BaseNamespace",
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, "BaseNamespace", markup: false).AsDirectiveToken(descriptor.Tokens[0])));
}
[Fact]
public void Parser_ParsesNamespaceDirectiveToken_WithMultipleSegments()
{
// Arrange
var descriptor = DirectiveDescriptor.CreateDirective(
"custom",
DirectiveKind.SingleLine,
b => b.AddNamespaceToken());
// Act & Assert
ParseCodeBlockTest(
"@custom BaseNamespace.Foo.Bar",
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, "BaseNamespace.Foo.Bar", markup: false).AsDirectiveToken(descriptor.Tokens[0])));
}
[Fact]
public void DirectiveDescriptor_UnderstandsStringTokens()
{
// Arrange
var descriptor = DirectiveDescriptor.CreateDirective(
"custom",
DirectiveKind.SingleLine,
b => b.AddStringToken());
// Act & Assert
ParseCodeBlockTest(
"@custom \"AString\"",
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, "\"AString\"", markup: false).AsDirectiveToken(descriptor.Tokens[0])));
}
[Fact]
public void DirectiveDescriptor_StringToken_ParserErrorForUnquotedValue()
{
// Arrange
var descriptor = DirectiveDescriptor.CreateDirective(
"custom",
DirectiveKind.SingleLine,
b => b.AddStringToken());
var chunkGenerator = new DirectiveChunkGenerator(descriptor);
chunkGenerator.Diagnostics.Add(
RazorDiagnosticFactory.CreateParsing_DirectiveExpectsQuotedStringLiteral(
new SourceSpan(new SourceLocation(8, 0, 8), contentLength: 7), "custom"));
// Act & Assert
ParseCodeBlockTest(
"@custom AString",
new[] { descriptor },
new DirectiveBlock(chunkGenerator,
Factory.CodeTransition(),
Factory.MetaCode("custom").Accepts(AcceptedCharactersInternal.None),
Factory.Span(SpanKindInternal.Markup, " ", markup: false).Accepts(AcceptedCharactersInternal.WhiteSpace)));
}
[Fact]
public void DirectiveDescriptor_StringToken_ParserErrorForNonStringValue()
{
// Arrange
var descriptor = DirectiveDescriptor.CreateDirective(
"custom",
DirectiveKind.SingleLine,
b => b.AddStringToken());
var chunkGenerator = new DirectiveChunkGenerator(descriptor);
chunkGenerator.Diagnostics.Add(
RazorDiagnosticFactory.CreateParsing_DirectiveExpectsQuotedStringLiteral(
new SourceSpan(new SourceLocation(8, 0, 8), contentLength: 1), "custom"));
// Act & Assert
ParseCodeBlockTest(
"@custom {foo?}",
new[] { descriptor },
new DirectiveBlock(chunkGenerator,
Factory.CodeTransition(),
Factory.MetaCode("custom").Accepts(AcceptedCharactersInternal.None),
Factory.Span(SpanKindInternal.Markup, " ", markup: false).Accepts(AcceptedCharactersInternal.WhiteSpace)));
}
[Fact]
public void DirectiveDescriptor_StringToken_ParserErrorForSingleQuotedValue()
{
// Arrange
var descriptor = DirectiveDescriptor.CreateDirective(
"custom",
DirectiveKind.SingleLine,
b => b.AddStringToken());
var chunkGenerator = new DirectiveChunkGenerator(descriptor);
chunkGenerator.Diagnostics.Add(
RazorDiagnosticFactory.CreateParsing_DirectiveExpectsQuotedStringLiteral(
new SourceSpan(new SourceLocation(8, 0, 8), contentLength: 9), "custom"));
// Act & Assert
ParseCodeBlockTest(
"@custom 'AString'",
new[] { descriptor },
new DirectiveBlock(chunkGenerator,
Factory.CodeTransition(),
Factory.MetaCode("custom").Accepts(AcceptedCharactersInternal.None),
Factory.Span(SpanKindInternal.Markup, " ", markup: false).Accepts(AcceptedCharactersInternal.WhiteSpace)));
}
[Fact]
public void DirectiveDescriptor_StringToken_ParserErrorForPartialQuotedValue()
{
// Arrange
var descriptor = DirectiveDescriptor.CreateDirective(
"custom",
DirectiveKind.SingleLine,
b => b.AddStringToken());
var chunkGenerator = new DirectiveChunkGenerator(descriptor);
chunkGenerator.Diagnostics.Add(
RazorDiagnosticFactory.CreateParsing_DirectiveExpectsQuotedStringLiteral(
new SourceSpan(new SourceLocation(8, 0, 8), contentLength: 7), "custom"));
// Act & Assert
ParseCodeBlockTest(
"@custom AString\"",
new[] { descriptor },
new DirectiveBlock(chunkGenerator,
Factory.CodeTransition(),
Factory.MetaCode("custom").Accepts(AcceptedCharactersInternal.None),
Factory.Span(SpanKindInternal.Markup, " ", markup: false).Accepts(AcceptedCharactersInternal.WhiteSpace)));
}
[Fact]
public void DirectiveDescriptor_UnderstandsMultipleTokens()
{
// Arrange
var descriptor = DirectiveDescriptor.CreateDirective(
"custom",
DirectiveKind.SingleLine,
b => b.AddTypeToken().AddMemberToken().AddStringToken());
// Act & Assert
ParseCodeBlockTest(
"@custom System.Text.Encoding.ASCIIEncoding Some_Member \"AString\"",
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.Span(SpanKindInternal.Markup, " ", markup: false).Accepts(AcceptedCharactersInternal.WhiteSpace),
Factory.Span(SpanKindInternal.Code, "\"AString\"", markup: false).AsDirectiveToken(descriptor.Tokens[2])));
}
[Fact]
public void DirectiveDescriptor_UnderstandsRazorBlocks()
{
// Arrange
var descriptor = DirectiveDescriptor.CreateDirective(
"custom",
DirectiveKind.RazorBlock,
b => b.AddStringToken());
// Act & Assert
ParseCodeBlockTest(
"@custom \"Header\" { F{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.Span(SpanKindInternal.None, " ", markup: false).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(
RazorDiagnosticFactory.CreateParsing_DirectiveExpectsIdentifier(
new SourceSpan(new SourceLocation(8, 0, 8), contentLength: 1), "custom"));
// 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.Span(SpanKindInternal.None, " ", markup: false).Accepts(AcceptedCharactersInternal.WhiteSpace),
Factory.MetaCode(";").Accepts(AcceptedCharactersInternal.WhiteSpace),
Factory.Span(SpanKindInternal.None, " ", markup: false).Accepts(AcceptedCharactersInternal.WhiteSpace)));
}
[Theory]
[InlineData("string?")]
[InlineData("string?[]")]
[InlineData("global::System.Int32?")]
[InlineData("KeyValuePair?")]
[InlineData("KeyValuePair?[]")]
[InlineData("global::System.Collections.Generic.KeyValuePair?[]")]
public void DirectiveDescriptor_AllowsNullableTypes(string expectedType)
{
// Arrange
var descriptor = DirectiveDescriptor.CreateDirective(
"custom",
DirectiveKind.SingleLine,
b => b.AddTypeToken());
// Act & Assert
ParseCodeBlockTest(
$"@custom {expectedType}",
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, expectedType, markup: false).AsDirectiveToken(descriptor.Tokens[0])));
}
[Theory]
[InlineData("(bool, int)")]
[InlineData("(int aa, string bb)?")]
[InlineData("( int? q , bool w )")]
[InlineData("( int ? q, bool ?w ,(long ? [])) ?")]
[InlineData("(List<(int, string)?> aa, string bb)")]
[InlineData("(string ss, (int u, List<(string, int)> k, (Char c, bool b, List l)), global::System.Int32[] a)")]
public void DirectiveDescriptor_AllowsTupleTypes(string expectedType)
{
// Arrange
var descriptor = DirectiveDescriptor.CreateDirective(
"custom",
DirectiveKind.SingleLine,
b => b.AddTypeToken());
// Act & Assert
ParseCodeBlockTest(
$"@custom {expectedType}",
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, expectedType, markup: false).AsDirectiveToken(descriptor.Tokens[0])));
}
[Fact]
public void DirectiveDescriptor_AllowsTupleTypes_IgnoresTrailingWhitespace()
{
// Arrange
var descriptor = DirectiveDescriptor.CreateDirective(
"custom",
DirectiveKind.SingleLine,
b => b.AddTypeToken());
// Act & Assert
ParseCodeBlockTest(
$"@custom (bool, int?) ",
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, "(bool, int?)", markup: false).AsDirectiveToken(descriptor.Tokens[0]),
Factory.Span(SpanKindInternal.None, " ", markup: false).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(
RazorDiagnosticFactory.CreateParsing_UnexpectedDirectiveLiteral(
new SourceSpan(new SourceLocation(16, 0, 16), contentLength: 7), "custom", "line break"));
// 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.Span(SpanKindInternal.None, " ", markup: false).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(
RazorDiagnosticFactory.CreateParsing_UnexpectedDirectiveLiteral(
new SourceSpan(new SourceLocation(16, 0, 16), contentLength: 5), "custom", "{"));
// 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(
RazorDiagnosticFactory.CreateParsing_UnexpectedEOFAfterDirective(
new SourceSpan(new SourceLocation(15, 0, 15), contentLength: 1), "custom", "{"));
// 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(
RazorDiagnosticFactory.CreateParsing_ExpectedEndOfBlockBeforeEOF(
new SourceSpan(new SourceLocation(16, 0, 16), contentLength: 1), "custom", "}", "{"));
// 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(
new SourceSpan(null, 22 + Environment.NewLine.Length, 1, 0, 16), "tagHelperPrefix");
// 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()
{
ParseBlockTest("@tagHelperPrefix \"\"",
new DirectiveBlock(
Factory.CodeTransition(),
Factory
.MetaCode(SyntaxConstants.CSharp.TagHelperPrefixKeyword)
.Accepts(AcceptedCharactersInternal.None),
Factory
.Span(SpanKindInternal.Markup, " ", markup: false)
.Accepts(AcceptedCharactersInternal.None),
Factory.Code("\"\"")
.AsTagHelperPrefixDirective("\"\"", string.Empty)));
}
[Fact]
public void TagHelperPrefixDirective_Succeeds()
{
ParseBlockTest("@tagHelperPrefix Foo",
new DirectiveBlock(
Factory.CodeTransition(),
Factory
.MetaCode(SyntaxConstants.CSharp.TagHelperPrefixKeyword)
.Accepts(AcceptedCharactersInternal.None),
Factory
.Span(SpanKindInternal.Markup, " ", markup: false)
.Accepts(AcceptedCharactersInternal.None),
Factory.Code("Foo")
.AsTagHelperPrefixDirective("Foo", "Foo")));
}
[Fact]
public void TagHelperPrefixDirective_WithQuotes_Succeeds()
{
ParseBlockTest("@tagHelperPrefix \"Foo\"",
new DirectiveBlock(
Factory.CodeTransition(),
Factory
.MetaCode(SyntaxConstants.CSharp.TagHelperPrefixKeyword)
.Accepts(AcceptedCharactersInternal.None),
Factory
.Span(SpanKindInternal.Markup, " ", markup: false)
.Accepts(AcceptedCharactersInternal.None),
Factory.Code("\"Foo\"")
.AsTagHelperPrefixDirective("\"Foo\"", "Foo")));
}
[Fact]
public void TagHelperPrefixDirective_RequiresValue()
{
// Arrange
var expectedError = RazorDiagnosticFactory.CreateParsing_DirectiveMustHaveValue(
new SourceSpan(filePath: null, absoluteIndex: 1, lineIndex: 0, characterIndex: 1, length: 15), SyntaxConstants.CSharp.TagHelperPrefixKeyword);
// Act & Assert
ParseBlockTest("@tagHelperPrefix ",
new DirectiveBlock(
Factory.CodeTransition(),
Factory
.MetaCode(SyntaxConstants.CSharp.TagHelperPrefixKeyword)
.Accepts(AcceptedCharactersInternal.None),
Factory
.Span(SpanKindInternal.Markup, " ", markup: false)
.Accepts(AcceptedCharactersInternal.None),
Factory.EmptyCSharp()
.AsTagHelperPrefixDirective(string.Empty, string.Empty, expectedError)
.Accepts(AcceptedCharactersInternal.AnyExceptNewline)));
}
[Fact]
public void TagHelperPrefixDirective_StartQuoteRequiresDoubleQuotesAroundValue()
{
// Arrange
var expectedErrors = new[]
{
RazorDiagnosticFactory.CreateParsing_UnterminatedStringLiteral(
new SourceSpan(filePath: null, absoluteIndex: 17, lineIndex: 0, characterIndex: 17, length: 1)),
RazorDiagnosticFactory.CreateParsing_IncompleteQuotesAroundDirective(
new SourceSpan(filePath: null, absoluteIndex: 17, lineIndex: 0, characterIndex: 17, length: 4), SyntaxConstants.CSharp.TagHelperPrefixKeyword),
RazorDiagnosticFactory.CreateParsing_InvalidTagHelperPrefixValue(
new SourceSpan(filePath: null, absoluteIndex: 17, lineIndex: 0, characterIndex: 17, length: 4), SyntaxConstants.CSharp.TagHelperPrefixKeyword, '"', "\"Foo"),
};
// Act & Assert
ParseBlockTest("@tagHelperPrefix \"Foo",
new DirectiveBlock(
Factory.CodeTransition(),
Factory
.MetaCode(SyntaxConstants.CSharp.TagHelperPrefixKeyword)
.Accepts(AcceptedCharactersInternal.None),
Factory
.Span(SpanKindInternal.Markup, " ", markup: false)
.Accepts(AcceptedCharactersInternal.None),
Factory.Code("\"Foo")
.AsTagHelperPrefixDirective("\"Foo", "\"Foo", expectedErrors)));
}
[Fact]
public void TagHelperPrefixDirective_EndQuoteRequiresDoubleQuotesAroundValue()
{
// Arrange
var expectedErrors = new[]
{
RazorDiagnosticFactory.CreateParsing_UnterminatedStringLiteral(
new SourceSpan(filePath: null, absoluteIndex: 23, lineIndex: 0, characterIndex: 23, length: 1)),
RazorDiagnosticFactory.CreateParsing_IncompleteQuotesAroundDirective(
new SourceSpan(filePath: null, absoluteIndex: 17, lineIndex: 0, characterIndex: 17, length: 7), SyntaxConstants.CSharp.TagHelperPrefixKeyword),
RazorDiagnosticFactory.CreateParsing_InvalidTagHelperPrefixValue(
new SourceSpan(filePath: null, absoluteIndex: 17, lineIndex: 0, characterIndex: 17, length: 7), SyntaxConstants.CSharp.TagHelperPrefixKeyword, ' ', "Foo \""),
};
// Act & Assert
ParseBlockTest("@tagHelperPrefix Foo \"",
new DirectiveBlock(
Factory.CodeTransition(),
Factory
.MetaCode(SyntaxConstants.CSharp.TagHelperPrefixKeyword)
.Accepts(AcceptedCharactersInternal.None),
Factory
.Span(SpanKindInternal.Markup, " ", markup: false)
.Accepts(AcceptedCharactersInternal.None),
Factory.Code("Foo \"")
.AsTagHelperPrefixDirective("Foo \"", "Foo \"", expectedErrors)));
}
[Fact]
public void RemoveTagHelperDirective_NoValue_Invalid()
{
var expectedErrors = new[]
{
RazorDiagnosticFactory.CreateParsing_InvalidTagHelperLookupText(
new SourceSpan(new SourceLocation(18, 0, 18), contentLength: 1), string.Empty)
};
ParseBlockTest("@removeTagHelper \"\"",
new DirectiveBlock(
Factory.CodeTransition(),
Factory
.MetaCode(SyntaxConstants.CSharp.RemoveTagHelperKeyword)
.Accepts(AcceptedCharactersInternal.None),
Factory
.Span(SpanKindInternal.Markup, " ", markup: false)
.Accepts(AcceptedCharactersInternal.None),
Factory.Code("\"\"")
.AsRemoveTagHelper(
"\"\"",
string.Empty,
errors: expectedErrors)));
}
[Fact]
public void RemoveTagHelperDirective_InvalidLookupText_AddsError()
{
var expectedErrors = new[]
{
RazorDiagnosticFactory.CreateParsing_InvalidTagHelperLookupText(
new SourceSpan(new SourceLocation(17, 0, 17), contentLength: 3), "Foo")
};
ParseBlockTest("@removeTagHelper Foo",
new DirectiveBlock(
Factory.CodeTransition(),
Factory
.MetaCode(SyntaxConstants.CSharp.RemoveTagHelperKeyword)
.Accepts(AcceptedCharactersInternal.None),
Factory
.Span(SpanKindInternal.Markup, " ", markup: false)
.Accepts(AcceptedCharactersInternal.None),
Factory.Code("Foo")
.AsRemoveTagHelper(
"Foo",
"Foo",
errors: expectedErrors)));
}
[Fact]
public void RemoveTagHelperDirective_SingleQuotes_AddsError()
{
var expectedErrors = new[]
{
RazorDiagnosticFactory.CreateParsing_InvalidTagHelperLookupText(
new SourceSpan(new SourceLocation(17, 0, 17), contentLength: 8), "'*, Foo'")
};
ParseBlockTest("@removeTagHelper '*, Foo'",
new DirectiveBlock(
Factory.CodeTransition(),
Factory.MetaCode(SyntaxConstants.CSharp.RemoveTagHelperKeyword)
.Accepts(AcceptedCharactersInternal.None),
Factory.Span(SpanKindInternal.Markup, " ", markup: false)
.Accepts(AcceptedCharactersInternal.None),
Factory.Code("'*, Foo'")
.AsRemoveTagHelper(
"'*, Foo'",
"'*, Foo'",
errors: expectedErrors)));
}
[Fact]
public void RemoveTagHelperDirective_WithQuotes_InvalidLookupText_AddsError()
{
var expectedErrors = new[]
{
RazorDiagnosticFactory.CreateParsing_InvalidTagHelperLookupText(
new SourceSpan(new SourceLocation(18, 0, 18), contentLength: 3), "Foo")
};
ParseBlockTest("@removeTagHelper \"Foo\"",
new DirectiveBlock(
Factory.CodeTransition(),
Factory
.MetaCode(SyntaxConstants.CSharp.RemoveTagHelperKeyword)
.Accepts(AcceptedCharactersInternal.None),
Factory
.Span(SpanKindInternal.Markup, " ", markup: false)
.Accepts(AcceptedCharactersInternal.None),
Factory.Code("\"Foo\"")
.AsRemoveTagHelper(
"\"Foo\"",
"Foo",
errors: expectedErrors)));
}
[Fact]
public void RemoveTagHelperDirective_SupportsSpaces()
{
ParseBlockTest("@removeTagHelper Foo, Bar ",
new DirectiveBlock(
Factory.CodeTransition(),
Factory.MetaCode(SyntaxConstants.CSharp.RemoveTagHelperKeyword)
.Accepts(AcceptedCharactersInternal.None),
Factory.Span(SpanKindInternal.Markup, " ", markup: false)
.Accepts(AcceptedCharactersInternal.None),
Factory.Code("Foo, Bar ")
.AsRemoveTagHelper(
"Foo, Bar",
"Foo, Bar",
"Foo",
"Bar")
.Accepts(AcceptedCharactersInternal.AnyExceptNewline)));
}
[Fact]
public void RemoveTagHelperDirective_RequiresValue()
{
// Arrange
var expectedErrors = new[]
{
RazorDiagnosticFactory.CreateParsing_DirectiveMustHaveValue(
new SourceSpan(filePath: null, absoluteIndex: 1, lineIndex: 0, characterIndex: 1, length: 15), SyntaxConstants.CSharp.RemoveTagHelperKeyword),
RazorDiagnosticFactory.CreateParsing_InvalidTagHelperLookupText(
new SourceSpan(new SourceLocation(17, 0, 17), contentLength: 1), string.Empty),
};
// Act & Assert
ParseBlockTest("@removeTagHelper ",
new DirectiveBlock(
Factory.CodeTransition(),
Factory.MetaCode(SyntaxConstants.CSharp.RemoveTagHelperKeyword)
.Accepts(AcceptedCharactersInternal.None),
Factory.Span(SpanKindInternal.Markup, " ", markup: false)
.Accepts(AcceptedCharactersInternal.None),
Factory.EmptyCSharp()
.AsRemoveTagHelper(string.Empty, string.Empty, errors: expectedErrors)
.Accepts(AcceptedCharactersInternal.AnyExceptNewline)));
}
[Fact]
public void RemoveTagHelperDirective_StartQuoteRequiresDoubleQuotesAroundValue()
{
// Arrange
var expectedErrors = new[]
{
RazorDiagnosticFactory.CreateParsing_UnterminatedStringLiteral(
new SourceSpan(filePath: null, absoluteIndex: 17, lineIndex: 0, characterIndex: 17, length: 1)),
RazorDiagnosticFactory.CreateParsing_IncompleteQuotesAroundDirective(
new SourceSpan(filePath: null, absoluteIndex: 17, lineIndex: 0, characterIndex: 17, length: 4), SyntaxConstants.CSharp.RemoveTagHelperKeyword),
RazorDiagnosticFactory.CreateParsing_InvalidTagHelperLookupText(
new SourceSpan(new SourceLocation(17, 0, 17), contentLength: 4), "\"Foo"),
};
// Act & Assert
ParseBlockTest("@removeTagHelper \"Foo",
new DirectiveBlock(
Factory.CodeTransition(),
Factory
.MetaCode(SyntaxConstants.CSharp.RemoveTagHelperKeyword)
.Accepts(AcceptedCharactersInternal.None),
Factory
.Span(SpanKindInternal.Markup, " ", markup: false)
.Accepts(AcceptedCharactersInternal.None),
Factory.Code("\"Foo")
.AsRemoveTagHelper("\"Foo", "\"Foo", errors: expectedErrors)));
}
[Fact]
public void RemoveTagHelperDirective_EndQuoteRequiresDoubleQuotesAroundValue()
{
// Arrange
var expectedErrors = new[]
{
RazorDiagnosticFactory.CreateParsing_UnterminatedStringLiteral(
new SourceSpan(new SourceLocation(absoluteIndex: 20, lineIndex: 0, characterIndex: 20), contentLength: 1)),
RazorDiagnosticFactory.CreateParsing_IncompleteQuotesAroundDirective(
new SourceSpan(filePath: null, absoluteIndex: 17, lineIndex: 0, characterIndex: 17, length: 4), SyntaxConstants.CSharp.RemoveTagHelperKeyword),
RazorDiagnosticFactory.CreateParsing_InvalidTagHelperLookupText(
new SourceSpan(new SourceLocation(17, 0, 17), contentLength: 4), "Foo\""),
};
// Act & Assert
ParseBlockTest("@removeTagHelper Foo\"",
new DirectiveBlock(
Factory.CodeTransition(),
Factory
.MetaCode(SyntaxConstants.CSharp.RemoveTagHelperKeyword)
.Accepts(AcceptedCharactersInternal.None),
Factory
.Span(SpanKindInternal.Markup, " ", markup: false)
.Accepts(AcceptedCharactersInternal.None),
Factory.Code("Foo\"")
.AsRemoveTagHelper("Foo\"", "Foo\"", errors: expectedErrors)
.Accepts(AcceptedCharactersInternal.AnyExceptNewline)));
}
[Fact]
public void AddTagHelperDirective_NoValue_Invalid()
{
var expectedErrors = new[]
{
RazorDiagnosticFactory.CreateParsing_InvalidTagHelperLookupText(
new SourceSpan(new SourceLocation(15, 0, 15), contentLength: 1), string.Empty),
};
ParseBlockTest("@addTagHelper \"\"",
new DirectiveBlock(
Factory.CodeTransition(),
Factory
.MetaCode(SyntaxConstants.CSharp.AddTagHelperKeyword)
.Accepts(AcceptedCharactersInternal.None),
Factory
.Span(SpanKindInternal.Markup, " ", markup: false)
.Accepts(AcceptedCharactersInternal.None),
Factory.Code("\"\"")
.AsAddTagHelper(
"\"\"",
string.Empty,
errors: expectedErrors)));
}
[Fact]
public void AddTagHelperDirective_InvalidLookupText_AddsError()
{
var expectedErrors = new[]
{
RazorDiagnosticFactory.CreateParsing_InvalidTagHelperLookupText(
new SourceSpan(new SourceLocation(14, 0, 14), contentLength: 3), "Foo"),
};
ParseBlockTest("@addTagHelper Foo",
new DirectiveBlock(
Factory.CodeTransition(),
Factory.MetaCode(SyntaxConstants.CSharp.AddTagHelperKeyword)
.Accepts(AcceptedCharactersInternal.None),
Factory.Span(SpanKindInternal.Markup, " ", markup: false)
.Accepts(AcceptedCharactersInternal.None),
Factory.Code("Foo")
.AsAddTagHelper(
"Foo",
"Foo",
errors: expectedErrors)));
}
[Fact]
public void AddTagHelperDirective_WithQuotes_InvalidLookupText_AddsError()
{
var expectedErrors = new[]
{
RazorDiagnosticFactory.CreateParsing_InvalidTagHelperLookupText(
new SourceSpan(new SourceLocation(15, 0, 15), contentLength: 3), "Foo")
};
ParseBlockTest("@addTagHelper \"Foo\"",
new DirectiveBlock(
Factory.CodeTransition(),
Factory
.MetaCode(SyntaxConstants.CSharp.AddTagHelperKeyword)
.Accepts(AcceptedCharactersInternal.None),
Factory
.Span(SpanKindInternal.Markup, " ", markup: false)
.Accepts(AcceptedCharactersInternal.None),
Factory.Code("\"Foo\"")
.AsAddTagHelper(
"\"Foo\"",
"Foo",
errors: expectedErrors)));
}
[Fact]
public void AddTagHelperDirective_SingleQuotes_AddsError()
{
var expectedErrors = new[]
{
RazorDiagnosticFactory.CreateParsing_InvalidTagHelperLookupText(
new SourceSpan(new SourceLocation(14, 0, 14), contentLength: 8), "'*, Foo'")
};
ParseBlockTest("@addTagHelper '*, Foo'",
new DirectiveBlock(
Factory.CodeTransition(),
Factory.MetaCode(SyntaxConstants.CSharp.AddTagHelperKeyword)
.Accepts(AcceptedCharactersInternal.None),
Factory.Span(SpanKindInternal.Markup, " ", markup: false)
.Accepts(AcceptedCharactersInternal.None),
Factory.Code("'*, Foo'")
.AsAddTagHelper(
"'*, Foo'",
"'*, Foo'",
errors: expectedErrors)));
}
[Fact]
public void AddTagHelperDirective_SupportsSpaces()
{
ParseBlockTest("@addTagHelper Foo, Bar ",
new DirectiveBlock(
Factory.CodeTransition(),
Factory
.MetaCode(SyntaxConstants.CSharp.AddTagHelperKeyword)
.Accepts(AcceptedCharactersInternal.None),
Factory
.Span(SpanKindInternal.Markup, " ", markup: false)
.Accepts(AcceptedCharactersInternal.None),
Factory.Code("Foo, Bar ")
.AsAddTagHelper(
"Foo, Bar",
"Foo, Bar",
"Foo",
"Bar")
.Accepts(AcceptedCharactersInternal.AnyExceptNewline)));
}
[Fact]
public void AddTagHelperDirective_RequiresValue()
{
// Arrange
var expectedErrors = new[]
{
RazorDiagnosticFactory.CreateParsing_DirectiveMustHaveValue(
new SourceSpan(filePath: null, absoluteIndex: 1, lineIndex: 0, characterIndex: 1, length: 12), SyntaxConstants.CSharp.AddTagHelperKeyword),
RazorDiagnosticFactory.CreateParsing_InvalidTagHelperLookupText(
new SourceSpan(new SourceLocation(14, 0, 14), contentLength: 1), string.Empty),
};
// Act & Assert
ParseBlockTest("@addTagHelper ",
new DirectiveBlock(
Factory.CodeTransition(),
Factory
.MetaCode(SyntaxConstants.CSharp.AddTagHelperKeyword)
.Accepts(AcceptedCharactersInternal.None),
Factory
.Span(SpanKindInternal.Markup, " ", markup: false)
.Accepts(AcceptedCharactersInternal.None),
Factory.EmptyCSharp()
.AsAddTagHelper(string.Empty, string.Empty, errors: expectedErrors)
.Accepts(AcceptedCharactersInternal.AnyExceptNewline)));
}
[Fact]
public void AddTagHelperDirective_StartQuoteRequiresDoubleQuotesAroundValue()
{
// Arrange
var expectedErrors = new[]
{
RazorDiagnosticFactory.CreateParsing_UnterminatedStringLiteral(
new SourceSpan(filePath: null, absoluteIndex: 14, lineIndex: 0, characterIndex: 14, length: 1)),
RazorDiagnosticFactory.CreateParsing_IncompleteQuotesAroundDirective(
new SourceSpan(filePath: null, absoluteIndex: 14, lineIndex: 0, characterIndex: 14, length: 4), SyntaxConstants.CSharp.AddTagHelperKeyword),
RazorDiagnosticFactory.CreateParsing_InvalidTagHelperLookupText(
new SourceSpan(new SourceLocation(14, 0, 14), contentLength: 4), "\"Foo"),
};
// Act & Assert
ParseBlockTest("@addTagHelper \"Foo",
new DirectiveBlock(
Factory.CodeTransition(),
Factory
.MetaCode(SyntaxConstants.CSharp.AddTagHelperKeyword)
.Accepts(AcceptedCharactersInternal.None),
Factory
.Span(SpanKindInternal.Markup, " ", markup: false)
.Accepts(AcceptedCharactersInternal.None),
Factory.Code("\"Foo")
.AsAddTagHelper("\"Foo", "\"Foo", errors: expectedErrors)));
}
[Fact]
public void AddTagHelperDirective_EndQuoteRequiresDoubleQuotesAroundValue()
{
// Arrange
var expectedErrors = new[]
{
RazorDiagnosticFactory.CreateParsing_UnterminatedStringLiteral(
new SourceSpan(filePath: null, absoluteIndex: 17, lineIndex: 0, characterIndex: 17, length: 1)),
RazorDiagnosticFactory.CreateParsing_IncompleteQuotesAroundDirective(
new SourceSpan(filePath: null, absoluteIndex: 14, lineIndex: 0, characterIndex: 14, length: 4), SyntaxConstants.CSharp.AddTagHelperKeyword),
RazorDiagnosticFactory.CreateParsing_InvalidTagHelperLookupText(
new SourceSpan(new SourceLocation(14, 0, 14), contentLength: 4), "Foo\""),
};
// Act & Assert
ParseBlockTest("@addTagHelper Foo\"",
new DirectiveBlock(
Factory.CodeTransition(),
Factory
.MetaCode(SyntaxConstants.CSharp.AddTagHelperKeyword)
.Accepts(AcceptedCharactersInternal.None),
Factory
.Span(SpanKindInternal.Markup, " ", markup: false)
.Accepts(AcceptedCharactersInternal.None),
Factory.Code("Foo\"")
.AsAddTagHelper("Foo\"", "Foo\"", errors: expectedErrors)
.Accepts(AcceptedCharactersInternal.AnyExceptNewline)));
}
[Fact]
public void InheritsDirectiveSupportsArrays()
{
ParseDocumentTest(
"@inherits string[[]][]",
new[] { InheritsDirective.Directive, },
new MarkupBlock(
Factory.EmptyHtml(),
new DirectiveBlock(new DirectiveChunkGenerator(InheritsDirective.Directive),
Factory.CodeTransition(),
Factory.MetaCode("inherits").Accepts(AcceptedCharactersInternal.None),
Factory.Span(SpanKindInternal.Code, " ", CSharpSymbolType.WhiteSpace).Accepts(AcceptedCharactersInternal.WhiteSpace),
Factory.Span(SpanKindInternal.Code, "string[[]][]", markup: false).AsDirectiveToken(InheritsDirective.Directive.Tokens.First())),
Factory.EmptyHtml()));
}
[Fact]
public void InheritsDirectiveSupportsNestedGenerics()
{
ParseDocumentTest(
"@inherits System.Web.Mvc.WebViewPage>",
new[] { InheritsDirective.Directive, },
new MarkupBlock(
Factory.EmptyHtml(),
new DirectiveBlock(new DirectiveChunkGenerator(InheritsDirective.Directive),
Factory.CodeTransition(),
Factory.MetaCode("inherits").Accepts(AcceptedCharactersInternal.None),
Factory.Span(SpanKindInternal.Code, " ", CSharpSymbolType.WhiteSpace).Accepts(AcceptedCharactersInternal.WhiteSpace),
Factory.Span(SpanKindInternal.Code, "System.Web.Mvc.WebViewPage>", markup: false)
.AsDirectiveToken(InheritsDirective.Directive.Tokens.First())),
Factory.EmptyHtml()));
}
[Fact]
public void InheritsDirectiveSupportsTypeKeywords()
{
ParseDocumentTest(
"@inherits string",
new[] { InheritsDirective.Directive, },
new MarkupBlock(
Factory.EmptyHtml(),
new DirectiveBlock(new DirectiveChunkGenerator(InheritsDirective.Directive),
Factory.CodeTransition(),
Factory.MetaCode("inherits").Accepts(AcceptedCharactersInternal.None),
Factory.Span(SpanKindInternal.Code, " ", CSharpSymbolType.WhiteSpace).Accepts(AcceptedCharactersInternal.WhiteSpace),
Factory.Span(SpanKindInternal.Code, "string", markup: false)
.AsDirectiveToken(InheritsDirective.Directive.Tokens.First())),
Factory.EmptyHtml()));
}
[Fact]
public void Parse_FunctionsDirective()
{
ParseCodeBlockTest(
"@functions { foo(); bar(); }",
new[] { FunctionsDirective.Directive, },
new DirectiveBlock(new DirectiveChunkGenerator(FunctionsDirective.Directive),
Factory.CodeTransition(),
Factory.MetaCode("functions").Accepts(AcceptedCharactersInternal.None),
Factory.Span(SpanKindInternal.Markup, " ", CSharpSymbolType.WhiteSpace).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 EmptyFunctionsDirective()
{
ParseCodeBlockTest(
"@functions { }",
new[] { FunctionsDirective.Directive, },
new DirectiveBlock(new DirectiveChunkGenerator(FunctionsDirective.Directive),
Factory.CodeTransition(),
Factory.MetaCode("functions").Accepts(AcceptedCharactersInternal.None),
Factory.Span(SpanKindInternal.Markup, " ", CSharpSymbolType.WhiteSpace).Accepts(AcceptedCharactersInternal.AllWhiteSpace),
Factory.MetaCode("{").AutoCompleteWith(null, atEndOfSpan: true).Accepts(AcceptedCharactersInternal.None),
Factory.Code(" ").AsStatement(),
Factory.MetaCode("}").Accepts(AcceptedCharactersInternal.None)));
}
[Fact]
public void Parse_SectionDirective()
{
ParseCodeBlockTest(
"@section Header { F{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),
Factory.Span(SpanKindInternal.Code, string.Empty, CSharpSymbolType.Unknown)
.AsDirectiveToken(descriptor.Tokens[0])));
}
[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)));
}
public static TheoryData InvalidTagHelperPrefixData
{
get
{
var directiveLocation = new SourceLocation(1, 2, 3);
RazorDiagnostic InvalidPrefixError(int length, char character, string prefix)
{
return RazorDiagnosticFactory.CreateParsing_InvalidTagHelperPrefixValue(
new SourceSpan(directiveLocation, length), SyntaxConstants.CSharp.TagHelperPrefixKeyword, character, prefix);
}
return new TheoryData>
{
{
"th ",
directiveLocation,
new[]
{
InvalidPrefixError(3, ' ', "th "),
}
},
{
"th\t",
directiveLocation,
new[]
{
InvalidPrefixError(3, '\t', "th\t"),
}
},
{
"th" + Environment.NewLine,
directiveLocation,
new[]
{
InvalidPrefixError(2 + Environment.NewLine.Length, Environment.NewLine[0], "th" + Environment.NewLine),
}
},
{
" th ",
directiveLocation,
new[]
{
InvalidPrefixError(4, ' ', " th "),
}
},
{
"@",
directiveLocation,
new[]
{
InvalidPrefixError(1, '@', "@"),
}
},
{
"t@h",
directiveLocation,
new[]
{
InvalidPrefixError(3, '@', "t@h"),
}
},
{
"!",
directiveLocation,
new[]
{
InvalidPrefixError(1, '!', "!"),
}
},
{
"!th",
directiveLocation,
new[]
{
InvalidPrefixError(3, '!', "!th"),
}
},
};
}
}
[Theory]
[MemberData(nameof(InvalidTagHelperPrefixData))]
public void ValidateTagHelperPrefix_ValidatesPrefix(
string directiveText,
SourceLocation directiveLocation,
object expectedErrors)
{
// Arrange
var expectedDiagnostics = (IEnumerable)expectedErrors;
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 directive = new CSharpCodeParser.ParsedDirective()
{
DirectiveText = directiveText
};
var diagnostics = new List();
var expectedError = RazorDiagnosticFactory.CreateParsing_InvalidTagHelperLookupText(
new SourceSpan(new SourceLocation(1, 2, 3), errorLength), directiveText);
// 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,
IEnumerable descriptors,
Block expected,
params RazorDiagnostic[] expectedErrors)
{
var result = ParseCodeBlock(document, descriptors, designTime: false);
EvaluateResults(result, expected, expectedErrors);
}
}
}