From 420d1d304459d540832bcbbdfbfe2540cd2eb2c9 Mon Sep 17 00:00:00 2001 From: Benjamin Lichtman Date: Fri, 25 Jan 2019 11:56:53 -0800 Subject: [PATCH 1/5] Allow per-file setting for rename default behavior preferences --- src/server/session.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/server/session.ts b/src/server/session.ts index 2257c6740f498..77167d907a913 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -1178,7 +1178,7 @@ namespace ts.server { private getRenameInfo(args: protocol.FileLocationRequestArgs): RenameInfo { const { file, project } = this.getFileAndProject(args); const position = this.getPositionInFile(args, file); - const preferences = this.getHostPreferences(); + const preferences = {...this.getHostPreferences(), ...this.getPreferences(file)}; return project.getLanguageService().getRenameInfo(file, position, { allowRenameOfImportPath: preferences.allowRenameOfImportPath }); } @@ -1234,12 +1234,13 @@ namespace ts.server { { fileName: args.file, pos: position }, !!args.findInStrings, !!args.findInComments, - this.getHostPreferences() + {...this.getHostPreferences(), ...this.getPreferences(file)} ); if (!simplifiedResult) return locations; const defaultProject = this.getDefaultProject(args); - const renameInfo: protocol.RenameInfo = this.mapRenameInfo(defaultProject.getLanguageService().getRenameInfo(file, position, { allowRenameOfImportPath: this.getHostPreferences().allowRenameOfImportPath }), Debug.assertDefined(this.projectService.getScriptInfo(file))); + const preferences = {...this.getHostPreferences(), ...this.getPreferences(file)}; + const renameInfo: protocol.RenameInfo = this.mapRenameInfo(defaultProject.getLanguageService().getRenameInfo(file, position, { allowRenameOfImportPath: preferences.allowRenameOfImportPath }), Debug.assertDefined(this.projectService.getScriptInfo(file))); return { info: renameInfo, locs: this.toSpanGroups(locations) }; } From 9898748274139bf0ff81c3786d6d21686e5567ba Mon Sep 17 00:00:00 2001 From: Benjamin Lichtman Date: Fri, 25 Jan 2019 15:24:58 -0800 Subject: [PATCH 2/5] Fix linting --- src/server/session.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/server/session.ts b/src/server/session.ts index 77167d907a913..0a5830844d4a5 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -1178,7 +1178,7 @@ namespace ts.server { private getRenameInfo(args: protocol.FileLocationRequestArgs): RenameInfo { const { file, project } = this.getFileAndProject(args); const position = this.getPositionInFile(args, file); - const preferences = {...this.getHostPreferences(), ...this.getPreferences(file)}; + const preferences = { ...this.getHostPreferences(), ...this.getPreferences(file) }; return project.getLanguageService().getRenameInfo(file, position, { allowRenameOfImportPath: preferences.allowRenameOfImportPath }); } @@ -1234,12 +1234,12 @@ namespace ts.server { { fileName: args.file, pos: position }, !!args.findInStrings, !!args.findInComments, - {...this.getHostPreferences(), ...this.getPreferences(file)} + { ...this.getHostPreferences(), ...this.getPreferences(file) } ); if (!simplifiedResult) return locations; const defaultProject = this.getDefaultProject(args); - const preferences = {...this.getHostPreferences(), ...this.getPreferences(file)}; + const preferences = { ...this.getHostPreferences(), ...this.getPreferences(file) }; const renameInfo: protocol.RenameInfo = this.mapRenameInfo(defaultProject.getLanguageService().getRenameInfo(file, position, { allowRenameOfImportPath: preferences.allowRenameOfImportPath }), Debug.assertDefined(this.projectService.getScriptInfo(file))); return { info: renameInfo, locs: this.toSpanGroups(locations) }; } From b0b23b3f0c1dcc7be567deee5dac59ec15d19186 Mon Sep 17 00:00:00 2001 From: Benjamin Lichtman Date: Fri, 25 Jan 2019 15:25:04 -0800 Subject: [PATCH 3/5] Add tests --- src/testRunner/unittests/tsserver/rename.ts | 46 ++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/src/testRunner/unittests/tsserver/rename.ts b/src/testRunner/unittests/tsserver/rename.ts index 571db235a6e5a..527868de02c25 100644 --- a/src/testRunner/unittests/tsserver/rename.ts +++ b/src/testRunner/unittests/tsserver/rename.ts @@ -7,6 +7,7 @@ namespace ts.projectSystem { const session = createSession(createServerHost([aTs, bTs])); openFilesForSession([bTs], session); + // rename fails with allowRenameOfImportPath disabled const response1 = executeSessionRequest(session, protocol.CommandTypes.Rename, protocolFileLocationFromSubstring(bTs, 'a";')); assert.deepEqual(response1, { info: { @@ -16,6 +17,7 @@ namespace ts.projectSystem { locs: [{ file: bTs.path, locs: [protocolRenameSpanFromSubstring(bTs.content, "./a")] }], }); + // rename succeeds with allowRenameOfImportPath enabled in host session.getProjectService().setHostConfiguration({ preferences: { allowRenameOfImportPath: true } }); const response2 = executeSessionRequest(session, protocol.CommandTypes.Rename, protocolFileLocationFromSubstring(bTs, 'a";')); assert.deepEqual(response2, { @@ -30,6 +32,23 @@ namespace ts.projectSystem { }, locs: [{ file: bTs.path, locs: [protocolRenameSpanFromSubstring(bTs.content, "./a")] }], }); + + // rename succeeds with allowRenameOfImportPath enabled in file + session.getProjectService().setHostConfiguration({ preferences: { allowRenameOfImportPath: false } }); + session.getProjectService().setHostConfiguration({ file: "/b.ts", formatOptions: {}, preferences: { allowRenameOfImportPath: true } }); + const response3 = executeSessionRequest(session, protocol.CommandTypes.Rename, protocolFileLocationFromSubstring(bTs, 'a";')); + assert.deepEqual(response3, { + info: { + canRename: true, + fileToRename: aTs.path, + displayName: aTs.path, + fullDisplayName: aTs.path, + kind: ScriptElementKind.moduleElement, + kindModifiers: "", + triggerSpan: protocolTextSpanFromSubstring(bTs.content, "a", { index: 1 }), + }, + locs: [{ file: bTs.path, locs: [protocolRenameSpanFromSubstring(bTs.content, "./a")] }], + }); }); it("works with prefixText and suffixText when enabled", () => { @@ -61,7 +80,7 @@ namespace ts.projectSystem { ], }); - // rename with prefixText and suffixText enabled + // rename with prefixText and suffixText enabled in host session.getProjectService().setHostConfiguration({ preferences: { providePrefixAndSuffixTextForRename: true } }); const response2 = executeSessionRequest(session, protocol.CommandTypes.Rename, protocolFileLocationFromSubstring(aTs, "x")); assert.deepEqual(response2, { @@ -84,6 +103,31 @@ namespace ts.projectSystem { }, ], }); + + // rename with prefixText and suffixText enabled for file + session.getProjectService().setHostConfiguration({ preferences: { providePrefixAndSuffixTextForRename: false } }); + session.getProjectService().setHostConfiguration({ file: "/a.ts", formatOptions: {}, preferences: { providePrefixAndSuffixTextForRename: true } }); + const response3 = executeSessionRequest(session, protocol.CommandTypes.Rename, protocolFileLocationFromSubstring(aTs, "x")); + assert.deepEqual(response3, { + info: { + canRename: true, + fileToRename: undefined, + displayName: "x", + fullDisplayName: "x", + kind: ScriptElementKind.constElement, + kindModifiers: ScriptElementKindModifier.none, + triggerSpan: protocolTextSpanFromSubstring(aTs.content, "x"), + }, + locs: [ + { + file: aTs.path, + locs: [ + protocolRenameSpanFromSubstring(aTs.content, "x"), + protocolRenameSpanFromSubstring(aTs.content, "x", { index: 1 }, { prefixText: "x: " }), + ], + }, + ], + }); }); }); } From 7eac16d5390375dbdefcf814e8bff887f95822bd Mon Sep 17 00:00:00 2001 From: Benjamin Lichtman Date: Fri, 25 Jan 2019 17:09:31 -0800 Subject: [PATCH 4/5] Default to file preferences using host preferences as backup values --- src/server/editorServices.ts | 2 +- src/server/session.ts | 8 +++----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 51833bb2e6e10..6dccc08c4b5cd 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -904,7 +904,7 @@ namespace ts.server { getPreferences(file: NormalizedPath): protocol.UserPreferences { const info = this.getScriptInfoForNormalizedPath(file); - return info && info.getPreferences() || this.hostConfiguration.preferences; + return { ...this.hostConfiguration.preferences, ...info && info.getPreferences() }; } getHostFormatCodeOptions(): FormatCodeSettings { diff --git a/src/server/session.ts b/src/server/session.ts index 0a5830844d4a5..e5b128c984e82 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -1178,8 +1178,7 @@ namespace ts.server { private getRenameInfo(args: protocol.FileLocationRequestArgs): RenameInfo { const { file, project } = this.getFileAndProject(args); const position = this.getPositionInFile(args, file); - const preferences = { ...this.getHostPreferences(), ...this.getPreferences(file) }; - return project.getLanguageService().getRenameInfo(file, position, { allowRenameOfImportPath: preferences.allowRenameOfImportPath }); + return project.getLanguageService().getRenameInfo(file, position, { allowRenameOfImportPath: this.getPreferences(file).allowRenameOfImportPath }); } private getProjects(args: protocol.FileRequestArgs, getScriptInfoEnsuringProjectsUptoDate?: boolean, ignoreNoProjectError?: boolean): Projects { @@ -1234,13 +1233,12 @@ namespace ts.server { { fileName: args.file, pos: position }, !!args.findInStrings, !!args.findInComments, - { ...this.getHostPreferences(), ...this.getPreferences(file) } + this.getPreferences(file) ); if (!simplifiedResult) return locations; const defaultProject = this.getDefaultProject(args); - const preferences = { ...this.getHostPreferences(), ...this.getPreferences(file) }; - const renameInfo: protocol.RenameInfo = this.mapRenameInfo(defaultProject.getLanguageService().getRenameInfo(file, position, { allowRenameOfImportPath: preferences.allowRenameOfImportPath }), Debug.assertDefined(this.projectService.getScriptInfo(file))); + const renameInfo: protocol.RenameInfo = this.mapRenameInfo(defaultProject.getLanguageService().getRenameInfo(file, position, { allowRenameOfImportPath: this.getPreferences(file).allowRenameOfImportPath }), Debug.assertDefined(this.projectService.getScriptInfo(file))); return { info: renameInfo, locs: this.toSpanGroups(locations) }; } From 9aa79c6988c089b344000caa2cb3ef378992c9ab Mon Sep 17 00:00:00 2001 From: Benjamin Lichtman Date: Mon, 28 Jan 2019 17:07:19 -0800 Subject: [PATCH 5/5] Add clarifying comment and test on multi-file rename behavior --- src/services/findAllReferences.ts | 1 + src/testRunner/unittests/tsserver/rename.ts | 62 +++++++++++++++++++++ 2 files changed, 63 insertions(+) diff --git a/src/services/findAllReferences.ts b/src/services/findAllReferences.ts index 52f3277574e3b..f96c53ee20076 100644 --- a/src/services/findAllReferences.ts +++ b/src/services/findAllReferences.ts @@ -41,6 +41,7 @@ namespace ts.FindAllReferences { readonly implementations?: boolean; /** * True to opt in for enhanced renaming of shorthand properties and import/export specifiers. + * The options controls the behavior for the whole rename operation; it cannot be changed on a per-file basis. * Default is false for backwards compatibility. */ readonly providePrefixAndSuffixTextForRename?: boolean; diff --git a/src/testRunner/unittests/tsserver/rename.ts b/src/testRunner/unittests/tsserver/rename.ts index 527868de02c25..f565524aded63 100644 --- a/src/testRunner/unittests/tsserver/rename.ts +++ b/src/testRunner/unittests/tsserver/rename.ts @@ -129,5 +129,67 @@ namespace ts.projectSystem { ], }); }); + + it("rename behavior is based on file of rename initiation", () => { + const aTs: File = { path: "/a.ts", content: "const x = 1; export { x };" }; + const bTs: File = { path: "/b.ts", content: `import { x } from "./a"; const y = x + 1;` }; + const host = createServerHost([aTs, bTs]); + const session = createSession(host); + openFilesForSession([aTs, bTs], session); + + // rename from file with prefixText and suffixText enabled + session.getProjectService().setHostConfiguration({ file: "/a.ts", formatOptions: {}, preferences: { providePrefixAndSuffixTextForRename: true } }); + const response1 = executeSessionRequest(session, protocol.CommandTypes.Rename, protocolFileLocationFromSubstring(aTs, "x")); + assert.deepEqual(response1, { + info: { + canRename: true, + fileToRename: undefined, + displayName: "x", + fullDisplayName: "x", + kind: ScriptElementKind.constElement, + kindModifiers: ScriptElementKindModifier.none, + triggerSpan: protocolTextSpanFromSubstring(aTs.content, "x"), + }, + locs: [ + { + file: aTs.path, + locs: [ + protocolRenameSpanFromSubstring(aTs.content, "x"), + protocolRenameSpanFromSubstring(aTs.content, "x", { index: 2 }, { suffixText: " as x" }), + ], + }, + ], + }); + + // rename from file with prefixText and suffixText disabled + const response2 = executeSessionRequest(session, protocol.CommandTypes.Rename, protocolFileLocationFromSubstring(bTs, "x")); + assert.deepEqual(response2, { + info: { + canRename: true, + fileToRename: undefined, + displayName: "x", + fullDisplayName: "x", + kind: ScriptElementKind.alias, + kindModifiers: ScriptElementKindModifier.none, + triggerSpan: protocolTextSpanFromSubstring(bTs.content, "x"), + }, + locs: [ + { + file: bTs.path, + locs: [ + protocolRenameSpanFromSubstring(bTs.content, "x"), + protocolRenameSpanFromSubstring(bTs.content, "x", { index: 1 }) + ] + }, + { + file: aTs.path, + locs: [ + protocolRenameSpanFromSubstring(aTs.content, "x"), + protocolRenameSpanFromSubstring(aTs.content, "x", { index: 2 }), + ], + }, + ], + }); + }); }); }