Skip to content
This repository has been archived by the owner on Dec 19, 2018. It is now read-only.

Commit

Permalink
Add file scoped extensible directives.
Browse files Browse the repository at this point in the history
- 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 single pre-content based directive.
- Added directive parsing tests.
- Removed no longer used `BlockKindInternal` items.

#1376
  • Loading branch information
NTaylorMullen committed Jun 21, 2017
1 parent 4811807 commit 714e045
Show file tree
Hide file tree
Showing 10 changed files with 425 additions and 9 deletions.
14 changes: 13 additions & 1 deletion src/Microsoft.AspNetCore.Razor.Language/DirectiveDescriptor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@ public abstract class DirectiveDescriptor
/// </summary>
public abstract DirectiveKind Kind { get; }

/// <summary>
/// Gets the way a directive can be used. The usage determines how many directives can exist per document
/// and where they can exist.
/// </summary>
public abstract DirectiveUsage Usage { get; }

/// <summary>
/// Gets the list of directive tokens that can follow the directive keyword.
/// </summary>
Expand Down Expand Up @@ -189,6 +195,8 @@ public DefaultDirectiveDescriptorBuilder(string name, DirectiveKind kind)

public DirectiveKind Kind { get; }

public DirectiveUsage Usage { get; set; }

public IList<DirectiveTokenDescriptor> Tokens { get; }

public DirectiveDescriptor Build()
Expand Down Expand Up @@ -218,7 +226,7 @@ public DirectiveDescriptor Build()
}
}

return new DefaultDirectiveDescriptor(Directive, Kind, Tokens.ToArray(), DisplayName, Description);
return new DefaultDirectiveDescriptor(Directive, Kind, Usage, Tokens.ToArray(), DisplayName, Description);
}
}

Expand All @@ -227,12 +235,14 @@ private class DefaultDirectiveDescriptor : DirectiveDescriptor
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;
Expand All @@ -246,6 +256,8 @@ public DefaultDirectiveDescriptor(

public override DirectiveKind Kind { get; }

public override DirectiveUsage Usage { get; }

public override IReadOnlyList<DirectiveTokenDescriptor> Tokens { get; }
}
}
Expand Down
11 changes: 11 additions & 0 deletions src/Microsoft.AspNetCore.Razor.Language/DirectiveUsage.cs
Original file line number Diff line number Diff line change
@@ -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,
SinglePreContent,
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ public interface IDirectiveDescriptorBuilder
/// </summary>
DirectiveKind Kind { get; }

/// <summary>
/// Gets the directive usage.
/// </summary>
DirectiveUsage Usage { get; set; }

/// <summary>
/// Gets a list of the directive tokens.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,10 @@ public enum BlockKindInternal
// Code
Statement,
Directive,
Functions,
Expression,
Helper,

// Markup
Markup,
Section,
Template,

// Special
Expand Down
72 changes: 70 additions & 2 deletions src/Microsoft.AspNetCore.Razor.Language/Legacy/CSharpCodeParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ internal class CSharpCodeParser : TokenizerBackedParser<CSharpTokenizer, CSharpS

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)
Expand Down Expand Up @@ -91,7 +92,12 @@ protected void MapDirectives(Action handler, params string[] directives)
{
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
Expand Down Expand Up @@ -1589,10 +1595,13 @@ private void HandleDirective(DirectiveDescriptor descriptor)
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(
Expand Down Expand Up @@ -1767,6 +1776,65 @@ private void HandleDirective(DirectiveDescriptor descriptor)
}
}


private void ValidateDirectiveUsage(DirectiveDescriptor descriptor)
{
if (descriptor.Usage == DirectiveUsage.SinglePreContent)
{
if (_seenDirectives.Contains(descriptor.Directive))
{
UsageError(Resources.FormatDuplicateDirective(descriptor.Directive));
return;
}

if (Context.Builder.ActiveBlocks.Count != 2)
{
// 1 block for the root, 1 for the directive block, any more than 2 active blocks
// means the directive is nested under another block.

UsageError(Resources.FormatDirectiveMustExistBeforeMarkupOrCode(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 = descriptor.Directive.Length + 1; // @directivename
Context.ErrorSink.OnError(directiveStart, message, errorLength);
}
}

private void ParseDirectiveBlock(DirectiveDescriptor descriptor, Action<SourceLocation> parseChildren)
{
if (EndOfFile)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public SyntaxTreeBuilder()
_endBlock = EndBlock;
}

public IEnumerable<BlockBuilder> ActiveBlocks => _blockStack;
public IReadOnlyCollection<BlockBuilder> ActiveBlocks => _blockStack;

public BlockBuilder CurrentBlock => _blockStack.Peek();

Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions src/Microsoft.AspNetCore.Razor.Language/Resources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -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 occurr 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>
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public void ConstructorTransfersInstanceOfChunkGeneratorFromBlockBuilder()
var expected = new ExpressionChunkGenerator();
var builder = new BlockBuilder()
{
Type = BlockKindInternal.Helper,
Type = BlockKindInternal.Statement,
ChunkGenerator = expected
};

Expand All @@ -48,7 +48,7 @@ public void ConstructorTransfersChildrenFromBlockBuilder()
var expected = new SpanBuilder(SourceLocation.Undefined) { Kind = SpanKindInternal.Code }.Build();
var builder = new BlockBuilder()
{
Type = BlockKindInternal.Functions
Type = BlockKindInternal.Statement
};
builder.Children.Add(expected);

Expand Down
Loading

0 comments on commit 714e045

Please sign in to comment.