Skip to content

Commit 62c2cf9

Browse files
authored
Fix issue with when a CR/LF split occurs at a SubText's underlying span start (#80911)
I hit an exception in the SourceText system when inserting a copilot edit for C# content in a razor file when the file uses newlines only. This ended up causing a CR/LF split in the subtext class, which had some bogus logic for when that happened. Added a very simple unit test that demonstrated previous failure.
1 parent 511a87e commit 62c2cf9

File tree

3 files changed

+55
-16
lines changed

3 files changed

+55
-16
lines changed

src/Compilers/Core/CodeAnalysisTest/Text/CompositeTextTests.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ private static IEnumerable<string> GetLinesTexts(TextLineCollection textLines)
6363
sourceTextsBuilder.AddRange(sourceTexts);
6464

6565
var compositeText = (CompositeText)CompositeText.ToSourceText(sourceTextsBuilder, sourceText, adjustSegments: false);
66+
sourceTextsBuilder.Free();
67+
6668
yield return (sourceText, compositeText);
6769
}
6870
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using Microsoft.CodeAnalysis.Text;
6+
using Xunit;
7+
8+
namespace Microsoft.CodeAnalysis.UnitTests.Text
9+
{
10+
public class SubTextTests
11+
{
12+
[Theory]
13+
[InlineData("abcdefghijkl")]
14+
[InlineData(["\r\r\r\r\r\r\r\r\r\r\r\r"])]
15+
[InlineData(["\n\n\n\n\n\n\n\n\n\n\n\n"])]
16+
[InlineData(["\r\n\r\n\r\n\r\n\r\n\r\n"])]
17+
[InlineData(["\n\r\n\r\n\r\n\r\n\r\n\r"])]
18+
[InlineData(["a\r\nb\r\nc\r\nd\r\n"])]
19+
[InlineData(["\ra\n\rb\n\rc\n\rd\n"])]
20+
[InlineData(["\na\r\nb\r\nc\r\nd\r"])]
21+
[InlineData(["ab\r\ncd\r\nef\r\n"])]
22+
[InlineData(["ab\r\r\ncd\r\r\nef"])]
23+
[InlineData(["ab\n\n\rcd\n\n\ref"])]
24+
[InlineData(["ab\u0085cdef\u2028ijkl\u2029op"])]
25+
[InlineData(["\u0085\u2028\u2029\u0085\u2028\u2029\u0085\u2028\u2029\u0085\u2028\u2029"])]
26+
public void SubTextTestAllPossibleSubstrings(string contents)
27+
{
28+
var fullStringText = SourceText.From(contents);
29+
for (var start = 0; start < contents.Length; start++)
30+
{
31+
for (var end = start + 1; end <= contents.Length; end++)
32+
{
33+
var stringText = SourceText.From(contents[start..end]);
34+
var subText = new SubText(fullStringText, new TextSpan(start, length: end - start));
35+
36+
Assert.Equal(stringText.Length, subText.Length);
37+
for (var i = 0; i < stringText.Length; i++)
38+
{
39+
Assert.Equal(stringText.Lines.IndexOf(i), subText.Lines.IndexOf(i));
40+
}
41+
42+
Assert.Equal(stringText.Lines.Count, subText.Lines.Count);
43+
for (var i = 0; i < stringText.Lines.Count; i++)
44+
{
45+
Assert.Equal(stringText.Lines[i].ToString(), subText.Lines[i].ToString());
46+
Assert.Equal(stringText.Lines[i].EndIncludingLineBreak, subText.Lines[i].EndIncludingLineBreak);
47+
}
48+
}
49+
}
50+
}
51+
}
52+
}

src/Compilers/Core/Portable/Text/SubText.cs

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,6 @@ private sealed class SubTextLineInfo : TextLineCollection
103103
private readonly SubText _subText;
104104
private readonly int _startLineNumberInUnderlyingText;
105105
private readonly int _lineCount;
106-
private readonly bool _startsWithinSplitCRLF;
107106
private readonly bool _endsWithinSplitCRLF;
108107

109108
public SubTextLineInfo(SubText subText)
@@ -116,14 +115,6 @@ public SubTextLineInfo(SubText subText)
116115
_startLineNumberInUnderlyingText = startLineInUnderlyingText.LineNumber;
117116
_lineCount = (endLineInUnderlyingText.LineNumber - _startLineNumberInUnderlyingText) + 1;
118117

119-
var underlyingSpanStart = _subText.UnderlyingSpan.Start;
120-
if (underlyingSpanStart == startLineInUnderlyingText.End + 1 &&
121-
underlyingSpanStart == startLineInUnderlyingText.EndIncludingLineBreak - 1)
122-
{
123-
Debug.Assert(_subText.UnderlyingText[underlyingSpanStart - 1] == '\r' && _subText.UnderlyingText[underlyingSpanStart] == '\n');
124-
_startsWithinSplitCRLF = true;
125-
}
126-
127118
var underlyingSpanEnd = _subText.UnderlyingSpan.End;
128119
if (underlyingSpanEnd == endLineInUnderlyingText.End + 1 &&
129120
underlyingSpanEnd == endLineInUnderlyingText.EndIncludingLineBreak - 1)
@@ -151,7 +142,7 @@ public override TextLine this[int lineNumber]
151142
// Special case splitting the CRLF at the end as the UnderlyingText doesn't view the position
152143
// after between the \r and \n as on a new line whereas this subtext doesn't contain the \n
153144
// and needs to view that position as on a new line.
154-
return TextLine.FromSpanUnsafe(_subText, new TextSpan(_subText.UnderlyingSpan.End, 0));
145+
return TextLine.FromSpanUnsafe(_subText, new TextSpan(_subText.UnderlyingSpan.Length, 0));
155146
}
156147

157148
var underlyingTextLine = _subText.UnderlyingText.Lines[lineNumber + _startLineNumberInUnderlyingText];
@@ -210,12 +201,6 @@ public override int IndexOf(int position)
210201
var underlyingPosition = position + _subText.UnderlyingSpan.Start;
211202
var underlyingLineNumber = _subText.UnderlyingText.Lines.IndexOf(underlyingPosition);
212203

213-
if (_startsWithinSplitCRLF && position != 0)
214-
{
215-
// The \n contributes a line to the count in this subtext, but not in the UnderlyingText.
216-
underlyingLineNumber += 1;
217-
}
218-
219204
return underlyingLineNumber - _startLineNumberInUnderlyingText;
220205
}
221206
}

0 commit comments

Comments
 (0)