• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1namespace ts {
2    function editFlat(position: number, deletedLength: number, newText: string, source: string) {
3        return source.substring(0, position) + newText + source.substring(position + deletedLength, source.length);
4    }
5
6    function lineColToPosition(lineIndex: server.LineIndex, line: number, col: number) {
7        return lineIndex.absolutePositionOfStartOfLine(line) + (col - 1);
8    }
9
10    function validateEdit(lineIndex: server.LineIndex, sourceText: string, position: number, deleteLength: number, insertString: string): void {
11        const checkText = editFlat(position, deleteLength, insertString, sourceText);
12        const snapshot = lineIndex.edit(position, deleteLength, insertString);
13        const editedText = snapshot.getText(0, snapshot.getLength());
14
15        assert.equal(editedText, checkText);
16    }
17
18    describe(`unittests:: tsserver:: VersionCache TS code`, () => {
19        let validateEditAtLineCharIndex: (line: number, char: number, deleteLength: number, insertString: string) => void;
20
21        before(() => {
22            const testContent = `/// <reference path="z.ts" />
23var x = 10;
24var y = { zebra: 12, giraffe: "ell" };
25z.a;
26class Point {
27    x: number;
28}
29k=y;
30var p:Point=new Point();
31var q:Point=<Point>p;`;
32
33            const { lines } = server.LineIndex.linesFromText(testContent);
34            assert.isTrue(lines.length > 0, "Failed to initialize test text. Expected text to have at least one line");
35
36            const lineIndex = new server.LineIndex();
37            lineIndex.load(lines);
38
39            validateEditAtLineCharIndex = (line: number, char: number, deleteLength: number, insertString: string) => {
40                const position = lineColToPosition(lineIndex, line, char);
41                validateEdit(lineIndex, testContent, position, deleteLength, insertString);
42            };
43        });
44
45        after(() => {
46            validateEditAtLineCharIndex = undefined!;
47        });
48
49        it("handles empty lines array", () => {
50            const lineIndex = new server.LineIndex();
51            lineIndex.load([]);
52            assert.deepEqual(lineIndex.positionToLineOffset(0), { line: 1, offset: 1 });
53        });
54
55        it(`change 9 1 0 1 {"y"}`, () => {
56            validateEditAtLineCharIndex(9, 1, 0, "y");
57        });
58
59        it(`change 9 2 0 1 {"."}`, () => {
60            validateEditAtLineCharIndex(9, 2, 0, ".");
61        });
62
63        it(`change 9 3 0 1 {"\\n"}`, () => {
64            validateEditAtLineCharIndex(9, 3, 0, "\n");
65        });
66
67        it(`change 10 1 0 10 {"\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n"}`, () => {
68            validateEditAtLineCharIndex(10, 1, 0, "\n\n\n\n\n\n\n\n\n\n");
69        });
70
71        it(`change 19 1 1 0`, () => {
72            validateEditAtLineCharIndex(19, 1, 1, "");
73        });
74
75        it(`change 18 1 1 0`, () => {
76            validateEditAtLineCharIndex(18, 1, 1, "");
77        });
78    });
79
80    describe(`unittests:: tsserver:: VersionCache simple text`, () => {
81        let validateEditAtPosition: (position: number, deleteLength: number, insertString: string) => void;
82        let testContent: string;
83        let lines: string[];
84        let lineMap: number[];
85        before(() => {
86            testContent = `in this story:
87the lazy brown fox
88jumped over the cow
89that ate the grass
90that was purple at the tips
91and grew 1cm per day`;
92
93            ({ lines, lineMap } = server.LineIndex.linesFromText(testContent));
94            assert.isTrue(lines.length > 0, "Failed to initialize test text. Expected text to have at least one line");
95
96            const lineIndex = new server.LineIndex();
97            lineIndex.load(lines);
98
99            validateEditAtPosition = (position: number, deleteLength: number, insertString: string) => {
100                validateEdit(lineIndex, testContent, position, deleteLength, insertString);
101            };
102        });
103
104        after(() => {
105            validateEditAtPosition = undefined!;
106            testContent = undefined!;
107            lines = undefined!;
108            lineMap = undefined!;
109        });
110
111        it(`Insert at end of file`, () => {
112            validateEditAtPosition(testContent.length, 0, "hmmmm...\r\n");
113        });
114
115        it(`Unusual line endings merge`, () => {
116            validateEditAtPosition(lines[0].length - 1, lines[1].length, "");
117        });
118
119        it(`Delete whole line and nothing but line (last line)`, () => {
120            validateEditAtPosition(lineMap[lineMap.length - 2], lines[lines.length - 1].length, "");
121        });
122
123        it(`Delete whole line and nothing but line (first line)`, () => {
124            validateEditAtPosition(0, lines[0].length, "");
125        });
126
127        it(`Delete whole line (first line) and insert with no line breaks`, () => {
128            validateEditAtPosition(0, lines[0].length, "moo, moo, moo! ");
129        });
130
131        it(`Delete whole line (first line) and insert with multiple line breaks`, () => {
132            validateEditAtPosition(0, lines[0].length, "moo, \r\nmoo, \r\nmoo! ");
133        });
134
135        it(`Delete multiple lines and nothing but lines (first and second lines)`, () => {
136            validateEditAtPosition(0, lines[0].length + lines[1].length, "");
137        });
138
139        it(`Delete multiple lines and nothing but lines (second and third lines)`, () => {
140            validateEditAtPosition(lines[0].length, lines[1].length + lines[2].length, "");
141        });
142
143        it(`Insert multiple line breaks`, () => {
144            validateEditAtPosition(21, 1, "cr...\r\ncr...\r\ncr...\r\ncr...\r\ncr...\r\ncr...\r\ncr...\r\ncr...\r\ncr...\r\ncr...\r\ncr...\r\ncr");
145        });
146
147        it(`Insert multiple line breaks`, () => {
148            validateEditAtPosition(21, 1, "cr...\r\ncr...\r\ncr");
149        });
150
151        it(`Insert multiple line breaks with leading \\n`, () => {
152            validateEditAtPosition(21, 1, "\ncr...\r\ncr...\r\ncr");
153        });
154
155        it(`Single line no line breaks deleted or inserted, delete 1 char`, () => {
156            validateEditAtPosition(21, 1, "");
157        });
158
159        it(`Single line no line breaks deleted or inserted, insert 1 char`, () => {
160            validateEditAtPosition(21, 0, "b");
161        });
162
163        it(`Single line no line breaks deleted or inserted, delete 1, insert 2 chars`, () => {
164            validateEditAtPosition(21, 1, "cr");
165        });
166
167        it(`Delete across line break (just the line break)`, () => {
168            validateEditAtPosition(21, 22, "");
169        });
170
171        it(`Delete across line break`, () => {
172            validateEditAtPosition(21, 32, "");
173        });
174
175        it(`Delete across multiple line breaks and insert no line breaks`, () => {
176            validateEditAtPosition(21, 42, "");
177        });
178
179        it(`Delete across multiple line breaks and insert text`, () => {
180            validateEditAtPosition(21, 42, "slithery ");
181        });
182    });
183
184    describe(`unittests:: tsserver:: VersionCache stress test`, () => {
185        let rsa: number[] = [];
186        let la: number[] = [];
187        let las: number[] = [];
188        let elas: number[] = [];
189        let ersa: number[] = [];
190        let ela: number[] = [];
191        const iterationCount = 20;
192        // const iterationCount = 20000; // uncomment for testing
193        let lines: string[];
194        let lineMap: number[];
195        let lineIndex: server.LineIndex;
196        let testContent: string;
197
198        before(() => {
199            // Use scanner.ts, decent size, does not change frequently
200            const testFileName = "src/compiler/scanner.ts";
201            testContent = Harness.IO.readFile(testFileName)!;
202            const totalChars = testContent.length;
203            assert.isTrue(totalChars > 0, "Failed to read test file.");
204
205            ({ lines, lineMap } = server.LineIndex.linesFromText(testContent));
206            assert.isTrue(lines.length > 0, "Failed to initialize test text. Expected text to have at least one line");
207
208            lineIndex = new server.LineIndex();
209            lineIndex.load(lines);
210
211            let etotalChars = totalChars;
212
213            for (let j = 0; j < 100000; j++) {
214                rsa[j] = Math.floor(Math.random() * totalChars);
215                la[j] = Math.floor(Math.random() * (totalChars - rsa[j]));
216                if (la[j] > 4) {
217                    las[j] = 4;
218                }
219                else {
220                    las[j] = la[j];
221                }
222                if (j < 4000) {
223                    ersa[j] = Math.floor(Math.random() * etotalChars);
224                    ela[j] = Math.floor(Math.random() * (etotalChars - ersa[j]));
225                    if (ela[j] > 4) {
226                        elas[j] = 4;
227                    }
228                    else {
229                        elas[j] = ela[j];
230                    }
231                    etotalChars += (las[j] - elas[j]);
232                }
233            }
234        });
235
236        after(() => {
237            rsa = undefined!;
238            la = undefined!;
239            las = undefined!;
240            elas = undefined!;
241            ersa = undefined!;
242            ela = undefined!;
243            lines = undefined!;
244            lineMap = undefined!;
245            lineIndex = undefined!;
246            testContent = undefined!;
247        });
248
249        it("Range (average length 1/4 file size)", () => {
250            for (let i = 0; i < iterationCount; i++) {
251                const s2 = lineIndex.getText(rsa[i], la[i]);
252                const s1 = testContent.substring(rsa[i], rsa[i] + la[i]);
253                assert.equal(s1, s2);
254            }
255        });
256
257        it("Range (average length 4 chars)", () => {
258            for (let j = 0; j < iterationCount; j++) {
259                const s2 = lineIndex.getText(rsa[j], las[j]);
260                const s1 = testContent.substring(rsa[j], rsa[j] + las[j]);
261                assert.equal(s1, s2);
262            }
263        });
264
265        it("Edit (average length 4)", () => {
266            for (let i = 0; i < iterationCount; i++) {
267                const insertString = testContent.substring(rsa[100000 - i], rsa[100000 - i] + las[100000 - i]);
268                const snapshot = lineIndex.edit(rsa[i], las[i], insertString);
269                const checkText = editFlat(rsa[i], las[i], insertString, testContent);
270                const snapText = snapshot.getText(0, checkText.length);
271                assert.equal(checkText, snapText);
272            }
273        });
274
275        it("Edit ScriptVersionCache ", () => {
276            const svc = server.ScriptVersionCache.fromString(testContent);
277            let checkText = testContent;
278
279            for (let i = 0; i < iterationCount; i++) {
280                const insertString = testContent.substring(rsa[i], rsa[i] + las[i]);
281                svc.edit(ersa[i], elas[i], insertString);
282                checkText = editFlat(ersa[i], elas[i], insertString, checkText);
283                if (0 === (i % 4)) {
284                    assert.equal(checkText, getSnapshotText(svc.getSnapshot()));
285                }
286            }
287        });
288
289        it("Edit (average length 1/4th file size)", () => {
290            for (let i = 0; i < iterationCount; i++) {
291                const insertString = testContent.substring(rsa[100000 - i], rsa[100000 - i] + la[100000 - i]);
292                const snapshot = lineIndex.edit(rsa[i], la[i], insertString);
293                const checkText = editFlat(rsa[i], la[i], insertString, testContent);
294                const snapText = snapshot.getText(0, checkText.length);
295                assert.equal(checkText, snapText);
296            }
297        });
298
299        it("Line/offset from pos", () => {
300            for (let i = 0; i < iterationCount; i++) {
301                const lp = lineIndex.positionToLineOffset(rsa[i]);
302                const lac = computeLineAndCharacterOfPosition(lineMap, rsa[i]);
303                assert.equal(lac.line + 1, lp.line, "Line number mismatch " + (lac.line + 1) + " " + lp.line + " " + i);
304                assert.equal(lac.character, lp.offset - 1, "Character offset mismatch " + lac.character + " " + (lp.offset - 1) + " " + i);
305            }
306        });
307
308        it("Start pos from line", () => {
309            for (let i = 0; i < iterationCount; i++) {
310                for (let j = 0; j < lines.length; j++) {
311                    assert.equal(lineIndex.absolutePositionOfStartOfLine(j + 1), lineMap[j]);
312                }
313            }
314        });
315    });
316}
317