diff --git a/src/compiler/scanner.ts b/src/compiler/scanner.ts index b19a1466328..6dab127ca33 100644 --- a/src/compiler/scanner.ts +++ b/src/compiler/scanner.ts @@ -351,7 +351,7 @@ namespace ts { /** * We assume the first line starts at position 0 and 'position' is non-negative. */ - export function computeLineAndCharacterOfPosition(lineStarts: ReadonlyArray, position: number) { + export function computeLineAndCharacterOfPosition(lineStarts: ReadonlyArray, position: number): LineAndCharacter { let lineNumber = binarySearch(lineStarts, position); if (lineNumber < 0) { // If the actual position was not found, diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 3f9b492f903..fb59c9eb881 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -3811,9 +3811,10 @@ namespace ts { } export interface LineAndCharacter { + /** 0-based. */ line: number; /* - * This value denotes the character position in line and is different from the 'column' because of tab characters. + * 0-based. This value denotes the character position in line and is different from the 'column' because of tab characters. */ character: number; } diff --git a/src/harness/unittests/session.ts b/src/harness/unittests/session.ts index fbef9cb4558..74c5de2bbaf 100644 --- a/src/harness/unittests/session.ts +++ b/src/harness/unittests/session.ts @@ -661,4 +661,28 @@ namespace ts.server { session.consumeQueue(); }); }); + + describe("helpers", () => { + it(getLocationInNewDocument.name, () => { + const text = `// blank line\nconst x = 0;`; + const renameLocationInOldText = text.indexOf("0"); + const fileName = "/a.ts"; + const edits: ts.FileTextChanges = { + fileName, + textChanges: [ + { + span: { start: 0, length: 0 }, + newText: "const newLocal = 0;\n\n", + }, + { + span: { start: renameLocationInOldText, length: 1 }, + newText: "newLocal", + }, + ], + }; + const renameLocationInNewText = renameLocationInOldText + edits.textChanges[0].newText.length; + const res = getLocationInNewDocument(text, fileName, renameLocationInNewText, [edits]); + assert.deepEqual(res, { line: 4, offset: 11 }); + }); + }); } diff --git a/src/loc/lcl/trk/diagnosticMessages/diagnosticMessages.generated.json.lcl b/src/loc/lcl/trk/diagnosticMessages/diagnosticMessages.generated.json.lcl index bcd7c4e2982..a30e3d898dc 100644 --- a/src/loc/lcl/trk/diagnosticMessages/diagnosticMessages.generated.json.lcl +++ b/src/loc/lcl/trk/diagnosticMessages/diagnosticMessages.generated.json.lcl @@ -785,6 +785,15 @@ + + + + + + + + + @@ -1322,6 +1331,24 @@ + + + + + + + + + + + + + + + + + + @@ -1469,6 +1496,15 @@ + + + + + + + + + @@ -1876,12 +1912,12 @@ - + - + - + @@ -1997,6 +2033,15 @@ + + + + + + + + + @@ -3779,6 +3824,24 @@ + + + + + + + + + + + + + + + + + + @@ -3971,20 +4034,20 @@ - + - + - + - + - + - + @@ -7247,6 +7310,15 @@ + + + + + + + + + @@ -7727,6 +7799,15 @@ + + + + + + + + + diff --git a/src/server/session.ts b/src/server/session.ts index 4c1f839d9ec..e8ce752745e 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -1514,16 +1514,18 @@ namespace ts.server { } if (simplifiedResult) { - const file = result.renameFilename; - let location: protocol.Location | undefined; - if (file !== undefined && result.renameLocation !== undefined) { - const renameScriptInfo = project.getScriptInfoForNormalizedPath(toNormalizedPath(file)); - location = renameScriptInfo.positionToLineOffset(result.renameLocation); + const { renameFilename, renameLocation, edits } = result; + let mappedRenameLocation: protocol.Location | undefined; + if (renameFilename !== undefined && renameLocation !== undefined) { + const renameScriptInfo = project.getScriptInfoForNormalizedPath(toNormalizedPath(renameFilename)); + const snapshot = renameScriptInfo.getSnapshot(); + const oldText = snapshot.getText(0, snapshot.getLength()); + mappedRenameLocation = getLocationInNewDocument(oldText, renameFilename, renameLocation, edits); } return { - renameLocation: location, - renameFilename: file, - edits: result.edits.map(change => this.mapTextChangesToCodeEdits(project, change)) + renameLocation: mappedRenameLocation, + renameFilename, + edits: edits.map(change => this.mapTextChangesToCodeEdits(project, change)) }; } else { @@ -2040,4 +2042,26 @@ namespace ts.server { response?: {}; responseRequired?: boolean; } + + /* @internal */ // Exported only for tests + export function getLocationInNewDocument(oldText: string, renameFilename: string, renameLocation: number, edits: ReadonlyArray): protocol.Location { + const newText = applyEdits(oldText, renameFilename, edits); + const { line, character } = computeLineAndCharacterOfPosition(computeLineStarts(newText), renameLocation); + return { line: line + 1, offset: character + 1 }; + } + + function applyEdits(text: string, textFilename: string, edits: ReadonlyArray): string { + for (const { fileName, textChanges } of edits) { + if (fileName !== textFilename) { + continue; + } + + for (let i = textChanges.length - 1; i >= 0; i--) { + const { newText, span: { start, length } } = textChanges[i]; + text = text.slice(0, start) + newText + text.slice(start + length); + } + } + + return text; + } } diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index ee118279bcb..0e065b57c8e 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -2307,6 +2307,7 @@ declare namespace ts { LineFeed = 1, } interface LineAndCharacter { + /** 0-based. */ line: number; character: number; } diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 89591ca1886..a4ed91c12d0 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -2307,6 +2307,7 @@ declare namespace ts { LineFeed = 1, } interface LineAndCharacter { + /** 0-based. */ line: number; character: number; }