Add extensible directive abstractions
- Based generic directive implementation off of descriptors. - Added parsing logic to consume descriptors and parse content that's expected. - Added parsing errors to automagically detect unexpected directive pieces. - Updated visitor implementations to understand the directive bits. - Added a builder abstraction to easily create descriptors. Had to maintain the ability to manually construct a descriptor to enable convenient serialization/deserialization. - Added tests/comparers to verify correctness of parsing. #853
This commit is contained in:
parent
522f6e969d
commit
518378f499
|
|
@ -206,6 +206,30 @@ namespace Microsoft.AspNetCore.Razor.Evolution
|
|||
Namespace.Children.Insert(i, @using);
|
||||
}
|
||||
|
||||
public override void VisitDirectiveToken(DirectiveTokenChunkGenerator chunkGenerator, Span span)
|
||||
{
|
||||
Builder.Add(new DirectiveTokenIRNode()
|
||||
{
|
||||
Content = span.Content,
|
||||
Descriptor = chunkGenerator.Descriptor,
|
||||
SourceLocation = span.Start,
|
||||
});
|
||||
}
|
||||
|
||||
public override void VisitStartDirectiveBlock(DirectiveChunkGenerator chunkGenerator, Block block)
|
||||
{
|
||||
Builder.Push(new DirectiveIRNode()
|
||||
{
|
||||
Name = chunkGenerator.Descriptor.Name,
|
||||
Descriptor = chunkGenerator.Descriptor,
|
||||
});
|
||||
}
|
||||
|
||||
public override void VisitEndDirectiveBlock(DirectiveChunkGenerator chunkGenerator, Block block)
|
||||
{
|
||||
Builder.Pop();
|
||||
}
|
||||
|
||||
private class ContainerRazorIRNode : RazorIRNode
|
||||
{
|
||||
private SourceLocation? _location;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,16 @@
|
|||
// 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.Collections.Generic;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.Evolution
|
||||
{
|
||||
public class DirectiveDescriptor
|
||||
{
|
||||
public string Name { get; set; }
|
||||
|
||||
public DirectiveDescriptorKind Kind { get; set; }
|
||||
|
||||
public IReadOnlyList<DirectiveTokenDescriptor> Tokens { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
// 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.Collections.Generic;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.Evolution
|
||||
{
|
||||
public static class DirectiveDescriptorBuilder
|
||||
{
|
||||
public static IDirectiveDescriptorBuilder Create(string name)
|
||||
{
|
||||
return new DefaultDirectiveDescriptorBuilder(name, DirectiveDescriptorKind.SingleLine);
|
||||
}
|
||||
|
||||
public static IDirectiveDescriptorBuilder CreateRazorBlock(string name)
|
||||
{
|
||||
return new DefaultDirectiveDescriptorBuilder(name, DirectiveDescriptorKind.RazorBlock);
|
||||
}
|
||||
|
||||
public static IDirectiveDescriptorBuilder CreateCodeBlock(string name)
|
||||
{
|
||||
return new DefaultDirectiveDescriptorBuilder(name, DirectiveDescriptorKind.CodeBlock);
|
||||
}
|
||||
|
||||
private class DefaultDirectiveDescriptorBuilder : IDirectiveDescriptorBuilder
|
||||
{
|
||||
private readonly List<DirectiveTokenDescriptor> _tokenDescriptors;
|
||||
private readonly string _name;
|
||||
private readonly DirectiveDescriptorKind _type;
|
||||
|
||||
public DefaultDirectiveDescriptorBuilder(string name, DirectiveDescriptorKind type)
|
||||
{
|
||||
_name = name;
|
||||
_type = type;
|
||||
_tokenDescriptors = new List<DirectiveTokenDescriptor>();
|
||||
}
|
||||
|
||||
public IDirectiveDescriptorBuilder AddType()
|
||||
{
|
||||
var descriptor = new DirectiveTokenDescriptor()
|
||||
{
|
||||
Kind = DirectiveTokenKind.Type
|
||||
};
|
||||
_tokenDescriptors.Add(descriptor);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public IDirectiveDescriptorBuilder AddMember()
|
||||
{
|
||||
var descriptor = new DirectiveTokenDescriptor()
|
||||
{
|
||||
Kind = DirectiveTokenKind.Member
|
||||
};
|
||||
_tokenDescriptors.Add(descriptor);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public IDirectiveDescriptorBuilder AddString()
|
||||
{
|
||||
var descriptor = new DirectiveTokenDescriptor()
|
||||
{
|
||||
Kind = DirectiveTokenKind.String
|
||||
};
|
||||
_tokenDescriptors.Add(descriptor);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public IDirectiveDescriptorBuilder AddLiteral(string literal)
|
||||
{
|
||||
var descriptor = new DirectiveTokenDescriptor()
|
||||
{
|
||||
Kind = DirectiveTokenKind.Literal,
|
||||
Value = literal,
|
||||
};
|
||||
_tokenDescriptors.Add(descriptor);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public DirectiveDescriptor Build()
|
||||
{
|
||||
var descriptor = new DirectiveDescriptor
|
||||
{
|
||||
Name = _name,
|
||||
Kind = _type,
|
||||
Tokens = _tokenDescriptors,
|
||||
};
|
||||
|
||||
return descriptor;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
// 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.Extensions.Internal;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.Evolution
|
||||
{
|
||||
internal class DirectiveDescriptorComparer : IEqualityComparer<DirectiveDescriptor>
|
||||
{
|
||||
public static readonly DirectiveDescriptorComparer Default = new DirectiveDescriptorComparer();
|
||||
|
||||
protected DirectiveDescriptorComparer()
|
||||
{
|
||||
}
|
||||
|
||||
public bool Equals(DirectiveDescriptor descriptorX, DirectiveDescriptor descriptorY)
|
||||
{
|
||||
if (descriptorX == descriptorY)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return descriptorX != null &&
|
||||
string.Equals(descriptorX.Name, descriptorY.Name, StringComparison.Ordinal) &&
|
||||
descriptorX.Kind == descriptorY.Kind &&
|
||||
Enumerable.SequenceEqual(
|
||||
descriptorX.Tokens,
|
||||
descriptorY.Tokens,
|
||||
DirectiveTokenDescriptorComparer.Default);
|
||||
}
|
||||
|
||||
public int GetHashCode(DirectiveDescriptor descriptor)
|
||||
{
|
||||
if (descriptor == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(descriptor));
|
||||
}
|
||||
|
||||
var hashCodeCombiner = HashCodeCombiner.Start();
|
||||
hashCodeCombiner.Add(descriptor.Name, StringComparer.Ordinal);
|
||||
hashCodeCombiner.Add(descriptor.Kind);
|
||||
|
||||
return hashCodeCombiner.CombinedHash;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
// 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.Evolution
|
||||
{
|
||||
public enum DirectiveDescriptorKind
|
||||
{
|
||||
SingleLine,
|
||||
RazorBlock,
|
||||
CodeBlock
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
// 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.Evolution
|
||||
{
|
||||
public class DirectiveTokenDescriptor
|
||||
{
|
||||
public DirectiveTokenKind Kind { get; set; }
|
||||
|
||||
public string Value { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
// 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 Microsoft.Extensions.Internal;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.Evolution
|
||||
{
|
||||
internal class DirectiveTokenDescriptorComparer : IEqualityComparer<DirectiveTokenDescriptor>
|
||||
{
|
||||
public static readonly DirectiveTokenDescriptorComparer Default = new DirectiveTokenDescriptorComparer();
|
||||
|
||||
protected DirectiveTokenDescriptorComparer()
|
||||
{
|
||||
}
|
||||
|
||||
public bool Equals(DirectiveTokenDescriptor descriptorX, DirectiveTokenDescriptor descriptorY)
|
||||
{
|
||||
if (descriptorX == descriptorY)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return descriptorX != null &&
|
||||
string.Equals(descriptorX.Value, descriptorY.Value, StringComparison.Ordinal) &&
|
||||
descriptorX.Kind == descriptorY.Kind;
|
||||
}
|
||||
|
||||
public int GetHashCode(DirectiveTokenDescriptor descriptor)
|
||||
{
|
||||
if (descriptor == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(descriptor));
|
||||
}
|
||||
|
||||
var hashCodeCombiner = HashCodeCombiner.Start();
|
||||
hashCodeCombiner.Add(descriptor.Value, StringComparer.Ordinal);
|
||||
hashCodeCombiner.Add(descriptor.Kind);
|
||||
|
||||
return hashCodeCombiner.CombinedHash;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
// 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.Evolution
|
||||
{
|
||||
public enum DirectiveTokenKind
|
||||
{
|
||||
Type,
|
||||
Member,
|
||||
String,
|
||||
Literal
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
// 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.Evolution
|
||||
{
|
||||
public interface IDirectiveDescriptorBuilder
|
||||
{
|
||||
IDirectiveDescriptorBuilder AddType();
|
||||
|
||||
IDirectiveDescriptorBuilder AddMember();
|
||||
|
||||
IDirectiveDescriptorBuilder AddString();
|
||||
|
||||
IDirectiveDescriptorBuilder AddLiteral(string literal);
|
||||
|
||||
DirectiveDescriptor Build();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
// 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.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Razor.Evolution.Legacy;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate
|
||||
{
|
||||
public class DirectiveIRNode : RazorIRNode
|
||||
{
|
||||
public override IList<RazorIRNode> Children { get; } = new List<RazorIRNode>();
|
||||
|
||||
public override RazorIRNode Parent { get; set; }
|
||||
|
||||
internal override SourceLocation SourceLocation { get; set; }
|
||||
|
||||
public string Name { get; set; }
|
||||
|
||||
public IEnumerable<DirectiveTokenIRNode> Tokens => Children.OfType<DirectiveTokenIRNode>();
|
||||
|
||||
public DirectiveDescriptor Descriptor { get; set; }
|
||||
|
||||
public override void Accept(RazorIRNodeVisitor visitor)
|
||||
{
|
||||
visitor.VisitDirective(this);
|
||||
}
|
||||
|
||||
public override TResult Accept<TResult>(RazorIRNodeVisitor<TResult> visitor)
|
||||
{
|
||||
return visitor.VisitDirective(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
// 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.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Razor.Evolution.Legacy;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate
|
||||
{
|
||||
public class DirectiveTokenIRNode : RazorIRNode
|
||||
{
|
||||
public override IList<RazorIRNode> Children { get; } = EmptyArray;
|
||||
|
||||
public override RazorIRNode Parent { get; set; }
|
||||
|
||||
internal override SourceLocation SourceLocation { get; set; }
|
||||
|
||||
public string Content { get; set; }
|
||||
|
||||
public DirectiveTokenDescriptor Descriptor { get; set; }
|
||||
|
||||
public override void Accept(RazorIRNodeVisitor visitor)
|
||||
{
|
||||
visitor.VisitDirectiveToken(this);
|
||||
}
|
||||
|
||||
public override TResult Accept<TResult>(RazorIRNodeVisitor<TResult> visitor)
|
||||
{
|
||||
return visitor.VisitDirectiveToken(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -14,6 +14,16 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate
|
|||
{
|
||||
}
|
||||
|
||||
public virtual void VisitDirectiveToken(DirectiveTokenIRNode node)
|
||||
{
|
||||
VisitDefault(node);
|
||||
}
|
||||
|
||||
public virtual void VisitDirective(DirectiveIRNode node)
|
||||
{
|
||||
VisitDefault(node);
|
||||
}
|
||||
|
||||
public virtual void VisitTemplate(TemplateIRNode node)
|
||||
{
|
||||
VisitDefault(node);
|
||||
|
|
|
|||
|
|
@ -15,6 +15,16 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate
|
|||
return default(TResult);
|
||||
}
|
||||
|
||||
public virtual TResult VisitDirectiveToken(DirectiveTokenIRNode node)
|
||||
{
|
||||
return VisitDefault(node);
|
||||
}
|
||||
|
||||
public virtual TResult VisitDirective(DirectiveIRNode node)
|
||||
{
|
||||
return VisitDefault(node);
|
||||
}
|
||||
|
||||
public virtual TResult VisitTemplate(TemplateIRNode node)
|
||||
{
|
||||
return VisitDefault(node);
|
||||
|
|
|
|||
|
|
@ -39,11 +39,16 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
|
|||
private Dictionary<CSharpKeyword, Action<bool>> _keywordParsers = new Dictionary<CSharpKeyword, Action<bool>>();
|
||||
|
||||
public CSharpCodeParser(ParserContext context)
|
||||
: this (directiveDescriptors: Enumerable.Empty<DirectiveDescriptor>(), context: context)
|
||||
{
|
||||
}
|
||||
|
||||
public CSharpCodeParser(IEnumerable<DirectiveDescriptor> directiveDescriptors, ParserContext context)
|
||||
: base(CSharpLanguageCharacteristics.Instance, context)
|
||||
{
|
||||
Keywords = new HashSet<string>();
|
||||
SetUpKeywords();
|
||||
SetupDirectives();
|
||||
SetupDirectives(directiveDescriptors);
|
||||
SetUpExpressions();
|
||||
}
|
||||
|
||||
|
|
@ -1404,8 +1409,13 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
|
|||
}
|
||||
}
|
||||
|
||||
private void SetupDirectives()
|
||||
private void SetupDirectives(IEnumerable<DirectiveDescriptor> directiveDescriptors)
|
||||
{
|
||||
foreach (var directiveDescriptor in directiveDescriptors)
|
||||
{
|
||||
MapDirectives(() => HandleDirective(directiveDescriptor), directiveDescriptor.Name);
|
||||
}
|
||||
|
||||
MapDirectives(TagHelperPrefixDirective, SyntaxConstants.CSharp.TagHelperPrefixKeyword);
|
||||
MapDirectives(AddTagHelperDirective, SyntaxConstants.CSharp.AddTagHelperKeyword);
|
||||
MapDirectives(RemoveTagHelperDirective, SyntaxConstants.CSharp.RemoveTagHelperKeyword);
|
||||
|
|
@ -1414,6 +1424,183 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
|
|||
MapDirectives(SectionDirective, SyntaxConstants.CSharp.SectionKeyword);
|
||||
}
|
||||
|
||||
private void HandleDirective(DirectiveDescriptor descriptor)
|
||||
{
|
||||
Context.Builder.CurrentBlock.Type = BlockType.Directive;
|
||||
Context.Builder.CurrentBlock.ChunkGenerator = new DirectiveChunkGenerator(descriptor);
|
||||
AssertDirective(descriptor.Name);
|
||||
|
||||
AcceptAndMoveNext();
|
||||
Output(SpanKind.MetaCode, AcceptedCharacters.None);
|
||||
|
||||
for (var i = 0; i < descriptor.Tokens.Count; i++)
|
||||
{
|
||||
var tokenDescriptor = descriptor.Tokens[i];
|
||||
AcceptWhile(IsSpacingToken(includeNewLines: false, includeComments: true));
|
||||
|
||||
if (tokenDescriptor.Kind == DirectiveTokenKind.Member || tokenDescriptor.Kind == DirectiveTokenKind.Type)
|
||||
{
|
||||
Span.ChunkGenerator = SpanChunkGenerator.Null;
|
||||
Output(SpanKind.Code, AcceptedCharacters.WhiteSpace);
|
||||
}
|
||||
else
|
||||
{
|
||||
Span.ChunkGenerator = SpanChunkGenerator.Null;
|
||||
Output(SpanKind.Markup, AcceptedCharacters.WhiteSpace);
|
||||
}
|
||||
|
||||
var outputKind = SpanKind.Markup;
|
||||
switch (tokenDescriptor.Kind)
|
||||
{
|
||||
case DirectiveTokenKind.Type:
|
||||
if (!NamespaceOrTypeName())
|
||||
{
|
||||
// Error logged for invalid type name, continue onto next piece.
|
||||
continue;
|
||||
}
|
||||
|
||||
outputKind = SpanKind.Code;
|
||||
break;
|
||||
case DirectiveTokenKind.Member:
|
||||
if (At(CSharpSymbolType.Identifier))
|
||||
{
|
||||
AcceptAndMoveNext();
|
||||
}
|
||||
else
|
||||
{
|
||||
Context.ErrorSink.OnError(
|
||||
CurrentLocation,
|
||||
LegacyResources.FormatDirectiveExpectsIdentifier(descriptor.Name),
|
||||
CurrentSymbol.Content.Length);
|
||||
return;
|
||||
}
|
||||
|
||||
outputKind = SpanKind.Code;
|
||||
break;
|
||||
case DirectiveTokenKind.String:
|
||||
AcceptAndMoveNext();
|
||||
break;
|
||||
case DirectiveTokenKind.Literal:
|
||||
if (string.Equals(CurrentSymbol.Content, tokenDescriptor.Value, StringComparison.Ordinal))
|
||||
{
|
||||
AcceptAndMoveNext();
|
||||
}
|
||||
else
|
||||
{
|
||||
Context.ErrorSink.OnError(
|
||||
CurrentLocation,
|
||||
LegacyResources.FormatUnexpectedDirectiveLiteral(descriptor.Name, tokenDescriptor.Value),
|
||||
CurrentSymbol.Content.Length);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
Span.ChunkGenerator = new DirectiveTokenChunkGenerator(tokenDescriptor);
|
||||
Output(outputKind, AcceptedCharacters.NonWhiteSpace);
|
||||
}
|
||||
|
||||
AcceptWhile(IsSpacingToken(includeNewLines: false, includeComments: true));
|
||||
Span.ChunkGenerator = SpanChunkGenerator.Null;
|
||||
|
||||
switch (descriptor.Kind)
|
||||
{
|
||||
case DirectiveDescriptorKind.SingleLine:
|
||||
Optional(CSharpSymbolType.Semicolon);
|
||||
|
||||
if (At(CSharpSymbolType.NewLine))
|
||||
{
|
||||
AcceptAndMoveNext();
|
||||
}
|
||||
else if (!EndOfFile)
|
||||
{
|
||||
Context.ErrorSink.OnError(
|
||||
CurrentLocation,
|
||||
LegacyResources.FormatUnexpectedDirectiveLiteral(descriptor.Name, Environment.NewLine),
|
||||
CurrentSymbol.Content.Length);
|
||||
}
|
||||
|
||||
Output(SpanKind.Markup, AcceptedCharacters.AllWhiteSpace);
|
||||
break;
|
||||
case DirectiveDescriptorKind.RazorBlock:
|
||||
Output(SpanKind.Markup, AcceptedCharacters.WhiteSpace);
|
||||
|
||||
ParseDirectiveBlock(descriptor, parseChildren: (startingBraceLocation) =>
|
||||
{
|
||||
// When transitioning to the HTML parser we no longer want to act as if we're in a nested C# state.
|
||||
// For instance, if <div>@hello.</div> is in a nested C# block we don't want the trailing '.' to be handled
|
||||
// as C#; it should be handled as a period because it's wrapped in markup.
|
||||
var wasNested = IsNested;
|
||||
IsNested = false;
|
||||
using (PushSpanConfig())
|
||||
{
|
||||
HtmlParser.ParseSection(Tuple.Create("{", "}"), caseSensitive: true);
|
||||
}
|
||||
Initialize(Span);
|
||||
IsNested = wasNested;
|
||||
NextToken();
|
||||
});
|
||||
break;
|
||||
case DirectiveDescriptorKind.CodeBlock:
|
||||
Output(SpanKind.Markup, AcceptedCharacters.WhiteSpace);
|
||||
|
||||
ParseDirectiveBlock(descriptor, parseChildren: (startingBraceLocation) =>
|
||||
{
|
||||
NextToken();
|
||||
Balance(BalancingModes.NoErrorOnFailure, CSharpSymbolType.LeftBrace, CSharpSymbolType.RightBrace, startingBraceLocation);
|
||||
Span.ChunkGenerator = new StatementChunkGenerator();
|
||||
Output(SpanKind.Code);
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void ParseDirectiveBlock(DirectiveDescriptor descriptor, Action<SourceLocation> parseChildren)
|
||||
{
|
||||
if (EndOfFile)
|
||||
{
|
||||
Context.ErrorSink.OnError(
|
||||
CurrentLocation,
|
||||
LegacyResources.FormatUnexpectedEOFAfterDirective(descriptor.Name, "{"),
|
||||
length: 1 /* { */);
|
||||
}
|
||||
else if (!At(CSharpSymbolType.LeftBrace))
|
||||
{
|
||||
Context.ErrorSink.OnError(
|
||||
CurrentLocation,
|
||||
LegacyResources.FormatUnexpectedDirectiveLiteral(descriptor.Name, "{"),
|
||||
CurrentSymbol.Content.Length);
|
||||
}
|
||||
else
|
||||
{
|
||||
var editHandler = new AutoCompleteEditHandler(Language.TokenizeString, autoCompleteAtEndOfSpan: true);
|
||||
Span.EditHandler = editHandler;
|
||||
var startingBraceLocation = CurrentLocation;
|
||||
Accept(CurrentSymbol);
|
||||
Span.ChunkGenerator = SpanChunkGenerator.Null;
|
||||
Output(SpanKind.MetaCode, AcceptedCharacters.None);
|
||||
|
||||
parseChildren(startingBraceLocation);
|
||||
|
||||
Span.ChunkGenerator = SpanChunkGenerator.Null;
|
||||
if (!Optional(CSharpSymbolType.RightBrace))
|
||||
{
|
||||
editHandler.AutoCompleteString = "}";
|
||||
Context.ErrorSink.OnError(
|
||||
startingBraceLocation,
|
||||
LegacyResources.FormatParseError_Expected_EndOfBlock_Before_EOF(descriptor.Name, "{", "}"),
|
||||
length: 1 /* } */);
|
||||
}
|
||||
else
|
||||
{
|
||||
Span.EditHandler.AcceptedCharacters = AcceptedCharacters.None;
|
||||
}
|
||||
CompleteBlock(insertMarkerIfNecessary: false, captureWhitespaceToEndOfLine: true);
|
||||
Span.ChunkGenerator = SpanChunkGenerator.Null;
|
||||
Output(SpanKind.MetaCode, AcceptedCharacters.None);
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void TagHelperPrefixDirective()
|
||||
{
|
||||
TagHelperDirective(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,45 @@
|
|||
// 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 Microsoft.Extensions.Internal;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
|
||||
{
|
||||
internal class DirectiveChunkGenerator : ParentChunkGenerator
|
||||
{
|
||||
private static readonly Type Type = typeof(DirectiveChunkGenerator);
|
||||
|
||||
public DirectiveChunkGenerator(DirectiveDescriptor descriptor)
|
||||
{
|
||||
Descriptor = descriptor;
|
||||
}
|
||||
|
||||
public DirectiveDescriptor Descriptor { get; }
|
||||
|
||||
public override void AcceptStart(ParserVisitor visitor, Block block)
|
||||
{
|
||||
visitor.VisitStartDirectiveBlock(this, block);
|
||||
}
|
||||
|
||||
public override void AcceptEnd(ParserVisitor visitor, Block block)
|
||||
{
|
||||
visitor.VisitEndDirectiveBlock(this, block);
|
||||
}
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
var other = obj as DirectiveChunkGenerator;
|
||||
return base.Equals(other) &&
|
||||
DirectiveDescriptorComparer.Default.Equals(Descriptor, other.Descriptor);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
var combiner = HashCodeCombiner.Start();
|
||||
combiner.Add(base.GetHashCode());
|
||||
combiner.Add(Type);
|
||||
|
||||
return combiner.CombinedHash;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
// 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 Microsoft.Extensions.Internal;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
|
||||
{
|
||||
internal class DirectiveTokenChunkGenerator : SpanChunkGenerator
|
||||
{
|
||||
private static readonly Type Type = typeof(DirectiveTokenChunkGenerator);
|
||||
|
||||
public DirectiveTokenChunkGenerator(DirectiveTokenDescriptor tokenDescriptor)
|
||||
{
|
||||
Descriptor = tokenDescriptor;
|
||||
}
|
||||
|
||||
public DirectiveTokenDescriptor Descriptor { get; set; }
|
||||
|
||||
public override void Accept(ParserVisitor visitor, Span span)
|
||||
{
|
||||
visitor.VisitDirectiveToken(this, span);
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
var other = obj as DirectiveTokenChunkGenerator;
|
||||
return base.Equals(other) &&
|
||||
DirectiveTokenDescriptorComparer.Default.Equals(Descriptor, other.Descriptor);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
var combiner = HashCodeCombiner.Start();
|
||||
combiner.Add(base.GetHashCode());
|
||||
combiner.Add(Type);
|
||||
|
||||
return combiner.CombinedHash;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -89,10 +89,22 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
|
|||
{
|
||||
}
|
||||
|
||||
public virtual void VisitDirectiveToken(DirectiveTokenChunkGenerator chunkGenerator, Span block)
|
||||
{
|
||||
}
|
||||
|
||||
public virtual void VisitEndTemplateBlock(TemplateBlockChunkGenerator chunkGenerator, Block block)
|
||||
{
|
||||
}
|
||||
|
||||
public virtual void VisitStartDirectiveBlock(DirectiveChunkGenerator chunkGenerator, Block block)
|
||||
{
|
||||
}
|
||||
|
||||
public virtual void VisitEndDirectiveBlock(DirectiveChunkGenerator chunkGenerator, Block block)
|
||||
{
|
||||
}
|
||||
|
||||
public virtual void VisitStartTemplateBlock(TemplateBlockChunkGenerator chunkGenerator, Block block)
|
||||
{
|
||||
}
|
||||
|
|
|
|||
|
|
@ -179,6 +179,9 @@
|
|||
<data name="CSharpSymbol_Whitespace" xml:space="preserve">
|
||||
<value><<white space>></value>
|
||||
</data>
|
||||
<data name="DirectiveExpectsIdentifier" xml:space="preserve">
|
||||
<value>The '{0}' directive expects an identifier.</value>
|
||||
</data>
|
||||
<data name="EndBlock_Called_Without_Matching_StartBlock" xml:space="preserve">
|
||||
<value>"EndBlock" was called without a matching call to "StartBlock".</value>
|
||||
</data>
|
||||
|
|
@ -381,4 +384,10 @@ Instead, wrap the contents of the block in "{{}}":
|
|||
<data name="TokenizerView_CannotPutBack" xml:space="preserve">
|
||||
<value>In order to put a symbol back, it must have been the symbol which ended at the current position. The specified symbol ends at {0}, but the current position is {1}</value>
|
||||
</data>
|
||||
<data name="UnexpectedDirectiveLiteral" xml:space="preserve">
|
||||
<value>Unexpected literal following the '{0}' directive. Expected '{1}'.</value>
|
||||
</data>
|
||||
<data name="UnexpectedEOFAfterDirective" xml:space="preserve">
|
||||
<value>Unexpected end of file following the '{0}' directive. Expected '{1}'.</value>
|
||||
</data>
|
||||
</root>
|
||||
|
|
@ -330,6 +330,22 @@ namespace Microsoft.AspNetCore.Razor.Evolution
|
|||
return GetString("CSharpSymbol_Whitespace");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The '{0}' directive expects an identifier.
|
||||
/// </summary>
|
||||
internal static string DirectiveExpectsIdentifier
|
||||
{
|
||||
get { return GetString("DirectiveExpectsIdentifier"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The '{0}' directive expects an identifier.
|
||||
/// </summary>
|
||||
internal static string FormatDirectiveExpectsIdentifier(object p0)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("DirectiveExpectsIdentifier"), p0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// "EndBlock" was called without a matching call to "StartBlock".
|
||||
/// </summary>
|
||||
|
|
@ -1318,6 +1334,38 @@ namespace Microsoft.AspNetCore.Razor.Evolution
|
|||
return string.Format(CultureInfo.CurrentCulture, GetString("TokenizerView_CannotPutBack"), p0, p1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unexpected literal following the '{0}' directive. Expected '{1}'.
|
||||
/// </summary>
|
||||
internal static string UnexpectedDirectiveLiteral
|
||||
{
|
||||
get { return GetString("UnexpectedDirectiveLiteral"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unexpected literal following the '{0}' directive. Expected '{1}'.
|
||||
/// </summary>
|
||||
internal static string FormatUnexpectedDirectiveLiteral(object p0, object p1)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("UnexpectedDirectiveLiteral"), p0, p1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unexpected end of file following the '{0}' directive. Expected '{1}'.
|
||||
/// </summary>
|
||||
internal static string UnexpectedEOFAfterDirective
|
||||
{
|
||||
get { return GetString("UnexpectedEOFAfterDirective"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unexpected end of file following the '{0}' directive. Expected '{1}'.
|
||||
/// </summary>
|
||||
internal static string FormatUnexpectedEOFAfterDirective(object p0, object p1)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("UnexpectedEOFAfterDirective"), p0, p1);
|
||||
}
|
||||
|
||||
private static string GetString(string name, params string[] formatterNames)
|
||||
{
|
||||
var value = _resourceManager.GetString(name);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,123 @@
|
|||
// 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 Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.Evolution
|
||||
{
|
||||
public class DirectiveDescriptorBuilderTest
|
||||
{
|
||||
[Fact]
|
||||
public void Create_BuildsSingleLineDirectiveDescriptor()
|
||||
{
|
||||
// Act
|
||||
var descriptor = DirectiveDescriptorBuilder.Create("custom").Build();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(DirectiveDescriptorKind.SingleLine, descriptor.Kind);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateRazorBlock_BuildsRazorBlockDirectiveDescriptor()
|
||||
{
|
||||
// Act
|
||||
var descriptor = DirectiveDescriptorBuilder.CreateRazorBlock("custom").Build();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(DirectiveDescriptorKind.RazorBlock, descriptor.Kind);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateCodeBlock_BuildsCodeBlockDirectiveDescriptor()
|
||||
{
|
||||
// Act
|
||||
var descriptor = DirectiveDescriptorBuilder.CreateCodeBlock("custom").Build();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(DirectiveDescriptorKind.CodeBlock, descriptor.Kind);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddType_AddsToken()
|
||||
{
|
||||
// Arrange
|
||||
var builder = DirectiveDescriptorBuilder.Create("custom");
|
||||
|
||||
// Act
|
||||
var descriptor = builder.AddType().Build();
|
||||
|
||||
// Assert
|
||||
var token = Assert.Single(descriptor.Tokens);
|
||||
Assert.Equal(DirectiveTokenKind.Type, token.Kind);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddMember_AddsToken()
|
||||
{
|
||||
// Arrange
|
||||
var builder = DirectiveDescriptorBuilder.Create("custom");
|
||||
|
||||
// Act
|
||||
var descriptor = builder.AddMember().Build();
|
||||
|
||||
// Assert
|
||||
var token = Assert.Single(descriptor.Tokens);
|
||||
Assert.Equal(DirectiveTokenKind.Member, token.Kind);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddString_AddsToken()
|
||||
{
|
||||
// Arrange
|
||||
var builder = DirectiveDescriptorBuilder.Create("custom");
|
||||
|
||||
// Act
|
||||
var descriptor = builder.AddString().Build();
|
||||
|
||||
// Assert
|
||||
var token = Assert.Single(descriptor.Tokens);
|
||||
Assert.Equal(DirectiveTokenKind.String, token.Kind);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddLiteral_AddsToken()
|
||||
{
|
||||
// Arrange
|
||||
var builder = DirectiveDescriptorBuilder.Create("custom");
|
||||
|
||||
// Act
|
||||
var descriptor = builder.AddLiteral(",").Build();
|
||||
|
||||
// Assert
|
||||
var token = Assert.Single(descriptor.Tokens);
|
||||
Assert.Equal(DirectiveTokenKind.Literal, token.Kind);
|
||||
Assert.Equal(",", token.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddX_MaintainsMultipleTokens()
|
||||
{
|
||||
// Arrange
|
||||
var builder = DirectiveDescriptorBuilder.Create("custom");
|
||||
|
||||
// Act
|
||||
var descriptor = builder
|
||||
.AddType()
|
||||
.AddMember()
|
||||
.AddString()
|
||||
.AddLiteral(",")
|
||||
.Build();
|
||||
|
||||
// Assert
|
||||
Assert.Collection(descriptor.Tokens,
|
||||
token => Assert.Equal(DirectiveTokenKind.Type, token.Kind),
|
||||
token => Assert.Equal(DirectiveTokenKind.Member, token.Kind),
|
||||
token => Assert.Equal(DirectiveTokenKind.String, token.Kind),
|
||||
token =>
|
||||
{
|
||||
Assert.Equal(DirectiveTokenKind.Literal, token.Kind);
|
||||
Assert.Equal(",", token.Value);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +1,378 @@
|
|||
// 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 Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
|
||||
{
|
||||
public class CSharpDirectivesTest : CsHtmlCodeParserTestBase
|
||||
{
|
||||
[Fact]
|
||||
public void DirectiveDescriptor_UnderstandsTypeTokens()
|
||||
{
|
||||
// Arrange
|
||||
var descriptor = DirectiveDescriptorBuilder.Create("custom").AddType().Build();
|
||||
|
||||
// Act & Assert
|
||||
ParseCodeBlockTest(
|
||||
"@custom System.Text.Encoding.ASCIIEncoding",
|
||||
new[] { descriptor },
|
||||
new DirectiveBlock(
|
||||
new DirectiveChunkGenerator(descriptor),
|
||||
Factory.CodeTransition(),
|
||||
Factory.MetaCode("custom").Accepts(AcceptedCharacters.None),
|
||||
Factory.Span(SpanKind.Code, " ", markup: false).Accepts(AcceptedCharacters.WhiteSpace),
|
||||
Factory.Span(SpanKind.Code, "System.Text.Encoding.ASCIIEncoding", markup: false)
|
||||
.With(new DirectiveTokenChunkGenerator(descriptor.Tokens[0]))
|
||||
.Accepts(AcceptedCharacters.NonWhiteSpace)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DirectiveDescriptor_UnderstandsMemberTokens()
|
||||
{
|
||||
// Arrange
|
||||
var descriptor = DirectiveDescriptorBuilder.Create("custom").AddMember().Build();
|
||||
|
||||
// Act & Assert
|
||||
ParseCodeBlockTest(
|
||||
"@custom Some_Member",
|
||||
new[] { descriptor },
|
||||
new DirectiveBlock(
|
||||
new DirectiveChunkGenerator(descriptor),
|
||||
Factory.CodeTransition(),
|
||||
Factory.MetaCode("custom").Accepts(AcceptedCharacters.None),
|
||||
Factory.Span(SpanKind.Code, " ", markup: false).Accepts(AcceptedCharacters.WhiteSpace),
|
||||
Factory.Span(SpanKind.Code, "Some_Member", markup: false)
|
||||
.With(new DirectiveTokenChunkGenerator(descriptor.Tokens[0]))
|
||||
.Accepts(AcceptedCharacters.NonWhiteSpace)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DirectiveDescriptor_UnderstandsStringTokens()
|
||||
{
|
||||
// Arrange
|
||||
var descriptor = DirectiveDescriptorBuilder.Create("custom").AddString().Build();
|
||||
|
||||
// Act & Assert
|
||||
ParseCodeBlockTest(
|
||||
"@custom AString",
|
||||
new[] { descriptor },
|
||||
new DirectiveBlock(
|
||||
new DirectiveChunkGenerator(descriptor),
|
||||
Factory.CodeTransition(),
|
||||
Factory.MetaCode("custom").Accepts(AcceptedCharacters.None),
|
||||
Factory.Span(SpanKind.Markup, " ", markup: false).Accepts(AcceptedCharacters.WhiteSpace),
|
||||
Factory.Span(SpanKind.Markup, "AString", markup: false)
|
||||
.With(new DirectiveTokenChunkGenerator(descriptor.Tokens[0]))
|
||||
.Accepts(AcceptedCharacters.NonWhiteSpace)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DirectiveDescriptor_UnderstandsLiteralTokens()
|
||||
{
|
||||
// Arrange
|
||||
var descriptor = DirectiveDescriptorBuilder.Create("custom").AddLiteral("!").Build();
|
||||
|
||||
// Act & Assert
|
||||
ParseCodeBlockTest(
|
||||
"@custom !",
|
||||
new[] { descriptor },
|
||||
new DirectiveBlock(
|
||||
new DirectiveChunkGenerator(descriptor),
|
||||
Factory.CodeTransition(),
|
||||
Factory.MetaCode("custom").Accepts(AcceptedCharacters.None),
|
||||
Factory.Span(SpanKind.Markup, " ", markup: false).Accepts(AcceptedCharacters.WhiteSpace),
|
||||
Factory.Span(SpanKind.Markup, "!", markup: false)
|
||||
.With(new DirectiveTokenChunkGenerator(descriptor.Tokens[0]))
|
||||
.Accepts(AcceptedCharacters.NonWhiteSpace)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DirectiveDescriptor_UnderstandsMultipleTokens()
|
||||
{
|
||||
// Arrange
|
||||
var descriptor = DirectiveDescriptorBuilder.Create("custom")
|
||||
.AddType()
|
||||
.AddMember()
|
||||
.AddString()
|
||||
.AddLiteral("!")
|
||||
.Build();
|
||||
|
||||
// Act & Assert
|
||||
ParseCodeBlockTest(
|
||||
"@custom System.Text.Encoding.ASCIIEncoding Some_Member AString !",
|
||||
new[] { descriptor },
|
||||
new DirectiveBlock(
|
||||
new DirectiveChunkGenerator(descriptor),
|
||||
Factory.CodeTransition(),
|
||||
Factory.MetaCode("custom").Accepts(AcceptedCharacters.None),
|
||||
|
||||
Factory.Span(SpanKind.Code, " ", markup: false).Accepts(AcceptedCharacters.WhiteSpace),
|
||||
Factory.Span(SpanKind.Code, "System.Text.Encoding.ASCIIEncoding", markup: false)
|
||||
.With(new DirectiveTokenChunkGenerator(descriptor.Tokens[0]))
|
||||
.Accepts(AcceptedCharacters.NonWhiteSpace),
|
||||
|
||||
Factory.Span(SpanKind.Code, " ", markup: false).Accepts(AcceptedCharacters.WhiteSpace),
|
||||
Factory.Span(SpanKind.Code, "Some_Member", markup: false)
|
||||
.With(new DirectiveTokenChunkGenerator(descriptor.Tokens[1]))
|
||||
.Accepts(AcceptedCharacters.NonWhiteSpace),
|
||||
|
||||
Factory.Span(SpanKind.Markup, " ", markup: false).Accepts(AcceptedCharacters.WhiteSpace),
|
||||
Factory.Span(SpanKind.Markup, "AString", markup: false)
|
||||
.With(new DirectiveTokenChunkGenerator(descriptor.Tokens[2]))
|
||||
.Accepts(AcceptedCharacters.NonWhiteSpace),
|
||||
|
||||
Factory.Span(SpanKind.Markup, " ", markup: false).Accepts(AcceptedCharacters.WhiteSpace),
|
||||
Factory.Span(SpanKind.Markup, "!", markup: false)
|
||||
.With(new DirectiveTokenChunkGenerator(descriptor.Tokens[3]))
|
||||
.Accepts(AcceptedCharacters.NonWhiteSpace)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DirectiveDescriptor_UnderstandsRazorBlocks()
|
||||
{
|
||||
// Arrange
|
||||
var descriptor = DirectiveDescriptorBuilder.CreateRazorBlock("custom").AddString().Build();
|
||||
|
||||
// Act & Assert
|
||||
ParseCodeBlockTest(
|
||||
"@custom Header { <p>F{o}o</p> }",
|
||||
new[] { descriptor },
|
||||
new DirectiveBlock(
|
||||
new DirectiveChunkGenerator(descriptor),
|
||||
Factory.CodeTransition(),
|
||||
Factory.MetaCode("custom").Accepts(AcceptedCharacters.None),
|
||||
Factory.Span(SpanKind.Markup, " ", markup: false).Accepts(AcceptedCharacters.WhiteSpace),
|
||||
Factory.Span(SpanKind.Markup, "Header", markup: false)
|
||||
.With(new DirectiveTokenChunkGenerator(descriptor.Tokens[0]))
|
||||
.Accepts(AcceptedCharacters.NonWhiteSpace),
|
||||
Factory.Span(SpanKind.Markup, " ", markup: false).Accepts(AcceptedCharacters.WhiteSpace),
|
||||
Factory.MetaCode("{")
|
||||
.AutoCompleteWith(null, atEndOfSpan: true)
|
||||
.Accepts(AcceptedCharacters.None),
|
||||
new MarkupBlock(
|
||||
Factory.Markup(" "),
|
||||
new MarkupTagBlock(
|
||||
Factory.Markup("<p>")),
|
||||
Factory.Markup("F", "{", "o", "}", "o"),
|
||||
new MarkupTagBlock(
|
||||
Factory.Markup("</p>")),
|
||||
Factory.Markup(" ")),
|
||||
Factory.MetaCode("}").Accepts(AcceptedCharacters.None)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DirectiveDescriptor_UnderstandsCodeBlocks()
|
||||
{
|
||||
// Arrange
|
||||
var descriptor = DirectiveDescriptorBuilder.CreateCodeBlock("custom").AddString().Build();
|
||||
|
||||
// Act & Assert
|
||||
ParseCodeBlockTest(
|
||||
"@custom Name { foo(); bar(); }",
|
||||
new[] { descriptor },
|
||||
new DirectiveBlock(
|
||||
new DirectiveChunkGenerator(descriptor),
|
||||
Factory.CodeTransition(),
|
||||
Factory.MetaCode("custom").Accepts(AcceptedCharacters.None),
|
||||
Factory.Span(SpanKind.Markup, " ", markup: false).Accepts(AcceptedCharacters.WhiteSpace),
|
||||
Factory.Span(SpanKind.Markup, "Name", markup: false)
|
||||
.With(new DirectiveTokenChunkGenerator(descriptor.Tokens[0]))
|
||||
.Accepts(AcceptedCharacters.NonWhiteSpace),
|
||||
Factory.Span(SpanKind.Markup, " ", markup: false).Accepts(AcceptedCharacters.WhiteSpace),
|
||||
Factory.MetaCode("{")
|
||||
.AutoCompleteWith(null, atEndOfSpan: true)
|
||||
.Accepts(AcceptedCharacters.None),
|
||||
Factory.Code(" foo(); bar(); ").AsStatement(),
|
||||
Factory.MetaCode("}").Accepts(AcceptedCharacters.None)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DirectiveDescriptor_AllowsWhiteSpaceAroundTokens()
|
||||
{
|
||||
// Arrange
|
||||
var descriptor = DirectiveDescriptorBuilder.Create("custom")
|
||||
.AddType()
|
||||
.AddMember()
|
||||
.Build();
|
||||
|
||||
// Act & Assert
|
||||
ParseCodeBlockTest(
|
||||
"@custom System.Text.Encoding.ASCIIEncoding Some_Member ",
|
||||
new[] { descriptor },
|
||||
new DirectiveBlock(
|
||||
new DirectiveChunkGenerator(descriptor),
|
||||
Factory.CodeTransition(),
|
||||
Factory.MetaCode("custom").Accepts(AcceptedCharacters.None),
|
||||
|
||||
Factory.Span(SpanKind.Code, " ", markup: false).Accepts(AcceptedCharacters.WhiteSpace),
|
||||
Factory.Span(SpanKind.Code, "System.Text.Encoding.ASCIIEncoding", markup: false)
|
||||
.With(new DirectiveTokenChunkGenerator(descriptor.Tokens[0]))
|
||||
.Accepts(AcceptedCharacters.NonWhiteSpace),
|
||||
|
||||
Factory.Span(SpanKind.Code, " ", markup: false).Accepts(AcceptedCharacters.WhiteSpace),
|
||||
Factory.Span(SpanKind.Code, "Some_Member", markup: false)
|
||||
.With(new DirectiveTokenChunkGenerator(descriptor.Tokens[1]))
|
||||
.Accepts(AcceptedCharacters.NonWhiteSpace),
|
||||
|
||||
Factory.Span(SpanKind.Markup, " ", markup: false)
|
||||
.Accepts(AcceptedCharacters.AllWhiteSpace)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DirectiveDescriptor_ErrorsForInvalidMemberTokens()
|
||||
{
|
||||
// Arrange
|
||||
var descriptor = DirectiveDescriptorBuilder.Create("custom").AddMember().Build();
|
||||
var expectedErorr = new RazorError(
|
||||
LegacyResources.FormatDirectiveExpectsIdentifier("custom"),
|
||||
new SourceLocation(8, 0, 8),
|
||||
length: 1);
|
||||
|
||||
// Act & Assert
|
||||
ParseCodeBlockTest(
|
||||
"@custom -Some_Member",
|
||||
new[] { descriptor },
|
||||
new DirectiveBlock(
|
||||
new DirectiveChunkGenerator(descriptor),
|
||||
Factory.CodeTransition(),
|
||||
Factory.MetaCode("custom").Accepts(AcceptedCharacters.None),
|
||||
Factory.Span(SpanKind.Code, " ", markup: false).Accepts(AcceptedCharacters.WhiteSpace)),
|
||||
expectedErorr);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DirectiveDescriptor_ErrorsForUnmatchedLiteralTokens()
|
||||
{
|
||||
// Arrange
|
||||
var descriptor = DirectiveDescriptorBuilder.Create("custom").AddLiteral("!").Build();
|
||||
var expectedErorr = new RazorError(
|
||||
LegacyResources.FormatUnexpectedDirectiveLiteral("custom", "!"),
|
||||
new SourceLocation(8, 0, 8),
|
||||
length: 2);
|
||||
|
||||
// Act & Assert
|
||||
ParseCodeBlockTest(
|
||||
"@custom hi",
|
||||
new[] { descriptor },
|
||||
new DirectiveBlock(
|
||||
new DirectiveChunkGenerator(descriptor),
|
||||
Factory.CodeTransition(),
|
||||
Factory.MetaCode("custom").Accepts(AcceptedCharacters.None),
|
||||
Factory.Span(SpanKind.Markup, " ", markup: false).Accepts(AcceptedCharacters.WhiteSpace)),
|
||||
expectedErorr);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DirectiveDescriptor_ErrorsExtraContentAfterDirective()
|
||||
{
|
||||
// Arrange
|
||||
var descriptor = DirectiveDescriptorBuilder.Create("custom").AddString().Build();
|
||||
var expectedErorr = new RazorError(
|
||||
LegacyResources.FormatUnexpectedDirectiveLiteral("custom", Environment.NewLine),
|
||||
new SourceLocation(14, 0, 14),
|
||||
length: 5);
|
||||
|
||||
// Act & Assert
|
||||
ParseCodeBlockTest(
|
||||
"@custom hello world",
|
||||
new[] { descriptor },
|
||||
new DirectiveBlock(
|
||||
new DirectiveChunkGenerator(descriptor),
|
||||
Factory.CodeTransition(),
|
||||
Factory.MetaCode("custom").Accepts(AcceptedCharacters.None),
|
||||
Factory.Span(SpanKind.Markup, " ", markup: false).Accepts(AcceptedCharacters.WhiteSpace),
|
||||
Factory.Span(SpanKind.Markup, "hello", markup: false)
|
||||
.With(new DirectiveTokenChunkGenerator(descriptor.Tokens[0]))
|
||||
.Accepts(AcceptedCharacters.NonWhiteSpace),
|
||||
|
||||
Factory.Span(SpanKind.Markup, " ", markup: false).Accepts(AcceptedCharacters.AllWhiteSpace)),
|
||||
expectedErorr);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DirectiveDescriptor_ErrorsWhenExtraContentBeforeBlockStart()
|
||||
{
|
||||
// Arrange
|
||||
var descriptor = DirectiveDescriptorBuilder.CreateCodeBlock("custom").AddString().Build();
|
||||
var expectedErorr = new RazorError(
|
||||
LegacyResources.FormatUnexpectedDirectiveLiteral("custom", "{"),
|
||||
new SourceLocation(14, 0, 14),
|
||||
length: 5);
|
||||
|
||||
// Act & Assert
|
||||
ParseCodeBlockTest(
|
||||
"@custom Hello World { foo(); bar(); }",
|
||||
new[] { descriptor },
|
||||
new DirectiveBlock(
|
||||
new DirectiveChunkGenerator(descriptor),
|
||||
Factory.CodeTransition(),
|
||||
Factory.MetaCode("custom").Accepts(AcceptedCharacters.None),
|
||||
Factory.Span(SpanKind.Markup, " ", markup: false).Accepts(AcceptedCharacters.WhiteSpace),
|
||||
Factory.Span(SpanKind.Markup, "Hello", markup: false)
|
||||
.With(new DirectiveTokenChunkGenerator(descriptor.Tokens[0]))
|
||||
.Accepts(AcceptedCharacters.NonWhiteSpace),
|
||||
|
||||
Factory.Span(SpanKind.Markup, " ", markup: false).Accepts(AcceptedCharacters.WhiteSpace)),
|
||||
expectedErorr);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DirectiveDescriptor_ErrorsWhenEOFBeforeDirectiveBlockStart()
|
||||
{
|
||||
// Arrange
|
||||
var descriptor = DirectiveDescriptorBuilder.CreateCodeBlock("custom").AddString().Build();
|
||||
var expectedErorr = new RazorError(
|
||||
LegacyResources.FormatUnexpectedEOFAfterDirective("custom", "{"),
|
||||
new SourceLocation(13, 0, 13),
|
||||
length: 1);
|
||||
|
||||
// Act & Assert
|
||||
ParseCodeBlockTest(
|
||||
"@custom Hello",
|
||||
new[] { descriptor },
|
||||
new DirectiveBlock(
|
||||
new DirectiveChunkGenerator(descriptor),
|
||||
Factory.CodeTransition(),
|
||||
Factory.MetaCode("custom").Accepts(AcceptedCharacters.None),
|
||||
Factory.Span(SpanKind.Markup, " ", markup: false).Accepts(AcceptedCharacters.WhiteSpace),
|
||||
Factory.Span(SpanKind.Markup, "Hello", markup: false)
|
||||
.With(new DirectiveTokenChunkGenerator(descriptor.Tokens[0]))
|
||||
.Accepts(AcceptedCharacters.NonWhiteSpace)),
|
||||
expectedErorr);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DirectiveDescriptor_ErrorsWhenMissingEndBrace()
|
||||
{
|
||||
// Arrange
|
||||
var descriptor = DirectiveDescriptorBuilder.CreateCodeBlock("custom").AddString().Build();
|
||||
var expectedErorr = new RazorError(
|
||||
LegacyResources.FormatParseError_Expected_EndOfBlock_Before_EOF("custom", "{", "}"),
|
||||
new SourceLocation(14, 0, 14),
|
||||
length: 1);
|
||||
|
||||
// Act & Assert
|
||||
ParseCodeBlockTest(
|
||||
"@custom Hello {",
|
||||
new[] { descriptor },
|
||||
new DirectiveBlock(
|
||||
new DirectiveChunkGenerator(descriptor),
|
||||
Factory.CodeTransition(),
|
||||
Factory.MetaCode("custom").Accepts(AcceptedCharacters.None),
|
||||
Factory.Span(SpanKind.Markup, " ", markup: false).Accepts(AcceptedCharacters.WhiteSpace),
|
||||
Factory.Span(SpanKind.Markup, "Hello", markup: false)
|
||||
.With(new DirectiveTokenChunkGenerator(descriptor.Tokens[0]))
|
||||
.Accepts(AcceptedCharacters.NonWhiteSpace),
|
||||
Factory.Span(SpanKind.Markup, " ", markup: false).Accepts(AcceptedCharacters.WhiteSpace),
|
||||
Factory.MetaCode("{")
|
||||
.AutoCompleteWith("}", atEndOfSpan: true)
|
||||
.Accepts(AcceptedCharacters.None)),
|
||||
expectedErorr);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TagHelperPrefixDirective_NoValueSucceeds()
|
||||
{
|
||||
|
|
@ -422,5 +788,16 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
|
|||
Factory.MetaCode("}")
|
||||
.Accepts(AcceptedCharacters.None)));
|
||||
}
|
||||
|
||||
internal virtual void ParseCodeBlockTest(
|
||||
string document,
|
||||
IEnumerable<DirectiveDescriptor> descriptors,
|
||||
Block expected,
|
||||
params RazorError[] expectedErrors)
|
||||
{
|
||||
var result = ParseCodeBlock(document, descriptors, designTime: false);
|
||||
|
||||
EvaluateResults(result, expected, expectedErrors);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -59,12 +59,20 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
|
|||
}
|
||||
|
||||
internal virtual RazorSyntaxTree ParseCodeBlock(string document, bool designTime = false)
|
||||
{
|
||||
return ParseCodeBlock(document, Enumerable.Empty<DirectiveDescriptor>(), designTime);
|
||||
}
|
||||
|
||||
internal virtual RazorSyntaxTree ParseCodeBlock(
|
||||
string document,
|
||||
IEnumerable<DirectiveDescriptor> descriptors,
|
||||
bool designTime)
|
||||
{
|
||||
using (var reader = new SeekableTextReader(document))
|
||||
{
|
||||
var context = new ParserContext(reader, designTime);
|
||||
|
||||
var parser = new CSharpCodeParser(context);
|
||||
var parser = new CSharpCodeParser(descriptors, context);
|
||||
parser.HtmlParser = new HtmlMarkupParser(context)
|
||||
{
|
||||
CodeParser = parser,
|
||||
|
|
|
|||
Loading…
Reference in New Issue