Add file scoped extensible directives.
- Added `DirectiveUsage` to enable extensible directive authors to indicate how their directives should be used. Currently support `Unrestricted` (how section directives have always worked) and a file scoped singly occurring directive. - Added directive parsing tests. - Removed no longer used `BlockKindInternal` items. #1376
This commit is contained in:
parent
e3b3e20738
commit
2453689804
|
|
@ -38,6 +38,11 @@ namespace Microsoft.AspNetCore.Razor.Language
|
|||
/// </summary>
|
||||
public abstract DirectiveKind Kind { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the way a directive can be used. The usage determines how many, and where directives can exist per document.
|
||||
/// </summary>
|
||||
public abstract DirectiveUsage Usage { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of directive tokens that can follow the directive keyword.
|
||||
/// </summary>
|
||||
|
|
@ -189,6 +194,8 @@ namespace Microsoft.AspNetCore.Razor.Language
|
|||
|
||||
public DirectiveKind Kind { get; }
|
||||
|
||||
public DirectiveUsage Usage { get; set; }
|
||||
|
||||
public IList<DirectiveTokenDescriptor> Tokens { get; }
|
||||
|
||||
public DirectiveDescriptor Build()
|
||||
|
|
@ -218,7 +225,7 @@ namespace Microsoft.AspNetCore.Razor.Language
|
|||
}
|
||||
}
|
||||
|
||||
return new DefaultDirectiveDescriptor(Directive, Kind, Tokens.ToArray(), DisplayName, Description);
|
||||
return new DefaultDirectiveDescriptor(Directive, Kind, Usage, Tokens.ToArray(), DisplayName, Description);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -227,12 +234,14 @@ namespace Microsoft.AspNetCore.Razor.Language
|
|||
public DefaultDirectiveDescriptor(
|
||||
string directive,
|
||||
DirectiveKind kind,
|
||||
DirectiveUsage usage,
|
||||
DirectiveTokenDescriptor[] tokens,
|
||||
string displayName,
|
||||
string description)
|
||||
{
|
||||
Directive = directive;
|
||||
Kind = kind;
|
||||
Usage = usage;
|
||||
Tokens = tokens;
|
||||
DisplayName = displayName;
|
||||
Description = description;
|
||||
|
|
@ -246,6 +255,8 @@ namespace Microsoft.AspNetCore.Razor.Language
|
|||
|
||||
public override DirectiveKind Kind { get; }
|
||||
|
||||
public override DirectiveUsage Usage { get; }
|
||||
|
||||
public override IReadOnlyList<DirectiveTokenDescriptor> Tokens { get; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,11 @@
|
|||
// 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.
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.Language
|
||||
{
|
||||
public enum DirectiveUsage
|
||||
{
|
||||
Unrestricted,
|
||||
FileScopedSinglyOccurring,
|
||||
}
|
||||
}
|
||||
|
|
@ -30,6 +30,11 @@ namespace Microsoft.AspNetCore.Razor.Language
|
|||
/// </summary>
|
||||
DirectiveKind Kind { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the directive usage. The usage determines how many, and where directives can exist per document.
|
||||
/// </summary>
|
||||
DirectiveUsage Usage { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of the directive tokens.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -8,13 +8,10 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
// Code
|
||||
Statement,
|
||||
Directive,
|
||||
Functions,
|
||||
Expression,
|
||||
Helper,
|
||||
|
||||
// Markup
|
||||
Markup,
|
||||
Section,
|
||||
Template,
|
||||
|
||||
// Special
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
|
||||
private Dictionary<string, Action> _directiveParsers = new Dictionary<string, Action>(StringComparer.Ordinal);
|
||||
private Dictionary<CSharpKeyword, Action<bool>> _keywordParsers = new Dictionary<CSharpKeyword, Action<bool>>();
|
||||
private HashSet<string> _seenDirectives = new HashSet<string>(StringComparer.Ordinal);
|
||||
|
||||
public CSharpCodeParser(ParserContext context)
|
||||
: this(directives: Enumerable.Empty<DirectiveDescriptor>(), context: context)
|
||||
|
|
@ -91,7 +92,12 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
{
|
||||
foreach (var directive in directives)
|
||||
{
|
||||
_directiveParsers.Add(directive, handler);
|
||||
_directiveParsers.Add(directive, () =>
|
||||
{
|
||||
handler();
|
||||
_seenDirectives.Add(directive);
|
||||
});
|
||||
|
||||
Keywords.Add(directive);
|
||||
|
||||
// These C# keywords are reserved for use in directives. It's an error to use them outside of
|
||||
|
|
@ -1589,10 +1595,13 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
AcceptAndMoveNext();
|
||||
Output(SpanKindInternal.MetaCode, AcceptedCharactersInternal.None);
|
||||
|
||||
// Even if an error was logged do not bail out early. If a directive was used incorrectly it doesn't mean it can't be parsed.
|
||||
ValidateDirectiveUsage(descriptor);
|
||||
|
||||
for (var i = 0; i < descriptor.Tokens.Count; i++)
|
||||
{
|
||||
if (!At(CSharpSymbolType.WhiteSpace) &&
|
||||
!At(CSharpSymbolType.NewLine) &&
|
||||
!At(CSharpSymbolType.NewLine) &&
|
||||
!EndOfFile)
|
||||
{
|
||||
Context.ErrorSink.OnError(
|
||||
|
|
@ -1767,6 +1776,56 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
private void ValidateDirectiveUsage(DirectiveDescriptor descriptor)
|
||||
{
|
||||
if (descriptor.Usage == DirectiveUsage.FileScopedSinglyOccurring)
|
||||
{
|
||||
if (_seenDirectives.Contains(descriptor.Directive))
|
||||
{
|
||||
UsageError(Resources.FormatDuplicateDirective(descriptor.Directive));
|
||||
return;
|
||||
}
|
||||
|
||||
var root = Context.Builder.ActiveBlocks.Last();
|
||||
for (var i = 0; i < root.Children.Count; i++)
|
||||
{
|
||||
// Directives, comments and whitespace are valid prior to an unnested directive.
|
||||
|
||||
var child = root.Children[i];
|
||||
if (child is Legacy.Block block)
|
||||
{
|
||||
if (block.Type == BlockKindInternal.Directive || block.Type == BlockKindInternal.Comment)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if (child is Span span)
|
||||
{
|
||||
if (span.Length == 0 ||
|
||||
span.Kind == SpanKindInternal.Comment ||
|
||||
span.Symbols.All(symbol => string.IsNullOrWhiteSpace(symbol.Content)))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
UsageError(Resources.FormatDirectiveMustExistBeforeMarkupOrCode(descriptor.Directive));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
void UsageError(string message)
|
||||
{
|
||||
// There wil always be at least 1 child because of the `@` transition.
|
||||
var directiveStart = Context.Builder.CurrentBlock.Children.First().Start;
|
||||
var errorLength = /* @ */ 1 + descriptor.Directive.Length;
|
||||
Context.ErrorSink.OnError(directiveStart, message, errorLength);
|
||||
}
|
||||
}
|
||||
|
||||
private void ParseDirectiveBlock(DirectiveDescriptor descriptor, Action<SourceLocation> parseChildren)
|
||||
{
|
||||
if (EndOfFile)
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
_endBlock = EndBlock;
|
||||
}
|
||||
|
||||
public IEnumerable<BlockBuilder> ActiveBlocks => _blockStack;
|
||||
public IReadOnlyCollection<BlockBuilder> ActiveBlocks => _blockStack;
|
||||
|
||||
public BlockBuilder CurrentBlock => _blockStack.Peek();
|
||||
|
||||
|
|
|
|||
|
|
@ -514,6 +514,34 @@ namespace Microsoft.AspNetCore.Razor.Language
|
|||
internal static string FormatIntermediateNodeReference_CollectionIsReadOnly(object p0)
|
||||
=> string.Format(CultureInfo.CurrentCulture, GetString("IntermediateNodeReference_CollectionIsReadOnly"), p0);
|
||||
|
||||
/// <summary>
|
||||
/// The '{0}' directive may only occur once per document.
|
||||
/// </summary>
|
||||
internal static string DuplicateDirective
|
||||
{
|
||||
get => GetString("DuplicateDirective");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The '{0}' directive may only occur once per document.
|
||||
/// </summary>
|
||||
internal static string FormatDuplicateDirective(object p0)
|
||||
=> string.Format(CultureInfo.CurrentCulture, GetString("DuplicateDirective"), p0);
|
||||
|
||||
/// <summary>
|
||||
/// The '{0}' directive must exist prior to markup or code.
|
||||
/// </summary>
|
||||
internal static string DirectiveMustExistBeforeMarkupOrCode
|
||||
{
|
||||
get => GetString("DirectiveMustExistBeforeMarkupOrCode");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The '{0}' directive must exist prior to markup or code.
|
||||
/// </summary>
|
||||
internal static string FormatDirectiveMustExistBeforeMarkupOrCode(object p0)
|
||||
=> string.Format(CultureInfo.CurrentCulture, GetString("DirectiveMustExistBeforeMarkupOrCode"), p0);
|
||||
|
||||
private static string GetString(string name, params string[] formatterNames)
|
||||
{
|
||||
var value = _resourceManager.GetString(name);
|
||||
|
|
|
|||
|
|
@ -225,4 +225,10 @@
|
|||
<data name="IntermediateNodeReference_CollectionIsReadOnly" xml:space="preserve">
|
||||
<value>The node '{0}' has a read-only child collection and cannot be modified.</value>
|
||||
</data>
|
||||
<data name="DuplicateDirective" xml:space="preserve">
|
||||
<value>The '{0}' directive may only occur once per document.</value>
|
||||
</data>
|
||||
<data name="DirectiveMustExistBeforeMarkupOrCode" xml:space="preserve">
|
||||
<value>The '{0}' directive must exist prior to markup or code.</value>
|
||||
</data>
|
||||
</root>
|
||||
|
|
@ -30,7 +30,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
var expected = new ExpressionChunkGenerator();
|
||||
var builder = new BlockBuilder()
|
||||
{
|
||||
Type = BlockKindInternal.Helper,
|
||||
Type = BlockKindInternal.Statement,
|
||||
ChunkGenerator = expected
|
||||
};
|
||||
|
||||
|
|
@ -48,7 +48,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
var expected = new SpanBuilder(SourceLocation.Undefined) { Kind = SpanKindInternal.Code }.Build();
|
||||
var builder = new BlockBuilder()
|
||||
{
|
||||
Type = BlockKindInternal.Functions
|
||||
Type = BlockKindInternal.Statement
|
||||
};
|
||||
builder.Children.Add(expected);
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,295 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
{
|
||||
public class CSharpDirectivesTest : CsHtmlCodeParserTestBase
|
||||
{
|
||||
[Fact]
|
||||
public void DirectiveDescriptor_SinglePreContent_ErrorsIfNestedInCode()
|
||||
{
|
||||
// Arrange
|
||||
var descriptor = DirectiveDescriptor.CreateDirective(
|
||||
"custom",
|
||||
DirectiveKind.SingleLine,
|
||||
builder =>
|
||||
{
|
||||
builder.Usage = DirectiveUsage.SinglePreContent;
|
||||
builder.AddTypeToken();
|
||||
});
|
||||
var chunkGenerator = new DirectiveChunkGenerator(descriptor);
|
||||
chunkGenerator.Diagnostics.Add(
|
||||
RazorDiagnostic.Create(
|
||||
new RazorError(
|
||||
Resources.FormatDirectiveMustExistBeforeMarkupOrCode("custom"),
|
||||
1 + Environment.NewLine.Length, 1, 0, 7)));
|
||||
|
||||
// Act & Assert
|
||||
ParseCodeBlockTest(
|
||||
@"{
|
||||
@custom System.Text.Encoding.ASCIIEncoding
|
||||
}",
|
||||
new[] { descriptor },
|
||||
new StatementBlock(
|
||||
Factory.MetaCode("{").Accepts(AcceptedCharactersInternal.None),
|
||||
Factory.Code(Environment.NewLine)
|
||||
.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.MetaCode(Environment.NewLine).Accepts(AcceptedCharactersInternal.WhiteSpace)),
|
||||
Factory.EmptyCSharp().AsStatement(),
|
||||
Factory.MetaCode("}").Accepts(AcceptedCharactersInternal.None)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DirectiveDescriptor_SinglePreContent_ErrorsIfNestedInHtml()
|
||||
{
|
||||
// Arrange
|
||||
var descriptor = DirectiveDescriptor.CreateDirective(
|
||||
"custom",
|
||||
DirectiveKind.SingleLine,
|
||||
builder =>
|
||||
{
|
||||
builder.Usage = DirectiveUsage.SinglePreContent;
|
||||
builder.AddTypeToken();
|
||||
});
|
||||
var chunkGenerator = new DirectiveChunkGenerator(descriptor);
|
||||
chunkGenerator.Diagnostics.Add(
|
||||
RazorDiagnostic.Create(
|
||||
new RazorError(
|
||||
Resources.FormatDirectiveMustExistBeforeMarkupOrCode("custom"),
|
||||
3 + Environment.NewLine.Length, 1, 0, 7)));
|
||||
|
||||
// Act & Assert
|
||||
ParseDocumentTest(
|
||||
@"<p>
|
||||
@custom System.Text.Encoding.ASCIIEncoding
|
||||
</p>",
|
||||
new[] { descriptor },
|
||||
new MarkupBlock(
|
||||
BlockFactory.MarkupTagBlock("<p>"),
|
||||
Factory.Markup(Environment.NewLine),
|
||||
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.MetaCode(Environment.NewLine).Accepts(AcceptedCharactersInternal.WhiteSpace)),
|
||||
BlockFactory.MarkupTagBlock("</p>")));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DirectiveDescriptor_SinglePreContent_ErrorsIfDuplicate()
|
||||
{
|
||||
// Arrange
|
||||
var descriptor = DirectiveDescriptor.CreateDirective(
|
||||
"custom",
|
||||
DirectiveKind.SingleLine,
|
||||
builder =>
|
||||
{
|
||||
builder.Usage = DirectiveUsage.SinglePreContent;
|
||||
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()));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DirectiveDescriptor_SinglePreContent_CanBeBeneathOtherDirectives()
|
||||
{
|
||||
// Arrange
|
||||
var customDescriptor = DirectiveDescriptor.CreateDirective(
|
||||
"custom",
|
||||
DirectiveKind.SingleLine,
|
||||
builder =>
|
||||
{
|
||||
builder.Usage = DirectiveUsage.SinglePreContent;
|
||||
builder.AddTypeToken();
|
||||
});
|
||||
var somethingDescriptor = DirectiveDescriptor.CreateDirective(
|
||||
"something",
|
||||
DirectiveKind.SingleLine,
|
||||
builder =>
|
||||
{
|
||||
builder.Usage = DirectiveUsage.SinglePreContent;
|
||||
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()));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DirectiveDescriptor_SinglePreContent_CanBeBeneathOtherWhiteSpaceCommentsAndDirectives()
|
||||
{
|
||||
// Arrange
|
||||
var customDescriptor = DirectiveDescriptor.CreateDirective(
|
||||
"custom",
|
||||
DirectiveKind.SingleLine,
|
||||
builder =>
|
||||
{
|
||||
builder.Usage = DirectiveUsage.SinglePreContent;
|
||||
builder.AddTypeToken();
|
||||
});
|
||||
var somethingDescriptor = DirectiveDescriptor.CreateDirective(
|
||||
"something",
|
||||
DirectiveKind.SingleLine,
|
||||
builder =>
|
||||
{
|
||||
builder.Usage = DirectiveUsage.SinglePreContent;
|
||||
builder.AddMemberToken();
|
||||
});
|
||||
|
||||
// Act & Assert
|
||||
ParseDocumentTest(
|
||||
@"@* There are two directives beneath this *@
|
||||
@custom System.Text.Encoding.ASCIIEncoding
|
||||
|
||||
@something Else
|
||||
|
||||
<p>This is extra</p>",
|
||||
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("<p>"),
|
||||
Factory.Markup("This is extra"),
|
||||
BlockFactory.MarkupTagBlock("</p>")));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DirectiveDescriptor_SinglePreContent_MixedContentErrors()
|
||||
{
|
||||
// Arrange
|
||||
var customDescriptor = DirectiveDescriptor.CreateDirective(
|
||||
"custom",
|
||||
DirectiveKind.SingleLine,
|
||||
builder =>
|
||||
{
|
||||
builder.Usage = DirectiveUsage.SinglePreContent;
|
||||
builder.AddTypeToken();
|
||||
});
|
||||
var somethingDescriptor = DirectiveDescriptor.CreateDirective(
|
||||
"something",
|
||||
DirectiveKind.SingleLine,
|
||||
builder =>
|
||||
{
|
||||
builder.Usage = DirectiveUsage.SinglePreContent;
|
||||
builder.AddMemberToken();
|
||||
});
|
||||
var chunkGenerator = new DirectiveChunkGenerator(somethingDescriptor);
|
||||
chunkGenerator.Diagnostics.Add(
|
||||
RazorDiagnostic.Create(
|
||||
new RazorError(
|
||||
Resources.FormatDirectiveMustExistBeforeMarkupOrCode("something"),
|
||||
151 + Environment.NewLine.Length * 4, 4, 0, 10)));
|
||||
|
||||
// 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 *@
|
||||
@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 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("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()));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DirectiveDescriptor_TokensMustBeSeparatedBySpace()
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in New Issue