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:
Ajay Bhargav Baaskaran 2017-02-10 12:39:58 -08:00
parent 80172c641d
commit 8ac5468714
32 changed files with 675 additions and 271 deletions

View File

@ -148,7 +148,6 @@ namespace Microsoft.AspNetCore.Razor.Evolution.CodeGeneration
Context.Writer.SetIndent(originalIndent);
}
Context.Writer.WriteLine("))();");
}
public override void VisitTemplate(TemplateIRNode node)

View File

@ -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);
}
}
}

View File

@ -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";

View File

@ -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)

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -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);

View File

@ -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);
}
}
}
}

View File

@ -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)

View File

@ -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
{
}

View File

@ -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);
}
}

View File

@ -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
{
}
}

View File

@ -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);
}
}

View File

@ -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 &quot;generic&quot; 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
{
}
}

View File

@ -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
{
}

View File

@ -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);
}
}

View File

@ -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
{
}
}

View File

@ -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
{
}
}

View File

@ -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;",

View File

@ -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)

View File

@ -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 &quot;generic&quot; 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;
}
}

View File

@ -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);
}
}

View File

@ -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

View File

@ -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;
}
}

View File

@ -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(

View File

@ -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]);
}
}
}

View File

@ -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]);
}
}
}

View File

@ -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]);
}
}
}

View File

@ -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;
}
}
}

View File

@ -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; }

View File

@ -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

View File

@ -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));
}
}