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>
|
/// </summary>
|
||||||
public abstract DirectiveKind Kind { get; }
|
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>
|
/// <summary>
|
||||||
/// Gets the list of directive tokens that can follow the directive keyword.
|
/// Gets the list of directive tokens that can follow the directive keyword.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -189,6 +194,8 @@ namespace Microsoft.AspNetCore.Razor.Language
|
||||||
|
|
||||||
public DirectiveKind Kind { get; }
|
public DirectiveKind Kind { get; }
|
||||||
|
|
||||||
|
public DirectiveUsage Usage { get; set; }
|
||||||
|
|
||||||
public IList<DirectiveTokenDescriptor> Tokens { get; }
|
public IList<DirectiveTokenDescriptor> Tokens { get; }
|
||||||
|
|
||||||
public DirectiveDescriptor Build()
|
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(
|
public DefaultDirectiveDescriptor(
|
||||||
string directive,
|
string directive,
|
||||||
DirectiveKind kind,
|
DirectiveKind kind,
|
||||||
|
DirectiveUsage usage,
|
||||||
DirectiveTokenDescriptor[] tokens,
|
DirectiveTokenDescriptor[] tokens,
|
||||||
string displayName,
|
string displayName,
|
||||||
string description)
|
string description)
|
||||||
{
|
{
|
||||||
Directive = directive;
|
Directive = directive;
|
||||||
Kind = kind;
|
Kind = kind;
|
||||||
|
Usage = usage;
|
||||||
Tokens = tokens;
|
Tokens = tokens;
|
||||||
DisplayName = displayName;
|
DisplayName = displayName;
|
||||||
Description = description;
|
Description = description;
|
||||||
|
|
@ -246,6 +255,8 @@ namespace Microsoft.AspNetCore.Razor.Language
|
||||||
|
|
||||||
public override DirectiveKind Kind { get; }
|
public override DirectiveKind Kind { get; }
|
||||||
|
|
||||||
|
public override DirectiveUsage Usage { get; }
|
||||||
|
|
||||||
public override IReadOnlyList<DirectiveTokenDescriptor> Tokens { 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>
|
/// </summary>
|
||||||
DirectiveKind Kind { get; }
|
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>
|
/// <summary>
|
||||||
/// Gets a list of the directive tokens.
|
/// Gets a list of the directive tokens.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
|
|
@ -8,13 +8,10 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
// Code
|
// Code
|
||||||
Statement,
|
Statement,
|
||||||
Directive,
|
Directive,
|
||||||
Functions,
|
|
||||||
Expression,
|
Expression,
|
||||||
Helper,
|
|
||||||
|
|
||||||
// Markup
|
// Markup
|
||||||
Markup,
|
Markup,
|
||||||
Section,
|
|
||||||
Template,
|
Template,
|
||||||
|
|
||||||
// Special
|
// Special
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
|
|
||||||
private Dictionary<string, Action> _directiveParsers = new Dictionary<string, Action>(StringComparer.Ordinal);
|
private Dictionary<string, Action> _directiveParsers = new Dictionary<string, Action>(StringComparer.Ordinal);
|
||||||
private Dictionary<CSharpKeyword, Action<bool>> _keywordParsers = new Dictionary<CSharpKeyword, Action<bool>>();
|
private Dictionary<CSharpKeyword, Action<bool>> _keywordParsers = new Dictionary<CSharpKeyword, Action<bool>>();
|
||||||
|
private HashSet<string> _seenDirectives = new HashSet<string>(StringComparer.Ordinal);
|
||||||
|
|
||||||
public CSharpCodeParser(ParserContext context)
|
public CSharpCodeParser(ParserContext context)
|
||||||
: this(directives: Enumerable.Empty<DirectiveDescriptor>(), context: context)
|
: this(directives: Enumerable.Empty<DirectiveDescriptor>(), context: context)
|
||||||
|
|
@ -91,7 +92,12 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
{
|
{
|
||||||
foreach (var directive in directives)
|
foreach (var directive in directives)
|
||||||
{
|
{
|
||||||
_directiveParsers.Add(directive, handler);
|
_directiveParsers.Add(directive, () =>
|
||||||
|
{
|
||||||
|
handler();
|
||||||
|
_seenDirectives.Add(directive);
|
||||||
|
});
|
||||||
|
|
||||||
Keywords.Add(directive);
|
Keywords.Add(directive);
|
||||||
|
|
||||||
// These C# keywords are reserved for use in directives. It's an error to use them outside of
|
// These C# keywords are reserved for use in directives. It's an error to use them outside of
|
||||||
|
|
@ -1589,6 +1595,9 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
AcceptAndMoveNext();
|
AcceptAndMoveNext();
|
||||||
Output(SpanKindInternal.MetaCode, AcceptedCharactersInternal.None);
|
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++)
|
for (var i = 0; i < descriptor.Tokens.Count; i++)
|
||||||
{
|
{
|
||||||
if (!At(CSharpSymbolType.WhiteSpace) &&
|
if (!At(CSharpSymbolType.WhiteSpace) &&
|
||||||
|
|
@ -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)
|
private void ParseDirectiveBlock(DirectiveDescriptor descriptor, Action<SourceLocation> parseChildren)
|
||||||
{
|
{
|
||||||
if (EndOfFile)
|
if (EndOfFile)
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
_endBlock = EndBlock;
|
_endBlock = EndBlock;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<BlockBuilder> ActiveBlocks => _blockStack;
|
public IReadOnlyCollection<BlockBuilder> ActiveBlocks => _blockStack;
|
||||||
|
|
||||||
public BlockBuilder CurrentBlock => _blockStack.Peek();
|
public BlockBuilder CurrentBlock => _blockStack.Peek();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -514,6 +514,34 @@ namespace Microsoft.AspNetCore.Razor.Language
|
||||||
internal static string FormatIntermediateNodeReference_CollectionIsReadOnly(object p0)
|
internal static string FormatIntermediateNodeReference_CollectionIsReadOnly(object p0)
|
||||||
=> string.Format(CultureInfo.CurrentCulture, GetString("IntermediateNodeReference_CollectionIsReadOnly"), 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)
|
private static string GetString(string name, params string[] formatterNames)
|
||||||
{
|
{
|
||||||
var value = _resourceManager.GetString(name);
|
var value = _resourceManager.GetString(name);
|
||||||
|
|
|
||||||
|
|
@ -225,4 +225,10 @@
|
||||||
<data name="IntermediateNodeReference_CollectionIsReadOnly" xml:space="preserve">
|
<data name="IntermediateNodeReference_CollectionIsReadOnly" xml:space="preserve">
|
||||||
<value>The node '{0}' has a read-only child collection and cannot be modified.</value>
|
<value>The node '{0}' has a read-only child collection and cannot be modified.</value>
|
||||||
</data>
|
</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>
|
</root>
|
||||||
|
|
@ -30,7 +30,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
var expected = new ExpressionChunkGenerator();
|
var expected = new ExpressionChunkGenerator();
|
||||||
var builder = new BlockBuilder()
|
var builder = new BlockBuilder()
|
||||||
{
|
{
|
||||||
Type = BlockKindInternal.Helper,
|
Type = BlockKindInternal.Statement,
|
||||||
ChunkGenerator = expected
|
ChunkGenerator = expected
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -48,7 +48,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
var expected = new SpanBuilder(SourceLocation.Undefined) { Kind = SpanKindInternal.Code }.Build();
|
var expected = new SpanBuilder(SourceLocation.Undefined) { Kind = SpanKindInternal.Code }.Build();
|
||||||
var builder = new BlockBuilder()
|
var builder = new BlockBuilder()
|
||||||
{
|
{
|
||||||
Type = BlockKindInternal.Functions
|
Type = BlockKindInternal.Statement
|
||||||
};
|
};
|
||||||
builder.Children.Add(expected);
|
builder.Children.Add(expected);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,295 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
{
|
{
|
||||||
public class CSharpDirectivesTest : CsHtmlCodeParserTestBase
|
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]
|
[Fact]
|
||||||
public void DirectiveDescriptor_TokensMustBeSeparatedBySpace()
|
public void DirectiveDescriptor_TokensMustBeSeparatedBySpace()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue