diff --git a/src/server/protocol.ts b/src/server/protocol.ts index a975b0c9b2d..58a5988a4bf 100644 --- a/src/server/protocol.ts +++ b/src/server/protocol.ts @@ -550,10 +550,8 @@ namespace ts.server.protocol { triggerReason?: RefactorTriggerReason; }; - export type RefactorTriggerReason = RefactorInvokedReason; - - export interface RefactorInvokedReason { - kind: "invoked"; + export enum RefactorTriggerReason { + Invoked = "invoked" } /** diff --git a/src/services/refactors/addOrRemoveBracesToArrowFunction.ts b/src/services/refactors/addOrRemoveBracesToArrowFunction.ts index de048b7dbd3..3f499a06e7f 100644 --- a/src/services/refactors/addOrRemoveBracesToArrowFunction.ts +++ b/src/services/refactors/addOrRemoveBracesToArrowFunction.ts @@ -17,7 +17,8 @@ namespace ts.refactor.addOrRemoveBracesToArrowFunction { function getAvailableActions(context: RefactorContext): readonly ApplicableRefactorInfo[] { const { file, startPosition, triggerReason } = context; - const info = getConvertibleArrowFunctionAtPosition(file, startPosition, triggerReason); + const forImplicitRequest = triggerReason ? triggerReason === RefactorTriggerReason.Implicit : true; + const info = getConvertibleArrowFunctionAtPosition(file, startPosition, forImplicitRequest); if (!info) return emptyArray; return [{ @@ -38,7 +39,7 @@ namespace ts.refactor.addOrRemoveBracesToArrowFunction { function getEditsForAction(context: RefactorContext, actionName: string): RefactorEditInfo | undefined { const { file, startPosition } = context; - const info = getConvertibleArrowFunctionAtPosition(file, startPosition, /*triggerReason*/ { kind: "invoked" }); + const info = getConvertibleArrowFunctionAtPosition(file, startPosition); if (!info) return undefined; const { expression, returnStatement, func } = info; @@ -70,12 +71,12 @@ namespace ts.refactor.addOrRemoveBracesToArrowFunction { return { renameFilename: undefined, renameLocation: undefined, edits }; } - function getConvertibleArrowFunctionAtPosition(file: SourceFile, startPosition: number, triggerReason?: RefactorTriggerReason): Info | undefined { + function getConvertibleArrowFunctionAtPosition(file: SourceFile, startPosition: number, forImplicitRequest = false): Info | undefined { const node = getTokenAtPosition(file, startPosition); const func = getContainingFunction(node); // Only offer a refactor in the function body on explicit refactor requests. if (!func || !isArrowFunction(func) || (!rangeContainsRange(func, node) - || (rangeContainsRange(func.body, node) && triggerReason?.kind !== "invoked"))) return undefined; + || (rangeContainsRange(func.body, node) && forImplicitRequest))) return undefined; if (isExpression(func.body)) { return { diff --git a/src/services/refactors/extractSymbol.ts b/src/services/refactors/extractSymbol.ts index 8b36c3afb62..b7c535fc44f 100644 --- a/src/services/refactors/extractSymbol.ts +++ b/src/services/refactors/extractSymbol.ts @@ -8,7 +8,8 @@ namespace ts.refactor.extractSymbol { * Exported for tests. */ export function getAvailableActions(context: RefactorContext): readonly ApplicableRefactorInfo[] { - const rangeToExtract = getRangeToExtract(context.file, getRefactorContextSpan(context), context.triggerReason); + const forImplicitRequest = context.triggerReason ? context.triggerReason === RefactorTriggerReason.Implicit : true; + const rangeToExtract = getRangeToExtract(context.file, getRefactorContextSpan(context), forImplicitRequest); const targetRange = rangeToExtract.targetRange; if (targetRange === undefined) { @@ -87,7 +88,7 @@ namespace ts.refactor.extractSymbol { /* Exported for tests */ export function getEditsForAction(context: RefactorContext, actionName: string): RefactorEditInfo | undefined { - const rangeToExtract = getRangeToExtract(context.file, getRefactorContextSpan(context), /* triggerReason*/ { kind: "invoked" }); + const rangeToExtract = getRangeToExtract(context.file, getRefactorContextSpan(context)); const targetRange = rangeToExtract.targetRange!; // TODO:GH#18217 const parsedFunctionIndexMatch = /^function_scope_(\d+)$/.exec(actionName); @@ -186,12 +187,12 @@ namespace ts.refactor.extractSymbol { * not shown to the user, but can be used by us diagnostically) */ // exported only for tests - export function getRangeToExtract(sourceFile: SourceFile, span: TextSpan, triggerReason?: RefactorTriggerReason): RangeToExtract { + export function getRangeToExtract(sourceFile: SourceFile, span: TextSpan, forImplicitRequest = false): RangeToExtract { const { length } = span; - if (length === 0 && triggerReason?.kind !== "invoked") { + if (length === 0 && forImplicitRequest) { return { errors: [createFileDiagnostic(sourceFile, span.start, length, Messages.cannotExtractEmpty)] }; } - const explicitCursorRequest = length === 0 && triggerReason?.kind === "invoked"; + const explicitCursorRequest = length === 0 && !forImplicitRequest; // Walk up starting from the the start position until we find a non-SourceFile node that subsumes the selected span. // This may fail (e.g. you select two statements in the root of a source file) diff --git a/src/services/refactors/extractType.ts b/src/services/refactors/extractType.ts index 1a22a8065c7..f442c5e0c0d 100644 --- a/src/services/refactors/extractType.ts +++ b/src/services/refactors/extractType.ts @@ -6,7 +6,8 @@ namespace ts.refactor { const extractToTypeDef = "Extract to typedef"; registerRefactor(refactorName, { getAvailableActions(context): readonly ApplicableRefactorInfo[] { - const info = getRangeToExtract(context, context.triggerReason); + const forImplicitRequest = context.triggerReason ? context.triggerReason === RefactorTriggerReason.Implicit : true; + const info = getRangeToExtract(context, forImplicitRequest); if (!info) return emptyArray; return [{ @@ -23,7 +24,7 @@ namespace ts.refactor { }, getEditsForAction(context, actionName): RefactorEditInfo { const { file, } = context; - const info = Debug.checkDefined(getRangeToExtract(context, /*triggerReason*/ { kind: "invoked" }), "Expected to find a range to extract"); + const info = Debug.checkDefined(getRangeToExtract(context), "Expected to find a range to extract"); const name = getUniqueName("NewType", file); const edits = textChanges.ChangeTracker.with(context, changes => { @@ -58,12 +59,12 @@ namespace ts.refactor { type Info = TypeAliasInfo | InterfaceInfo; - function getRangeToExtract(context: RefactorContext, triggerReason?: RefactorTriggerReason): Info | undefined { + function getRangeToExtract(context: RefactorContext, forImplicitRequest = false): Info | undefined { const { file, startPosition } = context; const isJS = isSourceFileJS(file); const current = getTokenAtPosition(file, startPosition); const range = createTextRangeFromSpan(getRefactorContextSpan(context)); - const explicitCursorRequest = range.pos === range.end && triggerReason?.kind === "invoked"; + const explicitCursorRequest = range.pos === range.end && !forImplicitRequest; const selection = findAncestor(current, (node => node.parent && isTypeNode(node) && !rangeContainsSkipTrivia(range, node.parent, file) && (explicitCursorRequest || nodeOverlapsWithStartEnd(current, file, range.pos, range.end)))); diff --git a/src/services/types.ts b/src/services/types.ts index d72060bf516..e4878471e68 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -741,10 +741,9 @@ namespace ts { commands?: CodeActionCommand[]; } - export type RefactorTriggerReason = RefactorInvokedReason; - - export interface RefactorInvokedReason { - kind: "invoked"; + export enum RefactorTriggerReason { + Implicit = "implicit", + Invoked = "invoked", } export interface TextInsertion { diff --git a/src/testRunner/unittests/services/extract/ranges.ts b/src/testRunner/unittests/services/extract/ranges.ts index ab205288b33..e10ed6cbb91 100644 --- a/src/testRunner/unittests/services/extract/ranges.ts +++ b/src/testRunner/unittests/services/extract/ranges.ts @@ -7,7 +7,7 @@ namespace ts { if (!selectionRange) { throw new Error(`Test ${s} does not specify selection range`); } - const result = refactor.extractSymbol.getRangeToExtract(file, createTextSpanFromRange(selectionRange)); + const result = refactor.extractSymbol.getRangeToExtract(file, createTextSpanFromRange(selectionRange), /*forImplicitRequest*/ true); assert(result.targetRange === undefined, "failure expected"); const sortedErrors = result.errors!.map(e => e.messageText).sort(); assert.deepEqual(sortedErrors, expectedErrors.sort(), "unexpected errors");