fix(54283): Provide better UX when an invalid file is selected for 'move to file' refactoring (#54300)

This commit is contained in:
Oleksandr T
2023-05-18 20:36:34 +03:00
committed by GitHub
parent 174599c554
commit ce1c97f4f0
11 changed files with 176 additions and 30 deletions

View File

@@ -7612,6 +7612,10 @@
"category": "Message",
"code": 95178
},
"Cannot move to file, selected file is invalid": {
"category": "Message",
"code": 95179
},
"No value exists in scope for the shorthand property '{0}'. Either declare one or provide an initializer.": {
"category": "Error",

View File

@@ -818,7 +818,7 @@ export class SessionClient implements LanguageService {
const response = this.processResponse<protocol.GetEditsForRefactorResponse>(request);
if (!response.body) {
return { edits: [], renameFilename: undefined, renameLocation: undefined };
return { edits: [], renameFilename: undefined, renameLocation: undefined, notApplicableReason: undefined };
}
const edits: FileTextChanges[] = this.convertCodeEditsToTextChanges(response.body.edits);
@@ -832,7 +832,8 @@ export class SessionClient implements LanguageService {
return {
edits,
renameFilename,
renameLocation
renameLocation,
notApplicableReason: response.body.notApplicableReason,
};
}

View File

@@ -721,6 +721,7 @@ export interface RefactorEditInfo {
*/
renameLocation?: Location;
renameFilename?: string;
notApplicableReason?: string;
}
/**

View File

@@ -2709,7 +2709,8 @@ export class Session<TMessage = string> implements EventSender {
return {
renameLocation: mappedRenameLocation,
renameFilename,
edits: this.mapTextChangesToCodeEdits(edits)
edits: this.mapTextChangesToCodeEdits(edits),
notApplicableReason: result.notApplicableReason,
};
}
return result;

View File

@@ -53,7 +53,9 @@ import {
getRelativePathFromFile,
getSynthesizedDeepClone,
getUniqueName,
hasJSFileExtension,
hasSyntacticModifier,
hasTSFileExtension,
hostGetCanonicalFileName,
Identifier,
ImportDeclaration,
@@ -162,8 +164,12 @@ registerRefactor(refactorNameForMoveToFile, {
Debug.assert(actionName === refactorNameForMoveToFile, "Wrong refactor invoked");
const statements = Debug.checkDefined(getStatementsToMove(context));
Debug.assert(interactiveRefactorArguments, "No interactive refactor arguments available");
const edits = textChanges.ChangeTracker.with(context, t => doChange(context, context.file, interactiveRefactorArguments.targetFile, context.program, statements, t, context.host, context.preferences));
return { edits, renameFilename: undefined, renameLocation: undefined };
const targetFile = interactiveRefactorArguments.targetFile;
if (hasJSFileExtension(targetFile) || hasTSFileExtension(targetFile)) {
const edits = textChanges.ChangeTracker.with(context, t => doChange(context, context.file, interactiveRefactorArguments.targetFile, context.program, statements, t, context.host, context.preferences));
return { edits, renameFilename: undefined, renameLocation: undefined };
}
return { edits: [], renameFilename: undefined, renameLocation: undefined, notApplicableReason: getLocaleSpecificMessage(Diagnostics.Cannot_move_to_file_selected_file_is_invalid) };
}
});

View File

@@ -1002,6 +1002,7 @@ export interface RefactorEditInfo {
renameFilename?: string;
renameLocation?: number;
commands?: CodeActionCommand[];
notApplicableReason?: string;
}
export type RefactorTriggerReason = "implicit" | "invoked";

View File

@@ -96,28 +96,61 @@ describe("unittests:: tsserver:: refactors", () => {
});
it("handles moving statement to an existing file", () => {
const aTs: File = { path: "/Foo/a.ts", content: "const x = 0;" };
const bTs: File = {
path: "/Foo/b.ts", content: `import {} from "./bar";
const a = 1;`};
const tsconfig: File = { path: "/Foo/tsconfig.json", content: `{ "files": ["./a.ts", "./b.ts"] }` };
const host = createServerHost([aTs, bTs, tsconfig]);
const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) });
openFilesForSession([aTs], session);
const aTs: File = { path: "/Foo/a.ts", content: "const x = 0;" };
const bTs: File = {
path: "/Foo/b.ts", content: `import {} from "./bar";
const a = 1;`};
const tsconfig: File = { path: "/Foo/tsconfig.json", content: `{ "files": ["./a.ts", "./b.ts"] }` };
const host = createServerHost([aTs, bTs, tsconfig]);
const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) });
openFilesForSession([aTs], session);
session.executeCommandSeq<ts.server.protocol.GetEditsForRefactorRequest>({
command: ts.server.protocol.CommandTypes.GetEditsForRefactor,
arguments: {
file: aTs.path,
startLine: 1,
startOffset: 1,
endLine: 2,
endOffset: aTs.content.length,
refactor: "Move to file",
action: "Move to file",
interactiveRefactorArguments: { targetFile: "/Foo/b.ts" },
}
session.executeCommandSeq<ts.server.protocol.GetEditsForRefactorRequest>({
command: ts.server.protocol.CommandTypes.GetEditsForRefactor,
arguments: {
file: aTs.path,
startLine: 1,
startOffset: 1,
endLine: 2,
endOffset: aTs.content.length,
refactor: "Move to file",
action: "Move to file",
interactiveRefactorArguments: { targetFile: "/Foo/b.ts" },
}
});
baselineTsserverLogs("refactors", "handles moving statement to an existing file", session);
});
baselineTsserverLogs("refactors", "handles moving statement to an existing file", session);
it("handles moving statements to a non-TS file", () => {
const aTs: File = {
path: "/Foo/a.ts",
content: "const x = 0;"
};
const bTxt: File = {
path: "/Foo/b.txt",
content: ""
};
const tsconfig: File = {
path: "/Foo/tsconfig.json",
content: `{ "files": ["./a.ts"] }`
};
const host = createServerHost([aTs, bTxt, tsconfig]);
const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) });
openFilesForSession([aTs], session);
session.executeCommandSeq<ts.server.protocol.GetEditsForRefactorRequest>({
command: ts.server.protocol.CommandTypes.GetEditsForRefactor,
arguments: {
file: aTs.path,
startLine: 1,
startOffset: 1,
endLine: 2,
endOffset: aTs.content.length,
refactor: "Move to file",
action: "Move to file",
interactiveRefactorArguments: { targetFile: "/Foo/b.txt" },
}
});
baselineTsserverLogs("refactors", "handles moving statements to a non-TS file", session);
});
});