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.
This commit is contained in:
N. Taylor Mullen 2014-03-03 16:52:18 -08:00
parent b6082d1523
commit 549e36b803
5 changed files with 149 additions and 49 deletions

View File

@ -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);

View File

@ -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);

View File

@ -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)

View File

@ -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);
}
}
}
}

View File

@ -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);
}
}
}