Skip to content
This repository was archived by the owner on Dec 19, 2018. It is now read-only.

Commit a86b0dc

Browse files
author
N. Taylor Mullen
committed
Add line mappings to project TagHelper attribute values.
- We now create LineMappings for instances where a TagHelper's attribute value is not of a string type. - This will enable the Razor editor to create projections from .cshtml => .cs for TagHelper attributes. - Modified the TagHelperCodeGenerator and TagHelperBlockBuiler to accurately track the attribute values start locations so it could flow into code generation. - Modified existing tests to account for the new line mappings. #207
1 parent ceaf257 commit a86b0dc

File tree

4 files changed

+69
-10
lines changed

4 files changed

+69
-10
lines changed

src/Microsoft.AspNet.Razor/Generator/Compiler/CodeBuilder/CSharp/CSharpTagHelperCodeRenderer.cs

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using System.Globalization;
77
using System.Linq;
88
using Microsoft.AspNet.Razor.TagHelpers;
9+
using Microsoft.AspNet.Razor.Text;
910

1011
namespace Microsoft.AspNet.Razor.Generator.Compiler.CSharp
1112
{
@@ -265,7 +266,7 @@ private void RenderBoundHTMLAttributes(IDictionary<string, Chunk> chunkAttribute
265266

266267
// We aren't a bufferable attribute which means we have no Razor code in our value.
267268
// Therefore we can just use the "textValue" as the attribute value.
268-
RenderRawAttributeValue(textValue, attributeDescriptor);
269+
RenderRawAttributeValue(textValue, attributeValueChunk.Start, attributeDescriptor);
269270
}
270271

271272
// End the assignment to the attribute.
@@ -421,13 +422,25 @@ private void RenderBufferedAttributeValue(TagHelperAttributeDescriptor attribute
421422
});
422423
}
423424

424-
private void RenderRawAttributeValue(string value, TagHelperAttributeDescriptor attributeDescriptor)
425+
private void RenderRawAttributeValue(string value,
426+
SourceLocation documentLocation,
427+
TagHelperAttributeDescriptor attributeDescriptor)
425428
{
426429
RenderAttributeValue(
427430
attributeDescriptor,
428431
valueRenderer: (writer) =>
429432
{
430-
writer.Write(value);
433+
if (_context.Host.DesignTimeMode)
434+
{
435+
using (new CSharpLineMappingWriter(writer, documentLocation, value.Length))
436+
{
437+
writer.Write(value);
438+
}
439+
}
440+
else
441+
{
442+
writer.Write(value);
443+
}
431444
});
432445
}
433446

src/Microsoft.AspNet.Razor/Generator/TagHelperCodeGenerator.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@
33

44
using System;
55
using System.Collections.Generic;
6+
using System.Linq;
67
using Microsoft.AspNet.Razor.Generator.Compiler;
78
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
89
using Microsoft.AspNet.Razor.Parser.TagHelpers;
910
using Microsoft.AspNet.Razor.TagHelpers;
11+
using Microsoft.AspNet.Razor.Text;
1012

1113
namespace Microsoft.AspNet.Razor.Generator
1214
{
@@ -60,10 +62,12 @@ public override void GenerateStartBlockCode(Block target, CodeGeneratorContext c
6062
attribute.Value.Accept(codeGenerator);
6163

6264
var chunks = codeGenerator.Context.CodeTreeBuilder.CodeTree.Chunks;
65+
var first = chunks.FirstOrDefault();
6366

6467
attributes[attribute.Key] = new ChunkBlock
6568
{
66-
Children = chunks
69+
Children = chunks,
70+
Start = first == null ? SourceLocation.Zero : first.Start
6771
};
6872

6973
// Reset the code tree builder so we can build a new one for the next attribute

src/Microsoft.AspNet.Razor/Parser/TagHelpers/TagHelperBlockBuilder.cs

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,8 @@ private static KeyValuePair<string, SyntaxTreeNode> ParseSpan(
166166
Kind = span.Kind
167167
};
168168
var htmlSymbols = span.Symbols.OfType<HtmlSymbol>().ToArray();
169+
var capturedAttributeValueStart = false;
170+
var attributeValueStartLocation = span.Start;
169171
var symbolOffset = 1;
170172
string name = null;
171173

@@ -175,13 +177,24 @@ private static KeyValuePair<string, SyntaxTreeNode> ParseSpan(
175177
{
176178
var symbol = htmlSymbols[i];
177179

178-
if (name == null && symbol.Type == HtmlSymbolType.Text)
180+
if (afterEquals)
179181
{
180-
name = symbol.Content;
182+
// When symbols are accepted into SpanBuilders, their locations get altered to be offset by the
183+
// parent which is why we need to mark our start location prior to adding the symbol.
184+
// This is needed to know the location of the attribute value start within the document.
185+
if (!capturedAttributeValueStart)
186+
{
187+
capturedAttributeValueStart = true;
188+
189+
attributeValueStartLocation = span.Start + symbol.Start;
190+
}
191+
192+
builder.Accept(symbol);
181193
}
182-
else if (afterEquals)
194+
else if (name == null && symbol.Type == HtmlSymbolType.Text)
183195
{
184-
builder.Accept(symbol);
196+
name = symbol.Content;
197+
attributeValueStartLocation = SourceLocation.Advance(span.Start, name);
185198
}
186199
else if (symbol.Type == HtmlSymbolType.Equals)
187200
{
@@ -193,22 +206,36 @@ private static KeyValuePair<string, SyntaxTreeNode> ParseSpan(
193206
// TODO: Handle malformed tags, if there's an '=' then there MUST be a value.
194207
// https://github.com/aspnet/Razor/issues/104
195208

209+
SourceLocation symbolStartLocation;
210+
196211
// Check for attribute start values, aka single or double quote
197212
if (IsQuote(htmlSymbols[i + 1]))
198213
{
199214
// Move past the attribute start so we can accept the true value.
200215
i++;
216+
symbolStartLocation = htmlSymbols[i + 1].Start;
201217
}
202218
else
203219
{
220+
symbolStartLocation = symbol.Start;
221+
204222
// Set the symbol offset to 0 so we don't attempt to skip an end quote that doesn't exist.
205223
symbolOffset = 0;
206224
}
207225

226+
attributeValueStartLocation = symbolStartLocation +
227+
span.Start +
228+
new SourceLocation(absoluteIndex: 1,
229+
lineIndex: 0,
230+
characterIndex: 1);
208231
afterEquals = true;
209232
}
210233
}
211234

235+
// After all symbols have been added we need to set the builders start position so we do not indirectly
236+
// modify each symbol's Start location.
237+
builder.Start = attributeValueStartLocation;
238+
212239
return CreateMarkupAttribute(name, builder, attributeValueTypes);
213240
}
214241

test/Microsoft.AspNet.Razor.Test/Generator/CSharpTagHelperRenderingTest.cs

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,14 @@ public static TheoryData DesignTimeTagHelperTestData
174174
generatedAbsoluteIndex: 475,
175175
generatedLineIndex: 15,
176176
characterOffsetIndex: 14,
177-
contentLength: 11)
177+
contentLength: 11),
178+
BuildLineMapping(documentAbsoluteIndex: 57,
179+
documentLineIndex: 2,
180+
documentCharacterOffsetIndex: 28,
181+
generatedAbsoluteIndex: 927,
182+
generatedLineIndex: 33,
183+
generatedCharacterOffsetIndex: 31,
184+
contentLength: 4)
178185
}
179186
},
180187
{
@@ -188,7 +195,14 @@ public static TheoryData DesignTimeTagHelperTestData
188195
generatedAbsoluteIndex: 475,
189196
generatedLineIndex: 15,
190197
characterOffsetIndex: 14,
191-
contentLength: 11)
198+
contentLength: 11),
199+
BuildLineMapping(documentAbsoluteIndex: 189,
200+
documentLineIndex: 6,
201+
documentCharacterOffsetIndex: 40,
202+
generatedAbsoluteIndex: 1599,
203+
generatedLineIndex: 44,
204+
generatedCharacterOffsetIndex: 40,
205+
contentLength: 4)
192206
}
193207
},
194208
{
@@ -218,6 +232,7 @@ public static TheoryData DesignTimeTagHelperTestData
218232
BuildLineMapping(218, 9, 13, 1356, 56, 12, 27),
219233
BuildLineMapping(346, 12, 1754, 68, 0, 48),
220234
BuildLineMapping(440, 15, 46, 2004, 78, 6, 8),
235+
BuildLineMapping(457, 15, 63, 2267, 85, 40, 4),
221236
BuildLineMapping(501, 16, 31, 2384, 88, 6, 30),
222237
BuildLineMapping(568, 17, 30, 2733, 97, 0, 10),
223238
BuildLineMapping(601, 17, 63, 2815, 103, 0, 8),

0 commit comments

Comments
 (0)