Add Razor runtime code generation.

- Added TabSize,IsIndentingWithTabs and NamespaceImports to the RazorParser options. These are replacements for the existing RazorEngineHost abstraction.
- Added RazorParserOptions consumption pattern to more than just the parsing phase.
- Added a ChecksumIRNode to ensure Debugging can work.
- Updated tests to to react to new Checksum and Namespace nodes in the IR tree.
This commit is contained in:
N. Taylor Mullen 2016-12-09 17:22:30 -08:00
parent 2db4985c21
commit 5d4c4e1ccf
17 changed files with 839 additions and 41 deletions

View File

@ -0,0 +1,571 @@
// 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.Diagnostics;
using System.Globalization;
using System.Linq;
using Microsoft.AspNetCore.Razor.Evolution.Intermediate;
using Microsoft.AspNetCore.Razor.Evolution.Legacy;
namespace Microsoft.AspNetCore.Razor.Evolution
{
internal class DefaultRazorCSharpLoweringPhase : RazorEnginePhaseBase, IRazorCSharpLoweringPhase
{
private IRazorConfigureParserFeature[] _parserOptionsCallbacks;
protected override void OnIntialized()
{
_parserOptionsCallbacks = Engine.Features.OfType<IRazorConfigureParserFeature>().ToArray();
}
protected override void ExecuteCore(RazorCodeDocument codeDocument)
{
var irDocument = codeDocument.GetIRDocument();
ThrowForMissingDependency(irDocument);
var syntaxTree = codeDocument.GetSyntaxTree();
ThrowForMissingDependency(syntaxTree);
var renderingContext = new CSharpRenderingContext()
{
Writer = new CSharpCodeWriter(),
SourceDocument = codeDocument.Source,
Options = syntaxTree.Options,
};
var visitor = new CSharpRenderer(renderingContext);
visitor.VisitDefault(irDocument);
var csharpDocument = new RazorCSharpDocument()
{
GeneratedCode = renderingContext.Writer.GenerateCode(),
LineMappings = renderingContext.Writer.LineMappingManager.Mappings,
};
codeDocument.SetCSharpDocument(csharpDocument);
}
public class CSharpRedirectRenderingConventions : CSharpRenderingConventions
{
private readonly string _redirectWriter;
public CSharpRedirectRenderingConventions(string redirectWriter, CSharpCodeWriter writer) : base(writer)
{
_redirectWriter = redirectWriter;
}
public override string StartWriteMethod => "WriteTo(" + _redirectWriter + ", " /* ORIGINAL: WriteToMethodName */;
public override string StartWriteLiteralMethod => "WriteLiteralTo(" + _redirectWriter + ", " /* ORIGINAL: WriteLiteralToMethodName */;
public override string StartBeginWriteAttributeMethod => "BeginWriteAttributeTo(" + _redirectWriter + ", " /* ORIGINAL: BeginWriteAttributeToMethodName */;
public override string StartWriteAttributeValueMethod => "WriteAttributeValueTo(" + _redirectWriter + ", " /* ORIGINAL: WriteAttributeValueToMethodName */;
public override string StartEndWriteAttributeMethod => "EndWriteAttributeTo(" + _redirectWriter /* ORIGINAL: EndWriteAttributeToMethodName */;
}
public class CSharpRenderingConventions
{
public CSharpRenderingConventions(CSharpCodeWriter writer)
{
Writer = writer;
}
protected CSharpCodeWriter Writer { get; }
public virtual string StartWriteMethod => "Write(" /* ORIGINAL: WriteMethodName */;
public virtual string StartWriteLiteralMethod => "WriteLiteral(" /* ORIGINAL: WriteLiteralMethodName */;
public virtual string StartBeginWriteAttributeMethod => "BeginWriteAttribute(" /* ORIGINAL: BeginWriteAttributeMethodName */;
public virtual string StartWriteAttributeValueMethod => "WriteAttributeValue(" /* ORIGINAL: WriteAttributeValueMethodName */;
public virtual string StartEndWriteAttributeMethod => "EndWriteAttribute(" /* ORIGINAL: EndWriteAttributeMethodName */;
}
public class CSharpRenderingContext
{
private CSharpRenderingConventions _renderingConventions;
public ICollection<DirectiveDescriptor> Directives { get; set; }
public CSharpCodeWriter Writer { get; set; }
public CSharpRenderingConventions RenderingConventions
{
get
{
if (_renderingConventions == null)
{
_renderingConventions = new CSharpRenderingConventions(Writer);
}
return _renderingConventions;
}
set
{
_renderingConventions = value;
}
}
public ICollection<RazorError> Errors { get; } = new List<RazorError>();
public RazorSourceDocument SourceDocument { get; set; }
public RazorParserOptions Options { get; set; }
}
public class LinePragmaWriter : IDisposable
{
private readonly CSharpCodeWriter _writer;
private readonly int _startIndent;
public LinePragmaWriter(CSharpCodeWriter writer, MappingLocation documentLocation)
{
if (writer == null)
{
throw new ArgumentNullException(nameof(writer));
}
_writer = writer;
_startIndent = _writer.CurrentIndent;
_writer.ResetIndent();
_writer.WriteLineNumberDirective(documentLocation, documentLocation.FilePath);
}
public void Dispose()
{
// Need to add an additional line at the end IF there wasn't one already written.
// This is needed to work with the C# editor's handling of #line ...
var builder = _writer.Builder;
var endsWithNewline = builder.Length > 0 && builder[builder.Length - 1] == '\n';
// Always write at least 1 empty line to potentially separate code from pragmas.
_writer.WriteLine();
// Check if the previous empty line wasn't enough to separate code from pragmas.
if (!endsWithNewline)
{
_writer.WriteLine();
}
_writer
.WriteLineDefaultDirective()
.WriteLineHiddenDirective()
.SetIndent(_startIndent);
}
}
public class PageStructureCSharpRenderer : RazorIRNodeWalker
{
protected readonly CSharpRenderingContext Context;
public PageStructureCSharpRenderer(CSharpRenderingContext context)
{
Context = context;
}
public override void VisitNamespace(NamespaceDeclarationIRNode node)
{
Context.Writer
.Write("namespace ")
.WriteLine(node.Content);
using (Context.Writer.BuildScope())
{
Context.Writer.WriteLineHiddenDirective();
VisitDefault(node);
}
}
public override void VisitRazorMethodDeclaration(RazorMethodDeclarationIRNode node)
{
Context.Writer
.WriteLine("#pragma warning disable 1998")
.Write(node.AccessModifier)
.Write(" ");
if (node.Modifiers != null)
{
for (var i = 0; i < node.Modifiers.Count; i++)
{
Context.Writer.Write(node.Modifiers[i]);
if (i + 1 < node.Modifiers.Count)
{
Context.Writer.Write(" ");
}
}
}
Context.Writer
.Write(" ")
.Write(node.ReturnType)
.Write(" ")
.Write(node.Name)
.WriteLine("()");
using (Context.Writer.BuildScope())
{
VisitDefault(node);
}
Context.Writer.WriteLine("#pragma warning restore 1998");
}
public override void VisitClass(ClassDeclarationIRNode node)
{
Context.Writer
.Write(node.AccessModifier)
.Write(" class ")
.Write(node.Name);
if (node.BaseType != null || node.Interfaces != null)
{
Context.Writer.Write(" : ");
}
if (node.BaseType != null)
{
Context.Writer.Write(node.BaseType);
if (node.Interfaces != null)
{
Context.Writer.WriteParameterSeparator();
}
}
if (node.Interfaces != null)
{
for (var i = 0; i < node.Interfaces.Count; i++)
{
Context.Writer.Write(node.Interfaces[i]);
if (i + 1 < node.Interfaces.Count)
{
Context.Writer.WriteParameterSeparator();
}
}
}
Context.Writer.WriteLine();
using (Context.Writer.BuildScope())
{
VisitDefault(node);
}
}
}
public class CSharpRenderer : PageStructureCSharpRenderer
{
public CSharpRenderer(CSharpRenderingContext context) : base(context)
{
}
public override void VisitChecksum(ChecksumIRNode node)
{
if (!string.IsNullOrEmpty(node.Bytes))
{
Context.Writer
.Write("#pragma checksum \"")
.Write(node.Filename)
.Write("\" \"")
.Write(node.Guid)
.Write("\" \"")
.Write(node.Bytes)
.WriteLine("\"");
}
}
public override void VisitCSharpToken(CSharpTokenIRNode node)
{
Context.Writer.Write(node.Content);
}
public override void VisitHtml(HtmlContentIRNode node)
{
const int MaxStringLiteralLength = 1024;
var charactersConsumed = 0;
// Render the string in pieces to avoid Roslyn OOM exceptions at compile time: https://github.com/aspnet/External/issues/54
while (charactersConsumed < node.Content.Length)
{
string textToRender;
if (node.Content.Length <= MaxStringLiteralLength)
{
textToRender = node.Content;
}
else
{
var charactersToSubstring = Math.Min(MaxStringLiteralLength, node.Content.Length - charactersConsumed);
textToRender = node.Content.Substring(charactersConsumed, charactersToSubstring);
}
Context.Writer
.Write(Context.RenderingConventions.StartWriteLiteralMethod)
.WriteStringLiteral(textToRender)
.WriteEndMethodInvocation();
charactersConsumed += textToRender.Length;
}
}
public override void VisitCSharpExpression(CSharpExpressionIRNode node)
{
IDisposable linePragmaScope = null;
if (node.SourceRange != null)
{
linePragmaScope = new LinePragmaWriter(Context.Writer, node.SourceRange);
}
var padding = BuildOffsetPadding(Context.RenderingConventions.StartWriteMethod.Length, node.SourceRange);
Context.Writer
.Write(padding)
.Write(Context.RenderingConventions.StartWriteMethod);
VisitDefault(node);
Context.Writer.WriteEndMethodInvocation();
linePragmaScope?.Dispose();
}
public override void VisitUsingStatement(UsingStatementIRNode node)
{
Context.Writer.WriteUsing(node.Content);
}
public override void VisitHtmlAttribute(HtmlAttributeIRNode node)
{
var valuePieceCount = node
.Children
.Count(child => child is HtmlAttributeValueIRNode || child is CSharpAttributeValueIRNode);
var prefixLocation = node.SourceRange.AbsoluteIndex;
var suffixLocation = node.SourceRange.AbsoluteIndex + node.SourceRange.ContentLength - node.Suffix.Length;
Context.Writer
.Write(Context.RenderingConventions.StartBeginWriteAttributeMethod)
.WriteStringLiteral(node.Name)
.WriteParameterSeparator()
.WriteStringLiteral(node.Prefix)
.WriteParameterSeparator()
.Write(prefixLocation.ToString(CultureInfo.InvariantCulture))
.WriteParameterSeparator()
.WriteStringLiteral(node.Suffix)
.WriteParameterSeparator()
.Write(suffixLocation.ToString(CultureInfo.InvariantCulture))
.WriteParameterSeparator()
.Write(valuePieceCount.ToString(CultureInfo.InvariantCulture))
.WriteEndMethodInvocation();
VisitDefault(node);
Context.Writer
.Write(Context.RenderingConventions.StartEndWriteAttributeMethod)
.WriteEndMethodInvocation();
}
public override void VisitHtmlAttributeValue(HtmlAttributeValueIRNode node)
{
var prefixLocation = node.SourceRange.AbsoluteIndex;
var valueLocation = node.SourceRange.AbsoluteIndex + node.Prefix.Length;
var valueLength = node.SourceRange.ContentLength;
Context.Writer
.Write(Context.RenderingConventions.StartWriteAttributeValueMethod)
.WriteStringLiteral(node.Prefix)
.WriteParameterSeparator()
.Write(prefixLocation.ToString(CultureInfo.InvariantCulture))
.WriteParameterSeparator()
.WriteStringLiteral(node.Content)
.WriteParameterSeparator()
.Write(valueLocation.ToString(CultureInfo.InvariantCulture))
.WriteParameterSeparator()
.Write(valueLength.ToString(CultureInfo.InvariantCulture))
.WriteParameterSeparator()
.WriteBooleanLiteral(true)
.WriteEndMethodInvocation();
}
public override void VisitCSharpAttributeValue(CSharpAttributeValueIRNode node)
{
const string ValueWriterName = "__razor_attribute_value_writer";
var expressionValue = node.Children.First() as CSharpExpressionIRNode;
var linePragma = expressionValue != null ? new LinePragmaWriter(Context.Writer, node.SourceRange) : null;
var prefixLocation = node.SourceRange.AbsoluteIndex;
var valueLocation = node.SourceRange.AbsoluteIndex + node.Prefix.Length;
var valueLength = node.SourceRange.ContentLength - node.Prefix.Length;
Context.Writer
.Write(Context.RenderingConventions.StartWriteAttributeValueMethod)
.WriteStringLiteral(node.Prefix)
.WriteParameterSeparator()
.Write(prefixLocation.ToString(CultureInfo.InvariantCulture))
.WriteParameterSeparator();
if (expressionValue != null)
{
Debug.Assert(node.Children.Count == 1);
RenderExpressionInline(expressionValue, Context);
}
else
{
// Not an expression; need to buffer the result.
Context.Writer.WriteStartNewObject("HelperResult" /* ORIGINAL: TemplateTypeName */);
var initialRenderingConventions = Context.RenderingConventions;
var redirectConventions = new CSharpRedirectRenderingConventions(ValueWriterName, Context.Writer);
Context.RenderingConventions = redirectConventions;
using (Context.Writer.BuildAsyncLambda(endLine: false, parameterNames: ValueWriterName))
{
VisitDefault(node);
}
Context.RenderingConventions = initialRenderingConventions;
Context.Writer.WriteEndMethodInvocation(false);
}
Context.Writer
.WriteParameterSeparator()
.Write(valueLocation.ToString(CultureInfo.InvariantCulture))
.WriteParameterSeparator()
.Write(valueLength.ToString(CultureInfo.InvariantCulture))
.WriteParameterSeparator()
.WriteBooleanLiteral(false)
.WriteEndMethodInvocation();
linePragma?.Dispose();
}
public override void VisitDirective(DirectiveIRNode node)
{
if (string.Equals(node.Name, CSharpCodeParser.SectionDirectiveDescriptor.Name, StringComparison.Ordinal))
{
const string SectionWriterName = "__razor_section_writer";
Context.Writer
.WriteStartMethodInvocation("DefineSection" /* ORIGINAL: DefineSectionMethodName */)
.WriteStringLiteral(node.Tokens.FirstOrDefault()?.Content)
.WriteParameterSeparator();
var initialRenderingConventions = Context.RenderingConventions;
var redirectConventions = new CSharpRedirectRenderingConventions(SectionWriterName, Context.Writer);
Context.RenderingConventions = redirectConventions;
using (Context.Writer.BuildAsyncLambda(endLine: false, parameterNames: SectionWriterName))
{
VisitDefault(node);
}
Context.RenderingConventions = initialRenderingConventions;
Context.Writer.WriteEndMethodInvocation();
}
}
public override void VisitCSharpStatement(CSharpStatementIRNode node)
{
if (string.IsNullOrWhiteSpace(node.Content))
{
return;
}
if (node.SourceRange != null)
{
using (new LinePragmaWriter(Context.Writer, node.SourceRange))
{
var padding = BuildOffsetPadding(0, node.SourceRange);
Context.Writer
.Write(padding)
.WriteLine(node.Content);
}
}
else
{
Context.Writer.WriteLine(node.Content);
}
}
public override void VisitTemplate(TemplateIRNode node)
{
const string ItemParameterName = "item";
const string TemplateWriterName = "__razor_template_writer";
Context.Writer
.Write(ItemParameterName).Write(" => ")
.WriteStartNewObject("HelperResult" /* ORIGINAL: TemplateTypeName */);
var initialRenderingConventions = Context.RenderingConventions;
var redirectConventions = new CSharpRedirectRenderingConventions(TemplateWriterName, Context.Writer);
Context.RenderingConventions = redirectConventions;
using (Context.Writer.BuildAsyncLambda(endLine: false, parameterNames: TemplateWriterName))
{
VisitDefault(node);
}
Context.RenderingConventions = initialRenderingConventions;
Context.Writer.WriteEndMethodInvocation(endLine: false);
}
private static int CalculateExpressionPadding(MappingLocation sourceRange, CSharpRenderingContext context)
{
var spaceCount = 0;
for (var i = sourceRange.AbsoluteIndex - 1; i >= 0; i--)
{
var @char = context.SourceDocument[i];
if (@char == '\n' || @char == '\r')
{
break;
}
else if (@char == '\t')
{
spaceCount += context.Options.TabSize;
}
else
{
spaceCount++;
}
}
return spaceCount;
}
private string BuildOffsetPadding(int generatedOffset, MappingLocation sourceRange)
{
var basePadding = CalculateExpressionPadding(sourceRange, Context);
var resolvedPadding = Math.Max(basePadding - generatedOffset, 0);
if (Context.Options.IsIndentingWithTabs)
{
var spaces = resolvedPadding % Context.Options.TabSize;
var tabs = resolvedPadding / Context.Options.TabSize;
return new string('\t', tabs) + new string(' ', spaces);
}
else
{
return new string(' ', resolvedPadding);
}
}
private static void RenderExpressionInline(RazorIRNode node, CSharpRenderingContext context)
{
if (node is CSharpTokenIRNode)
{
context.Writer.Write(((CSharpTokenIRNode)node).Content);
}
else
{
for (var i = 0; i < node.Children.Count; i++)
{
RenderExpressionInline(node.Children[i], context);
}
}
}
}
}
}

View File

@ -1,7 +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.
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Razor.Evolution.Intermediate;
using Microsoft.AspNetCore.Razor.Evolution.Legacy;
@ -9,12 +11,19 @@ namespace Microsoft.AspNetCore.Razor.Evolution
{
internal class DefaultRazorIRLoweringPhase : RazorEnginePhaseBase, IRazorIRLoweringPhase
{
private IRazorConfigureParserFeature[] _parserOptionsCallbacks;
protected override void OnIntialized()
{
_parserOptionsCallbacks = Engine.Features.OfType<IRazorConfigureParserFeature>().ToArray();
}
protected override void ExecuteCore(RazorCodeDocument codeDocument)
{
var syntaxTree = codeDocument.GetSyntaxTree();
ThrowForMissingDependency(syntaxTree);
var visitor = new Visitor();
var visitor = new Visitor(codeDocument, syntaxTree.Options);
visitor.VisitBlock(syntaxTree.Root);
@ -25,16 +34,34 @@ namespace Microsoft.AspNetCore.Razor.Evolution
private class Visitor : ParserVisitor
{
private readonly Stack<RazorIRBuilder> _builders;
private readonly RazorParserOptions _options;
private readonly RazorCodeDocument _codeDocument;
public Visitor()
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);
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);
@ -62,7 +89,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution
Name = chunkGenerator.Name,
Prefix = chunkGenerator.Prefix,
Suffix = chunkGenerator.Suffix,
SourceRange = new MappingLocation(block.Start, block.Length),
SourceRange = BuildSourceRangeFromNode(block),
});
}
@ -80,7 +107,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution
Builder.Push(new CSharpAttributeValueIRNode()
{
Prefix = chunkGenerator.Prefix,
SourceRange = new MappingLocation(block.Start, block.Length),
SourceRange = BuildSourceRangeFromNode(block),
});
}
@ -95,7 +122,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution
{
Prefix = chunkGenerator.Prefix,
Content = chunkGenerator.Value,
SourceRange = new MappingLocation(span.Start, span.Length),
SourceRange = BuildSourceRangeFromNode(span),
});
}
@ -126,20 +153,18 @@ namespace Microsoft.AspNetCore.Razor.Evolution
if (expressionNode.Children.Count > 0)
{
var sourceRangeStart = expressionNode.Children[0].SourceRange;
var contentLength = 0;
for (var i = 0; i < expressionNode.Children.Count; i++)
{
contentLength += expressionNode.Children[i].SourceRange.ContentLength;
}
var sourceRangeStart = expressionNode
.Children
.FirstOrDefault(child => child.SourceRange != null)
?.SourceRange;
var contentLength = expressionNode.Children.Sum(child => child.SourceRange?.ContentLength ?? 0);
expressionNode.SourceRange = new MappingLocation(
sourceRangeStart.AbsoluteIndex,
sourceRangeStart.LineIndex,
sourceRangeStart.CharacterIndex,
contentLength,
sourceRangeStart.FilePath);
sourceRangeStart.FilePath ?? _codeDocument.Source.Filename);
}
}
@ -149,7 +174,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution
Builder.Add(new CSharpTokenIRNode()
{
Content = span.Content,
SourceRange = new MappingLocation(span.Start, span.Length),
SourceRange = BuildSourceRangeFromNode(span),
});
}
@ -158,7 +183,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution
Builder.Add(new CSharpStatementIRNode()
{
Content = span.Content,
SourceRange = new MappingLocation(span.Start, span.Length),
SourceRange = BuildSourceRangeFromNode(span),
});
}
@ -175,13 +200,22 @@ namespace Microsoft.AspNetCore.Razor.Evolution
Builder.Add(new HtmlContentIRNode()
{
Content = span.Content,
SourceRange = new MappingLocation(span.Start, span.Length),
SourceRange = BuildSourceRangeFromNode(span),
});
}
}
public override void VisitImportSpan(AddImportChunkGenerator chunkGenerator, Span span)
{
var namespaceImport = chunkGenerator.Namespace.Trim();
if (_options.NamespaceImports.Contains(namespaceImport, StringComparer.Ordinal))
{
// 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++)
@ -194,9 +228,9 @@ namespace Microsoft.AspNetCore.Razor.Evolution
var @using = new UsingStatementIRNode()
{
Content = span.Content,
Content = namespaceImport,
Parent = Namespace,
SourceRange = new MappingLocation(span.Start, span.Length),
SourceRange = BuildSourceRangeFromNode(span),
};
Namespace.Children.Insert(i, @using);
@ -208,7 +242,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution
{
Content = span.Content,
Descriptor = chunkGenerator.Descriptor,
SourceRange = new MappingLocation(span.Start, span.Length),
SourceRange = BuildSourceRangeFromNode(span),
});
}
@ -225,6 +259,19 @@ namespace Microsoft.AspNetCore.Razor.Evolution
{
Builder.Pop();
}
private MappingLocation BuildSourceRangeFromNode(SyntaxTreeNode node)
{
var location = node.Start;
var sourceRange = new MappingLocation(
location.AbsoluteIndex,
location.LineIndex,
location.CharacterIndex,
node.Length,
location.FilePath ?? _codeDocument.Source.Filename);
return sourceRange;
}
}
}
}

View File

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

View File

@ -0,0 +1,69 @@
// 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.Security.Cryptography;
using System.Text;
using Microsoft.AspNetCore.Razor.Evolution.Legacy;
namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate
{
public class ChecksumIRNode : RazorIRNode
{
public override IList<RazorIRNode> Children => EmptyArray;
public override RazorIRNode Parent { get; set; }
internal override MappingLocation SourceRange { get; set; }
public string Bytes { get; set; }
public string Filename { get; set; }
public string Guid { get; set; }
public override void Accept(RazorIRNodeVisitor visitor)
{
visitor.VisitChecksum(this);
}
public override TResult Accept<TResult>(RazorIRNodeVisitor<TResult> visitor)
{
return visitor.VisitChecksum(this);
}
public static ChecksumIRNode Create(RazorSourceDocument sourceDocument)
{
// See http://msdn.microsoft.com/en-us/library/system.codedom.codechecksumpragma.checksumalgorithmid.aspx
const string Sha1AlgorithmId = "{ff1816ec-aa5e-4d10-87f7-6f4963833460}";
var node = new ChecksumIRNode()
{
Filename = sourceDocument.Filename,
Guid = Sha1AlgorithmId
};
var charBuffer = new char[sourceDocument.Length];
sourceDocument.CopyTo(0, charBuffer, 0, sourceDocument.Length);
var encoder = sourceDocument.Encoding.GetEncoder();
var byteCount = encoder.GetByteCount(charBuffer, 0, charBuffer.Length, flush: true);
var checksumBytes = new byte[byteCount];
encoder.GetBytes(charBuffer, 0, charBuffer.Length, checksumBytes, 0, flush: true);
using (var hashAlgorithm = SHA1.Create())
{
var hashedBytes = hashAlgorithm.ComputeHash(checksumBytes);
var fileHashBuilder = new StringBuilder(hashedBytes.Length * 2);
foreach (var value in hashedBytes)
{
fileHashBuilder.Append(value.ToString("x2"));
}
node.Bytes = fileHashBuilder.ToString();
}
return node;
}
}
}

View File

@ -14,6 +14,11 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate
{
}
public virtual void VisitChecksum(ChecksumIRNode node)
{
VisitDefault(node);
}
public virtual void VisitDirectiveToken(DirectiveTokenIRNode node)
{
VisitDefault(node);

View File

@ -15,6 +15,11 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate
return default(TResult);
}
public virtual TResult VisitChecksum(ChecksumIRNode node)
{
return VisitDefault(node);
}
public virtual TResult VisitDirectiveToken(DirectiveTokenIRNode node)
{
return VisitDefault(node);

View File

@ -198,7 +198,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
/// <param name="location">The location to generate the line pragma for.</param>
/// <param name="file">The file to generate the line pragma for.</param>
/// <returns>The current instance of <see cref="CSharpCodeWriter"/>.</returns>
public CSharpCodeWriter WriteLineNumberDirective(SourceLocation location, string file)
public CSharpCodeWriter WriteLineNumberDirective(MappingLocation location, string file)
{
if (location.FilePath != null)
{

View File

@ -34,6 +34,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution
builder.Phases.Add(new DefaultRazorSyntaxTreePhase());
builder.Phases.Add(new DefaultRazorIRLoweringPhase());
builder.Phases.Add(new DefaultRazorIRPhase());
builder.Phases.Add(new DefaultRazorCSharpLoweringPhase());
// Syntax Tree passes
builder.Features.Add(new DefaultDirectiveSyntaxTreePass());

View File

@ -1,7 +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.
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Microsoft.AspNetCore.Razor.Evolution
{
@ -15,10 +17,17 @@ namespace Microsoft.AspNetCore.Razor.Evolution
private RazorParserOptions()
{
Directives = new List<DirectiveDescriptor>();
NamespaceImports = new HashSet<string>(StringComparer.Ordinal) { nameof(System), typeof(Task).Namespace };
}
public bool DesignTimeMode { get; set; }
public int TabSize { get; set; } = 4;
public bool IsIndentingWithTabs { get; set; }
public ICollection<DirectiveDescriptor> Directives { get; }
public HashSet<string> NamespaceImports { get; }
}
}

View File

@ -0,0 +1,30 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.AspNetCore.Razor.Evolution.Intermediate;
using Microsoft.AspNetCore.Testing;
using Xunit;
namespace Microsoft.AspNetCore.Razor.Evolution
{
public class DefaultRazorCSharpLoweringPhaseTest
{
[Fact]
public void Execute_ThrowsForMissingDependency()
{
// Arrange
var phase = new DefaultRazorCSharpLoweringPhase();
var engine = RazorEngine.CreateEmpty(b => b.Phases.Add(phase));
var codeDocument = TestRazorCodeDocument.CreateEmpty();
// Act & Assert
ExceptionAssert.Throws<InvalidOperationException>(
() => phase.Execute(codeDocument),
$"The '{nameof(DefaultRazorCSharpLoweringPhase)}' phase requires a '{nameof(DocumentIRNode)}' " +
$"provided by the '{nameof(RazorCodeDocument)}'.");
}
}
}

View File

@ -97,8 +97,8 @@ namespace Microsoft.AspNetCore.Razor.Evolution.IntegrationTests
Assert.Equal("test_directive", directiveSpan.Content);
var irDocument = document.GetIRDocument();
var irNamespace = irDocument.Children[0];
var irClass = irNamespace.Children[0];
var irNamespace = irDocument.Children[1];
var irClass = irNamespace.Children[2];
var irMethod = irClass.Children[0];
var irDirective = (DirectiveIRNode)irMethod.Children[1];
Assert.Equal("test_directive", irDirective.Name);

View File

@ -4,6 +4,7 @@
using static Microsoft.AspNetCore.Razor.Evolution.Intermediate.RazorIRAssert;
using Xunit;
using System;
using System.Threading.Tasks;
namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate
{
@ -19,8 +20,15 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate
var irDocument = Lower(codeDocument);
// Assert
var @namespace = SingleChild<NamespaceDeclarationIRNode>(irDocument);
var @class = SingleChild<ClassDeclarationIRNode>(@namespace);
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);
@ -37,8 +45,15 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate
var irDocument = Lower(codeDocument);
// Assert
var @namespace = SingleChild<NamespaceDeclarationIRNode>(irDocument);
var @class = SingleChild<ClassDeclarationIRNode>(@namespace);
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);
@ -60,8 +75,15 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate
var irDocument = Lower(codeDocument);
// Assert
var @namespace = SingleChild<NamespaceDeclarationIRNode>(irDocument);
var @class = SingleChild<ClassDeclarationIRNode>(@namespace);
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(
@ -90,8 +112,15 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate
var irDocument = Lower(codeDocument);
// Assert
var @namespace = SingleChild<NamespaceDeclarationIRNode>(irDocument);
var @class = SingleChild<ClassDeclarationIRNode>(@namespace);
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(
@ -125,8 +154,15 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate
var irDocument = Lower(codeDocument);
// Assert
var @namespace = SingleChild<NamespaceDeclarationIRNode>(irDocument);
var @class = SingleChild<ClassDeclarationIRNode>(@namespace);
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));
@ -142,9 +178,13 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate
var irDocument = Lower(codeDocument);
// Assert
var @namespace = SingleChild<NamespaceDeclarationIRNode>(irDocument);
Children(irDocument,
n => Assert.IsType<ChecksumIRNode>(n),
n => Assert.IsType<NamespaceDeclarationIRNode>(n));
var @namespace = irDocument.Children[1];
Children(@namespace,
n => Using("using System", n),
n => Using("System", n),
n => Using(typeof(Task).Namespace, n),
n => Assert.IsType<ClassDeclarationIRNode>(n));
}

View File

@ -15,9 +15,10 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
var writer = new CSharpCodeWriter();
var expected = $"#line 5 \"{filePath}\"" + writer.NewLine;
var sourceLocation = new SourceLocation(10, 4, 3);
var mappingLocation = new MappingLocation(sourceLocation, 9);
// Act
writer.WriteLineNumberDirective(sourceLocation, filePath);
writer.WriteLineNumberDirective(mappingLocation, filePath);
var code = writer.GenerateCode();
// Assert
@ -35,9 +36,10 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
var writer = new CSharpCodeWriter();
var expected = $"#line 5 \"{sourceLocationFilePath}\"" + writer.NewLine;
var sourceLocation = new SourceLocation(sourceLocationFilePath, 10, 4, 3);
var mappingLocation = new MappingLocation(sourceLocation, 9);
// Act
writer.WriteLineNumberDirective(sourceLocation, filePath);
writer.WriteLineNumberDirective(mappingLocation, filePath);
var code = writer.GenerateCode();
// Assert

View File

@ -88,7 +88,8 @@ namespace Microsoft.AspNetCore.Razor.Evolution
phase => Assert.IsType<DefaultRazorParsingPhase>(phase),
phase => Assert.IsType<DefaultRazorSyntaxTreePhase>(phase),
phase => Assert.IsType<DefaultRazorIRLoweringPhase>(phase),
phase => Assert.IsType<DefaultRazorIRPhase>(phase));
phase => Assert.IsType<DefaultRazorIRPhase>(phase),
phase => Assert.IsType<DefaultRazorCSharpLoweringPhase>(phase));
}
}
}

View File

@ -1,7 +1,10 @@
Document -
Checksum -
NamespaceDeclaration - -
UsingStatement - - System
UsingStatement - - System.Threading.Tasks
ClassDeclaration - - - - -
RazorMethodDeclaration - - - - -
HtmlContent - (0:0,0 [0] ) -
HtmlContent - (0:0,0 [0] TestFiles/IntegrationTests/BasicIntegrationTest/CustomDirective.cshtml) -
Directive - - test_directive
HtmlContent - (15:0,15 [0] ) -
HtmlContent - (15:0,15 [0] TestFiles/IntegrationTests/BasicIntegrationTest/CustomDirective.cshtml) -

View File

@ -1,5 +1,8 @@
Document -
Checksum -
NamespaceDeclaration - -
UsingStatement - - System
UsingStatement - - System.Threading.Tasks
ClassDeclaration - - - - -
RazorMethodDeclaration - - - - -
HtmlContent - (0:0,0 [0] ) -
HtmlContent - (0:0,0 [0] TestFiles/IntegrationTests/BasicIntegrationTest/Empty.cshtml) -

View File

@ -1,5 +1,8 @@
Document -
Checksum -
NamespaceDeclaration - -
UsingStatement - - System
UsingStatement - - System.Threading.Tasks
ClassDeclaration - - - - -
RazorMethodDeclaration - - - - -
HtmlContent - (0:0,0 [13] ) - Hello, World!
HtmlContent - (0:0,0 [13] TestFiles/IntegrationTests/BasicIntegrationTest/HelloWorld.cshtml) - Hello, World!