Move section support into IR.

- Previously we'd special case `@section` at code generation time; now we transform the directive into an IR node.
- Changed the expectations of `DefineSection` to not take in a section writer. It's now expected to modify what `Write`, `WriteLiteral` etc. write to when inside of the lambda. This is done today in TagHelpers via `StartTagHelperWritingScope`.
- Updated baseline files to reflect new `DefineSection` expectations.
- Updated IR tests since we no longer leave around `DirectiveIRNode`s.

#901
This commit is contained in:
N. Taylor Mullen 2016-12-14 17:17:50 -08:00
parent 7657ea41a3
commit c6150ba287
12 changed files with 280 additions and 100 deletions

View File

@ -8,16 +8,25 @@ using Microsoft.AspNetCore.Razor.Evolution.Legacy;
namespace Microsoft.AspNetCore.Razor.Evolution
{
internal class DefaultDirectiveIRPass : IRazorIRPass
internal class DefaultDirectiveIRPass : RazorIRPassBase
{
public RazorEngine Engine { get; set; }
RazorParserOptions _parserOptions;
public int Order => 150;
public override int Order => 150;
public DocumentIRNode Execute(RazorCodeDocument codeDocument, DocumentIRNode irDocument)
protected override void OnIntialized(RazorCodeDocument codeDocument)
{
var walker = new DirectiveWalker();
walker.VisitDefault(irDocument);
var syntaxTree = codeDocument.GetSyntaxTree();
ThrowForMissingDocumentDependency(syntaxTree);
_parserOptions = syntaxTree.Options;
}
public override DocumentIRNode ExecuteCore(DocumentIRNode irDocument)
{
var designTime = _parserOptions.DesignTimeMode;
var walker = new DirectiveWalker(designTime);
walker.VisitDocument(irDocument);
return irDocument;
}
@ -25,6 +34,12 @@ namespace Microsoft.AspNetCore.Razor.Evolution
private class DirectiveWalker : RazorIRNodeWalker
{
private ClassDeclarationIRNode _classNode;
private readonly bool _designTime;
public DirectiveWalker(bool designTime)
{
_designTime = designTime;
}
public override void VisitClass(ClassDeclarationIRNode node)
{
@ -40,6 +55,8 @@ namespace Microsoft.AspNetCore.Razor.Evolution
{
if (string.Equals(node.Name, CSharpCodeParser.FunctionsDirectiveDescriptor.Name, StringComparison.Ordinal))
{
node.Parent.Children.Remove(node);
foreach (var child in node.Children.Except(node.Tokens))
{
child.Parent = _classNode;
@ -48,6 +65,8 @@ namespace Microsoft.AspNetCore.Razor.Evolution
}
else if (string.Equals(node.Name, CSharpCodeParser.InheritsDirectiveDescriptor.Name, StringComparison.Ordinal))
{
node.Parent.Children.Remove(node);
var token = node.Tokens.FirstOrDefault();
if (token != null)
@ -55,6 +74,31 @@ namespace Microsoft.AspNetCore.Razor.Evolution
_classNode.BaseType = token.Content;
}
}
else if (string.Equals(node.Name, CSharpCodeParser.SectionDirectiveDescriptor.Name, StringComparison.Ordinal))
{
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);
}
}
}
}

View File

@ -436,30 +436,6 @@ namespace Microsoft.AspNetCore.Razor.Evolution
linePragma?.Dispose();
}
public override void VisitDirective(DirectiveIRNode node)
{
if (string.Equals(node.Name, CSharpCodeParser.SectionDirectiveDescriptor.Name, StringComparison.Ordinal))
{
const string SectionWriterName = "__razor_section_writer";
Context.Writer
.WriteStartMethodInvocation("DefineSection" /* ORIGINAL: DefineSectionMethodName */)
.WriteStringLiteral(node.Tokens.FirstOrDefault()?.Content)
.WriteParameterSeparator();
var initialRenderingConventions = Context.RenderingConventions;
var redirectConventions = new CSharpRedirectRenderingConventions(SectionWriterName, Context.Writer);
Context.RenderingConventions = redirectConventions;
using (Context.Writer.BuildAsyncLambda(endLine: false, parameterNames: SectionWriterName))
{
VisitDefault(node);
}
Context.RenderingConventions = initialRenderingConventions;
Context.Writer.WriteEndMethodInvocation();
}
}
public override void VisitCSharpStatement(CSharpStatementIRNode node)
{
if (string.IsNullOrWhiteSpace(node.Content))

View File

@ -10,6 +10,22 @@ namespace Microsoft.AspNetCore.Razor.Evolution
private static readonly ResourceManager _resourceManager
= new ResourceManager("Microsoft.AspNetCore.Razor.Evolution.Resources", typeof(Resources).GetTypeInfo().Assembly);
/// <summary>
/// The '{0}' feature requires a '{1}' provided by the '{2}'.
/// </summary>
internal static string FeatureDependencyMissing
{
get { return GetString("FeatureDependencyMissing"); }
}
/// <summary>
/// The '{0}' feature requires a '{1}' provided by the '{2}'.
/// </summary>
internal static string FormatFeatureDependencyMissing(object p0, object p1, object p2)
{
return string.Format(CultureInfo.CurrentCulture, GetString("FeatureDependencyMissing"), p0, p1, p2);
}
/// <summary>
/// The '{0}' operation is not valid when the builder is empty.
/// </summary>

View File

@ -0,0 +1,67 @@
// 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;
namespace Microsoft.AspNetCore.Razor.Evolution
{
internal abstract class RazorIRPassBase : IRazorIRPass
{
public RazorEngine Engine { get; set; }
public virtual int Order => 0;
protected void ThrowForMissingDocumentDependency<TDocumentDependency>(TDocumentDependency value)
{
if (value == null)
{
throw new InvalidOperationException(
Resources.FormatFeatureDependencyMissing(
GetType().Name,
typeof(TDocumentDependency).Name,
typeof(RazorEngine).Name));
}
}
protected void ThrowForMissingEngineDependency<TEngineDependency>(TEngineDependency value)
{
if (value == null)
{
throw new InvalidOperationException(
Resources.FormatFeatureDependencyMissing(
GetType().Name,
typeof(TEngineDependency).Name,
typeof(RazorCodeDocument).Name));
}
}
protected virtual void OnIntialized(RazorCodeDocument codeDocument)
{
}
public DocumentIRNode Execute(RazorCodeDocument codeDocument, DocumentIRNode irDocument)
{
if (codeDocument == null)
{
throw new ArgumentNullException(nameof(codeDocument));
}
if (irDocument == null)
{
throw new ArgumentNullException(nameof(irDocument));
}
if (Engine == null)
{
throw new InvalidOperationException(Resources.FormatPhaseMustBeInitialized(nameof(Engine)));
}
OnIntialized(codeDocument);
return ExecuteCore(irDocument);
}
public abstract DocumentIRNode ExecuteCore(DocumentIRNode irDocument);
}
}

View File

@ -117,6 +117,9 @@
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="FeatureDependencyMissing" xml:space="preserve">
<value>The '{0}' feature requires a '{1}' provided by the '{2}'.</value>
</data>
<data name="IRBuilder_PopInvalid" xml:space="preserve">
<value>The '{0}' operation is not valid when the builder is empty.</value>
</data>

View File

@ -18,11 +18,17 @@ namespace Microsoft.AspNetCore.Razor.Evolution
@functions {
var value = true;
}";
var originalIRDocument = Lower(content);
var pass = new DefaultDirectiveIRPass();
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: null, irDocument: originalIRDocument);
var irDocument = pass.Execute(codeDocument, originalIRDocument);
// Assert
Assert.Same(originalIRDocument, irDocument);
@ -33,11 +39,17 @@ namespace Microsoft.AspNetCore.Razor.Evolution
{
// Arrange
var content = "@inherits Hello<World[]>";
var originalIRDocument = Lower(content);
var pass = new DefaultDirectiveIRPass();
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: null, irDocument: originalIRDocument);
var irDocument = pass.Execute(codeDocument, originalIRDocument);
// Assert
Children(irDocument,
@ -53,15 +65,21 @@ namespace Microsoft.AspNetCore.Razor.Evolution
}
[Fact]
public void Execute_Functions_ExistsAtClassDeclarationAndMethodLevel()
public void Execute_Functions_MovesStatementToClassLevel()
{
// Arrange
var content = "@functions { var value = true; }";
var originalIRDocument = Lower(content);
var pass = new DefaultDirectiveIRPass();
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: null, irDocument: originalIRDocument);
var irDocument = pass.Execute(codeDocument, originalIRDocument);
// Assert
Children(irDocument,
@ -79,17 +97,91 @@ namespace Microsoft.AspNetCore.Razor.Evolution
var method = (RazorMethodDeclarationIRNode)@class.Children[0];
Children(method,
node => Html(string.Empty, node),
node => Directive("functions", node,
directiveChild => CSharpStatement(" var value = true; ", directiveChild)),
node => Html(string.Empty, node));
}
private static DocumentIRNode Lower(string content)
[Fact]
public void Execute_Section_WrapsStatementInDefineSection()
{
// Arrange
var content = "@section Header { <p>Hello World</p> }";
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
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);
Children(method,
node => Html(string.Empty, node),
node => CSharpStatement("DefineSection(\"Header\", async () => {", node),
node => Html(" <p>Hello World</p> ", node),
node => CSharpStatement("});", node),
node => Html(string.Empty, node));
}
[Fact]
public void Execute_Section_DesignTime_WrapsStatementInBackwardsCompatibleDefineSection()
{
// Arrange
var content = "@section Header { <p>Hello World</p> }";
var designTimeEngine = RazorEngine.CreateDesignTime();
var sourceDocument = TestRazorSourceDocument.Create(content);
var codeDocument = RazorCodeDocument.Create(sourceDocument);
var originalIRDocument = Lower(codeDocument, designTimeEngine);
var defaultEngine = RazorEngine.Create();
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);
Children(method,
node => Html(string.Empty, node),
node => CSharpStatement("DefineSection(\"Header\", async (__razor_section_writer) => {", node),
node => Html(" <p>Hello World</p> ", node),
node => CSharpStatement("});", node),
node => Html(string.Empty, node));
}
private static DocumentIRNode Lower(RazorCodeDocument codeDocument)
{
var engine = RazorEngine.Create();
return Lower(codeDocument, engine);
}
private static DocumentIRNode Lower(RazorCodeDocument codeDocument, RazorEngine engine)
{
for (var i = 0; i < engine.Phases.Count; i++)
{
var phase = engine.Phases[i];

View File

@ -1,6 +1,7 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.Evolution.Intermediate;
using Microsoft.AspNetCore.Testing.xunit;
@ -556,13 +557,9 @@ namespace Microsoft.AspNetCore.Razor.Evolution.IntegrationTests
AssertCSharpDocumentMatchesBaseline(document.GetCSharpDocument());
}
private class ApiSetsIRTestAdapter : IRazorIRPass
private class ApiSetsIRTestAdapter : RazorIRPassBase
{
public RazorEngine Engine { get; set; }
public int Order { get; set; }
public DocumentIRNode Execute(RazorCodeDocument codeDocument, DocumentIRNode irDocument)
public override DocumentIRNode ExecuteCore(DocumentIRNode irDocument)
{
var walker = new ApiSetsIRWalker();
walker.Visit(irDocument);

View File

@ -1,22 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk" ToolsVersion="15.0">
<Project Sdk="Microsoft.NET.Sdk" ToolsVersion="15.0">
<Import Project="..\..\build\common.props" />
<PropertyGroup>
<TargetFrameworks>netcoreapp1.0;net451</TargetFrameworks>
<DefineConstants>$(DefineConstants);__RemoveThisBitTo__GENERATE_BASELINES</DefineConstants>
</PropertyGroup>
<ItemGroup>
<Compile Include="**\*.cs" Exclude="TestFiles\**\*" />
<EmbeddedResource Include="**\*.resx" />
<EmbeddedResource Include="TestFiles\**\*" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Razor.Evolution\Microsoft.AspNetCore.Razor.Evolution.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.0.0-preview-20161123-03" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.2.0-beta4-build1194" />
@ -24,16 +19,13 @@
<PackageReference Include="Moq" Version="4.6.36-*" />
<PackageReference Include="xunit" Version="2.2.0-beta4-build3444" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'netcoreapp1.0' ">
<PackageReference Include="Microsoft.NETCore.App" Version="1.2.0-*" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'net451' ">
<Reference Include="System.Runtime" />
<Reference Include="System.Threading.Tasks" />
<Reference Include="System" />
<Reference Include="Microsoft.CSharp" />
</ItemGroup>
</Project>
</Project>

View File

@ -51,16 +51,15 @@ WriteTo(__razor_template_writer, baz);
#line default
#line hidden
WriteLiteral("\r\n</p>\r\n\r\n");
DefineSection("Footer", async(__razor_section_writer) => {
WriteLiteralTo(__razor_section_writer, "\r\n <p>Foo</p>\r\n ");
DefineSection("Footer", async () => {
WriteLiteral("\r\n <p>Foo</p>\r\n ");
#line 14 "TestFiles/IntegrationTests/RuntimeCodeGenerationIntegrationTest/DesignTime.cshtml"
WriteTo(__razor_section_writer, bar);
Write(bar);
#line default
#line hidden
WriteLiteralTo(__razor_section_writer, "\r\n");
}
);
WriteLiteral("\r\n");
});
}
#pragma warning restore 1998
}

View File

@ -9,9 +9,8 @@ namespace Microsoft.AspNetCore.Razor.Evolution.IntegrationTests.TestFiles
#pragma warning disable 1998
public async System.Threading.Tasks.Task ExecuteAsync()
{
DefineSection("Link", async(__razor_section_writer) => {
}
);
DefineSection("Link", async () => {
});
WriteLiteral("(string link) {\r\n <a");
BeginWriteAttribute("href", " href=\"", 36, "\"", 93, 1);
WriteAttributeValue("", 43, new HelperResult(async(__razor_attribute_value_writer) => {

View File

@ -17,49 +17,46 @@ namespace Microsoft.AspNetCore.Razor.Evolution.IntegrationTests.TestFiles
#line default
#line hidden
WriteLiteral("\r\n<div>This is in the Body>\r\n\r\n");
DefineSection("Section2", async(__razor_section_writer) => {
WriteLiteralTo(__razor_section_writer, "\r\n <div");
BeginWriteAttributeTo(__razor_section_writer, "class", " class=\"", 109, "\"", 128, 2);
WriteAttributeValueTo(__razor_section_writer, "", 117, "some", 117, 4, true);
DefineSection("Section2", async () => {
WriteLiteral("\r\n <div");
BeginWriteAttribute("class", " class=\"", 109, "\"", 128, 2);
WriteAttributeValue("", 117, "some", 117, 4, true);
#line 8 "TestFiles/IntegrationTests/RuntimeCodeGenerationIntegrationTest/Sections.cshtml"
WriteAttributeValueTo(__razor_section_writer, " ", 121, thing, 122, 6, false);
WriteAttributeValue(" ", 121, thing, 122, 6, false);
#line default
#line hidden
EndWriteAttributeTo(__razor_section_writer);
WriteLiteralTo(__razor_section_writer, ">This is in Section 2</div>\r\n");
}
);
EndWriteAttribute();
WriteLiteral(">This is in Section 2</div>\r\n");
});
WriteLiteral("\r\n");
DefineSection("Section1", async(__razor_section_writer) => {
WriteLiteralTo(__razor_section_writer, "\r\n <div>This is in Section 1</div>\r\n");
}
);
DefineSection("Section1", async () => {
WriteLiteral("\r\n <div>This is in Section 1</div>\r\n");
});
WriteLiteral("\r\n");
DefineSection("NestedDelegates", async () => {
WriteLiteral("\r\n");
DefineSection("NestedDelegates", async(__razor_section_writer) => {
WriteLiteralTo(__razor_section_writer, "\r\n");
#line 16 "TestFiles/IntegrationTests/RuntimeCodeGenerationIntegrationTest/Sections.cshtml"
Func<dynamic, object> f =
#line default
#line hidden
item => new HelperResult(async(__razor_template_writer) => {
WriteLiteralTo(__razor_template_writer, "<span>");
item => new HelperResult(async(__razor_template_writer) => {
WriteLiteralTo(__razor_template_writer, "<span>");
#line 16 "TestFiles/IntegrationTests/RuntimeCodeGenerationIntegrationTest/Sections.cshtml"
WriteTo(__razor_template_writer, item);
#line default
#line hidden
WriteLiteralTo(__razor_template_writer, "</span>");
}
)
WriteLiteralTo(__razor_template_writer, "</span>");
}
)
#line 16 "TestFiles/IntegrationTests/RuntimeCodeGenerationIntegrationTest/Sections.cshtml"
;
#line default
#line hidden
}
);
});
}
#pragma warning restore 1998
}

View File

@ -96,8 +96,8 @@ namespace Microsoft.AspNetCore.Razor.Evolution.IntegrationTests.TestFiles
<p>This is line 84</p><br>
");
DefineSection("WriteLiteralsToInHere", async(__razor_section_writer) => {
WriteLiteralTo(__razor_section_writer, @"
DefineSection("WriteLiteralsToInHere", async () => {
WriteLiteral(@"
<p>This is line 1 nested</p>
<p>This is line 2 nested</p>
<p>This is line 3 nested</p>
@ -128,7 +128,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution.IntegrationTests.TestFiles
<p>This is line 28 nested</p>
<p>This is line 29 nested</p>
<p>This is l");
WriteLiteralTo(__razor_section_writer, @"ine 30 nested</p>
WriteLiteral(@"ine 30 nested</p>
<p>This is line 31 nested</p>
<p>This is line 32 nested</p>
<p>This is line 33 nested</p>
@ -158,7 +158,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution.IntegrationTests.TestFiles
<p>This is line 57 nested</p>
<p>This is line 58 nested</p>
<p>This is line 59 ne");
WriteLiteralTo(__razor_section_writer, @"sted</p>
WriteLiteral(@"sted</p>
<p>This is line 60 nested</p>
<p>This is line 61 nested</p>
<p>This is line 62 nested</p>
@ -176,8 +176,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution.IntegrationTests.TestFiles
<p>This is line 74 nested</p>
<p>This is line 75 nested</p>
");
}
);
});
WriteLiteral(@"<p>This is line 1</p>
<p>This is line 2</p>
<p>This is line 3</p>
@ -222,8 +221,8 @@ namespace Microsoft.AspNetCore.Razor.Evolution.IntegrationTests.TestFiles
<p>This is line 42</p>
<p>This is line 43</p>hi!");
WriteLiteral("\r\n");
DefineSection("WriteLiteralsToInHereAlso", async(__razor_section_writer) => {
WriteLiteralTo(__razor_section_writer, @"
DefineSection("WriteLiteralsToInHereAlso", async () => {
WriteLiteral(@"
<p>This is line 1 nested</p>
<p>This is line 2 nested</p>
<p>This is line 3 nested</p>
@ -255,8 +254,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution.IntegrationTests.TestFiles
<p>This is line 29 nested</p>
<p>30</p>
");
}
);
});
WriteLiteral("!");
}
#pragma warning restore 1998