diff --git a/src/Microsoft.AspNet.Razor/Generator/Compiler/CodeBuilder/CSharp/CSharpTagHelperCodeRenderer.cs b/src/Microsoft.AspNet.Razor/Generator/Compiler/CodeBuilder/CSharp/CSharpTagHelperCodeRenderer.cs index 356a91c17..0e1c3fd23 100644 --- a/src/Microsoft.AspNet.Razor/Generator/Compiler/CodeBuilder/CSharp/CSharpTagHelperCodeRenderer.cs +++ b/src/Microsoft.AspNet.Razor/Generator/Compiler/CodeBuilder/CSharp/CSharpTagHelperCodeRenderer.cs @@ -6,6 +6,7 @@ using System.Globalization; using System.Linq; using Microsoft.AspNet.Razor.TagHelpers; +using Microsoft.AspNet.Razor.Text; namespace Microsoft.AspNet.Razor.Generator.Compiler.CSharp { @@ -265,7 +266,7 @@ private void RenderBoundHTMLAttributes(IDictionary chunkAttribute // We aren't a bufferable attribute which means we have no Razor code in our value. // Therefore we can just use the "textValue" as the attribute value. - RenderRawAttributeValue(textValue, attributeDescriptor); + RenderRawAttributeValue(textValue, attributeValueChunk.Start, attributeDescriptor); } // End the assignment to the attribute. @@ -421,13 +422,25 @@ private void RenderBufferedAttributeValue(TagHelperAttributeDescriptor attribute }); } - private void RenderRawAttributeValue(string value, TagHelperAttributeDescriptor attributeDescriptor) + private void RenderRawAttributeValue(string value, + SourceLocation documentLocation, + TagHelperAttributeDescriptor attributeDescriptor) { RenderAttributeValue( attributeDescriptor, valueRenderer: (writer) => { - writer.Write(value); + if (_context.Host.DesignTimeMode) + { + using (new CSharpLineMappingWriter(writer, documentLocation, value.Length)) + { + writer.Write(value); + } + } + else + { + writer.Write(value); + } }); } diff --git a/src/Microsoft.AspNet.Razor/Generator/TagHelperCodeGenerator.cs b/src/Microsoft.AspNet.Razor/Generator/TagHelperCodeGenerator.cs index 570af0fa6..2a1fcf759 100644 --- a/src/Microsoft.AspNet.Razor/Generator/TagHelperCodeGenerator.cs +++ b/src/Microsoft.AspNet.Razor/Generator/TagHelperCodeGenerator.cs @@ -3,10 +3,12 @@ using System; using System.Collections.Generic; +using System.Linq; using Microsoft.AspNet.Razor.Generator.Compiler; using Microsoft.AspNet.Razor.Parser.SyntaxTree; using Microsoft.AspNet.Razor.Parser.TagHelpers; using Microsoft.AspNet.Razor.TagHelpers; +using Microsoft.AspNet.Razor.Text; namespace Microsoft.AspNet.Razor.Generator { @@ -60,10 +62,12 @@ public override void GenerateStartBlockCode(Block target, CodeGeneratorContext c attribute.Value.Accept(codeGenerator); var chunks = codeGenerator.Context.CodeTreeBuilder.CodeTree.Chunks; + var first = chunks.FirstOrDefault(); attributes[attribute.Key] = new ChunkBlock { - Children = chunks + Children = chunks, + Start = first == null ? SourceLocation.Zero : first.Start }; // Reset the code tree builder so we can build a new one for the next attribute diff --git a/src/Microsoft.AspNet.Razor/Parser/TagHelpers/TagHelperBlockBuilder.cs b/src/Microsoft.AspNet.Razor/Parser/TagHelpers/TagHelperBlockBuilder.cs index bf3463413..cbb43762c 100644 --- a/src/Microsoft.AspNet.Razor/Parser/TagHelpers/TagHelperBlockBuilder.cs +++ b/src/Microsoft.AspNet.Razor/Parser/TagHelpers/TagHelperBlockBuilder.cs @@ -166,6 +166,8 @@ private static KeyValuePair ParseSpan( Kind = span.Kind }; var htmlSymbols = span.Symbols.OfType().ToArray(); + var capturedAttributeValueStart = false; + var attributeValueStartLocation = span.Start; var symbolOffset = 1; string name = null; @@ -175,13 +177,24 @@ private static KeyValuePair ParseSpan( { var symbol = htmlSymbols[i]; - if (name == null && symbol.Type == HtmlSymbolType.Text) + if (afterEquals) { - name = symbol.Content; + // When symbols are accepted into SpanBuilders, their locations get altered to be offset by the + // parent which is why we need to mark our start location prior to adding the symbol. + // This is needed to know the location of the attribute value start within the document. + if (!capturedAttributeValueStart) + { + capturedAttributeValueStart = true; + + attributeValueStartLocation = span.Start + symbol.Start; + } + + builder.Accept(symbol); } - else if (afterEquals) + else if (name == null && symbol.Type == HtmlSymbolType.Text) { - builder.Accept(symbol); + name = symbol.Content; + attributeValueStartLocation = SourceLocation.Advance(span.Start, name); } else if (symbol.Type == HtmlSymbolType.Equals) { @@ -193,22 +206,36 @@ private static KeyValuePair ParseSpan( // TODO: Handle malformed tags, if there's an '=' then there MUST be a value. // https://github.com/aspnet/Razor/issues/104 + SourceLocation symbolStartLocation; + // Check for attribute start values, aka single or double quote if (IsQuote(htmlSymbols[i + 1])) { // Move past the attribute start so we can accept the true value. i++; + symbolStartLocation = htmlSymbols[i + 1].Start; } else { + symbolStartLocation = symbol.Start; + // Set the symbol offset to 0 so we don't attempt to skip an end quote that doesn't exist. symbolOffset = 0; } + attributeValueStartLocation = symbolStartLocation + + span.Start + + new SourceLocation(absoluteIndex: 1, + lineIndex: 0, + characterIndex: 1); afterEquals = true; } } + // After all symbols have been added we need to set the builders start position so we do not indirectly + // modify each symbol's Start location. + builder.Start = attributeValueStartLocation; + return CreateMarkupAttribute(name, builder, attributeValueTypes); } diff --git a/test/Microsoft.AspNet.Razor.Test/Generator/CSharpTagHelperRenderingTest.cs b/test/Microsoft.AspNet.Razor.Test/Generator/CSharpTagHelperRenderingTest.cs index ef5c9e36d..46c1d63f3 100644 --- a/test/Microsoft.AspNet.Razor.Test/Generator/CSharpTagHelperRenderingTest.cs +++ b/test/Microsoft.AspNet.Razor.Test/Generator/CSharpTagHelperRenderingTest.cs @@ -174,7 +174,14 @@ public static TheoryData DesignTimeTagHelperTestData generatedAbsoluteIndex: 475, generatedLineIndex: 15, characterOffsetIndex: 14, - contentLength: 11) + contentLength: 11), + BuildLineMapping(documentAbsoluteIndex: 57, + documentLineIndex: 2, + documentCharacterOffsetIndex: 28, + generatedAbsoluteIndex: 927, + generatedLineIndex: 33, + generatedCharacterOffsetIndex: 31, + contentLength: 4) } }, { @@ -188,7 +195,14 @@ public static TheoryData DesignTimeTagHelperTestData generatedAbsoluteIndex: 475, generatedLineIndex: 15, characterOffsetIndex: 14, - contentLength: 11) + contentLength: 11), + BuildLineMapping(documentAbsoluteIndex: 189, + documentLineIndex: 6, + documentCharacterOffsetIndex: 40, + generatedAbsoluteIndex: 1599, + generatedLineIndex: 44, + generatedCharacterOffsetIndex: 40, + contentLength: 4) } }, { @@ -218,6 +232,7 @@ public static TheoryData DesignTimeTagHelperTestData BuildLineMapping(218, 9, 13, 1356, 56, 12, 27), BuildLineMapping(346, 12, 1754, 68, 0, 48), BuildLineMapping(440, 15, 46, 2004, 78, 6, 8), + BuildLineMapping(457, 15, 63, 2267, 85, 40, 4), BuildLineMapping(501, 16, 31, 2384, 88, 6, 30), BuildLineMapping(568, 17, 30, 2733, 97, 0, 10), BuildLineMapping(601, 17, 63, 2815, 103, 0, 8),