From 966cd4a68dd6a84a83fd6e987d7f37b53f76c962 Mon Sep 17 00:00:00 2001 From: Ryan Nowak Date: Fri, 3 Mar 2017 15:00:21 -0800 Subject: [PATCH] Introducing BasicWriter and TagHelperWriter These are the replacements for CSharpRenderingConventions --- .../CodeGeneration/BasicWriter.cs | 18 +++ .../CodeGeneration/CSharpRenderingContext.cs | 99 ++++++++++++++ .../CodeGeneration/DefaultBasicWriter.cs | 129 ++++++++++++++++++ .../CodeGeneration/DefaultDocumentWriter.cs | 108 +++++++++++++++ .../CodeGeneration/DefaultTagHelperWriter.cs | 36 +++++ .../DesignTimeCSharpRenderer.cs | 1 + .../PageStructureCSharpRenderer.cs | 94 +------------ .../CodeGeneration/RedirectedBasicWriter.cs | 76 +++++++++++ .../CodeGeneration/RuntimeCSharpRenderer.cs | 3 +- .../CodeGeneration/TagHelperWriter.cs | 20 +++ .../CodeGeneration/TemplateTargetExtension.cs | 9 +- .../Legacy/CSharpCodeWriter.cs | 60 ++++++++ .../TemplateTargetExtensionTest.cs | 1 + 13 files changed, 559 insertions(+), 95 deletions(-) create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/BasicWriter.cs create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/DefaultBasicWriter.cs create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/DefaultTagHelperWriter.cs create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/RedirectedBasicWriter.cs create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/TagHelperWriter.cs diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/BasicWriter.cs b/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/BasicWriter.cs new file mode 100644 index 0000000000..869c5ef662 --- /dev/null +++ b/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/BasicWriter.cs @@ -0,0 +1,18 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.AspNetCore.Razor.Evolution.Intermediate; + +namespace Microsoft.AspNetCore.Razor.Evolution.CodeGeneration +{ + public abstract class BasicWriter + { + public abstract void WriteCSharpExpression(CSharpRenderingContext context, CSharpExpressionIRNode node); + + public abstract void WriteCSharpStatement(CSharpRenderingContext context, CSharpStatementIRNode node); + + public abstract void WriteHtmlContent(CSharpRenderingContext context, HtmlContentIRNode node); + + public abstract void WriteHtmlAttribute(CSharpRenderingContext context, HtmlAttributeIRNode node); + } +} diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/CSharpRenderingContext.cs b/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/CSharpRenderingContext.cs index f6f66ed3af..3691507a50 100644 --- a/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/CSharpRenderingContext.cs +++ b/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/CSharpRenderingContext.cs @@ -46,5 +46,104 @@ namespace Microsoft.AspNetCore.Razor.Evolution.CodeGeneration internal TagHelperRenderingContext TagHelperRenderingContext { get; set; } internal Action RenderChildren { get; set; } + + internal Action RenderNode { get; set; } + + public BasicWriter BasicWriter { get; set; } + + public TagHelperWriter TagHelperWriter { get; set; } + + public void AddLineMappingFor(RazorIRNode node) + { + if (node.Source == null) + { + return; + } + + var source = node.Source.Value; + + var generatedLocation = new SourceSpan(Writer.GetCurrentSourceLocation(), source.Length); + var lineMapping = new LineMapping(source, generatedLocation); + + LineMappings.Add(lineMapping); + } + + public BasicWriterScope Push(BasicWriter writer) + { + if (writer == null) + { + throw new ArgumentNullException(nameof(writer)); + } + + var scope = new BasicWriterScope(this, BasicWriter); + BasicWriter = writer; + return scope; + } + + public TagHelperWriterScope Push(TagHelperWriter writer) + { + if (writer == null) + { + throw new ArgumentNullException(nameof(writer)); + } + + var scope = new TagHelperWriterScope(this, BasicWriter); + TagHelperWriter = writer; + return scope; + } + + public struct BasicWriterScope : IDisposable + { + private readonly CSharpRenderingContext _context; + private readonly BasicWriter _writer; + + public BasicWriterScope(CSharpRenderingContext context, BasicWriter writer) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (writer == null) + { + throw new ArgumentNullException(nameof(writer)); + } + + _context = context; + _writer = writer; + } + + public void Dispose() + { + _context.BasicWriter = _writer; + } + } + + public struct TagHelperWriterScope : IDisposable + { + private readonly CSharpRenderingContext _context; + private readonly BasicWriter _writer; + + public TagHelperWriterScope(CSharpRenderingContext context, BasicWriter writer) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (writer == null) + { + throw new ArgumentNullException(nameof(writer)); + } + + _context = context; + _writer = writer; + } + + public void Dispose() + { + _context.BasicWriter = _writer; + } + } } } diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/DefaultBasicWriter.cs b/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/DefaultBasicWriter.cs new file mode 100644 index 0000000000..f9e0dec4de --- /dev/null +++ b/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/DefaultBasicWriter.cs @@ -0,0 +1,129 @@ +// 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 +{ + public class DefaultBasicWriter : BasicWriter + { + public string WriteCSharpExpressionMethod { get; set; } = "Write"; + + public override void WriteCSharpExpression(CSharpRenderingContext context, CSharpExpressionIRNode node) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (node == null) + { + throw new ArgumentNullException(nameof(node)); + } + + if (context.Options.DesignTimeMode) + { + WriteCSharpExpressionDesignTime(context, node); + } + else + { + WriteCSharpExpressionRuntime(context, node); + } + } + + public override void WriteCSharpStatement(CSharpRenderingContext context, CSharpStatementIRNode node) + { + throw new NotImplementedException(); + } + + public override void WriteHtmlAttribute(CSharpRenderingContext context, HtmlAttributeIRNode node) + { + throw new NotImplementedException(); + } + + public override void WriteHtmlContent(CSharpRenderingContext context, HtmlContentIRNode node) + { + throw new NotImplementedException(); + } + + protected void WriteCSharpExpressionRuntime(CSharpRenderingContext context, CSharpExpressionIRNode node) + { + IDisposable linePragmaScope = null; + if (node.Source != null) + { + linePragmaScope = context.Writer.BuildLinePragma(node.Source.Value); + context.Writer.WritePadding(WriteCSharpExpressionMethod.Length + 1, node.Source, context); + } + + context.Writer.WriteStartMethodInvocation(WriteCSharpExpressionMethod); + + for (var i = 0; i < node.Children.Count; i++) + { + if (node.Children[i] is RazorIRToken token && token.IsCSharp) + { + context.Writer.Write(token.Content); + } + else + { + // There may be something else inside the expression like a Template or another extension node. + context.RenderNode(node.Children[i]); + } + } + + context.Writer.WriteEndMethodInvocation(); + + linePragmaScope?.Dispose(); + } + + protected void WriteCSharpExpressionDesignTime(CSharpRenderingContext context, CSharpExpressionIRNode node) + { + if (node.Children.Count == 0) + { + return; + } + + if (node.Source != null) + { + using (context.Writer.BuildLinePragma(node.Source.Value)) + { + context.Writer.WritePadding(RazorDesignTimeIRPass.DesignTimeVariable.Length, node.Source, context); + context.Writer.WriteStartAssignment(RazorDesignTimeIRPass.DesignTimeVariable); + + for (var i = 0; i < node.Children.Count; i++) + { + if (node.Children[i] is RazorIRToken token && token.IsCSharp) + { + context.AddLineMappingFor(token); + context.Writer.Write(token.Content); + } + else + { + // There may be something else inside the expression like a Template or another extension node. + context.RenderNode(node.Children[i]); + } + } + + context.Writer.WriteLine(";"); + } + } + else + { + context.Writer.WriteStartAssignment(RazorDesignTimeIRPass.DesignTimeVariable); + for (var i = 0; i < node.Children.Count; i++) + { + if (node.Children[i] is RazorIRToken token && token.IsCSharp) + { + context.Writer.Write(token.Content); + } + else + { + // There may be something else inside the expression like a Template or another extension node. + context.RenderNode(node.Children[i]); + } + } + context.Writer.WriteLine(";"); + } + } + } +} diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/DefaultDocumentWriter.cs b/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/DefaultDocumentWriter.cs index 2fde3666a3..9c42327708 100644 --- a/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/DefaultDocumentWriter.cs +++ b/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/DefaultDocumentWriter.cs @@ -44,6 +44,10 @@ namespace Microsoft.AspNetCore.Razor.Evolution.CodeGeneration var visitor = new Visitor(_target, _context, _renderer); _context.RenderChildren = visitor.RenderChildren; + _context.RenderNode = visitor.Visit; + + _context.BasicWriter = new DefaultBasicWriter(); + _context.TagHelperWriter = new DefaultTagHelperWriter(); visitor.VisitDocument(node); _context.RenderChildren = null; @@ -63,6 +67,8 @@ namespace Microsoft.AspNetCore.Razor.Evolution.CodeGeneration _renderer = renderer; } + private CSharpRenderingContext Context => _context; + public void RenderChildren(RazorIRNode node) { for (var i = 0; i < node.Children.Count; i++) @@ -77,6 +83,108 @@ namespace Microsoft.AspNetCore.Razor.Evolution.CodeGeneration RenderChildren(node); } + public override void VisitNamespace(NamespaceDeclarationIRNode node) + { + Context.Writer + .Write("namespace ") + .WriteLine(node.Content); + + using (Context.Writer.BuildScope()) + { + Context.Writer.WriteLineHiddenDirective(); + RenderChildren(node); + } + } + + 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()) + { + RenderChildren(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()) + { + RenderChildren(node); + } + + Context.Writer.WriteLine("#pragma warning restore 1998"); + } + + public override void VisitExtension(ExtensionIRNode node) + { + node.WriteNode(_target, Context); + } + + public override void VisitCSharpExpression(CSharpExpressionIRNode node) + { + Context.BasicWriter.WriteCSharpExpression(Context, node); + } + public override void VisitDefault(RazorIRNode node) { // This is a temporary bridge to the renderer, which allows us to move functionality piecemeal diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/DefaultTagHelperWriter.cs b/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/DefaultTagHelperWriter.cs new file mode 100644 index 0000000000..17bc154e40 --- /dev/null +++ b/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/DefaultTagHelperWriter.cs @@ -0,0 +1,36 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNetCore.Razor.Evolution.Intermediate; + +namespace Microsoft.AspNetCore.Razor.Evolution.CodeGeneration +{ + public class DefaultTagHelperWriter : TagHelperWriter + { + public override void WriteAddTagHelperHtmlAttribute(CSharpRenderingContext context, AddTagHelperHtmlAttributeIRNode node) + { + throw new NotImplementedException(); + } + + public override void WriteCreateTagHelper(CSharpRenderingContext context, CreateTagHelperIRNode node) + { + throw new NotImplementedException(); + } + + public override void WriteExecuteTagHelpers(CSharpRenderingContext context, ExecuteTagHelpersIRNode node) + { + throw new NotImplementedException(); + } + + public override void WriteInitializeTagHelperStructure(CSharpRenderingContext context, InitializeTagHelperStructureIRNode node) + { + throw new NotImplementedException(); + } + + public override void WriteSetTagHelperProperty(CSharpRenderingContext context, SetTagHelperPropertyIRNode node) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/DesignTimeCSharpRenderer.cs b/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/DesignTimeCSharpRenderer.cs index ac12c435bd..070855fccb 100644 --- a/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/DesignTimeCSharpRenderer.cs +++ b/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/DesignTimeCSharpRenderer.cs @@ -17,6 +17,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution.CodeGeneration public override void VisitCSharpExpression(CSharpExpressionIRNode node) { + // We can't remove this yet, because it's still used recursively in a few places. if (node.Children.Count == 0) { return; diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/PageStructureCSharpRenderer.cs b/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/PageStructureCSharpRenderer.cs index 218668a645..756bcf2269 100644 --- a/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/PageStructureCSharpRenderer.cs +++ b/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/PageStructureCSharpRenderer.cs @@ -17,100 +17,10 @@ namespace Microsoft.AspNetCore.Razor.Evolution.CodeGeneration Target = target; } - 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); - } - } - public override void VisitExtension(ExtensionIRNode node) { + // This needs to stay here until the rest of the code in the renderers is rewritten because + // and extension can occur at any level. node.WriteNode(Target, Context); } diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/RedirectedBasicWriter.cs b/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/RedirectedBasicWriter.cs new file mode 100644 index 0000000000..5e53f51232 --- /dev/null +++ b/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/RedirectedBasicWriter.cs @@ -0,0 +1,76 @@ +// 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 RedirectedBasicWriter : BasicWriter + { + private readonly BasicWriter _previous; + private readonly string _textWriter; + + public RedirectedBasicWriter(BasicWriter previous, string textWriter) + { + _previous = previous; + _textWriter = textWriter; + } + + public string WriteCSharpExpressionMethod { get; set; } = "WriteTo"; + + public override void WriteCSharpExpression(CSharpRenderingContext context, CSharpExpressionIRNode node) + { + if (context.Options.DesignTimeMode) + { + _previous.WriteCSharpExpression(context, node); + return; + } + + IDisposable linePragmaScope = null; + if (node.Source != null) + { + linePragmaScope = context.Writer.BuildLinePragma(node.Source.Value); + + var offset = WriteCSharpExpressionMethod.Length + "(".Length + _textWriter.Length + ", ".Length; + context.Writer.WritePadding(offset, node.Source, context); + } + + context.Writer.WriteStartMethodInvocation(WriteCSharpExpressionMethod); + context.Writer.Write(_textWriter); + context.Writer.WriteParameterSeparator(); + + for (var i = 0; i < node.Children.Count; i++) + { + if (node.Children[i] is RazorIRToken token && token.IsCSharp) + { + context.Writer.Write(token.Content); + } + else + { + // There may be something else inside the expression like a Template or another extension node. + context.RenderNode(node.Children[i]); + } + } + + context.Writer.WriteEndMethodInvocation(); + + linePragmaScope?.Dispose(); + } + + public override void WriteCSharpStatement(CSharpRenderingContext context, CSharpStatementIRNode node) + { + _previous.WriteCSharpStatement(context, node); + } + + public override void WriteHtmlAttribute(CSharpRenderingContext context, HtmlAttributeIRNode node) + { + _previous.WriteHtmlAttribute(context, node); + } + + public override void WriteHtmlContent(CSharpRenderingContext context, HtmlContentIRNode node) + { + _previous.WriteHtmlContent(context, node); + } + } +} diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/RuntimeCSharpRenderer.cs b/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/RuntimeCSharpRenderer.cs index fdec104939..949970a990 100644 --- a/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/RuntimeCSharpRenderer.cs +++ b/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/RuntimeCSharpRenderer.cs @@ -63,6 +63,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution.CodeGeneration public override void VisitCSharpExpression(CSharpExpressionIRNode node) { + // We can't remove this yet, because it's still used recursively in a few places. IDisposable linePragmaScope = null; if (node.Source != null) { @@ -70,7 +71,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution.CodeGeneration var padding = BuildOffsetPadding(Context.RenderingConventions.StartWriteMethod.Length, node.Source.Value, Context); Context.Writer.Write(padding); } - + Context.Writer.Write(Context.RenderingConventions.StartWriteMethod); for (var i = 0; i < node.Children.Count; i++) diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/TagHelperWriter.cs b/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/TagHelperWriter.cs new file mode 100644 index 0000000000..efa8985193 --- /dev/null +++ b/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/TagHelperWriter.cs @@ -0,0 +1,20 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.AspNetCore.Razor.Evolution.Intermediate; + +namespace Microsoft.AspNetCore.Razor.Evolution.CodeGeneration +{ + public abstract class TagHelperWriter + { + public abstract void WriteInitializeTagHelperStructure(CSharpRenderingContext context, InitializeTagHelperStructureIRNode node); + + public abstract void WriteSetTagHelperProperty(CSharpRenderingContext context, SetTagHelperPropertyIRNode node); + + public abstract void WriteAddTagHelperHtmlAttribute(CSharpRenderingContext context, AddTagHelperHtmlAttributeIRNode node); + + public abstract void WriteCreateTagHelper(CSharpRenderingContext context, CreateTagHelperIRNode node); + + public abstract void WriteExecuteTagHelpers(CSharpRenderingContext context, ExecuteTagHelpersIRNode node); + } +} diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/TemplateTargetExtension.cs b/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/TemplateTargetExtension.cs index bfea04169f..e1fabf667f 100644 --- a/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/TemplateTargetExtension.cs +++ b/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/TemplateTargetExtension.cs @@ -22,10 +22,15 @@ namespace Microsoft.AspNetCore.Razor.Evolution.CodeGeneration var initialRenderingConventions = context.RenderingConventions; context.RenderingConventions = new CSharpRedirectRenderingConventions(TemplateWriterName, context.Writer); - using (context.Writer.BuildAsyncLambda(endLine: false, parameterNames: TemplateWriterName)) + + using (context.Push(new RedirectedBasicWriter(context.BasicWriter, TemplateWriterName))) { - context.RenderChildren(node); + using (context.Writer.BuildAsyncLambda(endLine: false, parameterNames: TemplateWriterName)) + { + context.RenderChildren(node); + } } + context.RenderingConventions = initialRenderingConventions; context.Writer.WriteEndMethodInvocation(endLine: false); diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/CSharpCodeWriter.cs b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/CSharpCodeWriter.cs index 99a4273d71..76d44f05e3 100644 --- a/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/CSharpCodeWriter.cs +++ b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/CSharpCodeWriter.cs @@ -66,6 +66,66 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy return (CSharpCodeWriter)base.WriteLine(); } + public CSharpCodeWriter WritePadding(int offset, SourceSpan? span, CSharpRenderingContext context) + { + if (span == null) + { + return this; + } + + var basePadding = CalculatePadding(); + var resolvedPadding = Math.Max(basePadding - offset, 0); + + if (context.Options.IsIndentingWithTabs) + { + // Avoid writing directly to the StringBuilder here, that will throw off the manual indexing + // done by the base class. + var tabs = resolvedPadding / context.Options.TabSize; + for (var i = 0; i < tabs; i++) + { + Write("\t"); + } + + var spaces = resolvedPadding % context.Options.TabSize; + for (var i = 0; i < spaces; i++) + { + Write(" "); + } + } + else + { + for (var i = 0; i < resolvedPadding; i++) + { + Write(" "); + } + } + + return this; + + int CalculatePadding() + { + var spaceCount = 0; + for (var i = span.Value.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; + } + } + public CSharpCodeWriter WriteVariableDeclaration(string type, string name, string value) { Write(type).Write(" ").Write(name); diff --git a/test/Microsoft.AspNetCore.Razor.Evolution.Test/CodeGeneration/TemplateTargetExtensionTest.cs b/test/Microsoft.AspNetCore.Razor.Evolution.Test/CodeGeneration/TemplateTargetExtensionTest.cs index a22e14916e..3c3c756309 100644 --- a/test/Microsoft.AspNetCore.Razor.Evolution.Test/CodeGeneration/TemplateTargetExtensionTest.cs +++ b/test/Microsoft.AspNetCore.Razor.Evolution.Test/CodeGeneration/TemplateTargetExtensionTest.cs @@ -22,6 +22,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution.CodeGeneration var context = new CSharpRenderingContext() { + BasicWriter = new DefaultBasicWriter(), Writer = new CSharpCodeWriter(), };