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
This commit is contained in:
N. Taylor Mullen 2014-11-25 11:50:33 -08:00
parent ceaf257cd5
commit a86b0dca3e
4 changed files with 70 additions and 11 deletions

View File

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

View File

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

View File

@ -166,6 +166,8 @@ namespace Microsoft.AspNet.Razor.Parser.TagHelpers
Kind = span.Kind
};
var htmlSymbols = span.Symbols.OfType<HtmlSymbol>().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);
}

View File

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