diff --git a/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorImportFeature.cs b/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorImportFeature.cs new file mode 100644 index 0000000000..ed25827c62 --- /dev/null +++ b/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorImportFeature.cs @@ -0,0 +1,15 @@ +// 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 +{ + internal class DefaultRazorImportFeature : IRazorImportFeature + { + public RazorProjectEngine ProjectEngine { get; set; } + + public IReadOnlyList GetImports(string sourceFilePath) => Array.Empty(); + } +} diff --git a/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorProjectEngine.cs b/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorProjectEngine.cs new file mode 100644 index 0000000000..8f3017cea1 --- /dev/null +++ b/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorProjectEngine.cs @@ -0,0 +1,93 @@ +// 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 +{ + internal class DefaultRazorProjectEngine : RazorProjectEngine + { + public DefaultRazorProjectEngine( + RazorEngine engine, + RazorProjectFileSystem fileSystem, + IReadOnlyList features) + { + if (engine == null) + { + throw new ArgumentNullException(nameof(engine)); + } + + if (fileSystem == null) + { + throw new ArgumentNullException(nameof(fileSystem)); + } + + if (features == null) + { + throw new ArgumentNullException(nameof(features)); + } + + Engine = engine; + FileSystem = fileSystem; + Features = features; + + for (var i = 0; i < features.Count; i++) + { + features[i].ProjectEngine = this; + } + } + + public override RazorProjectFileSystem FileSystem { get; } + + public override RazorEngine Engine { get; } + + public override IReadOnlyList Features { get; } + + public override RazorCodeDocument Process(string filePath) + { + if (string.IsNullOrEmpty(filePath)) + { + throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(filePath)); + } + + var projectItem = FileSystem.GetItem(filePath); + var sourceDocument = RazorSourceDocument.ReadFrom(projectItem); + var codeDocument = Process(sourceDocument); + + return codeDocument; + } + + public override RazorCodeDocument Process(RazorSourceDocument sourceDocument) + { + if (sourceDocument == null) + { + throw new ArgumentNullException(nameof(sourceDocument)); + } + + var importFeature = GetRequiredFeature(); + var imports = importFeature.GetImports(sourceDocument.FilePath); + + var codeDocument = RazorCodeDocument.Create(sourceDocument, imports); + + Engine.Process(codeDocument); + + return codeDocument; + } + + private TFeature GetRequiredFeature() where TFeature : IRazorProjectEngineFeature + { + var feature = Features.OfType().FirstOrDefault(); + if (feature == null) + { + throw new InvalidOperationException( + Resources.FormatRazorProjectEngineMissingFeatureDependency( + typeof(RazorProjectEngine).FullName, + typeof(TFeature).FullName)); + } + + return feature; + } + } +} diff --git a/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorProjectEngineBuilder.cs b/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorProjectEngineBuilder.cs new file mode 100644 index 0000000000..33c300cdb1 --- /dev/null +++ b/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorProjectEngineBuilder.cs @@ -0,0 +1,66 @@ +// 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 +{ + internal class DefaultRazorProjectEngineBuilder : RazorProjectEngineBuilder + { + public DefaultRazorProjectEngineBuilder(bool designTime, RazorProjectFileSystem fileSystem) + { + if (fileSystem == null) + { + throw new ArgumentNullException(nameof(fileSystem)); + } + + DesignTime = designTime; + FileSystem = fileSystem; + Features = new List(); + Phases = new List(); + } + + public override RazorProjectFileSystem FileSystem { get; } + + public override ICollection Features { get; } + + public override IList Phases { get; } + + public override bool DesignTime { get; } + + public override RazorProjectEngine Build() + { + RazorEngine engine = null; + + if (DesignTime) + { + engine = RazorEngine.CreateDesignTimeEmpty(ConfigureRazorEngine); + } + else + { + engine = RazorEngine.CreateEmpty(ConfigureRazorEngine); + } + + var projectEngineFeatures = Features.OfType().ToArray(); + var projectEngine = new DefaultRazorProjectEngine(engine, FileSystem, projectEngineFeatures); + + return projectEngine; + } + + private void ConfigureRazorEngine(IRazorEngineBuilder engineBuilder) + { + var engineFeatures = Features.OfType(); + foreach (var engineFeature in engineFeatures) + { + engineBuilder.Features.Add(engineFeature); + } + + for (var i = 0; i < Phases.Count; i++) + { + engineBuilder.Phases.Add(Phases[i]); + } + } + } +} diff --git a/src/Microsoft.AspNetCore.Razor.Language/IRazorEngineFeature.cs b/src/Microsoft.AspNetCore.Razor.Language/IRazorEngineFeature.cs index dcf3efdf8a..e06f3a65ce 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/IRazorEngineFeature.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/IRazorEngineFeature.cs @@ -3,7 +3,7 @@ namespace Microsoft.AspNetCore.Razor.Language { - public interface IRazorEngineFeature + public interface IRazorEngineFeature : IRazorFeature { RazorEngine Engine { get; set; } } diff --git a/src/Microsoft.AspNetCore.Razor.Language/IRazorFeature.cs b/src/Microsoft.AspNetCore.Razor.Language/IRazorFeature.cs new file mode 100644 index 0000000000..4b8d602601 --- /dev/null +++ b/src/Microsoft.AspNetCore.Razor.Language/IRazorFeature.cs @@ -0,0 +1,9 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Microsoft.AspNetCore.Razor.Language +{ + public interface IRazorFeature + { + } +} diff --git a/src/Microsoft.AspNetCore.Razor.Language/IRazorImportFeature.cs b/src/Microsoft.AspNetCore.Razor.Language/IRazorImportFeature.cs new file mode 100644 index 0000000000..76e50c31c7 --- /dev/null +++ b/src/Microsoft.AspNetCore.Razor.Language/IRazorImportFeature.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.Collections.Generic; + +namespace Microsoft.AspNetCore.Razor.Language +{ + public interface IRazorImportFeature : IRazorProjectEngineFeature + { + IReadOnlyList GetImports(string sourceFilePath); + } +} diff --git a/src/Microsoft.AspNetCore.Razor.Language/IRazorProjectEngineFeature.cs b/src/Microsoft.AspNetCore.Razor.Language/IRazorProjectEngineFeature.cs new file mode 100644 index 0000000000..3b97dbefd2 --- /dev/null +++ b/src/Microsoft.AspNetCore.Razor.Language/IRazorProjectEngineFeature.cs @@ -0,0 +1,10 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Microsoft.AspNetCore.Razor.Language +{ + public interface IRazorProjectEngineFeature : IRazorFeature + { + RazorProjectEngine ProjectEngine { get; set; } + } +} diff --git a/src/Microsoft.AspNetCore.Razor.Language/Properties/Resources.Designer.cs b/src/Microsoft.AspNetCore.Razor.Language/Properties/Resources.Designer.cs index 4c0119da51..e1b32b7613 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/Properties/Resources.Designer.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/Properties/Resources.Designer.cs @@ -1856,6 +1856,20 @@ namespace Microsoft.AspNetCore.Razor.Language internal static string FormatPropertyMustNotBeNull(object p0, object p1) => string.Format(CultureInfo.CurrentCulture, GetString("PropertyMustNotBeNull"), p0, p1); + /// + /// The '{0}' is missing feature '{1}'. + /// + internal static string RazorProjectEngineMissingFeatureDependency + { + get => GetString("RazorProjectEngineMissingFeatureDependency"); + } + + /// + /// The '{0}' is missing feature '{1}'. + /// + internal static string FormatRazorProjectEngineMissingFeatureDependency(object p0, object p1) + => string.Format(CultureInfo.CurrentCulture, GetString("RazorProjectEngineMissingFeatureDependency"), p0, p1); + private static string GetString(string name, params string[] formatterNames) { var value = _resourceManager.GetString(name); diff --git a/src/Microsoft.AspNetCore.Razor.Language/RazorEngine.cs b/src/Microsoft.AspNetCore.Razor.Language/RazorEngine.cs index 7ee23828c4..9a3c5da8a2 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/RazorEngine.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/RazorEngine.cs @@ -3,6 +3,8 @@ using System; using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Razor.Language.Extensions; @@ -19,7 +21,7 @@ namespace Microsoft.AspNetCore.Razor.Language { var builder = new DefaultRazorEngineBuilder(designTime: false); AddDefaults(builder); - AddRuntimeDefaults(builder); + AddDefaultRuntimeFeatures(builder.Features); configure?.Invoke(builder); return builder.Build(); } @@ -33,46 +35,73 @@ namespace Microsoft.AspNetCore.Razor.Language { var builder = new DefaultRazorEngineBuilder(designTime: true); AddDefaults(builder); - AddDesignTimeDefaults(builder); + AddDefaultDesignTimeFeatures(builder.Features); configure?.Invoke(builder); return builder.Build(); } public static RazorEngine CreateEmpty(Action configure) { + if (configure == null) + { + throw new ArgumentNullException(nameof(configure)); + } + var builder = new DefaultRazorEngineBuilder(designTime: false); - configure?.Invoke(builder); + configure(builder); + return builder.Build(); + } + + public static RazorEngine CreateDesignTimeEmpty(Action configure) + { + if (configure == null) + { + throw new ArgumentNullException(nameof(configure)); + } + + var builder = new DefaultRazorEngineBuilder(designTime: true); + configure(builder); return builder.Build(); } internal static void AddDefaults(IRazorEngineBuilder builder) { - builder.Phases.Add(new DefaultRazorParsingPhase()); - builder.Phases.Add(new DefaultRazorSyntaxTreePhase()); - builder.Phases.Add(new DefaultRazorTagHelperBinderPhase()); - builder.Phases.Add(new DefaultRazorIntermediateNodeLoweringPhase()); - builder.Phases.Add(new DefaultRazorDocumentClassifierPhase()); - builder.Phases.Add(new DefaultRazorDirectiveClassifierPhase()); - builder.Phases.Add(new DefaultRazorOptimizationPhase()); - builder.Phases.Add(new DefaultRazorCSharpLoweringPhase()); + AddDefaultPhases(builder.Phases); + AddDefaultFeatures(builder.Features); + } + internal static void AddDefaultPhases(IList phases) + { + phases.Add(new DefaultRazorParsingPhase()); + phases.Add(new DefaultRazorSyntaxTreePhase()); + phases.Add(new DefaultRazorTagHelperBinderPhase()); + phases.Add(new DefaultRazorIntermediateNodeLoweringPhase()); + phases.Add(new DefaultRazorDocumentClassifierPhase()); + phases.Add(new DefaultRazorDirectiveClassifierPhase()); + phases.Add(new DefaultRazorOptimizationPhase()); + phases.Add(new DefaultRazorCSharpLoweringPhase()); + } + + internal static void AddDefaultFeatures(ICollection features) + { // General extensibility - builder.Features.Add(new DefaultRazorDirectiveFeature()); - builder.Features.Add(new DefaultRazorTargetExtensionFeature()); - builder.Features.Add(new DefaultMetadataIdentifierFeature()); + features.Add(new DefaultRazorDirectiveFeature()); + var targetExtensionFeature = new DefaultRazorTargetExtensionFeature(); + features.Add(targetExtensionFeature); + features.Add(new DefaultMetadataIdentifierFeature()); // Syntax Tree passes - builder.Features.Add(new DefaultDirectiveSyntaxTreePass()); - builder.Features.Add(new HtmlNodeOptimizationPass()); + features.Add(new DefaultDirectiveSyntaxTreePass()); + features.Add(new HtmlNodeOptimizationPass()); // Intermediate Node Passes - builder.Features.Add(new DefaultDocumentClassifierPass()); - builder.Features.Add(new MetadataAttributePass()); - builder.Features.Add(new DirectiveRemovalOptimizationPass()); - builder.Features.Add(new DefaultTagHelperOptimizationPass()); + features.Add(new DefaultDocumentClassifierPass()); + features.Add(new MetadataAttributePass()); + features.Add(new DirectiveRemovalOptimizationPass()); + features.Add(new DefaultTagHelperOptimizationPass()); // Default Code Target Extensions - builder.AddTargetExtension(new MetadataAttributeTargetExtension()); + targetExtensionFeature.TargetExtensions.Add(new MetadataAttributeTargetExtension()); // Default configuration var configurationFeature = new DefaultDocumentClassifierPassFeature(); @@ -97,36 +126,42 @@ namespace Microsoft.AspNetCore.Razor.Language method.Modifiers.Add("override"); }); - builder.Features.Add(configurationFeature); + features.Add(configurationFeature); } - internal static void AddRuntimeDefaults(IRazorEngineBuilder builder) + internal static void AddDefaultRuntimeFeatures(ICollection features) { // Configure options - builder.Features.Add(new DefaultRazorParserOptionsFeature(designTime: false, version: RazorParserOptions.LatestRazorLanguageVersion)); - builder.Features.Add(new DefaultRazorCodeGenerationOptionsFeature(designTime: false)); + features.Add(new DefaultRazorParserOptionsFeature(designTime: false, version: RazorParserOptions.LatestRazorLanguageVersion)); + features.Add(new DefaultRazorCodeGenerationOptionsFeature(designTime: false)); // Intermediate Node Passes - builder.Features.Add(new PreallocatedTagHelperAttributeOptimizationPass()); + features.Add(new PreallocatedTagHelperAttributeOptimizationPass()); // Code Target Extensions - builder.AddTargetExtension(new DefaultTagHelperTargetExtension() { DesignTime = false }); - builder.AddTargetExtension(new PreallocatedAttributeTargetExtension()); + var targetExtension = features.OfType().FirstOrDefault(); + Debug.Assert(targetExtension != null); + + targetExtension.TargetExtensions.Add(new DefaultTagHelperTargetExtension() { DesignTime = false }); + targetExtension.TargetExtensions.Add(new PreallocatedAttributeTargetExtension()); } - internal static void AddDesignTimeDefaults(IRazorEngineBuilder builder) + internal static void AddDefaultDesignTimeFeatures(ICollection features) { // Configure options - builder.Features.Add(new DefaultRazorParserOptionsFeature(designTime: true, version: RazorParserOptions.LatestRazorLanguageVersion)); - builder.Features.Add(new DefaultRazorCodeGenerationOptionsFeature(designTime: true)); - builder.Features.Add(new SuppressChecksumOptionsFeature()); + features.Add(new DefaultRazorParserOptionsFeature(designTime: true, version: RazorParserOptions.LatestRazorLanguageVersion)); + features.Add(new DefaultRazorCodeGenerationOptionsFeature(designTime: true)); + features.Add(new SuppressChecksumOptionsFeature()); // Intermediate Node Passes - builder.Features.Add(new DesignTimeDirectivePass()); + features.Add(new DesignTimeDirectivePass()); // Code Target Extensions - builder.AddTargetExtension(new DefaultTagHelperTargetExtension() { DesignTime = true }); - builder.AddTargetExtension(new DesignTimeDirectiveTargetExtension()); + var targetExtension = features.OfType().FirstOrDefault(); + Debug.Assert(targetExtension != null); + + targetExtension.TargetExtensions.Add(new DefaultTagHelperTargetExtension() { DesignTime = true }); + targetExtension.TargetExtensions.Add(new DesignTimeDirectiveTargetExtension()); } public abstract IReadOnlyList Features { get; } diff --git a/src/Microsoft.AspNetCore.Razor.Language/RazorProjectEngine.cs b/src/Microsoft.AspNetCore.Razor.Language/RazorProjectEngine.cs new file mode 100644 index 0000000000..294dad449a --- /dev/null +++ b/src/Microsoft.AspNetCore.Razor.Language/RazorProjectEngine.cs @@ -0,0 +1,129 @@ +// 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 RazorProjectEngine + { + public abstract RazorProjectFileSystem FileSystem { get; } + + public abstract RazorEngine Engine { get; } + + public abstract IReadOnlyList Features { get; } + + public abstract RazorCodeDocument Process(string filePath); + + public abstract RazorCodeDocument Process(RazorSourceDocument sourceDocument); + + public static RazorProjectEngine Create(RazorProjectFileSystem fileSystem) => Create(fileSystem, configure: null); + + public static RazorProjectEngine Create(RazorProjectFileSystem fileSystem, Action configure) + { + if (fileSystem == null) + { + throw new ArgumentNullException(nameof(fileSystem)); + } + + var builder = new DefaultRazorProjectEngineBuilder(designTime: false, fileSystem: fileSystem); + + AddDefaults(builder); + AddRuntimeDefaults(builder); + configure?.Invoke(builder); + + return builder.Build(); + } + + public static RazorProjectEngine CreateDesignTime(RazorProjectFileSystem fileSystem) => CreateDesignTime(fileSystem, configure: null); + + public static RazorProjectEngine CreateDesignTime(RazorProjectFileSystem fileSystem, Action configure) + { + if (fileSystem == null) + { + throw new ArgumentNullException(nameof(fileSystem)); + } + + var builder = new DefaultRazorProjectEngineBuilder(designTime: true, fileSystem: fileSystem); + + AddDefaults(builder); + AddDesignTimeDefaults(builder); + configure?.Invoke(builder); + + return builder.Build(); + } + + public static RazorProjectEngine CreateEmpty(RazorProjectFileSystem fileSystem, Action configure) + { + if (fileSystem == null) + { + throw new ArgumentNullException(nameof(fileSystem)); + } + + if (configure == null) + { + throw new ArgumentNullException(nameof(configure)); + } + + var builder = new DefaultRazorProjectEngineBuilder(designTime: false, fileSystem: fileSystem); + + configure(builder); + + return builder.Build(); + } + + public static RazorProjectEngine CreateDesignTimeEmpty(RazorProjectFileSystem fileSystem, Action configure) + { + if (fileSystem == null) + { + throw new ArgumentNullException(nameof(fileSystem)); + } + + if (configure == null) + { + throw new ArgumentNullException(nameof(configure)); + } + + var builder = new DefaultRazorProjectEngineBuilder(designTime: true, fileSystem: fileSystem); + + configure(builder); + + return builder.Build(); + } + + private static void AddDefaults(RazorProjectEngineBuilder builder) + { + builder.Features.Add(new DefaultRazorImportFeature()); + } + + private static void AddDesignTimeDefaults(RazorProjectEngineBuilder builder) + { + var engineFeatures = new List(); + RazorEngine.AddDefaultFeatures(engineFeatures); + RazorEngine.AddDefaultDesignTimeFeatures(engineFeatures); + + AddEngineFeaturesAndPhases(builder, engineFeatures); + } + + private static void AddRuntimeDefaults(RazorProjectEngineBuilder builder) + { + var engineFeatures = new List(); + RazorEngine.AddDefaultFeatures(engineFeatures); + RazorEngine.AddDefaultRuntimeFeatures(engineFeatures); + + AddEngineFeaturesAndPhases(builder, engineFeatures); + } + + private static void AddEngineFeaturesAndPhases(RazorProjectEngineBuilder builder, IReadOnlyList engineFeatures) + { + for (var i = 0; i < engineFeatures.Count; i++) + { + var engineFeature = engineFeatures[i]; + builder.Features.Add(engineFeature); + } + + RazorEngine.AddDefaultPhases(builder.Phases); + } + } +} diff --git a/src/Microsoft.AspNetCore.Razor.Language/RazorProjectEngineBuilder.cs b/src/Microsoft.AspNetCore.Razor.Language/RazorProjectEngineBuilder.cs new file mode 100644 index 0000000000..6f9c3be10c --- /dev/null +++ b/src/Microsoft.AspNetCore.Razor.Language/RazorProjectEngineBuilder.cs @@ -0,0 +1,20 @@ +// 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 abstract class RazorProjectEngineBuilder + { + public abstract RazorProjectFileSystem FileSystem { get; } + + public abstract ICollection Features { get; } + + public abstract IList Phases { get; } + + public abstract bool DesignTime { get; } + + public abstract RazorProjectEngine Build(); + } +} diff --git a/src/Microsoft.AspNetCore.Razor.Language/RazorProjectEngineBuilderExtensions.cs b/src/Microsoft.AspNetCore.Razor.Language/RazorProjectEngineBuilderExtensions.cs new file mode 100644 index 0000000000..7001e288a5 --- /dev/null +++ b/src/Microsoft.AspNetCore.Razor.Language/RazorProjectEngineBuilderExtensions.cs @@ -0,0 +1,33 @@ +// 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 +{ + public static class RazorProjectEngineBuilderExtensions + { + public static void SetImportFeature(this RazorProjectEngineBuilder builder, IRazorImportFeature feature) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + if (feature == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + // Remove any existing import features in favor of the new one we're given. + var existingFeatures = builder.Features.OfType().ToArray(); + foreach (var existingFeature in existingFeatures) + { + builder.Features.Remove(existingFeature); + } + + builder.Features.Add(feature); + } + } +} diff --git a/src/Microsoft.AspNetCore.Razor.Language/RazorProjectFileSystem.cs b/src/Microsoft.AspNetCore.Razor.Language/RazorProjectFileSystem.cs new file mode 100644 index 0000000000..83b86dc8b7 --- /dev/null +++ b/src/Microsoft.AspNetCore.Razor.Language/RazorProjectFileSystem.cs @@ -0,0 +1,9 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Microsoft.AspNetCore.Razor.Language +{ + public abstract class RazorProjectFileSystem : RazorProject + { + } +} diff --git a/src/Microsoft.AspNetCore.Razor.Language/Resources.resx b/src/Microsoft.AspNetCore.Razor.Language/Resources.resx index f706f89561..1001ecdb8e 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/Resources.resx +++ b/src/Microsoft.AspNetCore.Razor.Language/Resources.resx @@ -533,4 +533,7 @@ Instead, wrap the contents of the block in "{{}}": The '{0}.{1}' property must not be null. + + The '{0}' is missing feature '{1}'. + \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Razor.Language.Test/DefaultRazorProjectEngineBuilderTest.cs b/test/Microsoft.AspNetCore.Razor.Language.Test/DefaultRazorProjectEngineBuilderTest.cs new file mode 100644 index 0000000000..07272aeeee --- /dev/null +++ b/test/Microsoft.AspNetCore.Razor.Language.Test/DefaultRazorProjectEngineBuilderTest.cs @@ -0,0 +1,65 @@ +// 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.Linq; +using Moq; +using Xunit; + +namespace Microsoft.AspNetCore.Razor.Language +{ + public class DefaultRazorProjectEngineBuilderTest + { + [Fact] + public void Build_AddsFeaturesToRazorEngine() + { + // Arrange + var builder = new DefaultRazorProjectEngineBuilder(false, Mock.Of()); + builder.Features.Add(Mock.Of()); + builder.Features.Add(Mock.Of()); + builder.Features.Add(Mock.Of()); + + var features = builder.Features.ToArray(); + + // Act + var projectEngine = builder.Build(); + + // Assert + Assert.Collection(projectEngine.Engine.Features, + feature => Assert.Same(features[0], feature), + feature => Assert.Same(features[1], feature)); + } + + [Fact] + public void Build_AddsPhasesToRazorEngine() + { + // Arrange + var builder = new DefaultRazorProjectEngineBuilder(false, Mock.Of()); + builder.Phases.Add(Mock.Of()); + builder.Phases.Add(Mock.Of()); + + var phases = builder.Phases.ToArray(); + + // Act + var projectEngine = builder.Build(); + + // Assert + Assert.Collection(projectEngine.Engine.Phases, + phase => Assert.Same(phases[0], phase), + phase => Assert.Same(phases[1], phase)); + } + + [Fact] + public void Build_CreatesProjectEngineWithFileSystem() + { + // Arrange + var fileSystem = Mock.Of(); + var builder = new DefaultRazorProjectEngineBuilder(false, fileSystem); + + // Act + var projectEngine = builder.Build(); + + // Assert + Assert.Same(fileSystem, projectEngine.FileSystem); + } + } +} diff --git a/test/Microsoft.AspNetCore.Razor.Language.Test/DefaultRazorProjectEngineIntegrationTest.cs b/test/Microsoft.AspNetCore.Razor.Language.Test/DefaultRazorProjectEngineIntegrationTest.cs new file mode 100644 index 0000000000..ce95f80f6f --- /dev/null +++ b/test/Microsoft.AspNetCore.Razor.Language.Test/DefaultRazorProjectEngineIntegrationTest.cs @@ -0,0 +1,49 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Moq; +using Xunit; + +namespace Microsoft.AspNetCore.Razor.Language +{ + public class DefaultRazorProjectEngineIntegrationTest + { + [Fact] + public void Process_GetsImportsFromFeature() + { + // Arrange + var sourceDocument = TestRazorSourceDocument.Create(); + var testImport = TestRazorSourceDocument.Create(); + var importFeature = new Mock(); + importFeature.Setup(feature => feature.GetImports(It.IsAny())) + .Returns(new[] { testImport }); + var projectEngine = RazorProjectEngine.Create(TestRazorProjectFileSystem.Empty, builder => + { + builder.SetImportFeature(importFeature.Object); + }); + + // Act + var codeDocument = projectEngine.Process(sourceDocument); + + // Assert + var import = Assert.Single(codeDocument.Imports); + Assert.Same(testImport, import); + } + + [Fact] + public void Process_GeneratesCodeDocumentWithValidCSharpDocument() + { + // Arrange + var sourceDocument = TestRazorSourceDocument.Create(); + var projectEngine = RazorProjectEngine.Create(TestRazorProjectFileSystem.Empty); + + // Act + var codeDocument = projectEngine.Process(sourceDocument); + + // Assert + var csharpDocument = codeDocument.GetCSharpDocument(); + Assert.NotNull(csharpDocument); + Assert.Empty(csharpDocument.Diagnostics); + } + } +} diff --git a/test/Microsoft.AspNetCore.Razor.Language.Test/DefaultRazorProjectEngineTest.cs b/test/Microsoft.AspNetCore.Razor.Language.Test/DefaultRazorProjectEngineTest.cs new file mode 100644 index 0000000000..3224a5d9f0 --- /dev/null +++ b/test/Microsoft.AspNetCore.Razor.Language.Test/DefaultRazorProjectEngineTest.cs @@ -0,0 +1,29 @@ +// 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 Moq; +using Xunit; + +namespace Microsoft.AspNetCore.Razor.Language +{ + public class DefaultRazorProjectEngineTest + { + [Fact] + public void Process_AsksFileSystemForItems() + { + // Arrange + var razorProjectItem = new TestRazorProjectItem("/some/path.cshtml"); + var testFileSystem = new Mock(); + testFileSystem.Setup(fileSystem => fileSystem.GetItem("/some/path.cshtml")) + .Returns(razorProjectItem) + .Verifiable(); + var projectEngine = RazorProjectEngine.Create(testFileSystem.Object); + + // Act + projectEngine.Process("/some/path.cshtml"); + + // Assert + testFileSystem.Verify(); + } + } +} diff --git a/test/Microsoft.AspNetCore.Razor.Language.Test/RazorProjectEngineBuilderExtensionsTest.cs b/test/Microsoft.AspNetCore.Razor.Language.Test/RazorProjectEngineBuilderExtensionsTest.cs new file mode 100644 index 0000000000..44f93a970a --- /dev/null +++ b/test/Microsoft.AspNetCore.Razor.Language.Test/RazorProjectEngineBuilderExtensionsTest.cs @@ -0,0 +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 Moq; +using Xunit; + +namespace Microsoft.AspNetCore.Razor.Language +{ + public class RazorProjectEngineBuilderExtensionsTest + { + [Fact] + public void SetImportFeature_SetsTheImportFeature() + { + // Arrange + var builder = new DefaultRazorProjectEngineBuilder(false, Mock.Of()); + var testFeature1 = Mock.Of(); + var testFeature2 = Mock.Of(); + builder.Features.Add(testFeature1); + builder.Features.Add(testFeature2); + var newFeature = Mock.Of(); + + // Act + builder.SetImportFeature(newFeature); + + // Assert + var feature = Assert.Single(builder.Features); + Assert.Same(newFeature, feature); + } + } +} diff --git a/test/Microsoft.AspNetCore.Razor.Language.Test/TestRazorProjectFileSystem.cs b/test/Microsoft.AspNetCore.Razor.Language.Test/TestRazorProjectFileSystem.cs new file mode 100644 index 0000000000..08528b1014 --- /dev/null +++ b/test/Microsoft.AspNetCore.Razor.Language.Test/TestRazorProjectFileSystem.cs @@ -0,0 +1,42 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Microsoft.AspNetCore.Razor.Language +{ + public class TestRazorProjectFileSystem : RazorProjectFileSystem + { + public static RazorProjectFileSystem Empty = new TestRazorProjectFileSystem(); + + private readonly Dictionary _lookup; + + public TestRazorProjectFileSystem() + : this(new RazorProjectItem[0]) + { + } + + public TestRazorProjectFileSystem(IList items) + { + _lookup = items.ToDictionary(item => item.FilePath); + } + + public override IEnumerable EnumerateItems(string basePath) + { + throw new NotImplementedException(); + } + + public override RazorProjectItem GetItem(string path) + { + if (!_lookup.TryGetValue(path, out var value)) + { + value = new NotFoundProjectItem("", path); + } + + return value; + } + } +}