aspnetcore/src/Microsoft.AspNetCore.Razor..../DefaultRazorIRLoweringPhase.cs

252 lines
8.9 KiB
C#

// 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 System.Linq;
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
{
private readonly Stack<RazorIRBuilder> _builders;
public Visitor()
{
_builders = new Stack<RazorIRBuilder>();
var document = RazorIRBuilder.Document();
_builders.Push(document);
Namespace = new NamespaceDeclarationIRNode();
Builder.Push(Namespace);
Class = new ClassDeclarationIRNode();
Builder.Push(Class);
Method = new RazorMethodDeclarationIRNode();
Builder.Push(Method);
}
public RazorIRBuilder Builder => _builders.Peek();
public NamespaceDeclarationIRNode Namespace { get; }
public ClassDeclarationIRNode Class { get; }
public RazorMethodDeclarationIRNode Method { get; }
public override void VisitStartAttributeBlock(AttributeBlockChunkGenerator chunkGenerator, Block block)
{
var value = new ContainerRazorIRNode();
Builder.Add(new HtmlAttributeIRNode()
{
Name = chunkGenerator.Name,
Prefix = chunkGenerator.Prefix,
Value = value,
Suffix = chunkGenerator.Suffix,
SourceLocation = block.Start,
});
var valueBuilder = RazorIRBuilder.Create(value);
_builders.Push(valueBuilder);
}
public override void VisitEndAttributeBlock(AttributeBlockChunkGenerator chunkGenerator, Block block)
{
_builders.Pop();
}
public override void VisitStartDynamicAttributeBlock(DynamicAttributeBlockChunkGenerator chunkGenerator, Block block)
{
var content = new ContainerRazorIRNode();
Builder.Add(new CSharpAttributeValueIRNode()
{
Prefix = chunkGenerator.Prefix,
Content = content,
SourceLocation = block.Start,
});
var valueBuilder = RazorIRBuilder.Create(content);
_builders.Push(valueBuilder);
}
public override void VisitEndDynamicAttributeBlock(DynamicAttributeBlockChunkGenerator chunkGenerator, Block block)
{
_builders.Pop();
}
public override void VisitLiteralAttributeSpan(LiteralAttributeChunkGenerator chunkGenerator, Span span)
{
Builder.Add(new HtmlAttributeValueIRNode()
{
Prefix = chunkGenerator.Prefix,
Content = chunkGenerator.Value,
SourceLocation = span.Start,
});
}
public override void VisitStartTemplateBlock(TemplateBlockChunkGenerator chunkGenerator, Block block)
{
Builder.Push(new TemplateIRNode());
}
public override void VisitEndTemplateBlock(TemplateBlockChunkGenerator chunkGenerator, 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 chunkGenerator, Block block)
{
var value = new ContainerRazorIRNode();
Builder.Add(new CSharpExpressionIRNode()
{
Content = value,
SourceLocation = block.Start,
});
var valueBuilder = RazorIRBuilder.Create(value);
_builders.Push(valueBuilder);
}
public override void VisitEndExpressionBlock(ExpressionChunkGenerator chunkGenerator, Block block)
{
_builders.Pop();
}
public override void VisitExpressionSpan(ExpressionChunkGenerator chunkGenerator, Span span)
{
Builder.Add(new CSharpTokenIRNode()
{
Content = span.Content,
SourceLocation = span.Start,
});
}
public override void VisitTypeMemberSpan(TypeMemberChunkGenerator chunkGenerator, Span span)
{
var functionsNode = new CSharpStatementIRNode()
{
Content = span.Content,
SourceLocation = span.Start,
Parent = Class,
};
Class.Children.Add(functionsNode);
}
public override void VisitStatementSpan(StatementChunkGenerator chunkGenerator, Span span)
{
Builder.Add(new CSharpStatementIRNode()
{
Content = span.Content,
SourceLocation = span.Start,
});
}
public override void VisitMarkupSpan(MarkupChunkGenerator chunkGenerator, Span span)
{
var currentChildren = Builder.Current.Children;
if (currentChildren.Count > 0 && currentChildren[currentChildren.Count - 1] is HtmlContentIRNode)
{
var existingHtmlContent = (HtmlContentIRNode)currentChildren[currentChildren.Count - 1];
existingHtmlContent.Content = string.Concat(existingHtmlContent.Content, span.Content);
}
else
{
Builder.Add(new HtmlContentIRNode()
{
Content = span.Content,
SourceLocation = span.Start,
});
}
}
public override void VisitImportSpan(AddImportChunkGenerator chunkGenerator, 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);
}
private class ContainerRazorIRNode : RazorIRNode
{
private SourceLocation? _location;
public override IList<RazorIRNode> Children { get; } = new List<RazorIRNode>();
public override RazorIRNode Parent { get; set; }
internal override SourceLocation SourceLocation
{
get
{
if (_location == null)
{
if (Children.Count > 0)
{
return Children[0].SourceLocation;
}
return SourceLocation.Undefined;
}
return _location.Value;
}
set
{
_location = value;
}
}
public override void Accept(RazorIRNodeVisitor visitor)
{
visitor.VisitDefault(this);
}
public override TResult Accept<TResult>(RazorIRNodeVisitor<TResult> visitor)
{
return visitor.VisitDefault(this);
}
}
}
}
}