diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/CSharpLiteralCodeConventions.cs b/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/CSharpLiteralCodeConventions.cs new file mode 100644 index 0000000000..e8e6364ba2 --- /dev/null +++ b/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/CSharpLiteralCodeConventions.cs @@ -0,0 +1,16 @@ +// 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.Legacy; + +namespace Microsoft.AspNetCore.Razor.Evolution.CodeGeneration +{ + internal class CSharpLiteralCodeConventions : CSharpRenderingConventions + { + public CSharpLiteralCodeConventions(CSharpCodeWriter writer) : base(writer) + { + } + + public override string StartWriteMethod => StartWriteLiteralMethod; + } +} diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/CSharpRedirectRenderingConventions.cs b/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/CSharpRedirectRenderingConventions.cs new file mode 100644 index 0000000000..85c1d8b998 --- /dev/null +++ b/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/CSharpRedirectRenderingConventions.cs @@ -0,0 +1,28 @@ +// 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.Legacy; + +namespace Microsoft.AspNetCore.Razor.Evolution.CodeGeneration +{ + internal 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 */; + } +} diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/CSharpRenderingContext.cs b/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/CSharpRenderingContext.cs new file mode 100644 index 0000000000..7cdff0e5bd --- /dev/null +++ b/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/CSharpRenderingContext.cs @@ -0,0 +1,47 @@ +// 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.CodeGeneration +{ + internal class CSharpRenderingContext + { + private CSharpRenderingConventions _renderingConventions; + + public ICollection Directives { get; set; } + + public Func IdGenerator { get; set; } = () => Guid.NewGuid().ToString("N"); + + public List LineMappings { get; } = new List(); + + public CSharpCodeWriter Writer { get; set; } + + public CSharpRenderingConventions RenderingConventions + { + get + { + if (_renderingConventions == null) + { + _renderingConventions = new CSharpRenderingConventions(Writer); + } + + return _renderingConventions; + } + set + { + _renderingConventions = value; + } + } + + public ErrorSink ErrorSink { get; } = new ErrorSink(); + + public RazorSourceDocument SourceDocument { get; set; } + + public RazorParserOptions Options { get; set; } + + public TagHelperRenderingContext TagHelperRenderingContext { get; set; } + } +} diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/CSharpRenderingConventions.cs b/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/CSharpRenderingConventions.cs new file mode 100644 index 0000000000..44ebf409a7 --- /dev/null +++ b/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/CSharpRenderingConventions.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 Microsoft.AspNetCore.Razor.Evolution.Legacy; + +namespace Microsoft.AspNetCore.Razor.Evolution.CodeGeneration +{ + internal 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 */; + } +} diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/DefaultRuntimeTarget.cs b/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/DefaultRuntimeTarget.cs new file mode 100644 index 0000000000..48726f3df2 --- /dev/null +++ b/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/DefaultRuntimeTarget.cs @@ -0,0 +1,39 @@ +// 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.CodeGeneration +{ + internal class DefaultRuntimeTarget : RuntimeTarget + { + private readonly RazorParserOptions _options; + + public DefaultRuntimeTarget(RazorParserOptions options) + { + _options = options; + } + + internal override PageStructureCSharpRenderer CreateRenderer(CSharpRenderingContext context) + { + if (_options.DesignTimeMode) + { + return new DesignTimeCSharpRenderer(context); + } + else + { + return new RuntimeCSharpRenderer(context); + } + } + + public override TExtension GetExtension() + { + throw new NotImplementedException(); + } + + public override bool HasExtension() + { + throw new NotImplementedException(); + } + } +} diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/DefaultRuntimeTargetBuilder.cs b/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/DefaultRuntimeTargetBuilder.cs new file mode 100644 index 0000000000..01e92da500 --- /dev/null +++ b/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/DefaultRuntimeTargetBuilder.cs @@ -0,0 +1,25 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.AspNetCore.Razor.Evolution.CodeGeneration +{ + internal class DefaultRuntimeTargetBuilder : IRuntimeTargetBuilder + { + public DefaultRuntimeTargetBuilder(RazorCodeDocument codeDocument, RazorParserOptions options) + { + CodeDocument = codeDocument; + Options = options; + } + + public RazorCodeDocument CodeDocument { get; } + + public RazorParserOptions Options { get; } + + public RuntimeTarget Build() + { + return new DefaultRuntimeTarget(Options); + } + } +} diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/DesignTimeCSharpRenderer.cs b/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/DesignTimeCSharpRenderer.cs new file mode 100644 index 0000000000..eba2208017 --- /dev/null +++ b/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/DesignTimeCSharpRenderer.cs @@ -0,0 +1,355 @@ +// 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.Linq; +using Microsoft.AspNetCore.Razor.Evolution.Intermediate; + +namespace Microsoft.AspNetCore.Razor.Evolution.CodeGeneration +{ + internal class DesignTimeCSharpRenderer : PageStructureCSharpRenderer + { + public DesignTimeCSharpRenderer(CSharpRenderingContext context) : base(context) + { + } + + public override void VisitCSharpToken(CSharpTokenIRNode node) + { + Context.Writer.Write(node.Content); + } + + public override void VisitCSharpExpression(CSharpExpressionIRNode node) + { + if (node.Children.Count == 0) + { + return; + } + + if (node.Source != null) + { + using (new LinePragmaWriter(Context.Writer, node.Source.Value)) + { + var padding = BuildOffsetPadding(RazorDesignTimeIRPass.DesignTimeVariable.Length, node.Source.Value, Context); + + Context.Writer + .Write(padding) + .WriteStartAssignment(RazorDesignTimeIRPass.DesignTimeVariable); + + for (var i = 0; i < node.Children.Count; i++) + { + var childNode = node.Children[i]; + + if (childNode is CSharpTokenIRNode) + { + AddLineMappingFor(childNode); + } + + childNode.Accept(this); + } + + Context.Writer.WriteLine(";"); + } + } + else + { + Context.Writer.WriteStartAssignment(RazorDesignTimeIRPass.DesignTimeVariable); + VisitDefault(node); + Context.Writer.WriteLine(";"); + } + } + + public override void VisitUsingStatement(UsingStatementIRNode node) + { + Context.Writer.WriteUsing(node.Content); + } + + public override void VisitCSharpStatement(CSharpStatementIRNode node) + { + if (node.Source != null) + { + using (new LinePragmaWriter(Context.Writer, node.Source.Value)) + { + var padding = BuildOffsetPadding(0, node.Source.Value, Context); + Context.Writer.Write(padding); + + AddLineMappingFor(node); + Context.Writer.Write(node.Content); + } + } + else + { + Context.Writer.WriteLine(node.Content); + } + } + + public override void VisitDirectiveToken(DirectiveTokenIRNode node) + { + const string TypeHelper = "__typeHelper"; + + var tokenKind = node.Descriptor.Kind; + if (node.Source == null) + { + return; + } + + // Wrap the directive token in a lambda to isolate variable names. + Context.Writer + .Write("((") + .Write(typeof(Action).FullName) + .Write(")("); + using (Context.Writer.BuildLambda(endLine: false)) + { + var originalIndent = Context.Writer.CurrentIndent; + Context.Writer.ResetIndent(); + switch (tokenKind) + { + case DirectiveTokenKind.Type: + + AddLineMappingFor(node); + Context.Writer + .Write(node.Content) + .Write(" ") + .WriteStartAssignment(TypeHelper) + .WriteLine("null;"); + break; + case DirectiveTokenKind.Member: + Context.Writer + .Write(typeof(object).FullName) + .Write(" "); + + AddLineMappingFor(node); + Context.Writer + .Write(node.Content) + .WriteLine(" = null;"); + break; + case DirectiveTokenKind.String: + Context.Writer + .Write(typeof(object).FullName) + .Write(" ") + .WriteStartAssignment(TypeHelper); + + if (node.Content.StartsWith("\"", StringComparison.Ordinal)) + { + AddLineMappingFor(node); + Context.Writer.Write(node.Content); + } + else + { + Context.Writer.Write("\""); + AddLineMappingFor(node); + Context.Writer + .Write(node.Content) + .Write("\""); + } + + Context.Writer.WriteLine(";"); + break; + } + Context.Writer.SetIndent(originalIndent); + } + Context.Writer.WriteLine("))();"); + + } + + public override void VisitTemplate(TemplateIRNode node) + { + const string ItemParameterName = "item"; + const string TemplateWriterName = "__razor_template_writer"; + + Context.Writer + .Write(ItemParameterName).Write(" => ") + .WriteStartNewObject("Microsoft.AspNetCore.Mvc.Razor.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); + } + + public override void VisitTagHelper(TagHelperIRNode node) + { + var initialTagHelperRenderingContext = Context.TagHelperRenderingContext; + Context.TagHelperRenderingContext = new TagHelperRenderingContext(); + VisitDefault(node); + Context.TagHelperRenderingContext = initialTagHelperRenderingContext; + } + + public override void VisitInitializeTagHelperStructure(InitializeTagHelperStructureIRNode node) + { + VisitDefault(node); + } + + public override void VisitCreateTagHelper(CreateTagHelperIRNode node) + { + var tagHelperVariableName = GetTagHelperVariableName(node.TagHelperTypeName); + + Context.Writer + .WriteStartAssignment(tagHelperVariableName) + .WriteStartMethodInvocation( + "CreateTagHelper" /* ORIGINAL: CreateTagHelperMethodName */, + "global::" + node.TagHelperTypeName) + .WriteEndMethodInvocation(); + } + + public override void VisitSetTagHelperProperty(SetTagHelperPropertyIRNode node) + { + var tagHelperVariableName = GetTagHelperVariableName(node.TagHelperTypeName); + var tagHelperRenderingContext = Context.TagHelperRenderingContext; + var propertyValueAccessor = GetTagHelperPropertyAccessor(tagHelperVariableName, node.AttributeName, node.Descriptor); + + string previousValueAccessor; + if (tagHelperRenderingContext.RenderedBoundAttributes.TryGetValue(node.AttributeName, out previousValueAccessor)) + { + Context.Writer + .WriteStartAssignment(propertyValueAccessor) + .Write(previousValueAccessor) + .WriteLine(";"); + + return; + } + else + { + tagHelperRenderingContext.RenderedBoundAttributes[node.AttributeName] = propertyValueAccessor; + } + + if (node.Descriptor.IsStringProperty) + { + VisitDefault(node); + + Context.Writer.WriteStartAssignment(propertyValueAccessor); + if (node.Children.Count == 1 && node.Children.First() is HtmlContentIRNode) + { + var htmlNode = node.Children.First() as HtmlContentIRNode; + if (htmlNode != null) + { + Context.Writer.WriteStringLiteral(htmlNode.Content); + } + } + else + { + Context.Writer.Write("string.Empty"); + } + Context.Writer.WriteLine(";"); + } + else + { + var firstMappedChild = node.Children.FirstOrDefault(child => child.Source != null) as RazorIRNode; + var valueStart = firstMappedChild?.Source; + + using (new LinePragmaWriter(Context.Writer, node.Source.Value)) + { + var assignmentPrefixLength = propertyValueAccessor.Length + " = ".Length; + if (node.Descriptor.IsEnum && + node.Children.Count == 1 && + node.Children.First() is HtmlContentIRNode) + { + assignmentPrefixLength += $"global::{node.Descriptor.TypeName}.".Length; + + if (valueStart != null) + { + var padding = BuildOffsetPadding(assignmentPrefixLength, node.Source.Value, Context); + + Context.Writer.Write(padding); + } + + Context.Writer + .WriteStartAssignment(propertyValueAccessor) + .Write("global::") + .Write(node.Descriptor.TypeName) + .Write("."); + } + else + { + if (valueStart != null) + { + var padding = BuildOffsetPadding(assignmentPrefixLength, node.Source.Value, Context); + + Context.Writer.Write(padding); + } + + Context.Writer.WriteStartAssignment(propertyValueAccessor); + } + + RenderTagHelperAttributeInline(node, node.Source.Value); + + Context.Writer.WriteLine(";"); + } + } + } + + public override void VisitDeclareTagHelperFields(DeclareTagHelperFieldsIRNode node) + { + foreach (var tagHelperTypeName in node.UsedTagHelperTypeNames) + { + var tagHelperVariableName = GetTagHelperVariableName(tagHelperTypeName); + Context.Writer + .Write("private global::") + .WriteVariableDeclaration( + tagHelperTypeName, + tagHelperVariableName, + value: null); + } + } + + private void AddLineMappingFor(RazorIRNode node) + { + var sourceLocation = node.Source.Value; + var generatedLocation = new SourceSpan(Context.Writer.GetCurrentSourceLocation(), sourceLocation.Length); + var lineMapping = new LineMapping(sourceLocation, generatedLocation); + + Context.LineMappings.Add(lineMapping); + } + + private void RenderTagHelperAttributeInline( + RazorIRNode node, + SourceSpan documentLocation) + { + if (node is SetTagHelperPropertyIRNode || node is CSharpExpressionIRNode) + { + for (var i = 0; i < node.Children.Count; i++) + { + RenderTagHelperAttributeInline(node.Children[i], documentLocation); + } + } + else if (node is HtmlContentIRNode) + { + if (node.Source != null) + { + AddLineMappingFor(node); + } + + Context.Writer.Write(((HtmlContentIRNode)node).Content); + } + else if (node is CSharpTokenIRNode) + { + if (node.Source != null) + { + AddLineMappingFor(node); + } + + Context.Writer.Write(((CSharpTokenIRNode)node).Content); + } + else if (node is CSharpStatementIRNode) + { + Context.ErrorSink.OnError( + new SourceLocation(documentLocation.AbsoluteIndex, documentLocation.CharacterIndex, documentLocation.Length), + LegacyResources.TagHelpers_CodeBlocks_NotSupported_InAttributes, + documentLocation.Length); + } + else if (node is TemplateIRNode) + { + var attributeValueNode = (SetTagHelperPropertyIRNode)node.Parent; + Context.ErrorSink.OnError( + new SourceLocation(documentLocation.AbsoluteIndex, documentLocation.CharacterIndex, documentLocation.Length), + LegacyResources.FormatTagHelpers_InlineMarkupBlocks_NotSupported_InAttributes(attributeValueNode.Descriptor.TypeName), + documentLocation.Length); + } + } + } +} diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/IRuntimeTargetBuilder.cs b/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/IRuntimeTargetBuilder.cs new file mode 100644 index 0000000000..65cc959e15 --- /dev/null +++ b/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/IRuntimeTargetBuilder.cs @@ -0,0 +1,14 @@ +// 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.CodeGeneration +{ + public interface IRuntimeTargetBuilder + { + RazorCodeDocument CodeDocument { get; } + + RazorParserOptions Options { get; } + + RuntimeTarget Build(); + } +} diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/IRuntimeTargetExtension.cs b/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/IRuntimeTargetExtension.cs new file mode 100644 index 0000000000..7377010013 --- /dev/null +++ b/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/IRuntimeTargetExtension.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.CodeGeneration +{ + public interface IRuntimeTargetExtension + { + } +} diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/LinePragmaWriter.cs b/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/LinePragmaWriter.cs new file mode 100644 index 0000000000..4302b80674 --- /dev/null +++ b/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/LinePragmaWriter.cs @@ -0,0 +1,49 @@ +// 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.Legacy; + +namespace Microsoft.AspNetCore.Razor.Evolution.CodeGeneration +{ + internal class LinePragmaWriter : IDisposable + { + private readonly CSharpCodeWriter _writer; + private readonly int _startIndent; + + public LinePragmaWriter(CSharpCodeWriter writer, SourceSpan 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); + } + } +} diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/PageStructureCSharpRenderer.cs b/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/PageStructureCSharpRenderer.cs new file mode 100644 index 0000000000..ade3a7a95b --- /dev/null +++ b/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/PageStructureCSharpRenderer.cs @@ -0,0 +1,184 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNetCore.Razor.Evolution.Intermediate; + +namespace Microsoft.AspNetCore.Razor.Evolution.CodeGeneration +{ + internal 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"); + + Context.Writer + .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); + } + } + + protected 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); + } + } + } + + protected static int CalculateExpressionPadding(SourceSpan 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; + } + + protected static string BuildOffsetPadding(int generatedOffset, SourceSpan sourceRange, CSharpRenderingContext context) + { + 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); + } + } + + protected static string GetTagHelperVariableName(string tagHelperTypeName) => "__" + tagHelperTypeName.Replace('.', '_'); + + protected static string GetTagHelperPropertyAccessor( + string tagHelperVariableName, + string attributeName, + TagHelperAttributeDescriptor descriptor) + { + var propertyAccessor = $"{tagHelperVariableName}.{descriptor.PropertyName}"; + + if (descriptor.IsIndexer) + { + var dictionaryKey = attributeName.Substring(descriptor.Name.Length); + propertyAccessor += $"[\"{dictionaryKey}\"]"; + } + + return propertyAccessor; + } + } +} diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/RuntimeCSharpRenderer.cs b/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/RuntimeCSharpRenderer.cs new file mode 100644 index 0000000000..bf01f6a366 --- /dev/null +++ b/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/RuntimeCSharpRenderer.cs @@ -0,0 +1,695 @@ +// 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; +using System.Linq; +using System.Diagnostics; +using Microsoft.AspNetCore.Razor.Evolution.Intermediate; +using Microsoft.AspNetCore.Razor.Evolution.Legacy; + +namespace Microsoft.AspNetCore.Razor.Evolution.CodeGeneration +{ + internal class RuntimeCSharpRenderer : PageStructureCSharpRenderer + { + public RuntimeCSharpRenderer(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.Source != null) + { + linePragmaScope = new LinePragmaWriter(Context.Writer, node.Source.Value); + var padding = BuildOffsetPadding(Context.RenderingConventions.StartWriteMethod.Length, node.Source.Value, Context); + Context.Writer.Write(padding); + } + + Context.Writer.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.Source.Value.AbsoluteIndex; + var suffixLocation = node.Source.Value.AbsoluteIndex + node.Source.Value.Length - 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.Source.Value.AbsoluteIndex; + var valueLocation = node.Source.Value.AbsoluteIndex + node.Prefix.Length; + var valueLength = node.Source.Value.Length; + 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.FirstOrDefault() as CSharpExpressionIRNode; + var linePragma = expressionValue != null ? new LinePragmaWriter(Context.Writer, node.Source.Value) : null; + var prefixLocation = node.Source.Value.AbsoluteIndex; + var valueLocation = node.Source.Value.AbsoluteIndex + node.Prefix.Length; + var valueLength = node.Source.Value.Length - 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("Microsoft.AspNetCore.Mvc.Razor.HelperResult" /* ORIGINAL: TemplateTypeName */); + + var initialRenderingConventions = Context.RenderingConventions; + Context.RenderingConventions = new CSharpRedirectRenderingConventions(ValueWriterName, Context.Writer); + 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 VisitCSharpStatement(CSharpStatementIRNode node) + { + if (string.IsNullOrWhiteSpace(node.Content)) + { + return; + } + + if (node.Source != null) + { + using (new LinePragmaWriter(Context.Writer, node.Source.Value)) + { + var padding = BuildOffsetPadding(0, node.Source.Value, Context); + 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("Microsoft.AspNetCore.Mvc.Razor.HelperResult" /* ORIGINAL: TemplateTypeName */); + + var initialRenderingConventions = Context.RenderingConventions; + Context.RenderingConventions = new CSharpRedirectRenderingConventions(TemplateWriterName, Context.Writer); + using (Context.Writer.BuildAsyncLambda(endLine: false, parameterNames: TemplateWriterName)) + { + VisitDefault(node); + } + Context.RenderingConventions = initialRenderingConventions; + + Context.Writer.WriteEndMethodInvocation(endLine: false); + } + + public override void VisitTagHelper(TagHelperIRNode node) + { + var initialTagHelperRenderingContext = Context.TagHelperRenderingContext; + Context.TagHelperRenderingContext = new TagHelperRenderingContext(); + VisitDefault(node); + Context.TagHelperRenderingContext = initialTagHelperRenderingContext; + } + + public override void VisitInitializeTagHelperStructure(InitializeTagHelperStructureIRNode node) + { + // Call into the tag helper scope manager to start a new tag helper scope. + // Also capture the value as the current execution context. + Context.Writer + .WriteStartAssignment("__tagHelperExecutionContext" /* ORIGINAL: ExecutionContextVariableName */) + .WriteStartInstanceMethodInvocation( + "__tagHelperScopeManager" /* ORIGINAL: ScopeManagerVariableName */, + "Begin" /* ORIGINAL: ScopeManagerBeginMethodName */); + + // Assign a unique ID for this instance of the source HTML tag. This must be unique + // per call site, e.g. if the tag is on the view twice, there should be two IDs. + Context.Writer.WriteStringLiteral(node.TagName) + .WriteParameterSeparator() + .Write("global::") + .Write("Microsoft.AspNetCore.Razor.TagHelpers.TagMode") + .Write(".") + .Write(node.TagMode.ToString()) + .WriteParameterSeparator() + .WriteStringLiteral(Context.IdGenerator()) + .WriteParameterSeparator(); + + // We remove and redirect writers so TagHelper authors can retrieve content. + var initialRenderingConventions = Context.RenderingConventions; + Context.RenderingConventions = new CSharpRenderingConventions(Context.Writer); + using (Context.Writer.BuildAsyncLambda(endLine: false)) + { + VisitDefault(node); + } + Context.RenderingConventions = initialRenderingConventions; + + Context.Writer.WriteEndMethodInvocation(); + } + + public override void VisitCreateTagHelper(CreateTagHelperIRNode node) + { + var tagHelperVariableName = GetTagHelperVariableName(node.TagHelperTypeName); + + Context.Writer + .WriteStartAssignment(tagHelperVariableName) + .WriteStartMethodInvocation( + "CreateTagHelper" /* ORIGINAL: CreateTagHelperMethodName */, + "global::" + node.TagHelperTypeName) + .WriteEndMethodInvocation(); + + Context.Writer.WriteInstanceMethodInvocation( + "__tagHelperExecutionContext" /* ORIGINAL: ExecutionContextVariableName */, + "Add" /* ORIGINAL: ExecutionContextAddMethodName */, + tagHelperVariableName); + } + + public override void VisitAddPreallocatedTagHelperHtmlAttribute(AddPreallocatedTagHelperHtmlAttributeIRNode node) + { + Context.Writer + .WriteStartInstanceMethodInvocation( + "__tagHelperExecutionContext" /* ORIGINAL: ExecutionContextVariableName */, + "AddHtmlAttribute" /* ORIGINAL: ExecutionContextAddHtmlAttributeMethodName */) + .Write(node.VariableName) + .WriteEndMethodInvocation(); + } + + public override void VisitAddTagHelperHtmlAttribute(AddTagHelperHtmlAttributeIRNode node) + { + var attributeValueStyleParameter = $"global::Microsoft.AspNetCore.Razor.TagHelpers.HtmlAttributeValueStyle.{node.ValueStyle}"; + var isConditionalAttributeValue = node.Children.Any(child => child is CSharpAttributeValueIRNode); + + // All simple text and minimized attributes will be pre-allocated. + if (isConditionalAttributeValue) + { + // Dynamic attribute value should be run through the conditional attribute removal system. It's + // unbound and contains C#. + + // TagHelper attribute rendering is buffered by default. We do not want to write to the current + // writer. + var valuePieceCount = node.Children.Count( + child => child is HtmlAttributeValueIRNode || child is CSharpAttributeValueIRNode); + + Context.Writer + .WriteStartMethodInvocation("BeginAddHtmlAttributeValues" /* ORIGINAL: BeginAddHtmlAttributeValuesMethodName */) + .Write("__tagHelperExecutionContext" /* ORIGINAL: ExecutionContextVariableName */) + .WriteParameterSeparator() + .WriteStringLiteral(node.Name) + .WriteParameterSeparator() + .Write(valuePieceCount.ToString(CultureInfo.InvariantCulture)) + .WriteParameterSeparator() + .Write(attributeValueStyleParameter) + .WriteEndMethodInvocation(); + + var initialRenderingConventions = Context.RenderingConventions; + Context.RenderingConventions = new TagHelperHtmlAttributeRenderingConventions(Context.Writer); + VisitDefault(node); + Context.RenderingConventions = initialRenderingConventions; + + Context.Writer + .WriteMethodInvocation( + "EndAddHtmlAttributeValues" /* ORIGINAL: EndAddHtmlAttributeValuesMethodName */, + "__tagHelperExecutionContext" /* ORIGINAL: ExecutionContextVariableName */); + } + else + { + // This is a data-* attribute which includes C#. Do not perform the conditional attribute removal or + // other special cases used when IsDynamicAttributeValue(). But the attribute must still be buffered to + // determine its final value. + + // Attribute value is not plain text, must be buffered to determine its final value. + Context.Writer.WriteMethodInvocation("BeginWriteTagHelperAttribute" /* ORIGINAL: BeginWriteTagHelperAttributeMethodName */); + + // We're building a writing scope around the provided chunks which captures everything written from the + // page. Therefore, we do not want to write to any other buffer since we're using the pages buffer to + // ensure we capture all content that's written, directly or indirectly. + var initialRenderingConventions = Context.RenderingConventions; + Context.RenderingConventions = new CSharpRenderingConventions(Context.Writer); + VisitDefault(node); + Context.RenderingConventions = initialRenderingConventions; + + Context.Writer + .WriteStartAssignment("__tagHelperStringValueBuffer" /* ORIGINAL: StringValueBufferVariableName */) + .WriteMethodInvocation("EndWriteTagHelperAttribute" /* ORIGINAL: EndWriteTagHelperAttributeMethodName */) + .WriteStartInstanceMethodInvocation( + "__tagHelperExecutionContext" /* ORIGINAL: ExecutionContextVariableName */, + "AddHtmlAttribute" /* ORIGINAL: ExecutionContextAddHtmlAttributeMethodName */) + .WriteStringLiteral(node.Name) + .WriteParameterSeparator() + .WriteStartMethodInvocation("Html.Raw" /* ORIGINAL: MarkAsHtmlEncodedMethodName */) + .Write("__tagHelperStringValueBuffer" /* ORIGINAL: StringValueBufferVariableName */) + .WriteEndMethodInvocation(endLine: false) + .WriteParameterSeparator() + .Write(attributeValueStyleParameter) + .WriteEndMethodInvocation(); + } + } + + public override void VisitSetPreallocatedTagHelperProperty(SetPreallocatedTagHelperPropertyIRNode node) + { + var tagHelperVariableName = GetTagHelperVariableName(node.TagHelperTypeName); + var propertyValueAccessor = GetTagHelperPropertyAccessor(tagHelperVariableName, node.AttributeName, node.Descriptor); + var attributeValueAccessor = $"{node.VariableName}.Value" /* ORIGINAL: TagHelperAttributeValuePropertyName */; + Context.Writer + .WriteStartAssignment(propertyValueAccessor) + .Write("(string)") + .Write(attributeValueAccessor) + .WriteLine(";") + .WriteStartInstanceMethodInvocation( + "__tagHelperExecutionContext" /* ORIGINAL: ExecutionContextVariableName */, + "AddTagHelperAttribute" /* ORIGINAL: ExecutionContextAddTagHelperAttributeMethodName */) + .Write(node.VariableName) + .WriteEndMethodInvocation(); + } + + public override void VisitSetTagHelperProperty(SetTagHelperPropertyIRNode node) + { + var tagHelperVariableName = GetTagHelperVariableName(node.TagHelperTypeName); + var tagHelperRenderingContext = Context.TagHelperRenderingContext; + + // Ensure that the property we're trying to set has initialized its dictionary bound properties. + if (node.Descriptor.IsIndexer && + tagHelperRenderingContext.VerifiedPropertyDictionaries.Add(node.Descriptor.PropertyName)) + { + // Throw a reasonable Exception at runtime if the dictionary property is null. + Context.Writer + .Write("if (") + .Write(tagHelperVariableName) + .Write(".") + .Write(node.Descriptor.PropertyName) + .WriteLine(" == null)"); + using (Context.Writer.BuildScope()) + { + // System is in Host.NamespaceImports for all MVC scenarios. No need to generate FullName + // of InvalidOperationException type. + Context.Writer + .Write("throw ") + .WriteStartNewObject(nameof(InvalidOperationException)) + .WriteStartMethodInvocation("InvalidTagHelperIndexerAssignment" /* ORIGINAL: FormatInvalidIndexerAssignmentMethodName */) + .WriteStringLiteral(node.AttributeName) + .WriteParameterSeparator() + .WriteStringLiteral(node.TagHelperTypeName) + .WriteParameterSeparator() + .WriteStringLiteral(node.Descriptor.PropertyName) + .WriteEndMethodInvocation(endLine: false) // End of method call + .WriteEndMethodInvocation(); // End of new expression / throw statement + } + } + + var propertyValueAccessor = GetTagHelperPropertyAccessor(tagHelperVariableName, node.AttributeName, node.Descriptor); + + string previousValueAccessor; + if (tagHelperRenderingContext.RenderedBoundAttributes.TryGetValue(node.AttributeName, out previousValueAccessor)) + { + Context.Writer + .WriteStartAssignment(propertyValueAccessor) + .Write(previousValueAccessor) + .WriteLine(";"); + + return; + } + else + { + tagHelperRenderingContext.RenderedBoundAttributes[node.AttributeName] = propertyValueAccessor; + } + + if (node.Descriptor.IsStringProperty) + { + Context.Writer.WriteMethodInvocation("BeginWriteTagHelperAttribute" /* ORIGINAL: BeginWriteTagHelperAttributeMethodName */); + + var initialRenderingConventions = Context.RenderingConventions; + Context.RenderingConventions = new CSharpLiteralCodeConventions(Context.Writer); + VisitDefault(node); + Context.RenderingConventions = initialRenderingConventions; + + Context.Writer + .WriteStartAssignment("__tagHelperStringValueBuffer" /* ORIGINAL: StringValueBufferVariableName */) + .WriteMethodInvocation("EndWriteTagHelperAttribute" /* ORIGINAL: EndWriteTagHelperAttributeMethodName */) + .WriteStartAssignment(propertyValueAccessor) + .Write("__tagHelperStringValueBuffer" /* ORIGINAL: StringValueBufferVariableName */) + .WriteLine(";"); + } + else + { + using (new LinePragmaWriter(Context.Writer, node.Source.Value)) + { + Context.Writer.WriteStartAssignment(propertyValueAccessor); + + if (node.Descriptor.IsEnum && + node.Children.Count == 1 && + node.Children.First() is HtmlContentIRNode) + { + Context.Writer + .Write("global::") + .Write(node.Descriptor.TypeName) + .Write("."); + } + + RenderTagHelperAttributeInline(node, node.Source.Value); + + Context.Writer.WriteLine(";"); + } + } + + // We need to inform the context of the attribute value. + Context.Writer + .WriteStartInstanceMethodInvocation( + "__tagHelperExecutionContext" /* ORIGINAL: ExecutionContextVariableName */, + "AddTagHelperAttribute" /* ORIGINAL: ExecutionContextAddTagHelperAttributeMethodName */) + .WriteStringLiteral(node.AttributeName) + .WriteParameterSeparator() + .Write(propertyValueAccessor) + .WriteParameterSeparator() + .Write($"global::Microsoft.AspNetCore.Razor.TagHelpers.HtmlAttributeValueStyle.{node.ValueStyle}") + .WriteEndMethodInvocation(); + } + + public override void VisitExecuteTagHelpers(ExecuteTagHelpersIRNode node) + { + Context.Writer + .Write("await ") + .WriteStartInstanceMethodInvocation( + "__tagHelperRunner" /* ORIGINAL: RunnerVariableName */, + "RunAsync" /* ORIGINAL: RunnerRunAsyncMethodName */) + .Write("__tagHelperExecutionContext" /* ORIGINAL: ExecutionContextVariableName */) + .WriteEndMethodInvocation(); + + var executionContextVariableName = "__tagHelperExecutionContext" /* ORIGINAL: ExecutionContextVariableName */; + var executionContextOutputPropertyName = "Output" /* ORIGINAL: ExecutionContextOutputPropertyName */; + var tagHelperOutputAccessor = $"{executionContextVariableName}.{executionContextOutputPropertyName}"; + + Context.Writer + .Write("if (!") + .Write(tagHelperOutputAccessor) + .Write(".") + .Write("IsContentModified" /* ORIGINAL: TagHelperOutputIsContentModifiedPropertyName */) + .WriteLine(")"); + + using (Context.Writer.BuildScope()) + { + Context.Writer + .Write("await ") + .WriteInstanceMethodInvocation( + executionContextVariableName, + "SetOutputContentAsync" /* ORIGINAL: ExecutionContextSetOutputContentAsyncMethodName */); + } + + Context.Writer + .Write(Context.RenderingConventions.StartWriteMethod) + .Write(tagHelperOutputAccessor) + .WriteEndMethodInvocation() + .WriteStartAssignment(executionContextVariableName) + .WriteInstanceMethodInvocation( + "__tagHelperScopeManager" /* ORIGINAL: ScopeManagerVariableName */, + "End" /* ORIGINAL: ScopeManagerEndMethodName */); + } + + public override void VisitDeclarePreallocatedTagHelperHtmlAttribute(DeclarePreallocatedTagHelperHtmlAttributeIRNode node) + { + Context.Writer + .Write("private static readonly global::") + .Write("Microsoft.AspNetCore.Razor.TagHelpers.TagHelperAttribute" /* ORIGINAL: TagHelperAttributeTypeName */) + .Write(" ") + .Write(node.VariableName) + .Write(" = ") + .WriteStartNewObject("global::" + "Microsoft.AspNetCore.Razor.TagHelpers.TagHelperAttribute" /* ORIGINAL: TagHelperAttributeTypeName */) + .WriteStringLiteral(node.Name); + + if (node.ValueStyle == HtmlAttributeValueStyle.Minimized) + { + Context.Writer.WriteEndMethodInvocation(); + } + else + { + Context.Writer + .WriteParameterSeparator() + .WriteStartNewObject("global::" + "Microsoft.AspNetCore.Html.HtmlString" /* ORIGINAL: EncodedHtmlStringTypeName */) + .WriteStringLiteral(node.Value) + .WriteEndMethodInvocation(endLine: false) + .WriteParameterSeparator() + .Write($"global::Microsoft.AspNetCore.Razor.TagHelpers.HtmlAttributeValueStyle.{node.ValueStyle}") + .WriteEndMethodInvocation(); + } + } + + public override void VisitDeclarePreallocatedTagHelperAttribute(DeclarePreallocatedTagHelperAttributeIRNode node) + { + Context.Writer + .Write("private static readonly global::") + .Write("Microsoft.AspNetCore.Razor.TagHelpers.TagHelperAttribute" /* ORIGINAL: TagHelperAttributeTypeName */) + .Write(" ") + .Write(node.VariableName) + .Write(" = ") + .WriteStartNewObject("global::" + "Microsoft.AspNetCore.Razor.TagHelpers.TagHelperAttribute" /* ORIGINAL: TagHelperAttributeTypeName */) + .WriteStringLiteral(node.Name) + .WriteParameterSeparator() + .WriteStringLiteral(node.Value) + .WriteParameterSeparator() + .Write($"global::Microsoft.AspNetCore.Razor.TagHelpers.HtmlAttributeValueStyle.{node.ValueStyle}") + .WriteEndMethodInvocation(); + } + + public override void VisitDeclareTagHelperFields(DeclareTagHelperFieldsIRNode node) + { + Context.Writer.WriteLineHiddenDirective(); + + // Need to disable the warning "X is assigned to but never used." for the value buffer since + // whether it's used depends on how a TagHelper is used. + Context.Writer + .WritePragma("warning disable 0414") + .Write("private ") + .WriteVariableDeclaration("string", "__tagHelperStringValueBuffer" /* ORIGINAL: StringValueBufferVariableName */, value: null) + .WritePragma("warning restore 0414"); + + Context.Writer + .Write("private global::") + .WriteVariableDeclaration( + "Microsoft.AspNetCore.Razor.Runtime.TagHelpers.TagHelperExecutionContext" /* ORIGINAL: ExecutionContextTypeName */, + "__tagHelperExecutionContext" /* ORIGINAL: ExecutionContextVariableName */, + value: null); + + Context.Writer + .Write("private global::") + .Write("Microsoft.AspNetCore.Razor.Runtime.TagHelpers.TagHelperRunner" /* ORIGINAL: RunnerTypeName */) + .Write(" ") + .Write("__tagHelperRunner" /* ORIGINAL: RunnerVariableName */) + .Write(" = new global::") + .Write("Microsoft.AspNetCore.Razor.Runtime.TagHelpers.TagHelperRunner" /* ORIGINAL: RunnerTypeName */) + .WriteLine("();"); + + const string backedScopeManageVariableName = "__backed" + "__tagHelperScopeManager" /* ORIGINAL: ScopeManagerVariableName */; + Context.Writer + .Write("private global::") + .WriteVariableDeclaration( + "Microsoft.AspNetCore.Razor.Runtime.TagHelpers.TagHelperScopeManager", + backedScopeManageVariableName, + value: null); + + Context.Writer + .Write("private global::") + .Write("Microsoft.AspNetCore.Razor.Runtime.TagHelpers.TagHelperScopeManager" /* ORIGINAL: ScopeManagerTypeName */) + .Write(" ") + .WriteLine("__tagHelperScopeManager" /* ORIGINAL: ScopeManagerVariableName */); + + using (Context.Writer.BuildScope()) + { + Context.Writer.WriteLine("get"); + using (Context.Writer.BuildScope()) + { + Context.Writer + .Write("if (") + .Write(backedScopeManageVariableName) + .WriteLine(" == null)"); + + using (Context.Writer.BuildScope()) + { + Context.Writer + .WriteStartAssignment(backedScopeManageVariableName) + .WriteStartNewObject("Microsoft.AspNetCore.Razor.Runtime.TagHelpers.TagHelperScopeManager" /* ORIGINAL: ScopeManagerTypeName */) + .Write("StartTagHelperWritingScope" /* ORIGINAL: StartTagHelperWritingScopeMethodName */) + .WriteParameterSeparator() + .Write("EndTagHelperWritingScope" /* ORIGINAL: EndTagHelperWritingScopeMethodName */) + .WriteEndMethodInvocation(); + } + + Context.Writer.WriteReturn(backedScopeManageVariableName); + } + } + + foreach (var tagHelperTypeName in node.UsedTagHelperTypeNames) + { + var tagHelperVariableName = GetTagHelperVariableName(tagHelperTypeName); + Context.Writer + .Write("private global::") + .WriteVariableDeclaration( + tagHelperTypeName, + tagHelperVariableName, + value: null); + } + } + + private void RenderTagHelperAttributeInline( + RazorIRNode node, + SourceSpan documentLocation) + { + if (node is SetTagHelperPropertyIRNode || node is CSharpExpressionIRNode) + { + for (var i = 0; i < node.Children.Count; i++) + { + RenderTagHelperAttributeInline(node.Children[i], documentLocation); + } + } + else if (node is HtmlContentIRNode) + { + Context.Writer.Write(((HtmlContentIRNode)node).Content); + } + else if (node is CSharpTokenIRNode) + { + Context.Writer.Write(((CSharpTokenIRNode)node).Content); + } + else if (node is CSharpStatementIRNode) + { + Context.ErrorSink.OnError( + new SourceLocation(documentLocation.AbsoluteIndex, documentLocation.CharacterIndex, documentLocation.Length), + LegacyResources.TagHelpers_CodeBlocks_NotSupported_InAttributes, + documentLocation.Length); + } + else if (node is TemplateIRNode) + { + var attributeValueNode = (SetTagHelperPropertyIRNode)node.Parent; + Context.ErrorSink.OnError( + new SourceLocation(documentLocation.AbsoluteIndex, documentLocation.CharacterIndex, documentLocation.Length), + LegacyResources.FormatTagHelpers_InlineMarkupBlocks_NotSupported_InAttributes(attributeValueNode.Descriptor.TypeName), + documentLocation.Length); + } + } + } +} diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/RuntimeTarget.cs b/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/RuntimeTarget.cs new file mode 100644 index 0000000000..009decbb10 --- /dev/null +++ b/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/RuntimeTarget.cs @@ -0,0 +1,95 @@ +// 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.CodeGeneration +{ + public abstract class RuntimeTarget + { + public static RuntimeTarget CreateDefault(RazorCodeDocument codeDocument, RazorParserOptions options) + { + if (codeDocument == null) + { + throw new ArgumentNullException(nameof(codeDocument)); + } + + if (options == null) + { + throw new ArgumentNullException(nameof(options)); + } + + return CreateDefault(codeDocument, options, configure: null); + } + + public static RuntimeTarget CreateDefault( + RazorCodeDocument codeDocument, + RazorParserOptions options, + Action configure) + { + if (codeDocument == null) + { + throw new ArgumentNullException(nameof(codeDocument)); + } + + if (options == null) + { + throw new ArgumentNullException(nameof(options)); + } + + var builder = new DefaultRuntimeTargetBuilder(codeDocument, options); + + if (builder.Options.DesignTimeMode) + { + AddDesignTimeDefaults(builder); + } + else + { + AddRuntimeDefaults(builder); + } + + if (configure != null) + { + configure.Invoke(builder); + } + + return builder.Build(); + } + + public static RuntimeTarget CreateEmpty( + RazorCodeDocument codeDocument, + RazorParserOptions options, + Action configure) + { + if (codeDocument == null) + { + throw new ArgumentNullException(nameof(codeDocument)); + } + + if (options == null) + { + throw new ArgumentNullException(nameof(options)); + } + + var builder = new DefaultRuntimeTargetBuilder(codeDocument, options); + configure?.Invoke(builder); + return builder.Build(); + } + + internal static void AddDesignTimeDefaults(IRuntimeTargetBuilder builder) + { + + } + + internal static void AddRuntimeDefaults(IRuntimeTargetBuilder builder) + { + + } + + internal abstract PageStructureCSharpRenderer CreateRenderer(CSharpRenderingContext context); + + public abstract TExtension GetExtension() where TExtension : class, IRuntimeTargetExtension; + + public abstract bool HasExtension() where TExtension : class, IRuntimeTargetExtension; + } +} diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/TagHelperHtmlAttributeRenderingConventions.cs b/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/TagHelperHtmlAttributeRenderingConventions.cs new file mode 100644 index 0000000000..1c5000355a --- /dev/null +++ b/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/TagHelperHtmlAttributeRenderingConventions.cs @@ -0,0 +1,16 @@ +// 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.Legacy; + +namespace Microsoft.AspNetCore.Razor.Evolution.CodeGeneration +{ + internal class TagHelperHtmlAttributeRenderingConventions : CSharpRenderingConventions + { + public TagHelperHtmlAttributeRenderingConventions(CSharpCodeWriter writer) : base(writer) + { + } + + public override string StartWriteAttributeValueMethod => "AddHtmlAttributeValue(" /* ORIGINAL: AddHtmlAttributeValueMethodName */; + } +} diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/TagHelperRenderingContext.cs b/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/TagHelperRenderingContext.cs new file mode 100644 index 0000000000..0c5c763e1f --- /dev/null +++ b/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/TagHelperRenderingContext.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; + +namespace Microsoft.AspNetCore.Razor.Evolution.CodeGeneration +{ + internal class TagHelperRenderingContext + { + private Dictionary _renderedBoundAttributes; + private HashSet _verifiedPropertyDictionaries; + + public Dictionary RenderedBoundAttributes + { + get + { + if (_renderedBoundAttributes == null) + { + _renderedBoundAttributes = new Dictionary(StringComparer.OrdinalIgnoreCase); + } + + return _renderedBoundAttributes; + } + } + + public HashSet VerifiedPropertyDictionaries + { + get + { + if (_verifiedPropertyDictionaries == null) + { + _verifiedPropertyDictionaries = new HashSet(StringComparer.Ordinal); + } + + return _verifiedPropertyDictionaries; + } + } + } +} diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/DefaultDirectiveIRPass.cs b/src/Microsoft.AspNetCore.Razor.Evolution/DefaultDirectiveIRPass.cs index f2dd916c25..58d7fcd543 100644 --- a/src/Microsoft.AspNetCore.Razor.Evolution/DefaultDirectiveIRPass.cs +++ b/src/Microsoft.AspNetCore.Razor.Evolution/DefaultDirectiveIRPass.cs @@ -14,10 +14,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution public override DocumentIRNode ExecuteCore(RazorCodeDocument codeDocument, DocumentIRNode irDocument) { - var syntaxTree = codeDocument.GetSyntaxTree(); - ThrowForMissingDocumentDependency(syntaxTree); - - var parserOptions = syntaxTree.Options; + var parserOptions = irDocument.Options; var designTime = parserOptions.DesignTimeMode; var walker = new DirectiveWalker(designTime); diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/DefaultRazorCSharpLoweringPhase.cs b/src/Microsoft.AspNetCore.Razor.Evolution/DefaultRazorCSharpLoweringPhase.cs new file mode 100644 index 0000000000..cffe510cf4 --- /dev/null +++ b/src/Microsoft.AspNetCore.Razor.Evolution/DefaultRazorCSharpLoweringPhase.cs @@ -0,0 +1,72 @@ +// 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.Linq; +using Microsoft.AspNetCore.Razor.Evolution.CodeGeneration; +using Microsoft.AspNetCore.Razor.Evolution.Legacy; +using Microsoft.AspNetCore.Razor.Evolution.Intermediate; + +namespace Microsoft.AspNetCore.Razor.Evolution +{ + internal class DefaultRazorCSharpLoweringPhase : RazorEnginePhaseBase, IRazorCSharpLoweringPhase + { + internal static readonly object NewLineString = "NewLineString"; + + internal static readonly object SuppressUniqueIds = "SuppressUniqueIds"; + + protected override void ExecuteCore(RazorCodeDocument codeDocument) + { + var irDocument = codeDocument.GetIRDocument(); + ThrowForMissingDependency(irDocument); + + var syntaxTree = codeDocument.GetSyntaxTree(); + ThrowForMissingDependency(syntaxTree); + + var target = irDocument.Target; + if (target == null) + { + var message = Resources.FormatDocumentMissingTarget( + irDocument.DocumentKind, + nameof(RuntimeTarget), + nameof(DocumentIRNode.Target)); + throw new InvalidOperationException(message); + } + + var codeWriter = new CSharpCodeWriter(); + var newLineString = codeDocument.Items[NewLineString]; + if (newLineString != null) + { + // Set new line character to a specific string regardless of platform, for testing purposes. + codeWriter.NewLine = (string)newLineString; + } + + var renderingContext = new CSharpRenderingContext() + { + Writer = codeWriter, + SourceDocument = codeDocument.Source, + Options = irDocument.Options, + }; + + var idValue = codeDocument.Items[SuppressUniqueIds]; + if (idValue != null) + { + // Generate a static value for unique ids instead of a guid, for testing purposes. + renderingContext.IdGenerator = () => idValue.ToString(); + } + + var renderer = target.CreateRenderer(renderingContext); + renderer.VisitDocument(irDocument); + + var combinedErrors = syntaxTree.Diagnostics.Concat(renderingContext.ErrorSink.Errors).ToList(); + var csharpDocument = new RazorCSharpDocument() + { + GeneratedCode = renderingContext.Writer.GenerateCode(), + LineMappings = renderingContext.LineMappings, + Diagnostics = combinedErrors + }; + + codeDocument.SetCSharpDocument(csharpDocument); + } + } +} diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/DefaultRazorDesignTimeCSharpLoweringPhase.cs b/src/Microsoft.AspNetCore.Razor.Evolution/DefaultRazorDesignTimeCSharpLoweringPhase.cs deleted file mode 100644 index f5f3217e2e..0000000000 --- a/src/Microsoft.AspNetCore.Razor.Evolution/DefaultRazorDesignTimeCSharpLoweringPhase.cs +++ /dev/null @@ -1,397 +0,0 @@ -// 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.Linq; -using Microsoft.AspNetCore.Razor.Evolution.Intermediate; -using Microsoft.AspNetCore.Razor.Evolution.Legacy; - -namespace Microsoft.AspNetCore.Razor.Evolution -{ - internal class DefaultRazorDesignTimeCSharpLoweringPhase : RazorCSharpLoweringPhaseBase - { - internal static readonly object NewLineString = "NewLineString"; - - protected override void ExecuteCore(RazorCodeDocument codeDocument) - { - var irDocument = codeDocument.GetIRDocument(); - ThrowForMissingDependency(irDocument); - - var syntaxTree = codeDocument.GetSyntaxTree(); - ThrowForMissingDependency(syntaxTree); - - var codeWriter = new CSharpCodeWriter(); - var newLineString = codeDocument.Items[NewLineString]; - if (newLineString != null) - { - // Set new line character to a specific string regardless of platform, for testing purposes. - codeWriter.NewLine = (string)newLineString; - } - - var renderingContext = new CSharpRenderingContext() - { - Writer = codeWriter, - SourceDocument = codeDocument.Source, - Options = syntaxTree.Options, - }; - var visitor = new CSharpRenderer(renderingContext); - visitor.VisitDocument(irDocument); - - var combinedErrors = syntaxTree.Diagnostics.Concat(renderingContext.ErrorSink.Errors).ToList(); - var csharpDocument = new RazorCSharpDocument() - { - GeneratedCode = renderingContext.Writer.GenerateCode(), - LineMappings = renderingContext.LineMappings, - Diagnostics = combinedErrors - }; - - codeDocument.SetCSharpDocument(csharpDocument); - } - - private class CSharpRenderer : PageStructureCSharpRenderer - { - public CSharpRenderer(CSharpRenderingContext context) : base(context) - { - } - - public override void VisitCSharpToken(CSharpTokenIRNode node) - { - Context.Writer.Write(node.Content); - } - - public override void VisitCSharpExpression(CSharpExpressionIRNode node) - { - if (node.Children.Count == 0) - { - return; - } - - if (node.Source != null) - { - using (new LinePragmaWriter(Context.Writer, node.Source.Value)) - { - var padding = BuildOffsetPadding(RazorDesignTimeIRPass.DesignTimeVariable.Length, node.Source.Value, Context); - - Context.Writer - .Write(padding) - .WriteStartAssignment(RazorDesignTimeIRPass.DesignTimeVariable); - - for (var i = 0; i < node.Children.Count; i++) - { - var childNode = node.Children[i]; - - if (childNode is CSharpTokenIRNode) - { - AddLineMappingFor(childNode); - } - - childNode.Accept(this); - } - - Context.Writer.WriteLine(";"); - } - } - else - { - Context.Writer.WriteStartAssignment(RazorDesignTimeIRPass.DesignTimeVariable); - VisitDefault(node); - Context.Writer.WriteLine(";"); - } - } - - public override void VisitUsingStatement(UsingStatementIRNode node) - { - Context.Writer.WriteUsing(node.Content); - } - - public override void VisitCSharpStatement(CSharpStatementIRNode node) - { - if (node.Source != null) - { - using (new LinePragmaWriter(Context.Writer, node.Source.Value)) - { - var padding = BuildOffsetPadding(0, node.Source.Value, Context); - Context.Writer.Write(padding); - - AddLineMappingFor(node); - Context.Writer.Write(node.Content); - } - } - else - { - Context.Writer.WriteLine(node.Content); - } - } - - public override void VisitDirectiveToken(DirectiveTokenIRNode node) - { - const string TypeHelper = "__typeHelper"; - - var tokenKind = node.Descriptor.Kind; - if (node.Source == null) - { - return; - } - - // Wrap the directive token in a lambda to isolate variable names. - Context.Writer - .Write("((") - .Write(typeof(Action).FullName) - .Write(")("); - using (Context.Writer.BuildLambda(endLine: false)) - { - var originalIndent = Context.Writer.CurrentIndent; - Context.Writer.ResetIndent(); - switch (tokenKind) - { - case DirectiveTokenKind.Type: - - AddLineMappingFor(node); - Context.Writer - .Write(node.Content) - .Write(" ") - .WriteStartAssignment(TypeHelper) - .WriteLine("null;"); - break; - case DirectiveTokenKind.Member: - Context.Writer - .Write(typeof(object).FullName) - .Write(" "); - - AddLineMappingFor(node); - Context.Writer - .Write(node.Content) - .WriteLine(" = null;"); - break; - case DirectiveTokenKind.String: - Context.Writer - .Write(typeof(object).FullName) - .Write(" ") - .WriteStartAssignment(TypeHelper); - - if (node.Content.StartsWith("\"", StringComparison.Ordinal)) - { - AddLineMappingFor(node); - Context.Writer.Write(node.Content); - } - else - { - Context.Writer.Write("\""); - AddLineMappingFor(node); - Context.Writer - .Write(node.Content) - .Write("\""); - } - - Context.Writer.WriteLine(";"); - break; - } - Context.Writer.SetIndent(originalIndent); - } - Context.Writer.WriteLine("))();"); - - } - - public override void VisitTemplate(TemplateIRNode node) - { - const string ItemParameterName = "item"; - const string TemplateWriterName = "__razor_template_writer"; - - Context.Writer - .Write(ItemParameterName).Write(" => ") - .WriteStartNewObject("Microsoft.AspNetCore.Mvc.Razor.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); - } - - public override void VisitTagHelper(TagHelperIRNode node) - { - var initialTagHelperRenderingContext = Context.TagHelperRenderingContext; - Context.TagHelperRenderingContext = new TagHelperRenderingContext(); - VisitDefault(node); - Context.TagHelperRenderingContext = initialTagHelperRenderingContext; - } - - public override void VisitInitializeTagHelperStructure(InitializeTagHelperStructureIRNode node) - { - VisitDefault(node); - } - - public override void VisitCreateTagHelper(CreateTagHelperIRNode node) - { - var tagHelperVariableName = GetTagHelperVariableName(node.TagHelperTypeName); - - Context.Writer - .WriteStartAssignment(tagHelperVariableName) - .WriteStartMethodInvocation( - "CreateTagHelper" /* ORIGINAL: CreateTagHelperMethodName */, - "global::" + node.TagHelperTypeName) - .WriteEndMethodInvocation(); - } - - public override void VisitSetTagHelperProperty(SetTagHelperPropertyIRNode node) - { - var tagHelperVariableName = GetTagHelperVariableName(node.TagHelperTypeName); - var tagHelperRenderingContext = Context.TagHelperRenderingContext; - var propertyValueAccessor = GetTagHelperPropertyAccessor(tagHelperVariableName, node.AttributeName, node.Descriptor); - - string previousValueAccessor; - if (tagHelperRenderingContext.RenderedBoundAttributes.TryGetValue(node.AttributeName, out previousValueAccessor)) - { - Context.Writer - .WriteStartAssignment(propertyValueAccessor) - .Write(previousValueAccessor) - .WriteLine(";"); - - return; - } - else - { - tagHelperRenderingContext.RenderedBoundAttributes[node.AttributeName] = propertyValueAccessor; - } - - if (node.Descriptor.IsStringProperty) - { - VisitDefault(node); - - Context.Writer.WriteStartAssignment(propertyValueAccessor); - if (node.Children.Count == 1 && node.Children.First() is HtmlContentIRNode) - { - var htmlNode = node.Children.First() as HtmlContentIRNode; - if (htmlNode != null) - { - Context.Writer.WriteStringLiteral(htmlNode.Content); - } - } - else - { - Context.Writer.Write("string.Empty"); - } - Context.Writer.WriteLine(";"); - } - else - { - var firstMappedChild = node.Children.FirstOrDefault(child => child.Source != null) as RazorIRNode; - var valueStart = firstMappedChild?.Source; - - using (new LinePragmaWriter(Context.Writer, node.Source.Value)) - { - var assignmentPrefixLength = propertyValueAccessor.Length + " = ".Length; - if (node.Descriptor.IsEnum && - node.Children.Count == 1 && - node.Children.First() is HtmlContentIRNode) - { - assignmentPrefixLength += $"global::{node.Descriptor.TypeName}.".Length; - - if (valueStart != null) - { - var padding = BuildOffsetPadding(assignmentPrefixLength, node.Source.Value, Context); - - Context.Writer.Write(padding); - } - - Context.Writer - .WriteStartAssignment(propertyValueAccessor) - .Write("global::") - .Write(node.Descriptor.TypeName) - .Write("."); - } - else - { - if (valueStart != null) - { - var padding = BuildOffsetPadding(assignmentPrefixLength, node.Source.Value, Context); - - Context.Writer.Write(padding); - } - - Context.Writer.WriteStartAssignment(propertyValueAccessor); - } - - RenderTagHelperAttributeInline(node, node.Source.Value); - - Context.Writer.WriteLine(";"); - } - } - } - - public override void VisitDeclareTagHelperFields(DeclareTagHelperFieldsIRNode node) - { - foreach (var tagHelperTypeName in node.UsedTagHelperTypeNames) - { - var tagHelperVariableName = GetTagHelperVariableName(tagHelperTypeName); - Context.Writer - .Write("private global::") - .WriteVariableDeclaration( - tagHelperTypeName, - tagHelperVariableName, - value: null); - } - } - - private void AddLineMappingFor(RazorIRNode node) - { - var sourceLocation = node.Source.Value; - var generatedLocation = new SourceSpan(Context.Writer.GetCurrentSourceLocation(), sourceLocation.Length); - var lineMapping = new LineMapping(sourceLocation, generatedLocation); - - Context.LineMappings.Add(lineMapping); - } - - private void RenderTagHelperAttributeInline( - RazorIRNode node, - SourceSpan documentLocation) - { - if (node is SetTagHelperPropertyIRNode || node is CSharpExpressionIRNode) - { - for (var i = 0; i < node.Children.Count; i++) - { - RenderTagHelperAttributeInline(node.Children[i], documentLocation); - } - } - else if (node is HtmlContentIRNode) - { - if (node.Source != null) - { - AddLineMappingFor(node); - } - - Context.Writer.Write(((HtmlContentIRNode)node).Content); - } - else if (node is CSharpTokenIRNode) - { - if (node.Source != null) - { - AddLineMappingFor(node); - } - - Context.Writer.Write(((CSharpTokenIRNode)node).Content); - } - else if (node is CSharpStatementIRNode) - { - Context.ErrorSink.OnError( - new SourceLocation(documentLocation.AbsoluteIndex, documentLocation.CharacterIndex, documentLocation.Length), - LegacyResources.TagHelpers_CodeBlocks_NotSupported_InAttributes, - documentLocation.Length); - } - else if (node is TemplateIRNode) - { - var attributeValueNode = (SetTagHelperPropertyIRNode)node.Parent; - Context.ErrorSink.OnError( - new SourceLocation(documentLocation.AbsoluteIndex, documentLocation.CharacterIndex, documentLocation.Length), - LegacyResources.FormatTagHelpers_InlineMarkupBlocks_NotSupported_InAttributes(attributeValueNode.Descriptor.TypeName), - documentLocation.Length); - } - } - } - } -} diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/DefaultRazorIRLoweringPhase.cs b/src/Microsoft.AspNetCore.Razor.Evolution/DefaultRazorIRLoweringPhase.cs index 99af7417dc..284d705ca3 100644 --- a/src/Microsoft.AspNetCore.Razor.Evolution/DefaultRazorIRLoweringPhase.cs +++ b/src/Microsoft.AspNetCore.Razor.Evolution/DefaultRazorIRLoweringPhase.cs @@ -17,7 +17,10 @@ namespace Microsoft.AspNetCore.Razor.Evolution ThrowForMissingDependency(syntaxTree); var builder = RazorIRBuilder.Document(); + var document = (DocumentIRNode)builder.Current; + document.Options = syntaxTree.Options; + var namespaces = new HashSet(); var i = 0; diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/DefaultRazorRuntimeCSharpLoweringPhase.cs b/src/Microsoft.AspNetCore.Razor.Evolution/DefaultRazorRuntimeCSharpLoweringPhase.cs deleted file mode 100644 index 2b5f9d5b03..0000000000 --- a/src/Microsoft.AspNetCore.Razor.Evolution/DefaultRazorRuntimeCSharpLoweringPhase.cs +++ /dev/null @@ -1,734 +0,0 @@ -// 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.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 DefaultRazorRuntimeCSharpLoweringPhase : RazorCSharpLoweringPhaseBase - { - internal static readonly object SuppressUniqueIds = "SuppressUniqueIds"; - - 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 idValue = codeDocument.Items[SuppressUniqueIds]; - if (idValue != null) - { - renderingContext.IdGenerator = () => idValue.ToString(); - } - - var visitor = new CSharpRenderer(renderingContext); - visitor.VisitDocument(irDocument); - - var combinedErrors = syntaxTree.Diagnostics.Concat(renderingContext.ErrorSink.Errors).ToList(); - var csharpDocument = new RazorCSharpDocument() - { - GeneratedCode = renderingContext.Writer.GenerateCode(), - LineMappings = renderingContext.LineMappings, - Diagnostics = combinedErrors - }; - - codeDocument.SetCSharpDocument(csharpDocument); - } - - private 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.Source != null) - { - linePragmaScope = new LinePragmaWriter(Context.Writer, node.Source.Value); - var padding = BuildOffsetPadding(Context.RenderingConventions.StartWriteMethod.Length, node.Source.Value, Context); - Context.Writer.Write(padding); - } - - Context.Writer.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.Source.Value.AbsoluteIndex; - var suffixLocation = node.Source.Value.AbsoluteIndex + node.Source.Value.Length - 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.Source.Value.AbsoluteIndex; - var valueLocation = node.Source.Value.AbsoluteIndex + node.Prefix.Length; - var valueLength = node.Source.Value.Length; - 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.FirstOrDefault() as CSharpExpressionIRNode; - var linePragma = expressionValue != null ? new LinePragmaWriter(Context.Writer, node.Source.Value) : null; - var prefixLocation = node.Source.Value.AbsoluteIndex; - var valueLocation = node.Source.Value.AbsoluteIndex + node.Prefix.Length; - var valueLength = node.Source.Value.Length - 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("Microsoft.AspNetCore.Mvc.Razor.HelperResult" /* ORIGINAL: TemplateTypeName */); - - var initialRenderingConventions = Context.RenderingConventions; - Context.RenderingConventions = new CSharpRedirectRenderingConventions(ValueWriterName, Context.Writer); - 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 VisitCSharpStatement(CSharpStatementIRNode node) - { - if (string.IsNullOrWhiteSpace(node.Content)) - { - return; - } - - if (node.Source != null) - { - using (new LinePragmaWriter(Context.Writer, node.Source.Value)) - { - var padding = BuildOffsetPadding(0, node.Source.Value, Context); - 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("Microsoft.AspNetCore.Mvc.Razor.HelperResult" /* ORIGINAL: TemplateTypeName */); - - var initialRenderingConventions = Context.RenderingConventions; - Context.RenderingConventions = new CSharpRedirectRenderingConventions(TemplateWriterName, Context.Writer); - using (Context.Writer.BuildAsyncLambda(endLine: false, parameterNames: TemplateWriterName)) - { - VisitDefault(node); - } - Context.RenderingConventions = initialRenderingConventions; - - Context.Writer.WriteEndMethodInvocation(endLine: false); - } - - public override void VisitTagHelper(TagHelperIRNode node) - { - var initialTagHelperRenderingContext = Context.TagHelperRenderingContext; - Context.TagHelperRenderingContext = new TagHelperRenderingContext(); - VisitDefault(node); - Context.TagHelperRenderingContext = initialTagHelperRenderingContext; - } - - public override void VisitInitializeTagHelperStructure(InitializeTagHelperStructureIRNode node) - { - // Call into the tag helper scope manager to start a new tag helper scope. - // Also capture the value as the current execution context. - Context.Writer - .WriteStartAssignment("__tagHelperExecutionContext" /* ORIGINAL: ExecutionContextVariableName */) - .WriteStartInstanceMethodInvocation( - "__tagHelperScopeManager" /* ORIGINAL: ScopeManagerVariableName */, - "Begin" /* ORIGINAL: ScopeManagerBeginMethodName */); - - // Assign a unique ID for this instance of the source HTML tag. This must be unique - // per call site, e.g. if the tag is on the view twice, there should be two IDs. - Context.Writer.WriteStringLiteral(node.TagName) - .WriteParameterSeparator() - .Write("global::") - .Write("Microsoft.AspNetCore.Razor.TagHelpers.TagMode") - .Write(".") - .Write(node.TagMode.ToString()) - .WriteParameterSeparator() - .WriteStringLiteral(Context.IdGenerator()) - .WriteParameterSeparator(); - - // We remove and redirect writers so TagHelper authors can retrieve content. - var initialRenderingConventions = Context.RenderingConventions; - Context.RenderingConventions = new CSharpRenderingConventions(Context.Writer); - using (Context.Writer.BuildAsyncLambda(endLine: false)) - { - VisitDefault(node); - } - Context.RenderingConventions = initialRenderingConventions; - - Context.Writer.WriteEndMethodInvocation(); - } - - public override void VisitCreateTagHelper(CreateTagHelperIRNode node) - { - var tagHelperVariableName = GetTagHelperVariableName(node.TagHelperTypeName); - - Context.Writer - .WriteStartAssignment(tagHelperVariableName) - .WriteStartMethodInvocation( - "CreateTagHelper" /* ORIGINAL: CreateTagHelperMethodName */, - "global::" + node.TagHelperTypeName) - .WriteEndMethodInvocation(); - - Context.Writer.WriteInstanceMethodInvocation( - "__tagHelperExecutionContext" /* ORIGINAL: ExecutionContextVariableName */, - "Add" /* ORIGINAL: ExecutionContextAddMethodName */, - tagHelperVariableName); - } - - public override void VisitAddPreallocatedTagHelperHtmlAttribute(AddPreallocatedTagHelperHtmlAttributeIRNode node) - { - Context.Writer - .WriteStartInstanceMethodInvocation( - "__tagHelperExecutionContext" /* ORIGINAL: ExecutionContextVariableName */, - "AddHtmlAttribute" /* ORIGINAL: ExecutionContextAddHtmlAttributeMethodName */) - .Write(node.VariableName) - .WriteEndMethodInvocation(); - } - - public override void VisitAddTagHelperHtmlAttribute(AddTagHelperHtmlAttributeIRNode node) - { - var attributeValueStyleParameter = $"global::Microsoft.AspNetCore.Razor.TagHelpers.HtmlAttributeValueStyle.{node.ValueStyle}"; - var isConditionalAttributeValue = node.Children.Any(child => child is CSharpAttributeValueIRNode); - - // All simple text and minimized attributes will be pre-allocated. - if (isConditionalAttributeValue) - { - // Dynamic attribute value should be run through the conditional attribute removal system. It's - // unbound and contains C#. - - // TagHelper attribute rendering is buffered by default. We do not want to write to the current - // writer. - var valuePieceCount = node.Children.Count( - child => child is HtmlAttributeValueIRNode || child is CSharpAttributeValueIRNode); - - Context.Writer - .WriteStartMethodInvocation("BeginAddHtmlAttributeValues" /* ORIGINAL: BeginAddHtmlAttributeValuesMethodName */) - .Write("__tagHelperExecutionContext" /* ORIGINAL: ExecutionContextVariableName */) - .WriteParameterSeparator() - .WriteStringLiteral(node.Name) - .WriteParameterSeparator() - .Write(valuePieceCount.ToString(CultureInfo.InvariantCulture)) - .WriteParameterSeparator() - .Write(attributeValueStyleParameter) - .WriteEndMethodInvocation(); - - var initialRenderingConventions = Context.RenderingConventions; - Context.RenderingConventions = new TagHelperHtmlAttributeRenderingConventions(Context.Writer); - VisitDefault(node); - Context.RenderingConventions = initialRenderingConventions; - - Context.Writer - .WriteMethodInvocation( - "EndAddHtmlAttributeValues" /* ORIGINAL: EndAddHtmlAttributeValuesMethodName */, - "__tagHelperExecutionContext" /* ORIGINAL: ExecutionContextVariableName */); - } - else - { - // This is a data-* attribute which includes C#. Do not perform the conditional attribute removal or - // other special cases used when IsDynamicAttributeValue(). But the attribute must still be buffered to - // determine its final value. - - // Attribute value is not plain text, must be buffered to determine its final value. - Context.Writer.WriteMethodInvocation("BeginWriteTagHelperAttribute" /* ORIGINAL: BeginWriteTagHelperAttributeMethodName */); - - // We're building a writing scope around the provided chunks which captures everything written from the - // page. Therefore, we do not want to write to any other buffer since we're using the pages buffer to - // ensure we capture all content that's written, directly or indirectly. - var initialRenderingConventions = Context.RenderingConventions; - Context.RenderingConventions = new CSharpRenderingConventions(Context.Writer); - VisitDefault(node); - Context.RenderingConventions = initialRenderingConventions; - - Context.Writer - .WriteStartAssignment("__tagHelperStringValueBuffer" /* ORIGINAL: StringValueBufferVariableName */) - .WriteMethodInvocation("EndWriteTagHelperAttribute" /* ORIGINAL: EndWriteTagHelperAttributeMethodName */) - .WriteStartInstanceMethodInvocation( - "__tagHelperExecutionContext" /* ORIGINAL: ExecutionContextVariableName */, - "AddHtmlAttribute" /* ORIGINAL: ExecutionContextAddHtmlAttributeMethodName */) - .WriteStringLiteral(node.Name) - .WriteParameterSeparator() - .WriteStartMethodInvocation("Html.Raw" /* ORIGINAL: MarkAsHtmlEncodedMethodName */) - .Write("__tagHelperStringValueBuffer" /* ORIGINAL: StringValueBufferVariableName */) - .WriteEndMethodInvocation(endLine: false) - .WriteParameterSeparator() - .Write(attributeValueStyleParameter) - .WriteEndMethodInvocation(); - } - } - - public override void VisitSetPreallocatedTagHelperProperty(SetPreallocatedTagHelperPropertyIRNode node) - { - var tagHelperVariableName = GetTagHelperVariableName(node.TagHelperTypeName); - var propertyValueAccessor = GetTagHelperPropertyAccessor(tagHelperVariableName, node.AttributeName, node.Descriptor); - var attributeValueAccessor = $"{node.VariableName}.Value" /* ORIGINAL: TagHelperAttributeValuePropertyName */; - Context.Writer - .WriteStartAssignment(propertyValueAccessor) - .Write("(string)") - .Write(attributeValueAccessor) - .WriteLine(";") - .WriteStartInstanceMethodInvocation( - "__tagHelperExecutionContext" /* ORIGINAL: ExecutionContextVariableName */, - "AddTagHelperAttribute" /* ORIGINAL: ExecutionContextAddTagHelperAttributeMethodName */) - .Write(node.VariableName) - .WriteEndMethodInvocation(); - } - - public override void VisitSetTagHelperProperty(SetTagHelperPropertyIRNode node) - { - var tagHelperVariableName = GetTagHelperVariableName(node.TagHelperTypeName); - var tagHelperRenderingContext = Context.TagHelperRenderingContext; - - // Ensure that the property we're trying to set has initialized its dictionary bound properties. - if (node.Descriptor.IsIndexer && - tagHelperRenderingContext.VerifiedPropertyDictionaries.Add(node.Descriptor.PropertyName)) - { - // Throw a reasonable Exception at runtime if the dictionary property is null. - Context.Writer - .Write("if (") - .Write(tagHelperVariableName) - .Write(".") - .Write(node.Descriptor.PropertyName) - .WriteLine(" == null)"); - using (Context.Writer.BuildScope()) - { - // System is in Host.NamespaceImports for all MVC scenarios. No need to generate FullName - // of InvalidOperationException type. - Context.Writer - .Write("throw ") - .WriteStartNewObject(nameof(InvalidOperationException)) - .WriteStartMethodInvocation("InvalidTagHelperIndexerAssignment" /* ORIGINAL: FormatInvalidIndexerAssignmentMethodName */) - .WriteStringLiteral(node.AttributeName) - .WriteParameterSeparator() - .WriteStringLiteral(node.TagHelperTypeName) - .WriteParameterSeparator() - .WriteStringLiteral(node.Descriptor.PropertyName) - .WriteEndMethodInvocation(endLine: false) // End of method call - .WriteEndMethodInvocation(); // End of new expression / throw statement - } - } - - var propertyValueAccessor = GetTagHelperPropertyAccessor(tagHelperVariableName, node.AttributeName, node.Descriptor); - - string previousValueAccessor; - if (tagHelperRenderingContext.RenderedBoundAttributes.TryGetValue(node.AttributeName, out previousValueAccessor)) - { - Context.Writer - .WriteStartAssignment(propertyValueAccessor) - .Write(previousValueAccessor) - .WriteLine(";"); - - return; - } - else - { - tagHelperRenderingContext.RenderedBoundAttributes[node.AttributeName] = propertyValueAccessor; - } - - if (node.Descriptor.IsStringProperty) - { - Context.Writer.WriteMethodInvocation("BeginWriteTagHelperAttribute" /* ORIGINAL: BeginWriteTagHelperAttributeMethodName */); - - var initialRenderingConventions = Context.RenderingConventions; - Context.RenderingConventions = new CSharpLiteralCodeConventions(Context.Writer); - VisitDefault(node); - Context.RenderingConventions = initialRenderingConventions; - - Context.Writer - .WriteStartAssignment("__tagHelperStringValueBuffer" /* ORIGINAL: StringValueBufferVariableName */) - .WriteMethodInvocation("EndWriteTagHelperAttribute" /* ORIGINAL: EndWriteTagHelperAttributeMethodName */) - .WriteStartAssignment(propertyValueAccessor) - .Write("__tagHelperStringValueBuffer" /* ORIGINAL: StringValueBufferVariableName */) - .WriteLine(";"); - } - else - { - using (new LinePragmaWriter(Context.Writer, node.Source.Value)) - { - Context.Writer.WriteStartAssignment(propertyValueAccessor); - - if (node.Descriptor.IsEnum && - node.Children.Count == 1 && - node.Children.First() is HtmlContentIRNode) - { - Context.Writer - .Write("global::") - .Write(node.Descriptor.TypeName) - .Write("."); - } - - RenderTagHelperAttributeInline(node, node.Source.Value); - - Context.Writer.WriteLine(";"); - } - } - - // We need to inform the context of the attribute value. - Context.Writer - .WriteStartInstanceMethodInvocation( - "__tagHelperExecutionContext" /* ORIGINAL: ExecutionContextVariableName */, - "AddTagHelperAttribute" /* ORIGINAL: ExecutionContextAddTagHelperAttributeMethodName */) - .WriteStringLiteral(node.AttributeName) - .WriteParameterSeparator() - .Write(propertyValueAccessor) - .WriteParameterSeparator() - .Write($"global::Microsoft.AspNetCore.Razor.TagHelpers.HtmlAttributeValueStyle.{node.ValueStyle}") - .WriteEndMethodInvocation(); - } - - public override void VisitExecuteTagHelpers(ExecuteTagHelpersIRNode node) - { - Context.Writer - .Write("await ") - .WriteStartInstanceMethodInvocation( - "__tagHelperRunner" /* ORIGINAL: RunnerVariableName */, - "RunAsync" /* ORIGINAL: RunnerRunAsyncMethodName */) - .Write("__tagHelperExecutionContext" /* ORIGINAL: ExecutionContextVariableName */) - .WriteEndMethodInvocation(); - - var executionContextVariableName = "__tagHelperExecutionContext" /* ORIGINAL: ExecutionContextVariableName */; - var executionContextOutputPropertyName = "Output" /* ORIGINAL: ExecutionContextOutputPropertyName */; - var tagHelperOutputAccessor = $"{executionContextVariableName}.{executionContextOutputPropertyName}"; - - Context.Writer - .Write("if (!") - .Write(tagHelperOutputAccessor) - .Write(".") - .Write("IsContentModified" /* ORIGINAL: TagHelperOutputIsContentModifiedPropertyName */) - .WriteLine(")"); - - using (Context.Writer.BuildScope()) - { - Context.Writer - .Write("await ") - .WriteInstanceMethodInvocation( - executionContextVariableName, - "SetOutputContentAsync" /* ORIGINAL: ExecutionContextSetOutputContentAsyncMethodName */); - } - - Context.Writer - .Write(Context.RenderingConventions.StartWriteMethod) - .Write(tagHelperOutputAccessor) - .WriteEndMethodInvocation() - .WriteStartAssignment(executionContextVariableName) - .WriteInstanceMethodInvocation( - "__tagHelperScopeManager" /* ORIGINAL: ScopeManagerVariableName */, - "End" /* ORIGINAL: ScopeManagerEndMethodName */); - } - - public override void VisitDeclarePreallocatedTagHelperHtmlAttribute(DeclarePreallocatedTagHelperHtmlAttributeIRNode node) - { - Context.Writer - .Write("private static readonly global::") - .Write("Microsoft.AspNetCore.Razor.TagHelpers.TagHelperAttribute" /* ORIGINAL: TagHelperAttributeTypeName */) - .Write(" ") - .Write(node.VariableName) - .Write(" = ") - .WriteStartNewObject("global::" + "Microsoft.AspNetCore.Razor.TagHelpers.TagHelperAttribute" /* ORIGINAL: TagHelperAttributeTypeName */) - .WriteStringLiteral(node.Name); - - if (node.ValueStyle == HtmlAttributeValueStyle.Minimized) - { - Context.Writer.WriteEndMethodInvocation(); - } - else - { - Context.Writer - .WriteParameterSeparator() - .WriteStartNewObject("global::" + "Microsoft.AspNetCore.Html.HtmlString" /* ORIGINAL: EncodedHtmlStringTypeName */) - .WriteStringLiteral(node.Value) - .WriteEndMethodInvocation(endLine: false) - .WriteParameterSeparator() - .Write($"global::Microsoft.AspNetCore.Razor.TagHelpers.HtmlAttributeValueStyle.{node.ValueStyle}") - .WriteEndMethodInvocation(); - } - } - - public override void VisitDeclarePreallocatedTagHelperAttribute(DeclarePreallocatedTagHelperAttributeIRNode node) - { - Context.Writer - .Write("private static readonly global::") - .Write("Microsoft.AspNetCore.Razor.TagHelpers.TagHelperAttribute" /* ORIGINAL: TagHelperAttributeTypeName */) - .Write(" ") - .Write(node.VariableName) - .Write(" = ") - .WriteStartNewObject("global::" + "Microsoft.AspNetCore.Razor.TagHelpers.TagHelperAttribute" /* ORIGINAL: TagHelperAttributeTypeName */) - .WriteStringLiteral(node.Name) - .WriteParameterSeparator() - .WriteStringLiteral(node.Value) - .WriteParameterSeparator() - .Write($"global::Microsoft.AspNetCore.Razor.TagHelpers.HtmlAttributeValueStyle.{node.ValueStyle}") - .WriteEndMethodInvocation(); - } - - public override void VisitDeclareTagHelperFields(DeclareTagHelperFieldsIRNode node) - { - Context.Writer.WriteLineHiddenDirective(); - - // Need to disable the warning "X is assigned to but never used." for the value buffer since - // whether it's used depends on how a TagHelper is used. - Context.Writer - .WritePragma("warning disable 0414") - .Write("private ") - .WriteVariableDeclaration("string", "__tagHelperStringValueBuffer" /* ORIGINAL: StringValueBufferVariableName */, value: null) - .WritePragma("warning restore 0414"); - - Context.Writer - .Write("private global::") - .WriteVariableDeclaration( - "Microsoft.AspNetCore.Razor.Runtime.TagHelpers.TagHelperExecutionContext" /* ORIGINAL: ExecutionContextTypeName */, - "__tagHelperExecutionContext" /* ORIGINAL: ExecutionContextVariableName */, - value: null); - - Context.Writer - .Write("private global::") - .Write("Microsoft.AspNetCore.Razor.Runtime.TagHelpers.TagHelperRunner" /* ORIGINAL: RunnerTypeName */) - .Write(" ") - .Write("__tagHelperRunner" /* ORIGINAL: RunnerVariableName */) - .Write(" = new global::") - .Write("Microsoft.AspNetCore.Razor.Runtime.TagHelpers.TagHelperRunner" /* ORIGINAL: RunnerTypeName */) - .WriteLine("();"); - - const string backedScopeManageVariableName = "__backed" + "__tagHelperScopeManager" /* ORIGINAL: ScopeManagerVariableName */; - Context.Writer - .Write("private global::") - .WriteVariableDeclaration( - "Microsoft.AspNetCore.Razor.Runtime.TagHelpers.TagHelperScopeManager", - backedScopeManageVariableName, - value: null); - - Context.Writer - .Write("private global::") - .Write("Microsoft.AspNetCore.Razor.Runtime.TagHelpers.TagHelperScopeManager" /* ORIGINAL: ScopeManagerTypeName */) - .Write(" ") - .WriteLine("__tagHelperScopeManager" /* ORIGINAL: ScopeManagerVariableName */); - - using (Context.Writer.BuildScope()) - { - Context.Writer.WriteLine("get"); - using (Context.Writer.BuildScope()) - { - Context.Writer - .Write("if (") - .Write(backedScopeManageVariableName) - .WriteLine(" == null)"); - - using (Context.Writer.BuildScope()) - { - Context.Writer - .WriteStartAssignment(backedScopeManageVariableName) - .WriteStartNewObject("Microsoft.AspNetCore.Razor.Runtime.TagHelpers.TagHelperScopeManager" /* ORIGINAL: ScopeManagerTypeName */) - .Write("StartTagHelperWritingScope" /* ORIGINAL: StartTagHelperWritingScopeMethodName */) - .WriteParameterSeparator() - .Write("EndTagHelperWritingScope" /* ORIGINAL: EndTagHelperWritingScopeMethodName */) - .WriteEndMethodInvocation(); - } - - Context.Writer.WriteReturn(backedScopeManageVariableName); - } - } - - foreach (var tagHelperTypeName in node.UsedTagHelperTypeNames) - { - var tagHelperVariableName = GetTagHelperVariableName(tagHelperTypeName); - Context.Writer - .Write("private global::") - .WriteVariableDeclaration( - tagHelperTypeName, - tagHelperVariableName, - value: null); - } - } - - private void RenderTagHelperAttributeInline( - RazorIRNode node, - SourceSpan documentLocation) - { - if (node is SetTagHelperPropertyIRNode || node is CSharpExpressionIRNode) - { - for (var i = 0; i < node.Children.Count; i++) - { - RenderTagHelperAttributeInline(node.Children[i], documentLocation); - } - } - else if (node is HtmlContentIRNode) - { - Context.Writer.Write(((HtmlContentIRNode)node).Content); - } - else if (node is CSharpTokenIRNode) - { - Context.Writer.Write(((CSharpTokenIRNode)node).Content); - } - else if (node is CSharpStatementIRNode) - { - Context.ErrorSink.OnError( - new SourceLocation(documentLocation.AbsoluteIndex, documentLocation.CharacterIndex, documentLocation.Length), - LegacyResources.TagHelpers_CodeBlocks_NotSupported_InAttributes, - documentLocation.Length); - } - else if (node is TemplateIRNode) - { - var attributeValueNode = (SetTagHelperPropertyIRNode)node.Parent; - Context.ErrorSink.OnError( - new SourceLocation(documentLocation.AbsoluteIndex, documentLocation.CharacterIndex, documentLocation.Length), - LegacyResources.FormatTagHelpers_InlineMarkupBlocks_NotSupported_InAttributes(attributeValueNode.Descriptor.TypeName), - documentLocation.Length); - } - } - } - } -} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/DocumentClassifierPassBase.cs b/src/Microsoft.AspNetCore.Razor.Evolution/DocumentClassifierPassBase.cs index f95cd4a03e..2d306aae96 100644 --- a/src/Microsoft.AspNetCore.Razor.Evolution/DocumentClassifierPassBase.cs +++ b/src/Microsoft.AspNetCore.Razor.Evolution/DocumentClassifierPassBase.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using Microsoft.AspNetCore.Razor.Evolution.Intermediate; +using Microsoft.AspNetCore.Razor.Evolution.CodeGeneration; namespace Microsoft.AspNetCore.Razor.Evolution { @@ -25,6 +26,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution } irDocument.DocumentKind = DocumentKind; + irDocument.Target = CreateTarget(codeDocument, irDocument.Options); Rewrite(codeDocument, irDocument); @@ -82,6 +84,16 @@ namespace Microsoft.AspNetCore.Razor.Evolution protected abstract bool IsMatch(RazorCodeDocument codeDocument, DocumentIRNode irDocument); + private RuntimeTarget CreateTarget(RazorCodeDocument codeDocument, RazorParserOptions options) + { + return RuntimeTarget.CreateDefault(codeDocument, options, (builder) => ConfigureTarget(codeDocument, builder)); + } + + protected virtual void ConfigureTarget(RazorCodeDocument codeDocument, IRuntimeTargetBuilder builder) + { + // Intentionally empty. + } + protected virtual void OnDocumentStructureCreated( RazorCodeDocument codeDocument, NamespaceDeclarationIRNode @namespace, diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/DocumentIRNode.cs b/src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/DocumentIRNode.cs index 86708cad08..ccd745f436 100644 --- a/src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/DocumentIRNode.cs +++ b/src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/DocumentIRNode.cs @@ -3,7 +3,7 @@ using System; using System.Collections.Generic; -using Microsoft.AspNetCore.Razor.Evolution.Legacy; +using Microsoft.AspNetCore.Razor.Evolution.CodeGeneration; namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate { @@ -13,10 +13,14 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate public string DocumentKind { get; set; } + public RazorParserOptions Options { get; set; } + public override RazorIRNode Parent { get; set; } public override SourceSpan? Source { get; set; } + public RuntimeTarget Target { get; set; } + public override void Accept(RazorIRNodeVisitor visitor) { if (visitor == null) diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/Properties/Resources.Designer.cs b/src/Microsoft.AspNetCore.Razor.Evolution/Properties/Resources.Designer.cs index 3cf3ce4f24..c262bd6f5d 100644 --- a/src/Microsoft.AspNetCore.Razor.Evolution/Properties/Resources.Designer.cs +++ b/src/Microsoft.AspNetCore.Razor.Evolution/Properties/Resources.Designer.cs @@ -202,6 +202,22 @@ namespace Microsoft.AspNetCore.Razor.Evolution return string.Format(CultureInfo.CurrentCulture, GetString("DirectiveDescriptor_BeginOptionalsAlreadyInvoked"), p0); } + /// + /// The document of kind '{0}' does not have a '{1}'. The document classifier must set a value for '{2}'. + /// + internal static string DocumentMissingTarget + { + get { return GetString("DocumentMissingTarget"); } + } + + /// + /// The document of kind '{0}' does not have a '{1}'. The document classifier must set a value for '{2}'. + /// + internal static string FormatDocumentMissingTarget(object p0, object p1, object p2) + { + return string.Format(CultureInfo.CurrentCulture, GetString("DocumentMissingTarget"), p0, p1, p2); + } + private static string GetString(string name, params string[] formatterNames) { var value = _resourceManager.GetString(name); diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/RazorCSharpLoweringPhaseBase.cs b/src/Microsoft.AspNetCore.Razor.Evolution/RazorCSharpLoweringPhaseBase.cs deleted file mode 100644 index b6f6eacdae..0000000000 --- a/src/Microsoft.AspNetCore.Razor.Evolution/RazorCSharpLoweringPhaseBase.cs +++ /dev/null @@ -1,358 +0,0 @@ -// 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.Intermediate; -using Microsoft.AspNetCore.Razor.Evolution.Legacy; - -namespace Microsoft.AspNetCore.Razor.Evolution -{ - internal abstract class RazorCSharpLoweringPhaseBase : RazorEnginePhaseBase, IRazorCSharpLoweringPhase - { - protected 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); - } - } - } - - protected static int CalculateExpressionPadding(SourceSpan 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; - } - - protected static string BuildOffsetPadding(int generatedOffset, SourceSpan sourceRange, CSharpRenderingContext context) - { - 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); - } - } - - protected static string GetTagHelperVariableName(string tagHelperTypeName) => "__" + tagHelperTypeName.Replace('.', '_'); - - protected static string GetTagHelperPropertyAccessor( - string tagHelperVariableName, - string attributeName, - TagHelperAttributeDescriptor descriptor) - { - var propertyAccessor = $"{tagHelperVariableName}.{descriptor.PropertyName}"; - - if (descriptor.IsIndexer) - { - var dictionaryKey = attributeName.Substring(descriptor.Name.Length); - propertyAccessor += $"[\"{dictionaryKey}\"]"; - } - - return propertyAccessor; - } - - protected class LinePragmaWriter : IDisposable - { - private readonly CSharpCodeWriter _writer; - private readonly int _startIndent; - - public LinePragmaWriter(CSharpCodeWriter writer, SourceSpan 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); - } - } - - protected 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 */; - } - - protected 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 */; - } - - protected class TagHelperHtmlAttributeRenderingConventions : CSharpRenderingConventions - { - public TagHelperHtmlAttributeRenderingConventions(CSharpCodeWriter writer) : base(writer) - { - } - - public override string StartWriteAttributeValueMethod => "AddHtmlAttributeValue(" /* ORIGINAL: AddHtmlAttributeValueMethodName */; - } - - protected class CSharpLiteralCodeConventions : CSharpRenderingConventions - { - public CSharpLiteralCodeConventions(CSharpCodeWriter writer) : base(writer) - { - } - - public override string StartWriteMethod => StartWriteLiteralMethod; - } - - protected class CSharpRenderingContext - { - private CSharpRenderingConventions _renderingConventions; - - public ICollection Directives { get; set; } - - public Func IdGenerator { get; set; } = () => Guid.NewGuid().ToString("N"); - - public List LineMappings { get; } = new List(); - - public CSharpCodeWriter Writer { get; set; } - - public CSharpRenderingConventions RenderingConventions - { - get - { - if (_renderingConventions == null) - { - _renderingConventions = new CSharpRenderingConventions(Writer); - } - - return _renderingConventions; - } - set - { - _renderingConventions = value; - } - } - - public ErrorSink ErrorSink { get; } = new ErrorSink(); - - public RazorSourceDocument SourceDocument { get; set; } - - public RazorParserOptions Options { get; set; } - - public TagHelperRenderingContext TagHelperRenderingContext { get; set; } - } - - protected class TagHelperRenderingContext - { - private Dictionary _renderedBoundAttributes; - private HashSet _verifiedPropertyDictionaries; - - public Dictionary RenderedBoundAttributes - { - get - { - if (_renderedBoundAttributes == null) - { - _renderedBoundAttributes = new Dictionary(StringComparer.OrdinalIgnoreCase); - } - - return _renderedBoundAttributes; - } - } - - public HashSet VerifiedPropertyDictionaries - { - get - { - if (_verifiedPropertyDictionaries == null) - { - _verifiedPropertyDictionaries = new HashSet(StringComparer.Ordinal); - } - - return _verifiedPropertyDictionaries; - } - } - } - - protected 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"); - - Context.Writer - .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); - } - } - } - } -} diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/RazorEngine.cs b/src/Microsoft.AspNetCore.Razor.Evolution/RazorEngine.cs index 31f3673757..335a780f3c 100644 --- a/src/Microsoft.AspNetCore.Razor.Evolution/RazorEngine.cs +++ b/src/Microsoft.AspNetCore.Razor.Evolution/RazorEngine.cs @@ -52,6 +52,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()); @@ -65,15 +66,11 @@ namespace Microsoft.AspNetCore.Razor.Evolution internal static void AddRuntimeDefaults(IRazorEngineBuilder builder) { - builder.Phases.Add(new DefaultRazorRuntimeCSharpLoweringPhase()); - builder.Features.Add(new RazorPreallocatedTagHelperAttributeOptimizationPass()); } internal static void AddDesignTimeDefaults(IRazorEngineBuilder builder) { - builder.Phases.Add(new DefaultRazorDesignTimeCSharpLoweringPhase()); - builder.Features.Add(new ConfigureDesignTimeOptions()); builder.Features.Add(new RazorDesignTimeIRPass()); } diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/Resources.resx b/src/Microsoft.AspNetCore.Razor.Evolution/Resources.resx index 9b0aa5e104..d19670778f 100644 --- a/src/Microsoft.AspNetCore.Razor.Evolution/Resources.resx +++ b/src/Microsoft.AspNetCore.Razor.Evolution/Resources.resx @@ -153,4 +153,7 @@ The method '{0}' has already been invoked. + + The document of kind '{0}' does not have a '{1}'. The document classifier must set a value for '{2}'. + \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Razor.Evolution.Test/CodeGeneration/DefaultRuntimeTargetBuilderTest.cs b/test/Microsoft.AspNetCore.Razor.Evolution.Test/CodeGeneration/DefaultRuntimeTargetBuilderTest.cs new file mode 100644 index 0000000000..58f993855b --- /dev/null +++ b/test/Microsoft.AspNetCore.Razor.Evolution.Test/CodeGeneration/DefaultRuntimeTargetBuilderTest.cs @@ -0,0 +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 Xunit; + +namespace Microsoft.AspNetCore.Razor.Evolution.CodeGeneration +{ + public class DefaultRuntimeTargetBuilderTest + { + [Fact] + public void Build_CreatesDefaultRuntimeTarget() + { + // Arrange + var codeDocument = TestRazorCodeDocument.CreateEmpty(); + var options = RazorParserOptions.CreateDefaultOptions(); + + var builder = new DefaultRuntimeTargetBuilder(codeDocument, options); + + // Act + var target = builder.Build(); + + // Assert + Assert.IsType(target); + } + } +} diff --git a/test/Microsoft.AspNetCore.Razor.Evolution.Test/CodeGeneration/DefaultRuntimeTargetTest.cs b/test/Microsoft.AspNetCore.Razor.Evolution.Test/CodeGeneration/DefaultRuntimeTargetTest.cs new file mode 100644 index 0000000000..724c4a8e94 --- /dev/null +++ b/test/Microsoft.AspNetCore.Razor.Evolution.Test/CodeGeneration/DefaultRuntimeTargetTest.cs @@ -0,0 +1,42 @@ +// 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 Xunit; + +namespace Microsoft.AspNetCore.Razor.Evolution.CodeGeneration +{ + public class DefaultRuntimeTargetTest + { + [Fact] + public void CreateRenderer_DesignTime_CreatesDesignTimeRenderer() + { + // Arrange + var options = RazorParserOptions.CreateDefaultOptions(); + options.DesignTimeMode = true; + + var target = new DefaultRuntimeTarget(options); + + // Act + var renderer = target.CreateRenderer(new CSharpRenderingContext()); + + // Assert + Assert.IsType(renderer); + } + + [Fact] + public void CreateRenderer_Runtime_CreatesRuntimeRenderer() + { + // Arrange + var options = RazorParserOptions.CreateDefaultOptions(); + options.DesignTimeMode = false; + + var target = new DefaultRuntimeTarget(options); + + // Act + var renderer = target.CreateRenderer(new CSharpRenderingContext()); + + // Assert + Assert.IsType(renderer); + } + } +} diff --git a/test/Microsoft.AspNetCore.Razor.Evolution.Test/CodeGeneration/RuntimeTargetTest.cs b/test/Microsoft.AspNetCore.Razor.Evolution.Test/CodeGeneration/RuntimeTargetTest.cs new file mode 100644 index 0000000000..166ecc128b --- /dev/null +++ b/test/Microsoft.AspNetCore.Razor.Evolution.Test/CodeGeneration/RuntimeTargetTest.cs @@ -0,0 +1,68 @@ +// 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 Xunit; + +namespace Microsoft.AspNetCore.Razor.Evolution.CodeGeneration +{ + public class RuntimeTargetTest + { + [Fact] + public void CreateDefault_CreatesDefaultRuntimeTarget() + { + // Arrange + var codeDocument = TestRazorCodeDocument.CreateEmpty(); + var options = RazorParserOptions.CreateDefaultOptions(); + + // Act + var target = RuntimeTarget.CreateDefault(codeDocument, options); + + // Assert + Assert.IsType(target); + } + + [Fact] + public void CreateDefault_CallsDelegate() + { + // Arrange + var wasCalled = false; + Action @delegate = (b) => { wasCalled = true; }; + + var codeDocument = TestRazorCodeDocument.CreateEmpty(); + var options = RazorParserOptions.CreateDefaultOptions(); + + // Act + RuntimeTarget.CreateDefault(codeDocument, options, @delegate); + + // Assert + Assert.True(wasCalled); + } + + [Fact] + public void CreateDefault_AllowsNullDelegate() + { + // Arrange + var codeDocument = TestRazorCodeDocument.CreateEmpty(); + var options = RazorParserOptions.CreateDefaultOptions(); + + // Act + RuntimeTarget.CreateDefault(codeDocument, options, configure: null); + + // Assert (does not throw) + } + + [Fact] + public void CreateEmpty_AllowsNullDelegate() + { + // Arrange + var codeDocument = TestRazorCodeDocument.CreateEmpty(); + var options = RazorParserOptions.CreateDefaultOptions(); + + // Act + RuntimeTarget.CreateDefault(codeDocument, options, configure: null); + + // Assert (does not throw) + } + } +} diff --git a/test/Microsoft.AspNetCore.Razor.Evolution.Test/DefaultDocumentClassifierPassTest.cs b/test/Microsoft.AspNetCore.Razor.Evolution.Test/DefaultDocumentClassifierPassTest.cs index c3eaf3f2b6..10e5c072c2 100644 --- a/test/Microsoft.AspNetCore.Razor.Evolution.Test/DefaultDocumentClassifierPassTest.cs +++ b/test/Microsoft.AspNetCore.Razor.Evolution.Test/DefaultDocumentClassifierPassTest.cs @@ -19,6 +19,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution var irDocument = new DocumentIRNode() { DocumentKind = "ignore", + Options = RazorParserOptions.CreateDefaultOptions(), }; var pass = new DefaultDocumentClassifierPass(); @@ -36,7 +37,10 @@ namespace Microsoft.AspNetCore.Razor.Evolution public void Execute_CreatesClassStructure() { // Arrange - var irDocument = new DocumentIRNode(); + var irDocument = new DocumentIRNode() + { + Options = RazorParserOptions.CreateDefaultOptions(), + }; var pass = new DefaultDocumentClassifierPass(); pass.Engine = RazorEngine.CreateEmpty(b =>{ }); diff --git a/test/Microsoft.AspNetCore.Razor.Evolution.Test/DefaultRazorCSharpLoweringPhaseTest.cs b/test/Microsoft.AspNetCore.Razor.Evolution.Test/DefaultRazorCSharpLoweringPhaseTest.cs new file mode 100644 index 0000000000..b9f0dfb13e --- /dev/null +++ b/test/Microsoft.AspNetCore.Razor.Evolution.Test/DefaultRazorCSharpLoweringPhaseTest.cs @@ -0,0 +1,79 @@ +// 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; +using Microsoft.AspNetCore.Razor.Evolution.Legacy; +using Microsoft.AspNetCore.Razor.Evolution.CodeGeneration; + +namespace Microsoft.AspNetCore.Razor.Evolution +{ + public class DefaultRazorCSharpLoweringPhaseTest + { + [Fact] + public void Execute_ThrowsForMissingDependency_IRDocument() + { + // Arrange + var phase = new DefaultRazorCSharpLoweringPhase(); + + var engine = RazorEngine.CreateEmpty(b => b.Phases.Add(phase)); + + var codeDocument = TestRazorCodeDocument.CreateEmpty(); + + codeDocument.SetSyntaxTree(RazorSyntaxTree.Parse(codeDocument.Source)); + + // Act & Assert + ExceptionAssert.Throws( + () => phase.Execute(codeDocument), + $"The '{nameof(DefaultRazorCSharpLoweringPhase)}' phase requires a '{nameof(DocumentIRNode)}' " + + $"provided by the '{nameof(RazorCodeDocument)}'."); + } + + [Fact] + public void Execute_ThrowsForMissingDependency_SyntaxTree() + { + // Arrange + var phase = new DefaultRazorCSharpLoweringPhase(); + + var engine = RazorEngine.CreateEmpty(b => b.Phases.Add(phase)); + + var codeDocument = TestRazorCodeDocument.CreateEmpty(); + + var irDocument = new DocumentIRNode(); + codeDocument.SetIRDocument(irDocument); + + // Act & Assert + ExceptionAssert.Throws( + () => phase.Execute(codeDocument), + $"The '{nameof(DefaultRazorCSharpLoweringPhase)}' phase requires a '{nameof(RazorSyntaxTree)}' " + + $"provided by the '{nameof(RazorCodeDocument)}'."); + } + + [Fact] + public void Execute_ThrowsForMissingDependency_RuntimeTarget() + { + // Arrange + var phase = new DefaultRazorCSharpLoweringPhase(); + + var engine = RazorEngine.CreateEmpty(b => b.Phases.Add(phase)); + + var codeDocument = TestRazorCodeDocument.CreateEmpty(); + + codeDocument.SetSyntaxTree(RazorSyntaxTree.Parse(codeDocument.Source)); + + var irDocument = new DocumentIRNode() + { + DocumentKind = "test", + }; + codeDocument.SetIRDocument(irDocument); + + // Act & Assert + ExceptionAssert.Throws( + () => phase.Execute(codeDocument), + $"The document of kind 'test' does not have a '{nameof(RuntimeTarget)}'. " + + $"The document classifier must set a value for '{nameof(DocumentIRNode.Target)}'."); + } + } +} diff --git a/test/Microsoft.AspNetCore.Razor.Evolution.Test/Intermediate/DefaultRazorIRLoweringPhaseIntegrationTest.cs b/test/Microsoft.AspNetCore.Razor.Evolution.Test/DefaultRazorIRLoweringPhaseIntegrationTest.cs similarity index 97% rename from test/Microsoft.AspNetCore.Razor.Evolution.Test/Intermediate/DefaultRazorIRLoweringPhaseIntegrationTest.cs rename to test/Microsoft.AspNetCore.Razor.Evolution.Test/DefaultRazorIRLoweringPhaseIntegrationTest.cs index 0e904374df..c6f2d70d60 100644 --- a/test/Microsoft.AspNetCore.Razor.Evolution.Test/Intermediate/DefaultRazorIRLoweringPhaseIntegrationTest.cs +++ b/test/Microsoft.AspNetCore.Razor.Evolution.Test/DefaultRazorIRLoweringPhaseIntegrationTest.cs @@ -3,13 +3,13 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Razor.Evolution.Legacy; +using Microsoft.AspNetCore.Razor.Evolution.Intermediate; using Xunit; using static Microsoft.AspNetCore.Razor.Evolution.Intermediate.RazorIRAssert; -namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate +namespace Microsoft.AspNetCore.Razor.Evolution { public class DefaultRazorIRLoweringPhaseIntegrationTest { @@ -29,6 +29,20 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate n => Using("System.Threading.Tasks", n)); } + [Fact] + public void Lower_SetsOptions() + { + // Arrange + var codeDocument = TestRazorCodeDocument.CreateEmpty(); + + // Act + var irDocument = Lower(codeDocument); + + // Assert + Assert.NotNull(irDocument.Options); + Assert.Same(codeDocument.GetSyntaxTree().Options, irDocument.Options); + } + [Fact] public void Lower_HelloWorld() { diff --git a/test/Microsoft.AspNetCore.Razor.Evolution.Test/DefaultRazorRuntimeCSharpLoweringPhaseTest.cs b/test/Microsoft.AspNetCore.Razor.Evolution.Test/DefaultRazorRuntimeCSharpLoweringPhaseTest.cs deleted file mode 100644 index d189c2cce2..0000000000 --- a/test/Microsoft.AspNetCore.Razor.Evolution.Test/DefaultRazorRuntimeCSharpLoweringPhaseTest.cs +++ /dev/null @@ -1,30 +0,0 @@ -// 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 DefaultRazorRuntimeCSharpLoweringPhaseTest - { - [Fact] - public void Execute_ThrowsForMissingDependency() - { - // Arrange - var phase = new DefaultRazorRuntimeCSharpLoweringPhase(); - - var engine = RazorEngine.CreateEmpty(b => b.Phases.Add(phase)); - - var codeDocument = TestRazorCodeDocument.CreateEmpty(); - - // Act & Assert - ExceptionAssert.Throws( - () => phase.Execute(codeDocument), - $"The '{nameof(DefaultRazorRuntimeCSharpLoweringPhase)}' phase requires a '{nameof(DocumentIRNode)}' " + - $"provided by the '{nameof(RazorCodeDocument)}'."); - } - } -} diff --git a/test/Microsoft.AspNetCore.Razor.Evolution.Test/DocumentClassifierPassBaseTest.cs b/test/Microsoft.AspNetCore.Razor.Evolution.Test/DocumentClassifierPassBaseTest.cs index 459b9f7eb7..7db437817b 100644 --- a/test/Microsoft.AspNetCore.Razor.Evolution.Test/DocumentClassifierPassBaseTest.cs +++ b/test/Microsoft.AspNetCore.Razor.Evolution.Test/DocumentClassifierPassBaseTest.cs @@ -18,6 +18,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution var irDocument = new DocumentIRNode() { DocumentKind = "ignore", + Options = RazorParserOptions.CreateDefaultOptions(), }; var pass = new TestDocumentClassifierPass(); @@ -35,7 +36,10 @@ namespace Microsoft.AspNetCore.Razor.Evolution public void Execute_NoMatch_IgnoresDocument() { // Arrange - var irDocument = new DocumentIRNode(); + var irDocument = new DocumentIRNode() + { + Options = RazorParserOptions.CreateDefaultOptions(), + }; var pass = new TestDocumentClassifierPass() { @@ -55,7 +59,10 @@ namespace Microsoft.AspNetCore.Razor.Evolution public void Execute_Match_SetsDocumentType_AndCreatesStructure() { // Arrange - var irDocument = new DocumentIRNode(); + var irDocument = new DocumentIRNode() + { + Options = RazorParserOptions.CreateDefaultOptions(), + }; var pass = new TestDocumentClassifierPass(); pass.Engine = RazorEngine.CreateEmpty(b => { }); @@ -65,6 +72,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution // Assert Assert.Equal("test", irDocument.DocumentKind); + Assert.NotNull(irDocument.Target); var @namespace = SingleChild(irDocument); var @class = SingleChild(@namespace); @@ -76,7 +84,10 @@ namespace Microsoft.AspNetCore.Razor.Evolution public void Execute_AddsCheckumFirstToDocument() { // Arrange - var irDocument = new DocumentIRNode(); + var irDocument = new DocumentIRNode() + { + Options = RazorParserOptions.CreateDefaultOptions(), + }; var builder = RazorIRBuilder.Create(irDocument); builder.Add(new ChecksumIRNode()); @@ -98,7 +109,10 @@ namespace Microsoft.AspNetCore.Razor.Evolution public void Execute_AddsUsingsToNamespace() { // Arrange - var irDocument = new DocumentIRNode(); + var irDocument = new DocumentIRNode() + { + Options = RazorParserOptions.CreateDefaultOptions(), + }; var builder = RazorIRBuilder.Create(irDocument); builder.Add(new UsingStatementIRNode()); @@ -121,7 +135,10 @@ namespace Microsoft.AspNetCore.Razor.Evolution public void Execute_AddsTagHelperFieldsToClass() { // Arrange - var irDocument = new DocumentIRNode(); + var irDocument = new DocumentIRNode() + { + Options = RazorParserOptions.CreateDefaultOptions(), + }; var builder = RazorIRBuilder.Create(irDocument); builder.Add(new DeclareTagHelperFieldsIRNode()); @@ -145,7 +162,10 @@ namespace Microsoft.AspNetCore.Razor.Evolution public void Execute_AddsTheRestToMethod() { // Arrange - var irDocument = new DocumentIRNode(); + var irDocument = new DocumentIRNode() + { + Options = RazorParserOptions.CreateDefaultOptions(), + }; var builder = RazorIRBuilder.Create(irDocument); builder.Add(new HtmlContentIRNode()); @@ -171,7 +191,10 @@ namespace Microsoft.AspNetCore.Razor.Evolution public void Execute_CanInitializeDefaults() { // Arrange - var irDocument = new DocumentIRNode(); + var irDocument = new DocumentIRNode() + { + Options = RazorParserOptions.CreateDefaultOptions(), + }; var builder = RazorIRBuilder.Create(irDocument); builder.Add(new HtmlContentIRNode()); diff --git a/test/Microsoft.AspNetCore.Razor.Evolution.Test/IntegrationTests/CodeGenerationIntegrationTest.cs b/test/Microsoft.AspNetCore.Razor.Evolution.Test/IntegrationTests/CodeGenerationIntegrationTest.cs index 6d87af8305..38340b8f5e 100644 --- a/test/Microsoft.AspNetCore.Razor.Evolution.Test/IntegrationTests/CodeGenerationIntegrationTest.cs +++ b/test/Microsoft.AspNetCore.Razor.Evolution.Test/IntegrationTests/CodeGenerationIntegrationTest.cs @@ -1486,10 +1486,10 @@ namespace Microsoft.AspNetCore.Razor.Evolution.IntegrationTests TestRazorSourceDocument.CreateResource(sourceFilename, encoding: null, normalizeNewLines: true)); // This will ensure that we're not putting any randomly generated data in a baseline. - codeDocument.Items[DefaultRazorRuntimeCSharpLoweringPhase.SuppressUniqueIds] = "test"; + codeDocument.Items[DefaultRazorCSharpLoweringPhase.SuppressUniqueIds] = "test"; // This is to make tests work cross platform. - codeDocument.Items[DefaultRazorDesignTimeCSharpLoweringPhase.NewLineString] = "\r\n"; + codeDocument.Items[DefaultRazorCSharpLoweringPhase.NewLineString] = "\r\n"; return codeDocument; } diff --git a/test/Microsoft.AspNetCore.Razor.Evolution.Test/IntegrationTests/IntegrationTestBase.cs b/test/Microsoft.AspNetCore.Razor.Evolution.Test/IntegrationTests/IntegrationTestBase.cs index 70fe15188d..59ffa0e4a7 100644 --- a/test/Microsoft.AspNetCore.Razor.Evolution.Test/IntegrationTests/IntegrationTestBase.cs +++ b/test/Microsoft.AspNetCore.Razor.Evolution.Test/IntegrationTests/IntegrationTestBase.cs @@ -105,7 +105,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution.IntegrationTests TestRazorSourceDocument.CreateResource(sourceFilename, encoding: null, normalizeNewLines: true), imports); // This will ensure that we're not putting any randomly generated data in a baseline. - codeDocument.Items[DefaultRazorRuntimeCSharpLoweringPhase.SuppressUniqueIds] = "test"; + codeDocument.Items[DefaultRazorCSharpLoweringPhase.SuppressUniqueIds] = "test"; return codeDocument; } diff --git a/test/Microsoft.AspNetCore.Razor.Evolution.Test/RazorEngineTest.cs b/test/Microsoft.AspNetCore.Razor.Evolution.Test/RazorEngineTest.cs index 6dd8a800d7..e02d6bdaa4 100644 --- a/test/Microsoft.AspNetCore.Razor.Evolution.Test/RazorEngineTest.cs +++ b/test/Microsoft.AspNetCore.Razor.Evolution.Test/RazorEngineTest.cs @@ -152,7 +152,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution phase => Assert.IsType(phase), phase => Assert.IsType(phase), phase => Assert.IsType(phase), - phase => Assert.IsType(phase)); + phase => Assert.IsType(phase)); } private static void AssertDefaultDesignTimeFeatures(IEnumerable features) @@ -176,7 +176,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution phase => Assert.IsType(phase), phase => Assert.IsType(phase), phase => Assert.IsType(phase), - phase => Assert.IsType(phase)); + phase => Assert.IsType(phase)); } } } \ No newline at end of file