From 0dbf62196caf49431aaec465bc280ef8cf6c8d9e Mon Sep 17 00:00:00 2001 From: Ryan Nowak Date: Fri, 10 Feb 2017 12:26:39 -0800 Subject: [PATCH] Introduces RuntimeTarget and abstraction for APIs This is a new abstraction that represents the api surface available for codegen to target. Every kind of document should have an associated RuntimeTarget or just use the default. To prevent breakage, our DocumentClassifierBase class will provide a default API set to implementors (like MVC). I haven't fundamentally changed how codegen is done yet, I've just hidden it behind a new abstraction. The RuntimeTarget now is also responsible for selecting between design time and runtime. The bulk of the noise here is from splitting a lot of the codegen stuff into its own files. --- .../CSharpLiteralCodeConventions.cs | 16 + .../CSharpRedirectRenderingConventions.cs | 28 + .../CodeGeneration/CSharpRenderingContext.cs | 47 ++ .../CSharpRenderingConventions.cs | 27 + .../CodeGeneration/DefaultRuntimeTarget.cs | 39 + .../DefaultRuntimeTargetBuilder.cs | 25 + .../DesignTimeCSharpRenderer.cs | 355 +++++++++ .../CodeGeneration/IRuntimeTargetBuilder.cs | 14 + .../CodeGeneration/IRuntimeTargetExtension.cs | 9 + .../CodeGeneration/LinePragmaWriter.cs | 49 ++ .../PageStructureCSharpRenderer.cs | 184 +++++ .../CodeGeneration/RuntimeCSharpRenderer.cs | 695 +++++++++++++++++ .../CodeGeneration/RuntimeTarget.cs | 95 +++ ...HelperHtmlAttributeRenderingConventions.cs | 16 + .../TagHelperRenderingContext.cs | 40 + .../DefaultDirectiveIRPass.cs | 5 +- .../DefaultRazorCSharpLoweringPhase.cs | 72 ++ ...faultRazorDesignTimeCSharpLoweringPhase.cs | 397 ---------- .../DefaultRazorIRLoweringPhase.cs | 3 + .../DefaultRazorRuntimeCSharpLoweringPhase.cs | 734 ------------------ .../DocumentClassifierPassBase.cs | 12 + .../Intermediate/DocumentIRNode.cs | 6 +- .../Properties/Resources.Designer.cs | 16 + .../RazorCSharpLoweringPhaseBase.cs | 358 --------- .../RazorEngine.cs | 5 +- .../Resources.resx | 3 + .../DefaultRuntimeTargetBuilderTest.cs | 26 + .../DefaultRuntimeTargetTest.cs | 42 + .../CodeGeneration/RuntimeTargetTest.cs | 68 ++ .../DefaultDocumentClassifierPassTest.cs | 6 +- .../DefaultRazorCSharpLoweringPhaseTest.cs | 79 ++ ...aultRazorIRLoweringPhaseIntegrationTest.cs | 18 +- ...aultRazorRuntimeCSharpLoweringPhaseTest.cs | 30 - .../DocumentClassifierPassBaseTest.cs | 37 +- .../CodeGenerationIntegrationTest.cs | 4 +- .../IntegrationTests/IntegrationTestBase.cs | 2 +- .../RazorEngineTest.cs | 4 +- 37 files changed, 2023 insertions(+), 1543 deletions(-) create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/CSharpLiteralCodeConventions.cs create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/CSharpRedirectRenderingConventions.cs create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/CSharpRenderingContext.cs create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/CSharpRenderingConventions.cs create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/DefaultRuntimeTarget.cs create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/DefaultRuntimeTargetBuilder.cs create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/DesignTimeCSharpRenderer.cs create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/IRuntimeTargetBuilder.cs create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/IRuntimeTargetExtension.cs create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/LinePragmaWriter.cs create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/PageStructureCSharpRenderer.cs create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/RuntimeCSharpRenderer.cs create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/RuntimeTarget.cs create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/TagHelperHtmlAttributeRenderingConventions.cs create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/TagHelperRenderingContext.cs create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/DefaultRazorCSharpLoweringPhase.cs delete mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/DefaultRazorDesignTimeCSharpLoweringPhase.cs delete mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/DefaultRazorRuntimeCSharpLoweringPhase.cs delete mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/RazorCSharpLoweringPhaseBase.cs create mode 100644 test/Microsoft.AspNetCore.Razor.Evolution.Test/CodeGeneration/DefaultRuntimeTargetBuilderTest.cs create mode 100644 test/Microsoft.AspNetCore.Razor.Evolution.Test/CodeGeneration/DefaultRuntimeTargetTest.cs create mode 100644 test/Microsoft.AspNetCore.Razor.Evolution.Test/CodeGeneration/RuntimeTargetTest.cs create mode 100644 test/Microsoft.AspNetCore.Razor.Evolution.Test/DefaultRazorCSharpLoweringPhaseTest.cs rename test/Microsoft.AspNetCore.Razor.Evolution.Test/{Intermediate => }/DefaultRazorIRLoweringPhaseIntegrationTest.cs (97%) delete mode 100644 test/Microsoft.AspNetCore.Razor.Evolution.Test/DefaultRazorRuntimeCSharpLoweringPhaseTest.cs 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