First cut of adding API sets

This change defines stages for IR processing. The comments in RazorIRPass
really explain the details. I've also made the preliminary changes to the
stuff we've built so far to follow the new conventions.

This is building towards multitargeting for Razor, being able to target
both Razor Pages and Razor MVC Views from the same engine, being able to
target different codegen and methods from within the same engine.
This commit is contained in:
Ryan Nowak 2016-12-29 22:15:28 -08:00
parent f45afb6c19
commit e84bc66700
20 changed files with 737 additions and 176 deletions

View File

@ -10,21 +10,16 @@ namespace Microsoft.AspNetCore.Razor.Evolution
{
internal class DefaultDirectiveIRPass : RazorIRPassBase
{
RazorParserOptions _parserOptions;
public override int Order => RazorIRPass.DefaultDirectiveClassifierOrder;
public override int Order => 150;
protected override void OnIntialized(RazorCodeDocument codeDocument)
public override DocumentIRNode ExecuteCore(RazorCodeDocument codeDocument, DocumentIRNode irDocument)
{
var syntaxTree = codeDocument.GetSyntaxTree();
ThrowForMissingDocumentDependency(syntaxTree);
_parserOptions = syntaxTree.Options;
}
var parserOptions = syntaxTree.Options;
public override DocumentIRNode ExecuteCore(DocumentIRNode irDocument)
{
var designTime = _parserOptions.DesignTimeMode;
var designTime = parserOptions.DesignTimeMode;
var walker = new DirectiveWalker(designTime);
walker.VisitDocument(irDocument);

View File

@ -0,0 +1,123 @@
// 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 DefaultDocumentClassifier : RazorIRPassBase
{
public override int Order => RazorIRPass.DefaultDocumentClassifierOrder;
public static string DocumentKind = "default";
public override DocumentIRNode ExecuteCore(RazorCodeDocument codeDocument, DocumentIRNode irDocument)
{
if (irDocument.DocumentKind != null)
{
return irDocument;
}
irDocument.DocumentKind = DocumentKind;
// Rewrite a use default namespace and class declaration.
var children = new List<RazorIRNode>(irDocument.Children);
irDocument.Children.Clear();
var @namespace = new NamespaceDeclarationIRNode()
{
//Content = "GeneratedNamespace",
};
var @class = new ClassDeclarationIRNode()
{
//AccessModifier = "public",
//Name = "GeneratedClass",
};
var method = new RazorMethodDeclarationIRNode()
{
//AccessModifier = "public",
// Modifiers = new List<string>() { "async" },
//Name = "Execute",
//ReturnType = "Task",
};
var documentBuilder = RazorIRBuilder.Create(irDocument);
var namespaceBuilder = RazorIRBuilder.Create(documentBuilder.Current);
namespaceBuilder.Push(@namespace);
var classBuilder = RazorIRBuilder.Create(namespaceBuilder.Current);
classBuilder.Push(@class);
var methodBuilder = RazorIRBuilder.Create(classBuilder.Current);
methodBuilder.Push(method);
var visitor = new Visitor(documentBuilder, namespaceBuilder, classBuilder, methodBuilder);
for (var i = 0; i < children.Count; i++)
{
visitor.Visit(children[i]);
}
return irDocument;
}
private class Visitor : RazorIRNodeVisitor
{
private readonly RazorIRBuilder _document;
private readonly RazorIRBuilder _namespace;
private readonly RazorIRBuilder _class;
private readonly RazorIRBuilder _method;
public Visitor(RazorIRBuilder document, RazorIRBuilder @namespace, RazorIRBuilder @class, RazorIRBuilder method)
{
_document = document;
_namespace = @namespace;
_class = @class;
_method = method;
}
public override void VisitChecksum(ChecksumIRNode node)
{
_document.Insert(0, node);
}
public override void VisitUsingStatement(UsingStatementIRNode node)
{
_namespace.AddAfter<UsingStatementIRNode>(node);
}
internal override void VisitDeclareTagHelperFields(DeclareTagHelperFieldsIRNode node)
{
_class.Insert(0, node);
}
public override void VisitDefault(RazorIRNode node)
{
_method.Add(node);
}
}
public void Foo()
{
//// For prettiness, let's insert the usings before the class declaration.
//var i = 0;
//for (; i < Namespace.Children.Count; i++)
//{
// if (Namespace.Children[i] is ClassDeclarationIRNode)
// {
// break;
// }
//}
//var @using = new UsingStatementIRNode()
//{
// Content = namespaceImport,
// SourceRange = BuildSourceRangeFromNode(span),
//};
}
}
}

View File

@ -18,57 +18,51 @@ namespace Microsoft.AspNetCore.Razor.Evolution
var visitor = new Visitor(codeDocument, syntaxTree.Options);
var i = 0;
var builder = visitor.Builder;
foreach (var namespaceImport in syntaxTree.Options.NamespaceImports)
{
if (visitor.Namespaces.Add(namespaceImport))
{
var @using = new UsingStatementIRNode()
{
Content = namespaceImport,
};
builder.Insert(i++, @using);
}
}
var checksum = ChecksumIRNode.Create(codeDocument.Source);
visitor.Builder.Insert(0, checksum);
visitor.VisitBlock(syntaxTree.Root);
var irDocument = (DocumentIRNode)visitor.Builder.Build();
codeDocument.SetIRDocument(irDocument);
}
private class Visitor : ParserVisitor
{
private readonly Stack<RazorIRBuilder> _builders;
private readonly RazorParserOptions _options;
private readonly RazorCodeDocument _codeDocument;
private DeclareTagHelperFieldsIRNode _tagHelperFields;
public Visitor(RazorCodeDocument codeDocument, RazorParserOptions options)
{
_codeDocument = codeDocument;
_options = options;
_builders = new Stack<RazorIRBuilder>();
var document = RazorIRBuilder.Document();
_builders.Push(document);
var checksum = ChecksumIRNode.Create(codeDocument.Source);
Builder.Add(checksum);
Namespaces = new HashSet<string>();
Namespace = new NamespaceDeclarationIRNode();
Builder.Push(Namespace);
foreach (var namespaceImport in options.NamespaceImports)
{
var @using = new UsingStatementIRNode()
{
Content = namespaceImport,
Parent = Namespace,
};
Builder.Add(@using);
}
Class = new ClassDeclarationIRNode();
Builder.Push(Class);
Method = new RazorMethodDeclarationIRNode();
Builder.Push(Method);
Builder = RazorIRBuilder.Document();
}
public RazorIRBuilder Builder => _builders.Peek();
public RazorIRBuilder Builder { get; }
public NamespaceDeclarationIRNode Namespace { get; }
public ClassDeclarationIRNode Class { get; }
public RazorMethodDeclarationIRNode Method { get; }
public HashSet<string> Namespaces { get; }
// Example
// <input` checked="hello-world @false"`/>
@ -254,31 +248,15 @@ namespace Microsoft.AspNetCore.Razor.Evolution
{
var namespaceImport = chunkGenerator.Namespace.Trim();
if (_options.NamespaceImports.Contains(namespaceImport, StringComparer.Ordinal))
// Track seen namespaces so we don't add duplicates from options.
if (Namespaces.Add(namespaceImport))
{
// Already added by default
return;
}
// For prettiness, let's insert the usings before the class declaration.
var i = 0;
for (; i < Namespace.Children.Count; i++)
{
if (Namespace.Children[i] is ClassDeclarationIRNode)
Builder.Add(new UsingStatementIRNode()
{
break;
}
Content = namespaceImport,
SourceRange = BuildSourceRangeFromNode(span),
});
}
var @using = new UsingStatementIRNode()
{
Content = namespaceImport,
Parent = Namespace,
SourceRange = BuildSourceRangeFromNode(span),
};
Namespace.Children.Insert(i, @using);
}
public override void VisitDirectiveToken(DirectiveTokenChunkGenerator chunkGenerator, Span span)
@ -355,19 +333,15 @@ namespace Microsoft.AspNetCore.Razor.Evolution
private void DeclareTagHelperFields(TagHelperBlock block)
{
var declareFieldsNode = Class.Children.OfType<DeclareTagHelperFieldsIRNode>().SingleOrDefault();
if (declareFieldsNode == null)
if (_tagHelperFields == null)
{
declareFieldsNode = new DeclareTagHelperFieldsIRNode();
declareFieldsNode.Parent = Class;
var methodIndex = Class.Children.IndexOf(Method);
Class.Children.Insert(methodIndex, declareFieldsNode);
_tagHelperFields = new DeclareTagHelperFieldsIRNode();
Builder.Add(_tagHelperFields);
}
foreach (var descriptor in block.Descriptors)
{
declareFieldsNode.UsedTagHelperTypeNames.Add(descriptor.TypeName);
_tagHelperFields.UsedTagHelperTypeNames.Add(descriptor.TypeName);
}
}

View File

@ -31,8 +31,27 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate
throw new ArgumentNullException(nameof(node));
}
Push(node);
Pop();
node.Parent = Current;
Current.Children.Add(node);
}
public override void Insert(int index, RazorIRNode node)
{
if (index < 0 || index - Current.Children.Count > 0)
{
throw new ArgumentOutOfRangeException(nameof(index));
}
node.Parent = Current;
if (index == Current.Children.Count)
{
// Allow inserting at 'Children.Count' to be friendlier than List<> typically is.
Current.Children.Add(node);
}
else
{
Current.Children.Insert(index, node);
}
}
public override RazorIRNode Build()

View File

@ -11,6 +11,8 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate
{
public override IList<RazorIRNode> Children { get; } = new List<RazorIRNode>();
public string DocumentKind { get; set; }
public override RazorIRNode Parent { get; set; }
internal override MappingLocation SourceRange { get; set; }

View File

@ -28,6 +28,8 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate
public abstract void Add(RazorIRNode node);
public abstract void Insert(int index, RazorIRNode node);
public abstract RazorIRNode Build();
public abstract void Push(RazorIRNode node);

View File

@ -0,0 +1,25 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate
{
public static class RazorIRBuilderExtensions
{
public static void AddAfter<TNode>(this RazorIRBuilder builder, RazorIRNode node)
where TNode : RazorIRNode
{
var children = builder.Current.Children;
var i = children.Count - 1;
for (; i >= 0; i--)
{
var child = children[i];
if (child is TNode || child.GetType() == node.GetType())
{
break;
}
}
builder.Insert(i + 1, node);
}
}
}

View File

@ -12,9 +12,9 @@ namespace Microsoft.AspNetCore.Razor.Evolution
{
internal const string DesignTimeVariable = "__o";
public override int Order => 25;
public override int Order => RazorIRPass.DirectiveClassifierOrder;
public override DocumentIRNode ExecuteCore(DocumentIRNode irDocument)
public override DocumentIRNode ExecuteCore(RazorCodeDocument codeDocument, DocumentIRNode irDocument)
{
var walker = new DesignTimeHelperWalker();
walker.VisitDocument(irDocument);

View File

@ -59,6 +59,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution
builder.Features.Add(new TagHelperBinderSyntaxTreePass());
// IR Passes
builder.Features.Add(new DefaultDocumentClassifier());
builder.Features.Add(new DefaultDirectiveIRPass());
}

View File

@ -0,0 +1,87 @@
// 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.
/// </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;
}
}

View File

@ -8,9 +8,24 @@ namespace Microsoft.AspNetCore.Razor.Evolution
{
internal abstract class RazorIRPassBase : IRazorIRPass
{
public RazorEngine Engine { get; set; }
private RazorEngine _engine;
public virtual int Order => 0;
public RazorEngine Engine
{
get { return _engine; }
set
{
if (value == null)
{
throw new ArgumentNullException(nameof(value));
}
_engine = value;
OnIntialized();
}
}
public abstract int Order { get; }
protected void ThrowForMissingDocumentDependency<TDocumentDependency>(TDocumentDependency value)
{
@ -36,7 +51,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution
}
}
protected virtual void OnIntialized(RazorCodeDocument codeDocument)
protected virtual void OnIntialized()
{
}
@ -57,11 +72,9 @@ namespace Microsoft.AspNetCore.Razor.Evolution
throw new InvalidOperationException(Resources.FormatPhaseMustBeInitialized(nameof(Engine)));
}
OnIntialized(codeDocument);
return ExecuteCore(irDocument);
return ExecuteCore(codeDocument, irDocument);
}
public abstract DocumentIRNode ExecuteCore(DocumentIRNode irDocument);
public abstract DocumentIRNode ExecuteCore(RazorCodeDocument codeDocument, DocumentIRNode irDocument);
}
}

View File

@ -180,7 +180,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution
{
var phase = engine.Phases[i];
phase.Execute(codeDocument);
if (phase is IRazorIRLoweringPhase)
{
break;
@ -189,6 +189,9 @@ namespace Microsoft.AspNetCore.Razor.Evolution
var irDocument = codeDocument.GetIRDocument();
Assert.NotNull(irDocument);
// These tests depend on the document->namespace->class structure.
irDocument = new DefaultDocumentClassifier() { Engine = engine, }.Execute(codeDocument, irDocument);
return irDocument;
}
}

View File

@ -0,0 +1,149 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNetCore.Razor.Evolution.Intermediate;
using Xunit;
using static Microsoft.AspNetCore.Razor.Evolution.Intermediate.RazorIRAssert;
namespace Microsoft.AspNetCore.Razor.Evolution
{
public class DefaultDocumentClassifierTest
{
[Fact]
public void Execute_IgnoresDocumentsWithDocumentKind()
{
// Arrange
var irDocument = new DocumentIRNode()
{
DocumentKind = "ignore",
};
var pass = new DefaultDocumentClassifier();
pass.Engine = RazorEngine.CreateEmpty(b => { });
// Act
pass.Execute(TestRazorCodeDocument.CreateEmpty(), irDocument);
// Assert
Assert.Equal("ignore", irDocument.DocumentKind);
NoChildren(irDocument);
}
[Fact]
public void Execute_CreatesClassStructure()
{
// Arrange
var irDocument = new DocumentIRNode();
var pass = new DefaultDocumentClassifier();
pass.Engine = RazorEngine.CreateEmpty(b =>{ });
// Act
pass.Execute(TestRazorCodeDocument.CreateEmpty(), irDocument);
// Assert
Assert.Equal(DefaultDocumentClassifier.DocumentKind, irDocument.DocumentKind);
var @namespace = SingleChild<NamespaceDeclarationIRNode>(irDocument);
var @class = SingleChild<ClassDeclarationIRNode>(@namespace);
var method = SingleChild<RazorMethodDeclarationIRNode>(@class);
NoChildren(method);
}
[Fact]
public void Execute_AddsCheckumFirstToDocument()
{
// Arrange
var irDocument = new DocumentIRNode();
var builder = RazorIRBuilder.Create(irDocument);
builder.Add(new ChecksumIRNode());
var pass = new DefaultDocumentClassifier();
pass.Engine = RazorEngine.CreateEmpty(b => { });
// Act
pass.Execute(TestRazorCodeDocument.CreateEmpty(), irDocument);
// Assert
Children(
irDocument,
n => Assert.IsType<ChecksumIRNode>(n),
n => Assert.IsType<NamespaceDeclarationIRNode>(n));
}
[Fact]
public void Execute_AddsUsingsToNamespace()
{
// Arrange
var irDocument = new DocumentIRNode();
var builder = RazorIRBuilder.Create(irDocument);
builder.Add(new UsingStatementIRNode());
var pass = new DefaultDocumentClassifier();
pass.Engine = RazorEngine.CreateEmpty(b => { });
// Act
pass.Execute(TestRazorCodeDocument.CreateEmpty(), irDocument);
// Assert
var @namespace = SingleChild<NamespaceDeclarationIRNode>(irDocument);
Children(
@namespace,
n => Assert.IsType<UsingStatementIRNode>(n),
n => Assert.IsType<ClassDeclarationIRNode>(n));
}
[Fact]
public void Execute_AddsTagHelperFieldsToClass()
{
// Arrange
var irDocument = new DocumentIRNode();
var builder = RazorIRBuilder.Create(irDocument);
builder.Add(new DeclareTagHelperFieldsIRNode());
var pass = new DefaultDocumentClassifier();
pass.Engine = RazorEngine.CreateEmpty(b => { });
// Act
pass.Execute(TestRazorCodeDocument.CreateEmpty(), irDocument);
// Assert
var @namespace = SingleChild<NamespaceDeclarationIRNode>(irDocument);
var @class = SingleChild<ClassDeclarationIRNode>(@namespace);
Children(
@class,
n => Assert.IsType<DeclareTagHelperFieldsIRNode>(n),
n => Assert.IsType<RazorMethodDeclarationIRNode>(n));
}
[Fact]
public void Execute_AddsTheRestToMethod()
{
// Arrange
var irDocument = new DocumentIRNode();
var builder = RazorIRBuilder.Create(irDocument);
builder.Add(new HtmlContentIRNode());
builder.Add(new CSharpStatementIRNode());
var pass = new DefaultDocumentClassifier();
pass.Engine = RazorEngine.CreateEmpty(b => { });
// Act
pass.Execute(TestRazorCodeDocument.CreateEmpty(), irDocument);
// Assert
var @namespace = SingleChild<NamespaceDeclarationIRNode>(irDocument);
var @class = SingleChild<ClassDeclarationIRNode>(@namespace);
var method = SingleChild<RazorMethodDeclarationIRNode>(@class);
Children(
method,
n => Assert.IsType<HtmlContentIRNode>(n),
n => Assert.IsType<CSharpStatementIRNode>(n));
}
}
}

View File

@ -1466,7 +1466,9 @@ namespace Microsoft.AspNetCore.Razor.Evolution.IntegrationTests
private class ApiSetsIRTestAdapter : RazorIRPassBase
{
public override DocumentIRNode ExecuteCore(DocumentIRNode irDocument)
public override int Order => RazorIRPass.LoweringOrder;
public override DocumentIRNode ExecuteCore(RazorCodeDocument codeDocument, DocumentIRNode irDocument)
{
var walker = new ApiSetsIRWalker();
walker.Visit(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.IntegrationTests
{
public class PageDocumentIntegrationTest
{
}
}

View File

@ -107,7 +107,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate
}
[Fact]
public void Add_DoesPushAndPop()
public void Add_AddsToChildrenAndSetsParent()
{
// Arrange
var builder = new DefaultRazorIRBuilder();
@ -126,6 +126,72 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate
Assert.Collection(parent.Children, n => Assert.Same(node, n));
}
[Fact]
public void Insert_AddsToChildrenAndSetsParent_EmptyCollection()
{
// Arrange
var builder = new DefaultRazorIRBuilder();
var parent = new BasicIRNode();
builder.Push(parent);
var node = new BasicIRNode();
// Act
builder.Insert(0, node);
// Assert
Assert.Same(parent, builder.Current);
Assert.Same(parent, node.Parent);
Assert.Collection(parent.Children, n => Assert.Same(node, n));
}
[Fact]
public void Insert_AddsToChildrenAndSetsParent_NonEmpyCollection()
{
// Arrange
var builder = new DefaultRazorIRBuilder();
var parent = new BasicIRNode();
builder.Push(parent);
var child = new BasicIRNode();
builder.Add(child);
var node = new BasicIRNode();
// Act
builder.Insert(0, node);
// Assert
Assert.Same(parent, builder.Current);
Assert.Same(parent, node.Parent);
Assert.Collection(parent.Children, n => Assert.Same(node, n), n => Assert.Same(child, n));
}
[Fact]
public void Insert_AddsToChildrenAndSetsParent_NonEmpyCollection_AtEnd()
{
// Arrange
var builder = new DefaultRazorIRBuilder();
var parent = new BasicIRNode();
builder.Push(parent);
var child = new BasicIRNode();
builder.Add(child);
var node = new BasicIRNode();
// Act
builder.Insert(1, node);
// Assert
Assert.Same(parent, builder.Current);
Assert.Same(parent, node.Parent);
Assert.Collection(parent.Children, n => Assert.Same(child, n), n => Assert.Same(node, n));
}
[Fact]
public void Build_PopsMultipleLevels()
{

View File

@ -1,20 +1,20 @@
// 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 static Microsoft.AspNetCore.Razor.Evolution.Intermediate.RazorIRAssert;
using Xunit;
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.Evolution.Legacy;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.Evolution.Legacy;
using Xunit;
using static Microsoft.AspNetCore.Razor.Evolution.Intermediate.RazorIRAssert;
namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate
{
public class DefaultRazorIRLoweringPhaseIntegrationTest
{
[Fact]
public void Lower_EmptyDocument()
public void Lower_EmptyDocument_AddsGlobalUsingsAndNamespace()
{
// Arrange
var codeDocument = TestRazorCodeDocument.CreateEmpty();
@ -25,15 +25,8 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate
// Assert
Children(irDocument,
n => Assert.IsType<ChecksumIRNode>(n),
n => Assert.IsType<NamespaceDeclarationIRNode>(n));
var @namespace = irDocument.Children[1];
Children(@namespace,
n => Assert.IsType<UsingStatementIRNode>(n),
n => Assert.IsType<UsingStatementIRNode>(n),
n => Assert.IsType<ClassDeclarationIRNode>(n));
var @class = @namespace.Children[2];
var method = SingleChild<RazorMethodDeclarationIRNode>(@class);
Assert.Empty(method.Children);
n => Using("System", n),
n => Using("System.Threading.Tasks", n));
}
[Fact]
@ -48,17 +41,9 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate
// Assert
Children(irDocument,
n => Assert.IsType<ChecksumIRNode>(n),
n => Assert.IsType<NamespaceDeclarationIRNode>(n));
var @namespace = irDocument.Children[1];
Children(@namespace,
n => Assert.IsType<UsingStatementIRNode>(n),
n => Assert.IsType<UsingStatementIRNode>(n),
n => Assert.IsType<ClassDeclarationIRNode>(n));
var @class = @namespace.Children[2];
var method = SingleChild<RazorMethodDeclarationIRNode>(@class);
var html = SingleChild<HtmlContentIRNode>(method);
Assert.Equal("Hello, World!", html.Content);
n => Html("Hello, World!", n));
}
[Fact]
@ -78,15 +63,8 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate
// Assert
Children(irDocument,
n => Assert.IsType<ChecksumIRNode>(n),
n => Assert.IsType<NamespaceDeclarationIRNode>(n));
var @namespace = irDocument.Children[1];
Children(@namespace,
n => Assert.IsType<UsingStatementIRNode>(n),
n => Assert.IsType<UsingStatementIRNode>(n),
n => Assert.IsType<ClassDeclarationIRNode>(n));
var @class = @namespace.Children[2];
var method = SingleChild<RazorMethodDeclarationIRNode>(@class);
Children(method,
n => Html(
@"
<html>
@ -115,15 +93,8 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate
// Assert
Children(irDocument,
n => Assert.IsType<ChecksumIRNode>(n),
n => Assert.IsType<NamespaceDeclarationIRNode>(n));
var @namespace = irDocument.Children[1];
Children(@namespace,
n => Assert.IsType<UsingStatementIRNode>(n),
n => Assert.IsType<UsingStatementIRNode>(n),
n => Assert.IsType<ClassDeclarationIRNode>(n));
var @class = @namespace.Children[2];
var method = SingleChild<RazorMethodDeclarationIRNode>(@class);
Children(method,
n => Html(
@"
<html>
@ -157,16 +128,12 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate
// Assert
Children(irDocument,
n => Assert.IsType<ChecksumIRNode>(n),
n => Assert.IsType<NamespaceDeclarationIRNode>(n));
var @namespace = irDocument.Children[1];
Children(@namespace,
n => Assert.IsType<UsingStatementIRNode>(n),
n => Assert.IsType<UsingStatementIRNode>(n),
n => Assert.IsType<ClassDeclarationIRNode>(n));
var @class = @namespace.Children[2];
Children(@class,
n => Assert.IsType<RazorMethodDeclarationIRNode>(n),
n => Assert.IsType<CSharpStatementIRNode>(n));
n => Directive(
"functions",
n,
c => Assert.IsType<CSharpStatementIRNode>(c)));
}
[Fact]
@ -181,12 +148,8 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate
// Assert
Children(irDocument,
n => Assert.IsType<ChecksumIRNode>(n),
n => Assert.IsType<NamespaceDeclarationIRNode>(n));
var @namespace = irDocument.Children[1];
Children(@namespace,
n => Using("System", n),
n => Using(typeof(Task).Namespace, n),
n => Assert.IsType<ClassDeclarationIRNode>(n));
n => Using(typeof(Task).Namespace, n));
}
[Fact]
@ -209,28 +172,23 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate
// Assert
Children(irDocument,
n => Assert.IsType<ChecksumIRNode>(n),
n => Assert.IsType<NamespaceDeclarationIRNode>(n));
var @namespace = irDocument.Children[1];
Children(@namespace,
n => Using("System", n),
n => Using(typeof(Task).Namespace, n),
n => Assert.IsType<ClassDeclarationIRNode>(n));
var @class = @namespace.Children[2];
Children(@class,
n => TagHelperFieldDeclaration(n, "SpanTagHelper"),
n => Assert.IsType<RazorMethodDeclarationIRNode>(n));
var method = @class.Children[1];
var tagHelperNode = SingleChild<TagHelperIRNode>(method);
Children(tagHelperNode,
n => TagHelperStructure("span", TagMode.StartTagAndEndTag, n),
n => Assert.IsType<CreateTagHelperIRNode>(n),
n => TagHelperHtmlAttribute(
"val",
HtmlAttributeValueStyle.DoubleQuotes,
n,
v => CSharpAttributeValue(string.Empty, "Hello", v),
v => LiteralAttributeValue(" ", "World", v)),
n => Assert.IsType<ExecuteTagHelpersIRNode>(n));
n =>
{
var tagHelperNode = Assert.IsType<TagHelperIRNode>(n);
Children(tagHelperNode,
c => TagHelperStructure("span", TagMode.StartTagAndEndTag, c),
c => Assert.IsType<CreateTagHelperIRNode>(c),
c => TagHelperHtmlAttribute(
"val",
HtmlAttributeValueStyle.DoubleQuotes,
c,
v => CSharpAttributeValue(string.Empty, "Hello", v),
v => LiteralAttributeValue(" ", "World", v)),
c => Assert.IsType<ExecuteTagHelpersIRNode>(c));
});
}
[Fact]
@ -256,28 +214,23 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate
// Assert
Children(irDocument,
n => Assert.IsType<ChecksumIRNode>(n),
n => Assert.IsType<NamespaceDeclarationIRNode>(n));
var @namespace = irDocument.Children[1];
Children(@namespace,
n => Using("System", n),
n => Using(typeof(Task).Namespace, n),
n => Assert.IsType<ClassDeclarationIRNode>(n));
var @class = @namespace.Children[2];
Children(@class,
n => TagHelperFieldDeclaration(n, "InputTagHelper"),
n => Assert.IsType<RazorMethodDeclarationIRNode>(n));
var method = @class.Children[1];
var tagHelperNode = SingleChild<TagHelperIRNode>(method);
Children(tagHelperNode,
n => TagHelperStructure("input", TagMode.SelfClosing, n),
n => Assert.IsType<CreateTagHelperIRNode>(n),
n => SetTagHelperProperty(
"bound",
"FooProp",
HtmlAttributeValueStyle.SingleQuotes,
n,
v => Html("foo", v)),
n => Assert.IsType<ExecuteTagHelpersIRNode>(n));
n =>
{
var tagHelperNode = Assert.IsType<TagHelperIRNode>(n);
Children(tagHelperNode,
c => TagHelperStructure("input", TagMode.SelfClosing, c),
c => Assert.IsType<CreateTagHelperIRNode>(c),
c => SetTagHelperProperty(
"bound",
"FooProp",
HtmlAttributeValueStyle.SingleQuotes,
c,
v => Html("foo", v)),
c => Assert.IsType<ExecuteTagHelpersIRNode>(c));
});
}
private DocumentIRNode Lower(RazorCodeDocument codeDocument)
@ -287,15 +240,17 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate
private DocumentIRNode Lower(RazorCodeDocument codeDocument, IEnumerable<TagHelperDescriptor> descriptors)
{
var engine = RazorEngine.Create(
builder => builder.Features.Add(new TagHelperFeature(new TestTagHelperDescriptorResolver(descriptors))));
var engine = RazorEngine.Create(builder =>
{
builder.Features.Add(new TagHelperFeature(new TestTagHelperDescriptorResolver(descriptors)));
});
for (var i = 0; i < engine.Phases.Count; i++)
{
var phase = engine.Phases[i];
phase.Execute(codeDocument);
if (phase is IRazorIRPhase)
if (phase is IRazorIRLoweringPhase)
{
break;
}
@ -303,6 +258,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate
var irDocument = codeDocument.GetIRDocument();
Assert.NotNull(irDocument);
return irDocument;
}

View File

@ -0,0 +1,132 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Razor.Evolution.Legacy;
using Microsoft.AspNetCore.Testing;
using Xunit;
namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate
{
public class RazorIRBuilderExtensionsTest
{
[Fact]
public void AddAfter_EmptyList()
{
// Arrange
var builder = RazorIRBuilder.Document();
var node = new BasicIRNode3();
// Act
builder.AddAfter<BasicIRNode>(node);
// Assert
Assert.Collection(builder.Current.Children, n => Assert.Same(node, n));
}
[Fact]
public void AddAfter_AfterMatch()
{
// Arrange
var builder = RazorIRBuilder.Document();
builder.Add(new BasicIRNode());
builder.Add(new BasicIRNode());
builder.Add(new BasicIRNode3());
var node = new BasicIRNode3();
// Act
builder.AddAfter<BasicIRNode>(node);
// Assert
Assert.Collection(
builder.Current.Children,
n => Assert.IsType<BasicIRNode>(n),
n => Assert.IsType<BasicIRNode>(n),
n => Assert.IsType<BasicIRNode3>(n),
n => Assert.Same(node, n));
}
[Fact]
public void AddAfter_AfterMatch_Noncontinuous()
{
// Arrange
var builder = RazorIRBuilder.Document();
builder.Add(new BasicIRNode());
builder.Add(new BasicIRNode2());
builder.Add(new BasicIRNode());
var node = new BasicIRNode3();
// Act
builder.AddAfter<BasicIRNode>(node);
// Assert
Assert.Collection(
builder.Current.Children,
n => Assert.IsType<BasicIRNode>(n),
n => Assert.IsType<BasicIRNode2>(n),
n => Assert.IsType<BasicIRNode>(n),
n => Assert.Same(node, n));
}
private class BasicIRNode : RazorIRNode
{
public override IList<RazorIRNode> Children { get; } = new List<RazorIRNode>();
public override RazorIRNode Parent { get; set; }
internal override MappingLocation SourceRange { get; set; }
public override void Accept(RazorIRNodeVisitor visitor)
{
throw new NotImplementedException();
}
public override TResult Accept<TResult>(RazorIRNodeVisitor<TResult> visitor)
{
throw new NotImplementedException();
}
}
private class BasicIRNode2 : RazorIRNode
{
public override IList<RazorIRNode> Children { get; } = new List<RazorIRNode>();
public override RazorIRNode Parent { get; set; }
internal override MappingLocation SourceRange { get; set; }
public override void Accept(RazorIRNodeVisitor visitor)
{
throw new NotImplementedException();
}
public override TResult Accept<TResult>(RazorIRNodeVisitor<TResult> visitor)
{
throw new NotImplementedException();
}
}
private class BasicIRNode3 : RazorIRNode
{
public override IList<RazorIRNode> Children { get; } = new List<RazorIRNode>();
public override RazorIRNode Parent { get; set; }
internal override MappingLocation SourceRange { get; set; }
public override void Accept(RazorIRNodeVisitor visitor)
{
throw new NotImplementedException();
}
public override TResult Accept<TResult>(RazorIRNodeVisitor<TResult> visitor)
{
throw new NotImplementedException();
}
}
}
}

View File

@ -139,6 +139,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution
feature => Assert.IsType<DefaultDirectiveSyntaxTreePass>(feature),
feature => Assert.IsType<HtmlNodeOptimizationPass>(feature),
feature => Assert.IsType<TagHelperBinderSyntaxTreePass>(feature),
feature => Assert.IsType<DefaultDocumentClassifier>(feature),
feature => Assert.IsType<DefaultDirectiveIRPass>(feature));
}
@ -160,6 +161,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution
feature => Assert.IsType<DefaultDirectiveSyntaxTreePass>(feature),
feature => Assert.IsType<HtmlNodeOptimizationPass>(feature),
feature => Assert.IsType<TagHelperBinderSyntaxTreePass>(feature),
feature => Assert.IsType<DefaultDocumentClassifier>(feature),
feature => Assert.IsType<DefaultDirectiveIRPass>(feature),
feature => Assert.IsType<RazorEngine.ConfigureDesignTimeOptions>(feature),
feature => Assert.IsType<RazorDesignTimeIRPass>(feature));

View File

@ -14,7 +14,8 @@ namespace Microsoft.AspNetCore.Razor.Evolution.IntegrationTests.TestFiles
#pragma warning disable 1998
public async System.Threading.Tasks.Task ExecuteAsync()
{
WriteLiteral("\r\n\r\n<p>Path\'s full type name is ");
WriteLiteral("\r\n");
WriteLiteral("\r\n<p>Path\'s full type name is ");
#line 9 "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/Imports.cshtml"
Write(typeof(Path).FullName);