diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 34abf3586fddd..102be920acf0e 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -7628,6 +7628,10 @@ "category": "Message", "code": 95182 }, + "Cannot move statements to the selected file": { + "category": "Message", + "code": 95183 + }, "No value exists in scope for the shorthand property '{0}'. Either declare one or provide an initializer.": { "category": "Error", diff --git a/src/services/refactors/moveToFile.ts b/src/services/refactors/moveToFile.ts index b595f6cdfa3d1..8c5480b9c7504 100644 --- a/src/services/refactors/moveToFile.ts +++ b/src/services/refactors/moveToFile.ts @@ -167,20 +167,28 @@ registerRefactor(refactorNameForMoveToFile, { getEditsForAction: function getRefactorEditsToMoveToFile(context, actionName, interactiveRefactorArguments): RefactorEditInfo | undefined { Debug.assert(actionName === refactorNameForMoveToFile, "Wrong refactor invoked"); const statements = Debug.checkDefined(getStatementsToMove(context)); + const { host, program } = context; Debug.assert(interactiveRefactorArguments, "No interactive refactor arguments available"); 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)); + if (host.fileExists(targetFile) && program.getSourceFile(targetFile) === undefined) { + return error(getLocaleSpecificMessage(Diagnostics.Cannot_move_statements_to_the_selected_file)); + } + const edits = textChanges.ChangeTracker.with(context, t => doChange(context, context.file, interactiveRefactorArguments.targetFile, 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) }; + return error(getLocaleSpecificMessage(Diagnostics.Cannot_move_to_file_selected_file_is_invalid)); } }); +function error(notApplicableReason: string) { + return { edits: [], renameFilename: undefined, renameLocation: undefined, notApplicableReason }; +} + function doChange(context: RefactorContext, oldFile: SourceFile, targetFile: string, program: Program, toMove: ToMove, changes: textChanges.ChangeTracker, host: LanguageServiceHost, preferences: UserPreferences): void { const checker = program.getTypeChecker(); const usage = getUsageInfo(oldFile, toMove.all, checker); - //For a new file + // For a new file if (!host.fileExists(targetFile)) { changes.createNewFile(oldFile, targetFile, getNewStatementsAndRemoveFromOldFile(oldFile, targetFile, usage, changes, toMove, program, host, preferences)); addNewFileToTsconfig(program, changes, oldFile.fileName, targetFile, hostGetCanonicalFileName(host)); diff --git a/src/testRunner/unittests/tsserver/refactors.ts b/src/testRunner/unittests/tsserver/refactors.ts index 71d68dfac7372..f27d828a93ec9 100644 --- a/src/testRunner/unittests/tsserver/refactors.ts +++ b/src/testRunner/unittests/tsserver/refactors.ts @@ -153,4 +153,46 @@ describe("unittests:: tsserver:: refactors", () => { }); baselineTsserverLogs("refactors", "handles moving statements to a non-TS file", session); }); + + it("handles moving statements to a TS file that is not included in the TS project", () => { + const fooATs: File = { + path: "/Foo/a.ts", + content: "" + }; + const fooTsconfig: File = { + path: "/Foo/tsconfig.json", + content: `{ "files": ["./a.ts"] }` + }; + + const barATs: File = { + path: "/Bar/a.ts", + content: [ + "const a = 1;", + "const b = 2;", + "console.log(a, b);", + ].join("\n") + }; + const barTsconfig: File = { + path: "/Bar/tsconfig.json", + content: `{ "files": ["./a.ts"] }` + }; + const host = createServerHost([fooATs, fooTsconfig, barATs, barTsconfig]); + const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + openFilesForSession([barATs], session); + + session.executeCommandSeq({ + command: ts.server.protocol.CommandTypes.GetEditsForRefactor, + arguments: { + file: barATs.path, + startLine: 1, + startOffset: 1, + endLine: 1, + endOffset: 13, + refactor: "Move to file", + action: "Move to file", + interactiveRefactorArguments: { targetFile: "/Foo/a.ts" }, + } + }); + baselineTsserverLogs("refactors", "handles moving statements to a TS file that is not included in the TS project", session); + }); }); diff --git a/tests/baselines/reference/tsserver/refactors/handles-moving-statements-to-a-TS-file-that-is-not-included-in-the-TS-project.js b/tests/baselines/reference/tsserver/refactors/handles-moving-statements-to-a-TS-file-that-is-not-included-in-the-TS-project.js new file mode 100644 index 0000000000000..15f63138f9733 --- /dev/null +++ b/tests/baselines/reference/tsserver/refactors/handles-moving-statements-to-a-TS-file-that-is-not-included-in-the-TS-project.js @@ -0,0 +1,101 @@ +currentDirectory:: / useCaseSensitiveFileNames: false +Info seq [hh:mm:ss:mss] Provided types map file "/a/lib/typesMap.json" doesn't exist +Before request +//// [/Foo/a.ts] + + +//// [/Foo/tsconfig.json] +{ "files": ["./a.ts"] } + +//// [/Bar/a.ts] +const a = 1; +const b = 2; +console.log(a, b); + +//// [/Bar/tsconfig.json] +{ "files": ["./a.ts"] } + + +Info seq [hh:mm:ss:mss] request: + { + "command": "open", + "arguments": { + "file": "/Bar/a.ts" + }, + "seq": 1, + "type": "request" + } +Info seq [hh:mm:ss:mss] Search path: /Bar +Info seq [hh:mm:ss:mss] For info: /Bar/a.ts :: Config file name: /Bar/tsconfig.json +Info seq [hh:mm:ss:mss] Creating configuration project /Bar/tsconfig.json +Info seq [hh:mm:ss:mss] FileWatcher:: Added:: WatchInfo: /Bar/tsconfig.json 2000 undefined Project: /Bar/tsconfig.json WatchType: Config file +Info seq [hh:mm:ss:mss] Config: /Bar/tsconfig.json : { + "rootNames": [ + "/Bar/a.ts" + ], + "options": { + "configFilePath": "/Bar/tsconfig.json" + } +} +Info seq [hh:mm:ss:mss] Starting updateGraphWorker: Project: /Bar/tsconfig.json +Info seq [hh:mm:ss:mss] FileWatcher:: Added:: WatchInfo: /a/lib/lib.d.ts 500 undefined Project: /Bar/tsconfig.json WatchType: Missing file +Info seq [hh:mm:ss:mss] Finishing updateGraphWorker: Project: /Bar/tsconfig.json Version: 1 structureChanged: true structureIsReused:: Not Elapsed:: *ms +Info seq [hh:mm:ss:mss] Project '/Bar/tsconfig.json' (Configured) +Info seq [hh:mm:ss:mss] Files (1) + /Bar/a.ts SVC-1-0 "const a = 1;\nconst b = 2;\nconsole.log(a, b);" + + + a.ts + Part of 'files' list in tsconfig.json + +Info seq [hh:mm:ss:mss] ----------------------------------------------- +Info seq [hh:mm:ss:mss] Project '/Bar/tsconfig.json' (Configured) +Info seq [hh:mm:ss:mss] Files (1) + +Info seq [hh:mm:ss:mss] ----------------------------------------------- +Info seq [hh:mm:ss:mss] Open files: +Info seq [hh:mm:ss:mss] FileName: /Bar/a.ts ProjectRootPath: undefined +Info seq [hh:mm:ss:mss] Projects: /Bar/tsconfig.json +Info seq [hh:mm:ss:mss] response: + { + "responseRequired": false + } +After request + +PolledWatches:: +/a/lib/lib.d.ts: *new* + {"pollingInterval":500} + +FsWatches:: +/bar/tsconfig.json: *new* + {} + +Before request + +Info seq [hh:mm:ss:mss] request: + { + "command": "getEditsForRefactor", + "arguments": { + "file": "/Bar/a.ts", + "startLine": 1, + "startOffset": 1, + "endLine": 1, + "endOffset": 13, + "refactor": "Move to file", + "action": "Move to file", + "interactiveRefactorArguments": { + "targetFile": "/Foo/a.ts" + } + }, + "seq": 2, + "type": "request" + } +Info seq [hh:mm:ss:mss] response: + { + "response": { + "edits": [], + "notApplicableReason": "Cannot move statements to the selected file" + }, + "responseRequired": true + } +After request