Skip to content

Commit 7e48587

Browse files
TypeScript Bota-tarasyuk
TypeScript Bot
andauthored
🤖 Pick PR #54300 (fix(54283): Provide better UX when ...) into release-5.1 (#54312)
Co-authored-by: Oleksandr T <[email protected]>
1 parent 5c47c6a commit 7e48587

File tree

11 files changed

+176
-30
lines changed

11 files changed

+176
-30
lines changed

‎src/compiler/diagnosticMessages.json

+4
Original file line numberDiff line numberDiff line change
@@ -7612,6 +7612,10 @@
76127612
"category": "Message",
76137613
"code": 95178
76147614
},
7615+
"Cannot move to file, selected file is invalid": {
7616+
"category": "Message",
7617+
"code": 95179
7618+
},
76157619

76167620
"No value exists in scope for the shorthand property '{0}'. Either declare one or provide an initializer.": {
76177621
"category": "Error",

‎src/harness/client.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -818,7 +818,7 @@ export class SessionClient implements LanguageService {
818818
const response = this.processResponse<protocol.GetEditsForRefactorResponse>(request);
819819

820820
if (!response.body) {
821-
return { edits: [], renameFilename: undefined, renameLocation: undefined };
821+
return { edits: [], renameFilename: undefined, renameLocation: undefined, notApplicableReason: undefined };
822822
}
823823

824824
const edits: FileTextChanges[] = this.convertCodeEditsToTextChanges(response.body.edits);
@@ -832,7 +832,8 @@ export class SessionClient implements LanguageService {
832832
return {
833833
edits,
834834
renameFilename,
835-
renameLocation
835+
renameLocation,
836+
notApplicableReason: response.body.notApplicableReason,
836837
};
837838
}
838839

‎src/server/protocol.ts

+1
Original file line numberDiff line numberDiff line change
@@ -721,6 +721,7 @@ export interface RefactorEditInfo {
721721
*/
722722
renameLocation?: Location;
723723
renameFilename?: string;
724+
notApplicableReason?: string;
724725
}
725726

726727
/**

‎src/server/session.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -2707,7 +2707,8 @@ export class Session<TMessage = string> implements EventSender {
27072707
return {
27082708
renameLocation: mappedRenameLocation,
27092709
renameFilename,
2710-
edits: this.mapTextChangesToCodeEdits(edits)
2710+
edits: this.mapTextChangesToCodeEdits(edits),
2711+
notApplicableReason: result.notApplicableReason,
27112712
};
27122713
}
27132714
return result;

‎src/services/refactors/moveToFile.ts

+8-2
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,9 @@ import {
5353
getRelativePathFromFile,
5454
getSynthesizedDeepClone,
5555
getUniqueName,
56+
hasJSFileExtension,
5657
hasSyntacticModifier,
58+
hasTSFileExtension,
5759
hostGetCanonicalFileName,
5860
Identifier,
5961
ImportDeclaration,
@@ -162,8 +164,12 @@ registerRefactor(refactorNameForMoveToFile, {
162164
Debug.assert(actionName === refactorNameForMoveToFile, "Wrong refactor invoked");
163165
const statements = Debug.checkDefined(getStatementsToMove(context));
164166
Debug.assert(interactiveRefactorArguments, "No interactive refactor arguments available");
165-
const edits = textChanges.ChangeTracker.with(context, t => doChange(context, context.file, interactiveRefactorArguments.targetFile, context.program, statements, t, context.host, context.preferences));
166-
return { edits, renameFilename: undefined, renameLocation: undefined };
167+
const targetFile = interactiveRefactorArguments.targetFile;
168+
if (hasJSFileExtension(targetFile) || hasTSFileExtension(targetFile)) {
169+
const edits = textChanges.ChangeTracker.with(context, t => doChange(context, context.file, interactiveRefactorArguments.targetFile, context.program, statements, t, context.host, context.preferences));
170+
return { edits, renameFilename: undefined, renameLocation: undefined };
171+
}
172+
return { edits: [], renameFilename: undefined, renameLocation: undefined, notApplicableReason: getLocaleSpecificMessage(Diagnostics.Cannot_move_to_file_selected_file_is_invalid) };
167173
}
168174
});
169175

‎src/services/types.ts

+1
Original file line numberDiff line numberDiff line change
@@ -1002,6 +1002,7 @@ export interface RefactorEditInfo {
10021002
renameFilename?: string;
10031003
renameLocation?: number;
10041004
commands?: CodeActionCommand[];
1005+
notApplicableReason?: string;
10051006
}
10061007

10071008
export type RefactorTriggerReason = "implicit" | "invoked";

‎src/testRunner/unittests/tsserver/refactors.ts

+54-21
Original file line numberDiff line numberDiff line change
@@ -96,28 +96,61 @@ describe("unittests:: tsserver:: refactors", () => {
9696
});
9797

9898
it("handles moving statement to an existing file", () => {
99-
const aTs: File = { path: "/Foo/a.ts", content: "const x = 0;" };
100-
const bTs: File = {
101-
path: "/Foo/b.ts", content: `import {} from "./bar";
102-
const a = 1;`};
103-
const tsconfig: File = { path: "/Foo/tsconfig.json", content: `{ "files": ["./a.ts", "./b.ts"] }` };
104-
const host = createServerHost([aTs, bTs, tsconfig]);
105-
const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) });
106-
openFilesForSession([aTs], session);
99+
const aTs: File = { path: "/Foo/a.ts", content: "const x = 0;" };
100+
const bTs: File = {
101+
path: "/Foo/b.ts", content: `import {} from "./bar";
102+
const a = 1;`};
103+
const tsconfig: File = { path: "/Foo/tsconfig.json", content: `{ "files": ["./a.ts", "./b.ts"] }` };
104+
const host = createServerHost([aTs, bTs, tsconfig]);
105+
const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) });
106+
openFilesForSession([aTs], session);
107107

108-
session.executeCommandSeq<ts.server.protocol.GetEditsForRefactorRequest>({
109-
command: ts.server.protocol.CommandTypes.GetEditsForRefactor,
110-
arguments: {
111-
file: aTs.path,
112-
startLine: 1,
113-
startOffset: 1,
114-
endLine: 2,
115-
endOffset: aTs.content.length,
116-
refactor: "Move to file",
117-
action: "Move to file",
118-
interactiveRefactorArguments: { targetFile: "/Foo/b.ts" },
119-
}
108+
session.executeCommandSeq<ts.server.protocol.GetEditsForRefactorRequest>({
109+
command: ts.server.protocol.CommandTypes.GetEditsForRefactor,
110+
arguments: {
111+
file: aTs.path,
112+
startLine: 1,
113+
startOffset: 1,
114+
endLine: 2,
115+
endOffset: aTs.content.length,
116+
refactor: "Move to file",
117+
action: "Move to file",
118+
interactiveRefactorArguments: { targetFile: "/Foo/b.ts" },
119+
}
120+
});
121+
baselineTsserverLogs("refactors", "handles moving statement to an existing file", session);
120122
});
121-
baselineTsserverLogs("refactors", "handles moving statement to an existing file", session);
123+
124+
it("handles moving statements to a non-TS file", () => {
125+
const aTs: File = {
126+
path: "/Foo/a.ts",
127+
content: "const x = 0;"
128+
};
129+
const bTxt: File = {
130+
path: "/Foo/b.txt",
131+
content: ""
132+
};
133+
const tsconfig: File = {
134+
path: "/Foo/tsconfig.json",
135+
content: `{ "files": ["./a.ts"] }`
136+
};
137+
const host = createServerHost([aTs, bTxt, tsconfig]);
138+
const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) });
139+
openFilesForSession([aTs], session);
140+
141+
session.executeCommandSeq<ts.server.protocol.GetEditsForRefactorRequest>({
142+
command: ts.server.protocol.CommandTypes.GetEditsForRefactor,
143+
arguments: {
144+
file: aTs.path,
145+
startLine: 1,
146+
startOffset: 1,
147+
endLine: 2,
148+
endOffset: aTs.content.length,
149+
refactor: "Move to file",
150+
action: "Move to file",
151+
interactiveRefactorArguments: { targetFile: "/Foo/b.txt" },
152+
}
153+
});
154+
baselineTsserverLogs("refactors", "handles moving statements to a non-TS file", session);
122155
});
123156
});

‎tests/baselines/reference/api/tsserverlibrary.d.ts

+2
Original file line numberDiff line numberDiff line change
@@ -619,6 +619,7 @@ declare namespace ts {
619619
*/
620620
renameLocation?: Location;
621621
renameFilename?: string;
622+
notApplicableReason?: string;
622623
}
623624
/**
624625
* Organize imports by:
@@ -10480,6 +10481,7 @@ declare namespace ts {
1048010481
renameFilename?: string;
1048110482
renameLocation?: number;
1048210483
commands?: CodeActionCommand[];
10484+
notApplicableReason?: string;
1048310485
}
1048410486
type RefactorTriggerReason = "implicit" | "invoked";
1048510487
interface TextInsertion {

‎tests/baselines/reference/api/typescript.d.ts

+1
Original file line numberDiff line numberDiff line change
@@ -6517,6 +6517,7 @@ declare namespace ts {
65176517
renameFilename?: string;
65186518
renameLocation?: number;
65196519
commands?: CodeActionCommand[];
6520+
notApplicableReason?: string;
65206521
}
65216522
type RefactorTriggerReason = "implicit" | "invoked";
65226523
interface TextInsertion {

‎tests/baselines/reference/tsserver/refactors/handles-moving-statement-to-an-existing-file.js

+4-4
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ const x = 0;
66

77
//// [/Foo/b.ts]
88
import {} from "./bar";
9-
const a = 1;
9+
const a = 1;
1010

1111
//// [/Foo/tsconfig.json]
1212
{ "files": ["./a.ts", "./b.ts"] }
@@ -41,7 +41,7 @@ Info seq [hh:mm:ss:mss] Finishing updateGraphWorker: Project: /Foo/tsconfig.jso
4141
Info seq [hh:mm:ss:mss] Project '/Foo/tsconfig.json' (Configured)
4242
Info seq [hh:mm:ss:mss] Files (2)
4343
/Foo/a.ts SVC-1-0 "const x = 0;"
44-
/Foo/b.ts Text-1 "import {} from \"./bar\";\nconst a = 1;"
44+
/Foo/b.ts Text-1 "import {} from \"./bar\";\n const a = 1;"
4545

4646

4747
a.ts
@@ -119,11 +119,11 @@ Info seq [hh:mm:ss:mss] response:
119119
{
120120
"start": {
121121
"line": 2,
122-
"offset": 13
122+
"offset": 17
123123
},
124124
"end": {
125125
"line": 2,
126-
"offset": 13
126+
"offset": 17
127127
},
128128
"newText": "\nconst x = 0;\n"
129129
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
currentDirectory:: / useCaseSensitiveFileNames: false
2+
Info seq [hh:mm:ss:mss] Provided types map file "/a/lib/typesMap.json" doesn't exist
3+
Before request
4+
//// [/Foo/a.ts]
5+
const x = 0;
6+
7+
//// [/Foo/b.txt]
8+
9+
10+
//// [/Foo/tsconfig.json]
11+
{ "files": ["./a.ts"] }
12+
13+
14+
Info seq [hh:mm:ss:mss] request:
15+
{
16+
"command": "open",
17+
"arguments": {
18+
"file": "/Foo/a.ts"
19+
},
20+
"seq": 1,
21+
"type": "request"
22+
}
23+
Info seq [hh:mm:ss:mss] Search path: /Foo
24+
Info seq [hh:mm:ss:mss] For info: /Foo/a.ts :: Config file name: /Foo/tsconfig.json
25+
Info seq [hh:mm:ss:mss] Creating configuration project /Foo/tsconfig.json
26+
Info seq [hh:mm:ss:mss] FileWatcher:: Added:: WatchInfo: /Foo/tsconfig.json 2000 undefined Project: /Foo/tsconfig.json WatchType: Config file
27+
Info seq [hh:mm:ss:mss] Config: /Foo/tsconfig.json : {
28+
"rootNames": [
29+
"/Foo/a.ts"
30+
],
31+
"options": {
32+
"configFilePath": "/Foo/tsconfig.json"
33+
}
34+
}
35+
Info seq [hh:mm:ss:mss] Starting updateGraphWorker: Project: /Foo/tsconfig.json
36+
Info seq [hh:mm:ss:mss] FileWatcher:: Added:: WatchInfo: /a/lib/lib.d.ts 500 undefined Project: /Foo/tsconfig.json WatchType: Missing file
37+
Info seq [hh:mm:ss:mss] Finishing updateGraphWorker: Project: /Foo/tsconfig.json Version: 1 structureChanged: true structureIsReused:: Not Elapsed:: *ms
38+
Info seq [hh:mm:ss:mss] Project '/Foo/tsconfig.json' (Configured)
39+
Info seq [hh:mm:ss:mss] Files (1)
40+
/Foo/a.ts SVC-1-0 "const x = 0;"
41+
42+
43+
a.ts
44+
Part of 'files' list in tsconfig.json
45+
46+
Info seq [hh:mm:ss:mss] -----------------------------------------------
47+
Info seq [hh:mm:ss:mss] Project '/Foo/tsconfig.json' (Configured)
48+
Info seq [hh:mm:ss:mss] Files (1)
49+
50+
Info seq [hh:mm:ss:mss] -----------------------------------------------
51+
Info seq [hh:mm:ss:mss] Open files:
52+
Info seq [hh:mm:ss:mss] FileName: /Foo/a.ts ProjectRootPath: undefined
53+
Info seq [hh:mm:ss:mss] Projects: /Foo/tsconfig.json
54+
Info seq [hh:mm:ss:mss] response:
55+
{
56+
"responseRequired": false
57+
}
58+
After request
59+
60+
PolledWatches::
61+
/a/lib/lib.d.ts: *new*
62+
{"pollingInterval":500}
63+
64+
FsWatches::
65+
/foo/tsconfig.json: *new*
66+
{}
67+
68+
Before request
69+
70+
Info seq [hh:mm:ss:mss] request:
71+
{
72+
"command": "getEditsForRefactor",
73+
"arguments": {
74+
"file": "/Foo/a.ts",
75+
"startLine": 1,
76+
"startOffset": 1,
77+
"endLine": 2,
78+
"endOffset": 12,
79+
"refactor": "Move to file",
80+
"action": "Move to file",
81+
"interactiveRefactorArguments": {
82+
"targetFile": "/Foo/b.txt"
83+
}
84+
},
85+
"seq": 2,
86+
"type": "request"
87+
}
88+
Info seq [hh:mm:ss:mss] response:
89+
{
90+
"response": {
91+
"edits": [],
92+
"notApplicableReason": "Cannot move to file, selected file is invalid"
93+
},
94+
"responseRequired": true
95+
}
96+
After request

0 commit comments

Comments
 (0)