From d8b626c843244ef381506bb744e8966c45ddbd20 Mon Sep 17 00:00:00 2001 From: Ryan Nowak Date: Tue, 1 Nov 2016 16:05:36 -0700 Subject: [PATCH] Implement IR lowering phase This is a first cut of IR lowering and includes the basic node types and some tests. --- .../DefaultRazorIRLoweringPhase.cs | 215 ++++++++++++++++++ .../IRazorIRLoweringPhase.cs | 9 + .../Intermediate/BlockDirectiveIRNode.cs | 29 +++ .../CSharpAttributeValueIRNode.cs | 29 +++ .../Intermediate/CSharpExpressionIRNode.cs | 29 +++ .../Intermediate/CSharpStatementIRNode.cs | 29 +++ .../Intermediate/CSharpTokenIRNode.cs | 29 +++ .../Intermediate/ClassDeclarationIRNode.cs | 36 +++ .../Intermediate/DefaultRazorIRBuilder.cs | 11 + .../Intermediate/DirectiveTokenIRNode.cs | 27 +++ .../{RazorIRDocument.cs => DocumentIRNode.cs} | 14 +- .../Intermediate/HtmlAttributeIRNode.cs | 33 +++ .../Intermediate/HtmlAttributeValueIRNode.cs | 29 +++ .../Intermediate/HtmlContentIRNode.cs | 40 ++++ .../Intermediate/MethodDeclarationIRNode.cs | 35 +++ .../NamespaceDeclarationIRNode.cs | 27 +++ .../Intermediate/RazorIRBuilder.cs | 16 +- .../Intermediate/RazorIRNode.cs | 3 + .../Intermediate/RazorIRNodeVisitor.cs | 82 ++++++- .../Intermediate/RazorIRNodeVisitorOfT.cs | 81 ++++++- .../Intermediate/SectionIRNode.cs | 29 +++ .../Intermediate/SingleLineDirectiveIRNode.cs | 29 +++ .../Intermediate/TemplateIRNode.cs | 27 +++ .../Intermediate/UsingStatementIRNode.cs | 40 ++++ .../Legacy/AddImportChunkGenerator.cs | 5 + .../Legacy/AddTagHelperChunkGenerator.cs | 5 + .../Legacy/AttributeBlockChunkGenerator.cs | 10 + .../Legacy/Block.cs | 5 + .../DynamicAttributeBlockChunkGenerator.cs | 11 + .../Legacy/ExpressionChunkGenerator.cs | 17 ++ .../Legacy/IParentChunkGenerator.cs | 2 + .../Legacy/ISpanChunkGenerator.cs | 2 + .../Legacy/LiteralAttributeChunkGenerator.cs | 5 + .../Legacy/MarkupChunkGenerator.cs | 5 + .../Legacy/ParentChunkGenerator.cs | 11 + .../Legacy/ParserVisitor.cs | 132 +++++++++++ .../Legacy/RazorCommentChunkGenerator.cs | 9 + .../Legacy/RemoveTagHelperChunkGenerator.cs | 5 + .../Legacy/SectionChunkGenerator.cs | 10 + .../Legacy/SetBaseTypeChunkGenerator.cs | 5 + .../Legacy/Span.cs | 5 + .../Legacy/SpanChunkGenerator.cs | 8 + .../Legacy/StatementChunkGenerator.cs | 5 + .../Legacy/SyntaxTreeNode.cs | 2 + .../TagHelperPrefixDirectiveChunkGenerator.cs | 5 + .../Legacy/TemplateBlockChunkGenerator.cs | 10 + .../Legacy/TypeMemberChunkGenerator.cs | 5 + .../RazorCodeDocumentExtensions.cs | 21 ++ .../RazorEngine.cs | 1 + .../Intermediate/DefaultRazorIRBuilderTest.cs | 23 ++ ...aultRazorIRLoweringPhaseIntegrationTest.cs | 118 ++++++++++ .../Intermediate/RazorIRAssert.cs | 169 ++++++++++++++ .../Intermediate/RazorIRBuilderTest.cs | 2 +- .../Intermediate/RazorIRNodeWalkerTest.cs | 3 + .../RazorCodeDocumentExtensionsTest.cs | 32 +++ .../RazorEngineTest.cs | 3 +- .../TestRazorCodeDocument.cs | 6 + 57 files changed, 1571 insertions(+), 14 deletions(-) create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/DefaultRazorIRLoweringPhase.cs create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/IRazorIRLoweringPhase.cs create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/BlockDirectiveIRNode.cs create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/CSharpAttributeValueIRNode.cs create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/CSharpExpressionIRNode.cs create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/CSharpStatementIRNode.cs create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/CSharpTokenIRNode.cs create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/ClassDeclarationIRNode.cs create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/DirectiveTokenIRNode.cs rename src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/{RazorIRDocument.cs => DocumentIRNode.cs} (65%) create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/HtmlAttributeIRNode.cs create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/HtmlAttributeValueIRNode.cs create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/HtmlContentIRNode.cs create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/MethodDeclarationIRNode.cs create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/NamespaceDeclarationIRNode.cs create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/SectionIRNode.cs create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/SingleLineDirectiveIRNode.cs create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/TemplateIRNode.cs create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/UsingStatementIRNode.cs create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/Legacy/ParserVisitor.cs create mode 100644 test/Microsoft.AspNetCore.Razor.Evolution.Test/Intermediate/DefaultRazorIRLoweringPhaseIntegrationTest.cs create mode 100644 test/Microsoft.AspNetCore.Razor.Evolution.Test/Intermediate/RazorIRAssert.cs diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/DefaultRazorIRLoweringPhase.cs b/src/Microsoft.AspNetCore.Razor.Evolution/DefaultRazorIRLoweringPhase.cs new file mode 100644 index 0000000000..bc4c151380 --- /dev/null +++ b/src/Microsoft.AspNetCore.Razor.Evolution/DefaultRazorIRLoweringPhase.cs @@ -0,0 +1,215 @@ +// 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 Microsoft.AspNetCore.Razor.Evolution.Legacy; + +namespace Microsoft.AspNetCore.Razor.Evolution +{ + internal class DefaultRazorIRLoweringPhase : RazorEnginePhaseBase, IRazorIRLoweringPhase + { + protected override void ExecuteCore(RazorCodeDocument codeDocument) + { + var syntaxTree = codeDocument.GetSyntaxTree(); + ThrowForMissingDependency(syntaxTree); + + var visitor = new Visitor(); + + visitor.VisitBlock(syntaxTree.Root); + + var irDocument = (DocumentIRNode)visitor.Builder.Build(); + codeDocument.SetIRDocument(irDocument); + } + + private class Visitor : ParserVisitor + { + public Visitor() + { + Builder = RazorIRBuilder.Document(); + + Namespace = new NamespaceDeclarationIRNode(); + NamespaceBuilder = RazorIRBuilder.Create(Namespace); + Builder.Push(Namespace); + + Class = new ClassDeclarationIRNode(); + ClassBuilder = RazorIRBuilder.Create(Class); + Builder.Push(Class); + + Method = new MethodDeclarationIRNode(); + Builder.Push(Method); + } + + public RazorIRBuilder Builder { get; } + + public NamespaceDeclarationIRNode Namespace { get; } + + public RazorIRBuilder NamespaceBuilder { get; } + + public ClassDeclarationIRNode Class { get; } + + public RazorIRBuilder ClassBuilder { get; } + + public MethodDeclarationIRNode Method { get; } + + public override void VisitStartAttributeBlock(AttributeBlockChunkGenerator chunk, Block block) + { + Builder.Push(new HtmlAttributeIRNode() + { + Name = chunk.Name, + Prefix = chunk.Prefix, + Suffix = chunk.Prefix, + + SourceLocation = block.Start, + }); + } + + public override void VisitEndAttributeBlock(AttributeBlockChunkGenerator chunk, Block block) + { + Builder.Pop(); + } + + public override void VisitStartDynamicAttributeBlock(DynamicAttributeBlockChunkGenerator chunk, Block block) + { + Builder.Push(new CSharpAttributeValueIRNode() + { + Prefix = chunk.Prefix, + SourceLocation = block.Start, + }); + } + + public override void VisitEndDynamicAttributeBlock(DynamicAttributeBlockChunkGenerator chunk, Block block) + { + Builder.Pop(); + } + + public override void VisitLiteralAttributeSpan(LiteralAttributeChunkGenerator chunk, Span span) + { + Builder.Add(new HtmlAttributeValueIRNode() + { + Prefix = chunk.Prefix, + SourceLocation = span.Start, + }); + } + + public override void VisitStartTemplateBlock(TemplateBlockChunkGenerator chunk, Block block) + { + Builder.Push(new TemplateIRNode()); + } + + public override void VisitEndTemplateBlock(TemplateBlockChunkGenerator chunk, Block block) + { + Builder.Pop(); + } + + // CSharp expressions are broken up into blocks and spans because Razor allows Razor comments + // inside an expression. + // Ex: + // @DateTime.@*This is a comment*@Now + // + // We need to capture this in the IR so that we can give each piece the correct source mappings + public override void VisitStartExpressionBlock(ExpressionChunkGenerator chunk, Block block) + { + Builder.Push(new CSharpExpressionIRNode() + { + SourceLocation = block.Start, + }); + } + + public override void VisitEndExpressionBlock(ExpressionChunkGenerator chunk, Block block) + { + Builder.Pop(); + } + + public override void VisitExpressionSpan(ExpressionChunkGenerator chunk, Span span) + { + Builder.Add(new CSharpTokenIRNode() + { + Content = span.Content, + SourceLocation = span.Start, + }); + } + + public override void VisitStartSectionBlock(SectionChunkGenerator chunk, Block block) + { + Builder.Push(new SectionIRNode() + { + Name = chunk.SectionName, + }); + } + + public override void VisitEndSectionBlock(SectionChunkGenerator chunk, Block block) + { + Builder.Pop(); + } + + public override void VisitTypeMemberSpan(TypeMemberChunkGenerator chunk, Span span) + { + ClassBuilder.Add(new CSharpStatementIRNode() + { + Content = span.Content, + SourceLocation = span.Start, + }); + } + + public override void VisitAddTagHelperSpan(AddTagHelperChunkGenerator chunk, Span span) + { + // Empty for now + } + + public override void VisitRemoveTagHelperSpan(RemoveTagHelperChunkGenerator chunk, Span span) + { + // Empty for now + } + + public override void VisitTagHelperPrefixSpan(TagHelperPrefixDirectiveChunkGenerator chunk, Span span) + { + // Empty for now + } + + public override void VisitStatementSpan(StatementChunkGenerator chunk, Span span) + { + Builder.Add(new CSharpStatementIRNode() + { + Content = span.Content, + SourceLocation = span.Start, + }); + } + + public override void VisitSetBaseTypeSpan(SetBaseTypeChunkGenerator chunk, Span span) + { + Class.BaseType = span.Content; + } + + public override void VisitMarkupSpan(MarkupChunkGenerator chunk, Span span) + { + Builder.Add(new HtmlContentIRNode() + { + Content = span.Content, + SourceLocation = span.Start, + }); + } + + public override void VisitImportSpan(AddImportChunkGenerator chunk, Span span) + { + // 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 = span.Content, + Parent = Namespace, + SourceLocation = span.Start, + }; + + Namespace.Children.Insert(i, @using); + } + } + } +} diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/IRazorIRLoweringPhase.cs b/src/Microsoft.AspNetCore.Razor.Evolution/IRazorIRLoweringPhase.cs new file mode 100644 index 0000000000..556f43e358 --- /dev/null +++ b/src/Microsoft.AspNetCore.Razor.Evolution/IRazorIRLoweringPhase.cs @@ -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 IRazorIRLoweringPhase : IRazorEnginePhase + { + } +} diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/BlockDirectiveIRNode.cs b/src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/BlockDirectiveIRNode.cs new file mode 100644 index 0000000000..6c09834b53 --- /dev/null +++ b/src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/BlockDirectiveIRNode.cs @@ -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 System.Collections.Generic; +using Microsoft.AspNetCore.Razor.Evolution.Legacy; + +namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate +{ + public class BlockDirectiveIRNode : RazorIRNode + { + public string Name { get; set; } + + public override IList Children { get; } = new List(); + + public override RazorIRNode Parent { get; set; } + + internal override SourceLocation SourceLocation { get; set; } + + public override void Accept(RazorIRNodeVisitor visitor) + { + visitor.VisitBlockDirective(this); + } + + public override TResult Accept(RazorIRNodeVisitor visitor) + { + return visitor.VisitBlockDirective(this); + } + } +} diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/CSharpAttributeValueIRNode.cs b/src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/CSharpAttributeValueIRNode.cs new file mode 100644 index 0000000000..c3dfa34e5e --- /dev/null +++ b/src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/CSharpAttributeValueIRNode.cs @@ -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 System.Collections.Generic; +using Microsoft.AspNetCore.Razor.Evolution.Legacy; + +namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate +{ + public class CSharpAttributeValueIRNode : RazorIRNode + { + public string Prefix { get; set; } + + public override IList Children { get; } = new List(); + + public override RazorIRNode Parent { get; set; } + + internal override SourceLocation SourceLocation { get; set; } + + public override void Accept(RazorIRNodeVisitor visitor) + { + visitor.VisitCSharpAttributeValue(this); + } + + public override TResult Accept(RazorIRNodeVisitor visitor) + { + return visitor.VisitCSharpAttributeValue(this); + } + } +} diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/CSharpExpressionIRNode.cs b/src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/CSharpExpressionIRNode.cs new file mode 100644 index 0000000000..6b669cb2b3 --- /dev/null +++ b/src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/CSharpExpressionIRNode.cs @@ -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 System.Collections.Generic; +using Microsoft.AspNetCore.Razor.Evolution.Legacy; + +namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate +{ + public class CSharpExpressionIRNode : RazorIRNode + { + public override IList Children { get; } = new List(); + + public override RazorIRNode Parent { get; set; } + + internal override SourceLocation SourceLocation { get; set; } + + public string Content { get; set; } + + public override void Accept(RazorIRNodeVisitor visitor) + { + visitor.VisitCSharpExpression(this); + } + + public override TResult Accept(RazorIRNodeVisitor visitor) + { + return visitor.VisitCSharpExpression(this); + } + } +} diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/CSharpStatementIRNode.cs b/src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/CSharpStatementIRNode.cs new file mode 100644 index 0000000000..684f87005b --- /dev/null +++ b/src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/CSharpStatementIRNode.cs @@ -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 System.Collections.Generic; +using Microsoft.AspNetCore.Razor.Evolution.Legacy; + +namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate +{ + public class CSharpStatementIRNode : RazorIRNode + { + public override IList Children { get; } = new List(); + + public override RazorIRNode Parent { get; set; } + + internal override SourceLocation SourceLocation { get; set; } + + public string Content { get; set; } + + public override void Accept(RazorIRNodeVisitor visitor) + { + visitor.VisitCSharpStatement(this); + } + + public override TResult Accept(RazorIRNodeVisitor visitor) + { + return visitor.VisitCSharpStatement(this); + } + } +} diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/CSharpTokenIRNode.cs b/src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/CSharpTokenIRNode.cs new file mode 100644 index 0000000000..1c823e02b0 --- /dev/null +++ b/src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/CSharpTokenIRNode.cs @@ -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 System.Collections.Generic; +using Microsoft.AspNetCore.Razor.Evolution.Legacy; + +namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate +{ + public class CSharpTokenIRNode : RazorIRNode + { + public override IList Children { get; } = EmptyArray; + + public override RazorIRNode Parent { get; set; } + + internal override SourceLocation SourceLocation { get; set; } + + public string Content { get; set; } + + public override void Accept(RazorIRNodeVisitor visitor) + { + visitor.VisitCSharpToken(this); + } + + public override TResult Accept(RazorIRNodeVisitor visitor) + { + return visitor.VisitCSharpToken(this); + } + } +} diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/ClassDeclarationIRNode.cs b/src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/ClassDeclarationIRNode.cs new file mode 100644 index 0000000000..bb57f0829f --- /dev/null +++ b/src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/ClassDeclarationIRNode.cs @@ -0,0 +1,36 @@ +// 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; + +namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate +{ + public class ClassDeclarationIRNode : RazorIRNode + { + public string AccessModifier { get; set; } + + public string Name { get; set; } + + public string BaseType { get; set; } + + public IList Interfaces { get; set; } + + public override IList Children { get; } = new List(); + + public override RazorIRNode Parent { get; set; } + + internal override SourceLocation SourceLocation { get; set; } + + public override void Accept(RazorIRNodeVisitor visitor) + { + visitor.VisitClass(this); + } + + public override TResult Accept(RazorIRNodeVisitor visitor) + { + return visitor.VisitClass(this); + } + } +} diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/DefaultRazorIRBuilder.cs b/src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/DefaultRazorIRBuilder.cs index 78915e5b9f..0a0e16d550 100644 --- a/src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/DefaultRazorIRBuilder.cs +++ b/src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/DefaultRazorIRBuilder.cs @@ -35,6 +35,17 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate Pop(); } + public override RazorIRNode Build() + { + RazorIRNode node = null; + while (_depth > 0) + { + node = Pop(); + } + + return node; + } + public override RazorIRNode Pop() { if (_depth == 0) diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/DirectiveTokenIRNode.cs b/src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/DirectiveTokenIRNode.cs new file mode 100644 index 0000000000..ff1988e871 --- /dev/null +++ b/src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/DirectiveTokenIRNode.cs @@ -0,0 +1,27 @@ +// 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.Legacy; + +namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate +{ + public class DirectiveTokenIRNode : RazorIRNode + { + public override IList Children { get; } = new List(); + + public override RazorIRNode Parent { get; set; } + + internal override SourceLocation SourceLocation { get; set; } + + public override void Accept(RazorIRNodeVisitor visitor) + { + visitor.VisitDirectiveToken(this); + } + + public override TResult Accept(RazorIRNodeVisitor visitor) + { + return visitor.VisitDirectiveToken(this); + } + } +} diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/RazorIRDocument.cs b/src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/DocumentIRNode.cs similarity index 65% rename from src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/RazorIRDocument.cs rename to src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/DocumentIRNode.cs index 84f1d65220..41f07531fc 100644 --- a/src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/RazorIRDocument.cs +++ b/src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/DocumentIRNode.cs @@ -2,22 +2,18 @@ // 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.Legacy; namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate { - public sealed class RazorIRDocument : RazorIRNode + public sealed class DocumentIRNode : RazorIRNode { - // Only allow creation of documents through the builder API because - // they can't be nested. - internal RazorIRDocument() - { - Children = new List(); - } - - public override IList Children { get; } + public override IList Children { get; } = new List(); public override RazorIRNode Parent { get; set; } + internal override SourceLocation SourceLocation { get; set; } + public override void Accept(RazorIRNodeVisitor visitor) { visitor.VisitDocument(this); diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/HtmlAttributeIRNode.cs b/src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/HtmlAttributeIRNode.cs new file mode 100644 index 0000000000..02ef2b96bd --- /dev/null +++ b/src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/HtmlAttributeIRNode.cs @@ -0,0 +1,33 @@ +// 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.Legacy; + +namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate +{ + public class HtmlAttributeIRNode : RazorIRNode + { + public string Name { get; set; } + + public string Prefix { get; set; } + + public string Suffix { get; set; } + + public override IList Children { get; } = new List(); + + public override RazorIRNode Parent { get; set; } + + internal override SourceLocation SourceLocation { get; set; } + + public override void Accept(RazorIRNodeVisitor visitor) + { + visitor.VisitHtmlAttribute(this); + } + + public override TResult Accept(RazorIRNodeVisitor visitor) + { + return visitor.VisitHtmlAttribute(this); + } + } +} diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/HtmlAttributeValueIRNode.cs b/src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/HtmlAttributeValueIRNode.cs new file mode 100644 index 0000000000..23657f751d --- /dev/null +++ b/src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/HtmlAttributeValueIRNode.cs @@ -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 System.Collections.Generic; +using Microsoft.AspNetCore.Razor.Evolution.Legacy; + +namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate +{ + public class HtmlAttributeValueIRNode : RazorIRNode + { + public string Prefix { get; set; } + + public override IList Children { get; } = new List(); + + public override RazorIRNode Parent { get; set; } + + internal override SourceLocation SourceLocation { get; set; } + + public override void Accept(RazorIRNodeVisitor visitor) + { + visitor.VisitHtmlAttributeValue(this); + } + + public override TResult Accept(RazorIRNodeVisitor visitor) + { + return visitor.VisitHtmlAttributeValue(this); + } + } +} diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/HtmlContentIRNode.cs b/src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/HtmlContentIRNode.cs new file mode 100644 index 0000000000..b58cd71362 --- /dev/null +++ b/src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/HtmlContentIRNode.cs @@ -0,0 +1,40 @@ +// 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; + +namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate +{ + public sealed class HtmlContentIRNode : RazorIRNode + { + public string Content { get; set; } + + public override IList Children { get; } = new List(); + + public override RazorIRNode Parent { get; set; } + + internal override SourceLocation SourceLocation { get; set; } + + public override void Accept(RazorIRNodeVisitor visitor) + { + if (visitor == null) + { + throw new ArgumentNullException(nameof(visitor)); + } + + visitor.VisitHtml(this); + } + + public override TResult Accept(RazorIRNodeVisitor visitor) + { + if (visitor == null) + { + throw new ArgumentNullException(nameof(visitor)); + } + + return visitor.VisitHtml(this); + } + } +} diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/MethodDeclarationIRNode.cs b/src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/MethodDeclarationIRNode.cs new file mode 100644 index 0000000000..38ca704ee9 --- /dev/null +++ b/src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/MethodDeclarationIRNode.cs @@ -0,0 +1,35 @@ +// 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.Legacy; + +namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate +{ + public class MethodDeclarationIRNode : RazorIRNode + { + public string AccessModifier { get; set; } + + public IList Modifiers { get; set; } + + public string Name { get; set; } + + public string ReturnType { get; set; } + + public override IList Children { get; } = new List(); + + public override RazorIRNode Parent { get; set; } + + internal override SourceLocation SourceLocation { get; set; } + + public override void Accept(RazorIRNodeVisitor visitor) + { + visitor.VisitMethodDeclaration(this); + } + + public override TResult Accept(RazorIRNodeVisitor visitor) + { + return visitor.VisitMethodDeclaration(this); + } + } +} diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/NamespaceDeclarationIRNode.cs b/src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/NamespaceDeclarationIRNode.cs new file mode 100644 index 0000000000..7c869c3558 --- /dev/null +++ b/src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/NamespaceDeclarationIRNode.cs @@ -0,0 +1,27 @@ +// 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.Legacy; + +namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate +{ + public class NamespaceDeclarationIRNode : RazorIRNode + { + public override IList Children { get; } = new List(); + + public override RazorIRNode Parent { get; set; } + + internal override SourceLocation SourceLocation { get; set; } + + public override void Accept(RazorIRNodeVisitor visitor) + { + visitor.VisitNamespace(this); + } + + public override TResult Accept(RazorIRNodeVisitor visitor) + { + return visitor.VisitNamespace(this); + } + } +} diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/RazorIRBuilder.cs b/src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/RazorIRBuilder.cs index 3ca31d81a0..14cf934d04 100644 --- a/src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/RazorIRBuilder.cs +++ b/src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/RazorIRBuilder.cs @@ -1,14 +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. +using System; + namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate { public abstract class RazorIRBuilder { public static RazorIRBuilder Document() { + return Create(new DocumentIRNode()); + } + + public static RazorIRBuilder Create(RazorIRNode root) + { + if (root == null) + { + throw new ArgumentNullException(nameof(root)); + } + var builder = new DefaultRazorIRBuilder(); - builder.Push(new RazorIRDocument()); + builder.Push(root); return builder; } @@ -16,6 +28,8 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate public abstract void Add(RazorIRNode node); + public abstract RazorIRNode Build(); + public abstract void Push(RazorIRNode node); public abstract RazorIRNode Pop(); diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/RazorIRNode.cs b/src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/RazorIRNode.cs index 00f6fedd26..5b6d9cc40d 100644 --- a/src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/RazorIRNode.cs +++ b/src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/RazorIRNode.cs @@ -2,6 +2,7 @@ // 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.Legacy; namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate { @@ -13,6 +14,8 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate public abstract RazorIRNode Parent { get; set; } + internal abstract SourceLocation SourceLocation { get; set; } + public abstract void Accept(RazorIRNodeVisitor visitor); public abstract TResult Accept(RazorIRNodeVisitor visitor); diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/RazorIRNodeVisitor.cs b/src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/RazorIRNodeVisitor.cs index 4fcd13f3a5..6e45ed1eb0 100644 --- a/src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/RazorIRNodeVisitor.cs +++ b/src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/RazorIRNodeVisitor.cs @@ -14,7 +14,87 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate { } - public virtual void VisitDocument(RazorIRDocument node) + public virtual void VisitDirectiveToken(DirectiveTokenIRNode node) + { + VisitDefault(node); + } + + public virtual void VisitTemplate(TemplateIRNode node) + { + VisitDefault(node); + } + + public virtual void VisitSection(SectionIRNode node) + { + VisitDefault(node); + } + + public virtual void VisitCSharpStatement(CSharpStatementIRNode node) + { + VisitDefault(node); + } + + public virtual void VisitCSharpExpression(CSharpExpressionIRNode node) + { + VisitDefault(node); + } + + public virtual void VisitCSharpToken(CSharpTokenIRNode node) + { + VisitDefault(node); + } + + public virtual void VisitHtmlAttributeValue(HtmlAttributeValueIRNode node) + { + VisitDefault(node); + } + + public virtual void VisitCSharpAttributeValue(CSharpAttributeValueIRNode node) + { + VisitDefault(node); + } + + public virtual void VisitHtmlAttribute(HtmlAttributeIRNode node) + { + VisitDefault(node); + } + + public virtual void VisitSingleLineDirective(SingleLineDirectiveIRNode node) + { + VisitDefault(node); + } + + public virtual void VisitBlockDirective(BlockDirectiveIRNode node) + { + VisitDefault(node); + } + + public virtual void VisitClass(ClassDeclarationIRNode node) + { + VisitDefault(node); + } + + public virtual void VisitMethodDeclaration(MethodDeclarationIRNode node) + { + VisitDefault(node); + } + + public virtual void VisitDocument(DocumentIRNode node) + { + VisitDefault(node); + } + + public virtual void VisitHtml(HtmlContentIRNode node) + { + VisitDefault(node); + } + + public virtual void VisitNamespace(NamespaceDeclarationIRNode node) + { + VisitDefault(node); + } + + public virtual void VisitUsingStatement(UsingStatementIRNode node) { VisitDefault(node); } diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/RazorIRNodeVisitorOfT.cs b/src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/RazorIRNodeVisitorOfT.cs index 21f9ec8fd1..b8ad7ad7ae 100644 --- a/src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/RazorIRNodeVisitorOfT.cs +++ b/src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/RazorIRNodeVisitorOfT.cs @@ -15,7 +15,86 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate return default(TResult); } - public virtual TResult VisitDocument(RazorIRDocument node) + public virtual TResult VisitDirectiveToken(DirectiveTokenIRNode node) + { + return VisitDefault(node); + } + + public virtual TResult VisitTemplate(TemplateIRNode node) + { + return VisitDefault(node); + } + + public virtual TResult VisitSection(SectionIRNode node) + { + return VisitDefault(node); + } + + public virtual TResult VisitCSharpStatement(CSharpStatementIRNode node) + { + return VisitDefault(node); + } + + public virtual TResult VisitCSharpExpression(CSharpExpressionIRNode node) + { + return VisitDefault(node); + } + + public virtual TResult VisitCSharpToken(CSharpTokenIRNode node) + { + return VisitDefault(node); + } + + public virtual TResult VisitHtmlAttributeValue(HtmlAttributeValueIRNode node) + { + return VisitDefault(node); + } + + public virtual TResult VisitCSharpAttributeValue(CSharpAttributeValueIRNode node) + { + return VisitDefault(node); + } + + public virtual TResult VisitHtmlAttribute(HtmlAttributeIRNode node) + { + return VisitDefault(node); + } + + public virtual TResult VisitClass(ClassDeclarationIRNode node) + { + return VisitDefault(node); + } + + public virtual TResult VisitSingleLineDirective(SingleLineDirectiveIRNode node) + { + return VisitDefault(node); + } + + public virtual TResult VisitBlockDirective(BlockDirectiveIRNode node) + { + return VisitDefault(node); + } + public virtual TResult VisitMethodDeclaration(MethodDeclarationIRNode node) + { + return VisitDefault(node); + } + + public virtual TResult VisitDocument(DocumentIRNode node) + { + return VisitDefault(node); + } + + public virtual TResult VisitHtml(HtmlContentIRNode node) + { + return VisitDefault(node); + } + + public virtual TResult VisitNamespace(NamespaceDeclarationIRNode node) + { + return VisitDefault(node); + } + + public virtual TResult VisitUsingStatement(UsingStatementIRNode node) { return VisitDefault(node); } diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/SectionIRNode.cs b/src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/SectionIRNode.cs new file mode 100644 index 0000000000..9d6dba5f3e --- /dev/null +++ b/src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/SectionIRNode.cs @@ -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 System.Collections.Generic; +using Microsoft.AspNetCore.Razor.Evolution.Legacy; + +namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate +{ + public class SectionIRNode : RazorIRNode + { + public string Name { get; set; } + + public override IList Children { get; } = new List(); + + public override RazorIRNode Parent { get; set; } + + internal override SourceLocation SourceLocation { get; set; } + + public override void Accept(RazorIRNodeVisitor visitor) + { + visitor.VisitSection(this); + } + + public override TResult Accept(RazorIRNodeVisitor visitor) + { + return visitor.VisitSection(this); + } + } +} diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/SingleLineDirectiveIRNode.cs b/src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/SingleLineDirectiveIRNode.cs new file mode 100644 index 0000000000..a599056b95 --- /dev/null +++ b/src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/SingleLineDirectiveIRNode.cs @@ -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 System.Collections.Generic; +using Microsoft.AspNetCore.Razor.Evolution.Legacy; + +namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate +{ + public class SingleLineDirectiveIRNode : RazorIRNode + { + public string Name { get; set; } + + public override IList Children { get; } = new List(); + + public override RazorIRNode Parent { get; set; } + + internal override SourceLocation SourceLocation { get; set; } + + public override void Accept(RazorIRNodeVisitor visitor) + { + visitor.VisitSingleLineDirective(this); + } + + public override TResult Accept(RazorIRNodeVisitor visitor) + { + return visitor.VisitSingleLineDirective(this); + } + } +} diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/TemplateIRNode.cs b/src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/TemplateIRNode.cs new file mode 100644 index 0000000000..b1cb73ff54 --- /dev/null +++ b/src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/TemplateIRNode.cs @@ -0,0 +1,27 @@ +// 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.Legacy; + +namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate +{ + public class TemplateIRNode : RazorIRNode + { + public override IList Children { get; } = new List(); + + public override RazorIRNode Parent { get; set; } + + internal override SourceLocation SourceLocation { get; set; } + + public override void Accept(RazorIRNodeVisitor visitor) + { + visitor.VisitTemplate(this); + } + + public override TResult Accept(RazorIRNodeVisitor visitor) + { + return visitor.VisitTemplate(this); + } + } +} diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/UsingStatementIRNode.cs b/src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/UsingStatementIRNode.cs new file mode 100644 index 0000000000..57cecee3b9 --- /dev/null +++ b/src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/UsingStatementIRNode.cs @@ -0,0 +1,40 @@ +// 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; + +namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate +{ + public class UsingStatementIRNode : RazorIRNode + { + public override IList Children { get; } = new List(); + + public override RazorIRNode Parent { get; set; } + + internal override SourceLocation SourceLocation { get; set; } + + public string Content { get; set; } + + public override void Accept(RazorIRNodeVisitor visitor) + { + if (visitor == null) + { + throw new ArgumentNullException(nameof(visitor)); + } + + visitor.VisitUsingStatement(this); + } + + public override TResult Accept(RazorIRNodeVisitor visitor) + { + if (visitor == null) + { + throw new ArgumentNullException(nameof(visitor)); + } + + return visitor.VisitUsingStatement(this); + } + } +} diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/AddImportChunkGenerator.cs b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/AddImportChunkGenerator.cs index ae0147117c..01f16ab40e 100644 --- a/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/AddImportChunkGenerator.cs +++ b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/AddImportChunkGenerator.cs @@ -14,6 +14,11 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy public string Namespace { get; } + public override void Accept(ParserVisitor visitor, Span span) + { + visitor.VisitImportSpan(this, span); + } + public override void GenerateChunk(Span target, ChunkGeneratorContext context) { var ns = Namespace; diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/AddTagHelperChunkGenerator.cs b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/AddTagHelperChunkGenerator.cs index e9577d7e4f..ca4e2d449f 100644 --- a/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/AddTagHelperChunkGenerator.cs +++ b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/AddTagHelperChunkGenerator.cs @@ -15,6 +15,11 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy public string LookupText { get; } + public override void Accept(ParserVisitor visitor, Span span) + { + visitor.VisitAddTagHelperSpan(this, span); + } + public override void GenerateChunk(Span target, ChunkGeneratorContext context) { //context.ChunkTreeBuilder.AddAddTagHelperChunk(LookupText, target); diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/AttributeBlockChunkGenerator.cs b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/AttributeBlockChunkGenerator.cs index 662441405f..544eb59bf3 100644 --- a/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/AttributeBlockChunkGenerator.cs +++ b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/AttributeBlockChunkGenerator.cs @@ -36,6 +36,16 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy //context.ChunkTreeBuilder.EndParentChunk(); } + public override void AcceptStart(ParserVisitor visitor, Block block) + { + visitor.VisitStartAttributeBlock(this, block); + } + + public override void AcceptEnd(ParserVisitor visitor, Block block) + { + visitor.VisitEndAttributeBlock(this, block); + } + public override string ToString() { return string.Format(CultureInfo.CurrentCulture, "Attr:{0},{1:F},{2:F}", Name, Prefix, Suffix); diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/Block.cs b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/Block.cs index f3bee78695..c6a7e6f1b9 100644 --- a/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/Block.cs +++ b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/Block.cs @@ -176,6 +176,11 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy return hashCodeCombiner.CombinedHash; } + public override void Accept(ParserVisitor visitor) + { + visitor.VisitBlock(this); + } + private class EquivalenceComparer : IEqualityComparer { public static readonly EquivalenceComparer Default = new EquivalenceComparer(); diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/DynamicAttributeBlockChunkGenerator.cs b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/DynamicAttributeBlockChunkGenerator.cs index c1d790ee4e..735e81e3cb 100644 --- a/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/DynamicAttributeBlockChunkGenerator.cs +++ b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/DynamicAttributeBlockChunkGenerator.cs @@ -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.Globalization; namespace Microsoft.AspNetCore.Razor.Evolution.Legacy @@ -22,6 +23,16 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy public SourceLocation ValueStart { get; } + public override void AcceptStart(ParserVisitor visitor, Block block) + { + visitor.VisitStartDynamicAttributeBlock(this, block); + } + + public override void AcceptEnd(ParserVisitor visitor, Block block) + { + visitor.VisitEndDynamicAttributeBlock(this, block); + } + public override void GenerateStartParentChunk(Block target, ChunkGeneratorContext context) { //var chunk = context.ChunkTreeBuilder.StartParentChunk(target); diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/ExpressionChunkGenerator.cs b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/ExpressionChunkGenerator.cs index 4bc1e7789b..08d7e3d1d1 100644 --- a/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/ExpressionChunkGenerator.cs +++ b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/ExpressionChunkGenerator.cs @@ -1,6 +1,8 @@ // 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; + namespace Microsoft.AspNetCore.Razor.Evolution.Legacy { internal class ExpressionChunkGenerator : ISpanChunkGenerator, IParentChunkGenerator @@ -22,6 +24,21 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy //context.ChunkTreeBuilder.EndParentChunk(); } + public void Accept(ParserVisitor visitor, Span span) + { + visitor.VisitExpressionSpan(this, span); + } + + public void AcceptStart(ParserVisitor visitor, Block block) + { + visitor.VisitStartExpressionBlock(this, block); + } + + public void AcceptEnd(ParserVisitor visitor, Block block) + { + visitor.VisitEndExpressionBlock(this, block); + } + public override string ToString() { return "Expr"; diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/IParentChunkGenerator.cs b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/IParentChunkGenerator.cs index 857e8b813a..072752042b 100644 --- a/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/IParentChunkGenerator.cs +++ b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/IParentChunkGenerator.cs @@ -7,5 +7,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy { void GenerateStartParentChunk(Block target, ChunkGeneratorContext context); void GenerateEndParentChunk(Block target, ChunkGeneratorContext context); + void AcceptStart(ParserVisitor visitor, Block block); + void AcceptEnd(ParserVisitor visitor, Block block); } } diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/ISpanChunkGenerator.cs b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/ISpanChunkGenerator.cs index 985cb9ddb8..3389366e7e 100644 --- a/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/ISpanChunkGenerator.cs +++ b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/ISpanChunkGenerator.cs @@ -6,5 +6,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy internal interface ISpanChunkGenerator { void GenerateChunk(Span target, ChunkGeneratorContext context); + + void Accept(ParserVisitor visitor, Span span); } } diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/LiteralAttributeChunkGenerator.cs b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/LiteralAttributeChunkGenerator.cs index ce81459d8e..4dc7f66db0 100644 --- a/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/LiteralAttributeChunkGenerator.cs +++ b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/LiteralAttributeChunkGenerator.cs @@ -28,6 +28,11 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy public LocationTagged ValueGenerator { get; } + public override void Accept(ParserVisitor visitor, Span span) + { + visitor.VisitLiteralAttributeSpan(this, span); + } + public override void GenerateChunk(Span target, ChunkGeneratorContext context) { //var chunk = context.ChunkTreeBuilder.StartParentChunk(target); diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/MarkupChunkGenerator.cs b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/MarkupChunkGenerator.cs index 22da931ebf..32cc5e229a 100644 --- a/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/MarkupChunkGenerator.cs +++ b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/MarkupChunkGenerator.cs @@ -10,6 +10,11 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy //context.ChunkTreeBuilder.AddLiteralChunk(target.Content, target); } + public override void Accept(ParserVisitor visitor, Span span) + { + visitor.VisitMarkupSpan(this, span); + } + public override string ToString() { return "Markup"; diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/ParentChunkGenerator.cs b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/ParentChunkGenerator.cs index b4278dd774..dfa047594d 100644 --- a/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/ParentChunkGenerator.cs +++ b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/ParentChunkGenerator.cs @@ -9,6 +9,9 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy public static readonly IParentChunkGenerator Null = new NullParentChunkGenerator(); + public abstract void AcceptStart(ParserVisitor visitor, Block block); + public abstract void AcceptEnd(ParserVisitor visitor, Block block); + public virtual void GenerateStartParentChunk(Block target, ChunkGeneratorContext context) { } @@ -42,6 +45,14 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy { return "None"; } + + public void AcceptStart(ParserVisitor visitor, Block block) + { + } + + public void AcceptEnd(ParserVisitor visitor, Block block) + { + } } } } diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/ParserVisitor.cs b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/ParserVisitor.cs new file mode 100644 index 0000000000..f8df1b37a5 --- /dev/null +++ b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/ParserVisitor.cs @@ -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. + +namespace Microsoft.AspNetCore.Razor.Evolution.Legacy +{ + internal abstract class ParserVisitor + { + public virtual void VisitBlock(Block block) + { + VisitStartBlock(block); + + for (var i = 0; i < block.Children.Count; i++) + { + block.Children[i].Accept(this); + } + + VisitEndBlock(block); + } + + public virtual void VisitStartBlock(Block block) + { + if (block.ChunkGenerator != null) + { + block.ChunkGenerator.AcceptStart(this, block); + } + } + + public virtual void VisitEndBlock(Block block) + { + if (block.ChunkGenerator != null) + { + block.ChunkGenerator.AcceptEnd(this, block); + } + } + + public virtual void VisitSpan(Span span) + { + if (span.ChunkGenerator != null) + { + span.ChunkGenerator.Accept(this, span); + } + } + + public virtual void VisitStartDynamicAttributeBlock(DynamicAttributeBlockChunkGenerator chunk, Block block) + { + } + + public virtual void VisitEndDynamicAttributeBlock(DynamicAttributeBlockChunkGenerator chunk, Block block) + { + } + + public virtual void VisitStartExpressionBlock(ExpressionChunkGenerator chunk, Block block) + { + } + + public virtual void VisitEndExpressionBlock(ExpressionChunkGenerator chunk, Block block) + { + } + + public virtual void VisitStartAttributeBlock(AttributeBlockChunkGenerator chunk, Block block) + { + } + + public virtual void VisitEndAttributeBlock(AttributeBlockChunkGenerator chunk, Block block) + { + } + + public virtual void VisitExpressionSpan(ExpressionChunkGenerator chunk, Span span) + { + } + + public virtual void VisitSetBaseTypeSpan(SetBaseTypeChunkGenerator chunk, Span span) + { + } + + public virtual void VisitTagHelperPrefixSpan(TagHelperPrefixDirectiveChunkGenerator chunk, Span span) + { + } + + public virtual void VisitTypeMemberSpan(TypeMemberChunkGenerator chunk, Span span) + { + } + + public virtual void VisitMarkupSpan(MarkupChunkGenerator chunk, Span span) + { + } + + public virtual void VisitRemoveTagHelperSpan(RemoveTagHelperChunkGenerator chunk, Span span) + { + } + + public virtual void VisitImportSpan(AddImportChunkGenerator chunk, Span span) + { + } + + public virtual void VisitAddTagHelperSpan(AddTagHelperChunkGenerator chunk, Span span) + { + } + + public virtual void VisitStatementSpan(StatementChunkGenerator chunk, Span span) + { + } + + public virtual void VisitLiteralAttributeSpan(LiteralAttributeChunkGenerator chunk, Span span) + { + } + + public virtual void VisitEndTemplateBlock(TemplateBlockChunkGenerator chunk, Block block) + { + } + + public virtual void VisitStartTemplateBlock(TemplateBlockChunkGenerator chunk, Block block) + { + } + + public virtual void VisitStartSectionBlock(SectionChunkGenerator chunk, Block block) + { + } + + public virtual void VisitEndSectionBlock(SectionChunkGenerator chunk, Block block) + { + } + + public virtual void VisitEndCommentBlock(RazorCommentChunkGenerator chunk, Block block) + { + } + + public virtual void VisitStartCommentBlock(RazorCommentChunkGenerator chunk, Block block) + { + } + } +} diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/RazorCommentChunkGenerator.cs b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/RazorCommentChunkGenerator.cs index 1c18216f6d..73d32d1006 100644 --- a/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/RazorCommentChunkGenerator.cs +++ b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/RazorCommentChunkGenerator.cs @@ -5,5 +5,14 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy { internal class RazorCommentChunkGenerator : ParentChunkGenerator { + public override void AcceptStart(ParserVisitor visitor, Block block) + { + visitor.VisitStartCommentBlock(this, block); + } + + public override void AcceptEnd(ParserVisitor visitor, Block block) + { + visitor.VisitEndCommentBlock(this, block); + } } } diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/RemoveTagHelperChunkGenerator.cs b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/RemoveTagHelperChunkGenerator.cs index dcdf48562f..6b6506dcfa 100644 --- a/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/RemoveTagHelperChunkGenerator.cs +++ b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/RemoveTagHelperChunkGenerator.cs @@ -15,6 +15,11 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy public string LookupText { get; } + public override void Accept(ParserVisitor visitor, Span span) + { + visitor.VisitRemoveTagHelperSpan(this, span); + } + public override void GenerateChunk(Span target, ChunkGeneratorContext context) { //context.ChunkTreeBuilder.AddRemoveTagHelperChunk(LookupText, target); diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/SectionChunkGenerator.cs b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/SectionChunkGenerator.cs index 956a1746c0..cf28e8e704 100644 --- a/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/SectionChunkGenerator.cs +++ b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/SectionChunkGenerator.cs @@ -14,6 +14,16 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy public string SectionName { get; } + public override void AcceptStart(ParserVisitor visitor, Block block) + { + visitor.VisitStartSectionBlock(this, block); + } + + public override void AcceptEnd(ParserVisitor visitor, Block block) + { + visitor.VisitEndSectionBlock(this, block); + } + public override void GenerateStartParentChunk(Block target, ChunkGeneratorContext context) { //var chunk = context.ChunkTreeBuilder.StartParentChunk(target); diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/SetBaseTypeChunkGenerator.cs b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/SetBaseTypeChunkGenerator.cs index 428c27b26a..27015a36a8 100644 --- a/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/SetBaseTypeChunkGenerator.cs +++ b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/SetBaseTypeChunkGenerator.cs @@ -14,6 +14,11 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy public string BaseType { get; } + public override void Accept(ParserVisitor visitor, Span span) + { + visitor.VisitSetBaseTypeSpan(this, span); + } + public override void GenerateChunk(Span target, ChunkGeneratorContext context) { } diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/Span.cs b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/Span.cs index 3eb8f3f48d..47e9e8e468 100644 --- a/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/Span.cs +++ b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/Span.cs @@ -131,5 +131,10 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy // Hash code should include only immutable properties but Equals also checks the type. return TypeHashCode; } + + public override void Accept(ParserVisitor visitor) + { + visitor.VisitSpan(this); + } } } diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/SpanChunkGenerator.cs b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/SpanChunkGenerator.cs index df39ec44d0..fee6d94499 100644 --- a/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/SpanChunkGenerator.cs +++ b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/SpanChunkGenerator.cs @@ -1,6 +1,8 @@ // 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; + namespace Microsoft.AspNetCore.Razor.Evolution.Legacy { internal abstract class SpanChunkGenerator : ISpanChunkGenerator @@ -9,6 +11,8 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy public static readonly ISpanChunkGenerator Null = new NullSpanChunkGenerator(); + public abstract void Accept(ParserVisitor visitor, Span span); + public virtual void GenerateChunk(Span target, ChunkGeneratorContext context) { } @@ -26,6 +30,10 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy private class NullSpanChunkGenerator : ISpanChunkGenerator { + public void Accept(ParserVisitor visitor, Span span) + { + } + public void GenerateChunk(Span target, ChunkGeneratorContext context) { } diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/StatementChunkGenerator.cs b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/StatementChunkGenerator.cs index 75308857ff..28786adcc0 100644 --- a/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/StatementChunkGenerator.cs +++ b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/StatementChunkGenerator.cs @@ -5,6 +5,11 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy { internal class StatementChunkGenerator : SpanChunkGenerator { + public override void Accept(ParserVisitor visitor, Span span) + { + visitor.VisitStatementSpan(this, span); + } + public override void GenerateChunk(Span target, ChunkGeneratorContext context) { //context.ChunkTreeBuilder.AddStatementChunk(target.Content, target); diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/SyntaxTreeNode.cs b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/SyntaxTreeNode.cs index bc08383f96..36d2bc79cb 100644 --- a/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/SyntaxTreeNode.cs +++ b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/SyntaxTreeNode.cs @@ -41,5 +41,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy /// comparisons. /// public abstract int GetEquivalenceHash(); + + public abstract void Accept(ParserVisitor visitor); } } diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/TagHelperPrefixDirectiveChunkGenerator.cs b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/TagHelperPrefixDirectiveChunkGenerator.cs index ebca778a3a..401873213f 100644 --- a/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/TagHelperPrefixDirectiveChunkGenerator.cs +++ b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/TagHelperPrefixDirectiveChunkGenerator.cs @@ -15,6 +15,11 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy public string Prefix { get; } + public override void Accept(ParserVisitor visitor, Span span) + { + visitor.VisitTagHelperPrefixSpan(this, span); + } + public override void GenerateChunk(Span target, ChunkGeneratorContext context) { //context.ChunkTreeBuilder.AddTagHelperPrefixDirectiveChunk(Prefix, target); diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/TemplateBlockChunkGenerator.cs b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/TemplateBlockChunkGenerator.cs index dce743d5dc..2358e35629 100644 --- a/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/TemplateBlockChunkGenerator.cs +++ b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/TemplateBlockChunkGenerator.cs @@ -5,6 +5,16 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy { internal class TemplateBlockChunkGenerator : ParentChunkGenerator { + public override void AcceptStart(ParserVisitor visitor, Block block) + { + visitor.VisitStartTemplateBlock(this, block); + } + + public override void AcceptEnd(ParserVisitor visitor, Block block) + { + visitor.VisitEndTemplateBlock(this, block); + } + public override void GenerateStartParentChunk(Block target, ChunkGeneratorContext context) { //context.ChunkTreeBuilder.StartParentChunk(target); diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/TypeMemberChunkGenerator.cs b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/TypeMemberChunkGenerator.cs index fbc02ecc5e..1fcd3599c0 100644 --- a/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/TypeMemberChunkGenerator.cs +++ b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/TypeMemberChunkGenerator.cs @@ -10,6 +10,11 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy //context.ChunkTreeBuilder.AddTypeMemberChunk(target.Content, target); } + public override void Accept(ParserVisitor visitor, Span span) + { + visitor.VisitTypeMemberSpan(this, span); + } + public override string ToString() { return "TypeMember"; diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/RazorCodeDocumentExtensions.cs b/src/Microsoft.AspNetCore.Razor.Evolution/RazorCodeDocumentExtensions.cs index 4302036b3b..286a2c7001 100644 --- a/src/Microsoft.AspNetCore.Razor.Evolution/RazorCodeDocumentExtensions.cs +++ b/src/Microsoft.AspNetCore.Razor.Evolution/RazorCodeDocumentExtensions.cs @@ -2,6 +2,7 @@ // 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 { @@ -26,5 +27,25 @@ namespace Microsoft.AspNetCore.Razor.Evolution document.Items[typeof(RazorSyntaxTree)] = syntaxTree; } + + public static DocumentIRNode GetIRDocument(this RazorCodeDocument document) + { + if (document == null) + { + throw new ArgumentNullException(nameof(document)); + } + + return document.Items[typeof(DocumentIRNode)] as DocumentIRNode; + } + + public static void SetIRDocument(this RazorCodeDocument document, DocumentIRNode irDocument) + { + if (document == null) + { + throw new ArgumentNullException(nameof(document)); + } + + document.Items[typeof(DocumentIRNode)] = irDocument; + } } } diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/RazorEngine.cs b/src/Microsoft.AspNetCore.Razor.Evolution/RazorEngine.cs index 5891743730..c028e901a2 100644 --- a/src/Microsoft.AspNetCore.Razor.Evolution/RazorEngine.cs +++ b/src/Microsoft.AspNetCore.Razor.Evolution/RazorEngine.cs @@ -32,6 +32,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution { builder.Phases.Add(new DefaultRazorParsingPhase()); builder.Phases.Add(new DefaultRazorSyntaxTreePhase()); + builder.Phases.Add(new DefaultRazorIRLoweringPhase()); builder.Features.Add(new TagHelperBinderSyntaxTreePass()); } diff --git a/test/Microsoft.AspNetCore.Razor.Evolution.Test/Intermediate/DefaultRazorIRBuilderTest.cs b/test/Microsoft.AspNetCore.Razor.Evolution.Test/Intermediate/DefaultRazorIRBuilderTest.cs index 834cef8299..97040a106a 100644 --- a/test/Microsoft.AspNetCore.Razor.Evolution.Test/Intermediate/DefaultRazorIRBuilderTest.cs +++ b/test/Microsoft.AspNetCore.Razor.Evolution.Test/Intermediate/DefaultRazorIRBuilderTest.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using Microsoft.AspNetCore.Razor.Evolution.Legacy; using Microsoft.AspNetCore.Testing; using Xunit; @@ -125,12 +126,34 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate Assert.Collection(parent.Children, n => Assert.Same(node, n)); } + [Fact] + public void Build_PopsMultipleLevels() + { + // Arrange + var builder = new DefaultRazorIRBuilder(); + + var document = new DocumentIRNode(); + builder.Push(document); + + var node = new BasicIRNode(); + builder.Push(node); + + // Act + var result = builder.Build(); + + // Assert + Assert.Same(document, result); + Assert.Null(builder.Current); + } + private class BasicIRNode : RazorIRNode { public override IList Children { get; } = new List(); public override RazorIRNode Parent { get; set; } + internal override SourceLocation SourceLocation { get; set; } + public override void Accept(RazorIRNodeVisitor visitor) { throw new NotImplementedException(); diff --git a/test/Microsoft.AspNetCore.Razor.Evolution.Test/Intermediate/DefaultRazorIRLoweringPhaseIntegrationTest.cs b/test/Microsoft.AspNetCore.Razor.Evolution.Test/Intermediate/DefaultRazorIRLoweringPhaseIntegrationTest.cs new file mode 100644 index 0000000000..93540a2e52 --- /dev/null +++ b/test/Microsoft.AspNetCore.Razor.Evolution.Test/Intermediate/DefaultRazorIRLoweringPhaseIntegrationTest.cs @@ -0,0 +1,118 @@ +// 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; + +namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate +{ + public class LoweringIntegrationTest + { + [Fact] + public void Lower_EmptyDocument() + { + var codeDocument = TestRazorCodeDocument.CreateEmpty(); + + var irDocument = Lower(codeDocument); + + var @namespace = SingleChild(irDocument); + var @class = SingleChild(@namespace); + var method = SingleChild(@class); + var html = SingleChild(method); + + Assert.Equal(string.Empty, html.Content); + } + + [Fact] + public void Lower_HelloWorld() + { + var codeDocument = TestRazorCodeDocument.Create("Hello, World!"); + + var irDocument = Lower(codeDocument); + + var @namespace = SingleChild(irDocument); + var @class = SingleChild(@namespace); + var method = SingleChild(@class); + var html = SingleChild(method); + + Assert.Equal("Hello, World!", html.Content); + } + + [Fact] + public void Lower_HtmlWithAttributes() + { + var codeDocument = TestRazorCodeDocument.Create(@" + + + + +"); + var irDocument = Lower(codeDocument); + + var @namespace = SingleChild(irDocument); + var @class = SingleChild(@namespace); + var method = SingleChild(@class); + Children(method, + n => Html(Environment.NewLine, n), + n => Html("", n), + n => Html(Environment.NewLine + " ", n), + n => Html("", n), + n => Html(Environment.NewLine + " ", n), + n => Html(" Html(" data-val=\"", n), + n => CSharpExpression("Hello", n), + n => Html("\"", n), + n => Html(" />", n), + n => Html(Environment.NewLine + " ", n), + n => Html("", n), + n => Html(Environment.NewLine, n), + n => Html("", n)); + } + + [Fact] + public void Lower_WithUsing() + { + var codeDocument = TestRazorCodeDocument.Create(@"@functions { public int Foo { get; set; }}"); + var irDocument = Lower(codeDocument); + + var @namespace = SingleChild(irDocument); + var @class = SingleChild(@namespace); + Children(@class, + n => Assert.IsType(n), + n => Assert.IsType(n)); + } + + [Fact] + public void Lower_WithFunctions() + { + var codeDocument = TestRazorCodeDocument.Create(@"@using System"); + var irDocument = Lower(codeDocument); + + var @namespace = SingleChild(irDocument); + Children(@namespace, + n => Using("using System", n), + n => Assert.IsType(n)); + } + + private DocumentIRNode Lower(RazorCodeDocument codeDocument) + { + var engine = RazorEngine.Create(); + + for (var i = 0; i < engine.Phases.Count; i++) + { + var phase = engine.Phases[i]; + phase.Execute(codeDocument); + + if (phase is IRazorIRLoweringPhase) + { + break; + } + } + + var irDocument = codeDocument.GetIRDocument(); + Assert.NotNull(irDocument); + return irDocument; + } + } +} diff --git a/test/Microsoft.AspNetCore.Razor.Evolution.Test/Intermediate/RazorIRAssert.cs b/test/Microsoft.AspNetCore.Razor.Evolution.Test/Intermediate/RazorIRAssert.cs new file mode 100644 index 0000000000..ef03f94933 --- /dev/null +++ b/test/Microsoft.AspNetCore.Razor.Evolution.Test/Intermediate/RazorIRAssert.cs @@ -0,0 +1,169 @@ +// 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 System.Text; +using Xunit; +using Xunit.Sdk; + +namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate +{ + public static class RazorIRAssert + { + public static TNode SingleChild(RazorIRNode node) + { + if (node.Children.Count == 0) + { + throw new IRAssertException(node, "The node has no children."); + } + else if (node.Children.Count > 1) + { + throw new IRAssertException(node, node.Children, "The node has multiple children"); + } + + var child = node.Children[0]; + return Assert.IsType(child); + } + + public static void NoChildren(RazorIRNode node) + { + if (node.Children.Count > 0) + { + throw new IRAssertException(node, node.Children, "The node has children."); + } + } + + public static void Children(RazorIRNode node, params Action[] validators) + { + var i = 0; + for (; i < validators.Length; i++) + { + if (node.Children.Count == i) + { + throw new IRAssertException(node, node.Children, $"The node only has {node.Children.Count} children."); + } + + try + { + validators[i].Invoke(node.Children[i]); + } + catch (XunitException e) + { + throw new IRAssertException(node, node.Children, $"Failed while validating node {node.Children[i]} at {i}.", e); + } + } + + if (i < node.Children.Count) + { + throw new IRAssertException(node, node.Children, $"The node has extra child {node.Children[i]} at {i}."); + } + } + + public static void Html(string expected, RazorIRNode node) + { + try + { + var html = Assert.IsType(node); + Assert.Equal(expected, html.Content); + } + catch (XunitException e) + { + throw new IRAssertException(node, node.Children, e.Message, e); + } + } + + public static void Using(string expected, RazorIRNode node) + { + try + { + var @using = Assert.IsType(node); + Assert.Equal(expected, @using.Content); + } + catch (XunitException e) + { + throw new IRAssertException(node, node.Children, e.Message, e); + } + } + + public static void CSharpExpression(string expected, RazorIRNode node) + { + try + { + var cSharp = Assert.IsType(node); + + var content = new StringBuilder(); + for (var i = 0; i < cSharp.Children.Count; i++) + { + content.Append(((CSharpTokenIRNode)cSharp.Children[i]).Content); + } + + Assert.Equal(expected, content.ToString()); + } + catch (XunitException e) + { + throw new IRAssertException(node, node.Children, e.Message, e); + } + } + + private class IRAssertException : XunitException + { + public IRAssertException(RazorIRNode node, string userMessage) + : base(Format(node, null, userMessage)) + { + Node = node; + } + + public IRAssertException(RazorIRNode node, IEnumerable nodes, string userMessage) + : base(Format(node, nodes, userMessage)) + { + Node = node; + Nodes = nodes; + } + + public IRAssertException( + RazorIRNode node, + IEnumerable nodes, + string userMessage, + Exception innerException) + : base(Format(node, nodes, userMessage), innerException) + { + } + + public RazorIRNode Node { get; } + + public IEnumerable Nodes { get; } + + private static string Format(RazorIRNode node, IEnumerable nodes, string userMessage) + { + var builder = new StringBuilder(); + builder.AppendLine(userMessage); + builder.AppendLine(); + + if (nodes != null) + { + builder.AppendLine("Nodes:"); + + foreach (var n in nodes) + { + builder.AppendLine(n.ToString()); + } + + builder.AppendLine(); + } + + + builder.AppendLine("Path:"); + + var current = node; + do + { + builder.AppendLine(current.ToString()); + } + while ((current = current.Parent) != null); + + return builder.ToString(); + } + } + } +} diff --git a/test/Microsoft.AspNetCore.Razor.Evolution.Test/Intermediate/RazorIRBuilderTest.cs b/test/Microsoft.AspNetCore.Razor.Evolution.Test/Intermediate/RazorIRBuilderTest.cs index c14aa27a5e..660fc782a0 100644 --- a/test/Microsoft.AspNetCore.Razor.Evolution.Test/Intermediate/RazorIRBuilderTest.cs +++ b/test/Microsoft.AspNetCore.Razor.Evolution.Test/Intermediate/RazorIRBuilderTest.cs @@ -14,7 +14,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate var builder = RazorIRBuilder.Document(); // Assert - Assert.IsType(builder.Current); + Assert.IsType(builder.Current); } } } diff --git a/test/Microsoft.AspNetCore.Razor.Evolution.Test/Intermediate/RazorIRNodeWalkerTest.cs b/test/Microsoft.AspNetCore.Razor.Evolution.Test/Intermediate/RazorIRNodeWalkerTest.cs index dfc2f5c6ed..60b11085cd 100644 --- a/test/Microsoft.AspNetCore.Razor.Evolution.Test/Intermediate/RazorIRNodeWalkerTest.cs +++ b/test/Microsoft.AspNetCore.Razor.Evolution.Test/Intermediate/RazorIRNodeWalkerTest.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using Microsoft.AspNetCore.Razor.Evolution.Legacy; using Xunit; namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate @@ -74,6 +75,8 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate public override RazorIRNode Parent { get; set; } + internal override SourceLocation SourceLocation { get; set; } + public override void Accept(RazorIRNodeVisitor visitor) { ((DerivedIRNodeWalker)visitor).VisitBasic(this); diff --git a/test/Microsoft.AspNetCore.Razor.Evolution.Test/RazorCodeDocumentExtensionsTest.cs b/test/Microsoft.AspNetCore.Razor.Evolution.Test/RazorCodeDocumentExtensionsTest.cs index 16bb6b1c84..b4a6ced9b3 100644 --- a/test/Microsoft.AspNetCore.Razor.Evolution.Test/RazorCodeDocumentExtensionsTest.cs +++ b/test/Microsoft.AspNetCore.Razor.Evolution.Test/RazorCodeDocumentExtensionsTest.cs @@ -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 Microsoft.AspNetCore.Razor.Evolution.Intermediate; using Xunit; namespace Microsoft.AspNetCore.Razor.Evolution @@ -37,5 +38,36 @@ namespace Microsoft.AspNetCore.Razor.Evolution // Assert Assert.Same(expected, codeDocument.Items[typeof(RazorSyntaxTree)]); } + + [Fact] + public void GetIRDocument_ReturnsIRDocument() + { + // Arrange + var codeDocument = TestRazorCodeDocument.CreateEmpty(); + + var expected = RazorIRBuilder.Document().Build(); + codeDocument.Items[typeof(DocumentIRNode)] = expected; + + // Act + var actual = codeDocument.GetIRDocument(); + + // Assert + Assert.Same(expected, actual); + } + + [Fact] + public void SetIRDocument_SetsIRDocument() + { + // Arrange + var codeDocument = TestRazorCodeDocument.CreateEmpty(); + + var expected = RazorIRBuilder.Document().Build(); + + // Act + codeDocument.SetIRDocument((DocumentIRNode)expected); + + // Assert + Assert.Same(expected, codeDocument.Items[typeof(DocumentIRNode)]); + } } } diff --git a/test/Microsoft.AspNetCore.Razor.Evolution.Test/RazorEngineTest.cs b/test/Microsoft.AspNetCore.Razor.Evolution.Test/RazorEngineTest.cs index 82e33ec997..7c6642b967 100644 --- a/test/Microsoft.AspNetCore.Razor.Evolution.Test/RazorEngineTest.cs +++ b/test/Microsoft.AspNetCore.Razor.Evolution.Test/RazorEngineTest.cs @@ -84,7 +84,8 @@ namespace Microsoft.AspNetCore.Razor.Evolution Assert.Collection( phases, phase => Assert.IsType(phase), - phase => Assert.IsType(phase)); + phase => Assert.IsType(phase), + phase => Assert.IsType(phase)); } } } diff --git a/test/Microsoft.AspNetCore.Razor.Evolution.Test/TestRazorCodeDocument.cs b/test/Microsoft.AspNetCore.Razor.Evolution.Test/TestRazorCodeDocument.cs index 7cb0668e3f..42c54cd585 100644 --- a/test/Microsoft.AspNetCore.Razor.Evolution.Test/TestRazorCodeDocument.cs +++ b/test/Microsoft.AspNetCore.Razor.Evolution.Test/TestRazorCodeDocument.cs @@ -11,6 +11,12 @@ namespace Microsoft.AspNetCore.Razor.Evolution return new TestRazorCodeDocument(source); } + public static TestRazorCodeDocument Create(string content) + { + var source = TestRazorSourceDocument.Create(content); + return new TestRazorCodeDocument(source); + } + private TestRazorCodeDocument(RazorSourceDocument source) : base(source) {