From 549e36b803f10e1b836ca9d4f747a02e4dabfe4e Mon Sep 17 00:00:00 2001 From: "N. Taylor Mullen" Date: Mon, 3 Mar 2014 16:52:18 -0800 Subject: [PATCH] Fix CodeGeneration process to format correctly within cshtml. Added newlines inbetween ending line pragma's and code. Without the extra line the document does not format correctly. Separated expression and statement padding functionality. Statements need to have 1 less padding to account for the transition. Changed how runtime and design time code generates to enable accurate debugging experiences in runtime and functional formatting experiences during design time. --- .../CodeBuilder/CSharp/CSharpCodeWriter.cs | 5 + .../CSharp/CSharpLineMappingWriter.cs | 36 +++-- .../CSharp/CSharpPaddingBuilder.cs | 27 +++- .../CSharp/Visitors/CSharpCodeVisitor.cs | 128 +++++++++++++----- .../Visitors/CSharpTypeMemberVisitor.cs | 2 +- 5 files changed, 149 insertions(+), 49 deletions(-) diff --git a/src/Microsoft.AspNet.Razor/Generator/Compiler/CodeBuilder/CSharp/CSharpCodeWriter.cs b/src/Microsoft.AspNet.Razor/Generator/Compiler/CodeBuilder/CSharp/CSharpCodeWriter.cs index d55a490755..39e2e27d87 100644 --- a/src/Microsoft.AspNet.Razor/Generator/Compiler/CodeBuilder/CSharp/CSharpCodeWriter.cs +++ b/src/Microsoft.AspNet.Razor/Generator/Compiler/CodeBuilder/CSharp/CSharpCodeWriter.cs @@ -25,6 +25,11 @@ namespace Microsoft.AspNet.Razor.Generator.Compiler.CSharp return (CSharpCodeWriter)base.Indent(size); } + public CSharpCodeWriter ResetIndent() + { + return (CSharpCodeWriter)base.ResetIndent(); + } + public CSharpCodeWriter SetIndent(int size) { return (CSharpCodeWriter)base.SetIndent(size); diff --git a/src/Microsoft.AspNet.Razor/Generator/Compiler/CodeBuilder/CSharp/CSharpLineMappingWriter.cs b/src/Microsoft.AspNet.Razor/Generator/Compiler/CodeBuilder/CSharp/CSharpLineMappingWriter.cs index 891cbd5a73..5a9b30fede 100644 --- a/src/Microsoft.AspNet.Razor/Generator/Compiler/CodeBuilder/CSharp/CSharpLineMappingWriter.cs +++ b/src/Microsoft.AspNet.Razor/Generator/Compiler/CodeBuilder/CSharp/CSharpLineMappingWriter.cs @@ -11,8 +11,9 @@ namespace Microsoft.AspNet.Razor.Generator.Compiler.CSharp private SourceLocation _generatedLocation; private int _startIndent; private int _generatedContentLength; + private bool _writePragmas; - public CSharpLineMappingWriter(CSharpCodeWriter writer, SourceLocation documentLocation, int contentLength, string sourceFilename) + public CSharpLineMappingWriter(CSharpCodeWriter writer, SourceLocation documentLocation, int contentLength) { _writer = writer; _documentMapping = new MappingLocation(documentLocation, contentLength); @@ -20,6 +21,14 @@ namespace Microsoft.AspNet.Razor.Generator.Compiler.CSharp _startIndent = _writer.CurrentIndent; _generatedContentLength = 0; _writer.ResetIndent(); + + _generatedLocation = _writer.GetCurrentSourceLocation(); + } + + public CSharpLineMappingWriter(CSharpCodeWriter writer, SourceLocation documentLocation, int contentLength, string sourceFilename) + : this(writer, documentLocation, contentLength) + { + _writePragmas = true; // TODO: Should this just be '\n'? if (_writer.LastWrite.Last() != '\n') @@ -49,9 +58,9 @@ namespace Microsoft.AspNet.Razor.Generator.Compiler.CSharp { _generatedContentLength = _writer.ToString().Length - _generatedLocation.AbsoluteIndex; } - + var generatedLocation = new MappingLocation(_generatedLocation, _generatedContentLength); - if(_documentMapping.ContentLength == -1) + if (_documentMapping.ContentLength == -1) { _documentMapping.ContentLength = generatedLocation.ContentLength; } @@ -60,13 +69,24 @@ namespace Microsoft.AspNet.Razor.Generator.Compiler.CSharp documentLocation: _documentMapping, generatedLocation: new MappingLocation(_generatedLocation, _generatedContentLength)); - if (_writer.ToString().Last() != '\n') + if (_writePragmas) { - _writer.WriteLine(); - } + // 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 ... + bool writeExtraLine = _writer.ToString().Last() != '\n'; - _writer.WriteLineDefaultDirective(); - _writer.WriteLineHiddenDirective(); + // 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 (writeExtraLine) + { + _writer.WriteLine(); + } + + _writer.WriteLineDefaultDirective() + .WriteLineHiddenDirective(); + } // Reset indent back to when it was started _writer.SetIndent(_startIndent); diff --git a/src/Microsoft.AspNet.Razor/Generator/Compiler/CodeBuilder/CSharp/CSharpPaddingBuilder.cs b/src/Microsoft.AspNet.Razor/Generator/Compiler/CodeBuilder/CSharp/CSharpPaddingBuilder.cs index fe01747084..cc7ea0bd19 100644 --- a/src/Microsoft.AspNet.Razor/Generator/Compiler/CodeBuilder/CSharp/CSharpPaddingBuilder.cs +++ b/src/Microsoft.AspNet.Razor/Generator/Compiler/CodeBuilder/CSharp/CSharpPaddingBuilder.cs @@ -23,7 +23,7 @@ namespace Microsoft.AspNet.Razor.Generator.Compiler.CSharp throw new ArgumentNullException("target"); } - int padding = CalculatePadding(target); + int padding = CalculatePadding(target, 0); // We treat statement padding specially so for brace positioning, so that in the following example: // @if (foo > 0) @@ -44,21 +44,38 @@ namespace Microsoft.AspNet.Razor.Generator.Compiler.CSharp return generatedCode; } - public string BuildExpressionPadding(Span target) + public string BuildExpressionPadding(Span target, int generatedStart = 0) { - int padding = CalculatePadding(target); + int padding = CalculatePadding(target, generatedStart); return BuildPaddingInternal(padding); } - internal int CalculatePadding(Span target) + internal int CalculatePadding(Span target, int generatedStart) { if (target == null) { throw new ArgumentNullException("target"); } - return CollectSpacesAndTabs(target, _host.TabSize); + int padding; + + padding = CollectSpacesAndTabs(target, _host.TabSize) - generatedStart; + + // if we add generated text that is longer than the padding we wanted to insert we have no recourse and we have to skip padding + // example: + // Razor code at column zero: @somecode() + // Generated code will be: + // In design time: __o = somecode(); + // In Run time: Write(somecode()); + // + // In both cases the padding would have been 1 space to remote the space the @ symbol takes, which will be smaller than the 6 chars the hidden generated code takes. + if (padding < 0) + { + padding = 0; + } + + return padding; } private string BuildPaddingInternal(int padding) diff --git a/src/Microsoft.AspNet.Razor/Generator/Compiler/CodeBuilder/CSharp/Visitors/CSharpCodeVisitor.cs b/src/Microsoft.AspNet.Razor/Generator/Compiler/CodeBuilder/CSharp/Visitors/CSharpCodeVisitor.cs index a29ac498b5..54ede121ed 100644 --- a/src/Microsoft.AspNet.Razor/Generator/Compiler/CodeBuilder/CSharp/Visitors/CSharpCodeVisitor.cs +++ b/src/Microsoft.AspNet.Razor/Generator/Compiler/CodeBuilder/CSharp/Visitors/CSharpCodeVisitor.cs @@ -2,6 +2,7 @@ using System.Globalization; using System.Linq; using Microsoft.AspNet.Razor.Parser.SyntaxTree; +using Microsoft.AspNet.Razor.Text; namespace Microsoft.AspNet.Razor.Generator.Compiler.CSharp { @@ -115,53 +116,25 @@ namespace Microsoft.AspNet.Razor.Generator.Compiler.CSharp protected override void Visit(ExpressionBlockChunk chunk) { // TODO: Handle instrumentation - // TODO: Refactor - if (!Context.Host.DesignTimeMode && Context.ExpressionRenderingMode == ExpressionRenderingMode.InjectCode) + if (Context.Host.DesignTimeMode) { - Visit((ChunkBlock)chunk); + RenderDesignTimeExpressionBlockChunk(chunk); } else { - if (Context.Host.DesignTimeMode) - { - Writer.WriteStartAssignment("__o"); - } - else if (Context.ExpressionRenderingMode == ExpressionRenderingMode.WriteToOutput) - { - if (!String.IsNullOrEmpty(Context.TargetWriterName)) - { - Writer.WriteStartMethodInvocation(Context.Host.GeneratedClassContext.WriteToMethodName) - .Write(Context.TargetWriterName) - .WriteParameterSeparator(); - } - else - { - Writer.WriteStartMethodInvocation(Context.Host.GeneratedClassContext.WriteMethodName); - } - } - - Visit((ChunkBlock)chunk); - - if (Context.Host.DesignTimeMode) - { - Writer.WriteLine(";"); - } - else if (Context.ExpressionRenderingMode == ExpressionRenderingMode.WriteToOutput) - { - Writer.WriteEndMethodInvocation(); - } + RenderRuntimeExpressionBlockChunk(chunk); } } protected override void Visit(ExpressionChunk chunk) { - CreateCodeMapping(chunk.Code, chunk); + CreateExpressionCodeMapping(chunk.Code, chunk); } protected override void Visit(StatementChunk chunk) { - CreateCodeMapping(chunk.Code, chunk); + CreateStatementCodeMapping(chunk.Code, chunk); Writer.WriteLine(); } @@ -308,16 +281,101 @@ namespace Microsoft.AspNet.Razor.Generator.Compiler.CSharp Writer.WriteEndMethodInvocation(); } - public void CreateCodeMapping(string code, Chunk chunk) + public void RenderDesignTimeExpressionBlockChunk(ExpressionBlockChunk chunk) + { + // TODO: Handle instrumentation + + int currentIndent = Writer.CurrentIndent; + string designTimeAssignment = "__o = "; + + // The first child should never be null, it should always be a transition span, that's what + // defines an expression block chunk. + var firstChild = (ExpressionChunk)chunk.Children.FirstOrDefault(); + + Writer.ResetIndent() + .WriteLineNumberDirective(1, "This is here only for document formatting.") + // We build the padding with an offset of the design time assignment statement. + .Write(_paddingBuilder.BuildExpressionPadding((Span)firstChild.Association, designTimeAssignment.Length)) + .Write(designTimeAssignment); + + // We map the first line of code but do not write the line pragmas associated with it. + CreateRawCodeMapping(firstChild.Code, firstChild.Association.Start); + + // This is a temporary block that is indentical to the current one with the exception of its children. + var subBlock = new ChunkBlock + { + Start = chunk.Start, + Association = chunk.Association, + Children = chunk.Children.Skip(1).ToList() + }; + + // Render all children (except for the first one) + Visit(subBlock); + + Writer.WriteLine(";") + .WriteLine() + .WriteLineDefaultDirective() + .WriteLineHiddenDirective() + .SetIndent(currentIndent); + } + + public void RenderRuntimeExpressionBlockChunk(ExpressionBlockChunk chunk) + { + // TODO: Handle instrumentation + + if (Context.ExpressionRenderingMode == ExpressionRenderingMode.InjectCode) + { + Visit((ChunkBlock)chunk); + } + else if (Context.ExpressionRenderingMode == ExpressionRenderingMode.WriteToOutput) + { + if (!String.IsNullOrEmpty(Context.TargetWriterName)) + { + Writer.WriteStartMethodInvocation(Context.Host.GeneratedClassContext.WriteToMethodName) + .Write(Context.TargetWriterName) + .WriteParameterSeparator(); + } + else + { + Writer.WriteStartMethodInvocation(Context.Host.GeneratedClassContext.WriteMethodName); + } + + Visit((ChunkBlock)chunk); + + Writer.WriteEndMethodInvocation() + .WriteLine(); + } + } + + public void CreateExpressionCodeMapping(string code, Chunk chunk) + { + CreateCodeMapping(_paddingBuilder.BuildExpressionPadding((Span)chunk.Association), code, chunk); + } + + public void CreateStatementCodeMapping(string code, Chunk chunk) + { + CreateCodeMapping(_paddingBuilder.BuildStatementPadding((Span)chunk.Association), code, chunk); + } + + public void CreateCodeMapping(string padding, string code, Chunk chunk) { using (CSharpLineMappingWriter mappingWriter = Writer.BuildLineMapping(chunk.Start, code.Length, Context.SourceFile)) { - Writer.Write(_paddingBuilder.BuildExpressionPadding((Span)chunk.Association)); + Writer.Write(padding); mappingWriter.MarkLineMappingStart(); Writer.Write(code); mappingWriter.MarkLineMappingEnd(); } } + + // Raw CodeMapping's do not write out line pragmas, they just map code. + public void CreateRawCodeMapping(string code, SourceLocation documentLocation) + { + using (new CSharpLineMappingWriter(Writer, documentLocation, code.Length)) + { + Writer.Write(code); + } + } } } diff --git a/src/Microsoft.AspNet.Razor/Generator/Compiler/CodeBuilder/CSharp/Visitors/CSharpTypeMemberVisitor.cs b/src/Microsoft.AspNet.Razor/Generator/Compiler/CodeBuilder/CSharp/Visitors/CSharpTypeMemberVisitor.cs index eb5b84df07..7fda51375e 100644 --- a/src/Microsoft.AspNet.Razor/Generator/Compiler/CodeBuilder/CSharp/Visitors/CSharpTypeMemberVisitor.cs +++ b/src/Microsoft.AspNet.Razor/Generator/Compiler/CodeBuilder/CSharp/Visitors/CSharpTypeMemberVisitor.cs @@ -16,7 +16,7 @@ namespace Microsoft.AspNet.Razor.Generator.Compiler.CSharp { if (!String.IsNullOrEmpty(chunk.Code)) { - _csharpCodeVisitor.CreateCodeMapping(chunk.Code, chunk); + _csharpCodeVisitor.CreateCodeMapping(String.Empty, chunk.Code, chunk); } } }