diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X/ModelDirective.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X/ModelDirective.cs index a2bce9974d..0c5859ce22 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X/ModelDirective.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X/ModelDirective.cs @@ -29,7 +29,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X } builder.AddDirective(Directive); - builder.Features.Add(new Pass(builder.Configuration.DesignTime)); + builder.Features.Add(new Pass()); return builder; } @@ -64,13 +64,6 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X internal class Pass : IntermediateNodePassBase, IRazorDirectiveClassifierPass { - private readonly bool _designTime; - - public Pass(bool designTime) - { - _designTime = designTime; - } - // Runs after the @inherits directive public override int Order => 5; @@ -79,7 +72,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X var visitor = new Visitor(); var modelType = GetModelType(documentNode, visitor); - if (_designTime) + if (documentNode.Options.DesignTime) { // Alias the TModel token to a known type. // This allows design time compilation to succeed for Razor files where the token isn't replaced. @@ -143,7 +136,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X } builder.AddDirective(Directive); - builder.Features.Add(new Pass(builder.DesignTime)); + builder.Features.Add(new Pass()); return builder; } #endregion diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X/DefaultMvcImportFeature.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X/MvcImportProjectFeature.cs similarity index 97% rename from src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X/DefaultMvcImportFeature.cs rename to src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X/MvcImportProjectFeature.cs index 055f306eb5..b7ef608871 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X/DefaultMvcImportFeature.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X/MvcImportProjectFeature.cs @@ -10,7 +10,7 @@ using Microsoft.AspNetCore.Razor.Language; namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X { - internal class DefaultMvcImportFeature : RazorProjectEngineFeatureBase, IRazorImportFeature + internal class MvcImportProjectFeature : RazorProjectEngineFeatureBase, IImportProjectFeature { private const string ImportsFileName = "_ViewImports.cshtml"; diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X/RazorExtensions.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X/RazorExtensions.cs index f94cf52abd..e8b4f0018c 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X/RazorExtensions.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X/RazorExtensions.cs @@ -16,8 +16,6 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X throw new ArgumentNullException(nameof(builder)); } - EnsureDesignTime(builder); - InjectDirective.Register(builder); ModelDirective.Register(builder); @@ -37,7 +35,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X builder.Features.Add(new ModelExpressionPass()); builder.Features.Add(new MvcViewDocumentClassifierPass()); - builder.SetImportFeature(new DefaultMvcImportFeature()); + builder.SetImportFeature(new MvcImportProjectFeature()); } public static void RegisterViewComponentTagHelpers(RazorProjectEngineBuilder builder) @@ -47,22 +45,10 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X throw new ArgumentNullException(nameof(builder)); } - EnsureDesignTime(builder); - builder.Features.Add(new ViewComponentTagHelperPass()); builder.AddTargetExtension(new ViewComponentTagHelperTargetExtension()); } - private static void EnsureDesignTime(RazorProjectEngineBuilder builder) - { - if (builder.Configuration.DesignTime) - { - return; - } - - throw new NotSupportedException(Resources.RuntimeCodeGenerationNotSupported); - } - #region Obsolete public static void Register(IRazorEngineBuilder builder) { diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/AssemblyAttributeInjectionPass.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/AssemblyAttributeInjectionPass.cs index 1123b1a31f..cf5549d0f5 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/AssemblyAttributeInjectionPass.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/AssemblyAttributeInjectionPass.cs @@ -14,6 +14,11 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions protected override void ExecuteCore(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode) { + if (documentNode.Options.DesignTime) + { + return; + } + var @namespace = documentNode.FindPrimaryNamespace(); if (@namespace == null || string.IsNullOrEmpty(@namespace.Content)) { diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/InstrumentationPass.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/InstrumentationPass.cs index 9f271faf82..1ae0a72210 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/InstrumentationPass.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/InstrumentationPass.cs @@ -15,6 +15,11 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions protected override void ExecuteCore(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode) { + if (documentNode.Options.DesignTime) + { + return; + } + var walker = new Visitor(); walker.VisitDocument(documentNode); diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/ModelDirective.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/ModelDirective.cs index 9c16b96e57..b6e2955f22 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/ModelDirective.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/ModelDirective.cs @@ -29,7 +29,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions } builder.AddDirective(Directive); - builder.Features.Add(new Pass(builder.Configuration.DesignTime)); + builder.Features.Add(new Pass()); return builder; } @@ -71,13 +71,6 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions internal class Pass : IntermediateNodePassBase, IRazorDirectiveClassifierPass { - private readonly bool _designTime; - - public Pass(bool designTime) - { - _designTime = designTime; - } - // Runs after the @inherits directive public override int Order => 5; @@ -86,7 +79,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions var visitor = new Visitor(); var modelType = GetModelType(documentNode, visitor); - if (_designTime) + if (documentNode.Options.DesignTime) { // Alias the TModel token to a known type. // This allows design time compilation to succeed for Razor files where the token isn't replaced. @@ -150,7 +143,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions } builder.AddDirective(Directive); - builder.Features.Add(new Pass(builder.DesignTime)); + builder.Features.Add(new Pass()); return builder; } #endregion diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/DefaultMvcImportFeature.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/MvcImportProjectFeature.cs similarity index 97% rename from src/Microsoft.AspNetCore.Mvc.Razor.Extensions/DefaultMvcImportFeature.cs rename to src/Microsoft.AspNetCore.Mvc.Razor.Extensions/MvcImportProjectFeature.cs index 4852951e33..999dd6fdbb 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/DefaultMvcImportFeature.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/MvcImportProjectFeature.cs @@ -10,7 +10,7 @@ using Microsoft.AspNetCore.Razor.Language; namespace Microsoft.AspNetCore.Mvc.Razor.Extensions { - internal class DefaultMvcImportFeature : RazorProjectEngineFeatureBase, IRazorImportFeature + internal class MvcImportProjectFeature : RazorProjectEngineFeatureBase, IImportProjectFeature { private const string ImportsFileName = "_ViewImports.cshtml"; diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/RazorExtensions.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/RazorExtensions.cs index ac1d8b35e9..c22878bd09 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/RazorExtensions.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/RazorExtensions.cs @@ -36,14 +36,10 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions builder.Features.Add(new ViewComponentTagHelperPass()); builder.Features.Add(new RazorPageDocumentClassifierPass()); builder.Features.Add(new MvcViewDocumentClassifierPass()); + builder.Features.Add(new AssemblyAttributeInjectionPass()); + builder.Features.Add(new InstrumentationPass()); - if (!builder.Configuration.DesignTime) - { - builder.Features.Add(new AssemblyAttributeInjectionPass()); - builder.Features.Add(new InstrumentationPass()); - } - - builder.SetImportFeature(new DefaultMvcImportFeature()); + builder.SetImportFeature(new MvcImportProjectFeature()); } #region Obsolete diff --git a/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorImportFeature.cs b/src/Microsoft.AspNetCore.Razor.Language/DefaultImportProjectFeature.cs similarity index 80% rename from src/Microsoft.AspNetCore.Razor.Language/DefaultRazorImportFeature.cs rename to src/Microsoft.AspNetCore.Razor.Language/DefaultImportProjectFeature.cs index ea626e97e0..15fc2635cd 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorImportFeature.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/DefaultImportProjectFeature.cs @@ -6,7 +6,7 @@ using System.Collections.Generic; namespace Microsoft.AspNetCore.Razor.Language { - internal class DefaultRazorImportFeature : RazorProjectEngineFeatureBase, IRazorImportFeature + internal class DefaultImportProjectFeature : RazorProjectEngineFeatureBase, IImportProjectFeature { public IReadOnlyList GetImports(RazorProjectItem projectItem) => Array.Empty(); } diff --git a/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorCodeGenerationOptionsBuilder.cs b/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorCodeGenerationOptionsBuilder.cs index ad3dc7b83d..8dd095a04e 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorCodeGenerationOptionsBuilder.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorCodeGenerationOptionsBuilder.cs @@ -1,16 +1,30 @@ // 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 { internal class DefaultRazorCodeGenerationOptionsBuilder : RazorCodeGenerationOptionsBuilder { - public DefaultRazorCodeGenerationOptionsBuilder(bool designTime) + private bool _designTime; + + public DefaultRazorCodeGenerationOptionsBuilder(RazorConfiguration configuration) { - DesignTime = designTime; + if (configuration == null) + { + throw new ArgumentNullException(nameof(configuration)); + } } - public override bool DesignTime { get; } + public DefaultRazorCodeGenerationOptionsBuilder(bool designTime) + { + _designTime = designTime; + } + + public override RazorConfiguration Configuration { get; } + + public override bool DesignTime => _designTime; public override int IndentSize { get; set; } = 4; @@ -22,5 +36,10 @@ namespace Microsoft.AspNetCore.Razor.Language { return new DefaultRazorCodeGenerationOptions(IndentWithTabs, IndentSize, DesignTime, SuppressChecksum, SuppressMetadataAttributes); } + + public override void SetDesignTime(bool designTime) + { + _designTime = designTime; + } } } diff --git a/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorCodeGenerationOptionsFactoryProjectFeature.cs b/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorCodeGenerationOptionsFactoryProjectFeature.cs new file mode 100644 index 0000000000..db2a25b81f --- /dev/null +++ b/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorCodeGenerationOptionsFactoryProjectFeature.cs @@ -0,0 +1,32 @@ +// 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.Linq; + +namespace Microsoft.AspNetCore.Razor.Language +{ + internal class DefaultRazorCodeGenerationOptionsFactoryProjectFeature : RazorProjectEngineFeatureBase, IRazorCodeGenerationOptionsFactoryProjectFeature + { + private IConfigureRazorCodeGenerationOptionsFeature[] _configureOptions; + + protected override void OnInitialized() + { + _configureOptions = ProjectEngine.EngineFeatures.OfType().ToArray(); + } + + public RazorCodeGenerationOptions Create(Action configure) + { + var builder = new DefaultRazorCodeGenerationOptionsBuilder(ProjectEngine.Configuration); + configure?.Invoke(builder); + + for (var i = 0; i < _configureOptions.Length; i++) + { + _configureOptions[i].Configure(builder); + } + + var options = builder.Build(); + return options; + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorCodeGenerationOptionsFeature.cs b/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorCodeGenerationOptionsFeature.cs index 0238ee2d76..1c915364d7 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorCodeGenerationOptionsFeature.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorCodeGenerationOptionsFeature.cs @@ -5,7 +5,9 @@ using System.Linq; namespace Microsoft.AspNetCore.Razor.Language { +#pragma warning disable CS0618 // Type or member is obsolete internal class DefaultRazorCodeGenerationOptionsFeature : RazorEngineFeatureBase, IRazorCodeGenerationOptionsFeature +#pragma warning restore CS0618 // Type or member is obsolete { private readonly bool _designTime; private IConfigureRazorCodeGenerationOptionsFeature[] _configureOptions; diff --git a/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorIntermediateNodeLoweringPhase.cs b/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorIntermediateNodeLoweringPhase.cs index b7047bca1b..5e3ae8a258 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorIntermediateNodeLoweringPhase.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorIntermediateNodeLoweringPhase.cs @@ -11,6 +11,7 @@ using Microsoft.AspNetCore.Razor.Language.Legacy; namespace Microsoft.AspNetCore.Razor.Language { +#pragma warning disable CS0618 // Type or member is obsolete internal class DefaultRazorIntermediateNodeLoweringPhase : RazorEnginePhaseBase, IRazorIntermediateNodeLoweringPhase { private IRazorCodeGenerationOptionsFeature _optionsFeature; @@ -31,7 +32,7 @@ namespace Microsoft.AspNetCore.Razor.Language var document = new DocumentIntermediateNode(); var builder = IntermediateNodeBuilder.Create(document); - document.Options = _optionsFeature.GetOptions(); + document.Options = codeDocument.GetCodeGenerationOptions() ?? _optionsFeature.GetOptions(); var namespaces = new Dictionary(StringComparer.Ordinal); @@ -785,4 +786,5 @@ namespace Microsoft.AspNetCore.Razor.Language private static bool IsMalformed(List diagnostics) => diagnostics.Count > 0 && diagnostics.Any(diagnostic => diagnostic.Severity == RazorDiagnosticSeverity.Error); } +#pragma warning restore CS0618 // Type or member is obsolete } diff --git a/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorParserOptionsBuilder.cs b/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorParserOptionsBuilder.cs index eadc773439..2e4d3273d2 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorParserOptionsBuilder.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorParserOptionsBuilder.cs @@ -9,23 +9,43 @@ namespace Microsoft.AspNetCore.Razor.Language { internal class DefaultRazorParserOptionsBuilder : RazorParserOptionsBuilder { - public DefaultRazorParserOptionsBuilder(bool designTime, RazorLanguageVersion version) + private bool _designTime; + + public DefaultRazorParserOptionsBuilder(RazorConfiguration configuration) { - DesignTime = designTime; - Version = version; + if (configuration == null) + { + throw new ArgumentNullException(nameof(configuration)); + } + + Configuration = configuration; + LanguageVersion = configuration.LanguageVersion; } - public override bool DesignTime { get; } + public DefaultRazorParserOptionsBuilder(bool designTime, RazorLanguageVersion version) + { + _designTime = designTime; + LanguageVersion = version; + } + + public override RazorConfiguration Configuration { get; } + + public override bool DesignTime => _designTime; public override ICollection Directives { get; } = new List(); public override bool ParseLeadingDirectives { get; set; } - public override RazorLanguageVersion Version { get; } + public override RazorLanguageVersion LanguageVersion { get; } public override RazorParserOptions Build() { - return new DefaultRazorParserOptions(Directives.ToArray(), DesignTime, ParseLeadingDirectives, Version); + return new DefaultRazorParserOptions(Directives.ToArray(), DesignTime, ParseLeadingDirectives, LanguageVersion); + } + + public override void SetDesignTime(bool designTime) + { + _designTime = designTime; } } } diff --git a/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorParserOptionsFactoryProjectFeature.cs b/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorParserOptionsFactoryProjectFeature.cs new file mode 100644 index 0000000000..32fb04af7f --- /dev/null +++ b/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorParserOptionsFactoryProjectFeature.cs @@ -0,0 +1,32 @@ +// 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.Linq; + +namespace Microsoft.AspNetCore.Razor.Language +{ + internal class DefaultRazorParserOptionsFactoryProjectFeature : RazorProjectEngineFeatureBase, IRazorParserOptionsFactoryProjectFeature + { + private IConfigureRazorParserOptionsFeature[] _configureOptions; + + protected override void OnInitialized() + { + _configureOptions = ProjectEngine.EngineFeatures.OfType().ToArray(); + } + + public RazorParserOptions Create(Action configure) + { + var builder = new DefaultRazorParserOptionsBuilder(ProjectEngine.Configuration); + configure?.Invoke(builder); + + for (var i = 0; i < _configureOptions.Length; i++) + { + _configureOptions[i].Configure(builder); + } + + var options = builder.Build(); + return options; + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorParserOptionsFeature.cs b/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorParserOptionsFeature.cs index 867e289ee7..eecb9bd9cc 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorParserOptionsFeature.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorParserOptionsFeature.cs @@ -5,7 +5,9 @@ using System.Linq; namespace Microsoft.AspNetCore.Razor.Language { +#pragma warning disable CS0618 // Type or member is obsolete internal class DefaultRazorParserOptionsFeature : RazorEngineFeatureBase, IRazorParserOptionsFeature +#pragma warning restore CS0618 // Type or member is obsolete { private readonly bool _designTime; private readonly RazorLanguageVersion _version; diff --git a/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorParsingPhase.cs b/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorParsingPhase.cs index 5a13e68760..8264201271 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorParsingPhase.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorParsingPhase.cs @@ -3,6 +3,7 @@ namespace Microsoft.AspNetCore.Razor.Language { +#pragma warning disable CS0618 // Type or member is obsolete internal class DefaultRazorParsingPhase : RazorEnginePhaseBase, IRazorParsingPhase { private IRazorParserOptionsFeature _optionsFeature; @@ -14,7 +15,7 @@ namespace Microsoft.AspNetCore.Razor.Language protected override void ExecuteCore(RazorCodeDocument codeDocument) { - var options = _optionsFeature.GetOptions(); + var options = codeDocument.GetParserOptions() ??_optionsFeature.GetOptions(); var syntaxTree = RazorSyntaxTree.Parse(codeDocument.Source, options); codeDocument.SetSyntaxTree(syntaxTree); @@ -26,4 +27,5 @@ namespace Microsoft.AspNetCore.Razor.Language codeDocument.SetImportSyntaxTrees(importSyntaxTrees); } } +#pragma warning restore CS0618 // Type or member is obsolete } diff --git a/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorProjectEngine.cs b/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorProjectEngine.cs index 6cac02dc25..6ac8e4c4cd 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorProjectEngine.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorProjectEngine.cs @@ -10,10 +10,16 @@ namespace Microsoft.AspNetCore.Razor.Language internal class DefaultRazorProjectEngine : RazorProjectEngine { public DefaultRazorProjectEngine( + RazorConfiguration configuration, RazorEngine engine, RazorProjectFileSystem fileSystem, - IReadOnlyList features) + IReadOnlyList projectFeatures) { + if (configuration == null) + { + throw new ArgumentNullException(nameof(configuration)); + } + if (engine == null) { throw new ArgumentNullException(nameof(engine)); @@ -24,39 +30,87 @@ namespace Microsoft.AspNetCore.Razor.Language throw new ArgumentNullException(nameof(fileSystem)); } - if (features == null) + if (projectFeatures == null) { - throw new ArgumentNullException(nameof(features)); + throw new ArgumentNullException(nameof(projectFeatures)); } + Configuration = configuration; Engine = engine; FileSystem = fileSystem; - Features = features; + ProjectFeatures = projectFeatures; - for (var i = 0; i < features.Count; i++) + for (var i = 0; i < projectFeatures.Count; i++) { - features[i].ProjectEngine = this; + projectFeatures[i].ProjectEngine = this; } } + public override RazorConfiguration Configuration { get; } + public override RazorProjectFileSystem FileSystem { get; } public override RazorEngine Engine { get; } - public override IReadOnlyList Features { get; } + public override IReadOnlyList ProjectFeatures { get; } - public override RazorCodeDocument Process(RazorProjectItem projectItem) + protected override void ConfigureParserOptions(RazorParserOptionsBuilder builder) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + } + + protected override void ConfigureDesignTimeParserOptions(RazorParserOptionsBuilder builder) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + builder.SetDesignTime(true); + } + + protected override void ConfigureCodeGenerationOptions(RazorCodeGenerationOptionsBuilder builder) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + } + + protected override void ConfigureDesignTimeCodeGenerationOptions(RazorCodeGenerationOptionsBuilder builder) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + builder.SetDesignTime(true); + builder.SuppressChecksum = true; + builder.SuppressMetadataAttributes = true; + } + + protected override RazorCodeDocument ProcessCore( + RazorProjectItem projectItem, + Action configureParser, + Action configureCodeGeneration) { if (projectItem == null) { throw new ArgumentNullException(nameof(projectItem)); } - var importFeature = GetRequiredFeature(); - var imports = importFeature.GetImports(projectItem); var sourceDocument = RazorSourceDocument.ReadFrom(projectItem); - var codeDocument = RazorCodeDocument.Create(sourceDocument, imports); + var importFeature = GetRequiredFeature(); + var imports = importFeature.GetImports(projectItem); + + var parserOptions = GetRequiredFeature().Create(configureParser); + var codeGenerationOptions = GetRequiredFeature().Create(configureCodeGeneration); + + var codeDocument = RazorCodeDocument.Create(sourceDocument, imports, parserOptions, codeGenerationOptions); Engine.Process(codeDocument); @@ -65,7 +119,7 @@ namespace Microsoft.AspNetCore.Razor.Language private TFeature GetRequiredFeature() where TFeature : IRazorProjectEngineFeature { - var feature = Features.OfType().FirstOrDefault(); + var feature = ProjectFeatures.OfType().FirstOrDefault(); if (feature == null) { throw new InvalidOperationException( diff --git a/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorProjectEngineBuilder.cs b/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorProjectEngineBuilder.cs index 85af0c68f2..a07f4581ff 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorProjectEngineBuilder.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorProjectEngineBuilder.cs @@ -22,29 +22,19 @@ namespace Microsoft.AspNetCore.Razor.Language Phases = new List(); } + public override RazorConfiguration Configuration { get; } + public override RazorProjectFileSystem FileSystem { get; } public override ICollection Features { get; } public override IList Phases { get; } - - public override RazorConfiguration Configuration { get; } - + public override RazorProjectEngine Build() { - RazorEngine engine = null; - - if (Configuration.DesignTime) - { - engine = RazorEngine.CreateDesignTimeEmpty(ConfigureRazorEngine); - } - else - { - engine = RazorEngine.CreateEmpty(ConfigureRazorEngine); - } - - var projectEngineFeatures = Features.OfType().ToArray(); - var projectEngine = new DefaultRazorProjectEngine(engine, FileSystem, projectEngineFeatures); + var engine = RazorEngine.CreateEmpty(ConfigureRazorEngine); + var projectFeatures = Features.OfType().ToArray(); + var projectEngine = new DefaultRazorProjectEngine(Configuration, engine, FileSystem, projectFeatures); return projectEngine; } diff --git a/src/Microsoft.AspNetCore.Razor.Language/Extensions/DefaultTagHelperTargetExtension.cs b/src/Microsoft.AspNetCore.Razor.Language/Extensions/DefaultTagHelperTargetExtension.cs index eacdf6b677..ad85ebf807 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/Extensions/DefaultTagHelperTargetExtension.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/Extensions/DefaultTagHelperTargetExtension.cs @@ -16,8 +16,6 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions { private static readonly string[] PrivateModifiers = new string[] { "private" }; - public bool DesignTime { get; set; } - public string RunnerVariableName { get; set; } = "__tagHelperRunner"; public string StringValueBufferVariableName { get; set; } = "__tagHelperStringValueBuffer"; @@ -82,7 +80,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions throw new InvalidOperationException(message); } - if (DesignTime) + if (context.Options.DesignTime) { context.RenderChildren(node); } @@ -136,7 +134,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions .Write(CreateTagHelperMethodName) .WriteLine("();"); - if (!DesignTime) + if (!context.Options.DesignTime) { context.CodeWriter.WriteInstanceMethodInvocation( ExecutionContextVariableName, @@ -153,7 +151,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions throw new InvalidOperationException(message); } - if (!DesignTime) + if (!context.Options.DesignTime) { context.CodeWriter .Write("await ") @@ -200,7 +198,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions throw new InvalidOperationException(message); } - if (DesignTime) + if (context.Options.DesignTime) { context.RenderChildren(node); } @@ -284,7 +282,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions throw new InvalidOperationException(message); } - if (!DesignTime) + if (!context.Options.DesignTime) { // Ensure that the property we're trying to set has initialized its dictionary bound properties. if (node.IsIndexerNameMatch && @@ -338,7 +336,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions // If we get there, this is the first time seeing this property so we need to evaluate the expression. if (node.BoundAttribute.ExpectsStringValue(node.AttributeName)) { - if (DesignTime) + if (context.Options.DesignTime) { context.RenderChildren(node); @@ -370,7 +368,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions } else { - if (DesignTime) + if (context.Options.DesignTime) { var firstMappedChild = node.Children.FirstOrDefault(child => child.Source != null) as IntermediateNode; var valueStart = firstMappedChild?.Source; @@ -456,7 +454,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions } } - if (!DesignTime) + if (!context.Options.DesignTime) { // We need to inform the context of the attribute value. context.CodeWriter @@ -474,7 +472,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions public void WriteTagHelperRuntime(CodeRenderingContext context, DefaultTagHelperRuntimeIntermediateNode node) { - if (!DesignTime) + if (!context.Options.DesignTime) { context.CodeWriter.WriteLine("#line hidden"); @@ -566,7 +564,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions } else if (node is IntermediateToken token) { - if (DesignTime && node.Source != null) + if (context.Options.DesignTime && node.Source != null) { context.AddSourceMappingFor(node); } diff --git a/src/Microsoft.AspNetCore.Razor.Language/Extensions/DesignTimeDirectivePass.cs b/src/Microsoft.AspNetCore.Razor.Language/Extensions/DesignTimeDirectivePass.cs index 31e9cbf361..6d6d7e261d 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/Extensions/DesignTimeDirectivePass.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/Extensions/DesignTimeDirectivePass.cs @@ -15,6 +15,13 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions protected override void ExecuteCore(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode) { + // Only supports design time. This pass rewrites directives so they will have the right design time + // behavior and would break things if it ran for runtime. + if (!documentNode.Options.DesignTime) + { + return; + } + var walker = new DesignTimeHelperWalker(); walker.VisitDocument(documentNode); } diff --git a/src/Microsoft.AspNetCore.Razor.Language/Extensions/PreallocatedTagHelperAttributeOptimizationPass.cs b/src/Microsoft.AspNetCore.Razor.Language/Extensions/PreallocatedTagHelperAttributeOptimizationPass.cs index ed9488da20..f6d0f40a3b 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/Extensions/PreallocatedTagHelperAttributeOptimizationPass.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/Extensions/PreallocatedTagHelperAttributeOptimizationPass.cs @@ -15,6 +15,12 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions protected override void ExecuteCore(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode) { + // There's no value in executing this pass at design time, it just prevents some allocations. + if (documentNode.Options.DesignTime) + { + return; + } + var walker = new PreallocatedTagHelperWalker(); walker.VisitDocument(documentNode); } diff --git a/src/Microsoft.AspNetCore.Razor.Language/IRazorImportFeature.cs b/src/Microsoft.AspNetCore.Razor.Language/IImportProjectFeature.cs similarity index 83% rename from src/Microsoft.AspNetCore.Razor.Language/IRazorImportFeature.cs rename to src/Microsoft.AspNetCore.Razor.Language/IImportProjectFeature.cs index f002cd20ce..90bd6638f4 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/IRazorImportFeature.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/IImportProjectFeature.cs @@ -5,7 +5,7 @@ using System.Collections.Generic; namespace Microsoft.AspNetCore.Razor.Language { - public interface IRazorImportFeature : IRazorProjectEngineFeature + public interface IImportProjectFeature : IRazorProjectEngineFeature { IReadOnlyList GetImports(RazorProjectItem projectItem); } diff --git a/src/Microsoft.AspNetCore.Razor.Language/IRazorCodeGenerationOptionsFactoryProjectFeature.cs b/src/Microsoft.AspNetCore.Razor.Language/IRazorCodeGenerationOptionsFactoryProjectFeature.cs new file mode 100644 index 0000000000..6d3ed5156c --- /dev/null +++ b/src/Microsoft.AspNetCore.Razor.Language/IRazorCodeGenerationOptionsFactoryProjectFeature.cs @@ -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. + +using System; + +namespace Microsoft.AspNetCore.Razor.Language +{ + internal interface IRazorCodeGenerationOptionsFactoryProjectFeature : IRazorProjectEngineFeature + { + RazorCodeGenerationOptions Create(Action configure); + } +} diff --git a/src/Microsoft.AspNetCore.Razor.Language/IRazorCodeGenerationOptionsFeature.cs b/src/Microsoft.AspNetCore.Razor.Language/IRazorCodeGenerationOptionsFeature.cs index dc97b6326c..b74bcc3d8d 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/IRazorCodeGenerationOptionsFeature.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/IRazorCodeGenerationOptionsFeature.cs @@ -1,8 +1,11 @@ // Copyright(c) .NET Foundation.All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; + namespace Microsoft.AspNetCore.Razor.Language { + [Obsolete("In Razor 2.1 and newer, use RazorCodeDocument.GetCodeGenerationOptions().")] public interface IRazorCodeGenerationOptionsFeature : IRazorEngineFeature { RazorCodeGenerationOptions GetOptions(); diff --git a/src/Microsoft.AspNetCore.Razor.Language/IRazorParserOptionsFactoryProjectFeature.cs b/src/Microsoft.AspNetCore.Razor.Language/IRazorParserOptionsFactoryProjectFeature.cs new file mode 100644 index 0000000000..e73bf8babc --- /dev/null +++ b/src/Microsoft.AspNetCore.Razor.Language/IRazorParserOptionsFactoryProjectFeature.cs @@ -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. + +using System; + +namespace Microsoft.AspNetCore.Razor.Language +{ + internal interface IRazorParserOptionsFactoryProjectFeature : IRazorProjectEngineFeature + { + RazorParserOptions Create(Action configure); + } +} diff --git a/src/Microsoft.AspNetCore.Razor.Language/IRazorParserOptionsFeature.cs b/src/Microsoft.AspNetCore.Razor.Language/IRazorParserOptionsFeature.cs index ec4b3f1ab1..b2c1e9ec3e 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/IRazorParserOptionsFeature.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/IRazorParserOptionsFeature.cs @@ -1,8 +1,11 @@ // Copyright(c) .NET Foundation.All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; + namespace Microsoft.AspNetCore.Razor.Language { + [Obsolete("In Razor 2.1 and newer, use RazorCodeDocument.GetParserOptions().")] public interface IRazorParserOptionsFeature : IRazorEngineFeature { RazorParserOptions GetOptions(); diff --git a/src/Microsoft.AspNetCore.Razor.Language/RazorCodeDocument.cs b/src/Microsoft.AspNetCore.Razor.Language/RazorCodeDocument.cs index 6ee41d0be5..0d57df2815 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/RazorCodeDocument.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/RazorCodeDocument.cs @@ -30,6 +30,23 @@ namespace Microsoft.AspNetCore.Razor.Language return new DefaultRazorCodeDocument(source, imports); } + public static RazorCodeDocument Create( + RazorSourceDocument source, + IEnumerable imports, + RazorParserOptions parserOptions, + RazorCodeGenerationOptions codeGenerationOptions) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + var codeDocument = new DefaultRazorCodeDocument(source, imports); + codeDocument.SetParserOptions(parserOptions); + codeDocument.SetCodeGenerationOptions(codeGenerationOptions); + return codeDocument; + } + public abstract IReadOnlyList Imports { get; } public abstract ItemCollection Items { get; } diff --git a/src/Microsoft.AspNetCore.Razor.Language/RazorCodeDocumentExtensions.cs b/src/Microsoft.AspNetCore.Razor.Language/RazorCodeDocumentExtensions.cs index 314b83d176..85df33837a 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/RazorCodeDocumentExtensions.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/RazorCodeDocumentExtensions.cs @@ -109,6 +109,46 @@ namespace Microsoft.AspNetCore.Razor.Language document.Items[typeof(RazorCSharpDocument)] = csharp; } + public static RazorParserOptions GetParserOptions(this RazorCodeDocument document) + { + if (document == null) + { + throw new ArgumentNullException(nameof(document)); + } + + return (RazorParserOptions)document.Items[typeof(RazorParserOptions)]; + } + + public static void SetParserOptions(this RazorCodeDocument document, RazorParserOptions parserOptions) + { + if (document == null) + { + throw new ArgumentNullException(nameof(document)); + } + + document.Items[typeof(RazorParserOptions)] = parserOptions; + } + + public static RazorCodeGenerationOptions GetCodeGenerationOptions(this RazorCodeDocument document) + { + if (document == null) + { + throw new ArgumentNullException(nameof(document)); + } + + return (RazorCodeGenerationOptions)document.Items[typeof(RazorCodeGenerationOptions)]; + } + + public static void SetCodeGenerationOptions(this RazorCodeDocument document, RazorCodeGenerationOptions codeGenerationOptions) + { + if (document == null) + { + throw new ArgumentNullException(nameof(document)); + } + + document.Items[typeof(RazorCodeGenerationOptions)] = codeGenerationOptions; + } + private class ImportSyntaxTreesHolder { public ImportSyntaxTreesHolder(IReadOnlyList syntaxTrees) diff --git a/src/Microsoft.AspNetCore.Razor.Language/RazorCodeGenerationOptionsBuilder.cs b/src/Microsoft.AspNetCore.Razor.Language/RazorCodeGenerationOptionsBuilder.cs index 55e1934ee7..be4c9dff3d 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/RazorCodeGenerationOptionsBuilder.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/RazorCodeGenerationOptionsBuilder.cs @@ -5,6 +5,8 @@ namespace Microsoft.AspNetCore.Razor.Language { public abstract class RazorCodeGenerationOptionsBuilder { + public virtual RazorConfiguration Configuration => null; + public abstract bool DesignTime { get; } public abstract int IndentSize { get; set; } @@ -41,5 +43,9 @@ namespace Microsoft.AspNetCore.Razor.Language public virtual bool SuppressMetadataAttributes { get; set; } public abstract RazorCodeGenerationOptions Build(); + + public virtual void SetDesignTime(bool designTime) + { + } } } diff --git a/src/Microsoft.AspNetCore.Razor.Language/RazorConfiguration.cs b/src/Microsoft.AspNetCore.Razor.Language/RazorConfiguration.cs index 0f00751497..f68db61f22 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/RazorConfiguration.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/RazorConfiguration.cs @@ -12,22 +12,12 @@ namespace Microsoft.AspNetCore.Razor.Language public static readonly RazorConfiguration Default = new RazorConfiguration( RazorLanguageVersion.Latest, "unnamed", - Array.Empty(), - designTime: false); - - // This is used only in some back-compat scenarios. We don't expose it because there's no - // use case for anyone else to use it. - internal static readonly RazorConfiguration DefaultDesignTime = new RazorConfiguration( - RazorLanguageVersion.Latest, - "unnamed", - Array.Empty(), - designTime: true); + Array.Empty()); public RazorConfiguration( RazorLanguageVersion languageVersion, string configurationName, - IEnumerable extensions, - bool designTime) + IEnumerable extensions) { if (languageVersion == null) { @@ -47,7 +37,6 @@ namespace Microsoft.AspNetCore.Razor.Language LanguageVersion = languageVersion; ConfigurationName = configurationName; Extensions = extensions.ToArray(); - DesignTime = designTime; } public string ConfigurationName { get; } @@ -55,7 +44,5 @@ namespace Microsoft.AspNetCore.Razor.Language public IReadOnlyList Extensions { get; } public RazorLanguageVersion LanguageVersion { get; } - - public bool DesignTime { get; } } } diff --git a/src/Microsoft.AspNetCore.Razor.Language/RazorEngine.cs b/src/Microsoft.AspNetCore.Razor.Language/RazorEngine.cs index a48c31893a..13f2f6be73 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/RazorEngine.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/RazorEngine.cs @@ -17,27 +17,27 @@ namespace Microsoft.AspNetCore.Razor.Language return Create(configure: null); } - public static RazorEngine Create(Action configure) => CreateCore(RazorConfiguration.Default, configure); + public static RazorEngine Create(Action configure) => CreateCore(RazorConfiguration.Default, false, configure); public static RazorEngine CreateDesignTime() { return CreateDesignTime(configure: null); } - public static RazorEngine CreateDesignTime(Action configure) => CreateCore(RazorConfiguration.DefaultDesignTime, configure); + public static RazorEngine CreateDesignTime(Action configure) => CreateCore(RazorConfiguration.Default, true, configure); // Internal since RazorEngine APIs are going to be obsolete. - internal static RazorEngine CreateCore(RazorConfiguration configuration, Action configure) + internal static RazorEngine CreateCore(RazorConfiguration configuration, bool designTime, Action configure) { if (configuration == null) { throw new ArgumentNullException(nameof(configuration)); } - var builder = new DefaultRazorEngineBuilder(configuration.DesignTime); + var builder = new DefaultRazorEngineBuilder(designTime); AddDefaults(builder); - if (configuration.DesignTime) + if (designTime) { AddDefaultDesignTimeFeatures(configuration, builder.Features); } @@ -152,7 +152,7 @@ namespace Microsoft.AspNetCore.Razor.Language var targetExtension = features.OfType().FirstOrDefault(); Debug.Assert(targetExtension != null); - targetExtension.TargetExtensions.Add(new DefaultTagHelperTargetExtension() { DesignTime = false }); + targetExtension.TargetExtensions.Add(new DefaultTagHelperTargetExtension()); targetExtension.TargetExtensions.Add(new PreallocatedAttributeTargetExtension()); } @@ -170,7 +170,7 @@ namespace Microsoft.AspNetCore.Razor.Language var targetExtension = features.OfType().FirstOrDefault(); Debug.Assert(targetExtension != null); - targetExtension.TargetExtensions.Add(new DefaultTagHelperTargetExtension() { DesignTime = true }); + targetExtension.TargetExtensions.Add(new DefaultTagHelperTargetExtension()); targetExtension.TargetExtensions.Add(new DesignTimeDirectiveTargetExtension()); } diff --git a/src/Microsoft.AspNetCore.Razor.Language/RazorParserOptionsBuilder.cs b/src/Microsoft.AspNetCore.Razor.Language/RazorParserOptionsBuilder.cs index 74b11fcc26..b036e3d58d 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/RazorParserOptionsBuilder.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/RazorParserOptionsBuilder.cs @@ -1,21 +1,26 @@ // 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 abstract class RazorParserOptionsBuilder { + public virtual RazorConfiguration Configuration => null; + public abstract bool DesignTime { get; } public abstract ICollection Directives { get; } public abstract bool ParseLeadingDirectives { get; set; } - public virtual RazorLanguageVersion Version { get; } + public virtual RazorLanguageVersion LanguageVersion { get; } public abstract RazorParserOptions Build(); + + public virtual void SetDesignTime(bool designTime) + { + } } } diff --git a/src/Microsoft.AspNetCore.Razor.Language/RazorProjectEngine.cs b/src/Microsoft.AspNetCore.Razor.Language/RazorProjectEngine.cs index 7f2c03e8e3..04f86c38d3 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/RazorProjectEngine.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/RazorProjectEngine.cs @@ -3,26 +3,63 @@ using System; using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Razor.Language.Extensions; namespace Microsoft.AspNetCore.Razor.Language { public abstract class RazorProjectEngine { + public abstract RazorConfiguration Configuration { get; } + public abstract RazorProjectFileSystem FileSystem { get; } public abstract RazorEngine Engine { get; } - public abstract IReadOnlyList Features { get; } + public IReadOnlyList EngineFeatures => Engine.Features; - public abstract RazorCodeDocument Process(RazorProjectItem projectItem); + public IReadOnlyList Phases => Engine.Phases; - public static RazorProjectEngine Create(RazorProjectFileSystem fileSystem) => Create(fileSystem, configure: null); + public abstract IReadOnlyList ProjectFeatures { get; } - public static RazorProjectEngine Create(RazorProjectFileSystem fileSystem, Action configure) => Create(fileSystem, RazorConfiguration.Default, configure); + protected abstract void ConfigureParserOptions(RazorParserOptionsBuilder builder); + + protected abstract void ConfigureDesignTimeParserOptions(RazorParserOptionsBuilder builder); + + protected abstract void ConfigureCodeGenerationOptions(RazorCodeGenerationOptionsBuilder builder); + + protected abstract void ConfigureDesignTimeCodeGenerationOptions(RazorCodeGenerationOptionsBuilder builder); + + public virtual RazorCodeDocument Process(RazorProjectItem projectItem) + { + if (projectItem == null) + { + throw new ArgumentNullException(nameof(projectItem)); + } + + return ProcessCore(projectItem, ConfigureParserOptions, ConfigureCodeGenerationOptions); + } + + public virtual RazorCodeDocument ProcessDesignTime(RazorProjectItem projectItem) + { + if (projectItem == null) + { + throw new ArgumentNullException(nameof(projectItem)); + } + + return ProcessCore(projectItem, ConfigureDesignTimeParserOptions, ConfigureDesignTimeCodeGenerationOptions); + } + + protected abstract RazorCodeDocument ProcessCore( + RazorProjectItem projectItem, + Action configureParser, + Action configureCodeGeneration); + + public static RazorProjectEngine Create(RazorConfiguration configuration, RazorProjectFileSystem fileSystem) => Create(configuration, fileSystem, configure: null); public static RazorProjectEngine Create( - RazorProjectFileSystem fileSystem, RazorConfiguration configuration, + RazorProjectFileSystem fileSystem, Action configure) { if (fileSystem == null) @@ -37,54 +74,90 @@ namespace Microsoft.AspNetCore.Razor.Language var builder = new DefaultRazorProjectEngineBuilder(configuration, fileSystem); - AddDefaults(builder); - - if (configuration.DesignTime) - { - AddDesignTimeDefaults(builder); - } - else - { - AddRuntimeDefaults(builder); - } + RazorEngine.AddDefaultPhases(builder.Phases); + AddDefaultsFeatures(builder.Features); configure?.Invoke(builder); return builder.Build(); } - - private static void AddDefaults(RazorProjectEngineBuilder builder) + + private static void AddDefaultsFeatures(ICollection features) { - builder.Features.Add(new DefaultRazorImportFeature()); - } + features.Add(new DefaultImportProjectFeature()); - private static void AddDesignTimeDefaults(RazorProjectEngineBuilder builder) - { - var engineFeatures = new List(); - RazorEngine.AddDefaultFeatures(engineFeatures); - RazorEngine.AddDefaultDesignTimeFeatures(builder.Configuration, engineFeatures); + // General extensibility + features.Add(new DefaultRazorDirectiveFeature()); + features.Add(new DefaultMetadataIdentifierFeature()); - AddEngineFeaturesAndPhases(builder, engineFeatures); - } + // Options features + features.Add(new DefaultRazorParserOptionsFactoryProjectFeature()); + features.Add(new DefaultRazorCodeGenerationOptionsFactoryProjectFeature()); - private static void AddRuntimeDefaults(RazorProjectEngineBuilder builder) - { - var engineFeatures = new List(); - RazorEngine.AddDefaultFeatures(engineFeatures); - RazorEngine.AddDefaultRuntimeFeatures(builder.Configuration, engineFeatures); + // Legacy options features + // + // These features are obsolete as of 2.1. Our code will resolve this but not invoke them. + features.Add(new DefaultRazorParserOptionsFeature(designTime: false, version: RazorLanguageVersion.Version_2_0)); + features.Add(new DefaultRazorCodeGenerationOptionsFeature(designTime: false)); - AddEngineFeaturesAndPhases(builder, engineFeatures); - } + // Syntax Tree passes + features.Add(new DefaultDirectiveSyntaxTreePass()); + features.Add(new HtmlNodeOptimizationPass()); + features.Add(new PreallocatedTagHelperAttributeOptimizationPass()); - private static void AddEngineFeaturesAndPhases(RazorProjectEngineBuilder builder, IReadOnlyList engineFeatures) - { - for (var i = 0; i < engineFeatures.Count; i++) + // Intermediate Node Passes + features.Add(new DefaultDocumentClassifierPass()); + features.Add(new MetadataAttributePass()); + features.Add(new DesignTimeDirectivePass()); + features.Add(new DirectiveRemovalOptimizationPass()); + features.Add(new DefaultTagHelperOptimizationPass()); + + // Default Code Target Extensions + var targetExtensionFeature = new DefaultRazorTargetExtensionFeature(); + features.Add(targetExtensionFeature); + targetExtensionFeature.TargetExtensions.Add(new MetadataAttributeTargetExtension()); + targetExtensionFeature.TargetExtensions.Add(new DefaultTagHelperTargetExtension()); + targetExtensionFeature.TargetExtensions.Add(new PreallocatedAttributeTargetExtension()); + targetExtensionFeature.TargetExtensions.Add(new DesignTimeDirectiveTargetExtension()); + + // Default configuration + var configurationFeature = new DefaultDocumentClassifierPassFeature(); + features.Add(configurationFeature); + configurationFeature.ConfigureClass.Add((document, @class) => { - var engineFeature = engineFeatures[i]; - builder.Features.Add(engineFeature); - } + @class.ClassName = "Template"; + @class.Modifiers.Add("public"); + }); - RazorEngine.AddDefaultPhases(builder.Phases); + configurationFeature.ConfigureNamespace.Add((document, @namespace) => + { + @namespace.Content = "Razor"; + }); + + configurationFeature.ConfigureMethod.Add((document, method) => + { + method.MethodName = "ExecuteAsync"; + method.ReturnType = $"global::{typeof(Task).FullName}"; + + method.Modifiers.Add("public"); + method.Modifiers.Add("async"); + method.Modifiers.Add("override"); + }); + } + + internal static void AddDefaultRuntimeFeatures(RazorConfiguration configuration, ICollection features) + { + // Configure options + features.Add(new DefaultRazorParserOptionsFeature(designTime: false, version: configuration.LanguageVersion)); + features.Add(new DefaultRazorCodeGenerationOptionsFeature(designTime: false)); + } + + internal static void AddDefaultDesignTimeFeatures(RazorConfiguration configuration, ICollection features) + { + // Configure options + features.Add(new DefaultRazorParserOptionsFeature(designTime: true, version: configuration.LanguageVersion)); + features.Add(new DefaultRazorCodeGenerationOptionsFeature(designTime: true)); + features.Add(new SuppressChecksumOptionsFeature()); } } } diff --git a/src/Microsoft.AspNetCore.Razor.Language/RazorProjectEngineBuilder.cs b/src/Microsoft.AspNetCore.Razor.Language/RazorProjectEngineBuilder.cs index 3177c4b13d..9fe4356fa9 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/RazorProjectEngineBuilder.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/RazorProjectEngineBuilder.cs @@ -7,14 +7,14 @@ namespace Microsoft.AspNetCore.Razor.Language { public abstract class RazorProjectEngineBuilder { + public abstract RazorConfiguration Configuration { get; } + public abstract RazorProjectFileSystem FileSystem { get; } public abstract ICollection Features { get; } public abstract IList Phases { get; } - public abstract RazorConfiguration Configuration { get; } - public abstract RazorProjectEngine Build(); } } diff --git a/src/Microsoft.AspNetCore.Razor.Language/RazorProjectEngineBuilderExtensions.cs b/src/Microsoft.AspNetCore.Razor.Language/RazorProjectEngineBuilderExtensions.cs index 8bd17e0dd6..6d14b838eb 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/RazorProjectEngineBuilderExtensions.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/RazorProjectEngineBuilderExtensions.cs @@ -9,7 +9,7 @@ namespace Microsoft.AspNetCore.Razor.Language { public static class RazorProjectEngineBuilderExtensions { - public static void SetImportFeature(this RazorProjectEngineBuilder builder, IRazorImportFeature feature) + public static void SetImportFeature(this RazorProjectEngineBuilder builder, IImportProjectFeature feature) { if (builder == null) { @@ -22,7 +22,7 @@ namespace Microsoft.AspNetCore.Razor.Language } // Remove any existing import features in favor of the new one we're given. - var existingFeatures = builder.Features.OfType().ToArray(); + var existingFeatures = builder.Features.OfType().ToArray(); foreach (var existingFeature in existingFeatures) { builder.Features.Remove(existingFeature); diff --git a/src/Microsoft.VisualStudio.Editor.Razor/DefaultTemplateEngineFactoryService.cs b/src/Microsoft.VisualStudio.Editor.Razor/DefaultTemplateEngineFactoryService.cs index 6c615077c7..f4f7c8b608 100644 --- a/src/Microsoft.VisualStudio.Editor.Razor/DefaultTemplateEngineFactoryService.cs +++ b/src/Microsoft.VisualStudio.Editor.Razor/DefaultTemplateEngineFactoryService.cs @@ -43,12 +43,12 @@ namespace Microsoft.VisualStudio.Editor.Razor var project = FindProject(projectPath); var configuration = (project?.Configuration as MvcExtensibilityConfiguration) ?? DefaultConfiguration; var razorLanguageVersion = configuration.LanguageVersion; - var razorConfiguration = new RazorConfiguration(razorLanguageVersion, "unnamed", Array.Empty(), designTime: true); + var razorConfiguration = new RazorConfiguration(razorLanguageVersion, "unnamed", Array.Empty()); RazorEngine engine; if (razorLanguageVersion.Major == 1) { - engine = RazorEngine.CreateCore(razorConfiguration, b => + engine = RazorEngine.CreateCore(razorConfiguration, true, b => { configure?.Invoke(b); @@ -66,7 +66,7 @@ namespace Microsoft.VisualStudio.Editor.Razor } else { - engine = RazorEngine.CreateCore(razorConfiguration, b => + engine = RazorEngine.CreateCore(razorConfiguration, true, b => { configure?.Invoke(b); diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/AssemblyAttributeInjectionPassTest.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/AssemblyAttributeInjectionPassTest.cs index 0c69be0e18..a507103fb7 100644 --- a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/AssemblyAttributeInjectionPassTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/AssemblyAttributeInjectionPassTest.cs @@ -13,7 +13,11 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions public void Execute_NoOps_IfNamespaceNodeIsMissing() { // Arrange - var irDocument = new DocumentIntermediateNode(); + var irDocument = new DocumentIntermediateNode() + { + Options = RazorCodeGenerationOptions.CreateDefault(), + }; + var pass = new AssemblyAttributeInjectionPass { Engine = RazorEngine.Create(), @@ -30,7 +34,10 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions public void Execute_NoOps_IfNamespaceNodeHasEmptyContent() { // Arrange - var irDocument = new DocumentIntermediateNode(); + var irDocument = new DocumentIntermediateNode() + { + Options = RazorCodeGenerationOptions.CreateDefault(), + }; var builder = IntermediateNodeBuilder.Create(irDocument); var @namespace = new NamespaceDeclarationIntermediateNode() { Content = string.Empty }; @namespace.Annotations[CommonAnnotations.PrimaryNamespace] = CommonAnnotations.PrimaryNamespace; @@ -53,7 +60,11 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions public void Execute_NoOps_IfClassNameNodeIsMissing() { // Arrange - var irDocument = new DocumentIntermediateNode(); + var irDocument = new DocumentIntermediateNode() + { + Options = RazorCodeGenerationOptions.CreateDefault(), + }; + var builder = IntermediateNodeBuilder.Create(irDocument); var @namespace = new NamespaceDeclarationIntermediateNode() { Content = "SomeNamespace" }; builder.Push(@namespace); @@ -67,7 +78,8 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions pass.Execute(TestRazorCodeDocument.CreateEmpty(), irDocument); // Assert - Assert.Collection(irDocument.Children, + Assert.Collection( + irDocument.Children, node => Assert.Same(@namespace, node)); } @@ -75,7 +87,10 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions public void Execute_NoOps_IfClassNameIsEmpty() { // Arrange - var irDocument = new DocumentIntermediateNode(); + var irDocument = new DocumentIntermediateNode() + { + Options = RazorCodeGenerationOptions.CreateDefault(), + }; var builder = IntermediateNodeBuilder.Create(irDocument); var @namespace = new NamespaceDeclarationIntermediateNode { @@ -115,6 +130,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions var irDocument = new DocumentIntermediateNode { DocumentKind = "Default", + Options = RazorCodeGenerationOptions.CreateDefault(), }; var builder = IntermediateNodeBuilder.Create(irDocument); var @namespace = new NamespaceDeclarationIntermediateNode() { Content = "SomeNamespace" }; @@ -138,7 +154,54 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions pass.Execute(TestRazorCodeDocument.CreateEmpty(), irDocument); // Assert - Assert.Collection(irDocument.Children, + Assert.Collection( + irDocument.Children, + node => Assert.Same(@namespace, node)); + } + + [Fact] + public void Execute_NoOps_ForDesignTime() + { + // Arrange + var irDocument = new DocumentIntermediateNode + { + DocumentKind = MvcViewDocumentClassifierPass.MvcViewDocumentKind, + Options = RazorCodeGenerationOptions.CreateDesignTimeDefault(), + }; + var builder = IntermediateNodeBuilder.Create(irDocument); + var @namespace = new NamespaceDeclarationIntermediateNode + { + Content = "SomeNamespace", + Annotations = + { + [CommonAnnotations.PrimaryNamespace] = CommonAnnotations.PrimaryNamespace + }, + }; + builder.Push(@namespace); + var @class = new ClassDeclarationIntermediateNode + { + ClassName = "SomeName", + Annotations = + { + [CommonAnnotations.PrimaryClass] = CommonAnnotations.PrimaryClass, + }, + }; + builder.Add(@class); + + var pass = new AssemblyAttributeInjectionPass + { + Engine = RazorEngine.Create(), + }; + + var source = TestRazorSourceDocument.Create("test", new RazorSourceDocumentProperties(filePath: null, relativePath: "/Views/Index.cshtml")); + var document = RazorCodeDocument.Create(source); + + // Act + pass.Execute(document, irDocument); + + // Assert + Assert.Collection( + irDocument.Children, node => Assert.Same(@namespace, node)); } @@ -150,6 +213,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions var irDocument = new DocumentIntermediateNode { DocumentKind = MvcViewDocumentClassifierPass.MvcViewDocumentKind, + Options = RazorCodeGenerationOptions.CreateDefault(), }; var builder = IntermediateNodeBuilder.Create(irDocument); var @namespace = new NamespaceDeclarationIntermediateNode @@ -202,6 +266,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions var irDocument = new DocumentIntermediateNode { DocumentKind = MvcViewDocumentClassifierPass.MvcViewDocumentKind, + Options = RazorCodeGenerationOptions.CreateDefault(), }; var builder = IntermediateNodeBuilder.Create(irDocument); var @namespace = new NamespaceDeclarationIntermediateNode @@ -254,6 +319,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions var irDocument = new DocumentIntermediateNode { DocumentKind = RazorPageDocumentClassifierPass.RazorPageDocumentKind, + Options = RazorCodeGenerationOptions.CreateDefault(), }; var builder = IntermediateNodeBuilder.Create(irDocument); var pageDirective = new DirectiveIntermediateNode @@ -313,6 +379,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions var irDocument = new DocumentIntermediateNode { DocumentKind = MvcViewDocumentClassifierPass.MvcViewDocumentKind, + Options = RazorCodeGenerationOptions.CreateDefault(), }; var builder = IntermediateNodeBuilder.Create(irDocument); var @namespace = new NamespaceDeclarationIntermediateNode diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/DefaultMvcImportFeatureTest.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/DefaultMvcImportFeatureTest.cs index 3ae6d8dd13..ccd59408f9 100644 --- a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/DefaultMvcImportFeatureTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/DefaultMvcImportFeatureTest.cs @@ -17,7 +17,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions var imports = new List(); // Act - DefaultMvcImportFeature.AddDefaultDirectivesImport(imports); + MvcImportProjectFeature.AddDefaultDirectivesImport(imports); // Assert var import = Assert.Single(imports); @@ -37,7 +37,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions new TestRazorProjectItem("/Contact/_ViewImports.cshtml"), projectItem, }); - var mvcImportFeature = new DefaultMvcImportFeature() + var mvcImportFeature = new MvcImportProjectFeature() { ProjectEngine = Mock.Of(projectEngine => projectEngine.FileSystem == testFileSystem) }; @@ -58,7 +58,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions var imports = new List(); var projectItem = new TestRazorProjectItem("/Pages/Contact/Index.cshtml"); var testFileSystem = new TestRazorProjectFileSystem(new[] { projectItem }); - var mvcImportFeature = new DefaultMvcImportFeature() + var mvcImportFeature = new MvcImportProjectFeature() { ProjectEngine = Mock.Of(projectEngine => projectEngine.FileSystem == testFileSystem) }; diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/InstrumentationPassTest.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/InstrumentationPassTest.cs index 0827042ac4..d59d1ad06b 100644 --- a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/InstrumentationPassTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/InstrumentationPassTest.cs @@ -10,11 +10,47 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions { public class InstrumentationPassTest { + [Fact] + public void InstrumentationPass_NoOps_ForDesignTime() + { + // Arrange + var document = new DocumentIntermediateNode() + { + Options = RazorCodeGenerationOptions.CreateDesignTimeDefault(), + }; + + var builder = IntermediateNodeBuilder.Create(document); + builder.Push(new HtmlContentIntermediateNode()); + builder.Add(new IntermediateToken() + { + Content = "Hi", + Kind = TokenKind.Html, + }); + builder.Pop(); + + var pass = new InstrumentationPass() + { + Engine = RazorEngine.CreateEmpty(b => { }), + }; + + // Act + pass.Execute(TestRazorCodeDocument.CreateEmpty(), document); + + // Assert + Children( + document, + n => IntermediateNodeAssert.Html("Hi", n)); + } + [Fact] public void InstrumentationPass_InstrumentsHtml() { // Arrange - var document = new DocumentIntermediateNode(); + var document = new DocumentIntermediateNode() + { + Options = RazorCodeGenerationOptions.CreateDefault(), + }; + var builder = IntermediateNodeBuilder.Create(document); builder.Push(new HtmlContentIntermediateNode() @@ -49,7 +85,11 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions public void InstrumentationPass_SkipsHtml_WithoutLocation() { // Arrange - var document = new DocumentIntermediateNode(); + var document = new DocumentIntermediateNode() + { + Options = RazorCodeGenerationOptions.CreateDefault(), + }; + var builder = IntermediateNodeBuilder.Create(document); builder.Push(new HtmlContentIntermediateNode()); builder.Add(new IntermediateToken() @@ -77,7 +117,11 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions public void InstrumentationPass_InstrumentsCSharpExpression() { // Arrange - var document = new DocumentIntermediateNode(); + var document = new DocumentIntermediateNode() + { + Options = RazorCodeGenerationOptions.CreateDefault(), + }; + var builder = IntermediateNodeBuilder.Create(document); builder.Push(new CSharpExpressionIntermediateNode() { @@ -109,7 +153,11 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions public void InstrumentationPass_SkipsCSharpExpression_WithoutLocation() { // Arrange - var document = new DocumentIntermediateNode(); + var document = new DocumentIntermediateNode() + { + Options = RazorCodeGenerationOptions.CreateDefault(), + }; + var builder = IntermediateNodeBuilder.Create(document); builder.Push(new CSharpExpressionIntermediateNode()); builder.Add(new IntermediateToken() @@ -136,7 +184,11 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions public void InstrumentationPass_SkipsCSharpExpression_InsideTagHelperAttribute() { // Arrange - var document = new DocumentIntermediateNode(); + var document = new DocumentIntermediateNode() + { + Options = RazorCodeGenerationOptions.CreateDefault(), + }; + var builder = IntermediateNodeBuilder.Create(document); builder.Push(new TagHelperIntermediateNode()); @@ -183,7 +235,11 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions public void InstrumentationPass_SkipsCSharpExpression_InsideTagHelperProperty() { // Arrange - var document = new DocumentIntermediateNode(); + var document = new DocumentIntermediateNode() + { + Options = RazorCodeGenerationOptions.CreateDefault(), + }; + var builder = IntermediateNodeBuilder.Create(document); builder.Push(new TagHelperIntermediateNode()); @@ -230,7 +286,11 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions public void InstrumentationPass_InstrumentsTagHelper() { // Arrange - var document = new DocumentIntermediateNode(); + var document = new DocumentIntermediateNode() + { + Options = RazorCodeGenerationOptions.CreateDefault(), + }; + var builder = IntermediateNodeBuilder.Create(document); builder.Add(new TagHelperIntermediateNode() { @@ -257,7 +317,11 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions public void InstrumentationPass_SkipsTagHelper_WithoutLocation() { // Arrange - var document = new DocumentIntermediateNode(); + var document = new DocumentIntermediateNode() + { + Options = RazorCodeGenerationOptions.CreateDefault(), + }; + var builder = IntermediateNodeBuilder.Create(document); builder.Push(new TagHelperIntermediateNode()); diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/ModelDirectiveTest.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/ModelDirectiveTest.cs index fcf8bc2422..f6466520af 100644 --- a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/ModelDirectiveTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/ModelDirectiveTest.cs @@ -61,7 +61,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions "); var engine = CreateEngine(); - var pass = new ModelDirective.Pass(designTime: false) + var pass = new ModelDirective.Pass() { Engine = engine, }; @@ -88,7 +88,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions "); var engine = CreateEngine(); - var pass = new ModelDirective.Pass(designTime: false) + var pass = new ModelDirective.Pass() { Engine = engine, }; @@ -114,7 +114,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions "); var engine = CreateEngine(); - var pass = new ModelDirective.Pass(designTime: false) + var pass = new ModelDirective.Pass() { Engine = engine, }; @@ -139,7 +139,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions "); var engine = CreateEngine(); - var pass = new ModelDirective.Pass(designTime: false) + var pass = new ModelDirective.Pass() { Engine = engine, }; @@ -163,8 +163,8 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions @inherits BaseType "); - var engine = CreateEngine(); - var pass = new ModelDirective.Pass(designTime: true) + var engine = CreateDesignTimeEngine(); + var pass = new ModelDirective.Pass() { Engine = engine, }; @@ -193,8 +193,8 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions @model SomeType "); - var engine = CreateEngine(); - var pass = new ModelDirective.Pass(designTime: true) + var engine = CreateDesignTimeEngine(); + var pass = new ModelDirective.Pass() { Engine = engine, }; @@ -246,6 +246,18 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions }); } + private RazorEngine CreateDesignTimeEngine() + { + return RazorEngine.CreateDesignTime(b => + { + // Notice we're not registering the ModelDirective.Pass here so we can run it on demand. + b.AddDirective(ModelDirective.Directive); + + // There's some special interaction with the inherits directive + InheritsDirective.Register(b); + }); + } + private DocumentIntermediateNode CreateIRDocument(RazorEngine engine, RazorCodeDocument codeDocument) { for (var i = 0; i < engine.Phases.Count; i++) diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X.Test/DefaultMvcImportFeatureTest.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X.Test/DefaultMvcImportFeatureTest.cs index 3741760b4a..680ab9ba1e 100644 --- a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X.Test/DefaultMvcImportFeatureTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X.Test/DefaultMvcImportFeatureTest.cs @@ -17,7 +17,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X var imports = new List(); // Act - DefaultMvcImportFeature.AddDefaultDirectivesImport(imports); + MvcImportProjectFeature.AddDefaultDirectivesImport(imports); // Assert var import = Assert.Single(imports); @@ -37,7 +37,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X new TestRazorProjectItem("/Contact/_ViewImports.cshtml"), projectItem, }); - var mvcImportFeature = new DefaultMvcImportFeature() + var mvcImportFeature = new MvcImportProjectFeature() { ProjectEngine = Mock.Of(projectEngine => projectEngine.FileSystem == testFileSystem) }; @@ -58,7 +58,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X var imports = new List(); var projectItem = new TestRazorProjectItem("/Pages/Contact/Index.cshtml"); var testFileSystem = new TestRazorProjectFileSystem(new[] { projectItem }); - var mvcImportFeature = new DefaultMvcImportFeature() + var mvcImportFeature = new MvcImportProjectFeature() { ProjectEngine = Mock.Of(projectEngine => projectEngine.FileSystem == testFileSystem) }; diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X.Test/ModelDirectiveTest.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X.Test/ModelDirectiveTest.cs index c366918999..c0319a9a0a 100644 --- a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X.Test/ModelDirectiveTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X.Test/ModelDirectiveTest.cs @@ -61,7 +61,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X "); var engine = CreateEngine(); - var pass = new ModelDirective.Pass(designTime: false) + var pass = new ModelDirective.Pass() { Engine = engine, }; @@ -88,7 +88,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X "); var engine = CreateEngine(); - var pass = new ModelDirective.Pass(designTime: false) + var pass = new ModelDirective.Pass() { Engine = engine, }; @@ -114,7 +114,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X "); var engine = CreateEngine(); - var pass = new ModelDirective.Pass(designTime: false) + var pass = new ModelDirective.Pass() { Engine = engine, }; @@ -139,7 +139,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X "); var engine = CreateEngine(); - var pass = new ModelDirective.Pass(designTime: false) + var pass = new ModelDirective.Pass() { Engine = engine, }; @@ -163,8 +163,8 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X @inherits BaseType "); - var engine = CreateEngine(); - var pass = new ModelDirective.Pass(designTime: true) + var engine = CreateDesignTimeEngine(); + var pass = new ModelDirective.Pass() { Engine = engine, }; @@ -193,8 +193,8 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X @model SomeType "); - var engine = CreateEngine(); - var pass = new ModelDirective.Pass(designTime: true) + var engine = CreateDesignTimeEngine(); + var pass = new ModelDirective.Pass() { Engine = engine, }; @@ -246,6 +246,18 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X }); } + private RazorEngine CreateDesignTimeEngine() + { + return RazorEngine.CreateDesignTime(b => + { + // Notice we're not registering the ModelDirective.Pass here so we can run it on demand. + b.AddDirective(ModelDirective.Directive); + + // There's some special interaction with the inherits directive + InheritsDirective.Register(b); + }); + } + private DocumentIntermediateNode CreateIRDocument(RazorEngine engine, RazorCodeDocument codeDocument) { for (var i = 0; i < engine.Phases.Count; i++) diff --git a/test/Microsoft.AspNetCore.Razor.Language.Test/DefaultRazorProjectEngineIntegrationTest.cs b/test/Microsoft.AspNetCore.Razor.Language.Test/DefaultRazorProjectEngineIntegrationTest.cs index e767978411..0edbd89311 100644 --- a/test/Microsoft.AspNetCore.Razor.Language.Test/DefaultRazorProjectEngineIntegrationTest.cs +++ b/test/Microsoft.AspNetCore.Razor.Language.Test/DefaultRazorProjectEngineIntegrationTest.cs @@ -8,16 +8,61 @@ namespace Microsoft.AspNetCore.Razor.Language { public class DefaultRazorProjectEngineIntegrationTest { + [Fact] + public void Process_SetsOptions_Runtime() + { + // Arrange + var projectItem = new TestRazorProjectItem("Index.cshtml"); + + var projectEngine = RazorProjectEngine.Create(RazorConfiguration.Default, TestRazorProjectFileSystem.Empty); + + // Act + var codeDocument = projectEngine.Process(projectItem); + + // Assert + var parserOptions = codeDocument.GetParserOptions(); + Assert.False(parserOptions.DesignTime); + + var codeGenerationOptions = codeDocument.GetCodeGenerationOptions(); + Assert.False(codeGenerationOptions.DesignTime); + Assert.False(codeGenerationOptions.SuppressChecksum); + Assert.False(codeGenerationOptions.SuppressMetadataAttributes); + } + + [Fact] + public void ProcessDesignTime_SetsOptions_DesignTime() + { + // Arrange + var projectItem = new TestRazorProjectItem("Index.cshtml"); + + var projectEngine = RazorProjectEngine.Create(RazorConfiguration.Default, TestRazorProjectFileSystem.Empty); + + // Act + var codeDocument = projectEngine.ProcessDesignTime(projectItem); + + // Assert + var parserOptions = codeDocument.GetParserOptions(); + Assert.True(parserOptions.DesignTime); + + var codeGenerationOptions = codeDocument.GetCodeGenerationOptions(); + Assert.True(codeGenerationOptions.DesignTime); + Assert.True(codeGenerationOptions.SuppressChecksum); + Assert.True(codeGenerationOptions.SuppressMetadataAttributes); + } + [Fact] public void Process_GetsImportsFromFeature() { // Arrange var projectItem = new TestRazorProjectItem("Index.cshtml"); + var testImport = TestRazorSourceDocument.Create(); - var importFeature = new Mock(); - importFeature.Setup(feature => feature.GetImports(It.IsAny())) + var importFeature = new Mock(); + importFeature + .Setup(feature => feature.GetImports(It.IsAny())) .Returns(new[] { testImport }); - var projectEngine = RazorProjectEngine.Create(TestRazorProjectFileSystem.Empty, builder => + + var projectEngine = RazorProjectEngine.Create(RazorConfiguration.Default, TestRazorProjectFileSystem.Empty, builder => { builder.SetImportFeature(importFeature.Object); }); @@ -35,7 +80,7 @@ namespace Microsoft.AspNetCore.Razor.Language { // Arrange var projectItem = new TestRazorProjectItem("Index.cshtml"); - var projectEngine = RazorProjectEngine.Create(TestRazorProjectFileSystem.Empty); + var projectEngine = RazorProjectEngine.Create(RazorConfiguration.Default, TestRazorProjectFileSystem.Empty); // Act var codeDocument = projectEngine.Process(projectItem); diff --git a/test/Microsoft.AspNetCore.Razor.Language.Test/Extensions/DefaultTagHelperTargetExtensionTest.cs b/test/Microsoft.AspNetCore.Razor.Language.Test/Extensions/DefaultTagHelperTargetExtensionTest.cs index 67084a4052..dcb8d6d6ff 100644 --- a/test/Microsoft.AspNetCore.Razor.Language.Test/Extensions/DefaultTagHelperTargetExtensionTest.cs +++ b/test/Microsoft.AspNetCore.Razor.Language.Test/Extensions/DefaultTagHelperTargetExtensionTest.cs @@ -68,7 +68,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions public void WriteTagHelperBody_DesignTime_WritesChildren() { // Arrange - var extension = new DefaultTagHelperTargetExtension() { DesignTime = true }; + var extension = new DefaultTagHelperTargetExtension(); var context = TestCodeRenderingContext.CreateDesignTime(); var tagHelperNode = new TagHelperIntermediateNode(); @@ -133,7 +133,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions public void WriteTagHelperCreate_DesignTime_RendersCorrectly_UsesSpecifiedTagHelperType() { // Arrange - var extension = new DefaultTagHelperTargetExtension() { DesignTime = true }; + var extension = new DefaultTagHelperTargetExtension(); var context = TestCodeRenderingContext.CreateDesignTime(); var tagHelperNode = new TagHelperIntermediateNode(); @@ -190,7 +190,7 @@ __tagHelperExecutionContext.Add(__TestNamespace_MyTagHelper); public void WriteTagHelperExecute_DesignTime_WritesNothing() { // Arrange - var extension = new DefaultTagHelperTargetExtension() { DesignTime = true }; + var extension = new DefaultTagHelperTargetExtension(); var context = TestCodeRenderingContext.CreateDesignTime(); var tagHelperNode = new TagHelperIntermediateNode(); @@ -243,7 +243,7 @@ __tagHelperExecutionContext = __tagHelperScopeManager.End(); public void WriteTagHelperHtmlAttribute_DesignTime_WritesNothing() { // Arrange - var extension = new DefaultTagHelperTargetExtension() { DesignTime = true }; + var extension = new DefaultTagHelperTargetExtension(); var context = TestCodeRenderingContext.CreateDesignTime(); var tagHelperNode = new TagHelperIntermediateNode(); @@ -431,7 +431,7 @@ EndAddHtmlAttributeValues(__tagHelperExecutionContext); public void WriteTagHelperProperty_DesignTime_StringProperty_HtmlContent_RendersCorrectly() { // Arrange - var extension = new DefaultTagHelperTargetExtension() { DesignTime = true }; + var extension = new DefaultTagHelperTargetExtension(); var context = TestCodeRenderingContext.CreateDesignTime(); var tagHelperNode = new TagHelperIntermediateNode(); @@ -472,7 +472,7 @@ __InputTagHelper.StringProp = ""value""; public void WriteTagHelperProperty_DesignTime_StringProperty_NonHtmlContent_RendersCorrectly() { // Arrange - var extension = new DefaultTagHelperTargetExtension() { DesignTime = true }; + var extension = new DefaultTagHelperTargetExtension(); var context = TestCodeRenderingContext.CreateDesignTime(); var tagHelperNode = new TagHelperIntermediateNode(); @@ -513,7 +513,7 @@ __InputTagHelper.StringProp = string.Empty; public void WriteTagHelperProperty_DesignTime_NonStringProperty_RendersCorrectly() { // Arrange - var extension = new DefaultTagHelperTargetExtension() { DesignTime = true }; + var extension = new DefaultTagHelperTargetExtension(); var context = TestCodeRenderingContext.CreateDesignTime(); var tagHelperNode = new TagHelperIntermediateNode(); @@ -560,7 +560,7 @@ __InputTagHelper.IntProp = 32; public void WriteTagHelperProperty_DesignTime_NonStringProperty_SecondUseOfAttribute() { // Arrange - var extension = new DefaultTagHelperTargetExtension() { DesignTime = true }; + var extension = new DefaultTagHelperTargetExtension(); var context = TestCodeRenderingContext.CreateDesignTime(); var tagHelperNode = new TagHelperIntermediateNode(); @@ -602,7 +602,7 @@ __InputTagHelper.IntProp = 32; public void WriteTagHelperProperty_DesignTime_NonStringProperty_RendersCorrectly_WithoutLocation() { // Arrange - var extension = new DefaultTagHelperTargetExtension() { DesignTime = true }; + var extension = new DefaultTagHelperTargetExtension(); var context = TestCodeRenderingContext.CreateDesignTime(); var tagHelperNode = new TagHelperIntermediateNode(); @@ -642,7 +642,7 @@ __InputTagHelper.IntProp = 32; public void WriteTagHelperProperty_DesignTime_NonStringIndexer_RendersCorrectly() { // Arrange - var extension = new DefaultTagHelperTargetExtension() { DesignTime = true }; + var extension = new DefaultTagHelperTargetExtension(); var context = TestCodeRenderingContext.CreateDesignTime(); var tagHelperNode = new TagHelperIntermediateNode(); @@ -687,7 +687,7 @@ __InputTagHelper.IntIndexer[""bound""] = 32; public void WriteTagHelperProperty_DesignTime_NonStringIndexer_RendersCorrectly_WithoutLocation() { // Arrange - var extension = new DefaultTagHelperTargetExtension() { DesignTime = true }; + var extension = new DefaultTagHelperTargetExtension(); var context = TestCodeRenderingContext.CreateDesignTime(); var tagHelperNode = new TagHelperIntermediateNode(); @@ -1064,7 +1064,7 @@ __tagHelperExecutionContext.AddTagHelperAttribute(""foo-bound"", __InputTagHelpe public void WriteTagHelperRuntime_DesignTime_WritesNothing() { // Arrange - var extension = new DefaultTagHelperTargetExtension() { DesignTime = true }; + var extension = new DefaultTagHelperTargetExtension(); var context = TestCodeRenderingContext.CreateDesignTime(); var node = new DefaultTagHelperRuntimeIntermediateNode(); diff --git a/test/Microsoft.AspNetCore.Razor.Language.Test/RazorCodeDocumentExtensionsTest.cs b/test/Microsoft.AspNetCore.Razor.Language.Test/RazorCodeDocumentExtensionsTest.cs index 14c6aa4453..41f899512b 100644 --- a/test/Microsoft.AspNetCore.Razor.Language.Test/RazorCodeDocumentExtensionsTest.cs +++ b/test/Microsoft.AspNetCore.Razor.Language.Test/RazorCodeDocumentExtensionsTest.cs @@ -148,5 +148,67 @@ namespace Microsoft.AspNetCore.Razor.Language // Assert Assert.Same(expected, codeDocument.Items[typeof(TagHelperDocumentContext)]); } + + [Fact] + public void GetParserOptions_ReturnsSyntaxTree() + { + // Arrange + var codeDocument = TestRazorCodeDocument.CreateEmpty(); + + var expected = RazorParserOptions.CreateDefault(); + codeDocument.Items[typeof(RazorParserOptions)] = expected; + + // Act + var actual = codeDocument.GetParserOptions(); + + // Assert + Assert.Same(expected, actual); + } + + [Fact] + public void SetParserOptions_SetsSyntaxTree() + { + // Arrange + var codeDocument = TestRazorCodeDocument.CreateEmpty(); + + var expected = RazorParserOptions.CreateDefault(); + + // Act + codeDocument.SetParserOptions(expected); + + // Assert + Assert.Same(expected, codeDocument.Items[typeof(RazorParserOptions)]); + } + + [Fact] + public void GetCodeGenerationOptions_ReturnsSyntaxTree() + { + // Arrange + var codeDocument = TestRazorCodeDocument.CreateEmpty(); + + var expected = RazorCodeGenerationOptions.CreateDefault(); + codeDocument.Items[typeof(RazorCodeGenerationOptions)] = expected; + + // Act + var actual = codeDocument.GetCodeGenerationOptions(); + + // Assert + Assert.Same(expected, actual); + } + + [Fact] + public void SetCodeGenerationOptions_SetsSyntaxTree() + { + // Arrange + var codeDocument = TestRazorCodeDocument.CreateEmpty(); + + var expected = RazorCodeGenerationOptions.CreateDefault(); + + // Act + codeDocument.SetCodeGenerationOptions(expected); + + // Assert + Assert.Same(expected, codeDocument.Items[typeof(RazorCodeGenerationOptions)]); + } } } diff --git a/test/Microsoft.AspNetCore.Razor.Language.Test/RazorEngineTest.cs b/test/Microsoft.AspNetCore.Razor.Language.Test/RazorEngineTest.cs index 50f9328b1b..424b5fddd4 100644 --- a/test/Microsoft.AspNetCore.Razor.Language.Test/RazorEngineTest.cs +++ b/test/Microsoft.AspNetCore.Razor.Language.Test/RazorEngineTest.cs @@ -145,7 +145,7 @@ namespace Microsoft.AspNetCore.Razor.Language Assert.Collection( feature.TargetExtensions, extension => Assert.IsType(extension), - extension => Assert.False(Assert.IsType(extension).DesignTime), + extension => Assert.IsType(extension), extension => Assert.IsType(extension)); } @@ -190,7 +190,7 @@ namespace Microsoft.AspNetCore.Razor.Language Assert.Collection( feature.TargetExtensions, extension => Assert.IsType(extension), - extension => Assert.True(Assert.IsType(extension).DesignTime), + extension => Assert.IsType(extension), extension => Assert.IsType(extension)); } diff --git a/test/Microsoft.AspNetCore.Razor.Language.Test/RazorProjectEngineBuilderExtensionsTest.cs b/test/Microsoft.AspNetCore.Razor.Language.Test/RazorProjectEngineBuilderExtensionsTest.cs index b3e91c29bc..f0a57128c3 100644 --- a/test/Microsoft.AspNetCore.Razor.Language.Test/RazorProjectEngineBuilderExtensionsTest.cs +++ b/test/Microsoft.AspNetCore.Razor.Language.Test/RazorProjectEngineBuilderExtensionsTest.cs @@ -14,11 +14,11 @@ namespace Microsoft.AspNetCore.Razor.Language { // Arrange var builder = new DefaultRazorProjectEngineBuilder(RazorConfiguration.Default, Mock.Of()); - var testFeature1 = Mock.Of(); - var testFeature2 = Mock.Of(); + var testFeature1 = Mock.Of(); + var testFeature2 = Mock.Of(); builder.Features.Add(testFeature1); builder.Features.Add(testFeature2); - var newFeature = Mock.Of(); + var newFeature = Mock.Of(); // Act builder.SetImportFeature(newFeature); diff --git a/test/Microsoft.AspNetCore.Razor.Language.Test/RazorProjectEngineTest.cs b/test/Microsoft.AspNetCore.Razor.Language.Test/RazorProjectEngineTest.cs new file mode 100644 index 0000000000..d901c16199 --- /dev/null +++ b/test/Microsoft.AspNetCore.Razor.Language.Test/RazorProjectEngineTest.cs @@ -0,0 +1,85 @@ +// 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.Language.Extensions; +using Moq; +using Xunit; + +namespace Microsoft.AspNetCore.Razor.Language.Test +{ + public class RazorProjectEngineTest + { + [Fact] + public void CreateDesignTime_Lambda_AddsFeaturesAndPhases() + { + // Arrange + + // Act + var engine = RazorProjectEngine.Create(RazorConfiguration.Default, Mock.Of()); + + // Assert + AssertDefaultPhases(engine); + AssertDefaultFeatures(engine); + AssertDefaultDirectives(engine); + AssertDefaultTargetExtensions(engine); + } + + private static void AssertDefaultPhases(RazorProjectEngine engine) + { + Assert.Collection( + engine.Phases, + phase => Assert.IsType(phase), + phase => Assert.IsType(phase), + phase => Assert.IsType(phase), + phase => Assert.IsType(phase), + phase => Assert.IsType(phase), + phase => Assert.IsType(phase), + phase => Assert.IsType(phase), + phase => Assert.IsType(phase)); + } + + private static void AssertDefaultFeatures(RazorProjectEngine engine) + { + var features = engine.EngineFeatures.OrderBy(f => f.GetType().Name).ToArray(); + Assert.Collection( + features, + feature => Assert.IsType(feature), + feature => Assert.IsType(feature), + feature => Assert.IsType(feature), + feature => Assert.IsType(feature), + feature => Assert.IsType(feature), + feature => Assert.IsType(feature), + feature => Assert.IsType(feature), + feature => Assert.IsType(feature), + feature => Assert.IsType(feature), + feature => Assert.IsType(feature), + feature => Assert.IsType(feature), + feature => Assert.IsType(feature), + feature => Assert.IsType(feature), + feature => Assert.IsType(feature)); + } + + private static void AssertDefaultDirectives(RazorProjectEngine engine) + { + var feature = engine.EngineFeatures.OfType().FirstOrDefault(); + Assert.NotNull(feature); + Assert.Empty(feature.Directives); + } + + private static void AssertDefaultTargetExtensions(RazorProjectEngine engine) + { + var feature = engine.EngineFeatures.OfType().FirstOrDefault(); + Assert.NotNull(feature); + + var extensions = feature.TargetExtensions.OrderBy(f => f.GetType().Name).ToArray(); + Assert.Collection( + extensions, + extension => Assert.IsType(extension), + extension => Assert.IsType(extension), + extension => Assert.IsType(extension), + extension => Assert.IsType(extension)); + } + } +}