From 4927525519e9dbf4dc5f2d157e19a7daa1fd3b1e Mon Sep 17 00:00:00 2001 From: Ryan Nowak Date: Wed, 17 Oct 2018 19:27:29 -0700 Subject: [PATCH] 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. --- .../Experiment/SpanMapResult.cs | 1 - .../ProjectSystem/GeneratedCodeContainer.cs | 41 +++++++------ .../GeneratedCodeContainerTest.cs | 58 ++++++++++++++++++- 3 files changed, 78 insertions(+), 22 deletions(-) diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/Experiment/SpanMapResult.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/Experiment/SpanMapResult.cs index 531f6adead..ea720c37ed 100644 --- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/Experiment/SpanMapResult.cs +++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/Experiment/SpanMapResult.cs @@ -11,7 +11,6 @@ namespace Microsoft.CodeAnalysis.Experiment { public SpanMapResult(Document document, LinePositionSpan linePositionSpan) { - } } diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/GeneratedCodeContainer.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/GeneratedCodeContainer.cs index aaae99fd2b..5b9c0c2d01 100644 --- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/GeneratedCodeContainer.cs +++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/GeneratedCodeContainer.cs @@ -153,6 +153,10 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem { results.Add(new SpanMapResult(document, linePositionSpan)); } + else + { + results.Add(null); + } } return Task.FromResult(results.ToImmutable()); @@ -161,31 +165,30 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem // Internal for testing. 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]; - 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 mapping = mappings[i]; var original = mapping.OriginalSpan.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 rightOffset = span.End - generated.End; - if (leftOffset >= 0 && rightOffset <= 0) - { - // This span mapping contains the span. - var adjusted = new TextSpan(original.Start + leftOffset, (original.End + rightOffset) - (original.Start + leftOffset)); - linePositionSpan = source.Lines.GetLinePositionSpan(adjusted); - return true; - } + Debug.Assert(leftOffset >= 0); + Debug.Assert(rightOffset <= 0); + + // Note: we don't handle imports here - the assumption is that for all of the scenarios we + // support, the span is in the original source document. + var adjusted = new TextSpan(original.Start + leftOffset, (original.End + rightOffset) - (original.Start + leftOffset)); + linePositionSpan = source.Lines.GetLinePositionSpan(adjusted); + return true; } linePositionSpan = default; diff --git a/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/ProjectSystem/GeneratedCodeContainerTest.cs b/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/ProjectSystem/GeneratedCodeContainerTest.cs index bb562eff1f..e394596fc7 100644 --- a/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/ProjectSystem/GeneratedCodeContainerTest.cs +++ b/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/ProjectSystem/GeneratedCodeContainerTest.cs @@ -59,6 +59,62 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem 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] public void TryGetLinePositionSpan_SpanWithinSourceMapping_ReturnsTrue() { @@ -73,8 +129,6 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem var csharpDocument = codeDocument.GetCSharpDocument(); var generatedCode = csharpDocument.GeneratedCode; - // TODO: Make writing these tests a little less manual. - // Position of `SomeProperty` in the generated code. var symbol = "SomeProperty"; var span = new TextSpan(generatedCode.IndexOf(symbol), symbol.Length);