Refactor IR phases
- Added DirectiveRemovalIRPass - Added IRazorDocumentClassifierPhase, IRazorDirectiveClassifierPhase and IRazorIROptimizationPhase - Added all the related passes and default implementations - Refactored DefaultDirectiveIRPass to do the right thing - Execute method in IR passes now return void - Added tests for the new phases
This commit is contained in:
parent
80172c641d
commit
8ac5468714
|
|
@ -148,7 +148,6 @@ namespace Microsoft.AspNetCore.Razor.Evolution.CodeGeneration
|
|||
Context.Writer.SetIndent(originalIndent);
|
||||
}
|
||||
Context.Writer.WriteLine("))();");
|
||||
|
||||
}
|
||||
|
||||
public override void VisitTemplate(TemplateIRNode node)
|
||||
|
|
|
|||
|
|
@ -2,42 +2,89 @@
|
|||
// 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 Microsoft.AspNetCore.Razor.Evolution.Intermediate;
|
||||
using Microsoft.AspNetCore.Razor.Evolution.Legacy;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.Evolution
|
||||
{
|
||||
internal class DefaultDirectiveIRPass : RazorIRPassBase
|
||||
internal class DefaultDirectiveIRPass : RazorIRPassBase, IRazorDirectiveClassifierPass
|
||||
{
|
||||
public override int Order => RazorIRPass.DefaultDirectiveClassifierOrder;
|
||||
|
||||
public override DocumentIRNode ExecuteCore(RazorCodeDocument codeDocument, DocumentIRNode irDocument)
|
||||
public override void ExecuteCore(RazorCodeDocument codeDocument, DocumentIRNode irDocument)
|
||||
{
|
||||
var parserOptions = irDocument.Options;
|
||||
|
||||
var designTime = parserOptions.DesignTimeMode;
|
||||
var walker = new DirectiveWalker(designTime);
|
||||
var walker = new DirectiveWalker();
|
||||
walker.VisitDocument(irDocument);
|
||||
|
||||
return irDocument;
|
||||
var classNode = walker.ClassNode;
|
||||
foreach (var node in walker.FunctionsDirectiveNodes)
|
||||
{
|
||||
node.Parent.Children.Remove(node);
|
||||
|
||||
foreach (var child in node.Children.Except(node.Tokens))
|
||||
{
|
||||
child.Parent = classNode;
|
||||
classNode.Children.Add(child);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var node in walker.InheritsDirectiveNodes.Reverse())
|
||||
{
|
||||
node.Parent.Children.Remove(node);
|
||||
|
||||
var token = node.Tokens.FirstOrDefault();
|
||||
if (token != null)
|
||||
{
|
||||
classNode.BaseType = token.Content;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var node in walker.SectionDirectiveNodes)
|
||||
{
|
||||
var sectionIndex = node.Parent.Children.IndexOf(node);
|
||||
node.Parent.Children.Remove(node);
|
||||
|
||||
var defineSectionEndStatement = new CSharpStatementIRNode()
|
||||
{
|
||||
Content = "});",
|
||||
};
|
||||
node.Parent.Children.Insert(sectionIndex, defineSectionEndStatement);
|
||||
|
||||
foreach (var child in node.Children.Except(node.Tokens).Reverse())
|
||||
{
|
||||
node.Parent.Children.Insert(sectionIndex, child);
|
||||
}
|
||||
|
||||
var lambdaContent = designTime ? "__razor_section_writer" : string.Empty;
|
||||
var sectionName = node.Tokens.FirstOrDefault()?.Content;
|
||||
var defineSectionStartStatement = new CSharpStatementIRNode()
|
||||
{
|
||||
Content = /* ORIGINAL: DefineSectionMethodName */ $"DefineSection(\"{sectionName}\", async ({lambdaContent}) => {{",
|
||||
};
|
||||
|
||||
node.Parent.Children.Insert(sectionIndex, defineSectionStartStatement);
|
||||
}
|
||||
}
|
||||
|
||||
private class DirectiveWalker : RazorIRNodeWalker
|
||||
{
|
||||
private ClassDeclarationIRNode _classNode;
|
||||
private readonly bool _designTime;
|
||||
public ClassDeclarationIRNode ClassNode { get; private set; }
|
||||
|
||||
public DirectiveWalker(bool designTime)
|
||||
{
|
||||
_designTime = designTime;
|
||||
}
|
||||
public IList<DirectiveIRNode> FunctionsDirectiveNodes { get; } = new List<DirectiveIRNode>();
|
||||
|
||||
public IList<DirectiveIRNode> InheritsDirectiveNodes { get; } = new List<DirectiveIRNode>();
|
||||
|
||||
public IList<DirectiveIRNode> SectionDirectiveNodes { get; } = new List<DirectiveIRNode>();
|
||||
|
||||
public override void VisitClass(ClassDeclarationIRNode node)
|
||||
{
|
||||
if (_classNode == null)
|
||||
if (ClassNode == null)
|
||||
{
|
||||
_classNode = node;
|
||||
ClassNode = node;
|
||||
}
|
||||
|
||||
VisitDefault(node);
|
||||
|
|
@ -47,47 +94,16 @@ namespace Microsoft.AspNetCore.Razor.Evolution
|
|||
{
|
||||
if (string.Equals(node.Name, CSharpCodeParser.FunctionsDirectiveDescriptor.Name, StringComparison.Ordinal))
|
||||
{
|
||||
foreach (var child in node.Children.Except(node.Tokens))
|
||||
{
|
||||
child.Parent = _classNode;
|
||||
_classNode.Children.Add(child);
|
||||
}
|
||||
FunctionsDirectiveNodes.Add(node);
|
||||
}
|
||||
else if (string.Equals(node.Name, CSharpCodeParser.InheritsDirectiveDescriptor.Name, StringComparison.Ordinal))
|
||||
{
|
||||
var token = node.Tokens.FirstOrDefault();
|
||||
|
||||
if (token != null)
|
||||
{
|
||||
_classNode.BaseType = token.Content;
|
||||
}
|
||||
InheritsDirectiveNodes.Add(node);
|
||||
}
|
||||
else if (string.Equals(node.Name, CSharpCodeParser.SectionDirectiveDescriptor.Name, StringComparison.Ordinal))
|
||||
{
|
||||
var sectionIndex = node.Parent.Children.IndexOf(node);
|
||||
|
||||
var defineSectionEndStatement = new CSharpStatementIRNode()
|
||||
{
|
||||
Content = "});",
|
||||
};
|
||||
node.Parent.Children.Insert(sectionIndex, defineSectionEndStatement);
|
||||
|
||||
foreach (var child in node.Children.Except(node.Tokens).Reverse())
|
||||
{
|
||||
node.Parent.Children.Insert(sectionIndex, child);
|
||||
}
|
||||
|
||||
var lambdaContent = _designTime ? "__razor_section_writer" : string.Empty;
|
||||
var sectionName = node.Tokens.FirstOrDefault()?.Content;
|
||||
var defineSectionStartStatement = new CSharpStatementIRNode()
|
||||
{
|
||||
Content = /* ORIGINAL: DefineSectionMethodName */ $"DefineSection(\"{sectionName}\", async ({lambdaContent}) => {{",
|
||||
};
|
||||
|
||||
node.Parent.Children.Insert(sectionIndex, defineSectionStartStatement);
|
||||
SectionDirectiveNodes.Add(node);
|
||||
}
|
||||
|
||||
node.Parent.Children.Remove(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution
|
|||
{
|
||||
internal class DefaultDocumentClassifierPass : DocumentClassifierPassBase
|
||||
{
|
||||
public override int Order => RazorIRPass.DefaultDocumentClassifierOrder;
|
||||
public override int Order => RazorIRPass.DefaultFeatureOrder;
|
||||
|
||||
protected override string DocumentKind => "default";
|
||||
|
||||
|
|
|
|||
|
|
@ -7,11 +7,11 @@ using Microsoft.AspNetCore.Razor.Evolution.Intermediate;
|
|||
|
||||
namespace Microsoft.AspNetCore.Razor.Evolution
|
||||
{
|
||||
public class DefaultInstrumentationPass : RazorIRPassBase
|
||||
public class DefaultInstrumentationPass : RazorIRPassBase, IRazorIROptimizationPass
|
||||
{
|
||||
public override int Order => RazorIRPass.DefaultLoweringOrder;
|
||||
public override int Order => RazorIRPass.DefaultFeatureOrder;
|
||||
|
||||
public override DocumentIRNode ExecuteCore(RazorCodeDocument codeDocument, DocumentIRNode irDocument)
|
||||
public override void ExecuteCore(RazorCodeDocument codeDocument, DocumentIRNode irDocument)
|
||||
{
|
||||
var walker = new Visitor();
|
||||
walker.VisitDocument(irDocument);
|
||||
|
|
@ -22,8 +22,6 @@ namespace Microsoft.AspNetCore.Razor.Evolution
|
|||
|
||||
AddInstrumentation(node);
|
||||
}
|
||||
|
||||
return irDocument;
|
||||
}
|
||||
|
||||
private static void AddInstrumentation(InstrumentationItem item)
|
||||
|
|
|
|||
|
|
@ -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 System.Linq;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.Evolution
|
||||
{
|
||||
internal class DefaultRazorDirectiveClassifierPhase : RazorEnginePhaseBase, IRazorDirectiveClassifierPhase
|
||||
{
|
||||
public IRazorDirectiveClassifierPass[] Passes { get; private set; }
|
||||
|
||||
protected override void OnIntialized()
|
||||
{
|
||||
Passes = Engine.Features.OfType<IRazorDirectiveClassifierPass>().OrderBy(p => p.Order).ToArray();
|
||||
}
|
||||
|
||||
protected override void ExecuteCore(RazorCodeDocument codeDocument)
|
||||
{
|
||||
var irDocument = codeDocument.GetIRDocument();
|
||||
ThrowForMissingDependency(irDocument);
|
||||
|
||||
foreach (var pass in Passes)
|
||||
{
|
||||
pass.Execute(codeDocument, irDocument);
|
||||
}
|
||||
|
||||
codeDocument.SetIRDocument(irDocument);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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 System.Linq;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.Evolution
|
||||
{
|
||||
internal class DefaultRazorDocumentClassifierPhase : RazorEnginePhaseBase, IRazorDocumentClassifierPhase
|
||||
{
|
||||
public IRazorDocumentClassifierPass[] Passes { get; private set; }
|
||||
|
||||
protected override void OnIntialized()
|
||||
{
|
||||
Passes = Engine.Features.OfType<IRazorDocumentClassifierPass>().OrderBy(p => p.Order).ToArray();
|
||||
}
|
||||
|
||||
protected override void ExecuteCore(RazorCodeDocument codeDocument)
|
||||
{
|
||||
var irDocument = codeDocument.GetIRDocument();
|
||||
ThrowForMissingDependency(irDocument);
|
||||
|
||||
foreach (var pass in Passes)
|
||||
{
|
||||
pass.Execute(codeDocument, irDocument);
|
||||
}
|
||||
|
||||
codeDocument.SetIRDocument(irDocument);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5,13 +5,13 @@ using System.Linq;
|
|||
|
||||
namespace Microsoft.AspNetCore.Razor.Evolution
|
||||
{
|
||||
internal class DefaultRazorIRPhase : RazorEnginePhaseBase, IRazorIRPhase
|
||||
internal class DefaultRazorIROptimizationPhase : RazorEnginePhaseBase, IRazorIROptimizationPhase
|
||||
{
|
||||
public IRazorIRPass[] Passes { get; private set; }
|
||||
public IRazorIROptimizationPass[] Passes { get; private set; }
|
||||
|
||||
protected override void OnIntialized()
|
||||
{
|
||||
Passes = Engine.Features.OfType<IRazorIRPass>().OrderBy(p => p.Order).ToArray();
|
||||
Passes = Engine.Features.OfType<IRazorIROptimizationPass>().OrderBy(p => p.Order).ToArray();
|
||||
}
|
||||
|
||||
protected override void ExecuteCore(RazorCodeDocument codeDocument)
|
||||
|
|
@ -21,7 +21,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution
|
|||
|
||||
foreach (var pass in Passes)
|
||||
{
|
||||
irDocument = pass.Execute(codeDocument, irDocument);
|
||||
pass.Execute(codeDocument, irDocument);
|
||||
}
|
||||
|
||||
codeDocument.SetIRDocument(irDocument);
|
||||
|
|
@ -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 System.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Razor.Evolution.Intermediate;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.Evolution
|
||||
{
|
||||
internal class DirectiveRemovalIROptimizationPass : RazorIRPassBase, IRazorIROptimizationPass
|
||||
{
|
||||
public override int Order => RazorIRPass.DefaultFeatureOrder + 50;
|
||||
|
||||
public override void ExecuteCore(RazorCodeDocument codeDocument, DocumentIRNode irDocument)
|
||||
{
|
||||
var visitor = new Visitor();
|
||||
visitor.VisitDocument(irDocument);
|
||||
|
||||
foreach (var node in visitor.DirectiveNodes)
|
||||
{
|
||||
node.Parent.Children.Remove(node);
|
||||
}
|
||||
}
|
||||
|
||||
private class Visitor : RazorIRNodeWalker
|
||||
{
|
||||
public IList<DirectiveIRNode> DirectiveNodes { get; } = new List<DirectiveIRNode>();
|
||||
|
||||
public override void VisitDirective(DirectiveIRNode node)
|
||||
{
|
||||
DirectiveNodes.Add(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -7,30 +7,26 @@ using Microsoft.AspNetCore.Razor.Evolution.CodeGeneration;
|
|||
|
||||
namespace Microsoft.AspNetCore.Razor.Evolution
|
||||
{
|
||||
public abstract class DocumentClassifierPassBase : RazorIRPassBase
|
||||
public abstract class DocumentClassifierPassBase : RazorIRPassBase, IRazorDocumentClassifierPass
|
||||
{
|
||||
protected abstract string DocumentKind { get; }
|
||||
|
||||
public override int Order => RazorIRPass.DocumentClassifierOrder;
|
||||
|
||||
public sealed override DocumentIRNode ExecuteCore(RazorCodeDocument codeDocument, DocumentIRNode irDocument)
|
||||
public sealed override void ExecuteCore(RazorCodeDocument codeDocument, DocumentIRNode irDocument)
|
||||
{
|
||||
if (irDocument.DocumentKind != null)
|
||||
{
|
||||
return irDocument;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!IsMatch(codeDocument, irDocument))
|
||||
{
|
||||
return irDocument;
|
||||
return;
|
||||
}
|
||||
|
||||
irDocument.DocumentKind = DocumentKind;
|
||||
irDocument.Target = CreateTarget(codeDocument, irDocument.Options);
|
||||
|
||||
Rewrite(codeDocument, irDocument);
|
||||
|
||||
return irDocument;
|
||||
}
|
||||
|
||||
private void Rewrite(RazorCodeDocument codeDocument, DocumentIRNode irDocument)
|
||||
|
|
|
|||
|
|
@ -3,6 +3,14 @@
|
|||
|
||||
namespace Microsoft.AspNetCore.Razor.Evolution
|
||||
{
|
||||
/// <summary>
|
||||
/// Generates C# code using the IR document.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// After IR processing, the <see cref="IRazorCSharpLoweringPhase"/> transforms the IR document into generated C# code.
|
||||
/// At this time any directives or IR constructs that cannot be understood by code generation will result
|
||||
/// in an error.
|
||||
/// </remarks>
|
||||
public interface IRazorCSharpLoweringPhase : IRazorEnginePhase
|
||||
{
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,14 @@
|
|||
// 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
|
||||
{
|
||||
public interface IRazorDirectiveClassifierPass : IRazorEngineFeature
|
||||
{
|
||||
int Order { get; }
|
||||
|
||||
void Execute(RazorCodeDocument codeDocument, DocumentIRNode irDocument);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +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.
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.Evolution
|
||||
{
|
||||
/// <summary>
|
||||
/// Understands directive IR nodes and performs the necessary modifications to the IR document.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// The second phase of IR processing is directive classification. IR passes in this phase should interpret
|
||||
/// directives and processing them accordingly by transforming IR nodes or adding diagnostics to the IR. At
|
||||
/// this time the document kind has been identified, so any directive that can't be applied should trigger
|
||||
/// errors. If implementing a document kind that diverges from the standard structure of Razor documents
|
||||
/// it may be necessary to reimplement processing of default directives.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// <see cref="IRazorDirectiveClassifierPass"/> objects are executed according to an ascending ordering of the
|
||||
/// <see cref="IRazorDirectiveClassifierPass.Order"/> property. The default configuration of <see cref="RazorEngine"/>
|
||||
/// prescribes a logical ordering of specific phases of IR processing.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public interface IRazorDirectiveClassifierPhase : IRazorEnginePhase
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
// 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
|
||||
{
|
||||
public interface IRazorDocumentClassifierPass : IRazorEngineFeature
|
||||
{
|
||||
int Order { get; }
|
||||
|
||||
void Execute(RazorCodeDocument codeDocument, DocumentIRNode irDocument);
|
||||
}
|
||||
}
|
||||
|
|
@ -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 Microsoft.AspNetCore.Razor.Evolution.Intermediate;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.Evolution
|
||||
{
|
||||
/// <summary>
|
||||
/// Modifies the IR document to a desired structure.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// The first phase of IR procesing is document classification. IR passes in this phase should classify the
|
||||
/// document according to any relevant criteria (project configuration, file extension, directive) and modify
|
||||
/// the IR tree to suit the desired document shape. Document classifiers should also set
|
||||
/// <see cref="DocumentIRNode.DocumentKind"/> to prevent other classifiers from running. If no classifier
|
||||
/// matches the document, then it will be classified as "generic" and processed according to set
|
||||
/// of reasonable defaults.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// <see cref="IRazorDocumentClassifierPass"/> objects are executed according to an ascending ordering of the
|
||||
/// <see cref="IRazorDocumentClassifierPass.Order"/> property. The default configuration of <see cref="RazorEngine"/>
|
||||
/// prescribes a logical ordering of specific phases of IR processing.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public interface IRazorDocumentClassifierPhase : IRazorEnginePhase
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -3,6 +3,16 @@
|
|||
|
||||
namespace Microsoft.AspNetCore.Razor.Evolution
|
||||
{
|
||||
/// <summary>
|
||||
/// Generates the IR document from <see cref="RazorSyntaxTree"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The IR document is first produced by <see cref="IRazorIRLoweringPhase"/>. At this point no IR passes have
|
||||
/// been executed. The default <see cref="IRazorIRLoweringPhase"/> will perform a mechanical transformation
|
||||
/// of the syntax tree to IR resulting in a mostly flat structure. It is up to later phases to give the document
|
||||
/// structure and semantics according to a document kind. The default <see cref="IRazorIRLoweringPhase"/> is
|
||||
/// also responsible for synthesizing IR nodes for global cross-current concerns such as checksums or global settings.
|
||||
/// </remarks>
|
||||
public interface IRazorIRLoweringPhase : IRazorEnginePhase
|
||||
{
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,10 +5,10 @@ using Microsoft.AspNetCore.Razor.Evolution.Intermediate;
|
|||
|
||||
namespace Microsoft.AspNetCore.Razor.Evolution
|
||||
{
|
||||
public interface IRazorIRPass : IRazorEngineFeature
|
||||
public interface IRazorIROptimizationPass : IRazorEngineFeature
|
||||
{
|
||||
int Order { get; }
|
||||
|
||||
DocumentIRNode Execute(RazorCodeDocument codeDocument, DocumentIRNode irDocument);
|
||||
void Execute(RazorCodeDocument codeDocument, DocumentIRNode irDocument);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
// 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
|
||||
{
|
||||
/// <summary>
|
||||
/// Performs necessary modifications to the IR document to optimize code generation.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// The last phase of IR processing is lowering. IR passes in this phase perform some kind of transformation
|
||||
/// on the IR that optimizes the generated code. The key distinction here is that information may be discarded
|
||||
/// during this phase.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// <see cref="IRazorIROptimizationPass"/> objects are executed according to an ascending ordering of the
|
||||
/// <see cref="IRazorIROptimizationPass.Order"/> property. The default configuration of <see cref="RazorEngine"/>
|
||||
/// prescribes a logical ordering of specific phases of IR processing.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public interface IRazorIROptimizationPhase : IRazorEnginePhase
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.Evolution
|
||||
{
|
||||
public interface IRazorIRPhase : IRazorEnginePhase
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -6,18 +6,17 @@ using Microsoft.AspNetCore.Razor.Evolution.Intermediate;
|
|||
|
||||
namespace Microsoft.AspNetCore.Razor.Evolution
|
||||
{
|
||||
internal class RazorDesignTimeIRPass : RazorIRPassBase
|
||||
internal class RazorDesignTimeIRPass : RazorIRPassBase, IRazorDirectiveClassifierPass
|
||||
{
|
||||
internal const string DesignTimeVariable = "__o";
|
||||
|
||||
public override int Order => RazorIRPass.DirectiveClassifierOrder;
|
||||
// This needs to run before other directive classifiers.
|
||||
public override int Order => -10;
|
||||
|
||||
public override DocumentIRNode ExecuteCore(RazorCodeDocument codeDocument, DocumentIRNode irDocument)
|
||||
public override void ExecuteCore(RazorCodeDocument codeDocument, DocumentIRNode irDocument)
|
||||
{
|
||||
var walker = new DesignTimeHelperWalker();
|
||||
walker.VisitDocument(irDocument);
|
||||
|
||||
return irDocument;
|
||||
}
|
||||
|
||||
internal class DesignTimeHelperWalker : RazorIRNodeWalker
|
||||
|
|
@ -26,7 +25,6 @@ namespace Microsoft.AspNetCore.Razor.Evolution
|
|||
|
||||
public override void VisitClass(ClassDeclarationIRNode node)
|
||||
{
|
||||
|
||||
var designTimeHelperDeclaration = new CSharpStatementIRNode()
|
||||
{
|
||||
Content = $"private static {typeof(object).FullName} {DesignTimeVariable} = null;",
|
||||
|
|
|
|||
|
|
@ -51,7 +51,9 @@ namespace Microsoft.AspNetCore.Razor.Evolution
|
|||
builder.Phases.Add(new DefaultRazorParsingPhase());
|
||||
builder.Phases.Add(new DefaultRazorSyntaxTreePhase());
|
||||
builder.Phases.Add(new DefaultRazorIRLoweringPhase());
|
||||
builder.Phases.Add(new DefaultRazorIRPhase());
|
||||
builder.Phases.Add(new DefaultRazorDocumentClassifierPhase());
|
||||
builder.Phases.Add(new DefaultRazorDirectiveClassifierPhase());
|
||||
builder.Phases.Add(new DefaultRazorIROptimizationPhase());
|
||||
builder.Phases.Add(new DefaultRazorCSharpLoweringPhase());
|
||||
|
||||
// Syntax Tree passes
|
||||
|
|
@ -62,6 +64,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution
|
|||
// IR Passes
|
||||
builder.Features.Add(new DefaultDocumentClassifierPass());
|
||||
builder.Features.Add(new DefaultDirectiveIRPass());
|
||||
builder.Features.Add(new DirectiveRemovalIROptimizationPass());
|
||||
}
|
||||
|
||||
internal static void AddRuntimeDefaults(IRazorEngineBuilder builder)
|
||||
|
|
|
|||
|
|
@ -1,87 +1,18 @@
|
|||
// 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
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides constants for ordering of <see cref="IRazorIRPass"/> objects. When implementing an
|
||||
/// <see cref="IRazorIRPass"/>, choose a value for <see cref="IRazorIRPass.Order"/> according to
|
||||
/// the logical task that must be performed.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// <see cref="IRazorIRPass"/> objects are executed according to an ascending ordering of the
|
||||
/// <see cref="IRazorIRPass.Order"/> property. The default configuration of <see cref="RazorEngine"/>
|
||||
/// prescribes a logical ordering of specific phases of IR processing.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// The IR document is first produced by <see cref="IRazorIRLoweringPhase"/>. At this point no IR passes have
|
||||
/// been executed. The default <see cref="IRazorIRLoweringPhase"/> will perform a mechanical transformation
|
||||
/// of the syntax tree to IR resulting in a mostly flat structure. It is up to later phases to give the document
|
||||
/// structure and semantics according to a document kind. The default <see cref="IRazorIRLoweringPhase"/> is
|
||||
/// also responsible for synthesizing IR nodes for global cross-current concerns such as checksums or global settings.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// The first phase of IR procesing is document classification. IR passes in this phase should classify the
|
||||
/// document according to any relevant criteria (project configuration, file extension, directive) and modify
|
||||
/// the IR tree to suit the desired document shape. Document classifiers should also set
|
||||
/// <see cref="DocumentIRNode.DocumentKind"/> to prevent other classifiers from running. If no classifier
|
||||
/// matches the document, then it will be classified as "generic" and processed according to set
|
||||
/// of reasonable defaults.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// The second phase of IR processing is directive classification. IR passes in this phase should interpret
|
||||
/// directives and processing them accordingly by transforming IR nodes or adding diagnostics to the IR. At
|
||||
/// this time the document kind has been identified, so any directive that can't be applied should trigger
|
||||
/// errors. If implementing a document kind that diverges from the standard structure of Razor documents
|
||||
/// it may be necessary to reimplement processing of default directives.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// The last phase of IR processing is lowering. IR passes in this phase perform some kind of transformation
|
||||
/// on the IR that optimizes the generated code. The key distinction here is that information may be discarded
|
||||
/// during this phase.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Finally, the <see cref="IRazorCSharpLoweringPhase"/> transforms the IR document into generated C# code.
|
||||
/// At this time any directives or IR constructs that cannot be understood by code generation will result
|
||||
/// in an error.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public static class RazorIRPass
|
||||
{
|
||||
/// <summary>
|
||||
/// An <see cref="IRazorIRPass"/> that implements a document classifier should use this value as its
|
||||
/// <see cref="IRazorIRPass.Order"/>.
|
||||
/// </summary>
|
||||
public static readonly int DocumentClassifierOrder = 1100;
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="IRazorIRPass.Order"/> value used by the default document classifier.
|
||||
/// The default implementation of the <see cref="IRazorEngineFeature"/>s that run in a
|
||||
/// <see cref="IRazorEnginePhase"/> will use this value for its Order property.
|
||||
/// </summary>
|
||||
public static readonly int DefaultDocumentClassifierOrder = 1900;
|
||||
|
||||
/// <summary>
|
||||
/// An <see cref="IRazorIRPass"/> that implements a directive classifier should use this value as its
|
||||
/// <see cref="IRazorIRPass.Order"/>.
|
||||
/// </summary>
|
||||
public static readonly int DirectiveClassifierOrder = 2100;
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="IRazorIRPass.Order"/> value used by the default directive classifier.
|
||||
/// </summary>
|
||||
public static readonly int DefaultDirectiveClassifierOrder = 2900;
|
||||
|
||||
/// <summary>
|
||||
/// An <see cref="IRazorIRPass"/> that implements a lowering phase should use this value as its
|
||||
/// <see cref="IRazorIRPass.Order"/>.
|
||||
/// </summary>
|
||||
public static readonly int LoweringOrder = 4100;
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="IRazorIRPass.Order"/> value used by the default lowering phase.
|
||||
/// </summary>
|
||||
public static readonly int DefaultLoweringOrder = 4900;
|
||||
/// <remarks>
|
||||
/// This value is chosen in such a way that the default implementation runs after the other
|
||||
/// custom <see cref="IRazorEngineFeature"/> implementations for a particular <see cref="IRazorEnginePhase"/>.
|
||||
/// </remarks>
|
||||
public static readonly int DefaultFeatureOrder = 1000;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ using Microsoft.AspNetCore.Razor.Evolution.Intermediate;
|
|||
|
||||
namespace Microsoft.AspNetCore.Razor.Evolution
|
||||
{
|
||||
public abstract class RazorIRPassBase : IRazorIRPass
|
||||
public abstract class RazorIRPassBase
|
||||
{
|
||||
private RazorEngine _engine;
|
||||
|
||||
|
|
@ -25,7 +25,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution
|
|||
}
|
||||
}
|
||||
|
||||
public abstract int Order { get; }
|
||||
public virtual int Order { get; }
|
||||
|
||||
protected void ThrowForMissingDocumentDependency<TDocumentDependency>(TDocumentDependency value)
|
||||
{
|
||||
|
|
@ -55,7 +55,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution
|
|||
{
|
||||
}
|
||||
|
||||
public DocumentIRNode Execute(RazorCodeDocument codeDocument, DocumentIRNode irDocument)
|
||||
public void Execute(RazorCodeDocument codeDocument, DocumentIRNode irDocument)
|
||||
{
|
||||
if (codeDocument == null)
|
||||
{
|
||||
|
|
@ -72,9 +72,9 @@ namespace Microsoft.AspNetCore.Razor.Evolution
|
|||
throw new InvalidOperationException(Resources.FormatPhaseMustBeInitialized(nameof(Engine)));
|
||||
}
|
||||
|
||||
return ExecuteCore(codeDocument, irDocument);
|
||||
ExecuteCore(codeDocument, irDocument);
|
||||
}
|
||||
|
||||
public abstract DocumentIRNode ExecuteCore(RazorCodeDocument codeDocument, DocumentIRNode irDocument);
|
||||
public abstract void ExecuteCore(RazorCodeDocument codeDocument, DocumentIRNode irDocument);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,16 +7,14 @@ using Microsoft.AspNetCore.Razor.Evolution.Intermediate;
|
|||
|
||||
namespace Microsoft.AspNetCore.Razor.Evolution
|
||||
{
|
||||
internal class RazorPreallocatedTagHelperAttributeOptimizationPass : RazorIRPassBase
|
||||
internal class RazorPreallocatedTagHelperAttributeOptimizationPass : RazorIRPassBase, IRazorIROptimizationPass
|
||||
{
|
||||
public override int Order => RazorIRPass.DefaultLoweringOrder;
|
||||
public override int Order => RazorIRPass.DefaultFeatureOrder;
|
||||
|
||||
public override DocumentIRNode ExecuteCore(RazorCodeDocument codeDocument, DocumentIRNode irDocument)
|
||||
public override void ExecuteCore(RazorCodeDocument codeDocument, DocumentIRNode irDocument)
|
||||
{
|
||||
var walker = new PreallocatedTagHelperWalker();
|
||||
walker.VisitDocument(irDocument);
|
||||
|
||||
return irDocument;
|
||||
}
|
||||
|
||||
internal class PreallocatedTagHelperWalker : RazorIRNodeWalker
|
||||
|
|
@ -9,31 +9,6 @@ namespace Microsoft.AspNetCore.Razor.Evolution
|
|||
{
|
||||
public class DefaultDirectiveIRPassTest
|
||||
{
|
||||
[Fact]
|
||||
public void Execute_MutatesIRDocument()
|
||||
{
|
||||
// Arrange
|
||||
var content =
|
||||
@"@inherits Hello<World[]>
|
||||
@functions {
|
||||
var value = true;
|
||||
}";
|
||||
var sourceDocument = TestRazorSourceDocument.Create(content);
|
||||
var codeDocument = RazorCodeDocument.Create(sourceDocument);
|
||||
var originalIRDocument = Lower(codeDocument);
|
||||
var defaultEngine = RazorEngine.Create();
|
||||
var pass = new DefaultDirectiveIRPass()
|
||||
{
|
||||
Engine = defaultEngine,
|
||||
};
|
||||
|
||||
// Act
|
||||
var irDocument = pass.Execute(codeDocument, originalIRDocument);
|
||||
|
||||
// Assert
|
||||
Assert.Same(originalIRDocument, irDocument);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Execute_Inherits_SetsClassDeclarationBaseType()
|
||||
{
|
||||
|
|
@ -41,7 +16,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution
|
|||
var content = "@inherits Hello<World[]>";
|
||||
var sourceDocument = TestRazorSourceDocument.Create(content);
|
||||
var codeDocument = RazorCodeDocument.Create(sourceDocument);
|
||||
var originalIRDocument = Lower(codeDocument);
|
||||
var irDocument = Lower(codeDocument);
|
||||
var defaultEngine = RazorEngine.Create();
|
||||
var pass = new DefaultDirectiveIRPass()
|
||||
{
|
||||
|
|
@ -49,7 +24,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution
|
|||
};
|
||||
|
||||
// Act
|
||||
var irDocument = pass.Execute(codeDocument, originalIRDocument);
|
||||
pass.Execute(codeDocument, irDocument);
|
||||
|
||||
// Assert
|
||||
Children(irDocument,
|
||||
|
|
@ -71,7 +46,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution
|
|||
var content = "@functions { var value = true; }";
|
||||
var sourceDocument = TestRazorSourceDocument.Create(content);
|
||||
var codeDocument = RazorCodeDocument.Create(sourceDocument);
|
||||
var originalIRDocument = Lower(codeDocument);
|
||||
var irDocument = Lower(codeDocument);
|
||||
var defaultEngine = RazorEngine.Create();
|
||||
var pass = new DefaultDirectiveIRPass()
|
||||
{
|
||||
|
|
@ -79,7 +54,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution
|
|||
};
|
||||
|
||||
// Act
|
||||
var irDocument = pass.Execute(codeDocument, originalIRDocument);
|
||||
pass.Execute(codeDocument, irDocument);
|
||||
|
||||
// Assert
|
||||
Children(irDocument,
|
||||
|
|
@ -105,7 +80,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution
|
|||
var content = "@section Header { <p>Hello World</p> }";
|
||||
var sourceDocument = TestRazorSourceDocument.Create(content);
|
||||
var codeDocument = RazorCodeDocument.Create(sourceDocument);
|
||||
var originalIRDocument = Lower(codeDocument);
|
||||
var irDocument = Lower(codeDocument);
|
||||
var defaultEngine = RazorEngine.Create();
|
||||
var pass = new DefaultDirectiveIRPass()
|
||||
{
|
||||
|
|
@ -113,7 +88,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution
|
|||
};
|
||||
|
||||
// Act
|
||||
var irDocument = pass.Execute(codeDocument, originalIRDocument);
|
||||
pass.Execute(codeDocument, irDocument);
|
||||
|
||||
// Assert
|
||||
Children(irDocument,
|
||||
|
|
@ -140,7 +115,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution
|
|||
var designTimeEngine = RazorEngine.CreateDesignTime();
|
||||
var sourceDocument = TestRazorSourceDocument.Create(content);
|
||||
var codeDocument = RazorCodeDocument.Create(sourceDocument);
|
||||
var originalIRDocument = Lower(codeDocument, designTimeEngine);
|
||||
var irDocument = Lower(codeDocument, designTimeEngine);
|
||||
var defaultEngine = RazorEngine.Create();
|
||||
var pass = new DefaultDirectiveIRPass()
|
||||
{
|
||||
|
|
@ -148,7 +123,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution
|
|||
};
|
||||
|
||||
// Act
|
||||
var irDocument = pass.Execute(codeDocument, originalIRDocument);
|
||||
pass.Execute(codeDocument, irDocument);
|
||||
|
||||
// Assert
|
||||
Children(irDocument,
|
||||
|
|
@ -167,41 +142,6 @@ namespace Microsoft.AspNetCore.Razor.Evolution
|
|||
node => CSharpStatement("});", node));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Execute_Custom_RemovesDirectiveIRNodeFromIRDocument()
|
||||
{
|
||||
// Arrange
|
||||
var content = "@custom Hello";
|
||||
var sourceDocument = TestRazorSourceDocument.Create(content);
|
||||
var codeDocument = RazorCodeDocument.Create(sourceDocument);
|
||||
var defaultEngine = RazorEngine.Create(b =>
|
||||
{
|
||||
var customDirective = DirectiveDescriptorBuilder.Create("custom").AddString().Build();
|
||||
b.AddDirective(customDirective);
|
||||
});
|
||||
var originalIRDocument = Lower(codeDocument, defaultEngine);
|
||||
var pass = new DefaultDirectiveIRPass()
|
||||
{
|
||||
Engine = defaultEngine,
|
||||
};
|
||||
|
||||
// Act
|
||||
var irDocument = pass.Execute(codeDocument, originalIRDocument);
|
||||
|
||||
// Assert
|
||||
Children(irDocument,
|
||||
node => Assert.IsType<ChecksumIRNode>(node),
|
||||
node => Assert.IsType<NamespaceDeclarationIRNode>(node));
|
||||
var @namespace = irDocument.Children[1];
|
||||
Children(@namespace,
|
||||
node => Assert.IsType<UsingStatementIRNode>(node),
|
||||
node => Assert.IsType<UsingStatementIRNode>(node),
|
||||
node => Assert.IsType<ClassDeclarationIRNode>(node));
|
||||
var @class = @namespace.Children[2];
|
||||
var method = SingleChild<RazorMethodDeclarationIRNode>(@class);
|
||||
Assert.Empty(method.Children);
|
||||
}
|
||||
|
||||
private static DocumentIRNode Lower(RazorCodeDocument codeDocument)
|
||||
{
|
||||
var engine = RazorEngine.Create();
|
||||
|
|
@ -216,7 +156,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution
|
|||
var phase = engine.Phases[i];
|
||||
phase.Execute(codeDocument);
|
||||
|
||||
if (phase is IRazorIRLoweringPhase)
|
||||
if (phase is IRazorDocumentClassifierPhase)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
|
@ -225,8 +165,6 @@ namespace Microsoft.AspNetCore.Razor.Evolution
|
|||
var irDocument = codeDocument.GetIRDocument();
|
||||
Assert.NotNull(irDocument);
|
||||
|
||||
// These tests depend on the document->namespace->class structure.
|
||||
irDocument = new DefaultDocumentClassifierPass() { Engine = engine, }.Execute(codeDocument, irDocument);
|
||||
return irDocument;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution
|
|||
var pass = new DefaultInstrumentationPass();
|
||||
|
||||
// Act
|
||||
var result = pass.ExecuteCore(TestRazorCodeDocument.CreateEmpty(), irDocument);
|
||||
pass.ExecuteCore(TestRazorCodeDocument.CreateEmpty(), irDocument);
|
||||
|
||||
// Assert
|
||||
Children(
|
||||
|
|
@ -50,7 +50,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution
|
|||
var pass = new DefaultInstrumentationPass();
|
||||
|
||||
// Act
|
||||
var result = pass.ExecuteCore(TestRazorCodeDocument.CreateEmpty(), irDocument);
|
||||
pass.ExecuteCore(TestRazorCodeDocument.CreateEmpty(), irDocument);
|
||||
|
||||
// Assert
|
||||
Children(
|
||||
|
|
@ -77,7 +77,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution
|
|||
var pass = new DefaultInstrumentationPass();
|
||||
|
||||
// Act
|
||||
var result = pass.ExecuteCore(TestRazorCodeDocument.CreateEmpty(), irDocument);
|
||||
pass.ExecuteCore(TestRazorCodeDocument.CreateEmpty(), irDocument);
|
||||
|
||||
// Assert
|
||||
Children(
|
||||
|
|
@ -103,7 +103,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution
|
|||
var pass = new DefaultInstrumentationPass();
|
||||
|
||||
// Act
|
||||
var result = pass.ExecuteCore(TestRazorCodeDocument.CreateEmpty(), irDocument);
|
||||
pass.ExecuteCore(TestRazorCodeDocument.CreateEmpty(), irDocument);
|
||||
|
||||
// Assert
|
||||
Children(
|
||||
|
|
@ -138,7 +138,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution
|
|||
var pass = new DefaultInstrumentationPass();
|
||||
|
||||
// Act
|
||||
var result = pass.ExecuteCore(TestRazorCodeDocument.CreateEmpty(), irDocument);
|
||||
pass.ExecuteCore(TestRazorCodeDocument.CreateEmpty(), irDocument);
|
||||
|
||||
// Assert
|
||||
Children(
|
||||
|
|
@ -185,7 +185,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution
|
|||
var pass = new DefaultInstrumentationPass();
|
||||
|
||||
// Act
|
||||
var result = pass.ExecuteCore(TestRazorCodeDocument.CreateEmpty(), irDocument);
|
||||
pass.ExecuteCore(TestRazorCodeDocument.CreateEmpty(), irDocument);
|
||||
|
||||
// Assert
|
||||
Children(
|
||||
|
|
@ -222,7 +222,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution
|
|||
var pass = new DefaultInstrumentationPass();
|
||||
|
||||
// Act
|
||||
var result = pass.ExecuteCore(TestRazorCodeDocument.CreateEmpty(), irDocument);
|
||||
pass.ExecuteCore(TestRazorCodeDocument.CreateEmpty(), irDocument);
|
||||
|
||||
// Assert
|
||||
Children(
|
||||
|
|
@ -252,7 +252,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution
|
|||
var pass = new DefaultInstrumentationPass();
|
||||
|
||||
// Act
|
||||
var result = pass.ExecuteCore(TestRazorCodeDocument.CreateEmpty(), irDocument);
|
||||
pass.ExecuteCore(TestRazorCodeDocument.CreateEmpty(), irDocument);
|
||||
|
||||
// Assert
|
||||
Children(
|
||||
|
|
@ -283,7 +283,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution
|
|||
var pass = new DefaultInstrumentationPass();
|
||||
|
||||
// Act
|
||||
var result = pass.ExecuteCore(TestRazorCodeDocument.CreateEmpty(), irDocument);
|
||||
pass.ExecuteCore(TestRazorCodeDocument.CreateEmpty(), irDocument);
|
||||
|
||||
// Assert
|
||||
Children(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,102 @@
|
|||
// 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 Microsoft.AspNetCore.Razor.Evolution.Intermediate;
|
||||
using Microsoft.AspNetCore.Testing;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.Evolution
|
||||
{
|
||||
public class DefaultRazorDirectiveClassifierPhaseTest
|
||||
{
|
||||
[Fact]
|
||||
public void OnInitialized_OrdersPassesInAscendingOrder()
|
||||
{
|
||||
// Arrange & Act
|
||||
var phase = new DefaultRazorDirectiveClassifierPhase();
|
||||
|
||||
var first = Mock.Of<IRazorDirectiveClassifierPass>(p => p.Order == 15);
|
||||
var second = Mock.Of<IRazorDirectiveClassifierPass>(p => p.Order == 17);
|
||||
|
||||
var engine = RazorEngine.CreateEmpty(b =>
|
||||
{
|
||||
b.Phases.Add(phase);
|
||||
|
||||
b.Features.Add(second);
|
||||
b.Features.Add(first);
|
||||
});
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
phase.Passes,
|
||||
p => Assert.Same(first, p),
|
||||
p => Assert.Same(second, p));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Execute_ThrowsForMissingDependency()
|
||||
{
|
||||
// Arrange
|
||||
var phase = new DefaultRazorDirectiveClassifierPhase();
|
||||
|
||||
var engine = RazorEngine.CreateEmpty(b => b.Phases.Add(phase));
|
||||
|
||||
var codeDocument = TestRazorCodeDocument.CreateEmpty();
|
||||
|
||||
// Act & Assert
|
||||
ExceptionAssert.Throws<InvalidOperationException>(
|
||||
() => phase.Execute(codeDocument),
|
||||
$"The '{nameof(DefaultRazorDirectiveClassifierPhase)}' phase requires a '{nameof(DocumentIRNode)}' " +
|
||||
$"provided by the '{nameof(RazorCodeDocument)}'.");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Execute_ExecutesPhasesInOrder()
|
||||
{
|
||||
// Arrange
|
||||
var codeDocument = TestRazorCodeDocument.CreateEmpty();
|
||||
|
||||
// We're going to set up mocks to simulate a sequence of passes. We don't care about
|
||||
// what's in the nodes, we're just going to look at the identity via strict mocks.
|
||||
var originalNode = new DocumentIRNode();
|
||||
var firstPassNode = new DocumentIRNode();
|
||||
var secondPassNode = new DocumentIRNode();
|
||||
codeDocument.SetIRDocument(originalNode);
|
||||
|
||||
var firstPass = new Mock<IRazorDirectiveClassifierPass>(MockBehavior.Strict);
|
||||
firstPass.SetupGet(m => m.Order).Returns(0);
|
||||
firstPass.SetupProperty(m => m.Engine);
|
||||
firstPass.Setup(m => m.Execute(codeDocument, originalNode)).Callback(() =>
|
||||
{
|
||||
originalNode.Children.Add(firstPassNode);
|
||||
});
|
||||
|
||||
var secondPass = new Mock<IRazorDirectiveClassifierPass>(MockBehavior.Strict);
|
||||
secondPass.SetupGet(m => m.Order).Returns(1);
|
||||
secondPass.SetupProperty(m => m.Engine);
|
||||
secondPass.Setup(m => m.Execute(codeDocument, originalNode)).Callback(() =>
|
||||
{
|
||||
// Works only when the first pass has run before this.
|
||||
originalNode.Children[0].Children.Add(secondPassNode);
|
||||
});
|
||||
|
||||
var phase = new DefaultRazorDirectiveClassifierPhase();
|
||||
|
||||
var engine = RazorEngine.CreateEmpty(b =>
|
||||
{
|
||||
b.Phases.Add(phase);
|
||||
|
||||
b.Features.Add(firstPass.Object);
|
||||
b.Features.Add(secondPass.Object);
|
||||
});
|
||||
|
||||
// Act
|
||||
phase.Execute(codeDocument);
|
||||
|
||||
// Assert
|
||||
Assert.Same(secondPassNode, codeDocument.GetIRDocument().Children[0].Children[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,102 @@
|
|||
// 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 Microsoft.AspNetCore.Razor.Evolution.Intermediate;
|
||||
using Microsoft.AspNetCore.Testing;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.Evolution
|
||||
{
|
||||
public class DefaultRazorDocumentClassifierPhaseTest
|
||||
{
|
||||
[Fact]
|
||||
public void OnInitialized_OrdersPassesInAscendingOrder()
|
||||
{
|
||||
// Arrange & Act
|
||||
var phase = new DefaultRazorDocumentClassifierPhase();
|
||||
|
||||
var first = Mock.Of<IRazorDocumentClassifierPass>(p => p.Order == 15);
|
||||
var second = Mock.Of<IRazorDocumentClassifierPass>(p => p.Order == 17);
|
||||
|
||||
var engine = RazorEngine.CreateEmpty(b =>
|
||||
{
|
||||
b.Phases.Add(phase);
|
||||
|
||||
b.Features.Add(second);
|
||||
b.Features.Add(first);
|
||||
});
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
phase.Passes,
|
||||
p => Assert.Same(first, p),
|
||||
p => Assert.Same(second, p));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Execute_ThrowsForMissingDependency()
|
||||
{
|
||||
// Arrange
|
||||
var phase = new DefaultRazorDocumentClassifierPhase();
|
||||
|
||||
var engine = RazorEngine.CreateEmpty(b => b.Phases.Add(phase));
|
||||
|
||||
var codeDocument = TestRazorCodeDocument.CreateEmpty();
|
||||
|
||||
// Act & Assert
|
||||
ExceptionAssert.Throws<InvalidOperationException>(
|
||||
() => phase.Execute(codeDocument),
|
||||
$"The '{nameof(DefaultRazorDocumentClassifierPhase)}' phase requires a '{nameof(DocumentIRNode)}' " +
|
||||
$"provided by the '{nameof(RazorCodeDocument)}'.");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Execute_ExecutesPhasesInOrder()
|
||||
{
|
||||
// Arrange
|
||||
var codeDocument = TestRazorCodeDocument.CreateEmpty();
|
||||
|
||||
// We're going to set up mocks to simulate a sequence of passes. We don't care about
|
||||
// what's in the nodes, we're just going to look at the identity via strict mocks.
|
||||
var originalNode = new DocumentIRNode();
|
||||
var firstPassNode = new DocumentIRNode();
|
||||
var secondPassNode = new DocumentIRNode();
|
||||
codeDocument.SetIRDocument(originalNode);
|
||||
|
||||
var firstPass = new Mock<IRazorDocumentClassifierPass>(MockBehavior.Strict);
|
||||
firstPass.SetupGet(m => m.Order).Returns(0);
|
||||
firstPass.SetupProperty(m => m.Engine);
|
||||
firstPass.Setup(m => m.Execute(codeDocument, originalNode)).Callback(() =>
|
||||
{
|
||||
originalNode.Children.Add(firstPassNode);
|
||||
});
|
||||
|
||||
var secondPass = new Mock<IRazorDocumentClassifierPass>(MockBehavior.Strict);
|
||||
secondPass.SetupGet(m => m.Order).Returns(1);
|
||||
secondPass.SetupProperty(m => m.Engine);
|
||||
secondPass.Setup(m => m.Execute(codeDocument, originalNode)).Callback(() =>
|
||||
{
|
||||
// Works only when the first pass has run before this.
|
||||
originalNode.Children[0].Children.Add(secondPassNode);
|
||||
});
|
||||
|
||||
var phase = new DefaultRazorDocumentClassifierPhase();
|
||||
|
||||
var engine = RazorEngine.CreateEmpty(b =>
|
||||
{
|
||||
b.Phases.Add(phase);
|
||||
|
||||
b.Features.Add(firstPass.Object);
|
||||
b.Features.Add(secondPass.Object);
|
||||
});
|
||||
|
||||
// Act
|
||||
phase.Execute(codeDocument);
|
||||
|
||||
// Assert
|
||||
Assert.Same(secondPassNode, codeDocument.GetIRDocument().Children[0].Children[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -9,16 +9,16 @@ using Xunit;
|
|||
|
||||
namespace Microsoft.AspNetCore.Razor.Evolution
|
||||
{
|
||||
public class DefaultRazorIRPhaseTest
|
||||
public class DefaultRazorIROptimizationPhaseTest
|
||||
{
|
||||
[Fact]
|
||||
public void OnInitialized_OrdersPassesInAscendingOrder()
|
||||
{
|
||||
// Arrange & Act
|
||||
var phase = new DefaultRazorIRPhase();
|
||||
var phase = new DefaultRazorIROptimizationPhase();
|
||||
|
||||
var first = Mock.Of<IRazorIRPass>(p => p.Order == 15);
|
||||
var second = Mock.Of<IRazorIRPass>(p => p.Order == 17);
|
||||
var first = Mock.Of<IRazorIROptimizationPass>(p => p.Order == 15);
|
||||
var second = Mock.Of<IRazorIROptimizationPass>(p => p.Order == 17);
|
||||
|
||||
var engine = RazorEngine.CreateEmpty(b =>
|
||||
{
|
||||
|
|
@ -39,7 +39,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution
|
|||
public void Execute_ThrowsForMissingDependency()
|
||||
{
|
||||
// Arrange
|
||||
var phase = new DefaultRazorIRPhase();
|
||||
var phase = new DefaultRazorIROptimizationPhase();
|
||||
|
||||
var engine = RazorEngine.CreateEmpty(b => b.Phases.Add(phase));
|
||||
|
||||
|
|
@ -48,7 +48,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution
|
|||
// Act & Assert
|
||||
ExceptionAssert.Throws<InvalidOperationException>(
|
||||
() => phase.Execute(codeDocument),
|
||||
$"The '{nameof(DefaultRazorIRPhase)}' phase requires a '{nameof(DocumentIRNode)}' " +
|
||||
$"The '{nameof(DefaultRazorIROptimizationPhase)}' phase requires a '{nameof(DocumentIRNode)}' " +
|
||||
$"provided by the '{nameof(RazorCodeDocument)}'.");
|
||||
}
|
||||
|
||||
|
|
@ -65,17 +65,24 @@ namespace Microsoft.AspNetCore.Razor.Evolution
|
|||
var secondPassNode = new DocumentIRNode();
|
||||
codeDocument.SetIRDocument(originalNode);
|
||||
|
||||
var firstPass = new Mock<IRazorIRPass>(MockBehavior.Strict);
|
||||
var firstPass = new Mock<IRazorIROptimizationPass>(MockBehavior.Strict);
|
||||
firstPass.SetupGet(m => m.Order).Returns(0);
|
||||
firstPass.SetupProperty(m => m.Engine);
|
||||
firstPass.Setup(m => m.Execute(codeDocument, originalNode)).Returns(firstPassNode);
|
||||
firstPass.Setup(m => m.Execute(codeDocument, originalNode)).Callback(() =>
|
||||
{
|
||||
originalNode.Children.Add(firstPassNode);
|
||||
});
|
||||
|
||||
var secondPass = new Mock<IRazorIRPass>(MockBehavior.Strict);
|
||||
var secondPass = new Mock<IRazorIROptimizationPass>(MockBehavior.Strict);
|
||||
secondPass.SetupGet(m => m.Order).Returns(1);
|
||||
secondPass.SetupProperty(m => m.Engine);
|
||||
secondPass.Setup(m => m.Execute(codeDocument, firstPassNode)).Returns(secondPassNode);
|
||||
secondPass.Setup(m => m.Execute(codeDocument, originalNode)).Callback(() =>
|
||||
{
|
||||
// Works only when the first pass has run before this.
|
||||
originalNode.Children[0].Children.Add(secondPassNode);
|
||||
});
|
||||
|
||||
var phase = new DefaultRazorIRPhase();
|
||||
var phase = new DefaultRazorIROptimizationPhase();
|
||||
|
||||
var engine = RazorEngine.CreateEmpty(b =>
|
||||
{
|
||||
|
|
@ -89,7 +96,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution
|
|||
phase.Execute(codeDocument);
|
||||
|
||||
// Assert
|
||||
Assert.Same(secondPassNode, codeDocument.GetIRDocument());
|
||||
Assert.Same(secondPassNode, codeDocument.GetIRDocument().Children[0].Children[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,102 @@
|
|||
// 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 Microsoft.AspNetCore.Razor.Evolution.Intermediate;
|
||||
using Xunit;
|
||||
using static Microsoft.AspNetCore.Razor.Evolution.Intermediate.RazorIRAssert;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.Evolution
|
||||
{
|
||||
public class DirectiveRemovalIROptimizationPassTest
|
||||
{
|
||||
[Fact]
|
||||
public void Execute_Custom_RemovesDirectiveIRNodeFromIRDocument()
|
||||
{
|
||||
// Arrange
|
||||
var content = "@custom Hello";
|
||||
var sourceDocument = TestRazorSourceDocument.Create(content);
|
||||
var codeDocument = RazorCodeDocument.Create(sourceDocument);
|
||||
var defaultEngine = RazorEngine.Create(b =>
|
||||
{
|
||||
var customDirective = DirectiveDescriptorBuilder.Create("custom").AddString().Build();
|
||||
b.AddDirective(customDirective);
|
||||
});
|
||||
var irDocument = Lower(codeDocument, defaultEngine);
|
||||
var pass = new DirectiveRemovalIROptimizationPass()
|
||||
{
|
||||
Engine = defaultEngine,
|
||||
};
|
||||
|
||||
// Act
|
||||
pass.Execute(codeDocument, irDocument);
|
||||
|
||||
// Assert
|
||||
Children(irDocument,
|
||||
node => Assert.IsType<ChecksumIRNode>(node),
|
||||
node => Assert.IsType<NamespaceDeclarationIRNode>(node));
|
||||
var @namespace = irDocument.Children[1];
|
||||
Children(@namespace,
|
||||
node => Assert.IsType<UsingStatementIRNode>(node),
|
||||
node => Assert.IsType<UsingStatementIRNode>(node),
|
||||
node => Assert.IsType<ClassDeclarationIRNode>(node));
|
||||
var @class = @namespace.Children[2];
|
||||
var method = SingleChild<RazorMethodDeclarationIRNode>(@class);
|
||||
Assert.Empty(method.Children);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Execute_MultipleCustomDirectives_RemovesDirectiveIRNodesFromIRDocument()
|
||||
{
|
||||
// Arrange
|
||||
var content = "@custom Hello" + Environment.NewLine + "@custom World";
|
||||
var sourceDocument = TestRazorSourceDocument.Create(content);
|
||||
var codeDocument = RazorCodeDocument.Create(sourceDocument);
|
||||
var defaultEngine = RazorEngine.Create(b =>
|
||||
{
|
||||
var customDirective = DirectiveDescriptorBuilder.Create("custom").AddString().Build();
|
||||
b.AddDirective(customDirective);
|
||||
});
|
||||
var irDocument = Lower(codeDocument, defaultEngine);
|
||||
var pass = new DirectiveRemovalIROptimizationPass()
|
||||
{
|
||||
Engine = defaultEngine,
|
||||
};
|
||||
|
||||
// Act
|
||||
pass.Execute(codeDocument, irDocument);
|
||||
|
||||
// Assert
|
||||
Children(irDocument,
|
||||
node => Assert.IsType<ChecksumIRNode>(node),
|
||||
node => Assert.IsType<NamespaceDeclarationIRNode>(node));
|
||||
var @namespace = irDocument.Children[1];
|
||||
Children(@namespace,
|
||||
node => Assert.IsType<UsingStatementIRNode>(node),
|
||||
node => Assert.IsType<UsingStatementIRNode>(node),
|
||||
node => Assert.IsType<ClassDeclarationIRNode>(node));
|
||||
var @class = @namespace.Children[2];
|
||||
var method = SingleChild<RazorMethodDeclarationIRNode>(@class);
|
||||
Assert.Empty(method.Children);
|
||||
}
|
||||
|
||||
private static DocumentIRNode Lower(RazorCodeDocument codeDocument, RazorEngine engine)
|
||||
{
|
||||
for (var i = 0; i < engine.Phases.Count; i++)
|
||||
{
|
||||
var phase = engine.Phases[i];
|
||||
phase.Execute(codeDocument);
|
||||
|
||||
if (phase is IRazorDirectiveClassifierPhase)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var irDocument = codeDocument.GetIRDocument();
|
||||
Assert.NotNull(irDocument);
|
||||
|
||||
return irDocument;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -224,6 +224,8 @@ namespace Microsoft.AspNetCore.Razor.Evolution
|
|||
|
||||
private class TestDocumentClassifierPass : DocumentClassifierPassBase
|
||||
{
|
||||
public override int Order => RazorIRPass.DefaultFeatureOrder;
|
||||
|
||||
public bool ShouldMatch { get; set; } = true;
|
||||
|
||||
public string Namespace { get; set; }
|
||||
|
|
|
|||
|
|
@ -1530,16 +1530,12 @@ namespace Microsoft.AspNetCore.Razor.Evolution.IntegrationTests
|
|||
AssertDesignTimeDocumentMatchBaseline(document);
|
||||
}
|
||||
|
||||
private class ApiSetsIRTestAdapter : RazorIRPassBase
|
||||
private class ApiSetsIRTestAdapter : RazorIRPassBase, IRazorIROptimizationPass
|
||||
{
|
||||
public override int Order => RazorIRPass.LoweringOrder;
|
||||
|
||||
public override DocumentIRNode ExecuteCore(RazorCodeDocument codeDocument, DocumentIRNode irDocument)
|
||||
public override void ExecuteCore(RazorCodeDocument codeDocument, DocumentIRNode irDocument)
|
||||
{
|
||||
var walker = new ApiSetsIRWalker();
|
||||
walker.Visit(irDocument);
|
||||
|
||||
return irDocument;
|
||||
}
|
||||
|
||||
private class ApiSetsIRWalker : RazorIRNodeWalker
|
||||
|
|
|
|||
|
|
@ -141,6 +141,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution
|
|||
feature => Assert.IsType<TagHelperBinderSyntaxTreePass>(feature),
|
||||
feature => Assert.IsType<DefaultDocumentClassifierPass>(feature),
|
||||
feature => Assert.IsType<DefaultDirectiveIRPass>(feature),
|
||||
feature => Assert.IsType<DirectiveRemovalIROptimizationPass>(feature),
|
||||
feature => Assert.IsType<RazorPreallocatedTagHelperAttributeOptimizationPass>(feature));
|
||||
}
|
||||
|
||||
|
|
@ -151,7 +152,9 @@ namespace Microsoft.AspNetCore.Razor.Evolution
|
|||
phase => Assert.IsType<DefaultRazorParsingPhase>(phase),
|
||||
phase => Assert.IsType<DefaultRazorSyntaxTreePhase>(phase),
|
||||
phase => Assert.IsType<DefaultRazorIRLoweringPhase>(phase),
|
||||
phase => Assert.IsType<DefaultRazorIRPhase>(phase),
|
||||
phase => Assert.IsType<DefaultRazorDocumentClassifierPhase>(phase),
|
||||
phase => Assert.IsType<DefaultRazorDirectiveClassifierPhase>(phase),
|
||||
phase => Assert.IsType<DefaultRazorIROptimizationPhase>(phase),
|
||||
phase => Assert.IsType<DefaultRazorCSharpLoweringPhase>(phase));
|
||||
}
|
||||
|
||||
|
|
@ -164,6 +167,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution
|
|||
feature => Assert.IsType<TagHelperBinderSyntaxTreePass>(feature),
|
||||
feature => Assert.IsType<DefaultDocumentClassifierPass>(feature),
|
||||
feature => Assert.IsType<DefaultDirectiveIRPass>(feature),
|
||||
feature => Assert.IsType<DirectiveRemovalIROptimizationPass>(feature),
|
||||
feature => Assert.IsType<RazorEngine.ConfigureDesignTimeOptions>(feature),
|
||||
feature => Assert.IsType<RazorDesignTimeIRPass>(feature));
|
||||
}
|
||||
|
|
@ -175,7 +179,9 @@ namespace Microsoft.AspNetCore.Razor.Evolution
|
|||
phase => Assert.IsType<DefaultRazorParsingPhase>(phase),
|
||||
phase => Assert.IsType<DefaultRazorSyntaxTreePhase>(phase),
|
||||
phase => Assert.IsType<DefaultRazorIRLoweringPhase>(phase),
|
||||
phase => Assert.IsType<DefaultRazorIRPhase>(phase),
|
||||
phase => Assert.IsType<DefaultRazorDocumentClassifierPhase>(phase),
|
||||
phase => Assert.IsType<DefaultRazorDirectiveClassifierPhase>(phase),
|
||||
phase => Assert.IsType<DefaultRazorIROptimizationPhase>(phase),
|
||||
phase => Assert.IsType<DefaultRazorCSharpLoweringPhase>(phase));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue