Add IR phase

This change adds a phase which runs IR passes. Design and code are almost
exactly the same as the existing SyntaxTree phase. However all of this is
public because the IR is public API.
This commit is contained in:
Ryan Nowak 2016-11-29 21:28:27 -08:00
parent 518378f499
commit 46018f9512
7 changed files with 151 additions and 3 deletions

View File

@ -2,7 +2,6 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Razor.Evolution.Intermediate;
using Microsoft.AspNetCore.Razor.Evolution.Legacy;

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 DefaultRazorIRPhase : RazorEnginePhaseBase
{
public IRazorIRPass[] Passes { get; private set; }
protected override void OnIntialized()
{
Passes = Engine.Features.OfType<IRazorIRPass>().OrderBy(p => p.Order).ToArray();
}
protected override void ExecuteCore(RazorCodeDocument codeDocument)
{
var irDocument = codeDocument.GetIRDocument();
ThrowForMissingDependency(irDocument);
foreach (var pass in Passes)
{
irDocument = pass.Execute(codeDocument, irDocument);
}
codeDocument.SetIRDocument(irDocument);
}
}
}

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 IRazorIRPass : IRazorEngineFeature
{
int Order { get; }
DocumentIRNode Execute(RazorCodeDocument codeDocument, DocumentIRNode irDocument);
}
}

View File

@ -0,0 +1,9 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.AspNetCore.Razor.Evolution
{
public interface IRazorIRPhase : IRazorEnginePhase
{
}
}

View File

@ -33,6 +33,7 @@ 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.Features.Add(new TagHelperBinderSyntaxTreePass());
builder.Features.Add(new HtmlNodeOptimizationPass());

View File

@ -0,0 +1,95 @@
// 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 DefaultRazorIRPhaseTest
{
[Fact]
public void OnInitialized_OrdersPassesInAscendingOrder()
{
// Arrange & Act
var phase = new DefaultRazorIRPhase();
var first = Mock.Of<IRazorIRPass>(p => p.Order == 15);
var second = Mock.Of<IRazorIRPass>(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 DefaultRazorIRPhase();
var engine = RazorEngine.CreateEmpty(b => b.Phases.Add(phase));
var codeDocument = TestRazorCodeDocument.CreateEmpty();
// Act & Assert
ExceptionAssert.Throws<InvalidOperationException>(
() => phase.Execute(codeDocument),
$"The '{nameof(DefaultRazorIRPhase)}' 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<IRazorIRPass>(MockBehavior.Strict);
firstPass.SetupGet(m => m.Order).Returns(0);
firstPass.SetupProperty(m => m.Engine);
firstPass.Setup(m => m.Execute(codeDocument, originalNode)).Returns(firstPassNode);
var secondPass = new Mock<IRazorIRPass>(MockBehavior.Strict);
secondPass.SetupGet(m => m.Order).Returns(1);
secondPass.SetupProperty(m => m.Engine);
secondPass.Setup(m => m.Execute(codeDocument, firstPassNode)).Returns(secondPassNode);
var phase = new DefaultRazorIRPhase();
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());
}
}
}

View File

@ -3,7 +3,6 @@
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Razor.Evolution;
using Moq;
using Xunit;
@ -86,7 +85,8 @@ namespace Microsoft.AspNetCore.Razor.Evolution
phases,
phase => Assert.IsType<DefaultRazorParsingPhase>(phase),
phase => Assert.IsType<DefaultRazorSyntaxTreePhase>(phase),
phase => Assert.IsType<DefaultRazorIRLoweringPhase>(phase));
phase => Assert.IsType<DefaultRazorIRLoweringPhase>(phase),
phase => Assert.IsType<DefaultRazorIRPhase>(phase));
}
}
}