diff --git a/src/harness/unittests/textChanges.ts b/src/harness/unittests/textChanges.ts index 61f6d738e5..8724f7aa39 100644 --- a/src/harness/unittests/textChanges.ts +++ b/src/harness/unittests/textChanges.ts @@ -650,5 +650,22 @@ import { changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), createImportSpecifier(undefined, createIdentifier("a"))); }) } + { + const text = ` +class A { + x; +}`; + runSingleFileTest("insertNodeAfterMultipleNodes", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + let newNodes = []; + for (let i = 0; i < 11 /*error doesn't occur with fewer nodes*/; ++i) { + newNodes.push( + createProperty(undefined, undefined, i + "", undefined, undefined, undefined)); + } + const insertAfter = findChild("x", sourceFile); + for (const newNode of newNodes) { + changeTracker.insertNodeAfter(sourceFile, insertAfter, newNode, { suffix: newLineCharacter }); + } + }); + } }); } \ No newline at end of file diff --git a/src/services/textChanges.ts b/src/services/textChanges.ts index 847bebdfe0..a5e4fe6f2e 100644 --- a/src/services/textChanges.ts +++ b/src/services/textChanges.ts @@ -190,6 +190,7 @@ namespace ts.textChanges { public deleteNodeInList(sourceFile: SourceFile, node: Node) { const containingList = formatting.SmartIndenter.getContainingList(node, sourceFile); if (!containingList) { + Debug.fail("node is not a list element"); return this; } const index = containingList.indexOf(node); @@ -260,6 +261,7 @@ namespace ts.textChanges { public insertNodeInListAfter(sourceFile: SourceFile, after: Node, newNode: Node) { const containingList = formatting.SmartIndenter.getContainingList(after, sourceFile); if (!containingList) { + Debug.fail("node is not a list element"); return this; } const index = containingList.indexOf(after); @@ -407,10 +409,8 @@ namespace ts.textChanges { changesPerFile.forEachValue(path => { const changesInFile = changesPerFile.get(path); const sourceFile = changesInFile[0].sourceFile; - ChangeTracker.normalize(changesInFile); - const fileTextChanges: FileTextChanges = { fileName: sourceFile.fileName, textChanges: [] }; - for (const c of changesInFile) { + for (const c of ChangeTracker.normalize(changesInFile)) { fileTextChanges.textChanges.push({ span: this.computeSpan(c, sourceFile), newText: this.computeNewText(c, sourceFile) @@ -463,11 +463,15 @@ namespace ts.textChanges { private static normalize(changes: Change[]) { // order changes by start position - changes.sort((a, b) => a.range.pos - b.range.pos); + const normalized = changes + .map((c, i) => ({ c, i })) + .sort(({ c: a, i: i1 }, { c: b, i: i2 }) => (a.range.pos - b.range.pos) || i1 - i2) + .map(({ c }) => c); // verify that end position of the change is less than start position of the next change - for (let i = 0; i < changes.length - 2; i++) { - Debug.assert(changes[i].range.end <= changes[i + 1].range.pos); + for (let i = 0; i < normalized.length - 2; i++) { + Debug.assert(normalized[i].range.end <= normalized[i + 1].range.pos); } + return normalized; } } diff --git a/tests/baselines/reference/textChanges/insertNodeAfterMultipleNodes.js b/tests/baselines/reference/textChanges/insertNodeAfterMultipleNodes.js new file mode 100644 index 0000000000..1c3bbe4d3e --- /dev/null +++ b/tests/baselines/reference/textChanges/insertNodeAfterMultipleNodes.js @@ -0,0 +1,21 @@ +===ORIGINAL=== + +class A { + x; +} +===MODIFIED=== + +class A { + x; + 0; + 1; + 2; + 3; + 4; + 5; + 6; + 7; + 8; + 9; + 10; +} \ No newline at end of file