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 356a91c176..0e1c3fd23d 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.Collections.Generic; 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 @@ namespace Microsoft.AspNet.Razor.Generator.Compiler.CSharp // 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 @@ namespace Microsoft.AspNet.Razor.Generator.Compiler.CSharp }); } - 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 570af0fa60..2a1fcf7593 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 @@ namespace Microsoft.AspNet.Razor.Generator 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 bf34634136..cbb43762c9 100644 --- a/src/Microsoft.AspNet.Razor/Parser/TagHelpers/TagHelperBlockBuilder.cs +++ b/src/Microsoft.AspNet.Razor/Parser/TagHelpers/TagHelperBlockBuilder.cs @@ -166,6 +166,8 @@ namespace Microsoft.AspNet.Razor.Parser.TagHelpers 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 @@ namespace Microsoft.AspNet.Razor.Parser.TagHelpers { var symbol = htmlSymbols[i]; - if (name == null && symbol.Type == HtmlSymbolType.Text) + if (afterEquals) + { + // 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 (name == null && symbol.Type == HtmlSymbolType.Text) { name = symbol.Content; - } - else if (afterEquals) - { - builder.Accept(symbol); + attributeValueStartLocation = SourceLocation.Advance(span.Start, name); } else if (symbol.Type == HtmlSymbolType.Equals) { @@ -193,22 +206,36 @@ namespace Microsoft.AspNet.Razor.Parser.TagHelpers // 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 ef5c9e36de..46c1d63f32 100644 --- a/test/Microsoft.AspNet.Razor.Test/Generator/CSharpTagHelperRenderingTest.cs +++ b/test/Microsoft.AspNet.Razor.Test/Generator/CSharpTagHelperRenderingTest.cs @@ -174,7 +174,14 @@ namespace Microsoft.AspNet.Razor.Test.Generator 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 @@ namespace Microsoft.AspNet.Razor.Test.Generator 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 @@ namespace Microsoft.AspNet.Razor.Test.Generator 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),