Add file scoped multiple occurring extensible directives.

- Updated existing singly occurring tests that verified that the directives must occur prior to any content to be theories that tested the joint "file scoped" behavior of the usage.
- Added tests that validate `FileScopedMultipleOccurring` can have duplicate directives without erroring.

#1377
This commit is contained in:
N. Taylor Mullen 2017-06-26 17:31:37 -07:00
parent d73ecf8b36
commit c2c95ec175
3 changed files with 127 additions and 13 deletions

View File

@ -18,5 +18,10 @@ namespace Microsoft.AspNetCore.Razor.Language
/// <see cref="DirectiveKind.SingleLine"/>, the last occurrence of the directive is imported. /// <see cref="DirectiveKind.SingleLine"/>, the last occurrence of the directive is imported.
/// </summary> /// </summary>
FileScopedSinglyOccurring, FileScopedSinglyOccurring,
/// <summary>
/// Directive must exist prior to any HTML or code.
/// </summary>
FileScopedMultipleOccurring,
} }
} }

View File

@ -1785,7 +1785,11 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
UsageError(Resources.FormatDuplicateDirective(descriptor.Directive)); UsageError(Resources.FormatDuplicateDirective(descriptor.Directive));
return; return;
} }
}
if (descriptor.Usage == DirectiveUsage.FileScopedSinglyOccurring ||
descriptor.Usage == DirectiveUsage.FileScopedMultipleOccurring)
{
var root = Context.Builder.ActiveBlocks.Last(); var root = Context.Builder.ActiveBlocks.Last();
for (var i = 0; i < root.Children.Count; i++) for (var i = 0; i < root.Children.Count; i++)
{ {

View File

@ -12,7 +12,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
public class CSharpDirectivesTest : CsHtmlCodeParserTestBase public class CSharpDirectivesTest : CsHtmlCodeParserTestBase
{ {
[Fact] [Fact]
public void DirectiveDescriptor_FileScopedSinglyOccurring_ErrorsIfNestedInCode() public void DirectiveDescriptor_FileScopedMultipleOccurring_CanHaveDuplicates()
{ {
// Arrange // Arrange
var descriptor = DirectiveDescriptor.CreateDirective( var descriptor = DirectiveDescriptor.CreateDirective(
@ -20,7 +20,106 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
DirectiveKind.SingleLine, DirectiveKind.SingleLine,
builder => builder =>
{ {
builder.Usage = DirectiveUsage.FileScopedSinglyOccurring; 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_FileScopedMultipleOccurring_MixedContentErrors()
{
// Arrange
var customDescriptor = DirectiveDescriptor.CreateDirective(
"custom",
DirectiveKind.SingleLine,
builder =>
{
builder.Usage = DirectiveUsage.FileScopedMultipleOccurring;
builder.AddTypeToken();
});
var chunkGenerator = new DirectiveChunkGenerator(customDescriptor);
chunkGenerator.Diagnostics.Add(
RazorDiagnostic.Create(
new RazorError(
Resources.FormatDirectiveMustExistBeforeMarkupOrCode("custom"),
151 + Environment.NewLine.Length * 4, 4, 0, 7)));
// Act & Assert
ParseDocumentTest(
@"@custom System.Text.Encoding.ASCIIEncoding
@* There is invalid content beneath this *@
<p>Should cause error</p>
@* There is invalid content above this *@
@custom Else",
new[] { customDescriptor },
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 CommentBlock(
Factory.MarkupTransition(HtmlSymbolType.RazorCommentTransition).Accepts(AcceptedCharactersInternal.None),
Factory.MetaMarkup("*", HtmlSymbolType.RazorCommentStar).Accepts(AcceptedCharactersInternal.None),
Factory.Span(SpanKindInternal.Comment, new HtmlSymbol(" There is invalid content 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),
BlockFactory.MarkupTagBlock("<p>"),
Factory.Markup("Should cause error"),
BlockFactory.MarkupTagBlock("</p>"),
Factory.Markup(Environment.NewLine),
new CommentBlock(
Factory.MarkupTransition(HtmlSymbolType.RazorCommentTransition).Accepts(AcceptedCharactersInternal.None),
Factory.MetaMarkup("*", HtmlSymbolType.RazorCommentStar).Accepts(AcceptedCharactersInternal.None),
Factory.Span(SpanKindInternal.Comment, new HtmlSymbol(" There is invalid content above this ", HtmlSymbolType.RazorComment)).Accepts(AcceptedCharactersInternal.Any),
Factory.MetaMarkup("*", HtmlSymbolType.RazorCommentStar).Accepts(AcceptedCharactersInternal.None),
Factory.MarkupTransition(HtmlSymbolType.RazorCommentTransition).Accepts(AcceptedCharactersInternal.None)),
Factory.Markup(Environment.NewLine).With(SpanChunkGenerator.Null),
new DirectiveBlock(chunkGenerator,
Factory.CodeTransition(),
Factory.MetaCode("custom").Accepts(AcceptedCharactersInternal.None),
Factory.Span(SpanKindInternal.Code, " ", markup: false).Accepts(AcceptedCharactersInternal.WhiteSpace),
Factory.Span(SpanKindInternal.Code, "Else", markup: false).AsDirectiveToken(customDescriptor.Tokens[0])),
Factory.EmptyHtml()));
}
[Theory]
[InlineData(DirectiveUsage.FileScopedSinglyOccurring)]
[InlineData(DirectiveUsage.FileScopedMultipleOccurring)]
public void DirectiveDescriptor_FileScopedSinglyOccurring_ErrorsIfNestedInCode(DirectiveUsage directiveUsage)
{
// Arrange
var descriptor = DirectiveDescriptor.CreateDirective(
"custom",
DirectiveKind.SingleLine,
builder =>
{
builder.Usage = directiveUsage;
builder.AddTypeToken(); builder.AddTypeToken();
}); });
var chunkGenerator = new DirectiveChunkGenerator(descriptor); var chunkGenerator = new DirectiveChunkGenerator(descriptor);
@ -51,8 +150,10 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
Factory.MetaCode("}").Accepts(AcceptedCharactersInternal.None))); Factory.MetaCode("}").Accepts(AcceptedCharactersInternal.None)));
} }
[Fact] [Theory]
public void DirectiveDescriptor_FileScopedSinglyOccurring_ErrorsIfNestedInHtml() [InlineData(DirectiveUsage.FileScopedSinglyOccurring)]
[InlineData(DirectiveUsage.FileScopedMultipleOccurring)]
public void DirectiveDescriptor_FileScoped_ErrorsIfNestedInHtml(DirectiveUsage directiveUsage)
{ {
// Arrange // Arrange
var descriptor = DirectiveDescriptor.CreateDirective( var descriptor = DirectiveDescriptor.CreateDirective(
@ -60,7 +161,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
DirectiveKind.SingleLine, DirectiveKind.SingleLine,
builder => builder =>
{ {
builder.Usage = DirectiveUsage.FileScopedSinglyOccurring; builder.Usage = directiveUsage;
builder.AddTypeToken(); builder.AddTypeToken();
}); });
var chunkGenerator = new DirectiveChunkGenerator(descriptor); var chunkGenerator = new DirectiveChunkGenerator(descriptor);
@ -129,8 +230,10 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
Factory.EmptyHtml())); Factory.EmptyHtml()));
} }
[Fact] [Theory]
public void DirectiveDescriptor_FileScopedSinglyOccurring_CanBeBeneathOtherDirectives() [InlineData(DirectiveUsage.FileScopedSinglyOccurring)]
[InlineData(DirectiveUsage.FileScopedMultipleOccurring)]
public void DirectiveDescriptor_FileScoped_CanBeBeneathOtherDirectives(DirectiveUsage directiveUsage)
{ {
// Arrange // Arrange
var customDescriptor = DirectiveDescriptor.CreateDirective( var customDescriptor = DirectiveDescriptor.CreateDirective(
@ -138,7 +241,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
DirectiveKind.SingleLine, DirectiveKind.SingleLine,
builder => builder =>
{ {
builder.Usage = DirectiveUsage.FileScopedSinglyOccurring; builder.Usage = directiveUsage;
builder.AddTypeToken(); builder.AddTypeToken();
}); });
var somethingDescriptor = DirectiveDescriptor.CreateDirective( var somethingDescriptor = DirectiveDescriptor.CreateDirective(
@ -146,7 +249,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
DirectiveKind.SingleLine, DirectiveKind.SingleLine,
builder => builder =>
{ {
builder.Usage = DirectiveUsage.FileScopedSinglyOccurring; builder.Usage = directiveUsage;
builder.AddMemberToken(); builder.AddMemberToken();
}); });
@ -172,8 +275,10 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
Factory.EmptyHtml())); Factory.EmptyHtml()));
} }
[Fact] [Theory]
public void DirectiveDescriptor_FileScopedSinglyOccurring_CanBeBeneathOtherWhiteSpaceCommentsAndDirectives() [InlineData(DirectiveUsage.FileScopedSinglyOccurring)]
[InlineData(DirectiveUsage.FileScopedMultipleOccurring)]
public void DirectiveDescriptor_FileScoped_CanBeBeneathOtherWhiteSpaceCommentsAndDirectives(DirectiveUsage directiveUsage)
{ {
// Arrange // Arrange
var customDescriptor = DirectiveDescriptor.CreateDirective( var customDescriptor = DirectiveDescriptor.CreateDirective(
@ -181,7 +286,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
DirectiveKind.SingleLine, DirectiveKind.SingleLine,
builder => builder =>
{ {
builder.Usage = DirectiveUsage.FileScopedSinglyOccurring; builder.Usage = directiveUsage;
builder.AddTypeToken(); builder.AddTypeToken();
}); });
var somethingDescriptor = DirectiveDescriptor.CreateDirective( var somethingDescriptor = DirectiveDescriptor.CreateDirective(
@ -189,7 +294,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
DirectiveKind.SingleLine, DirectiveKind.SingleLine,
builder => builder =>
{ {
builder.Usage = DirectiveUsage.FileScopedSinglyOccurring; builder.Usage = directiveUsage;
builder.AddMemberToken(); builder.AddMemberToken();
}); });