Skip to content

Commit 04d7162

Browse files
mrochdbaeumeraeschli
authored
add TextDocument tests for invalid inputs (#1286)
* add TextDocument tests for invalid inputs * TextDocument.positionAt and offsetAt incorrectly include line endings * polish --------- Co-authored-by: Dirk Bäumer <[email protected]> Co-authored-by: Martin Aeschlimann <[email protected]>
1 parent 046423e commit 04d7162

File tree

2 files changed

+80
-28
lines changed

2 files changed

+80
-28
lines changed

textDocument/src/main.ts

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,8 @@ class FullTextDocument implements TextDocument {
297297
// low is the least x for which the line offset is larger than the current offset
298298
// or array.length if no line offset is larger than the current offset
299299
const line = low - 1;
300+
301+
offset = this.ensureBeforeEOL(offset, lineOffsets[line]);
300302
return { line, character: offset - lineOffsets[line] };
301303
}
302304

@@ -308,8 +310,20 @@ class FullTextDocument implements TextDocument {
308310
return 0;
309311
}
310312
const lineOffset = lineOffsets[position.line];
313+
if (position.character <= 0) {
314+
return lineOffset;
315+
}
316+
311317
const nextLineOffset = (position.line + 1 < lineOffsets.length) ? lineOffsets[position.line + 1] : this._content.length;
312-
return Math.max(Math.min(lineOffset + position.character, nextLineOffset), lineOffset);
318+
const offset = Math.min(lineOffset + position.character, nextLineOffset);
319+
return this.ensureBeforeEOL(offset, lineOffset);
320+
}
321+
322+
private ensureBeforeEOL(offset: number, lineOffset: number): number {
323+
while (offset > lineOffset && isEOL(this._content.charCodeAt(offset - 1))) {
324+
offset--;
325+
}
326+
return offset;
313327
}
314328

315329
public get lineCount() {
@@ -438,7 +452,7 @@ function computeLineOffsets(text: string, isAtLineStart: boolean, textOffset = 0
438452
const result: number[] = isAtLineStart ? [textOffset] : [];
439453
for (let i = 0; i < text.length; i++) {
440454
const ch = text.charCodeAt(i);
441-
if (ch === CharCode.CarriageReturn || ch === CharCode.LineFeed) {
455+
if (isEOL(ch)) {
442456
if (ch === CharCode.CarriageReturn && i + 1 < text.length && text.charCodeAt(i + 1) === CharCode.LineFeed) {
443457
i++;
444458
}
@@ -448,6 +462,10 @@ function computeLineOffsets(text: string, isAtLineStart: boolean, textOffset = 0
448462
return result;
449463
}
450464

465+
function isEOL(char: number) {
466+
return char === CharCode.CarriageReturn || char === CharCode.LineFeed;
467+
}
468+
451469
function getWellformedRange(range: Range): Range {
452470
const start = range.start;
453471
const end = range.end;

textDocument/src/test/textdocument.test.ts

Lines changed: 60 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -63,23 +63,32 @@ suite('Text Document Lines Model Validator', () => {
6363
});
6464

6565
test('New line characters', () => {
66-
let str = 'ABCDE\rFGHIJ';
67-
assert.equal(newDocument(str).lineCount, 2);
66+
let document = newDocument('ABCDE\rFGHIJ');
67+
assert.equal(document.lineCount, 2);
68+
assert.equal(document.offsetAt(Positions.create(1, 0)), 6);
6869

69-
str = 'ABCDE\nFGHIJ';
70-
assert.equal(newDocument(str).lineCount, 2);
70+
document = newDocument('ABCDE\nFGHIJ');
71+
assert.equal(document.lineCount, 2);
72+
assert.equal(document.offsetAt(Positions.create(1, 0)), 6);
7173

72-
str = 'ABCDE\r\nFGHIJ';
73-
assert.equal(newDocument(str).lineCount, 2);
74+
document = newDocument('ABCDE\r\nFGHIJ');
75+
assert.equal(document.lineCount, 2);
76+
assert.equal(document.offsetAt(Positions.create(1, 0)), 7);
7477

75-
str = 'ABCDE\n\nFGHIJ';
76-
assert.equal(newDocument(str).lineCount, 3);
78+
document = newDocument('ABCDE\n\nFGHIJ');
79+
assert.equal(document.lineCount, 3);
80+
assert.equal(document.offsetAt(Positions.create(1, 0)), 6);
81+
assert.equal(document.offsetAt(Positions.create(2, 0)), 7);
7782

78-
str = 'ABCDE\r\rFGHIJ';
79-
assert.equal(newDocument(str).lineCount, 3);
83+
document = newDocument('ABCDE\r\rFGHIJ');
84+
assert.equal(document.lineCount, 3);
85+
assert.equal(document.offsetAt(Positions.create(1, 0)), 6);
86+
assert.equal(document.offsetAt(Positions.create(2, 0)), 7);
8087

81-
str = 'ABCDE\n\rFGHIJ';
82-
assert.equal(newDocument(str).lineCount, 3);
88+
document = newDocument('ABCDE\n\rFGHIJ');
89+
assert.equal(document.lineCount, 3);
90+
assert.equal(document.offsetAt(Positions.create(1, 0)), 6);
91+
assert.equal(document.offsetAt(Positions.create(2, 0)), 7);
8392
});
8493

8594
test('getText(Range)', () => {
@@ -94,22 +103,47 @@ suite('Text Document Lines Model Validator', () => {
94103
assert.equal(document.getText(Ranges.create(0, 0, 3, 5)), str);
95104
});
96105

97-
test('Invalid inputs', () => {
98-
const str = 'Hello World';
99-
const document = newDocument(str);
106+
test('Invalid inputs at beginning of file', () => {
107+
let document = newDocument('ABCDE');
108+
assert.equal(document.offsetAt(Positions.create(-1, 0)), 0);
109+
assert.equal(document.offsetAt(Positions.create(0, -1)), 0);
110+
assert.deepEqual(document.positionAt(-1), Positions.create(0, 0));
111+
});
100112

101-
// invalid position
102-
assert.equal(document.offsetAt(Positions.create(0, str.length)), str.length);
103-
assert.equal(document.offsetAt(Positions.create(0, str.length + 3)), str.length);
104-
assert.equal(document.offsetAt(Positions.create(2, 3)), str.length);
105-
assert.equal(document.offsetAt(Positions.create(-1, 3)), 0);
106-
assert.equal(document.offsetAt(Positions.create(0, -3)), 0);
107-
assert.equal(document.offsetAt(Positions.create(1, -3)), str.length);
113+
test('Invalid inputs at end of file', () => {
114+
let str = 'ABCDE\n';
115+
let document = newDocument(str);
116+
assert.equal(document.offsetAt(Positions.create(1, 1)), str.length);
117+
assert.equal(document.offsetAt(Positions.create(2, 0)), str.length);
118+
assert.deepEqual(document.positionAt(str.length), Positions.create(1, 0));
119+
assert.deepEqual(document.positionAt(str.length + 3), Positions.create(1, 0));
120+
121+
str = 'ABCDE';
122+
document = newDocument(str);
123+
assert.equal(document.offsetAt(Positions.create(0, 10)), str.length);
124+
assert.equal(document.offsetAt(Positions.create(1, 1)), str.length);
125+
assert.deepEqual(document.positionAt(str.length), Positions.create(0, 5));
126+
assert.deepEqual(document.positionAt(str.length + 3), Positions.create(0, 5));
127+
});
108128

109-
// invalid offsets
110-
assert.deepEqual(document.positionAt(-1), Positions.create(0, 0));
111-
assert.deepEqual(document.positionAt(str.length), Positions.create(0, str.length));
112-
assert.deepEqual(document.positionAt(str.length + 3), Positions.create(0, str.length));
129+
test('Invalid inputs at beginning of line', () => {
130+
let document = newDocument('A\nB\rC\r\nD');
131+
assert.equal(document.offsetAt(Positions.create(0, -1)), 0);
132+
assert.equal(document.offsetAt(Positions.create(1, -1)), 2);
133+
assert.equal(document.offsetAt(Positions.create(2, -1)), 4);
134+
assert.equal(document.offsetAt(Positions.create(3, -1)), 7);
135+
});
136+
137+
test('Invalid inputs at end of line', () => {
138+
let document = newDocument('A\nB\rC\r\nD');
139+
assert.equal(document.offsetAt(Positions.create(0, 10)), 1);
140+
assert.equal(document.offsetAt(Positions.create(1, 10)), 3);
141+
assert.equal(document.offsetAt(Positions.create(2, 2)), 5); // between \r and \n
142+
assert.equal(document.offsetAt(Positions.create(2, 3)), 5);
143+
assert.equal(document.offsetAt(Positions.create(2, 10)), 5);
144+
assert.equal(document.offsetAt(Positions.create(3, 10)), 8);
145+
146+
assert.deepEqual(document.positionAt(6), Positions.create(2, 1)); // between \r and \n
113147
});
114148
});
115149

0 commit comments

Comments
 (0)