From e6ec9d9254dcb120ef5b67478452a2f6d6866d6b Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Thu, 13 Apr 2023 13:29:57 -0700 Subject: [PATCH 1/2] Name per refactor --- .../addOrRemoveBracesToArrowFunction.ts | 3 +- ...onvertArrowFunctionOrFunctionExpression.ts | 3 +- src/services/refactors/convertExport.ts | 3 +- src/services/refactors/convertImport.ts | 3 +- .../convertOverloadListToSingleSignature.ts | 3 +- .../convertParamsToDestructuredObject.ts | 3 +- .../convertStringOrTemplateLiteral.ts | 3 +- .../convertToOptionalChainExpression.ts | 3 +- src/services/refactors/extractSymbol.ts | 3 +- src/services/refactors/extractType.ts | 3 +- .../generateGetAccessorAndSetAccessor.ts | 3 +- .../refactors/inferFunctionReturnType.ts | 3 +- src/services/refactors/moveToNewFile.ts | 3 +- src/services/types.ts | 41 ++++++++++++++++++- 14 files changed, 66 insertions(+), 14 deletions(-) diff --git a/src/services/refactors/addOrRemoveBracesToArrowFunction.ts b/src/services/refactors/addOrRemoveBracesToArrowFunction.ts index 930a1b42d443f..4a0691e0f84cf 100644 --- a/src/services/refactors/addOrRemoveBracesToArrowFunction.ts +++ b/src/services/refactors/addOrRemoveBracesToArrowFunction.ts @@ -22,6 +22,7 @@ import { rangeContainsRange, RefactorContext, RefactorEditInfo, + RefactorName, ReturnStatement, SourceFile, SyntaxKind, @@ -34,7 +35,7 @@ import { registerRefactor, } from "../_namespaces/ts.refactor"; -const refactorName = "Add or remove braces in an arrow function"; +const refactorName = RefactorName.AddOrRemoveBracesToArrowFunction; const refactorDescription = Diagnostics.Add_or_remove_braces_in_an_arrow_function.message; const addBracesAction = { diff --git a/src/services/refactors/convertArrowFunctionOrFunctionExpression.ts b/src/services/refactors/convertArrowFunctionOrFunctionExpression.ts index 414ef006bc926..f362ca5b72397 100644 --- a/src/services/refactors/convertArrowFunctionOrFunctionExpression.ts +++ b/src/services/refactors/convertArrowFunctionOrFunctionExpression.ts @@ -40,6 +40,7 @@ import { RefactorActionInfo, RefactorContext, RefactorEditInfo, + RefactorName, ReturnStatement, setTextRange, SourceFile, @@ -58,7 +59,7 @@ import { registerRefactor, } from "../_namespaces/ts.refactor"; -const refactorName = "Convert arrow function or function expression"; +const refactorName = RefactorName.ConvertArrowFunctionOrFunctionExpression; const refactorDescription = getLocaleSpecificMessage(Diagnostics.Convert_arrow_function_or_function_expression); const toAnonymousFunctionAction = { diff --git a/src/services/refactors/convertExport.ts b/src/services/refactors/convertExport.ts index 3760cd8b31321..819921a5d560a 100644 --- a/src/services/refactors/convertExport.ts +++ b/src/services/refactors/convertExport.ts @@ -43,6 +43,7 @@ import { quotePreferenceFromString, RefactorContext, RefactorEditInfo, + RefactorName, SourceFile, Symbol, SyntaxKind, @@ -57,7 +58,7 @@ import { registerRefactor, } from "../_namespaces/ts.refactor"; -const refactorName = "Convert export"; +const refactorName = RefactorName.ConvertExport; const defaultToNamedAction = { name: "Convert default export to named export", diff --git a/src/services/refactors/convertImport.ts b/src/services/refactors/convertImport.ts index da3a66107def6..18a58839e47ba 100644 --- a/src/services/refactors/convertImport.ts +++ b/src/services/refactors/convertImport.ts @@ -35,6 +35,7 @@ import { QualifiedName, RefactorContext, RefactorEditInfo, + RefactorName, ScriptTarget, some, SourceFile, @@ -50,7 +51,7 @@ import { registerRefactor, } from "../_namespaces/ts.refactor"; -const refactorName = "Convert import"; +const refactorName = RefactorName.ConvertImport; const actions = { [ImportKind.Named]: { diff --git a/src/services/refactors/convertOverloadListToSingleSignature.ts b/src/services/refactors/convertOverloadListToSingleSignature.ts index c0ad2970a6138..de42457f21ad3 100644 --- a/src/services/refactors/convertOverloadListToSingleSignature.ts +++ b/src/services/refactors/convertOverloadListToSingleSignature.ts @@ -30,6 +30,7 @@ import { rangeContainsPosition, RefactorContext, RefactorEditInfo, + RefactorName, setEmitFlags, setSyntheticLeadingComments, setTextRange, @@ -41,7 +42,7 @@ import { } from "../_namespaces/ts"; import { registerRefactor } from "../_namespaces/ts.refactor"; -const refactorName = "Convert overload list to single signature"; +const refactorName = RefactorName.ConvertOverloadListToSingleSignature; const refactorDescription = Diagnostics.Convert_overload_list_to_single_signature.message; const functionOverloadAction = { diff --git a/src/services/refactors/convertParamsToDestructuredObject.ts b/src/services/refactors/convertParamsToDestructuredObject.ts index a86e190d2d8cd..2f7220dad5e5c 100644 --- a/src/services/refactors/convertParamsToDestructuredObject.ts +++ b/src/services/refactors/convertParamsToDestructuredObject.ts @@ -90,6 +90,7 @@ import { rangeContainsRange, RefactorContext, RefactorEditInfo, + RefactorName, SemanticMeaning, ShorthandPropertyAssignment, sortAndDeduplicate, @@ -106,7 +107,7 @@ import { } from "../_namespaces/ts"; import { registerRefactor } from "../_namespaces/ts.refactor"; -const refactorName = "Convert parameters to destructured object"; +const refactorName = RefactorName.ConvertParamsToDestructuredObject; const minimumParameterLength = 1; const refactorDescription = getLocaleSpecificMessage(Diagnostics.Convert_parameters_to_destructured_object); diff --git a/src/services/refactors/convertStringOrTemplateLiteral.ts b/src/services/refactors/convertStringOrTemplateLiteral.ts index 53ac3c4bb5628..035902f909c0b 100644 --- a/src/services/refactors/convertStringOrTemplateLiteral.ts +++ b/src/services/refactors/convertStringOrTemplateLiteral.ts @@ -27,6 +27,7 @@ import { ParenthesizedExpression, RefactorContext, RefactorEditInfo, + RefactorName, SourceFile, SyntaxKind, TemplateHead, @@ -38,7 +39,7 @@ import { } from "../_namespaces/ts"; import { registerRefactor } from "../_namespaces/ts.refactor"; -const refactorName = "Convert to template string"; +const refactorName = RefactorName.ConvertStringOrTemplateLiteral; const refactorDescription = getLocaleSpecificMessage(Diagnostics.Convert_to_template_string); const convertStringAction = { diff --git a/src/services/refactors/convertToOptionalChainExpression.ts b/src/services/refactors/convertToOptionalChainExpression.ts index a86777e97dd63..39424e82c2dc8 100644 --- a/src/services/refactors/convertToOptionalChainExpression.ts +++ b/src/services/refactors/convertToOptionalChainExpression.ts @@ -32,6 +32,7 @@ import { PropertyAccessExpression, RefactorContext, RefactorEditInfo, + RefactorName, ReturnStatement, skipParentheses, SourceFile, @@ -47,7 +48,7 @@ import { registerRefactor, } from "../_namespaces/ts.refactor"; -const refactorName = "Convert to optional chain expression"; +const refactorName = RefactorName.ConvertToOptionalChainExpression; const convertToOptionalChainExpressionMessage = getLocaleSpecificMessage(Diagnostics.Convert_to_optional_chain_expression); const toOptionalChainAction = { diff --git a/src/services/refactors/extractSymbol.ts b/src/services/refactors/extractSymbol.ts index 9efcce3006624..a26308f4adc4e 100644 --- a/src/services/refactors/extractSymbol.ts +++ b/src/services/refactors/extractSymbol.ts @@ -129,6 +129,7 @@ import { RefactorActionInfo, RefactorContext, RefactorEditInfo, + RefactorName, setEmitFlags, ShorthandPropertyAssignment, SignatureKind, @@ -165,7 +166,7 @@ import { registerRefactor, } from "../_namespaces/ts.refactor"; -const refactorName = "Extract Symbol"; +const refactorName = RefactorName.ExtractSymbol; const extractConstantAction = { name: "Extract Constant", diff --git a/src/services/refactors/extractType.ts b/src/services/refactors/extractType.ts index 02e5f10ba22d9..4e3dfd557640b 100644 --- a/src/services/refactors/extractType.ts +++ b/src/services/refactors/extractType.ts @@ -52,6 +52,7 @@ import { rangeContainsStartEnd, RefactorContext, RefactorEditInfo, + RefactorName, setEmitFlags, setTextRange, skipTrivia, @@ -70,7 +71,7 @@ import { registerRefactor, } from "../_namespaces/ts.refactor"; -const refactorName = "Extract type"; +const refactorName = RefactorName.ExtractType; const extractToTypeAliasAction = { name: "Extract to type alias", diff --git a/src/services/refactors/generateGetAccessorAndSetAccessor.ts b/src/services/refactors/generateGetAccessorAndSetAccessor.ts index 2982cacd8af7c..5e39c096983e2 100644 --- a/src/services/refactors/generateGetAccessorAndSetAccessor.ts +++ b/src/services/refactors/generateGetAccessorAndSetAccessor.ts @@ -8,13 +8,14 @@ import { isIdentifier, isParameter, RefactorContext, + RefactorName, } from "../_namespaces/ts"; import { isRefactorErrorInfo, registerRefactor, } from "../_namespaces/ts.refactor"; -const actionName = "Generate 'get' and 'set' accessors"; +const actionName = RefactorName.GenerateGetAccessorAndSetAccessor; const actionDescription = Diagnostics.Generate_get_and_set_accessors.message; const generateGetSetAction = { diff --git a/src/services/refactors/inferFunctionReturnType.ts b/src/services/refactors/inferFunctionReturnType.ts index ccce34a8a14ac..c6777dc8a2aa4 100644 --- a/src/services/refactors/inferFunctionReturnType.ts +++ b/src/services/refactors/inferFunctionReturnType.ts @@ -20,6 +20,7 @@ import { NodeBuilderFlags, RefactorContext, RefactorEditInfo, + RefactorName, SourceFile, SyntaxKind, textChanges, @@ -34,7 +35,7 @@ import { registerRefactor, } from "../_namespaces/ts.refactor"; -const refactorName = "Infer function return type"; +const refactorName = RefactorName.InferFunctionReturnType; const refactorDescription = Diagnostics.Infer_function_return_type.message; const inferReturnTypeAction = { diff --git a/src/services/refactors/moveToNewFile.ts b/src/services/refactors/moveToNewFile.ts index 2fdff08a85282..d5f18d52efd8e 100644 --- a/src/services/refactors/moveToNewFile.ts +++ b/src/services/refactors/moveToNewFile.ts @@ -104,6 +104,7 @@ import { rangeContainsRange, RefactorContext, RefactorEditInfo, + RefactorName, RequireOrImportCall, RequireVariableStatement, resolvePath, @@ -131,7 +132,7 @@ import { } from "../_namespaces/ts"; import { registerRefactor } from "../_namespaces/ts.refactor"; -const refactorName = "Move to a new file"; +const refactorName = RefactorName.MoveToNewFile; const description = getLocaleSpecificMessage(Diagnostics.Move_to_a_new_file); const moveToNewFileAction = { diff --git a/src/services/types.ts b/src/services/types.ts index 08d983b8691c9..b28d477a18a7f 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -631,6 +631,11 @@ export interface LanguageService { applyCodeActionCommand(fileName: string, action: CodeActionCommand | CodeActionCommand[]): Promise; getApplicableRefactors(fileName: string, positionOrRange: number | TextRange, preferences: UserPreferences | undefined, triggerReason?: RefactorTriggerReason, kind?: string): ApplicableRefactorInfo[]; + getEditsForRefactor(fileName: string, formatOptions: FormatCodeSettings, positionOrRange: number | TextRange, refactorName: Name, actionName: string, preferences: UserPreferences | undefined, ...args: RefactorHandlerArgs): RefactorEditInfo | undefined; + /** + * @deprecated + * As of TypeScript 5.1, some refactors may require an additional argument. + */ getEditsForRefactor(fileName: string, formatOptions: FormatCodeSettings, positionOrRange: number | TextRange, refactorName: string, actionName: string, preferences: UserPreferences | undefined): RefactorEditInfo | undefined; organizeImports(args: OrganizeImportsArgs, formatOptions: FormatCodeSettings, preferences: UserPreferences | undefined): readonly FileTextChanges[]; getEditsForFileRename(oldFilePath: string, newFilePath: string, formatOptions: FormatCodeSettings, preferences: UserPreferences | undefined): readonly FileTextChanges[]; @@ -911,6 +916,40 @@ export interface InstallPackageAction { /** @internal */ readonly packageName: string; } +export const enum RefactorName { + AddOrRemoveBracesToArrowFunction = "Add or remove braces in an arrow function", + ConvertArrowFunctionOrFunctionExpression = "Convert arrow function or function expression", + ConvertExport = "Convert export", + ConvertImport = "Convert import", + ConvertOverloadListToSingleSignature = "Convert overload list to single signature", + ConvertParamsToDestructuredObject = "Convert parameters to destructured object", + ConvertStringOrTemplateLiteral = "Convert to template string", + ConvertToOptionalChainExpression = "Convert to optional chain expression", + ExtractSymbol = "Extract Symbol", + ExtractType = "Extract type", + GenerateGetAccessorAndSetAccessor = "Generate 'get' and 'set' accessors", + InferFunctionReturnType = "Infer function return type", + MoveToNewFile = "Move to a new file", +} + +interface RefactorHandlerMap { + [RefactorName.AddOrRemoveBracesToArrowFunction]: () => void; + [RefactorName.ConvertArrowFunctionOrFunctionExpression]: () => void; + [RefactorName.ConvertExport]: () => void; + [RefactorName.ConvertImport]: () => void; + [RefactorName.ConvertOverloadListToSingleSignature]: () => void; + [RefactorName.ConvertParamsToDestructuredObject]: () => void; + [RefactorName.ConvertStringOrTemplateLiteral]: () => void; + [RefactorName.ConvertToOptionalChainExpression]: () => void; + [RefactorName.ExtractSymbol]: () => void; + [RefactorName.ExtractType]: () => void; + [RefactorName.GenerateGetAccessorAndSetAccessor]: () => void; + [RefactorName.InferFunctionReturnType]: () => void; + [RefactorName.MoveToNewFile]: () => void; +} + +type RefactorHandlerArgs = RefactorHandlerMap[T] extends (...args: infer A) => void ? A : never; + /** * A set of one or more available refactoring actions, grouped under a parent refactoring. */ @@ -918,7 +957,7 @@ export interface ApplicableRefactorInfo { /** * The programmatic name of the refactoring */ - name: string; + name: RefactorName; /** * A description of this refactoring category to show to the user. * If the refactoring gets inlined (see below), this text will not be visible. From 2ca0dfd5815d137880e59db9baf9e96858d179a0 Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Fri, 14 Apr 2023 15:14:19 -0700 Subject: [PATCH 2/2] Separate interactive from non-interactive refactors in API --- src/harness/client.ts | 10 ++- src/harness/harnessLanguageService.ts | 3 + src/server/protocol.ts | 23 ++++- src/services/refactorProvider.ts | 21 +++-- .../addOrRemoveBracesToArrowFunction.ts | 4 +- ...onvertArrowFunctionOrFunctionExpression.ts | 4 +- src/services/refactors/convertExport.ts | 4 +- src/services/refactors/convertImport.ts | 4 +- .../convertOverloadListToSingleSignature.ts | 4 +- .../convertParamsToDestructuredObject.ts | 4 +- .../convertStringOrTemplateLiteral.ts | 4 +- .../convertToOptionalChainExpression.ts | 4 +- src/services/refactors/extractSymbol.ts | 4 +- src/services/refactors/extractType.ts | 4 +- .../generateGetAccessorAndSetAccessor.ts | 4 +- .../refactors/inferFunctionReturnType.ts | 4 +- src/services/refactors/moveToNewFile.ts | 4 +- src/services/services.ts | 13 ++- src/services/types.ts | 84 ++++++++++++++----- .../reference/api/tsserverlibrary.d.ts | 75 +++++++++++++++-- tests/baselines/reference/api/typescript.d.ts | 59 ++++++++++++- 21 files changed, 266 insertions(+), 74 deletions(-) diff --git a/src/harness/client.ts b/src/harness/client.ts index a74e616072302..230ba2354a959 100644 --- a/src/harness/client.ts +++ b/src/harness/client.ts @@ -1,4 +1,6 @@ import { + ApplicableInteractiveRefactorInfo, + ApplicableNonInteractiveRefactorInfo, ApplicableRefactorInfo, CallHierarchyIncomingCall, CallHierarchyItem, @@ -52,6 +54,7 @@ import { Program, QuickInfo, RefactorEditInfo, + RefactorTriggerReason, ReferencedSymbol, ReferenceEntry, RenameInfo, @@ -785,10 +788,13 @@ export class SessionClient implements LanguageService { return { file, line, offset, endLine, endOffset }; } - getApplicableRefactors(fileName: string, positionOrRange: number | TextRange): ApplicableRefactorInfo[] { + getApplicableRefactors(fileName: string, positionOrRange: number | TextRange, preferences: UserPreferences | undefined, triggerReason?: RefactorTriggerReason, kind?: string): ApplicableNonInteractiveRefactorInfo[]; + getApplicableRefactors(fileName: string, positionOrRange: number | TextRange, preferences: UserPreferences | undefined, triggerReason: RefactorTriggerReason | undefined, kind: string | undefined, includeInteractive: true): ApplicableInteractiveRefactorInfo[]; + getApplicableRefactors(fileName: string, positionOrRange: number | TextRange, preferences: UserPreferences | undefined, triggerReason?: RefactorTriggerReason, kind?: string, includeInteractive?: boolean): ApplicableRefactorInfo[]; + getApplicableRefactors(fileName: string, positionOrRange: number | TextRange, _preferences: UserPreferences | undefined, _triggerReason?: RefactorTriggerReason, _kind?: string, includeInteractive?: boolean): ApplicableRefactorInfo[] { const args = this.createFileLocationOrRangeRequestArgs(positionOrRange, fileName); - const request = this.processRequest(protocol.CommandTypes.GetApplicableRefactors, args); + const request = this.processRequest(protocol.CommandTypes.GetApplicableRefactors, { ...args, includeInteractive }); const response = this.processResponse(request); return response.body!; // TODO: GH#18217 } diff --git a/src/harness/harnessLanguageService.ts b/src/harness/harnessLanguageService.ts index a8c854325a266..edab4b4461cd6 100644 --- a/src/harness/harnessLanguageService.ts +++ b/src/harness/harnessLanguageService.ts @@ -613,6 +613,9 @@ class LanguageServiceShimProxy implements ts.LanguageService { getEditsForRefactor(): ts.RefactorEditInfo { throw new Error("Not supported on the shim."); } + getApplicableRefactors(): ts.ApplicableNonInteractiveRefactorInfo[]; + getApplicableRefactors(): ts.ApplicableInteractiveRefactorInfo[]; + getApplicableRefactors(): ts.ApplicableRefactorInfo[]; getApplicableRefactors(): ts.ApplicableRefactorInfo[] { throw new Error("Not supported on the shim."); } diff --git a/src/server/protocol.ts b/src/server/protocol.ts index 819c4797084fc..24fb1603cdcfe 100644 --- a/src/server/protocol.ts +++ b/src/server/protocol.ts @@ -4,11 +4,14 @@ import type { EndOfLineState, FileExtensionInfo, HighlightSpanKind, + InteractiveRefactorName, MapLike, + NonInteractiveRefactorName, OutliningSpanKind, OutputFile, PluginImport, ProjectReference, + RefactorName, RenameLocation, ScriptElementKind, ScriptKind, @@ -586,6 +589,7 @@ export interface GetApplicableRefactorsRequest extends Request { export type GetApplicableRefactorsRequestArgs = FileLocationOrRangeRequestArgs & { triggerReason?: RefactorTriggerReason; kind?: string; + includeInteractive?: boolean; }; export type RefactorTriggerReason = "implicit" | "invoked"; @@ -594,18 +598,18 @@ export type RefactorTriggerReason = "implicit" | "invoked"; * Response is a list of available refactorings. * Each refactoring exposes one or more "Actions"; a user selects one action to invoke a refactoring */ -export interface GetApplicableRefactorsResponse extends Response { - body?: ApplicableRefactorInfo[]; +export interface GetApplicableRefactorsResponse extends Response { + body?: TRefactorInfo[]; } /** * A set of one or more available refactoring actions, grouped under a parent refactoring. */ -export interface ApplicableRefactorInfo { +export interface BaseApplicableRefactorInfo { /** * The programmatic name of the refactoring */ - name: string; + name: RefactorName; /** * A description of this refactoring category to show to the user. * If the refactoring gets inlined (see below), this text will not be visible. @@ -623,6 +627,17 @@ export interface ApplicableRefactorInfo { actions: RefactorActionInfo[]; } +export interface ApplicableNonInteractiveRefactorInfo extends BaseApplicableRefactorInfo { + name: NonInteractiveRefactorName; +} + +export interface ApplicableInteractiveRefactorInfo extends BaseApplicableRefactorInfo { + name: InteractiveRefactorName; +} + +export type ApplicableRefactorInfo = ApplicableNonInteractiveRefactorInfo | ApplicableInteractiveRefactorInfo; + + /** * Represents a single refactoring action - for example, the "Extract Method..." refactor might * offer several actions, each corresponding to a surround class or closure to extract into. diff --git a/src/services/refactorProvider.ts b/src/services/refactorProvider.ts index 611c846c21c99..d04357c683349 100644 --- a/src/services/refactorProvider.ts +++ b/src/services/refactorProvider.ts @@ -2,35 +2,44 @@ import { ApplicableRefactorInfo, arrayFrom, flatMapIterator, + InteractiveRefactor, + InteractiveRefactorName, + NonInteractiveRefactor, + NonInteractiveRefactorName, Refactor, RefactorContext, RefactorEditInfo, + RefactorName, } from "./_namespaces/ts"; import { refactorKindBeginsWith } from "./_namespaces/ts.refactor"; // A map with the refactor code as key, the refactor itself as value // e.g. nonSuggestableRefactors[refactorCode] -> the refactor you want -const refactors = new Map(); +const refactors = new Map(); /** * @param name An unique code associated with each refactor. Does not have to be human-readable. * * @internal */ -export function registerRefactor(name: string, refactor: Refactor) { +export function registerRefactor(name: Name, refactor: InteractiveRefactor): void; +/** @internal */ +export function registerRefactor(name: NonInteractiveRefactorName, refactor: NonInteractiveRefactor): void; +/** @internal */ +export function registerRefactor(name: RefactorName, refactor: Refactor) { refactors.set(name, refactor); } /** @internal */ -export function getApplicableRefactors(context: RefactorContext): ApplicableRefactorInfo[] { +export function getApplicableRefactors(context: RefactorContext, includeInteractive?: boolean): ApplicableRefactorInfo[] { return arrayFrom(flatMapIterator(refactors.values(), refactor => context.cancellationToken && context.cancellationToken.isCancellationRequested() || - !refactor.kinds?.some(kind => refactorKindBeginsWith(kind, context.kind)) ? undefined : + (!refactor.kinds?.some(kind => refactorKindBeginsWith(kind, context.kind)) || refactor.isInteractive && !includeInteractive) ? undefined : refactor.getAvailableActions(context))); } /** @internal */ -export function getEditsForRefactor(context: RefactorContext, refactorName: string, actionName: string): RefactorEditInfo | undefined { +export function getEditsForRefactor(context: RefactorContext, refactorName: RefactorName, actionName: string, ...args: unknown[]): RefactorEditInfo | undefined { const refactor = refactors.get(refactorName); - return refactor && refactor.getEditsForAction(context, actionName); + return refactor && refactor.getEditsForAction(context, actionName, ...args); } diff --git a/src/services/refactors/addOrRemoveBracesToArrowFunction.ts b/src/services/refactors/addOrRemoveBracesToArrowFunction.ts index 4a0691e0f84cf..e592704bda29b 100644 --- a/src/services/refactors/addOrRemoveBracesToArrowFunction.ts +++ b/src/services/refactors/addOrRemoveBracesToArrowFunction.ts @@ -19,10 +19,10 @@ import { isExpression, isReturnStatement, needsParentheses, + NonInteractiveRefactorName, rangeContainsRange, RefactorContext, RefactorEditInfo, - RefactorName, ReturnStatement, SourceFile, SyntaxKind, @@ -35,7 +35,7 @@ import { registerRefactor, } from "../_namespaces/ts.refactor"; -const refactorName = RefactorName.AddOrRemoveBracesToArrowFunction; +const refactorName = NonInteractiveRefactorName.AddOrRemoveBracesToArrowFunction; const refactorDescription = Diagnostics.Add_or_remove_braces_in_an_arrow_function.message; const addBracesAction = { diff --git a/src/services/refactors/convertArrowFunctionOrFunctionExpression.ts b/src/services/refactors/convertArrowFunctionOrFunctionExpression.ts index f362ca5b72397..c4ca5de7f4f1e 100644 --- a/src/services/refactors/convertArrowFunctionOrFunctionExpression.ts +++ b/src/services/refactors/convertArrowFunctionOrFunctionExpression.ts @@ -35,12 +35,12 @@ import { length, ModifierFlags, Node, + NonInteractiveRefactorName, Program, rangeContainsRange, RefactorActionInfo, RefactorContext, RefactorEditInfo, - RefactorName, ReturnStatement, setTextRange, SourceFile, @@ -59,7 +59,7 @@ import { registerRefactor, } from "../_namespaces/ts.refactor"; -const refactorName = RefactorName.ConvertArrowFunctionOrFunctionExpression; +const refactorName = NonInteractiveRefactorName.ConvertArrowFunctionOrFunctionExpression; const refactorDescription = getLocaleSpecificMessage(Diagnostics.Convert_arrow_function_or_function_expression); const toAnonymousFunctionAction = { diff --git a/src/services/refactors/convertExport.ts b/src/services/refactors/convertExport.ts index 819921a5d560a..f01a9f9d3093d 100644 --- a/src/services/refactors/convertExport.ts +++ b/src/services/refactors/convertExport.ts @@ -37,13 +37,13 @@ import { NamespaceDeclaration, Node, NodeFlags, + NonInteractiveRefactorName, Program, PropertyAccessExpression, QuotePreference, quotePreferenceFromString, RefactorContext, RefactorEditInfo, - RefactorName, SourceFile, Symbol, SyntaxKind, @@ -58,7 +58,7 @@ import { registerRefactor, } from "../_namespaces/ts.refactor"; -const refactorName = RefactorName.ConvertExport; +const refactorName = NonInteractiveRefactorName.ConvertExport; const defaultToNamedAction = { name: "Convert default export to named export", diff --git a/src/services/refactors/convertImport.ts b/src/services/refactors/convertImport.ts index 18a58839e47ba..5d81ddda9e5b0 100644 --- a/src/services/refactors/convertImport.ts +++ b/src/services/refactors/convertImport.ts @@ -30,12 +30,12 @@ import { isStringLiteral, NamedImports, NamespaceImport, + NonInteractiveRefactorName, Program, PropertyAccessExpression, QualifiedName, RefactorContext, RefactorEditInfo, - RefactorName, ScriptTarget, some, SourceFile, @@ -51,7 +51,7 @@ import { registerRefactor, } from "../_namespaces/ts.refactor"; -const refactorName = RefactorName.ConvertImport; +const refactorName = NonInteractiveRefactorName.ConvertImport; const actions = { [ImportKind.Named]: { diff --git a/src/services/refactors/convertOverloadListToSingleSignature.ts b/src/services/refactors/convertOverloadListToSingleSignature.ts index de42457f21ad3..6faf0a310569e 100644 --- a/src/services/refactors/convertOverloadListToSingleSignature.ts +++ b/src/services/refactors/convertOverloadListToSingleSignature.ts @@ -25,12 +25,12 @@ import { NamedTupleMember, Node, NodeArray, + NonInteractiveRefactorName, ParameterDeclaration, Program, rangeContainsPosition, RefactorContext, RefactorEditInfo, - RefactorName, setEmitFlags, setSyntheticLeadingComments, setTextRange, @@ -42,7 +42,7 @@ import { } from "../_namespaces/ts"; import { registerRefactor } from "../_namespaces/ts.refactor"; -const refactorName = RefactorName.ConvertOverloadListToSingleSignature; +const refactorName = NonInteractiveRefactorName.ConvertOverloadListToSingleSignature; const refactorDescription = Diagnostics.Convert_overload_list_to_single_signature.message; const functionOverloadAction = { diff --git a/src/services/refactors/convertParamsToDestructuredObject.ts b/src/services/refactors/convertParamsToDestructuredObject.ts index 2f7220dad5e5c..217baf29fb80c 100644 --- a/src/services/refactors/convertParamsToDestructuredObject.ts +++ b/src/services/refactors/convertParamsToDestructuredObject.ts @@ -80,6 +80,7 @@ import { NewExpression, Node, NodeArray, + NonInteractiveRefactorName, ObjectLiteralElementLike, ObjectLiteralExpression, ParameterDeclaration, @@ -90,7 +91,6 @@ import { rangeContainsRange, RefactorContext, RefactorEditInfo, - RefactorName, SemanticMeaning, ShorthandPropertyAssignment, sortAndDeduplicate, @@ -107,7 +107,7 @@ import { } from "../_namespaces/ts"; import { registerRefactor } from "../_namespaces/ts.refactor"; -const refactorName = RefactorName.ConvertParamsToDestructuredObject; +const refactorName = NonInteractiveRefactorName.ConvertParamsToDestructuredObject; const minimumParameterLength = 1; const refactorDescription = getLocaleSpecificMessage(Diagnostics.Convert_parameters_to_destructured_object); diff --git a/src/services/refactors/convertStringOrTemplateLiteral.ts b/src/services/refactors/convertStringOrTemplateLiteral.ts index 035902f909c0b..3b24487ae3a57 100644 --- a/src/services/refactors/convertStringOrTemplateLiteral.ts +++ b/src/services/refactors/convertStringOrTemplateLiteral.ts @@ -24,10 +24,10 @@ import { isTemplateMiddle, map, Node, + NonInteractiveRefactorName, ParenthesizedExpression, RefactorContext, RefactorEditInfo, - RefactorName, SourceFile, SyntaxKind, TemplateHead, @@ -39,7 +39,7 @@ import { } from "../_namespaces/ts"; import { registerRefactor } from "../_namespaces/ts.refactor"; -const refactorName = RefactorName.ConvertStringOrTemplateLiteral; +const refactorName = NonInteractiveRefactorName.ConvertStringOrTemplateLiteral; const refactorDescription = getLocaleSpecificMessage(Diagnostics.Convert_to_template_string); const convertStringAction = { diff --git a/src/services/refactors/convertToOptionalChainExpression.ts b/src/services/refactors/convertToOptionalChainExpression.ts index 39424e82c2dc8..40d4e05f151cd 100644 --- a/src/services/refactors/convertToOptionalChainExpression.ts +++ b/src/services/refactors/convertToOptionalChainExpression.ts @@ -29,10 +29,10 @@ import { isStringOrNumericLiteralLike, isVariableStatement, Node, + NonInteractiveRefactorName, PropertyAccessExpression, RefactorContext, RefactorEditInfo, - RefactorName, ReturnStatement, skipParentheses, SourceFile, @@ -48,7 +48,7 @@ import { registerRefactor, } from "../_namespaces/ts.refactor"; -const refactorName = RefactorName.ConvertToOptionalChainExpression; +const refactorName = NonInteractiveRefactorName.ConvertToOptionalChainExpression; const convertToOptionalChainExpressionMessage = getLocaleSpecificMessage(Diagnostics.Convert_to_optional_chain_expression); const toOptionalChainAction = { diff --git a/src/services/refactors/extractSymbol.ts b/src/services/refactors/extractSymbol.ts index a26308f4adc4e..5e3eccf0316c1 100644 --- a/src/services/refactors/extractSymbol.ts +++ b/src/services/refactors/extractSymbol.ts @@ -120,6 +120,7 @@ import { Node, NodeBuilderFlags, NodeFlags, + NonInteractiveRefactorName, nullTransformationContext, ObjectLiteralElementLike, ParameterDeclaration, @@ -129,7 +130,6 @@ import { RefactorActionInfo, RefactorContext, RefactorEditInfo, - RefactorName, setEmitFlags, ShorthandPropertyAssignment, SignatureKind, @@ -166,7 +166,7 @@ import { registerRefactor, } from "../_namespaces/ts.refactor"; -const refactorName = RefactorName.ExtractSymbol; +const refactorName = NonInteractiveRefactorName.ExtractSymbol; const extractConstantAction = { name: "Extract Constant", diff --git a/src/services/refactors/extractType.ts b/src/services/refactors/extractType.ts index 4e3dfd557640b..587b1149fc1a5 100644 --- a/src/services/refactors/extractType.ts +++ b/src/services/refactors/extractType.ts @@ -48,11 +48,11 @@ import { JSDocTemplateTag, Node, nodeOverlapsWithStartEnd, + NonInteractiveRefactorName, pushIfUnique, rangeContainsStartEnd, RefactorContext, RefactorEditInfo, - RefactorName, setEmitFlags, setTextRange, skipTrivia, @@ -71,7 +71,7 @@ import { registerRefactor, } from "../_namespaces/ts.refactor"; -const refactorName = RefactorName.ExtractType; +const refactorName = NonInteractiveRefactorName.ExtractType; const extractToTypeAliasAction = { name: "Extract to type alias", diff --git a/src/services/refactors/generateGetAccessorAndSetAccessor.ts b/src/services/refactors/generateGetAccessorAndSetAccessor.ts index 5e39c096983e2..f85eb48d54bec 100644 --- a/src/services/refactors/generateGetAccessorAndSetAccessor.ts +++ b/src/services/refactors/generateGetAccessorAndSetAccessor.ts @@ -7,15 +7,15 @@ import { getRenameLocation, isIdentifier, isParameter, + NonInteractiveRefactorName, RefactorContext, - RefactorName, } from "../_namespaces/ts"; import { isRefactorErrorInfo, registerRefactor, } from "../_namespaces/ts.refactor"; -const actionName = RefactorName.GenerateGetAccessorAndSetAccessor; +const actionName = NonInteractiveRefactorName.GenerateGetAccessorAndSetAccessor; const actionDescription = Diagnostics.Generate_get_and_set_accessors.message; const generateGetSetAction = { diff --git a/src/services/refactors/inferFunctionReturnType.ts b/src/services/refactors/inferFunctionReturnType.ts index c6777dc8a2aa4..7c05531ca6b84 100644 --- a/src/services/refactors/inferFunctionReturnType.ts +++ b/src/services/refactors/inferFunctionReturnType.ts @@ -18,9 +18,9 @@ import { MethodDeclaration, Node, NodeBuilderFlags, + NonInteractiveRefactorName, RefactorContext, RefactorEditInfo, - RefactorName, SourceFile, SyntaxKind, textChanges, @@ -35,7 +35,7 @@ import { registerRefactor, } from "../_namespaces/ts.refactor"; -const refactorName = RefactorName.InferFunctionReturnType; +const refactorName = NonInteractiveRefactorName.InferFunctionReturnType; const refactorDescription = Diagnostics.Infer_function_return_type.message; const inferReturnTypeAction = { diff --git a/src/services/refactors/moveToNewFile.ts b/src/services/refactors/moveToNewFile.ts index d5f18d52efd8e..f18a643f1330a 100644 --- a/src/services/refactors/moveToNewFile.ts +++ b/src/services/refactors/moveToNewFile.ts @@ -95,6 +95,7 @@ import { Node, NodeFlags, nodeSeenTracker, + NonInteractiveRefactorName, normalizePath, ObjectBindingElementWithoutPropertyName, Program, @@ -104,7 +105,6 @@ import { rangeContainsRange, RefactorContext, RefactorEditInfo, - RefactorName, RequireOrImportCall, RequireVariableStatement, resolvePath, @@ -132,7 +132,7 @@ import { } from "../_namespaces/ts"; import { registerRefactor } from "../_namespaces/ts.refactor"; -const refactorName = RefactorName.MoveToNewFile; +const refactorName = NonInteractiveRefactorName.MoveToNewFile; const description = getLocaleSpecificMessage(Diagnostics.Move_to_a_new_file); const moveToNewFileAction = { diff --git a/src/services/services.ts b/src/services/services.ts index ceb05c7420949..7bd2c6e472c72 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1,5 +1,6 @@ import { __String, + ApplicableNonInteractiveRefactorInfo, ApplicableRefactorInfo, ApplyCodeActionCommandResult, AssignmentDeclarationKind, @@ -243,6 +244,7 @@ import { refactor, RefactorContext, RefactorEditInfo, + RefactorName, RefactorTriggerReason, ReferencedSymbol, ReferenceEntry, @@ -2967,23 +2969,26 @@ export function createLanguageService( return SmartSelectionRange.getSmartSelectionRange(position, syntaxTreeCache.getCurrentSourceFile(fileName)); } - function getApplicableRefactors(fileName: string, positionOrRange: number | TextRange, preferences: UserPreferences = emptyOptions, triggerReason: RefactorTriggerReason, kind: string): ApplicableRefactorInfo[] { + function getApplicableRefactors(fileName: string, positionOrRange: number | TextRange, preferences: UserPreferences | undefined, triggerReason?: RefactorTriggerReason, kind?: string): ApplicableNonInteractiveRefactorInfo[]; + function getApplicableRefactors(fileName: string, positionOrRange: number | TextRange, preferences: UserPreferences | undefined, triggerReason?: RefactorTriggerReason, kind?: string, includeInteractive?: boolean): ApplicableRefactorInfo[]; + function getApplicableRefactors(fileName: string, positionOrRange: number | TextRange, preferences: UserPreferences | undefined = emptyOptions, triggerReason?: RefactorTriggerReason, kind?: string, includeInteractive?: boolean): ApplicableRefactorInfo[] { synchronizeHostData(); const file = getValidSourceFile(fileName); - return refactor.getApplicableRefactors(getRefactorContext(file, positionOrRange, preferences, emptyOptions, triggerReason, kind)); + return refactor.getApplicableRefactors(getRefactorContext(file, positionOrRange, preferences, emptyOptions, triggerReason, kind), includeInteractive); } function getEditsForRefactor( fileName: string, formatOptions: FormatCodeSettings, positionOrRange: number | TextRange, - refactorName: string, + refactorName: RefactorName, actionName: string, preferences: UserPreferences = emptyOptions, + ...args: unknown[] ): RefactorEditInfo | undefined { synchronizeHostData(); const file = getValidSourceFile(fileName); - return refactor.getEditsForRefactor(getRefactorContext(file, positionOrRange, preferences, formatOptions), refactorName, actionName); + return refactor.getEditsForRefactor(getRefactorContext(file, positionOrRange, preferences, formatOptions), refactorName, actionName, ...args); } function toLineColumnOffset(fileName: string, position: number): LineAndCharacter { diff --git a/src/services/types.ts b/src/services/types.ts index b28d477a18a7f..f7245bbd80155 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -630,12 +630,31 @@ export interface LanguageService { /** @deprecated `fileName` will be ignored */ applyCodeActionCommand(fileName: string, action: CodeActionCommand | CodeActionCommand[]): Promise; - getApplicableRefactors(fileName: string, positionOrRange: number | TextRange, preferences: UserPreferences | undefined, triggerReason?: RefactorTriggerReason, kind?: string): ApplicableRefactorInfo[]; - getEditsForRefactor(fileName: string, formatOptions: FormatCodeSettings, positionOrRange: number | TextRange, refactorName: Name, actionName: string, preferences: UserPreferences | undefined, ...args: RefactorHandlerArgs): RefactorEditInfo | undefined; + getApplicableRefactors(fileName: string, positionOrRange: number | TextRange, preferences: UserPreferences | undefined, triggerReason?: RefactorTriggerReason, kind?: string, includeInteractive?: false): ApplicableNonInteractiveRefactorInfo[]; + getApplicableRefactors(fileName: string, positionOrRange: number | TextRange, preferences: UserPreferences | undefined, triggerReason?: RefactorTriggerReason, kind?: string, includeInteractive?: boolean): ApplicableRefactorInfo[]; + + getEditsForRefactor(fileName: string, formatOptions: FormatCodeSettings, positionOrRange: number | TextRange, refactorName: Name, actionName: string, preferences: UserPreferences | undefined, ...args: RefactorArgs): RefactorEditInfo | undefined; + getEditsForRefactor(fileName: string, formatOptions: FormatCodeSettings, positionOrRange: number | TextRange, refactorName: NonInteractiveRefactorName, actionName: string, preferences: UserPreferences | undefined): RefactorEditInfo | undefined; /** * @deprecated * As of TypeScript 5.1, some refactors may require an additional argument. + * If the `isInteractive` property of the `ApplicableRefactorInfo` object is `true`, + * getting edits for the refactor will require a specific argument linked to its name: + * + * ```ts + * if (refactor.isInteractive) { + * if (refactor.name === ts.InteractiveRefactorName.MoveToFile) { + * const action = getAction(refactor); + * const edits = ls.getEditsForRefactor(fileName, formatOptions, positionOrRange, refactor.name, action.name, preferences, { + * targetFile: await askUserForTargetFileName() + * }); + * } + * } else { + * const edits = ls.getEditsForRefactor(...) + * } + * ``` */ + // eslint-disable-next-line @typescript-eslint/unified-signatures getEditsForRefactor(fileName: string, formatOptions: FormatCodeSettings, positionOrRange: number | TextRange, refactorName: string, actionName: string, preferences: UserPreferences | undefined): RefactorEditInfo | undefined; organizeImports(args: OrganizeImportsArgs, formatOptions: FormatCodeSettings, preferences: UserPreferences | undefined): readonly FileTextChanges[]; getEditsForFileRename(oldFilePath: string, newFilePath: string, formatOptions: FormatCodeSettings, preferences: UserPreferences | undefined): readonly FileTextChanges[]; @@ -916,7 +935,7 @@ export interface InstallPackageAction { /** @internal */ readonly packageName: string; } -export const enum RefactorName { +export const enum NonInteractiveRefactorName { AddOrRemoveBracesToArrowFunction = "Add or remove braces in an arrow function", ConvertArrowFunctionOrFunctionExpression = "Convert arrow function or function expression", ConvertExport = "Convert export", @@ -932,28 +951,20 @@ export const enum RefactorName { MoveToNewFile = "Move to a new file", } -interface RefactorHandlerMap { - [RefactorName.AddOrRemoveBracesToArrowFunction]: () => void; - [RefactorName.ConvertArrowFunctionOrFunctionExpression]: () => void; - [RefactorName.ConvertExport]: () => void; - [RefactorName.ConvertImport]: () => void; - [RefactorName.ConvertOverloadListToSingleSignature]: () => void; - [RefactorName.ConvertParamsToDestructuredObject]: () => void; - [RefactorName.ConvertStringOrTemplateLiteral]: () => void; - [RefactorName.ConvertToOptionalChainExpression]: () => void; - [RefactorName.ExtractSymbol]: () => void; - [RefactorName.ExtractType]: () => void; - [RefactorName.GenerateGetAccessorAndSetAccessor]: () => void; - [RefactorName.InferFunctionReturnType]: () => void; - [RefactorName.MoveToNewFile]: () => void; +export const enum InteractiveRefactorName { + MoveToFile = "Move to file" } -type RefactorHandlerArgs = RefactorHandlerMap[T] extends (...args: infer A) => void ? A : never; +export type RefactorName = NonInteractiveRefactorName | InteractiveRefactorName; + +export type RefactorArgs = { + [InteractiveRefactorName.MoveToFile]: (args: { targetFile: string }) => void; +}[T] extends (...args: infer A) => void ? A : never; /** * A set of one or more available refactoring actions, grouped under a parent refactoring. */ -export interface ApplicableRefactorInfo { +export interface BaseApplicableRefactorInfo { /** * The programmatic name of the refactoring */ @@ -975,6 +986,16 @@ export interface ApplicableRefactorInfo { actions: RefactorActionInfo[]; } +export interface ApplicableNonInteractiveRefactorInfo extends BaseApplicableRefactorInfo { + name: NonInteractiveRefactorName; +} + +export interface ApplicableInteractiveRefactorInfo extends BaseApplicableRefactorInfo { + name: InteractiveRefactorName; +} + +export type ApplicableRefactorInfo = ApplicableNonInteractiveRefactorInfo | ApplicableInteractiveRefactorInfo; + /** * Represents a single refactoring action - for example, the "Extract Method..." refactor might * offer several actions, each corresponding to a surround class or closure to extract into. @@ -1791,18 +1812,37 @@ export interface CodeFixContext extends CodeFixContextBase { } /** @internal */ -export interface Refactor { +export interface RefactorBase { /** List of action kinds a refactor can provide. * Used to skip unnecessary calculation when specific refactors are requested. */ kinds?: string[]; + /** Compute (quickly) which actions are available here */ + getAvailableActions(context: RefactorContext): readonly ApplicableRefactorInfo[]; + + isInteractive?: boolean; +} + +/** @internal */ +export interface InteractiveRefactor extends RefactorBase { + /** Compute the associated code actions */ + getEditsForAction(context: RefactorContext, actionName: string, ...args: RefactorArgs): RefactorEditInfo | undefined; + + /** Indicates that `getEditsForAction` requires additional arguments */ + isInteractive: true; +} + +/** @internal */ +export interface NonInteractiveRefactor extends RefactorBase { /** Compute the associated code actions */ getEditsForAction(context: RefactorContext, actionName: string): RefactorEditInfo | undefined; - /** Compute (quickly) which actions are available here */ - getAvailableActions(context: RefactorContext): readonly ApplicableRefactorInfo[]; + isInteractive?: undefined; } +/** @internal */ +export type Refactor = InteractiveRefactor | NonInteractiveRefactor; + /** @internal */ export interface RefactorContext extends textChanges.TextChangesContext { file: SourceFile; diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 4bf70d659b1ff..1b5e38d004d54 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -501,23 +501,24 @@ declare namespace ts { type GetApplicableRefactorsRequestArgs = FileLocationOrRangeRequestArgs & { triggerReason?: RefactorTriggerReason; kind?: string; + includeInteractive?: boolean; }; type RefactorTriggerReason = "implicit" | "invoked"; /** * Response is a list of available refactorings. * Each refactoring exposes one or more "Actions"; a user selects one action to invoke a refactoring */ - interface GetApplicableRefactorsResponse extends Response { - body?: ApplicableRefactorInfo[]; + interface GetApplicableRefactorsResponse extends Response { + body?: TRefactorInfo[]; } /** * A set of one or more available refactoring actions, grouped under a parent refactoring. */ - interface ApplicableRefactorInfo { + interface BaseApplicableRefactorInfo { /** * The programmatic name of the refactoring */ - name: string; + name: RefactorName; /** * A description of this refactoring category to show to the user. * If the refactoring gets inlined (see below), this text will not be visible. @@ -533,6 +534,13 @@ declare namespace ts { inlineable?: boolean; actions: RefactorActionInfo[]; } + interface ApplicableNonInteractiveRefactorInfo extends BaseApplicableRefactorInfo { + name: NonInteractiveRefactorName; + } + interface ApplicableInteractiveRefactorInfo extends BaseApplicableRefactorInfo { + name: InteractiveRefactorName; + } + type ApplicableRefactorInfo = ApplicableNonInteractiveRefactorInfo | ApplicableInteractiveRefactorInfo; /** * Represents a single refactoring action - for example, the "Extract Method..." refactor might * offer several actions, each corresponding to a surround class or closure to extract into. @@ -10117,7 +10125,29 @@ declare namespace ts { applyCodeActionCommand(fileName: string, action: CodeActionCommand[]): Promise; /** @deprecated `fileName` will be ignored */ applyCodeActionCommand(fileName: string, action: CodeActionCommand | CodeActionCommand[]): Promise; - getApplicableRefactors(fileName: string, positionOrRange: number | TextRange, preferences: UserPreferences | undefined, triggerReason?: RefactorTriggerReason, kind?: string): ApplicableRefactorInfo[]; + getApplicableRefactors(fileName: string, positionOrRange: number | TextRange, preferences: UserPreferences | undefined, triggerReason?: RefactorTriggerReason, kind?: string, includeInteractive?: false): ApplicableNonInteractiveRefactorInfo[]; + getApplicableRefactors(fileName: string, positionOrRange: number | TextRange, preferences: UserPreferences | undefined, triggerReason?: RefactorTriggerReason, kind?: string, includeInteractive?: boolean): ApplicableRefactorInfo[]; + getEditsForRefactor(fileName: string, formatOptions: FormatCodeSettings, positionOrRange: number | TextRange, refactorName: Name, actionName: string, preferences: UserPreferences | undefined, ...args: RefactorArgs): RefactorEditInfo | undefined; + getEditsForRefactor(fileName: string, formatOptions: FormatCodeSettings, positionOrRange: number | TextRange, refactorName: NonInteractiveRefactorName, actionName: string, preferences: UserPreferences | undefined): RefactorEditInfo | undefined; + /** + * @deprecated + * As of TypeScript 5.1, some refactors may require an additional argument. + * If the `isInteractive` property of the `ApplicableRefactorInfo` object is `true`, + * getting edits for the refactor will require a specific argument linked to its name: + * + * ```ts + * if (refactor.isInteractive) { + * if (refactor.name === ts.InteractiveRefactorName.MoveToFile) { + * const action = getAction(refactor); + * const edits = ls.getEditsForRefactor(fileName, formatOptions, positionOrRange, refactor.name, action.name, preferences, { + * targetFile: await askUserForTargetFileName() + * }); + * } + * } else { + * const edits = ls.getEditsForRefactor(...) + * } + * ``` + */ getEditsForRefactor(fileName: string, formatOptions: FormatCodeSettings, positionOrRange: number | TextRange, refactorName: string, actionName: string, preferences: UserPreferences | undefined): RefactorEditInfo | undefined; organizeImports(args: OrganizeImportsArgs, formatOptions: FormatCodeSettings, preferences: UserPreferences | undefined): readonly FileTextChanges[]; getEditsForFileRename(oldFilePath: string, newFilePath: string, formatOptions: FormatCodeSettings, preferences: UserPreferences | undefined): readonly FileTextChanges[]; @@ -10342,14 +10372,38 @@ declare namespace ts { type CodeActionCommand = InstallPackageAction; interface InstallPackageAction { } + enum NonInteractiveRefactorName { + AddOrRemoveBracesToArrowFunction = "Add or remove braces in an arrow function", + ConvertArrowFunctionOrFunctionExpression = "Convert arrow function or function expression", + ConvertExport = "Convert export", + ConvertImport = "Convert import", + ConvertOverloadListToSingleSignature = "Convert overload list to single signature", + ConvertParamsToDestructuredObject = "Convert parameters to destructured object", + ConvertStringOrTemplateLiteral = "Convert to template string", + ConvertToOptionalChainExpression = "Convert to optional chain expression", + ExtractSymbol = "Extract Symbol", + ExtractType = "Extract type", + GenerateGetAccessorAndSetAccessor = "Generate 'get' and 'set' accessors", + InferFunctionReturnType = "Infer function return type", + MoveToNewFile = "Move to a new file" + } + enum InteractiveRefactorName { + MoveToFile = "Move to file" + } + type RefactorName = NonInteractiveRefactorName | InteractiveRefactorName; + type RefactorArgs = { + [InteractiveRefactorName.MoveToFile]: (args: { + targetFile: string; + }) => void; + }[T] extends (...args: infer A) => void ? A : never; /** * A set of one or more available refactoring actions, grouped under a parent refactoring. */ - interface ApplicableRefactorInfo { + interface BaseApplicableRefactorInfo { /** * The programmatic name of the refactoring */ - name: string; + name: RefactorName; /** * A description of this refactoring category to show to the user. * If the refactoring gets inlined (see below), this text will not be visible. @@ -10365,6 +10419,13 @@ declare namespace ts { inlineable?: boolean; actions: RefactorActionInfo[]; } + interface ApplicableNonInteractiveRefactorInfo extends BaseApplicableRefactorInfo { + name: NonInteractiveRefactorName; + } + interface ApplicableInteractiveRefactorInfo extends BaseApplicableRefactorInfo { + name: InteractiveRefactorName; + } + type ApplicableRefactorInfo = ApplicableNonInteractiveRefactorInfo | ApplicableInteractiveRefactorInfo; /** * Represents a single refactoring action - for example, the "Extract Method..." refactor might * offer several actions, each corresponding to a surround class or closure to extract into. diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 6acc0c5bc906e..65b744cffaeec 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -6186,7 +6186,29 @@ declare namespace ts { applyCodeActionCommand(fileName: string, action: CodeActionCommand[]): Promise; /** @deprecated `fileName` will be ignored */ applyCodeActionCommand(fileName: string, action: CodeActionCommand | CodeActionCommand[]): Promise; - getApplicableRefactors(fileName: string, positionOrRange: number | TextRange, preferences: UserPreferences | undefined, triggerReason?: RefactorTriggerReason, kind?: string): ApplicableRefactorInfo[]; + getApplicableRefactors(fileName: string, positionOrRange: number | TextRange, preferences: UserPreferences | undefined, triggerReason?: RefactorTriggerReason, kind?: string, includeInteractive?: false): ApplicableNonInteractiveRefactorInfo[]; + getApplicableRefactors(fileName: string, positionOrRange: number | TextRange, preferences: UserPreferences | undefined, triggerReason?: RefactorTriggerReason, kind?: string, includeInteractive?: boolean): ApplicableRefactorInfo[]; + getEditsForRefactor(fileName: string, formatOptions: FormatCodeSettings, positionOrRange: number | TextRange, refactorName: Name, actionName: string, preferences: UserPreferences | undefined, ...args: RefactorArgs): RefactorEditInfo | undefined; + getEditsForRefactor(fileName: string, formatOptions: FormatCodeSettings, positionOrRange: number | TextRange, refactorName: NonInteractiveRefactorName, actionName: string, preferences: UserPreferences | undefined): RefactorEditInfo | undefined; + /** + * @deprecated + * As of TypeScript 5.1, some refactors may require an additional argument. + * If the `isInteractive` property of the `ApplicableRefactorInfo` object is `true`, + * getting edits for the refactor will require a specific argument linked to its name: + * + * ```ts + * if (refactor.isInteractive) { + * if (refactor.name === ts.InteractiveRefactorName.MoveToFile) { + * const action = getAction(refactor); + * const edits = ls.getEditsForRefactor(fileName, formatOptions, positionOrRange, refactor.name, action.name, preferences, { + * targetFile: await askUserForTargetFileName() + * }); + * } + * } else { + * const edits = ls.getEditsForRefactor(...) + * } + * ``` + */ getEditsForRefactor(fileName: string, formatOptions: FormatCodeSettings, positionOrRange: number | TextRange, refactorName: string, actionName: string, preferences: UserPreferences | undefined): RefactorEditInfo | undefined; organizeImports(args: OrganizeImportsArgs, formatOptions: FormatCodeSettings, preferences: UserPreferences | undefined): readonly FileTextChanges[]; getEditsForFileRename(oldFilePath: string, newFilePath: string, formatOptions: FormatCodeSettings, preferences: UserPreferences | undefined): readonly FileTextChanges[]; @@ -6411,14 +6433,38 @@ declare namespace ts { type CodeActionCommand = InstallPackageAction; interface InstallPackageAction { } + enum NonInteractiveRefactorName { + AddOrRemoveBracesToArrowFunction = "Add or remove braces in an arrow function", + ConvertArrowFunctionOrFunctionExpression = "Convert arrow function or function expression", + ConvertExport = "Convert export", + ConvertImport = "Convert import", + ConvertOverloadListToSingleSignature = "Convert overload list to single signature", + ConvertParamsToDestructuredObject = "Convert parameters to destructured object", + ConvertStringOrTemplateLiteral = "Convert to template string", + ConvertToOptionalChainExpression = "Convert to optional chain expression", + ExtractSymbol = "Extract Symbol", + ExtractType = "Extract type", + GenerateGetAccessorAndSetAccessor = "Generate 'get' and 'set' accessors", + InferFunctionReturnType = "Infer function return type", + MoveToNewFile = "Move to a new file" + } + enum InteractiveRefactorName { + MoveToFile = "Move to file" + } + type RefactorName = NonInteractiveRefactorName | InteractiveRefactorName; + type RefactorArgs = { + [InteractiveRefactorName.MoveToFile]: (args: { + targetFile: string; + }) => void; + }[T] extends (...args: infer A) => void ? A : never; /** * A set of one or more available refactoring actions, grouped under a parent refactoring. */ - interface ApplicableRefactorInfo { + interface BaseApplicableRefactorInfo { /** * The programmatic name of the refactoring */ - name: string; + name: RefactorName; /** * A description of this refactoring category to show to the user. * If the refactoring gets inlined (see below), this text will not be visible. @@ -6434,6 +6480,13 @@ declare namespace ts { inlineable?: boolean; actions: RefactorActionInfo[]; } + interface ApplicableNonInteractiveRefactorInfo extends BaseApplicableRefactorInfo { + name: NonInteractiveRefactorName; + } + interface ApplicableInteractiveRefactorInfo extends BaseApplicableRefactorInfo { + name: InteractiveRefactorName; + } + type ApplicableRefactorInfo = ApplicableNonInteractiveRefactorInfo | ApplicableInteractiveRefactorInfo; /** * Represents a single refactoring action - for example, the "Extract Method..." refactor might * offer several actions, each corresponding to a surround class or closure to extract into.