/// /// module ts { function editFlat(position: number, deletedLength: number, newText: string, source: string) { return source.substring(0, position) + newText + source.substring(position + deletedLength, source.length); } function lineColToPosition(lineIndex: server.LineIndex, line: number, col: number) { var lineInfo = lineIndex.lineNumberToInfo(line); return (lineInfo.offset + col - 1); } function validateEdit(lineIndex: server.LineIndex, sourceText: string, position: number, deleteLength: number, insertString: string): void { let checkText = editFlat(position, deleteLength, insertString, sourceText); let snapshot = lineIndex.edit(position, deleteLength, insertString); let editedText = snapshot.getText(0, snapshot.getLength()); assert.equal(editedText, checkText); } describe('VersionCache TS code', () => { var testContent = `/// var x = 10; var y = { zebra: 12, giraffe: "ell" }; z.a; class Point { x: number; } k=y; var p:Point=new Point(); var q:Point=p;` let {lines, lineMap} = server.LineIndex.linesFromText(testContent); assert.isTrue(lines.length > 0, "Failed to initialize test text. Expected text to have at least one line"); let lineIndex = new server.LineIndex(); lineIndex.load(lines); function validateEditAtLineCharIndex(line: number, char: number, deleteLength: number, insertString: string): void { let position = lineColToPosition(lineIndex, line, char); validateEdit(lineIndex, testContent, position, deleteLength, insertString); } it('change 9 1 0 1 {"y"}', () => { validateEditAtLineCharIndex(9, 1, 0, "y"); }); it('change 9 2 0 1 {"."}', () => { validateEditAtLineCharIndex(9, 2, 0, "."); }); it('change 9 3 0 1 {"\\n"}', () => { validateEditAtLineCharIndex(9, 3, 0, "\n"); }); it('change 10 1 0 10 {"\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n"}', () => { validateEditAtLineCharIndex(10, 1, 0, "\n\n\n\n\n\n\n\n\n\n"); }); it('change 19 1 1 0', () => { validateEditAtLineCharIndex(19, 1, 1, ""); }); it('change 18 1 1 0', () => { validateEditAtLineCharIndex(18, 1, 1, ""); }); }); describe('VersionCache simple text', () => { let testContent = `in this story: the lazy brown fox jumped over the cow that ate the grass that was purple at the tips and grew 1cm per day`; let {lines, lineMap} = server.LineIndex.linesFromText(testContent); assert.isTrue(lines.length > 0, "Failed to initialize test text. Expected text to have at least one line"); let lineIndex = new server.LineIndex(); lineIndex.load(lines); function validateEditAtPosition(position: number, deleteLength: number, insertString: string): void { validateEdit(lineIndex, testContent, position, deleteLength, insertString); } it('Insert at end of file', () => { validateEditAtPosition(testContent.length, 0, "hmmmm...\r\n"); }); it('Unusual line endings merge', () => { validateEditAtPosition(lines[0].length - 1, lines[1].length, ""); }); it('Delete whole line and nothing but line (last line)', () => { validateEditAtPosition(lineMap[lineMap.length - 2], lines[lines.length - 1].length, ""); }); it('Delete whole line and nothing but line (first line)', () => { validateEditAtPosition(0, lines[0].length, ""); }); it('Delete whole line (first line) and insert with no line breaks', () => { validateEditAtPosition(0, lines[0].length, "moo, moo, moo! "); }); it('Delete whole line (first line) and insert with multiple line breaks', () => { validateEditAtPosition(0, lines[0].length, "moo, \r\nmoo, \r\nmoo! "); }); it('Delete multiple lines and nothing but lines (first and second lines)', () => { validateEditAtPosition(0, lines[0].length + lines[1].length, ""); }); it('Delete multiple lines and nothing but lines (second and third lines)', () => { validateEditAtPosition(lines[0].length, lines[1].length + lines[2].length, ""); }); it('Insert multiple line breaks', () => { 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"); }); it('Insert multiple line breaks', () => { validateEditAtPosition(21, 1, "cr...\r\ncr...\r\ncr"); }); it('Insert multiple line breaks with leading \\n', () => { validateEditAtPosition(21, 1, "\ncr...\r\ncr...\r\ncr"); }); it('Single line no line breaks deleted or inserted, delete 1 char', () => { validateEditAtPosition(21, 1, ""); }); it('Single line no line breaks deleted or inserted, insert 1 char', () => { validateEditAtPosition(21, 0, "b"); }); it('Single line no line breaks deleted or inserted, delete 1, insert 2 chars', () => { validateEditAtPosition(21, 1, "cr"); }); it('Delete across line break (just the line break)', () => { validateEditAtPosition(21, 22, ""); }); it('Delete across line break', () => { validateEditAtPosition(21, 32, ""); }); it('Delete across multiple line breaks and insert no line breaks', () => { validateEditAtPosition(21, 42, ""); }); it('Delete across multiple line breaks and insert text', () => { validateEditAtPosition(21, 42, "slithery "); }); }); describe('VersionCache stress test', () => { const iterationCount = 20; //const interationCount = 20000; // uncomment for testing // Use scanner.ts, decent size, does not change frequentlly let testFileName = "src/compiler/scanner.ts"; let testContent = Harness.IO.readFile(testFileName); let totalChars = testContent.length; assert.isTrue(totalChars > 0, "Failed to read test file."); let {lines, lineMap} = server.LineIndex.linesFromText(testContent); assert.isTrue(lines.length > 0, "Failed to initialize test text. Expected text to have at least one line"); let lineIndex = new server.LineIndex(); lineIndex.load(lines); let rsa: number[] = []; let la: number[] = []; let las: number[] = []; let elas: number[] = []; let ersa: number[] = []; let ela: number[] = []; let etotalChars = totalChars; for (let j = 0; j < 100000; j++) { rsa[j] = Math.floor(Math.random() * totalChars); la[j] = Math.floor(Math.random() * (totalChars - rsa[j])); if (la[j] > 4) { las[j] = 4; } else { las[j] = la[j]; } if (j < 4000) { ersa[j] = Math.floor(Math.random() * etotalChars); ela[j] = Math.floor(Math.random() * (etotalChars - ersa[j])); if (ela[j] > 4) { elas[j] = 4; } else { elas[j] = ela[j]; } etotalChars += (las[j] - elas[j]); } } it("Range (average length 1/4 file size)", () => { for (let i = 0; i < iterationCount; i++) { let s2 = lineIndex.getText(rsa[i], la[i]); let s1 = testContent.substring(rsa[i], rsa[i] + la[i]); assert.equal(s1, s2); } }); it("Range (average length 4 chars)", () => { for (let j = 0; j < iterationCount; j++) { let s2 = lineIndex.getText(rsa[j], las[j]); let s1 = testContent.substring(rsa[j], rsa[j] + las[j]); assert.equal(s1, s2); } }); it("Edit (average length 4)", () => { for (let i = 0; i < iterationCount; i++) { let insertString = testContent.substring(rsa[100000 - i], rsa[100000 - i] + las[100000 - i]); let snapshot = lineIndex.edit(rsa[i], las[i], insertString); let checkText = editFlat(rsa[i], las[i], insertString, testContent); let snapText = snapshot.getText(0, checkText.length); assert.equal(checkText, snapText); } }); it("Edit ScriptVersionCache ", () => { let svc = server.ScriptVersionCache.fromString(ts.sys, testContent); let checkText = testContent; for (let i = 0; i < iterationCount; i++) { let insertString = testContent.substring(rsa[i], rsa[i] + las[i]); svc.edit(ersa[i], elas[i], insertString); checkText = editFlat(ersa[i], elas[i], insertString, checkText); if (0 == (i % 4)) { let snap = svc.getSnapshot(); let snapText = snap.getText(0, checkText.length); assert.equal(checkText, snapText); } } }); it("Edit (average length 1/4th file size)", () => { for (let i = 0; i < iterationCount; i++) { let insertString = testContent.substring(rsa[100000 - i], rsa[100000 - i] + la[100000 - i]); let snapshot = lineIndex.edit(rsa[i], la[i], insertString); let checkText = editFlat(rsa[i], la[i], insertString, testContent); let snapText = snapshot.getText(0, checkText.length); assert.equal(checkText, snapText); } }); it("Line/offset from pos", () => { for (let i = 0; i < iterationCount; i++) { let lp = lineIndex.charOffsetToLineNumberAndPos(rsa[i]); let lac = ts.computeLineAndCharacterOfPosition(lineMap, rsa[i]); assert.equal(lac.line + 1, lp.line, "Line number mismatch " + (lac.line + 1) + " " + lp.line + " " + i); assert.equal(lac.character, (lp.offset), "Charachter offset mismatch " + lac.character + " " + lp.offset + " " + i); } }); it("Start pos from line", () => { for (let i = 0; i < iterationCount; i++) { for (let j = 0, llen = lines.length; j < llen; j++) { let lineInfo = lineIndex.lineNumberToInfo(j + 1); let lineIndexOffset = lineInfo.offset; let lineMapOffset = lineMap[j]; assert.equal(lineIndexOffset, lineMapOffset); } } }); }); }