From 2eba53de1b240bf78e3cbe564739982dd4a2607a Mon Sep 17 00:00:00 2001 From: Pranav K Date: Tue, 7 Feb 2017 16:55:18 -0800 Subject: [PATCH] Add support for optional directives --- .../DirectiveDescriptorBuilder.cs | 23 ++++++++++++++++--- .../DirectiveTokenDescriptor.cs | 2 ++ .../DirectiveTokenDescriptorComparer.cs | 9 ++++++-- .../IDirectiveDescriptorBuilder.cs | 2 ++ .../Legacy/CSharpCodeParser.cs | 10 +++++--- .../Properties/Resources.Designer.cs | 16 +++++++++++++ .../Resources.resx | 3 +++ .../Legacy/CSharpDirectivesTest.cs | 17 ++++++++++++++ 8 files changed, 74 insertions(+), 8 deletions(-) diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/DirectiveDescriptorBuilder.cs b/src/Microsoft.AspNetCore.Razor.Evolution/DirectiveDescriptorBuilder.cs index 65d91d144d..bd5308767f 100644 --- a/src/Microsoft.AspNetCore.Razor.Evolution/DirectiveDescriptorBuilder.cs +++ b/src/Microsoft.AspNetCore.Razor.Evolution/DirectiveDescriptorBuilder.cs @@ -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 _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 diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/DirectiveTokenDescriptor.cs b/src/Microsoft.AspNetCore.Razor.Evolution/DirectiveTokenDescriptor.cs index 074f4003ea..30dfb54471 100644 --- a/src/Microsoft.AspNetCore.Razor.Evolution/DirectiveTokenDescriptor.cs +++ b/src/Microsoft.AspNetCore.Razor.Evolution/DirectiveTokenDescriptor.cs @@ -6,5 +6,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution public class DirectiveTokenDescriptor { public DirectiveTokenKind Kind { get; set; } + + public bool Optional { get; set; } } } diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/DirectiveTokenDescriptorComparer.cs b/src/Microsoft.AspNetCore.Razor.Evolution/DirectiveTokenDescriptorComparer.cs index 45a67ada3f..295fe5ba77 100644 --- a/src/Microsoft.AspNetCore.Razor.Evolution/DirectiveTokenDescriptorComparer.cs +++ b/src/Microsoft.AspNetCore.Razor.Evolution/DirectiveTokenDescriptorComparer.cs @@ -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; } } } diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/IDirectiveDescriptorBuilder.cs b/src/Microsoft.AspNetCore.Razor.Evolution/IDirectiveDescriptorBuilder.cs index 2fcab31c39..9bb22d3699 100644 --- a/src/Microsoft.AspNetCore.Razor.Evolution/IDirectiveDescriptorBuilder.cs +++ b/src/Microsoft.AspNetCore.Razor.Evolution/IDirectiveDescriptorBuilder.cs @@ -11,6 +11,8 @@ namespace Microsoft.AspNetCore.Razor.Evolution IDirectiveDescriptorBuilder AddString(); + IDirectiveDescriptorBuilder BeginOptionals(); + DirectiveDescriptor Build(); } } diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/CSharpCodeParser.cs b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/CSharpCodeParser.cs index 4524902dda..fb4fa75fc4 100644 --- a/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/CSharpCodeParser.cs +++ b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/CSharpCodeParser.cs @@ -83,7 +83,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy private Dictionary> _keywordParsers = new Dictionary>(); public CSharpCodeParser(ParserContext context) - : this (directiveDescriptors: Enumerable.Empty(), context: context) + : this(directiveDescriptors: Enumerable.Empty(), 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, diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/Properties/Resources.Designer.cs b/src/Microsoft.AspNetCore.Razor.Evolution/Properties/Resources.Designer.cs index f9e00ec9df..3cf3ce4f24 100644 --- a/src/Microsoft.AspNetCore.Razor.Evolution/Properties/Resources.Designer.cs +++ b/src/Microsoft.AspNetCore.Razor.Evolution/Properties/Resources.Designer.cs @@ -186,6 +186,22 @@ namespace Microsoft.AspNetCore.Razor.Evolution return GetString("RazorProject_PathMustStartWithForwardSlash"); } + /// + /// The method '{0}' has already been invoked. + /// + internal static string DirectiveDescriptor_BeginOptionalsAlreadyInvoked + { + get { return GetString("DirectiveDescriptor_BeginOptionalsAlreadyInvoked"); } + } + + /// + /// The method '{0}' has already been invoked. + /// + 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); diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/Resources.resx b/src/Microsoft.AspNetCore.Razor.Evolution/Resources.resx index 045870c0e4..9b0aa5e104 100644 --- a/src/Microsoft.AspNetCore.Razor.Evolution/Resources.resx +++ b/src/Microsoft.AspNetCore.Razor.Evolution/Resources.resx @@ -150,4 +150,7 @@ Path must begin with a forward slash '/'. + + The method '{0}' has already been invoked. + \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/CSharpDirectivesTest.cs b/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/CSharpDirectivesTest.cs index 08d1f8c64c..639e5b88d7 100644 --- a/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/CSharpDirectivesTest.cs +++ b/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/CSharpDirectivesTest.cs @@ -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 descriptors,