Fix a bug in the span mapping code

This wasn't quite doing the right thing, and it's simpler than we make
it to be.
This commit is contained in:
Ryan Nowak 2018-10-17 19:27:29 -07:00
parent bafe1b27ff
commit 4927525519
3 changed files with 78 additions and 22 deletions

View File

@ -11,7 +11,6 @@ namespace Microsoft.CodeAnalysis.Experiment
{ {
public SpanMapResult(Document document, LinePositionSpan linePositionSpan) public SpanMapResult(Document document, LinePositionSpan linePositionSpan)
{ {
} }
} }

View File

@ -153,6 +153,10 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
{ {
results.Add(new SpanMapResult(document, linePositionSpan)); results.Add(new SpanMapResult(document, linePositionSpan));
} }
else
{
results.Add(null);
}
} }
return Task.FromResult(results.ToImmutable()); return Task.FromResult(results.ToImmutable());
@ -161,31 +165,30 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
// Internal for testing. // Internal for testing.
internal static bool TryGetLinePositionSpan(TextSpan span, SourceText source, RazorCSharpDocument output, out LinePositionSpan linePositionSpan) internal static bool TryGetLinePositionSpan(TextSpan span, SourceText source, RazorCSharpDocument output, out LinePositionSpan linePositionSpan)
{ {
for (var i = 0; i < output.SourceMappings.Count; i++) var mappings = output.SourceMappings;
for (var i = 0; i < mappings.Count; i++)
{ {
var mapping = output.SourceMappings[i]; var mapping = mappings[i];
if (span.Length > mapping.GeneratedSpan.Length)
{
// If the length of the generated span is smaller they can't match. A C# expression
// won't cover multiple generated spans.
//
// This heuristic is useful in the Razor context to filter out zero-length
// spans.
continue;
}
var original = mapping.OriginalSpan.AsTextSpan(); var original = mapping.OriginalSpan.AsTextSpan();
var generated = mapping.GeneratedSpan.AsTextSpan(); var generated = mapping.GeneratedSpan.AsTextSpan();
if (!generated.Contains(span))
{
// If the search span isn't contained within the generated span, it is not a match.
// A C# identifier won't cover multiple generated spans.
continue;
}
var leftOffset = span.Start - generated.Start; var leftOffset = span.Start - generated.Start;
var rightOffset = span.End - generated.End; var rightOffset = span.End - generated.End;
if (leftOffset >= 0 && rightOffset <= 0) Debug.Assert(leftOffset >= 0);
{ Debug.Assert(rightOffset <= 0);
// This span mapping contains the span.
var adjusted = new TextSpan(original.Start + leftOffset, (original.End + rightOffset) - (original.Start + leftOffset)); // Note: we don't handle imports here - the assumption is that for all of the scenarios we
linePositionSpan = source.Lines.GetLinePositionSpan(adjusted); // support, the span is in the original source document.
return true; var adjusted = new TextSpan(original.Start + leftOffset, (original.End + rightOffset) - (original.Start + leftOffset));
} linePositionSpan = source.Lines.GetLinePositionSpan(adjusted);
return true;
} }
linePositionSpan = default; linePositionSpan = default;

View File

@ -59,6 +59,62 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
Assert.NotNull(container.LatestDocument); Assert.NotNull(container.LatestDocument);
} }
[Fact]
public void TryGetLinePositionSpan_SpanMatchesSourceMapping_ReturnsTrue()
{
// Arrange
var content = @"
@SomeProperty
";
var sourceText = SourceText.From(content);
var codeDocument = GetCodeDocument(content);
var csharpDocument = codeDocument.GetCSharpDocument();
var generatedCode = csharpDocument.GeneratedCode;
var symbol = "SomeProperty";
var span = new TextSpan(generatedCode.IndexOf(symbol), symbol.Length);
// Position of `SomeProperty` in the source code.
var expectedLineSpan = new LinePositionSpan(new LinePosition(1, 1), new LinePosition(1, 13));
// Act
var result = GeneratedCodeContainer.TryGetLinePositionSpan(span, sourceText, csharpDocument, out var lineSpan);
// Assert
Assert.True(result);
Assert.Equal(expectedLineSpan, lineSpan);
}
[Fact]
public void TryGetLinePositionSpan_SpanMatchesSourceMapping_MatchingOnPosition_ReturnsTrue()
{
// Arrange
var content = @"
@SomeProperty
@SomeProperty
@SomeProperty
";
var sourceText = SourceText.From(content);
var codeDocument = GetCodeDocument(content);
var csharpDocument = codeDocument.GetCSharpDocument();
var generatedCode = csharpDocument.GeneratedCode;
var symbol = "SomeProperty";
// Second occurrence
var span = new TextSpan(generatedCode.IndexOf(symbol, generatedCode.IndexOf(symbol) + symbol.Length), symbol.Length);
// Position of `SomeProperty` in the source code.
var expectedLineSpan = new LinePositionSpan(new LinePosition(2, 1), new LinePosition(2, 13));
// Act
var result = GeneratedCodeContainer.TryGetLinePositionSpan(span, sourceText, csharpDocument, out var lineSpan);
// Assert
Assert.True(result);
Assert.Equal(expectedLineSpan, lineSpan);
}
[Fact] [Fact]
public void TryGetLinePositionSpan_SpanWithinSourceMapping_ReturnsTrue() public void TryGetLinePositionSpan_SpanWithinSourceMapping_ReturnsTrue()
{ {
@ -73,8 +129,6 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
var csharpDocument = codeDocument.GetCSharpDocument(); var csharpDocument = codeDocument.GetCSharpDocument();
var generatedCode = csharpDocument.GeneratedCode; var generatedCode = csharpDocument.GeneratedCode;
// TODO: Make writing these tests a little less manual.
// Position of `SomeProperty` in the generated code.
var symbol = "SomeProperty"; var symbol = "SomeProperty";
var span = new TextSpan(generatedCode.IndexOf(symbol), symbol.Length); var span = new TextSpan(generatedCode.IndexOf(symbol), symbol.Length);