Add support for optional directives

This commit is contained in:
Pranav K 2017-02-07 16:55:18 -08:00
parent 6d18c3e85a
commit 2eba53de1b
8 changed files with 74 additions and 8 deletions

View File

@ -1,6 +1,7 @@
// 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;
namespace Microsoft.AspNetCore.Razor.Evolution
@ -27,6 +28,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution
private readonly List<DirectiveTokenDescriptor> _tokenDescriptors;
private readonly string _name;
private readonly DirectiveDescriptorKind _type;
private bool _optional;
public DefaultDirectiveDescriptorBuilder(string name, DirectiveDescriptorKind type)
{
@ -39,7 +41,8 @@ namespace Microsoft.AspNetCore.Razor.Evolution
{
var descriptor = new DirectiveTokenDescriptor()
{
Kind = DirectiveTokenKind.Type
Kind = DirectiveTokenKind.Type,
Optional = _optional,
};
_tokenDescriptors.Add(descriptor);
@ -50,7 +53,8 @@ namespace Microsoft.AspNetCore.Razor.Evolution
{
var descriptor = new DirectiveTokenDescriptor()
{
Kind = DirectiveTokenKind.Member
Kind = DirectiveTokenKind.Member,
Optional = _optional,
};
_tokenDescriptors.Add(descriptor);
@ -61,13 +65,26 @@ namespace Microsoft.AspNetCore.Razor.Evolution
{
var descriptor = new DirectiveTokenDescriptor()
{
Kind = DirectiveTokenKind.String
Kind = DirectiveTokenKind.String,
Optional = _optional,
};
_tokenDescriptors.Add(descriptor);
return this;
}
public IDirectiveDescriptorBuilder BeginOptionals()
{
if (_optional)
{
throw new InvalidOperationException(
Resources.FormatDirectiveDescriptor_BeginOptionalsAlreadyInvoked(nameof(BeginOptionals)));
}
_optional = true;
return this;
}
public DirectiveDescriptor Build()
{
var descriptor = new DirectiveDescriptor

View File

@ -6,5 +6,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution
public class DirectiveTokenDescriptor
{
public DirectiveTokenKind Kind { get; set; }
public bool Optional { get; set; }
}
}

View File

@ -23,7 +23,8 @@ namespace Microsoft.AspNetCore.Razor.Evolution
}
return descriptorX != null &&
descriptorX.Kind == descriptorY.Kind;
descriptorX.Kind == descriptorY.Kind &&
descriptorX.Optional == descriptorY.Optional;
}
public int GetHashCode(DirectiveTokenDescriptor descriptor)
@ -33,7 +34,11 @@ namespace Microsoft.AspNetCore.Razor.Evolution
throw new ArgumentNullException(nameof(descriptor));
}
return descriptor.Kind.GetHashCode();
var hashCodeCombiner = HashCodeCombiner.Start();
hashCodeCombiner.Add(descriptor.Kind);
hashCodeCombiner.Add(descriptor.Optional ? 1 : 0);
return hashCodeCombiner.CombinedHash;
}
}
}

View File

@ -11,6 +11,8 @@ namespace Microsoft.AspNetCore.Razor.Evolution
IDirectiveDescriptorBuilder AddString();
IDirectiveDescriptorBuilder BeginOptionals();
DirectiveDescriptor Build();
}
}

View File

@ -83,7 +83,7 @@ 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)
: this(directiveDescriptors: Enumerable.Empty<DirectiveDescriptor>(), context: context)
{
}
@ -223,7 +223,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
// No "@" => Jump straight to AfterTransition
AfterTransition();
}
Output(SpanKind.Code);
}
}
@ -1507,7 +1507,11 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
Output(SpanKind.Markup, AcceptedCharacters.WhiteSpace);
}
if (EndOfFile)
if (tokenDescriptor.Optional && (EndOfFile || At(CSharpSymbolType.NewLine)))
{
break;
}
else if (EndOfFile)
{
Context.ErrorSink.OnError(
CurrentStart,

View File

@ -186,6 +186,22 @@ namespace Microsoft.AspNetCore.Razor.Evolution
return GetString("RazorProject_PathMustStartWithForwardSlash");
}
/// <summary>
/// The method '{0}' has already been invoked.
/// </summary>
internal static string DirectiveDescriptor_BeginOptionalsAlreadyInvoked
{
get { return GetString("DirectiveDescriptor_BeginOptionalsAlreadyInvoked"); }
}
/// <summary>
/// The method '{0}' has already been invoked.
/// </summary>
internal static string FormatDirectiveDescriptor_BeginOptionalsAlreadyInvoked(object p0)
{
return string.Format(CultureInfo.CurrentCulture, GetString("DirectiveDescriptor_BeginOptionalsAlreadyInvoked"), p0);
}
private static string GetString(string name, params string[] formatterNames)
{
var value = _resourceManager.GetString(name);

View File

@ -150,4 +150,7 @@
<data name="RazorProject_PathMustStartWithForwardSlash" xml:space="preserve">
<value>Path must begin with a forward slash '/'.</value>
</data>
<data name="DirectiveDescriptor_BeginOptionalsAlreadyInvoked" xml:space="preserve">
<value>The method '{0}' has already been invoked.</value>
</data>
</root>

View File

@ -734,6 +734,23 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
.Accepts(AcceptedCharacters.None)));
}
[Fact]
public void OptionalDirectiveTokens_AreSkipped()
{
// Arrange
var descriptor = DirectiveDescriptorBuilder.Create("custom").BeginOptionals().AddString().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)));
}
internal virtual void ParseCodeBlockTest(
string document,
IEnumerable<DirectiveDescriptor> descriptors,