Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow per-file setting for rename default behavior preferences #29593

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/server/editorServices.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
7 changes: 3 additions & 4 deletions src/server/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
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 {
Expand Down Expand Up @@ -1234,12 +1233,12 @@ namespace ts.server {
{ fileName: args.file, pos: position },
!!args.findInStrings,
!!args.findInComments,
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 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) };
}

Expand Down
1 change: 1 addition & 0 deletions src/services/findAllReferences.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
108 changes: 107 additions & 1 deletion src/testRunner/unittests/tsserver/rename.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ namespace ts.projectSystem {
const session = createSession(createServerHost([aTs, bTs]));
openFilesForSession([bTs], session);

// rename fails with allowRenameOfImportPath disabled
const response1 = executeSessionRequest<protocol.RenameRequest, protocol.RenameResponse>(session, protocol.CommandTypes.Rename, protocolFileLocationFromSubstring(bTs, 'a";'));
assert.deepEqual<protocol.RenameResponseBody | undefined>(response1, {
info: {
Expand All @@ -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<protocol.RenameRequest, protocol.RenameResponse>(session, protocol.CommandTypes.Rename, protocolFileLocationFromSubstring(bTs, 'a";'));
assert.deepEqual<protocol.RenameResponseBody | undefined>(response2, {
Expand All @@ -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<protocol.RenameRequest, protocol.RenameResponse>(session, protocol.CommandTypes.Rename, protocolFileLocationFromSubstring(bTs, 'a";'));
assert.deepEqual<protocol.RenameResponseBody | undefined>(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", () => {
Expand Down Expand Up @@ -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<protocol.RenameRequest, protocol.RenameResponse>(session, protocol.CommandTypes.Rename, protocolFileLocationFromSubstring(aTs, "x"));
assert.deepEqual<protocol.RenameResponseBody | undefined>(response2, {
Expand All @@ -84,6 +103,93 @@ 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<protocol.RenameRequest, protocol.RenameResponse>(session, protocol.CommandTypes.Rename, protocolFileLocationFromSubstring(aTs, "x"));
assert.deepEqual<protocol.RenameResponseBody | undefined>(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: " }),
],
},
],
});
});

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<protocol.RenameRequest, protocol.RenameResponse>(session, protocol.CommandTypes.Rename, protocolFileLocationFromSubstring(aTs, "x"));
assert.deepEqual<protocol.RenameResponseBody | undefined>(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<protocol.RenameRequest, protocol.RenameResponse>(session, protocol.CommandTypes.Rename, protocolFileLocationFromSubstring(bTs, "x"));
assert.deepEqual<protocol.RenameResponseBody | undefined>(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 }),
],
},
],
});
});
});
}