diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/CSharpRedirectRenderingConventions.cs b/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/CSharpRedirectRenderingConventions.cs index 85c1d8b998..138216fb4b 100644 --- a/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/CSharpRedirectRenderingConventions.cs +++ b/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/CSharpRedirectRenderingConventions.cs @@ -7,22 +7,22 @@ namespace Microsoft.AspNetCore.Razor.Evolution.CodeGeneration { internal class CSharpRedirectRenderingConventions : CSharpRenderingConventions { - private readonly string _redirectWriter; - public CSharpRedirectRenderingConventions(string redirectWriter, CSharpCodeWriter writer) : base(writer) { - _redirectWriter = redirectWriter; + RedirectWriter = redirectWriter; } - public override string StartWriteMethod => "WriteTo(" + _redirectWriter + ", " /* ORIGINAL: WriteToMethodName */; + public string RedirectWriter { get; } - public override string StartWriteLiteralMethod => "WriteLiteralTo(" + _redirectWriter + ", " /* ORIGINAL: WriteLiteralToMethodName */; + public override string StartWriteMethod => "WriteTo(" + RedirectWriter + ", " /* ORIGINAL: WriteToMethodName */; - public override string StartBeginWriteAttributeMethod => "BeginWriteAttributeTo(" + _redirectWriter + ", " /* ORIGINAL: BeginWriteAttributeToMethodName */; + public override string StartWriteLiteralMethod => "WriteLiteralTo(" + RedirectWriter + ", " /* ORIGINAL: WriteLiteralToMethodName */; - public override string StartWriteAttributeValueMethod => "WriteAttributeValueTo(" + _redirectWriter + ", " /* ORIGINAL: WriteAttributeValueToMethodName */; + public override string StartBeginWriteAttributeMethod => "BeginWriteAttributeTo(" + RedirectWriter + ", " /* ORIGINAL: BeginWriteAttributeToMethodName */; - public override string StartEndWriteAttributeMethod => "EndWriteAttributeTo(" + _redirectWriter /* ORIGINAL: EndWriteAttributeToMethodName */; + public override string StartWriteAttributeValueMethod => "WriteAttributeValueTo(" + RedirectWriter + ", " /* ORIGINAL: WriteAttributeValueToMethodName */; + + public override string StartEndWriteAttributeMethod => "EndWriteAttributeTo(" + RedirectWriter /* ORIGINAL: EndWriteAttributeToMethodName */; } } diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/CSharpRenderingContext.cs b/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/CSharpRenderingContext.cs index 7cdff0e5bd..ea0ef88004 100644 --- a/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/CSharpRenderingContext.cs +++ b/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/CSharpRenderingContext.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using Microsoft.AspNetCore.Razor.Evolution.Legacy; +using Microsoft.AspNetCore.Razor.Evolution.Intermediate; namespace Microsoft.AspNetCore.Razor.Evolution.CodeGeneration { @@ -43,5 +44,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution.CodeGeneration public RazorParserOptions Options { get; set; } public TagHelperRenderingContext TagHelperRenderingContext { get; set; } + + public Action RenderChildren { get; set; } } } diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/DefaultRuntimeTarget.cs b/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/DefaultRuntimeTarget.cs index 48726f3df2..f529ebb7a9 100644 --- a/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/DefaultRuntimeTarget.cs +++ b/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/DefaultRuntimeTarget.cs @@ -1,7 +1,8 @@ // 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.Evolution.CodeGeneration { @@ -9,31 +10,52 @@ namespace Microsoft.AspNetCore.Razor.Evolution.CodeGeneration { private readonly RazorParserOptions _options; - public DefaultRuntimeTarget(RazorParserOptions options) + public DefaultRuntimeTarget(RazorParserOptions options, IEnumerable extensions) { _options = options; + Extensions = extensions.ToArray(); } + public IRuntimeTargetExtension[] Extensions { get; } + internal override PageStructureCSharpRenderer CreateRenderer(CSharpRenderingContext context) { if (_options.DesignTimeMode) { - return new DesignTimeCSharpRenderer(context); + return new DesignTimeCSharpRenderer(this, context); } else { - return new RuntimeCSharpRenderer(context); + return new RuntimeCSharpRenderer(this, context); } } public override TExtension GetExtension() { - throw new NotImplementedException(); + for (var i = 0; i < Extensions.Length; i++) + { + var match = Extensions[i] as TExtension; + if (match != null) + { + return match; + } + } + + return null; } public override bool HasExtension() { - throw new NotImplementedException(); + for (var i = 0; i < Extensions.Length; i++) + { + var match = Extensions[i] as TExtension; + if (match != null) + { + return true; + } + } + + return false; } } } diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/DefaultRuntimeTargetBuilder.cs b/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/DefaultRuntimeTargetBuilder.cs index 01e92da500..eda7a9998e 100644 --- a/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/DefaultRuntimeTargetBuilder.cs +++ b/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/DefaultRuntimeTargetBuilder.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections.Generic; namespace Microsoft.AspNetCore.Razor.Evolution.CodeGeneration { @@ -11,15 +12,19 @@ namespace Microsoft.AspNetCore.Razor.Evolution.CodeGeneration { CodeDocument = codeDocument; Options = options; + + TargetExtensions = new List(); } public RazorCodeDocument CodeDocument { get; } public RazorParserOptions Options { get; } + public ICollection TargetExtensions { get; } + public RuntimeTarget Build() { - return new DefaultRuntimeTarget(Options); + return new DefaultRuntimeTarget(Options, TargetExtensions); } } } diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/DesignTimeCSharpRenderer.cs b/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/DesignTimeCSharpRenderer.cs index cbb1a742eb..46ee7b2d06 100644 --- a/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/DesignTimeCSharpRenderer.cs +++ b/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/DesignTimeCSharpRenderer.cs @@ -9,7 +9,8 @@ namespace Microsoft.AspNetCore.Razor.Evolution.CodeGeneration { internal class DesignTimeCSharpRenderer : PageStructureCSharpRenderer { - public DesignTimeCSharpRenderer(CSharpRenderingContext context) : base(context) + public DesignTimeCSharpRenderer(RuntimeTarget target, CSharpRenderingContext context) + : base(target, context) { } @@ -150,27 +151,6 @@ namespace Microsoft.AspNetCore.Razor.Evolution.CodeGeneration Context.Writer.WriteLine("))();"); } - public override void VisitTemplate(TemplateIRNode node) - { - const string ItemParameterName = "item"; - const string TemplateWriterName = "__razor_template_writer"; - - Context.Writer - .Write(ItemParameterName).Write(" => ") - .WriteStartNewObject("Microsoft.AspNetCore.Mvc.Razor.HelperResult" /* ORIGINAL: TemplateTypeName */); - - var initialRenderingConventions = Context.RenderingConventions; - var redirectConventions = new CSharpRedirectRenderingConventions(TemplateWriterName, Context.Writer); - Context.RenderingConventions = redirectConventions; - using (Context.Writer.BuildAsyncLambda(endLine: false, parameterNames: TemplateWriterName)) - { - VisitDefault(node); - } - Context.RenderingConventions = initialRenderingConventions; - - Context.Writer.WriteEndMethodInvocation(endLine: false); - } - public override void VisitTagHelper(TagHelperIRNode node) { var initialTagHelperRenderingContext = Context.TagHelperRenderingContext; diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/IRuntimeTargetBuilder.cs b/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/IRuntimeTargetBuilder.cs index 65cc959e15..d67b2eeb48 100644 --- a/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/IRuntimeTargetBuilder.cs +++ b/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/IRuntimeTargetBuilder.cs @@ -1,6 +1,8 @@ // 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.Evolution.CodeGeneration { public interface IRuntimeTargetBuilder @@ -9,6 +11,8 @@ namespace Microsoft.AspNetCore.Razor.Evolution.CodeGeneration RazorParserOptions Options { get; } + ICollection TargetExtensions { get; } + RuntimeTarget Build(); } } diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/ITemplateTargetExtension.cs b/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/ITemplateTargetExtension.cs new file mode 100644 index 0000000000..c8f7cfd254 --- /dev/null +++ b/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/ITemplateTargetExtension.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 Microsoft.AspNetCore.Razor.Evolution.Intermediate; + +namespace Microsoft.AspNetCore.Razor.Evolution.CodeGeneration +{ + internal interface ITemplateTargetExtension : IRuntimeTargetExtension + { + void WriteTemplate(CSharpRenderingContext context, TemplateIRNode node); + } +} diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/PageStructureCSharpRenderer.cs b/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/PageStructureCSharpRenderer.cs index ade3a7a95b..dde8a540d7 100644 --- a/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/PageStructureCSharpRenderer.cs +++ b/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/PageStructureCSharpRenderer.cs @@ -9,10 +9,12 @@ namespace Microsoft.AspNetCore.Razor.Evolution.CodeGeneration internal class PageStructureCSharpRenderer : RazorIRNodeWalker { protected readonly CSharpRenderingContext Context; + protected readonly RuntimeTarget Target; - public PageStructureCSharpRenderer(CSharpRenderingContext context) + public PageStructureCSharpRenderer(RuntimeTarget target, CSharpRenderingContext context) { Context = context; + Target = target; } public override void VisitNamespace(NamespaceDeclarationIRNode node) @@ -107,6 +109,11 @@ namespace Microsoft.AspNetCore.Razor.Evolution.CodeGeneration } } + public override void VisitExtension(ExtensionIRNode node) + { + node.WriteNode(Target, Context); + } + protected static void RenderExpressionInline(RazorIRNode node, CSharpRenderingContext context) { if (node is CSharpTokenIRNode) diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/RuntimeCSharpRenderer.cs b/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/RuntimeCSharpRenderer.cs index bf01f6a366..71d48c265d 100644 --- a/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/RuntimeCSharpRenderer.cs +++ b/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/RuntimeCSharpRenderer.cs @@ -12,8 +12,8 @@ namespace Microsoft.AspNetCore.Razor.Evolution.CodeGeneration { internal class RuntimeCSharpRenderer : PageStructureCSharpRenderer { - public RuntimeCSharpRenderer(CSharpRenderingContext context) - : base(context) + public RuntimeCSharpRenderer(RuntimeTarget target, CSharpRenderingContext context) + : base(target, context) { } @@ -213,26 +213,6 @@ namespace Microsoft.AspNetCore.Razor.Evolution.CodeGeneration } } - public override void VisitTemplate(TemplateIRNode node) - { - const string ItemParameterName = "item"; - const string TemplateWriterName = "__razor_template_writer"; - - Context.Writer - .Write(ItemParameterName).Write(" => ") - .WriteStartNewObject("Microsoft.AspNetCore.Mvc.Razor.HelperResult" /* ORIGINAL: TemplateTypeName */); - - var initialRenderingConventions = Context.RenderingConventions; - Context.RenderingConventions = new CSharpRedirectRenderingConventions(TemplateWriterName, Context.Writer); - using (Context.Writer.BuildAsyncLambda(endLine: false, parameterNames: TemplateWriterName)) - { - VisitDefault(node); - } - Context.RenderingConventions = initialRenderingConventions; - - Context.Writer.WriteEndMethodInvocation(endLine: false); - } - public override void VisitTagHelper(TagHelperIRNode node) { var initialTagHelperRenderingContext = Context.TagHelperRenderingContext; diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/TemplateTargetExtension.cs b/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/TemplateTargetExtension.cs new file mode 100644 index 0000000000..bfea04169f --- /dev/null +++ b/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/TemplateTargetExtension.cs @@ -0,0 +1,34 @@ +// 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 Microsoft.AspNetCore.Razor.Evolution.Intermediate; + +namespace Microsoft.AspNetCore.Razor.Evolution.CodeGeneration +{ + internal class TemplateTargetExtension : ITemplateTargetExtension + { + public static readonly string DefaultTemplateTypeName = "Microsoft.AspNetCore.Mvc.Razor.HelperResult"; + + public string TemplateTypeName { get; set; } = DefaultTemplateTypeName; + + public void WriteTemplate(CSharpRenderingContext context, TemplateIRNode node) + { + const string ItemParameterName = "item"; + const string TemplateWriterName = "__razor_template_writer"; + + context.Writer + .Write(ItemParameterName).Write(" => ") + .WriteStartNewObject(TemplateTypeName); + + var initialRenderingConventions = context.RenderingConventions; + context.RenderingConventions = new CSharpRedirectRenderingConventions(TemplateWriterName, context.Writer); + using (context.Writer.BuildAsyncLambda(endLine: false, parameterNames: TemplateWriterName)) + { + context.RenderChildren(node); + } + context.RenderingConventions = initialRenderingConventions; + + context.Writer.WriteEndMethodInvocation(endLine: false); + } + } +} diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/DefaultRazorCSharpLoweringPhase.cs b/src/Microsoft.AspNetCore.Razor.Evolution/DefaultRazorCSharpLoweringPhase.cs index cffe510cf4..d03a2d852b 100644 --- a/src/Microsoft.AspNetCore.Razor.Evolution/DefaultRazorCSharpLoweringPhase.cs +++ b/src/Microsoft.AspNetCore.Razor.Evolution/DefaultRazorCSharpLoweringPhase.cs @@ -56,6 +56,8 @@ namespace Microsoft.AspNetCore.Razor.Evolution } var renderer = target.CreateRenderer(renderingContext); + renderingContext.RenderChildren = renderer.VisitDefault; + renderer.VisitDocument(irDocument); var combinedErrors = syntaxTree.Diagnostics.Concat(renderingContext.ErrorSink.Errors).ToList(); diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/DefaultRazorTargetExtensionFeature.cs b/src/Microsoft.AspNetCore.Razor.Evolution/DefaultRazorTargetExtensionFeature.cs new file mode 100644 index 0000000000..2a8d8ff797 --- /dev/null +++ b/src/Microsoft.AspNetCore.Razor.Evolution/DefaultRazorTargetExtensionFeature.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.Collections.Generic; +using Microsoft.AspNetCore.Razor.Evolution.CodeGeneration; + +namespace Microsoft.AspNetCore.Razor.Evolution +{ + internal class DefaultRazorTargetExtensionFeature : IRazorTargetExtensionFeature + { + public RazorEngine Engine { get; set; } + + public ICollection TargetExtensions { get; } = new List(); + } +} diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/DocumentClassifierPassBase.cs b/src/Microsoft.AspNetCore.Razor.Evolution/DocumentClassifierPassBase.cs index 166bdaecee..64066420d8 100644 --- a/src/Microsoft.AspNetCore.Razor.Evolution/DocumentClassifierPassBase.cs +++ b/src/Microsoft.AspNetCore.Razor.Evolution/DocumentClassifierPassBase.cs @@ -2,15 +2,27 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Collections.Generic; -using Microsoft.AspNetCore.Razor.Evolution.Intermediate; +using System.Linq; using Microsoft.AspNetCore.Razor.Evolution.CodeGeneration; +using Microsoft.AspNetCore.Razor.Evolution.Intermediate; namespace Microsoft.AspNetCore.Razor.Evolution { public abstract class DocumentClassifierPassBase : RazorIRPassBase, IRazorDocumentClassifierPass { + private static readonly IRuntimeTargetExtension[] EmptyExtensionArray = new IRuntimeTargetExtension[0]; + protected abstract string DocumentKind { get; } + protected IRuntimeTargetExtension[] TargetExtensions { get; private set; } + + protected override void OnIntialized() + { + var feature = Engine.Features.OfType(); + + TargetExtensions = feature.FirstOrDefault()?.TargetExtensions.ToArray() ?? EmptyExtensionArray; + } + public sealed override void ExecuteCore(RazorCodeDocument codeDocument, DocumentIRNode irDocument) { if (irDocument.DocumentKind != null) @@ -82,10 +94,18 @@ namespace Microsoft.AspNetCore.Razor.Evolution private RuntimeTarget CreateTarget(RazorCodeDocument codeDocument, RazorParserOptions options) { - return RuntimeTarget.CreateDefault(codeDocument, options, (builder) => ConfigureTarget(codeDocument, builder)); + return RuntimeTarget.CreateDefault(codeDocument, options, (builder) => + { + for (var i = 0; i < TargetExtensions.Length; i++) + { + builder.TargetExtensions.Add(TargetExtensions[i]); + } + + ConfigureTarget(builder); + }); } - protected virtual void ConfigureTarget(RazorCodeDocument codeDocument, IRuntimeTargetBuilder builder) + protected virtual void ConfigureTarget(IRuntimeTargetBuilder builder) { // Intentionally empty. } diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/IRazorTargetExtensionFeature.cs b/src/Microsoft.AspNetCore.Razor.Evolution/IRazorTargetExtensionFeature.cs new file mode 100644 index 0000000000..515c7fbe75 --- /dev/null +++ b/src/Microsoft.AspNetCore.Razor.Evolution/IRazorTargetExtensionFeature.cs @@ -0,0 +1,13 @@ +// 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 Microsoft.AspNetCore.Razor.Evolution.CodeGeneration; + +namespace Microsoft.AspNetCore.Razor.Evolution +{ + public interface IRazorTargetExtensionFeature : IRazorEngineFeature + { + ICollection TargetExtensions { get; } + } +} diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/ExtensionIRNode.cs b/src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/ExtensionIRNode.cs new file mode 100644 index 0000000000..11644fc968 --- /dev/null +++ b/src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/ExtensionIRNode.cs @@ -0,0 +1,40 @@ +// 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 Microsoft.AspNetCore.Razor.Evolution.CodeGeneration; + +namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate +{ + public abstract class ExtensionIRNode : RazorIRNode + { + internal abstract void WriteNode(RuntimeTarget target, CSharpRenderingContext context); + + protected static void AcceptExtensionNode(TNode node, RazorIRNodeVisitor visitor) + where TNode : ExtensionIRNode + { + var typedVisitor = visitor as IExtensionIRNodeVisitor; + if (typedVisitor == null) + { + visitor.VisitExtension(node); + } + else + { + typedVisitor.VisitExtension(node); + } + } + + protected static TResult AcceptExtensionNode(TNode node, RazorIRNodeVisitor visitor) + where TNode : ExtensionIRNode + { + var typedVisitor = visitor as IExtensionIRNodeVisitor; + if (typedVisitor == null) + { + return visitor.VisitExtension(node); + } + else + { + return typedVisitor.VisitExtension(node); + } + } + } +} diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/IExtensionIRNodeVisitor`1.cs b/src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/IExtensionIRNodeVisitor`1.cs new file mode 100644 index 0000000000..1ef2d3f420 --- /dev/null +++ b/src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/IExtensionIRNodeVisitor`1.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.Evolution.Intermediate +{ + public interface IExtensionIRNodeVisitor where TNode : ExtensionIRNode + { + void VisitExtension(TNode node); + } +} diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/IExtensionIRNodeVisitor`2.cs b/src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/IExtensionIRNodeVisitor`2.cs new file mode 100644 index 0000000000..bebd0ace8e --- /dev/null +++ b/src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/IExtensionIRNodeVisitor`2.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.Evolution.Intermediate +{ + public interface IExtensionIRNodeVisitor where TNode : ExtensionIRNode + { + TResult VisitExtension(TNode node); + } +} diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/RazorIRNodeVisitor.cs b/src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/RazorIRNodeVisitor.cs index 456140b6b8..263f4341bd 100644 --- a/src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/RazorIRNodeVisitor.cs +++ b/src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/RazorIRNodeVisitor.cs @@ -29,7 +29,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate VisitDefault(node); } - public virtual void VisitTemplate(TemplateIRNode node) + public virtual void VisitExtension(ExtensionIRNode node) { VisitDefault(node); } diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/RazorIRNodeVisitorOfT.cs b/src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/RazorIRNodeVisitorOfT.cs index 400f02e9c1..b8f9a6d33b 100644 --- a/src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/RazorIRNodeVisitorOfT.cs +++ b/src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/RazorIRNodeVisitorOfT.cs @@ -30,7 +30,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate return VisitDefault(node); } - public virtual TResult VisitTemplate(TemplateIRNode node) + public virtual TResult VisitExtension(ExtensionIRNode node) { return VisitDefault(node); } diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/TemplateIRNode.cs b/src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/TemplateIRNode.cs index 9d2a653cc3..baebcfe841 100644 --- a/src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/TemplateIRNode.cs +++ b/src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/TemplateIRNode.cs @@ -3,10 +3,11 @@ using System; using System.Collections.Generic; +using Microsoft.AspNetCore.Razor.Evolution.CodeGeneration; namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate { - public class TemplateIRNode : RazorIRNode + public class TemplateIRNode : ExtensionIRNode { public override IList Children { get; } = new List(); @@ -21,7 +22,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate throw new ArgumentNullException(nameof(visitor)); } - visitor.VisitTemplate(this); + AcceptExtensionNode(this, visitor); } public override TResult Accept(RazorIRNodeVisitor visitor) @@ -31,7 +32,13 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate throw new ArgumentNullException(nameof(visitor)); } - return visitor.VisitTemplate(this); + return AcceptExtensionNode(this, visitor); + } + + internal override void WriteNode(RuntimeTarget target, CSharpRenderingContext context) + { + var extension = target.GetExtension(); + extension.WriteTemplate(context, this); } } } diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/RazorEngine.cs b/src/Microsoft.AspNetCore.Razor.Evolution/RazorEngine.cs index 56c91966be..c20352a147 100644 --- a/src/Microsoft.AspNetCore.Razor.Evolution/RazorEngine.cs +++ b/src/Microsoft.AspNetCore.Razor.Evolution/RazorEngine.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using Microsoft.AspNetCore.Razor.Evolution.CodeGeneration; namespace Microsoft.AspNetCore.Razor.Evolution { @@ -56,6 +57,10 @@ namespace Microsoft.AspNetCore.Razor.Evolution builder.Phases.Add(new DefaultRazorIROptimizationPhase()); builder.Phases.Add(new DefaultRazorCSharpLoweringPhase()); + // General extensibility + builder.Features.Add(new DefaultRazorDirectiveFeature()); + builder.Features.Add(new DefaultRazorTargetExtensionFeature()); + // Syntax Tree passes builder.Features.Add(new DefaultDirectiveSyntaxTreePass()); builder.Features.Add(new HtmlNodeOptimizationPass()); @@ -65,6 +70,9 @@ namespace Microsoft.AspNetCore.Razor.Evolution builder.Features.Add(new DefaultDocumentClassifierPass()); builder.Features.Add(new DefaultDirectiveIRPass()); builder.Features.Add(new DirectiveRemovalIROptimizationPass()); + + // Default Runtime Targets + builder.AddTargetExtension(new TemplateTargetExtension()); } internal static void AddRuntimeDefaults(IRazorEngineBuilder builder) diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/RazorEngineBuilderExtensions.cs b/src/Microsoft.AspNetCore.Razor.Evolution/RazorEngineBuilderExtensions.cs index f0f6fe9751..6effde8441 100644 --- a/src/Microsoft.AspNetCore.Razor.Evolution/RazorEngineBuilderExtensions.cs +++ b/src/Microsoft.AspNetCore.Razor.Evolution/RazorEngineBuilderExtensions.cs @@ -3,6 +3,7 @@ using System; using System.Linq; +using Microsoft.AspNetCore.Razor.Evolution.CodeGeneration; namespace Microsoft.AspNetCore.Razor.Evolution { @@ -12,7 +13,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution { if (builder == null) { - throw new ArgumentNullException(nameof(directive)); + throw new ArgumentNullException(nameof(builder)); } if (directive == null) @@ -26,6 +27,24 @@ namespace Microsoft.AspNetCore.Razor.Evolution return builder; } + public static IRazorEngineBuilder AddTargetExtension(this IRazorEngineBuilder builder, IRuntimeTargetExtension extension) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + if (extension == null) + { + throw new ArgumentNullException(nameof(extension)); + } + + var targetExtensionFeature = GetTargetExtensionFeature(builder); + targetExtensionFeature.TargetExtensions.Add(extension); + + return builder; + } + private static IRazorDirectiveFeature GetDirectiveFeature(IRazorEngineBuilder builder) { var directiveFeature = builder.Features.OfType().FirstOrDefault(); @@ -37,5 +56,17 @@ namespace Microsoft.AspNetCore.Razor.Evolution return directiveFeature; } + + private static IRazorTargetExtensionFeature GetTargetExtensionFeature(IRazorEngineBuilder builder) + { + var targetExtensionFeature = builder.Features.OfType().FirstOrDefault(); + if (targetExtensionFeature == null) + { + targetExtensionFeature = new DefaultRazorTargetExtensionFeature(); + builder.Features.Add(targetExtensionFeature); + } + + return targetExtensionFeature; + } } } diff --git a/test/Microsoft.AspNetCore.Razor.Evolution.Test/CodeGeneration/DefaultRuntimeTargetBuilderTest.cs b/test/Microsoft.AspNetCore.Razor.Evolution.Test/CodeGeneration/DefaultRuntimeTargetBuilderTest.cs index 58f993855b..eae4b36ec9 100644 --- a/test/Microsoft.AspNetCore.Razor.Evolution.Test/CodeGeneration/DefaultRuntimeTargetBuilderTest.cs +++ b/test/Microsoft.AspNetCore.Razor.Evolution.Test/CodeGeneration/DefaultRuntimeTargetBuilderTest.cs @@ -16,11 +16,31 @@ namespace Microsoft.AspNetCore.Razor.Evolution.CodeGeneration var builder = new DefaultRuntimeTargetBuilder(codeDocument, options); + var extensions = new IRuntimeTargetExtension[] + { + new MyExtension1(), + new MyExtension2(), + }; + + for (var i = 0; i < extensions.Length; i++) + { + builder.TargetExtensions.Add(extensions[i]); + } + // Act - var target = builder.Build(); + var result = builder.Build(); // Assert - Assert.IsType(target); + var target = Assert.IsType(result); + Assert.Equal(extensions, target.Extensions); + } + + private class MyExtension1 : IRuntimeTargetExtension + { + } + + private class MyExtension2 : IRuntimeTargetExtension + { } } } diff --git a/test/Microsoft.AspNetCore.Razor.Evolution.Test/CodeGeneration/DefaultRuntimeTargetTest.cs b/test/Microsoft.AspNetCore.Razor.Evolution.Test/CodeGeneration/DefaultRuntimeTargetTest.cs index 724c4a8e94..0d0cd35073 100644 --- a/test/Microsoft.AspNetCore.Razor.Evolution.Test/CodeGeneration/DefaultRuntimeTargetTest.cs +++ b/test/Microsoft.AspNetCore.Razor.Evolution.Test/CodeGeneration/DefaultRuntimeTargetTest.cs @@ -1,12 +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.Linq; using Xunit; namespace Microsoft.AspNetCore.Razor.Evolution.CodeGeneration { public class DefaultRuntimeTargetTest { + [Fact] + public void Constructor_CreatesDefensiveCopy() + { + // Arrange + var options = RazorParserOptions.CreateDefaultOptions(); + + var extensions = new IRuntimeTargetExtension[] + { + new MyExtension2(), + new MyExtension1(), + }; + + // Act + var target = new DefaultRuntimeTarget(options, extensions); + + // Assert + Assert.NotSame(extensions, target); + } + [Fact] public void CreateRenderer_DesignTime_CreatesDesignTimeRenderer() { @@ -14,7 +34,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution.CodeGeneration var options = RazorParserOptions.CreateDefaultOptions(); options.DesignTimeMode = true; - var target = new DefaultRuntimeTarget(options); + var target = new DefaultRuntimeTarget(options, Enumerable.Empty()); // Act var renderer = target.CreateRenderer(new CSharpRenderingContext()); @@ -30,7 +50,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution.CodeGeneration var options = RazorParserOptions.CreateDefaultOptions(); options.DesignTimeMode = false; - var target = new DefaultRuntimeTarget(options); + var target = new DefaultRuntimeTarget(options, Enumerable.Empty()); // Act var renderer = target.CreateRenderer(new CSharpRenderingContext()); @@ -38,5 +58,121 @@ namespace Microsoft.AspNetCore.Razor.Evolution.CodeGeneration // Assert Assert.IsType(renderer); } + + [Fact] + public void HasExtension_ReturnsTrue_WhenExtensionFound() + { + // Arrange + var options = RazorParserOptions.CreateDefaultOptions(); + + var extensions = new IRuntimeTargetExtension[] + { + new MyExtension2(), + new MyExtension1(), + }; + + var target = new DefaultRuntimeTarget(options, extensions); + + // Act + var result = target.HasExtension(); + + // Assert + Assert.True(result); + } + + [Fact] + public void HasExtension_ReturnsFalse_WhenExtensionNotFound() + { + // Arrange + var options = RazorParserOptions.CreateDefaultOptions(); + + var extensions = new IRuntimeTargetExtension[] + { + new MyExtension2(), + new MyExtension2(), + }; + + var target = new DefaultRuntimeTarget(options, extensions); + + // Act + var result = target.HasExtension(); + + // Assert + Assert.False(result); + } + + [Fact] + public void GetExtension_ReturnsExtension_WhenExtensionFound() + { + // Arrange + var options = RazorParserOptions.CreateDefaultOptions(); + + var extensions = new IRuntimeTargetExtension[] + { + new MyExtension2(), + new MyExtension1(), + }; + + var target = new DefaultRuntimeTarget(options, extensions); + + // Act + var result = target.GetExtension(); + + // Assert + Assert.Same(extensions[1], result); + } + + [Fact] + public void GetExtension_ReturnsFirstMatch_WhenExtensionFound() + { + // Arrange + var options = RazorParserOptions.CreateDefaultOptions(); + + var extensions = new IRuntimeTargetExtension[] + { + new MyExtension2(), + new MyExtension1(), + new MyExtension2(), + new MyExtension1(), + }; + + var target = new DefaultRuntimeTarget(options, extensions); + + // Act + var result = target.GetExtension(); + + // Assert + Assert.Same(extensions[1], result); + } + + + [Fact] + public void GetExtension_ReturnsNull_WhenExtensionNotFound() + { + // Arrange + var options = RazorParserOptions.CreateDefaultOptions(); + + var extensions = new IRuntimeTargetExtension[] + { + new MyExtension2(), + new MyExtension2(), + }; + + var target = new DefaultRuntimeTarget(options, extensions); + + // Act + var result = target.GetExtension(); + + // Assert + Assert.Null(result); + } + + private class MyExtension1 : IRuntimeTargetExtension + { + } + + private class MyExtension2 : IRuntimeTargetExtension + { + } } } diff --git a/test/Microsoft.AspNetCore.Razor.Evolution.Test/CodeGeneration/TemplateTargetExtensionTest.cs b/test/Microsoft.AspNetCore.Razor.Evolution.Test/CodeGeneration/TemplateTargetExtensionTest.cs new file mode 100644 index 0000000000..a22e14916e --- /dev/null +++ b/test/Microsoft.AspNetCore.Razor.Evolution.Test/CodeGeneration/TemplateTargetExtensionTest.cs @@ -0,0 +1,51 @@ +// 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 Microsoft.AspNetCore.Razor.Evolution.Intermediate; +using Microsoft.AspNetCore.Razor.Evolution.Legacy; +using Xunit; + +namespace Microsoft.AspNetCore.Razor.Evolution.CodeGeneration +{ + public class TemplateTargetExtensionTest + { + [Fact] + public void WriteTemplate_WritesTemplateCode() + { + // Arrange + var node = new TemplateIRNode(); + + var extension = new TemplateTargetExtension() + { + TemplateTypeName = "global::TestTemplate", + }; + + var context = new CSharpRenderingContext() + { + Writer = new CSharpCodeWriter(), + }; + + context.RenderChildren = (n) => + { + Assert.Same(node, n); + + var conventions = Assert.IsType(context.RenderingConventions); + Assert.Equal("__razor_template_writer", conventions.RedirectWriter); + + context.Writer.Write(" var s = \"Inside\""); + }; + + // Act + extension.WriteTemplate(context, node); + + // Assert + var expected = @"item => new global::TestTemplate(async(__razor_template_writer) => { + var s = ""Inside"" +} +)"; + + var output = context.Writer.Builder.ToString(); + Assert.Equal(expected, output); + } + } +} diff --git a/test/Microsoft.AspNetCore.Razor.Evolution.Test/DocumentClassifierPassBaseTest.cs b/test/Microsoft.AspNetCore.Razor.Evolution.Test/DocumentClassifierPassBaseTest.cs index 3fcd234496..d957159f93 100644 --- a/test/Microsoft.AspNetCore.Razor.Evolution.Test/DocumentClassifierPassBaseTest.cs +++ b/test/Microsoft.AspNetCore.Razor.Evolution.Test/DocumentClassifierPassBaseTest.cs @@ -3,6 +3,8 @@ using System; +using System.Linq; +using Microsoft.AspNetCore.Razor.Evolution.CodeGeneration; using Microsoft.AspNetCore.Razor.Evolution.Intermediate; using Xunit; using static Microsoft.AspNetCore.Razor.Evolution.Intermediate.RazorIRAssert; @@ -55,6 +57,41 @@ namespace Microsoft.AspNetCore.Razor.Evolution NoChildren(irDocument); } + [Fact] + public void Execute_Match_AddsGlobalTargetExtensions() + { + // Arrange + var irDocument = new DocumentIRNode() + { + Options = RazorParserOptions.CreateDefaultOptions(), + }; + + var expected = new IRuntimeTargetExtension[] + { + new MyExtension1(), + new MyExtension2(), + }; + + var pass = new TestDocumentClassifierPass(); + pass.Engine = RazorEngine.CreateEmpty(b => + { + for (var i = 0; i < expected.Length; i++) + { + b.AddTargetExtension(expected[i]); + } + }); + + IRuntimeTargetExtension[] extensions = null; + + pass.RuntimeTargetCallback = (builder) => extensions = builder.TargetExtensions.ToArray(); + + // Act + pass.Execute(TestRazorCodeDocument.CreateEmpty(), irDocument); + + // Assert + Assert.Equal(expected, extensions); + } + [Fact] public void Execute_Match_SetsDocumentType_AndCreatesStructure() { @@ -228,6 +265,8 @@ namespace Microsoft.AspNetCore.Razor.Evolution public bool ShouldMatch { get; set; } = true; + public Action RuntimeTargetCallback { get; set; } + public string Namespace { get; set; } public string Class { get; set; } @@ -251,6 +290,19 @@ namespace Microsoft.AspNetCore.Razor.Evolution @class.Name = Class; @method.Name = Method; } + + protected override void ConfigureTarget(IRuntimeTargetBuilder builder) + { + RuntimeTargetCallback?.Invoke(builder); + } + } + + private class MyExtension1 : IRuntimeTargetExtension + { + } + + private class MyExtension2 : IRuntimeTargetExtension + { } } } diff --git a/test/Microsoft.AspNetCore.Razor.Evolution.Test/Intermediate/ExtensionIRNodeTest.cs b/test/Microsoft.AspNetCore.Razor.Evolution.Test/Intermediate/ExtensionIRNodeTest.cs new file mode 100644 index 0000000000..197621214a --- /dev/null +++ b/test/Microsoft.AspNetCore.Razor.Evolution.Test/Intermediate/ExtensionIRNodeTest.cs @@ -0,0 +1,168 @@ +// 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 Microsoft.AspNetCore.Razor.Evolution.CodeGeneration; +using Xunit; + +namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate +{ + // These tests cover the methods on ExtensionIRNode that are used to implement visitors + // that special case an extension node. + public class ExtensionIRNodeTest + { + [Fact] + public void Accept_CallsStandardVisitExtension_ForStandardVisitor() + { + // Arrange + var node = new TestExtensionIRNode(); + var visitor = new StandardVisitor(); + + // Act + node.Accept(visitor); + + // Assert + Assert.True(visitor.WasStandardMethodCalled); + Assert.False(visitor.WasSpecificMethodCalled); + } + + [Fact] + public void Accept_CallsSpecialVisitExtension_ForSpecialVisitor() + { + // Arrange + var node = new TestExtensionIRNode(); + var visitor = new SpecialVisitor(); + + // Act + node.Accept(visitor); + + // Assert + Assert.False(visitor.WasStandardMethodCalled); + Assert.True(visitor.WasSpecificMethodCalled); + } + + [Fact] + public void Accept_TResult_CallsStandardVisitExtension_ForStandardVisitor() + { + // Arrange + var node = new TestExtensionIRNode(); + var visitor = new StandardVisitor(); + + // Act + node.Accept(visitor); + + // Assert + Assert.True(visitor.WasStandardMethodCalled); + Assert.False(visitor.WasSpecificMethodCalled); + } + + [Fact] + public void Accept_TResult_CallsSpecialVisitExtension_ForSpecialVisitor() + { + // Arrange + var node = new TestExtensionIRNode(); + var visitor = new SpecialVisitor(); + + // Act + node.Accept(visitor); + + // Assert + Assert.False(visitor.WasStandardMethodCalled); + Assert.True(visitor.WasSpecificMethodCalled); + } + + private class TestExtensionIRNode : ExtensionIRNode + { + public override IList Children => throw new NotImplementedException(); + + public override RazorIRNode Parent { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } + public override SourceSpan? Source { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } + + public override void Accept(RazorIRNodeVisitor visitor) + { + // This is the standard visitor boilerplate for an extension node. + AcceptExtensionNode(this, visitor); + } + + public override TResult Accept(RazorIRNodeVisitor visitor) + { + // This is the standard visitor boilerplate for an extension node. + return AcceptExtensionNode(this, visitor); + } + + internal override void WriteNode(RuntimeTarget target, CSharpRenderingContext context) + { + throw new NotImplementedException(); + } + } + + private class StandardVisitor : RazorIRNodeVisitor + { + public bool WasStandardMethodCalled { get; private set; } + public bool WasSpecificMethodCalled { get; private set; } + + public override void VisitExtension(ExtensionIRNode node) + { + WasStandardMethodCalled = true; + } + + public void VisitExtension(TestExtensionIRNode node) + { + WasSpecificMethodCalled = true; + } + } + + private class StandardVisitor : RazorIRNodeVisitor + { + public bool WasStandardMethodCalled { get; private set; } + public bool WasSpecificMethodCalled { get; private set; } + + public override T VisitExtension(ExtensionIRNode node) + { + WasStandardMethodCalled = true; + return default(T); + } + + public T VisitExtension(TestExtensionIRNode node) + { + WasSpecificMethodCalled = true; + return default(T); + } + } + + private class SpecialVisitor : RazorIRNodeVisitor, IExtensionIRNodeVisitor + { + public bool WasStandardMethodCalled { get; private set; } + public bool WasSpecificMethodCalled { get; private set; } + + public override void VisitExtension(ExtensionIRNode node) + { + WasStandardMethodCalled = true; + } + + public void VisitExtension(TestExtensionIRNode node) + { + WasSpecificMethodCalled = true; + } + } + + private class SpecialVisitor : RazorIRNodeVisitor, IExtensionIRNodeVisitor + { + public bool WasStandardMethodCalled { get; private set; } + public bool WasSpecificMethodCalled { get; private set; } + + public override T VisitExtension(ExtensionIRNode node) + { + WasStandardMethodCalled = true; + return default(T); + } + + public T VisitExtension(TestExtensionIRNode node) + { + WasSpecificMethodCalled = true; + return default(T); + } + } + } +} diff --git a/test/Microsoft.AspNetCore.Razor.Evolution.Test/RazorEngineBuilderExtensionsTest.cs b/test/Microsoft.AspNetCore.Razor.Evolution.Test/RazorEngineBuilderExtensionsTest.cs index 1078789e74..a48c0f87c1 100644 --- a/test/Microsoft.AspNetCore.Razor.Evolution.Test/RazorEngineBuilderExtensionsTest.cs +++ b/test/Microsoft.AspNetCore.Razor.Evolution.Test/RazorEngineBuilderExtensionsTest.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Linq; +using Microsoft.AspNetCore.Razor.Evolution.CodeGeneration; using Xunit; namespace Microsoft.AspNetCore.Razor.Evolution @@ -29,6 +30,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution Assert.Equal("test_directive", directive.Name); } + [Fact] public void AddDirective_NoFeature_CreatesFeature() { // Arrange @@ -45,5 +47,50 @@ namespace Microsoft.AspNetCore.Razor.Evolution var directive = Assert.Single(actual.Directives); Assert.Equal("test_directive", directive.Name); } + + [Fact] + public void AddTargetExtensions_ExistingFeature_UsesFeature() + { + // Arrange + var extension = new MyTargetExtension(); + + var expected = new DefaultRazorTargetExtensionFeature(); + var engine = RazorEngine.CreateEmpty(b => + { + b.Features.Add(expected); + + // Act + b.AddTargetExtension(extension); + }); + + // Assert + var actual = Assert.Single(engine.Features.OfType()); + Assert.Same(expected, actual); + + Assert.Same(extension, Assert.Single(actual.TargetExtensions)); + } + + [Fact] + public void AddTargetExtensions_NoFeature_CreatesFeature() + { + // Arrange + var extension = new MyTargetExtension(); + + var engine = RazorEngine.CreateEmpty(b => + { + // Act + b.AddTargetExtension(extension); + }); + + // Assert + var actual = Assert.Single(engine.Features.OfType()); + Assert.IsType(actual); + + Assert.Same(extension, Assert.Single(actual.TargetExtensions)); + } + + private class MyTargetExtension : IRuntimeTargetExtension + { + } } } diff --git a/test/Microsoft.AspNetCore.Razor.Evolution.Test/RazorEngineTest.cs b/test/Microsoft.AspNetCore.Razor.Evolution.Test/RazorEngineTest.cs index 433b502755..eac1ac9c75 100644 --- a/test/Microsoft.AspNetCore.Razor.Evolution.Test/RazorEngineTest.cs +++ b/test/Microsoft.AspNetCore.Razor.Evolution.Test/RazorEngineTest.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Linq; using Moq; using Xunit; +using Microsoft.AspNetCore.Razor.Evolution.CodeGeneration; namespace Microsoft.AspNetCore.Razor.Evolution { @@ -132,10 +133,22 @@ namespace Microsoft.AspNetCore.Razor.Evolution p => Assert.Same(phases[1], p)); } + private static void AssertDefaultTargetExtensions(RazorEngine engine) + { + var feature = engine.Features.OfType().FirstOrDefault(); + Assert.NotNull(feature); + + Assert.Collection( + feature.TargetExtensions, + f => Assert.IsType(f)); + } + private static void AssertDefaultRuntimeFeatures(IEnumerable features) { Assert.Collection( features, + feature => Assert.IsType(feature), + feature => Assert.IsType(feature), feature => Assert.IsType(feature), feature => Assert.IsType(feature), feature => Assert.IsType(feature), @@ -162,6 +175,8 @@ namespace Microsoft.AspNetCore.Razor.Evolution { Assert.Collection( features, + feature => Assert.IsType(feature), + feature => Assert.IsType(feature), feature => Assert.IsType(feature), feature => Assert.IsType(feature), feature => Assert.IsType(feature),