diff --git a/src/harness/client.ts b/src/harness/client.ts index a97fde1279ed9..e513f79b3265b 100644 --- a/src/harness/client.ts +++ b/src/harness/client.ts @@ -36,6 +36,7 @@ import { ImplementationLocation, InlayHint, InlayHintKind, + InteractiveRefactorArguments, isString, JSDocTagInfo, LanguageService, @@ -52,6 +53,7 @@ import { Program, QuickInfo, RefactorEditInfo, + RefactorTriggerReason, ReferencedSymbol, ReferenceEntry, RenameInfo, @@ -787,11 +789,25 @@ export class SessionClient implements LanguageService { return { file, line, offset, endLine, endOffset }; } - getApplicableRefactors(fileName: string, positionOrRange: number | TextRange): ApplicableRefactorInfo[] { - const args = this.createFileLocationOrRangeRequestArgs(positionOrRange, fileName); - + getApplicableRefactors( + fileName: string, + positionOrRange: number | TextRange, + preferences: UserPreferences | undefined, + triggerReason?: RefactorTriggerReason, + kind?: string, + includeInteractiveActions?: boolean): ApplicableRefactorInfo[] { + if (preferences) { // Temporarily set preferences + this.configure(preferences); + } + const args: protocol.GetApplicableRefactorsRequestArgs = this.createFileLocationOrRangeRequestArgs(positionOrRange, fileName); + args.triggerReason = triggerReason; + args.kind = kind; + args.includeInteractiveActions = includeInteractiveActions; const request = this.processRequest(protocol.CommandTypes.GetApplicableRefactors, args); const response = this.processResponse(request); + if (preferences) { // Restore preferences + this.configure(this.preferences || {}); + } return response.body!; // TODO: GH#18217 } @@ -808,11 +824,17 @@ export class SessionClient implements LanguageService { _formatOptions: FormatCodeSettings, positionOrRange: number | TextRange, refactorName: string, - actionName: string): RefactorEditInfo { - - const args = this.createFileLocationOrRangeRequestArgs(positionOrRange, fileName) as protocol.GetEditsForRefactorRequestArgs; + actionName: string, + preferences: UserPreferences | undefined, + interactiveRefactorArguments?: InteractiveRefactorArguments): RefactorEditInfo { + if (preferences) { // Temporarily set preferences + this.configure(preferences); + } + const args = + this.createFileLocationOrRangeRequestArgs(positionOrRange, fileName) as protocol.GetEditsForRefactorRequestArgs; args.refactor = refactorName; args.action = actionName; + args.interactiveRefactorArguments = interactiveRefactorArguments; const request = this.processRequest(protocol.CommandTypes.GetEditsForRefactor, args); const response = this.processResponse(request); @@ -829,6 +851,10 @@ export class SessionClient implements LanguageService { renameLocation = this.lineOffsetToPosition(renameFilename, response.body.renameLocation!); } + if (preferences) { // Restore preferences + this.configure(this.preferences || {}); + } + return { edits, renameFilename, diff --git a/src/services/refactors/moveToFile.ts b/src/services/refactors/moveToFile.ts index aa566694e8a9b..3b0a0c22ab8e3 100644 --- a/src/services/refactors/moveToFile.ts +++ b/src/services/refactors/moveToFile.ts @@ -176,8 +176,8 @@ registerRefactor(refactorNameForMoveToFile, { 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 or an existing blank target file - if (!host.fileExists(targetFile) || host.fileExists(targetFile) && program.getSourceFile(targetFile)?.statements.length === 0) { + //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)); } @@ -189,7 +189,15 @@ function doChange(context: RefactorContext, oldFile: SourceFile, targetFile: str } function getNewStatementsAndRemoveFromOldFile( - oldFile: SourceFile, targetFile: string | SourceFile, usage: UsageInfo, changes: textChanges.ChangeTracker, toMove: ToMove, program: Program, host: LanguageServiceHost, preferences: UserPreferences, importAdder?: codefix.ImportAdder + oldFile: SourceFile, + targetFile: string | SourceFile, + usage: UsageInfo, + changes: textChanges.ChangeTracker, + toMove: ToMove, + program: Program, + host: LanguageServiceHost, + preferences: UserPreferences, + importAdder?: codefix.ImportAdder ) { const checker = program.getTypeChecker(); const prologueDirectives = takeWhile(oldFile.statements, isPrologueDirective); @@ -218,6 +226,9 @@ function getNewStatementsAndRemoveFromOldFile( if (targetFile.statements.length > 0) { changes.insertNodesAfter(targetFile, targetFile.statements[targetFile.statements.length - 1], body); } + else { + changes.insertNodesAtEndOfFile(targetFile, body, /*blankLineBetween*/ false); + } if (imports.length > 0) { insertImports(changes, targetFile, imports, /*blankLineBetween*/ true, preferences); } diff --git a/src/services/textChanges.ts b/src/services/textChanges.ts index 3579b47fbcfde..d52ab642a5e5d 100644 --- a/src/services/textChanges.ts +++ b/src/services/textChanges.ts @@ -631,6 +631,25 @@ export class ChangeTracker { } } + public insertNodesAtEndOfFile( + sourceFile: SourceFile, + newNodes: readonly Statement[], + blankLineBetween: boolean): void { + this.insertAtEndOfFile(sourceFile, newNodes, blankLineBetween); + } + + private insertAtEndOfFile( + sourceFile: SourceFile, + insert: readonly Statement[], + blankLineBetween: boolean): void { + const pos = sourceFile.end + 1; + const options = { + prefix: this.newLineCharacter, + suffix: this.newLineCharacter + (blankLineBetween ? this.newLineCharacter : ""), + }; + this.insertNodesAt(sourceFile, pos, insert, options); + } + private insertStatementsInNewFile(fileName: string, statements: readonly (Statement | SyntaxKind.NewLineTrivia)[], oldFile?: SourceFile): void { if (!this.newFileChanges) { this.newFileChanges = createMultiMap(); diff --git a/src/services/types.ts b/src/services/types.ts index 57daf3dd85e62..ace7789a0efc6 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -652,7 +652,7 @@ export interface LanguageService { * arguments for any interactive action before offering it. */ getApplicableRefactors(fileName: string, positionOrRange: number | TextRange, preferences: UserPreferences | undefined, triggerReason?: RefactorTriggerReason, kind?: string, includeInteractiveActions?: boolean): ApplicableRefactorInfo[]; - getEditsForRefactor(fileName: string, formatOptions: FormatCodeSettings, positionOrRange: number | TextRange, refactorName: string, actionName: string, preferences: UserPreferences | undefined, includeInteractiveActions?: InteractiveRefactorArguments): RefactorEditInfo | undefined; + getEditsForRefactor(fileName: string, formatOptions: FormatCodeSettings, positionOrRange: number | TextRange, refactorName: string, actionName: string, preferences: UserPreferences | undefined, interactiveRefactorArguments?: InteractiveRefactorArguments): RefactorEditInfo | undefined; getMoveToRefactoringFileSuggestions(fileName: string, positionOrRange: number | TextRange, preferences: UserPreferences | undefined, triggerReason?: RefactorTriggerReason, kind?: string): { newFileName: string, files: string[] }; organizeImports(args: OrganizeImportsArgs, formatOptions: FormatCodeSettings, preferences: UserPreferences | undefined): readonly FileTextChanges[]; getEditsForFileRename(oldFilePath: string, newFilePath: string, formatOptions: FormatCodeSettings, preferences: UserPreferences | undefined): readonly FileTextChanges[]; diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index af2e3d634e957..56c4b49ff6df2 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -10196,7 +10196,7 @@ declare namespace ts { * arguments for any interactive action before offering it. */ getApplicableRefactors(fileName: string, positionOrRange: number | TextRange, preferences: UserPreferences | undefined, triggerReason?: RefactorTriggerReason, kind?: string, includeInteractiveActions?: boolean): ApplicableRefactorInfo[]; - getEditsForRefactor(fileName: string, formatOptions: FormatCodeSettings, positionOrRange: number | TextRange, refactorName: string, actionName: string, preferences: UserPreferences | undefined, includeInteractiveActions?: InteractiveRefactorArguments): RefactorEditInfo | undefined; + getEditsForRefactor(fileName: string, formatOptions: FormatCodeSettings, positionOrRange: number | TextRange, refactorName: string, actionName: string, preferences: UserPreferences | undefined, interactiveRefactorArguments?: InteractiveRefactorArguments): RefactorEditInfo | undefined; getMoveToRefactoringFileSuggestions(fileName: string, positionOrRange: number | TextRange, preferences: UserPreferences | undefined, triggerReason?: RefactorTriggerReason, kind?: string): { newFileName: string; files: string[]; diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index e91614119d1c3..2b642a091ce2e 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -6227,7 +6227,7 @@ declare namespace ts { * arguments for any interactive action before offering it. */ getApplicableRefactors(fileName: string, positionOrRange: number | TextRange, preferences: UserPreferences | undefined, triggerReason?: RefactorTriggerReason, kind?: string, includeInteractiveActions?: boolean): ApplicableRefactorInfo[]; - getEditsForRefactor(fileName: string, formatOptions: FormatCodeSettings, positionOrRange: number | TextRange, refactorName: string, actionName: string, preferences: UserPreferences | undefined, includeInteractiveActions?: InteractiveRefactorArguments): RefactorEditInfo | undefined; + getEditsForRefactor(fileName: string, formatOptions: FormatCodeSettings, positionOrRange: number | TextRange, refactorName: string, actionName: string, preferences: UserPreferences | undefined, interactiveRefactorArguments?: InteractiveRefactorArguments): RefactorEditInfo | undefined; getMoveToRefactoringFileSuggestions(fileName: string, positionOrRange: number | TextRange, preferences: UserPreferences | undefined, triggerReason?: RefactorTriggerReason, kind?: string): { newFileName: string; files: string[]; diff --git a/tests/cases/fourslash/moveToFile_blankExistingFile.ts b/tests/cases/fourslash/moveToFile_blankExistingFile.ts index dda33fca96f55..7cccbec7c47bc 100644 --- a/tests/cases/fourslash/moveToFile_blankExistingFile.ts +++ b/tests/cases/fourslash/moveToFile_blankExistingFile.ts @@ -18,9 +18,13 @@ verify.moveToFile({ `, "/bar.ts": -`import { b } from './other'; +`// import { p } from './a'; + +import { b } from "./other"; + + const y: Date = p + b; `, }, diff --git a/tests/cases/fourslash/moveToFile_differentDirectories2.ts b/tests/cases/fourslash/moveToFile_differentDirectories2.ts index 4dba2881f189c..5c03c2bb6cfe2 100644 --- a/tests/cases/fourslash/moveToFile_differentDirectories2.ts +++ b/tests/cases/fourslash/moveToFile_differentDirectories2.ts @@ -23,8 +23,10 @@ export const a = 10; y;`, "/src/dir2/bar.ts": -`import { b } from '../dir1/other'; -import { a } from '../dir1/a'; +`import { a } from '../dir1/a'; + +import { b } from "../dir1/other"; + export const y = b + a; `, diff --git a/tests/cases/fourslash/server/moveToFile_emptyTargetFile.ts b/tests/cases/fourslash/server/moveToFile_emptyTargetFile.ts new file mode 100644 index 0000000000000..18429d0672915 --- /dev/null +++ b/tests/cases/fourslash/server/moveToFile_emptyTargetFile.ts @@ -0,0 +1,27 @@ +/// + +// @Filename: /source.ts +////[|export const a = 1;|] +////const b = 2; +////console.log(a, b); + +// @Filename: /target.ts +/////** empty */ + +// @Filename: /tsconfig.json +///// { "compilerOptions": { "newLine": "lf" } } + +verify.moveToFile({ + newFileContents: { + "/source.ts": +`import { a } from "./target"; + +const b = 2; +console.log(a, b);`, + "/target.ts": +`/** empty */ +export const a = 1; +`, + }, + interactiveRefactorArguments: { targetFile: "/target.ts" }, +});