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 4242e1228..4109d3657 100644
--- a/src/Microsoft.AspNet.Razor/Generator/Compiler/CodeBuilder/CSharp/CSharpCodeWriter.cs
+++ b/src/Microsoft.AspNet.Razor/Generator/Compiler/CodeBuilder/CSharp/CSharpCodeWriter.cs
@@ -180,9 +180,27 @@ public CSharpCodeWriter WriteReturn(string value, bool endLine)
return WriteLine();
}
+ ///
+ /// Writes a #line pragma directive for the line number at the specified .
+ ///
+ /// The location to generate the line pragma for.
+ /// The file to generate the line pragma for.
+ /// The current instance of .
+ public CSharpCodeWriter WriteLineNumberDirective(SourceLocation location, string file)
+ {
+ return WriteLineNumberDirective(location.LineIndex + 1, file);
+ }
+
public CSharpCodeWriter WriteLineNumberDirective(int lineNumber, string file)
{
- return Write("#line ").Write(lineNumber.ToString()).Write(" \"").Write(file).WriteLine("\"");
+ if (!string.IsNullOrEmpty(LastWrite) &&
+ !LastWrite.EndsWith(Environment.NewLine, StringComparison.Ordinal))
+ {
+ WriteLine();
+ }
+
+ var lineNumberAsString = lineNumber.ToString(CultureInfo.InvariantCulture);
+ return Write("#line ").Write(lineNumberAsString).Write(" \"").Write(file).WriteLine("\"");
}
public CSharpCodeWriter WriteStartMethodInvocation(string methodName)
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 d8a39dc85..8b998fa15 100644
--- a/src/Microsoft.AspNet.Razor/Generator/Compiler/CodeBuilder/CSharp/CSharpLineMappingWriter.cs
+++ b/src/Microsoft.AspNet.Razor/Generator/Compiler/CodeBuilder/CSharp/CSharpLineMappingWriter.cs
@@ -2,7 +2,6 @@
// 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.AspNet.Razor.Text;
namespace Microsoft.AspNet.Razor.Generator.Compiler.CSharp
@@ -15,16 +14,21 @@ public class CSharpLineMappingWriter : IDisposable
private int _startIndent;
private int _generatedContentLength;
private bool _writePragmas;
+ private bool _addLineMapping;
- public CSharpLineMappingWriter(CSharpCodeWriter writer, SourceLocation documentLocation, int contentLength)
+ private CSharpLineMappingWriter([NotNull] CSharpCodeWriter writer,
+ bool addLineMappings)
{
_writer = writer;
- _documentMapping = new MappingLocation(documentLocation, contentLength);
-
+ _addLineMapping = addLineMappings;
_startIndent = _writer.CurrentIndent;
- _generatedContentLength = 0;
_writer.ResetIndent();
-
+ }
+
+ public CSharpLineMappingWriter(CSharpCodeWriter writer, SourceLocation documentLocation, int contentLength)
+ : this(writer, addLineMappings: true)
+ {
+ _documentMapping = new MappingLocation(documentLocation, contentLength);
_generatedLocation = _writer.GetCurrentSourceLocation();
}
@@ -33,17 +37,27 @@ public CSharpLineMappingWriter(CSharpCodeWriter writer, SourceLocation documentL
{
_writePragmas = true;
- // TODO: Should this just be '\n'?
- if (!_writer.LastWrite.EndsWith("\n"))
- {
- _writer.WriteLine();
- }
-
- _writer.WriteLineNumberDirective(documentLocation.LineIndex + 1, sourceFilename);
-
+ _writer.WriteLineNumberDirective(documentLocation, sourceFilename);
_generatedLocation = _writer.GetCurrentSourceLocation();
}
+ ///
+ /// Initializes a new instance of used for generation of runtime
+ /// line mappings. The constructed instance of does not track
+ /// mappings between the Razor content and the generated content.
+ ///
+ /// The to write output to.
+ /// The of the Razor content being mapping.
+ /// The input file path.
+ public CSharpLineMappingWriter([NotNull] CSharpCodeWriter writer,
+ [NotNull] SourceLocation documentLocation,
+ [NotNull] string sourceFileName)
+ : this(writer, addLineMappings: false)
+ {
+ _writePragmas = true;
+ _writer.WriteLineNumberDirective(documentLocation, sourceFileName);
+ }
+
public void MarkLineMappingStart()
{
_generatedLocation = _writer.GetCurrentSourceLocation();
@@ -54,9 +68,9 @@ public void MarkLineMappingEnd()
_generatedContentLength = _writer.GenerateCode().Length - _generatedLocation.AbsoluteIndex;
}
- protected virtual void Dispose(bool disposing)
+ public void Dispose()
{
- if(disposing)
+ if (_addLineMapping)
{
// Verify that the generated length has not already been calculated
if (_generatedContentLength == 0)
@@ -73,34 +87,29 @@ protected virtual void Dispose(bool disposing)
_writer.LineMappingManager.AddMapping(
documentLocation: _documentMapping,
generatedLocation: new MappingLocation(_generatedLocation, _generatedContentLength));
+ }
- if (_writePragmas)
- {
- // 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 endsWithNewline = _writer.GenerateCode().EndsWith("\n");
-
- // Always write at least 1 empty line to potentially separate code from pragmas.
- _writer.WriteLine();
+ if (_writePragmas)
+ {
+ // 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 endsWithNewline = _writer.GenerateCode().EndsWith("\n");
- // Check if the previous empty line wasn't enough to separate code from pragmas.
- if (!endsWithNewline)
- {
- _writer.WriteLine();
- }
+ // Always write at least 1 empty line to potentially separate code from pragmas.
+ _writer.WriteLine();
- _writer.WriteLineDefaultDirective()
- .WriteLineHiddenDirective();
+ // Check if the previous empty line wasn't enough to separate code from pragmas.
+ if (!endsWithNewline)
+ {
+ _writer.WriteLine();
}
- // Reset indent back to when it was started
- _writer.SetIndent(_startIndent);
+ _writer.WriteLineDefaultDirective()
+ .WriteLineHiddenDirective();
}
- }
- public void Dispose()
- {
- Dispose(disposing: true);
+ // Reset indent back to when it was started
+ _writer.SetIndent(_startIndent);
}
}
}
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 ef32591f2..7d4224fb4 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
@@ -149,7 +149,7 @@ protected override void Visit(ExpressionBlockChunk chunk)
protected override void Visit(ExpressionChunk chunk)
{
- CreateExpressionCodeMapping(chunk.Code, chunk);
+ Writer.Write(chunk.Code);
}
protected override void Visit(StatementChunk chunk)
@@ -343,66 +343,103 @@ public void RenderDesignTimeExpressionBlockChunk(ExpressionBlockChunk chunk)
public void RenderRuntimeExpressionBlockChunk(ExpressionBlockChunk chunk)
{
- var generateInstrumentation = ShouldGenerateInstrumentationForExpressions();
- Span contentSpan = null;
+ // For expression chunks, such as @value, @(value) etc, pick the first Code or Markup span
+ // from the expression (in this case "value") and use that to calculate the length. This works
+ // accurately for most parts. The scenarios that don't work are
+ // (a) Expressions with inline comments (e.g. @(a @* comment *@ b)) - these have multiple code spans
+ // (b) Expressions with inline templates (e.g. @Foo(@
Hello world
)).
+ // Tracked via https://github.com/aspnet/Razor/issues/153
+
+ var block = (Block)chunk.Association;
+ var contentSpan = block.Children
+ .OfType()
+ .FirstOrDefault(s => s.Kind == SpanKind.Code || s.Kind == SpanKind.Markup);
- if (generateInstrumentation)
+ if (Context.ExpressionRenderingMode == ExpressionRenderingMode.InjectCode)
+ {
+ Accept(chunk.Children);
+ }
+ else if (Context.ExpressionRenderingMode == ExpressionRenderingMode.WriteToOutput)
{
- // For expression chunks, such as @value, @(value) etc, pick the first Code or Markup span
- // from the expression (in this case "value") and use that to calculate the length. This works
- // accurately for most parts. The scenarios that don't work are
- // (a) Expressions with inline comments (e.g. @(a @* comment *@ b)) - these have multiple code spans
- // (b) Expressions with inline templates (e.g. @Foo(@
Hello world
)).
- // Tracked via https://github.com/aspnet/Razor/issues/153
-
- var block = (Block)chunk.Association;
- contentSpan = block.Children
- .OfType()
- .FirstOrDefault(s => s.Kind == SpanKind.Code || s.Kind == SpanKind.Markup);
-
if (contentSpan != null)
{
- Writer.WriteStartInstrumentationContext(Context, contentSpan, isLiteral: false);
+ RenderRuntimeExpressionBlockChunkWithContentSpan(chunk, contentSpan);
+ }
+ else
+ {
+ if (!string.IsNullOrEmpty(Context.TargetWriterName))
+ {
+ Writer.WriteStartMethodInvocation(Context.Host.GeneratedClassContext.WriteToMethodName)
+ .Write(Context.TargetWriterName)
+ .WriteParameterSeparator();
+ }
+ else
+ {
+ Writer.WriteStartMethodInvocation(Context.Host.GeneratedClassContext.WriteMethodName);
+ }
+
+ Accept(chunk.Children);
+
+ Writer.WriteEndMethodInvocation()
+ .WriteLine();
}
}
+ }
- if (Context.ExpressionRenderingMode == ExpressionRenderingMode.InjectCode)
+ private void RenderRuntimeExpressionBlockChunkWithContentSpan(ExpressionBlockChunk chunk, Span contentSpan)
+ {
+ var generateInstrumentation = ShouldGenerateInstrumentationForExpressions();
+
+ if (generateInstrumentation)
{
- Accept(chunk.Children);
+ Writer.WriteStartInstrumentationContext(Context, contentSpan, isLiteral: false);
}
- else if (Context.ExpressionRenderingMode == ExpressionRenderingMode.WriteToOutput)
+
+ using (var mappingWriter = new CSharpLineMappingWriter(Writer, chunk.Start, Context.SourceFile))
{
- if (!String.IsNullOrEmpty(Context.TargetWriterName))
+ if (!string.IsNullOrEmpty(Context.TargetWriterName))
{
- Writer.WriteStartMethodInvocation(Context.Host.GeneratedClassContext.WriteToMethodName)
+ var generatedStart = Context.Host.GeneratedClassContext.WriteToMethodName.Length +
+ Context.TargetWriterName.Length +
+ 3; // 1 for the opening '(' and 2 for ', '
+
+ var padding = _paddingBuilder.BuildExpressionPadding(contentSpan, generatedStart);
+
+
+ Writer.Write(padding)
+ .WriteStartMethodInvocation(Context.Host.GeneratedClassContext.WriteToMethodName)
.Write(Context.TargetWriterName)
.WriteParameterSeparator();
}
else
{
- Writer.WriteStartMethodInvocation(Context.Host.GeneratedClassContext.WriteMethodName);
+ var generatedStart = Context.Host.GeneratedClassContext.WriteMethodName.Length +
+ 1; // for the opening '('
+ var padding = _paddingBuilder.BuildExpressionPadding(contentSpan, generatedStart);
+
+ Writer.Write(padding)
+ .WriteStartMethodInvocation(Context.Host.GeneratedClassContext.WriteMethodName);
}
Accept(chunk.Children);
- Writer.WriteEndMethodInvocation()
- .WriteLine();
+ Writer.WriteEndMethodInvocation();
}
- if (contentSpan != null)
+ if (generateInstrumentation)
{
Writer.WriteEndInstrumentationContext(Context);
}
}
- public void CreateExpressionCodeMapping(string code, Chunk chunk)
+ public void CreateStatementCodeMapping(string code, Chunk chunk)
{
- CreateCodeMapping(_paddingBuilder.BuildExpressionPadding((Span)chunk.Association), code, chunk);
+ CreateCodeMapping(_paddingBuilder.BuildStatementPadding((Span)chunk.Association), code, chunk);
}
- public void CreateStatementCodeMapping(string code, Chunk chunk)
+ public void CreateExpressionCodeMapping(string code, Chunk chunk)
{
- CreateCodeMapping(_paddingBuilder.BuildStatementPadding((Span)chunk.Association), code, chunk);
+ CreateCodeMapping(_paddingBuilder.BuildExpressionPadding((Span)chunk.Association), code, chunk);
}
public void CreateCodeMapping(string padding, string code, Chunk chunk)
diff --git a/test/Microsoft.AspNet.Razor.Test/Generator/CSharpRazorCodeGeneratorTest.cs b/test/Microsoft.AspNet.Razor.Test/Generator/CSharpRazorCodeGeneratorTest.cs
index f78caa5f6..61a038dc8 100644
--- a/test/Microsoft.AspNet.Razor.Test/Generator/CSharpRazorCodeGeneratorTest.cs
+++ b/test/Microsoft.AspNet.Razor.Test/Generator/CSharpRazorCodeGeneratorTest.cs
@@ -145,8 +145,6 @@ public void CSharpCodeGeneratorCorrectlyGeneratesMappingsForRazorCommentsAtDesig
BuildLineMapping(238, 11, 899, 45, 2, 24),
BuildLineMapping(310, 12, 1036, 51, 45, 3),
BuildLineMapping(323, 14, 2, 1112, 56, 6, 1),
- BuildLineMapping(328, 14, 1155, 58, 7, 1)
-
});
}
@@ -296,8 +294,7 @@ public void CSharpCodeGeneratorCorrectlyGeneratesDesignTimePragmasMarkupAndExpre
BuildLineMapping(113, 7, 2, 1262, 71, 6, 12),
BuildLineMapping(129, 8, 1, 1343, 76, 6, 4),
BuildLineMapping(142, 8, 1443, 78, 14, 3),
- BuildLineMapping(153, 8, 1540, 85, 25, 1),
- BuildLineMapping(204, 13, 5, 1725, 95, 6, 3)
+ BuildLineMapping(204, 13, 5, 1638, 90, 6, 3)
});
}
diff --git a/test/Microsoft.AspNet.Razor.Test/Generator/Compiler/CSharpLineMappingWriterTest.cs b/test/Microsoft.AspNet.Razor.Test/Generator/Compiler/CSharpLineMappingWriterTest.cs
new file mode 100644
index 000000000..0fb5c4d35
--- /dev/null
+++ b/test/Microsoft.AspNet.Razor.Test/Generator/Compiler/CSharpLineMappingWriterTest.cs
@@ -0,0 +1,91 @@
+// Copyright (c) Microsoft Open Technologies, Inc. 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.AspNet.Razor.Text;
+using Xunit;
+
+namespace Microsoft.AspNet.Razor.Generator.Compiler.CSharp
+{
+ public class CSharpLineMappingWriterTest
+ {
+ [Fact]
+ public void WriterConstructedWithContentLength_AddsLineMappings_OnDispose()
+ {
+ // Arrange
+ var location = new SourceLocation(10, 15, 20);
+ var expected = new LineMapping(
+ new MappingLocation(location, 30),
+ new MappingLocation(new SourceLocation(0, 0, 0), 11));
+ var writer = new CSharpCodeWriter();
+
+ // Act
+ using (var mappingWriter = new CSharpLineMappingWriter(writer, location, 30))
+ {
+ writer.Write("Hello world");
+ }
+
+ // Assert
+ Assert.Equal("Hello world", writer.GenerateCode());
+ var mapping = Assert.Single(writer.LineMappingManager.Mappings);
+ Assert.Equal(expected, mapping);
+ }
+
+ [Fact]
+ public void WriterConstructedWithContentLengthAndSourceFile_AddsLineMappingsAndLinePragmas_OnDispose()
+ {
+ // Arrange
+ var location = new SourceLocation(10, 1, 20);
+ var expected = string.Join(Environment.NewLine,
+ @"#line 2 ""myfile""",
+ "Hello world",
+ "",
+ "#line default",
+ "#line hidden",
+ "");
+ var expectedMappings = new LineMapping(
+ new MappingLocation(location, 30),
+ new MappingLocation(new SourceLocation(18, 1, 0), 11));
+ var writer = new CSharpCodeWriter();
+
+ // Act
+ using (var mappingWriter = new CSharpLineMappingWriter(writer, location, 30, "myfile"))
+ {
+ writer.Write("Hello world");
+ }
+
+ // Assert
+ Assert.Equal(expected, writer.GenerateCode());
+ var mapping = Assert.Single(writer.LineMappingManager.Mappings);
+ Assert.Equal(expectedMappings, mapping);
+ }
+
+ [Fact]
+ public void WriterConstructedWithoutContentLengthAndSourceFile_AddsLinePragmas_OnDispose()
+ {
+ // Arrange
+ var location = new SourceLocation(10, 1, 20);
+ var expected = string.Join(Environment.NewLine,
+ @"#line 2 ""myfile""",
+ "Hello world",
+ "",
+ "#line default",
+ "#line hidden",
+ "");
+ var expectedMappings = new LineMapping(
+ new MappingLocation(location, 30),
+ new MappingLocation(new SourceLocation(18, 1, 0), 11));
+ var writer = new CSharpCodeWriter();
+
+ // Act
+ using (var mappingWriter = new CSharpLineMappingWriter(writer, location, "myfile"))
+ {
+ writer.Write("Hello world");
+ }
+
+ // Assert
+ Assert.Equal(expected, writer.GenerateCode());
+ Assert.Empty(writer.LineMappingManager.Mappings);
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/Microsoft.AspNet.Razor.Test/TestFiles/CodeGenerator/CS/Output/Await.cs b/test/Microsoft.AspNet.Razor.Test/TestFiles/CodeGenerator/CS/Output/Await.cs
index 7083ce8d3..d90faf9ad 100644
--- a/test/Microsoft.AspNet.Razor.Test/TestFiles/CodeGenerator/CS/Output/Await.cs
+++ b/test/Microsoft.AspNet.Razor.Test/TestFiles/CodeGenerator/CS/Output/Await.cs
@@ -28,27 +28,21 @@ public override async Task ExecuteAsync()
"ronous Expression: ");
Instrumentation.EndContext();
Instrumentation.BeginContext(192, 11, false);
- Write(
#line 10 "Await.cshtml"
- await Foo()
+ Write(await Foo());
#line default
#line hidden
- );
-
Instrumentation.EndContext();
Instrumentation.BeginContext(203, 42, true);
WriteLiteral("\r\n