From b17e506ce83007bddd7838e9a71958bc002f7660 Mon Sep 17 00:00:00 2001 From: Ryan Nowak Date: Wed, 10 May 2017 17:40:51 -0700 Subject: [PATCH] Some API cleanup around directivest --- .../InjectDirective.cs | 5 +- .../ModelDirective.cs | 5 +- .../NamespaceDirective.cs | 7 +- .../PageDirective.cs | 14 +- .../DefaultRazorIRLoweringPhase.cs | 2 +- .../DirectiveDescriptor.cs | 155 +++++++++++++++++- .../DirectiveDescriptorBuilder.cs | 113 ------------- .../DirectiveDescriptorBuilderExtensions.cs | 98 +++++++++++ ...tiveDescriptorKind.cs => DirectiveKind.cs} | 2 +- .../DirectiveTokenDescriptor.cs | 29 +++- .../IDirectiveDescriptorBuilder.cs | 12 +- .../Legacy/CSharpCodeParser.cs | 58 +++---- .../Properties/Resources.Designer.cs | 26 ++- .../Resources.resx | 7 +- .../PageDirectiveTest.cs | 19 --- .../DesignTimeDirectiveTargetExtensionTest.cs | 30 +--- ...aultRazorIRLoweringPhaseIntegrationTest.cs | 4 +- .../DefaultRazorParsingPhaseTest.cs | 9 +- .../DirectiveDescriptorBuilderTest.cs | 144 ++++++++-------- .../DirectiveDescriptorTest.cs | 150 +++++++++++++++++ .../DirectiveRemovalIROptimizationPassTest.cs | 6 +- .../IntegrationTests/BasicIntegrationTest.cs | 2 +- .../ExtensibleDirectiveTest.cs | 2 +- .../Legacy/CSharpDirectivesTest.cs | 151 +++++++++++++---- .../RazorEngineBuilderExtensionsTest.cs | 8 +- .../CustomDirective.cshtml | 2 +- .../RazorInfo/DirectiveViewModel.cs | 2 +- 27 files changed, 715 insertions(+), 347 deletions(-) delete mode 100644 src/Microsoft.AspNetCore.Razor.Language/DirectiveDescriptorBuilder.cs create mode 100644 src/Microsoft.AspNetCore.Razor.Language/DirectiveDescriptorBuilderExtensions.cs rename src/Microsoft.AspNetCore.Razor.Language/{DirectiveDescriptorKind.cs => DirectiveKind.cs} (87%) create mode 100644 test/Microsoft.AspNetCore.Razor.Language.Test/DirectiveDescriptorTest.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/InjectDirective.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/InjectDirective.cs index 77a2284ee4..368f96393e 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/InjectDirective.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/InjectDirective.cs @@ -11,7 +11,10 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions { public static class InjectDirective { - public static readonly DirectiveDescriptor Directive = DirectiveDescriptorBuilder.Create("inject").AddType().AddMember().Build(); + public static readonly DirectiveDescriptor Directive = DirectiveDescriptor.CreateDirective( + "inject", + DirectiveKind.SingleLine, + builder => builder.AddTypeToken().AddMemberToken()); public static IRazorEngineBuilder Register(IRazorEngineBuilder builder) { diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/ModelDirective.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/ModelDirective.cs index 0edd7bdd10..8b358ec8d0 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/ModelDirective.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/ModelDirective.cs @@ -11,7 +11,10 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions { public static class ModelDirective { - public static readonly DirectiveDescriptor Directive = DirectiveDescriptorBuilder.Create("model").AddType().Build(); + public static readonly DirectiveDescriptor Directive = DirectiveDescriptor.CreateDirective( + "model", + DirectiveKind.SingleLine, + builder => builder.AddTypeToken()); public static IRazorEngineBuilder Register(IRazorEngineBuilder builder) { diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/NamespaceDirective.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/NamespaceDirective.cs index e953280ff5..263f2d1b86 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/NamespaceDirective.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/NamespaceDirective.cs @@ -12,9 +12,12 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions { public static class NamespaceDirective { - private static readonly char[] Separators = new char[] { '\\', '/' }; + private static readonly char[] Separators = new char[] { '\\', '/' }; - public static readonly DirectiveDescriptor Directive = DirectiveDescriptorBuilder.Create("namespace").AddNamespace().Build(); + public static readonly DirectiveDescriptor Directive = DirectiveDescriptor.CreateDirective( + "namespace", + DirectiveKind.SingleLine, + builder => builder.AddNamespaceToken()); public static void Register(IRazorEngineBuilder builder) { diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/PageDirective.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/PageDirective.cs index b46fb85612..93bb60aab4 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/PageDirective.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/PageDirective.cs @@ -11,12 +11,10 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions { public class PageDirective { - public static readonly DirectiveDescriptor DirectiveDescriptor = DirectiveDescriptorBuilder - .Create("page") - .BeginOptionals() - .AddString() // Route template - .AddString() // Page Name - .Build(); + public static readonly DirectiveDescriptor Directive = DirectiveDescriptor.CreateDirective( + "page", + DirectiveKind.SingleLine, + builder => builder.AddOptionalStringToken()); private PageDirective(string routeTemplate, string pageName) { @@ -30,7 +28,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions public static IRazorEngineBuilder Register(IRazorEngineBuilder builder) { - builder.AddDirective(DirectiveDescriptor); + builder.AddDirective(Directive); return builder; } @@ -80,7 +78,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions public override void VisitDirective(DirectiveIRNode node) { - if (node.Descriptor == DirectiveDescriptor) + if (node.Descriptor == Directive) { DirectiveNode = node; } diff --git a/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorIRLoweringPhase.cs b/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorIRLoweringPhase.cs index 64d099a08f..9c19004431 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorIRLoweringPhase.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorIRLoweringPhase.cs @@ -199,7 +199,7 @@ namespace Microsoft.AspNetCore.Razor.Language public override void VisitDirectiveBlock(DirectiveChunkGenerator chunkGenerator, Block block) { - if (chunkGenerator.Descriptor.Kind == DirectiveDescriptorKind.SingleLine) + if (chunkGenerator.Descriptor.Kind == DirectiveKind.SingleLine) { _insideLineDirective = true; diff --git a/src/Microsoft.AspNetCore.Razor.Language/DirectiveDescriptor.cs b/src/Microsoft.AspNetCore.Razor.Language/DirectiveDescriptor.cs index 263681379e..c9d401fde8 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/DirectiveDescriptor.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/DirectiveDescriptor.cs @@ -1,16 +1,163 @@ // 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; namespace Microsoft.AspNetCore.Razor.Language { - public class DirectiveDescriptor + public abstract class DirectiveDescriptor { - public string Name { get; set; } + public abstract string Name { get; } - public DirectiveDescriptorKind Kind { get; set; } + public abstract DirectiveKind Kind { get; } - public IReadOnlyList Tokens { get; set; } + public abstract IReadOnlyList Tokens { get; } + + public static DirectiveDescriptor CreateDirective(string name, DirectiveKind kind) + { + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + + return CreateDirective(name, kind, configure: null); + } + + public static DirectiveDescriptor CreateDirective(string name, DirectiveKind kind, Action configure) + { + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + + var builder = new DefaultDirectiveDescriptorBuilder(name, kind); + configure?.Invoke(builder); + return builder.Build(); + } + + public static DirectiveDescriptor CreateSingleLineDirective(string name) + { + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + + return CreateDirective(name, DirectiveKind.SingleLine, configure: null); + } + + public static DirectiveDescriptor CreateSingleLineDirective(string name, Action configure) + { + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + + return CreateDirective(name, DirectiveKind.SingleLine, configure); + } + + public static DirectiveDescriptor CreateRazorBlockDirective(string name) + { + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + + return CreateDirective(name, DirectiveKind.RazorBlock, configure: null); + } + + public static DirectiveDescriptor CreateRazorBlockDirective(string name, Action configure) + { + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + + return CreateDirective(name, DirectiveKind.RazorBlock, configure); + } + + public static DirectiveDescriptor CreateCodeBlockDirective(string name) + { + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + + return CreateDirective(name, DirectiveKind.CodeBlock, configure: null); + } + + public static DirectiveDescriptor CreateCodeBlockDirective(string name, Action configure) + { + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + + return CreateDirective(name, DirectiveKind.CodeBlock, configure); + } + + private class DefaultDirectiveDescriptorBuilder : IDirectiveDescriptorBuilder + { + public DefaultDirectiveDescriptorBuilder(string name, DirectiveKind kind) + { + Name = name; + Kind = kind; + + Tokens = new List(); + } + + public string Name { get; } + + public DirectiveKind Kind { get; } + + public IList Tokens { get; } + + public DirectiveDescriptor Build() + { + if (Name.Length == 0) + { + throw new InvalidOperationException(Resources.FormatDirectiveDescriptor_InvalidDirectiveName(Name)); + } + + for (var i = 0; i < Name.Length; i++) + { + if (!char.IsLetter(Name[i])) + { + throw new InvalidOperationException(Resources.FormatDirectiveDescriptor_InvalidDirectiveName(Name)); + } + } + + var foundOptionalToken = false; + for (var i = 0; i < Tokens.Count; i++) + { + var token = Tokens[i]; + foundOptionalToken |= token.Optional; + + if (foundOptionalToken && !token.Optional) + { + throw new InvalidOperationException(Resources.DirectiveDescriptor_InvalidNonOptionalToken); + } + } + + return new DefaultDirectiveDescriptor(Name, Kind, Tokens.ToArray()); + } + } + + private class DefaultDirectiveDescriptor : DirectiveDescriptor + { + public DefaultDirectiveDescriptor(string name, DirectiveKind kind, DirectiveTokenDescriptor[] tokens) + { + Name = name; + Kind = kind; + Tokens = tokens; + } + + public override string Name { get; } + + public override DirectiveKind Kind { get; } + + public override IReadOnlyList Tokens { get; } + } } } diff --git a/src/Microsoft.AspNetCore.Razor.Language/DirectiveDescriptorBuilder.cs b/src/Microsoft.AspNetCore.Razor.Language/DirectiveDescriptorBuilder.cs deleted file mode 100644 index c47aad26f0..0000000000 --- a/src/Microsoft.AspNetCore.Razor.Language/DirectiveDescriptorBuilder.cs +++ /dev/null @@ -1,113 +0,0 @@ -// 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.Language -{ - 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 _tokenDescriptors; - private readonly string _name; - private readonly DirectiveDescriptorKind _type; - private bool _optional; - - public DefaultDirectiveDescriptorBuilder(string name, DirectiveDescriptorKind type) - { - _name = name; - _type = type; - _tokenDescriptors = new List(); - } - - public IDirectiveDescriptorBuilder AddType() - { - var descriptor = new DirectiveTokenDescriptor() - { - Kind = DirectiveTokenKind.Type, - Optional = _optional, - }; - _tokenDescriptors.Add(descriptor); - - return this; - } - - public IDirectiveDescriptorBuilder AddMember() - { - var descriptor = new DirectiveTokenDescriptor() - { - Kind = DirectiveTokenKind.Member, - Optional = _optional, - }; - _tokenDescriptors.Add(descriptor); - - return this; - } - - public IDirectiveDescriptorBuilder AddNamespace() - { - var descriptor = new DirectiveTokenDescriptor() - { - Kind = DirectiveTokenKind.Namespace, - Optional = _optional, - }; - _tokenDescriptors.Add(descriptor); - - return this; - } - - public IDirectiveDescriptorBuilder AddString() - { - var descriptor = new DirectiveTokenDescriptor() - { - 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 - { - Name = _name, - Kind = _type, - Tokens = _tokenDescriptors, - }; - - return descriptor; - } - } - } -} diff --git a/src/Microsoft.AspNetCore.Razor.Language/DirectiveDescriptorBuilderExtensions.cs b/src/Microsoft.AspNetCore.Razor.Language/DirectiveDescriptorBuilderExtensions.cs new file mode 100644 index 0000000000..0758671e4b --- /dev/null +++ b/src/Microsoft.AspNetCore.Razor.Language/DirectiveDescriptorBuilderExtensions.cs @@ -0,0 +1,98 @@ +// 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; + +namespace Microsoft.AspNetCore.Razor.Language +{ + public static class DirectiveDescriptorBuilderExtensions + { + public static IDirectiveDescriptorBuilder AddMemberToken(this IDirectiveDescriptorBuilder builder) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + builder.Tokens.Add(DirectiveTokenDescriptor.CreateToken(DirectiveTokenKind.Member)); + return builder; + } + + public static IDirectiveDescriptorBuilder AddNamespaceToken(this IDirectiveDescriptorBuilder builder) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + builder.Tokens.Add(DirectiveTokenDescriptor.CreateToken(DirectiveTokenKind.Namespace)); + return builder; + } + + public static IDirectiveDescriptorBuilder AddStringToken(this IDirectiveDescriptorBuilder builder) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + builder.Tokens.Add(DirectiveTokenDescriptor.CreateToken(DirectiveTokenKind.String)); + return builder; + } + + public static IDirectiveDescriptorBuilder AddTypeToken(this IDirectiveDescriptorBuilder builder) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + builder.Tokens.Add(DirectiveTokenDescriptor.CreateToken(DirectiveTokenKind.Type)); + return builder; + } + + public static IDirectiveDescriptorBuilder AddOptionalMemberToken(this IDirectiveDescriptorBuilder builder) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + builder.Tokens.Add(DirectiveTokenDescriptor.CreateToken(DirectiveTokenKind.Member, optional: true)); + return builder; + } + + public static IDirectiveDescriptorBuilder AddOptionalNamespaceToken(this IDirectiveDescriptorBuilder builder) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + builder.Tokens.Add(DirectiveTokenDescriptor.CreateToken(DirectiveTokenKind.Namespace, optional: true)); + return builder; + } + + public static IDirectiveDescriptorBuilder AddOptionalStringToken(this IDirectiveDescriptorBuilder builder) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + builder.Tokens.Add(DirectiveTokenDescriptor.CreateToken(DirectiveTokenKind.String, optional: true)); + return builder; + } + + public static IDirectiveDescriptorBuilder AddOptionalTypeToken(this IDirectiveDescriptorBuilder builder) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + builder.Tokens.Add(DirectiveTokenDescriptor.CreateToken(DirectiveTokenKind.Type, optional: true)); + return builder; + } + } +} diff --git a/src/Microsoft.AspNetCore.Razor.Language/DirectiveDescriptorKind.cs b/src/Microsoft.AspNetCore.Razor.Language/DirectiveKind.cs similarity index 87% rename from src/Microsoft.AspNetCore.Razor.Language/DirectiveDescriptorKind.cs rename to src/Microsoft.AspNetCore.Razor.Language/DirectiveKind.cs index f7d79e9135..4c26a3c2af 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/DirectiveDescriptorKind.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/DirectiveKind.cs @@ -3,7 +3,7 @@ namespace Microsoft.AspNetCore.Razor.Language { - public enum DirectiveDescriptorKind + public enum DirectiveKind { SingleLine, RazorBlock, diff --git a/src/Microsoft.AspNetCore.Razor.Language/DirectiveTokenDescriptor.cs b/src/Microsoft.AspNetCore.Razor.Language/DirectiveTokenDescriptor.cs index 24bc4f99fa..54a09ce4e9 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/DirectiveTokenDescriptor.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/DirectiveTokenDescriptor.cs @@ -3,10 +3,33 @@ namespace Microsoft.AspNetCore.Razor.Language { - public class DirectiveTokenDescriptor + public abstract class DirectiveTokenDescriptor { - public DirectiveTokenKind Kind { get; set; } + public abstract DirectiveTokenKind Kind { get; } - public bool Optional { get; set; } + public abstract bool Optional { get; } + + public static DirectiveTokenDescriptor CreateToken(DirectiveTokenKind kind) + { + return CreateToken(kind, optional: false); + } + + public static DirectiveTokenDescriptor CreateToken(DirectiveTokenKind kind, bool optional) + { + return new DefaultDirectiveTokenDescriptor(kind, optional); + } + + private class DefaultDirectiveTokenDescriptor : DirectiveTokenDescriptor + { + public DefaultDirectiveTokenDescriptor(DirectiveTokenKind kind, bool optional) + { + Kind = kind; + Optional = optional; + } + + public override DirectiveTokenKind Kind { get; } + + public override bool Optional { get; } + } } } diff --git a/src/Microsoft.AspNetCore.Razor.Language/IDirectiveDescriptorBuilder.cs b/src/Microsoft.AspNetCore.Razor.Language/IDirectiveDescriptorBuilder.cs index c3dcfaf0fa..fd60d215a7 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/IDirectiveDescriptorBuilder.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/IDirectiveDescriptorBuilder.cs @@ -1,19 +1,17 @@ // 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.Language { public interface IDirectiveDescriptorBuilder { - IDirectiveDescriptorBuilder AddType(); + string Name { get; } - IDirectiveDescriptorBuilder AddMember(); + DirectiveKind Kind { get; } - IDirectiveDescriptorBuilder AddNamespace(); - - IDirectiveDescriptorBuilder AddString(); - - IDirectiveDescriptorBuilder BeginOptionals(); + IList Tokens { get; } DirectiveDescriptor Build(); } diff --git a/src/Microsoft.AspNetCore.Razor.Language/Legacy/CSharpCodeParser.cs b/src/Microsoft.AspNetCore.Razor.Language/Legacy/CSharpCodeParser.cs index 67c72f9712..e48004e4e6 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/Legacy/CSharpCodeParser.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/Legacy/CSharpCodeParser.cs @@ -13,40 +13,34 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy private static readonly Func IsValidStatementSpacingSymbol = IsSpacingToken(includeNewLines: true, includeComments: true); - internal static readonly DirectiveDescriptor SectionDirectiveDescriptor = - DirectiveDescriptorBuilder - .CreateRazorBlock(SyntaxConstants.CSharp.SectionKeyword) - .AddMember() - .Build(); + internal static readonly DirectiveDescriptor SectionDirectiveDescriptor = DirectiveDescriptor.CreateDirective( + SyntaxConstants.CSharp.SectionKeyword, + DirectiveKind.RazorBlock, + builder => builder.AddMemberToken()); - internal static readonly DirectiveDescriptor FunctionsDirectiveDescriptor = - DirectiveDescriptorBuilder - .CreateCodeBlock(SyntaxConstants.CSharp.FunctionsKeyword) - .Build(); + internal static readonly DirectiveDescriptor FunctionsDirectiveDescriptor = DirectiveDescriptor.CreateDirective( + SyntaxConstants.CSharp.FunctionsKeyword, + DirectiveKind.CodeBlock); - internal static readonly DirectiveDescriptor InheritsDirectiveDescriptor = - DirectiveDescriptorBuilder - .Create(SyntaxConstants.CSharp.InheritsKeyword) - .AddType() - .Build(); + internal static readonly DirectiveDescriptor InheritsDirectiveDescriptor = DirectiveDescriptor.CreateDirective( + SyntaxConstants.CSharp.InheritsKeyword, + DirectiveKind.SingleLine, + builder => builder.AddTypeToken()); - internal static readonly DirectiveDescriptor AddTagHelperDirectiveDescriptor = - DirectiveDescriptorBuilder - .Create(SyntaxConstants.CSharp.AddTagHelperKeyword) - .AddString() - .Build(); + internal static readonly DirectiveDescriptor AddTagHelperDirectiveDescriptor = DirectiveDescriptor.CreateDirective( + SyntaxConstants.CSharp.AddTagHelperKeyword, + DirectiveKind.SingleLine, + builder => builder.AddStringToken()); - internal static readonly DirectiveDescriptor RemoveTagHelperDirectiveDescriptor = - DirectiveDescriptorBuilder - .Create(SyntaxConstants.CSharp.RemoveTagHelperKeyword) - .AddString() - .Build(); + internal static readonly DirectiveDescriptor RemoveTagHelperDirectiveDescriptor = DirectiveDescriptor.CreateDirective( + SyntaxConstants.CSharp.RemoveTagHelperKeyword, + DirectiveKind.SingleLine, + builder => builder.AddStringToken()); - internal static readonly DirectiveDescriptor TagHelperPrefixDirectiveDescriptor = - DirectiveDescriptorBuilder - .Create(SyntaxConstants.CSharp.TagHelperPrefixKeyword) - .AddString() - .Build(); + internal static readonly DirectiveDescriptor TagHelperPrefixDirectiveDescriptor = DirectiveDescriptor.CreateDirective( + SyntaxConstants.CSharp.TagHelperPrefixKeyword, + DirectiveKind.SingleLine, + builder => builder.AddStringToken()); internal static readonly IEnumerable DefaultDirectiveDescriptors = new[] { @@ -1638,7 +1632,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy switch (descriptor.Kind) { - case DirectiveDescriptorKind.SingleLine: + case DirectiveKind.SingleLine: Optional(CSharpSymbolType.Semicolon); AcceptWhile(IsSpacingToken(includeNewLines: false, includeComments: true)); @@ -1656,7 +1650,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy Output(SpanKind.Markup, AcceptedCharacters.WhiteSpace); break; - case DirectiveDescriptorKind.RazorBlock: + case DirectiveKind.RazorBlock: AcceptWhile(IsSpacingToken(includeNewLines: true, includeComments: true)); Output(SpanKind.Markup, AcceptedCharacters.AllWhiteSpace); @@ -1681,7 +1675,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy NextToken(); }); break; - case DirectiveDescriptorKind.CodeBlock: + case DirectiveKind.CodeBlock: AcceptWhile(IsSpacingToken(includeNewLines: true, includeComments: true)); Output(SpanKind.Markup, AcceptedCharacters.AllWhiteSpace); diff --git a/src/Microsoft.AspNetCore.Razor.Language/Properties/Resources.Designer.cs b/src/Microsoft.AspNetCore.Razor.Language/Properties/Resources.Designer.cs index 8617863c1d..79a7fff09f 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/Properties/Resources.Designer.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/Properties/Resources.Designer.cs @@ -151,18 +151,18 @@ namespace Microsoft.AspNetCore.Razor.Language => GetString("RazorProject_PathMustStartWithForwardSlash"); /// - /// The method '{0}' has already been invoked. + /// A non-optional directive token cannot follow an optional directive token. /// - internal static string DirectiveDescriptor_BeginOptionalsAlreadyInvoked + internal static string DirectiveDescriptor_InvalidNonOptionalToken { - get => GetString("DirectiveDescriptor_BeginOptionalsAlreadyInvoked"); + get => GetString("DirectiveDescriptor_InvalidNonOptionalToken"); } /// - /// The method '{0}' has already been invoked. + /// A non-optional directive token cannot follow an optional directive token. /// - internal static string FormatDirectiveDescriptor_BeginOptionalsAlreadyInvoked(object p0) - => string.Format(CultureInfo.CurrentCulture, GetString("DirectiveDescriptor_BeginOptionalsAlreadyInvoked"), p0); + internal static string FormatDirectiveDescriptor_InvalidNonOptionalToken() + => GetString("DirectiveDescriptor_InvalidNonOptionalToken"); /// /// The document of kind '{0}' does not have a '{1}'. The document classifier must set a value for '{2}'. @@ -388,6 +388,20 @@ namespace Microsoft.AspNetCore.Razor.Language internal static string FormatInvalidOperation_SpanIsNotChangeOwner(object p0, object p1) => string.Format(CultureInfo.CurrentCulture, GetString("InvalidOperation_SpanIsNotChangeOwner"), p0, p1); + /// + /// Invalid directive name '{0}'. Directives must have a non-empty name that consists only of letters. + /// + internal static string DirectiveDescriptor_InvalidDirectiveName + { + get => GetString("DirectiveDescriptor_InvalidDirectiveName"); + } + + /// + /// Invalid directive name '{0}'. Directives must have a non-empty name that consists only of letters. + /// + internal static string FormatDirectiveDescriptor_InvalidDirectiveName(object p0) + => string.Format(CultureInfo.CurrentCulture, GetString("DirectiveDescriptor_InvalidDirectiveName"), p0); + private static string GetString(string name, params string[] formatterNames) { var value = _resourceManager.GetString(name); diff --git a/src/Microsoft.AspNetCore.Razor.Language/Resources.resx b/src/Microsoft.AspNetCore.Razor.Language/Resources.resx index 8a2d4c4fcf..3f9f426265 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/Resources.resx +++ b/src/Microsoft.AspNetCore.Razor.Language/Resources.resx @@ -147,8 +147,8 @@ Path must begin with a forward slash '/'. - - The method '{0}' has already been invoked. + + A non-optional directive token cannot follow an optional directive token. The document of kind '{0}' does not have a '{1}'. The document classifier must set a value for '{2}'. @@ -198,4 +198,7 @@ The node '{0}' is not the owner of change '{1}'. + + Invalid directive name '{0}'. Directives must have a non-empty name that consists only of letters. + \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/PageDirectiveTest.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/PageDirectiveTest.cs index f94bbf8559..c8190d89a9 100644 --- a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/PageDirectiveTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/PageDirectiveTest.cs @@ -102,25 +102,6 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions Assert.Null(pageDirective.PageName); } - [Fact] - public void TryGetPageDirective_ParsesPageName() - { - // Arrange - var content = "@page \"some-route\" \"some name\""; - var sourceDocument = RazorSourceDocument.Create(content, "file"); - var codeDocument = RazorCodeDocument.Create(sourceDocument); - var engine = CreateEngine(); - var irDocument = CreateIRDocument(engine, codeDocument); - - // Act - var result = PageDirective.TryGetPageDirective(irDocument, out var pageDirective); - - // Assert - Assert.True(result); - Assert.Equal("some-route", pageDirective.RouteTemplate); - Assert.Equal("some name", pageDirective.PageName); - } - private RazorEngine CreateEngine() { return RazorEngine.Create(b => diff --git a/test/Microsoft.AspNetCore.Razor.Language.Test/CodeGeneration/DesignTimeDirectiveTargetExtensionTest.cs b/test/Microsoft.AspNetCore.Razor.Language.Test/CodeGeneration/DesignTimeDirectiveTargetExtensionTest.cs index a3cf9463de..32a4ea69a2 100644 --- a/test/Microsoft.AspNetCore.Razor.Language.Test/CodeGeneration/DesignTimeDirectiveTargetExtensionTest.cs +++ b/test/Microsoft.AspNetCore.Razor.Language.Test/CodeGeneration/DesignTimeDirectiveTargetExtensionTest.cs @@ -52,10 +52,7 @@ private void __RazorDirectiveTokenHelpers__() { { Source = new SourceSpan("test.cshtml", 0, 0, 0, 5), Content = "System.String", - Descriptor = new DirectiveTokenDescriptor() - { - Kind = DirectiveTokenKind.Type - } + Descriptor = DirectiveTokenDescriptor.CreateToken(DirectiveTokenKind.Type), }; node.Children.Add(token); @@ -94,10 +91,7 @@ System.String __typeHelper = null; { Source = new SourceSpan("test.cshtml", 0, 0, 0, 5), Content = "System.Collections.Generic", - Descriptor = new DirectiveTokenDescriptor() - { - Kind = DirectiveTokenKind.Namespace - } + Descriptor = DirectiveTokenDescriptor.CreateToken(DirectiveTokenKind.Namespace), }; node.Children.Add(token); @@ -136,10 +130,7 @@ global::System.Object __typeHelper = nameof(System.Collections.Generic); { Source = new SourceSpan("test.cshtml", 0, 0, 0, 5), Content = "Foo", - Descriptor = new DirectiveTokenDescriptor() - { - Kind = DirectiveTokenKind.Member - } + Descriptor = DirectiveTokenDescriptor.CreateToken(DirectiveTokenKind.Member), }; node.Children.Add(token); @@ -178,19 +169,13 @@ global::System.Object Foo = null; { Source = new SourceSpan("test.cshtml", 0, 0, 0, 5), Content = "Value", - Descriptor = new DirectiveTokenDescriptor() - { - Kind = DirectiveTokenKind.String - } + Descriptor = DirectiveTokenDescriptor.CreateToken(DirectiveTokenKind.String), }; var tokenWithQuotedContent = new DirectiveTokenIRNode() { Source = new SourceSpan("test.cshtml", 0, 0, 0, 5), Content = "\"Value\"", - Descriptor = new DirectiveTokenDescriptor() - { - Kind = DirectiveTokenKind.String - } + Descriptor = DirectiveTokenDescriptor.CreateToken(DirectiveTokenKind.String), }; node.Children.Add(token); node.Children.Add(tokenWithQuotedContent); @@ -233,10 +218,7 @@ global::System.Object __typeHelper = ""Value""; var token = new DirectiveTokenIRNode() { Content = "Value", - Descriptor = new DirectiveTokenDescriptor() - { - Kind = DirectiveTokenKind.String - } + Descriptor = DirectiveTokenDescriptor.CreateToken(DirectiveTokenKind.String), }; node.Children.Add(token); diff --git a/test/Microsoft.AspNetCore.Razor.Language.Test/DefaultRazorIRLoweringPhaseIntegrationTest.cs b/test/Microsoft.AspNetCore.Razor.Language.Test/DefaultRazorIRLoweringPhaseIntegrationTest.cs index 44f58f4108..5efe93738f 100644 --- a/test/Microsoft.AspNetCore.Razor.Language.Test/DefaultRazorIRLoweringPhaseIntegrationTest.cs +++ b/test/Microsoft.AspNetCore.Razor.Language.Test/DefaultRazorIRLoweringPhaseIntegrationTest.cs @@ -411,7 +411,7 @@ namespace Microsoft.AspNetCore.Razor.Language // Act var irDocument = Lower(codeDocument, b => { - b.AddDirective(DirectiveDescriptorBuilder.Create("test").AddMember().Build()); + b.AddDirective(DirectiveDescriptor.CreateDirective("test", DirectiveKind.SingleLine, d => d.AddMemberToken())); }); // Assert @@ -440,7 +440,7 @@ namespace Microsoft.AspNetCore.Razor.Language // Act var irDocument = Lower(codeDocument, b => { - b.AddDirective(DirectiveDescriptorBuilder.CreateRazorBlock("block").AddMember().Build()); + b.AddDirective(DirectiveDescriptor.CreateDirective("block", DirectiveKind.RazorBlock, d => d.AddMemberToken())); }); // Assert diff --git a/test/Microsoft.AspNetCore.Razor.Language.Test/DefaultRazorParsingPhaseTest.cs b/test/Microsoft.AspNetCore.Razor.Language.Test/DefaultRazorParsingPhaseTest.cs index 4be0207674..abfc574c37 100644 --- a/test/Microsoft.AspNetCore.Razor.Language.Test/DefaultRazorParsingPhaseTest.cs +++ b/test/Microsoft.AspNetCore.Razor.Language.Test/DefaultRazorParsingPhaseTest.cs @@ -1,7 +1,6 @@ // 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 Xunit; namespace Microsoft.AspNetCore.Razor.Language @@ -43,7 +42,7 @@ namespace Microsoft.AspNetCore.Razor.Language // Assert var syntaxTree = codeDocument.GetSyntaxTree(); var directive = Assert.Single(syntaxTree.Options.Directives); - Assert.Equal("test_directive", directive.Name); + Assert.Equal("test", directive.Name); } [Fact] @@ -71,8 +70,8 @@ namespace Microsoft.AspNetCore.Razor.Language // Assert Assert.Collection( codeDocument.GetImportSyntaxTrees(), - t => { Assert.Same(t.Source, imports[0]); Assert.Equal("test_directive", Assert.Single(t.Options.Directives).Name); }, - t => { Assert.Same(t.Source, imports[1]); Assert.Equal("test_directive", Assert.Single(t.Options.Directives).Name); }); + t => { Assert.Same(t.Source, imports[0]); Assert.Equal("test", Assert.Single(t.Options.Directives).Name); }, + t => { Assert.Same(t.Source, imports[1]); Assert.Equal("test", Assert.Single(t.Options.Directives).Name); }); } private class MyParserOptionsFeature : IRazorParserOptionsFeature @@ -83,7 +82,7 @@ namespace Microsoft.AspNetCore.Razor.Language public void Configure(RazorParserOptions options) { - options.Directives.Add(DirectiveDescriptorBuilder.Create("test_directive").Build()); + options.Directives.Add(DirectiveDescriptor.CreateDirective("test", DirectiveKind.SingleLine)); } } } diff --git a/test/Microsoft.AspNetCore.Razor.Language.Test/DirectiveDescriptorBuilderTest.cs b/test/Microsoft.AspNetCore.Razor.Language.Test/DirectiveDescriptorBuilderTest.cs index bc32f13fc2..a8087a2b8e 100644 --- a/test/Microsoft.AspNetCore.Razor.Language.Test/DirectiveDescriptorBuilderTest.cs +++ b/test/Microsoft.AspNetCore.Razor.Language.Test/DirectiveDescriptorBuilderTest.cs @@ -5,98 +5,102 @@ using Xunit; namespace Microsoft.AspNetCore.Razor.Language { - public class DirectiveDescriptorBuilderTest + public class DirectiveDescriptorBuilderExtensionsTest { [Fact] - public void Create_BuildsSingleLineDirectiveDescriptor() + public void AddMemberToken_AddsToken() { - // 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(); + // Arrange & Act + var descriptor = DirectiveDescriptor.CreateDirective("custom", DirectiveKind.SingleLine, b => b.AddMemberToken()); // Assert var token = Assert.Single(descriptor.Tokens); Assert.Equal(DirectiveTokenKind.Member, token.Kind); + Assert.False(token.Optional); } [Fact] - public void AddString_AddsToken() + public void AddNamespaceToken_AddsToken() { - // Arrange - var builder = DirectiveDescriptorBuilder.Create("custom"); + // Arrange & Act + var descriptor = DirectiveDescriptor.CreateDirective("custom", DirectiveKind.SingleLine, b => b.AddNamespaceToken()); - // Act - var descriptor = builder.AddString().Build(); + // Assert + var token = Assert.Single(descriptor.Tokens); + Assert.Equal(DirectiveTokenKind.Namespace, token.Kind); + Assert.False(token.Optional); + } + + [Fact] + public void AddStringToken_AddsToken() + { + // Arrange & Act + var descriptor = DirectiveDescriptor.CreateDirective("custom", DirectiveKind.SingleLine, b => b.AddStringToken()); // Assert var token = Assert.Single(descriptor.Tokens); Assert.Equal(DirectiveTokenKind.String, token.Kind); + Assert.False(token.Optional); } [Fact] - public void AddX_MaintainsMultipleTokens() + public void AddTypeToken_AddsToken() { - // Arrange - var builder = DirectiveDescriptorBuilder.Create("custom"); - - // Act - var descriptor = builder - .AddType() - .AddMember() - .AddString() - .Build(); + // Arrange & Act + var descriptor = DirectiveDescriptor.CreateDirective("custom", DirectiveKind.SingleLine, b => b.AddTypeToken()); // 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)); + var token = Assert.Single(descriptor.Tokens); + Assert.Equal(DirectiveTokenKind.Type, token.Kind); + Assert.False(token.Optional); + } + + [Fact] + public void AddOptionalTypeToken_AddsToken() + { + // Arrange & Act + var descriptor = DirectiveDescriptor.CreateDirective("custom", DirectiveKind.SingleLine, b => b.AddOptionalTypeToken()); + + // Assert + var token = Assert.Single(descriptor.Tokens); + Assert.Equal(DirectiveTokenKind.Type, token.Kind); + Assert.True(token.Optional); + } + + [Fact] + public void AddOptionalMemberToken_AddsToken() + { + // Arrange & Act + var descriptor = DirectiveDescriptor.CreateDirective("custom", DirectiveKind.SingleLine, b => b.AddOptionalMemberToken()); + + // Assert + var token = Assert.Single(descriptor.Tokens); + Assert.Equal(DirectiveTokenKind.Member, token.Kind); + Assert.True(token.Optional); + } + + [Fact] + public void AddOptionalNamespaceToken_AddsToken() + { + // Arrange & Act + var descriptor = DirectiveDescriptor.CreateDirective("custom", DirectiveKind.SingleLine, b => b.AddOptionalNamespaceToken()); + + // Assert + var token = Assert.Single(descriptor.Tokens); + Assert.Equal(DirectiveTokenKind.Namespace, token.Kind); + Assert.True(token.Optional); + } + + [Fact] + public void AddOptionalStringToken_AddsToken() + { + // Arrange & Act + var descriptor = DirectiveDescriptor.CreateDirective("custom", DirectiveKind.SingleLine, b => b.AddOptionalStringToken()); + + // Assert + var token = Assert.Single(descriptor.Tokens); + Assert.Equal(DirectiveTokenKind.String, token.Kind); + Assert.True(token.Optional); } } } diff --git a/test/Microsoft.AspNetCore.Razor.Language.Test/DirectiveDescriptorTest.cs b/test/Microsoft.AspNetCore.Razor.Language.Test/DirectiveDescriptorTest.cs new file mode 100644 index 0000000000..a57cc88966 --- /dev/null +++ b/test/Microsoft.AspNetCore.Razor.Language.Test/DirectiveDescriptorTest.cs @@ -0,0 +1,150 @@ +// 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 Xunit; + +namespace Microsoft.AspNetCore.Razor.Language +{ + public class DirectiveDescriptorTest + { + [Fact] + public void CreateDirective_CreatesDirective_WithProvidedKind() + { + // Arrange & Act + var directive = DirectiveDescriptor.CreateDirective("test", DirectiveKind.SingleLine); + + // Assert + Assert.Equal("test", directive.Name); + Assert.Equal(DirectiveKind.SingleLine, directive.Kind); + } + + [Fact] + public void CreateDirective_WithConfigure_CreatesDirective_WithProvidedKind() + { + // Arrange + var called = false; + Action configure = b => { called = true; }; + + // Act + var directive = DirectiveDescriptor.CreateDirective("test", DirectiveKind.SingleLine, configure); + + // Assert + Assert.Equal("test", directive.Name); + Assert.Equal(DirectiveKind.SingleLine, directive.Kind); + Assert.True(called); + } + + [Fact] + public void CreateSingleLineDirective_CreatesSingleLineDirective() + { + // Arrange & Act + var directive = DirectiveDescriptor.CreateSingleLineDirective("test"); + + // Assert + Assert.Equal("test", directive.Name); + Assert.Equal(DirectiveKind.SingleLine, directive.Kind); + } + + [Fact] + public void CreateSingleLineDirective_WithConfigure_CreatesSingleLineDirective() + { + // Arrange + var called = false; + Action configure = b => { called = true; }; + + // Act + var directive = DirectiveDescriptor.CreateSingleLineDirective("test", configure); + + // Assert + Assert.Equal("test", directive.Name); + Assert.Equal(DirectiveKind.SingleLine, directive.Kind); + Assert.True(called); + } + + [Fact] + public void CreateRazorBlockDirective_CreatesRazorBlockDirective() + { + // Arrange & Act + var directive = DirectiveDescriptor.CreateRazorBlockDirective("test"); + + // Assert + Assert.Equal("test", directive.Name); + Assert.Equal(DirectiveKind.RazorBlock, directive.Kind); + } + + [Fact] + public void CreateRazorBlockDirective_WithConfigure_CreatesRazorBlockDirective() + { + // Arrange + var called = false; + Action configure = b => { called = true; }; + + // Act + var directive = DirectiveDescriptor.CreateRazorBlockDirective("test", configure); + + // Assert + Assert.Equal("test", directive.Name); + Assert.Equal(DirectiveKind.RazorBlock, directive.Kind); + Assert.True(called); + } + + [Fact] + public void CreateCodeBlockDirective_CreatesCodeBlockDirective() + { + // Arrange & Act + var directive = DirectiveDescriptor.CreateCodeBlockDirective("test"); + + // Assert + Assert.Equal("test", directive.Name); + Assert.Equal(DirectiveKind.CodeBlock, directive.Kind); + } + + [Fact] + public void CreateCodeBlockDirective_WithConfigure_CreatesCodeBlockDirective() + { + // Arrange + var called = false; + Action configure = b => { called = true; }; + + // Act + var directive = DirectiveDescriptor.CreateCodeBlockDirective("test", configure); + + // Assert + Assert.Equal("test", directive.Name); + Assert.Equal(DirectiveKind.CodeBlock, directive.Kind); + Assert.True(called); + } + + [Fact] + public void Build_ValidatesDirectiveName_EmptyIsInvalid() + { + // Arrange & Act + var ex = Assert.Throws(() => DirectiveDescriptor.CreateSingleLineDirective("")); + + // Assert + Assert.Equal("Invalid directive name ''. Directives must have a non-empty name that consists only of letters.", ex.Message); + } + + [Fact] + public void Build_ValidatesDirectiveName_InvalidCharacter() + { + // Arrange & Act + var ex = Assert.Throws(() => DirectiveDescriptor.CreateSingleLineDirective("test_directive")); + + // Assert + Assert.Equal("Invalid directive name 'test_directive'. Directives must have a non-empty name that consists only of letters.", ex.Message); + } + + [Fact] + public void Build_ValidatesDirectiveName_NonOptionalTokenFollowsOptionalToken() + { + // Arrange & Act + var ex = Assert.Throws( + () => DirectiveDescriptor.CreateSingleLineDirective("test", b => { b.AddOptionalMemberToken(); b.AddMemberToken(); })); + + // Assert + Assert.Equal("A non-optional directive token cannot follow an optional directive token.", ex.Message); + } + } +} diff --git a/test/Microsoft.AspNetCore.Razor.Language.Test/DirectiveRemovalIROptimizationPassTest.cs b/test/Microsoft.AspNetCore.Razor.Language.Test/DirectiveRemovalIROptimizationPassTest.cs index 293b5e3a89..f3863a8c1f 100644 --- a/test/Microsoft.AspNetCore.Razor.Language.Test/DirectiveRemovalIROptimizationPassTest.cs +++ b/test/Microsoft.AspNetCore.Razor.Language.Test/DirectiveRemovalIROptimizationPassTest.cs @@ -19,8 +19,7 @@ namespace Microsoft.AspNetCore.Razor.Language var codeDocument = RazorCodeDocument.Create(sourceDocument); var defaultEngine = RazorEngine.Create(b => { - var customDirective = DirectiveDescriptorBuilder.Create("custom").AddString().Build(); - b.AddDirective(customDirective); + b.AddDirective(DirectiveDescriptor.CreateDirective("custom", DirectiveKind.SingleLine, d => d.AddStringToken())); }); var irDocument = Lower(codeDocument, defaultEngine); var pass = new DirectiveRemovalIROptimizationPass() @@ -54,8 +53,7 @@ namespace Microsoft.AspNetCore.Razor.Language var codeDocument = RazorCodeDocument.Create(sourceDocument); var defaultEngine = RazorEngine.Create(b => { - var customDirective = DirectiveDescriptorBuilder.Create("custom").AddString().Build(); - b.AddDirective(customDirective); + b.AddDirective(DirectiveDescriptor.CreateDirective("custom", DirectiveKind.SingleLine, d => d.AddStringToken())); }); var irDocument = Lower(codeDocument, defaultEngine); var pass = new DirectiveRemovalIROptimizationPass() diff --git a/test/Microsoft.AspNetCore.Razor.Language.Test/IntegrationTests/BasicIntegrationTest.cs b/test/Microsoft.AspNetCore.Razor.Language.Test/IntegrationTests/BasicIntegrationTest.cs index 8ada0c3da1..832f782f23 100644 --- a/test/Microsoft.AspNetCore.Razor.Language.Test/IntegrationTests/BasicIntegrationTest.cs +++ b/test/Microsoft.AspNetCore.Razor.Language.Test/IntegrationTests/BasicIntegrationTest.cs @@ -44,7 +44,7 @@ namespace Microsoft.AspNetCore.Razor.Language.IntegrationTests // Arrange var engine = RazorEngine.Create(b => { - b.AddDirective(DirectiveDescriptorBuilder.Create("test_directive").Build()); + b.AddDirective(DirectiveDescriptor.CreateDirective("test", DirectiveKind.SingleLine)); }); var document = CreateCodeDocument(); diff --git a/test/Microsoft.AspNetCore.Razor.Language.Test/IntegrationTests/ExtensibleDirectiveTest.cs b/test/Microsoft.AspNetCore.Razor.Language.Test/IntegrationTests/ExtensibleDirectiveTest.cs index 144c6ab88f..447afd077b 100644 --- a/test/Microsoft.AspNetCore.Razor.Language.Test/IntegrationTests/ExtensibleDirectiveTest.cs +++ b/test/Microsoft.AspNetCore.Razor.Language.Test/IntegrationTests/ExtensibleDirectiveTest.cs @@ -16,7 +16,7 @@ namespace Microsoft.AspNetCore.Razor.Language.IntegrationTests { builder.Features.Add(new ApiSetsIRTestAdapter()); - builder.AddDirective(DirectiveDescriptorBuilder.Create("custom").AddNamespace().Build()); + builder.AddDirective(DirectiveDescriptor.CreateDirective("custom", DirectiveKind.SingleLine, b => b.AddNamespaceToken())); }); var document = CreateCodeDocument(); diff --git a/test/Microsoft.AspNetCore.Razor.Language.Test/Legacy/CSharpDirectivesTest.cs b/test/Microsoft.AspNetCore.Razor.Language.Test/Legacy/CSharpDirectivesTest.cs index b909ce8eac..ee598cf1a3 100644 --- a/test/Microsoft.AspNetCore.Razor.Language.Test/Legacy/CSharpDirectivesTest.cs +++ b/test/Microsoft.AspNetCore.Razor.Language.Test/Legacy/CSharpDirectivesTest.cs @@ -14,7 +14,10 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy public void DirectiveDescriptor_UnderstandsTypeTokens() { // Arrange - var descriptor = DirectiveDescriptorBuilder.Create("custom").AddType().Build(); + var descriptor = DirectiveDescriptor.CreateDirective( + "custom", + DirectiveKind.SingleLine, + b => b.AddTypeToken()); // Act & Assert ParseCodeBlockTest( @@ -34,7 +37,10 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy public void DirectiveDescriptor_UnderstandsMemberTokens() { // Arrange - var descriptor = DirectiveDescriptorBuilder.Create("custom").AddMember().Build(); + var descriptor = DirectiveDescriptor.CreateDirective( + "custom", + DirectiveKind.SingleLine, + b => b.AddMemberToken()); // Act & Assert ParseCodeBlockTest( @@ -54,7 +60,10 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy public void Parser_ParsesNamespaceDirectiveToken_WithSingleSegment() { // Arrange - var descriptor = DirectiveDescriptorBuilder.Create("custom").AddNamespace().Build(); + var descriptor = DirectiveDescriptor.CreateDirective( + "custom", + DirectiveKind.SingleLine, + b => b.AddNamespaceToken()); // Act & Assert ParseCodeBlockTest( @@ -74,7 +83,10 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy public void Parser_ParsesNamespaceDirectiveToken_WithMultipleSegments() { // Arrange - var descriptor = DirectiveDescriptorBuilder.Create("custom").AddNamespace().Build(); + var descriptor = DirectiveDescriptor.CreateDirective( + "custom", + DirectiveKind.SingleLine, + b => b.AddNamespaceToken()); // Act & Assert ParseCodeBlockTest( @@ -94,7 +106,10 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy public void DirectiveDescriptor_UnderstandsStringTokens() { // Arrange - var descriptor = DirectiveDescriptorBuilder.Create("custom").AddString().Build(); + var descriptor = DirectiveDescriptor.CreateDirective( + "custom", + DirectiveKind.SingleLine, + b => b.AddStringToken()); // Act & Assert ParseCodeBlockTest( @@ -114,7 +129,11 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy public void DirectiveDescriptor_StringToken_ParserErrorForUnquotedValue() { // Arrange - var descriptor = DirectiveDescriptorBuilder.Create("custom").AddString().Build(); + var descriptor = DirectiveDescriptor.CreateDirective( + "custom", + DirectiveKind.SingleLine, + b => b.AddStringToken()); + var expectedError = new RazorError( LegacyResources.FormatDirectiveExpectsQuotedStringLiteral("custom"), new SourceLocation(8, 0, 8), @@ -135,7 +154,11 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy public void DirectiveDescriptor_StringToken_ParserErrorForNonStringValue() { // Arrange - var descriptor = DirectiveDescriptorBuilder.Create("custom").AddString().Build(); + var descriptor = DirectiveDescriptor.CreateDirective( + "custom", + DirectiveKind.SingleLine, + b => b.AddStringToken()); + var expectedError = new RazorError( LegacyResources.FormatDirectiveExpectsQuotedStringLiteral("custom"), new SourceLocation(8, 0, 8), @@ -156,7 +179,11 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy public void DirectiveDescriptor_StringToken_ParserErrorForSingleQuotedValue() { // Arrange - var descriptor = DirectiveDescriptorBuilder.Create("custom").AddString().Build(); + var descriptor = DirectiveDescriptor.CreateDirective( + "custom", + DirectiveKind.SingleLine, + b => b.AddStringToken()); + var expectedError = new RazorError( LegacyResources.FormatDirectiveExpectsQuotedStringLiteral("custom"), new SourceLocation(8, 0, 8), @@ -177,7 +204,11 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy public void DirectiveDescriptor_StringToken_ParserErrorForPartialQuotedValue() { // Arrange - var descriptor = DirectiveDescriptorBuilder.Create("custom").AddString().Build(); + var descriptor = DirectiveDescriptor.CreateDirective( + "custom", + DirectiveKind.SingleLine, + b => b.AddStringToken()); + var expectedError = new RazorError( LegacyResources.FormatDirectiveExpectsQuotedStringLiteral("custom"), new SourceLocation(8, 0, 8), @@ -199,11 +230,10 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy public void DirectiveDescriptor_UnderstandsMultipleTokens() { // Arrange - var descriptor = DirectiveDescriptorBuilder.Create("custom") - .AddType() - .AddMember() - .AddString() - .Build(); + var descriptor = DirectiveDescriptor.CreateDirective( + "custom", + DirectiveKind.SingleLine, + b => b.AddTypeToken().AddMemberToken().AddStringToken()); // Act & Assert ParseCodeBlockTest( @@ -234,7 +264,10 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy public void DirectiveDescriptor_UnderstandsRazorBlocks() { // Arrange - var descriptor = DirectiveDescriptorBuilder.CreateRazorBlock("custom").AddString().Build(); + var descriptor = DirectiveDescriptor.CreateDirective( + "custom", + DirectiveKind.RazorBlock, + b => b.AddStringToken()); // Act & Assert ParseCodeBlockTest( @@ -267,7 +300,10 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy public void DirectiveDescriptor_UnderstandsCodeBlocks() { // Arrange - var descriptor = DirectiveDescriptorBuilder.CreateCodeBlock("custom").AddString().Build(); + var descriptor = DirectiveDescriptor.CreateDirective( + "custom", + DirectiveKind.CodeBlock, + b => b.AddStringToken()); // Act & Assert ParseCodeBlockTest( @@ -293,10 +329,10 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy public void DirectiveDescriptor_AllowsWhiteSpaceAroundTokens() { // Arrange - var descriptor = DirectiveDescriptorBuilder.Create("custom") - .AddType() - .AddMember() - .Build(); + var descriptor = DirectiveDescriptor.CreateDirective( + "custom", + DirectiveKind.SingleLine, + b => b.AddTypeToken().AddMemberToken()); // Act & Assert ParseCodeBlockTest( @@ -325,7 +361,11 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy public void DirectiveDescriptor_ErrorsForInvalidMemberTokens() { // Arrange - var descriptor = DirectiveDescriptorBuilder.Create("custom").AddMember().Build(); + var descriptor = DirectiveDescriptor.CreateDirective( + "custom", + DirectiveKind.SingleLine, + b => b.AddMemberToken()); + var expectedErorr = new RazorError( LegacyResources.FormatDirectiveExpectsIdentifier("custom"), new SourceLocation(8, 0, 8), @@ -347,7 +387,10 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy public void DirectiveDescriptor_NoErrorsSemicolonAfterDirective() { // Arrange - var descriptor = DirectiveDescriptorBuilder.Create("custom").AddString().Build(); + var descriptor = DirectiveDescriptor.CreateDirective( + "custom", + DirectiveKind.SingleLine, + b => b.AddStringToken()); // Act & Assert ParseCodeBlockTest( @@ -368,7 +411,11 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy public void DirectiveDescriptor_ErrorsExtraContentAfterDirective() { // Arrange - var descriptor = DirectiveDescriptorBuilder.Create("custom").AddString().Build(); + var descriptor = DirectiveDescriptor.CreateDirective( + "custom", + DirectiveKind.SingleLine, + b => b.AddStringToken()); + var expectedErorr = new RazorError( LegacyResources.FormatUnexpectedDirectiveLiteral("custom", "line break"), new SourceLocation(16, 0, 16), @@ -395,7 +442,11 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy public void DirectiveDescriptor_ErrorsWhenExtraContentBeforeBlockStart() { // Arrange - var descriptor = DirectiveDescriptorBuilder.CreateCodeBlock("custom").AddString().Build(); + var descriptor = DirectiveDescriptor.CreateDirective( + "custom", + DirectiveKind.CodeBlock, + b => b.AddStringToken()); + var expectedErorr = new RazorError( LegacyResources.FormatUnexpectedDirectiveLiteral("custom", "{"), new SourceLocation(16, 0, 16), @@ -422,7 +473,11 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy public void DirectiveDescriptor_ErrorsWhenEOFBeforeDirectiveBlockStart() { // Arrange - var descriptor = DirectiveDescriptorBuilder.CreateCodeBlock("custom").AddString().Build(); + var descriptor = DirectiveDescriptor.CreateDirective( + "custom", + DirectiveKind.CodeBlock, + b => b.AddStringToken()); + var expectedErorr = new RazorError( LegacyResources.FormatUnexpectedEOFAfterDirective("custom", "{"), new SourceLocation(15, 0, 15), @@ -447,7 +502,11 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy public void DirectiveDescriptor_ErrorsWhenMissingEndBrace() { // Arrange - var descriptor = DirectiveDescriptorBuilder.CreateCodeBlock("custom").AddString().Build(); + var descriptor = DirectiveDescriptor.CreateDirective( + "custom", + DirectiveKind.CodeBlock, + b => b.AddStringToken()); + var expectedErorr = new RazorError( LegacyResources.FormatParseError_Expected_EndOfBlock_Before_EOF("custom", "}", "{"), new SourceLocation(16, 0, 16), @@ -884,7 +943,10 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy public void OptionalDirectiveTokens_AreSkipped() { // Arrange - var descriptor = DirectiveDescriptorBuilder.Create("custom").BeginOptionals().AddString().Build(); + var descriptor = DirectiveDescriptor.CreateDirective( + "custom", + DirectiveKind.SingleLine, + b => b.AddOptionalStringToken()); // Act & Assert ParseCodeBlockTest( @@ -901,7 +963,11 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy public void OptionalDirectiveTokens_WithSimpleTokens_AreParsed() { // Arrange - var descriptor = DirectiveDescriptorBuilder.Create("custom").BeginOptionals().AddString().Build(); + var descriptor = DirectiveDescriptor.CreateDirective( + "custom", + DirectiveKind.SingleLine, + b => b.AddOptionalStringToken()); + var chunkGenerator = new DirectiveTokenChunkGenerator(descriptor.Tokens.First()); // Act & Assert @@ -922,7 +988,11 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy public void OptionalDirectiveTokens_WithBraces_AreParsed() { // Arrange - var descriptor = DirectiveDescriptorBuilder.Create("custom").BeginOptionals().AddString().Build(); + var descriptor = DirectiveDescriptor.CreateDirective( + "custom", + DirectiveKind.SingleLine, + b => b.AddOptionalStringToken()); + var chunkGenerator = new DirectiveTokenChunkGenerator(descriptor.Tokens.First()); // Act & Assert @@ -943,7 +1013,10 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy public void OptionalDirectiveTokens_WithMultipleOptionalTokens_AreParsed() { // Arrange - var descriptor = DirectiveDescriptorBuilder.Create("custom").BeginOptionals().AddString().AddType().Build(); + var descriptor = DirectiveDescriptor.CreateDirective( + "custom", + DirectiveKind.SingleLine, + b => b.AddOptionalStringToken().AddOptionalTypeToken()); // Act & Assert ParseCodeBlockTest( @@ -967,7 +1040,10 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy public void OptionalMemberTokens_WithMissingMember_IsParsed() { // Arrange - var descriptor = DirectiveDescriptorBuilder.Create("TestDirective").BeginOptionals().AddMember().AddString().Build(); + var descriptor = DirectiveDescriptor.CreateDirective( + "TestDirective", + DirectiveKind.SingleLine, + b => b.AddOptionalMemberToken().AddOptionalStringToken()); // Act & Assert ParseCodeBlockTest( @@ -984,7 +1060,10 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy public void OptionalMemberTokens_WithMemberSpecified_IsParsed() { // Arrange - var descriptor = DirectiveDescriptorBuilder.Create("TestDirective").BeginOptionals().AddMember().AddString().Build(); + var descriptor = DirectiveDescriptor.CreateDirective( + "TestDirective", + DirectiveKind.SingleLine, + b => b.AddOptionalMemberToken().AddOptionalStringToken()); // Act & Assert ParseCodeBlockTest( @@ -1004,7 +1083,9 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy public void Directives_CanUseReservedWord_Class() { // Arrange - var descriptor = DirectiveDescriptorBuilder.Create("class").Build(); + var descriptor = DirectiveDescriptor.CreateDirective( + "class", + DirectiveKind.SingleLine); // Act & Assert ParseCodeBlockTest( @@ -1020,7 +1101,9 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy public void Directives_CanUseReservedWord_Namespace() { // Arrange - var descriptor = DirectiveDescriptorBuilder.Create("namespace").Build(); + var descriptor = DirectiveDescriptor.CreateDirective( + "namespace", + DirectiveKind.SingleLine); // Act & Assert ParseCodeBlockTest( diff --git a/test/Microsoft.AspNetCore.Razor.Language.Test/RazorEngineBuilderExtensionsTest.cs b/test/Microsoft.AspNetCore.Razor.Language.Test/RazorEngineBuilderExtensionsTest.cs index 0571752455..3c054dc5ed 100644 --- a/test/Microsoft.AspNetCore.Razor.Language.Test/RazorEngineBuilderExtensionsTest.cs +++ b/test/Microsoft.AspNetCore.Razor.Language.Test/RazorEngineBuilderExtensionsTest.cs @@ -19,7 +19,7 @@ namespace Microsoft.AspNetCore.Razor.Language b.Features.Add(expected); // Act - b.AddDirective(DirectiveDescriptorBuilder.Create("test_directive").Build()); + b.AddDirective(DirectiveDescriptor.CreateDirective("test", DirectiveKind.SingleLine)); }); // Assert @@ -27,7 +27,7 @@ namespace Microsoft.AspNetCore.Razor.Language Assert.Same(expected, actual); var directive = Assert.Single(actual.Directives); - Assert.Equal("test_directive", directive.Name); + Assert.Equal("test", directive.Name); } [Fact] @@ -37,7 +37,7 @@ namespace Microsoft.AspNetCore.Razor.Language var engine = RazorEngine.CreateEmpty(b => { // Act - b.AddDirective(DirectiveDescriptorBuilder.Create("test_directive").Build()); + b.AddDirective(DirectiveDescriptor.CreateDirective("test", DirectiveKind.SingleLine)); }); // Assert @@ -45,7 +45,7 @@ namespace Microsoft.AspNetCore.Razor.Language Assert.IsType(actual); var directive = Assert.Single(actual.Directives); - Assert.Equal("test_directive", directive.Name); + Assert.Equal("test", directive.Name); } [Fact] diff --git a/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/IntegrationTests/BasicIntegrationTest/CustomDirective.cshtml b/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/IntegrationTests/BasicIntegrationTest/CustomDirective.cshtml index b2726851aa..f347706f3d 100644 --- a/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/IntegrationTests/BasicIntegrationTest/CustomDirective.cshtml +++ b/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/IntegrationTests/BasicIntegrationTest/CustomDirective.cshtml @@ -1 +1 @@ -@test_directive \ No newline at end of file +@test \ No newline at end of file diff --git a/tooling/Microsoft.VisualStudio.RazorExtension/RazorInfo/DirectiveViewModel.cs b/tooling/Microsoft.VisualStudio.RazorExtension/RazorInfo/DirectiveViewModel.cs index 12033e4f0f..9e1fbddac6 100644 --- a/tooling/Microsoft.VisualStudio.RazorExtension/RazorInfo/DirectiveViewModel.cs +++ b/tooling/Microsoft.VisualStudio.RazorExtension/RazorInfo/DirectiveViewModel.cs @@ -26,7 +26,7 @@ namespace Microsoft.VisualStudio.RazorExtension.RazorInfo builder.Append(")"); } - if (directive.Kind == DirectiveDescriptorKind.CodeBlock || directive.Kind == DirectiveDescriptorKind.RazorBlock) + if (directive.Kind == DirectiveKind.CodeBlock || directive.Kind == DirectiveKind.RazorBlock) { builder.Append("{ ... }"); }