From 59134dc2bd5a1bce047270049d40b8bd749e6a2a Mon Sep 17 00:00:00 2001 From: Andy Date: Thu, 7 Jun 2018 15:09:33 -0700 Subject: [PATCH] Support 'tsconfig.json' when converting TextChanges to CodeEdits (#24667) (#24772) * Support 'tsconfig.json' when converting TextChanges to CodeEdits * Create Project#getSourceFileOrConfigFile to use instead --- .../unittests/tsserverProjectSystem.ts | 65 ++++++++++++++++++- src/server/project.ts | 6 ++ src/server/session.ts | 55 +++++++++------- .../reference/api/tsserverlibrary.d.ts | 2 - 4 files changed, 103 insertions(+), 25 deletions(-) diff --git a/src/harness/unittests/tsserverProjectSystem.ts b/src/harness/unittests/tsserverProjectSystem.ts index a2125a1fb6019..f3bcbc4584553 100644 --- a/src/harness/unittests/tsserverProjectSystem.ts +++ b/src/harness/unittests/tsserverProjectSystem.ts @@ -466,7 +466,7 @@ namespace ts.projectSystem { return newRequest; } - export function openFilesForSession(files: File[], session: server.Session) { + export function openFilesForSession(files: ReadonlyArray, session: server.Session) { for (const file of files) { const request = makeSessionRequest(CommandNames.Open, { file: file.path }); session.executeCommand(request); @@ -6194,6 +6194,69 @@ namespace ts.projectSystem { renameLocation: { line: 2, offset: 3 }, }); }); + + it("handles text changes in tsconfig.json", () => { + const aTs = { + path: "/a.ts", + content: "export const a = 0;", + }; + const tsconfig = { + path: "/tsconfig.json", + content: '{ "files": ["./a.ts"] }', + }; + + const session = createSession(createServerHost([aTs, tsconfig])); + openFilesForSession([aTs], session); + + const response1 = session.executeCommandSeq({ + command: server.protocol.CommandTypes.GetEditsForRefactor, + arguments: { + refactor: "Move to a new file", + action: "Move to a new file", + file: "/a.ts", + startLine: 1, + startOffset: 1, + endLine: 1, + endOffset: 20, + }, + }).response; + assert.deepEqual(response1, { + edits: [ + { + fileName: "/a.ts", + textChanges: [ + { + start: { line: 1, offset: 1 }, + end: { line: 1, offset: 20 }, + newText: "", + }, + ], + }, + { + fileName: "/tsconfig.json", + textChanges: [ + { + start: { line: 1, offset: 21 }, + end: { line: 1, offset: 21 }, + newText: ", \"./a.1.ts\"", + }, + ], + }, + { + fileName: "/a.1.ts", + textChanges: [ + { + start: { line: 0, offset: 0 }, + end: { line: 0, offset: 0 }, + newText: "export const a = 0;", + }, + ], + } + ], + renameFilename: undefined, + renameLocation: undefined, + }); + }); }); describe("tsserverProjectSystem CachingFileSystemInformation", () => { diff --git a/src/server/project.ts b/src/server/project.ts index 754d061235d7c..6b83527d59643 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -550,6 +550,12 @@ namespace ts.server { return this.program.getSourceFileByPath(path); } + /* @internal */ + getSourceFileOrConfigFile(path: Path): SourceFile | undefined { + const options = this.program.getCompilerOptions(); + return path === options.configFilePath ? options.configFile : this.getSourceFile(path); + } + close() { if (this.program) { // if we have a program - release all files that are enlisted in program but arent root diff --git a/src/server/session.ts b/src/server/session.ts index 4462b4ba701ec..93468de0a8dc3 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -1766,20 +1766,10 @@ namespace ts.server { } private mapTextChangesToCodeEdits(project: Project, textChanges: ReadonlyArray): protocol.FileCodeEdits[] { - return textChanges.map(change => this.mapTextChangesToCodeEditsUsingScriptinfo(change, project.getScriptInfoForNormalizedPath(toNormalizedPath(change.fileName)))); - } - - private mapTextChangesToCodeEditsUsingScriptinfo(textChanges: FileTextChanges, scriptInfo: ScriptInfo | undefined): protocol.FileCodeEdits { - Debug.assert(!!textChanges.isNewFile === !scriptInfo); - if (scriptInfo) { - return { - fileName: textChanges.fileName, - textChanges: textChanges.textChanges.map(textChange => this.convertTextChangeToCodeEdit(textChange, scriptInfo)) - }; - } - else { - return this.convertNewFileTextChangeToCodeEdit(textChanges); - } + return textChanges.map(change => { + const path = normalizedPathToPath(toNormalizedPath(change.fileName), this.host.getCurrentDirectory(), fileName => this.getCanonicalFileName(fileName)); + return mapTextChangesToCodeEdits(change, project.getSourceFileOrConfigFile(path)); + }); } private convertTextChangeToCodeEdit(change: TextChange, scriptInfo: ScriptInfo): protocol.CodeEdit { @@ -1790,14 +1780,7 @@ namespace ts.server { }; } - private convertNewFileTextChangeToCodeEdit(textChanges: FileTextChanges): protocol.FileCodeEdits { - Debug.assert(textChanges.textChanges.length === 1); - const change = first(textChanges.textChanges); - Debug.assert(change.span.start === 0 && change.span.length === 0); - return { fileName: textChanges.fileName, textChanges: [{ start: { line: 0, offset: 0 }, end: { line: 0, offset: 0 }, newText: change.newText }] }; - } - - private getBraceMatching(args: protocol.FileLocationRequestArgs, simplifiedResult: boolean): protocol.TextSpan[] | TextSpan[] { + private getBraceMatching(args: protocol.FileLocationRequestArgs, simplifiedResult: boolean): protocol.TextSpan[] | TextSpan[] | undefined { const { file, languageService } = this.getFileAndLanguageServiceForSyntacticOperation(args); const scriptInfo = this.projectService.getScriptInfoForNormalizedPath(file); const position = this.getPosition(args, scriptInfo); @@ -2272,6 +2255,34 @@ namespace ts.server { } } + function mapTextChangesToCodeEdits(textChanges: FileTextChanges, sourceFile: SourceFile | undefined): protocol.FileCodeEdits { + Debug.assert(!!textChanges.isNewFile === !sourceFile); + if (sourceFile) { + return { + fileName: textChanges.fileName, + textChanges: textChanges.textChanges.map(textChange => convertTextChangeToCodeEdit(textChange, sourceFile)), + }; + } + else { + return convertNewFileTextChangeToCodeEdit(textChanges); + } + } + + function convertTextChangeToCodeEdit(change: TextChange, sourceFile: SourceFile): protocol.CodeEdit { + return { + start: convertToLocation(sourceFile.getLineAndCharacterOfPosition(change.span.start)), + end: convertToLocation(sourceFile.getLineAndCharacterOfPosition(change.span.start + change.span.length)), + newText: change.newText ? change.newText : "", + }; + } + + function convertNewFileTextChangeToCodeEdit(textChanges: FileTextChanges): protocol.FileCodeEdits { + Debug.assert(textChanges.textChanges.length === 1); + const change = first(textChanges.textChanges); + Debug.assert(change.span.start === 0 && change.span.length === 0); + return { fileName: textChanges.fileName, textChanges: [{ start: { line: 0, offset: 0 }, end: { line: 0, offset: 0 }, newText: change.newText }] }; + } + export interface HandlerResponse { response?: {}; responseRequired?: boolean; diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 67733c1fb1a68..cee465384614a 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -8523,9 +8523,7 @@ declare namespace ts.server { private mapCodeAction; private mapCodeFixAction; private mapTextChangesToCodeEdits; - private mapTextChangesToCodeEditsUsingScriptinfo; private convertTextChangeToCodeEdit; - private convertNewFileTextChangeToCodeEdit; private getBraceMatching; private getDiagnosticsForProject; getCanonicalFileName(fileName: string): string;