From f1127e6e9471bc289fc233a6be240f9dba1cb69c Mon Sep 17 00:00:00 2001 From: Jesse Trinity Date: Wed, 6 May 2020 14:59:21 -0700 Subject: [PATCH] extract symbol --- src/services/refactors/extractSymbol.ts | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/services/refactors/extractSymbol.ts b/src/services/refactors/extractSymbol.ts index 8c6c8f6fc69..8b36c3afb62 100644 --- a/src/services/refactors/extractSymbol.ts +++ b/src/services/refactors/extractSymbol.ts @@ -8,7 +8,7 @@ namespace ts.refactor.extractSymbol { * Exported for tests. */ export function getAvailableActions(context: RefactorContext): readonly ApplicableRefactorInfo[] { - const rangeToExtract = getRangeToExtract(context.file, getRefactorContextSpan(context)); + const rangeToExtract = getRangeToExtract(context.file, getRefactorContextSpan(context), context.triggerReason); const targetRange = rangeToExtract.targetRange; if (targetRange === undefined) { @@ -87,7 +87,7 @@ namespace ts.refactor.extractSymbol { /* Exported for tests */ export function getEditsForAction(context: RefactorContext, actionName: string): RefactorEditInfo | undefined { - const rangeToExtract = getRangeToExtract(context.file, getRefactorContextSpan(context)); + const rangeToExtract = getRangeToExtract(context.file, getRefactorContextSpan(context), /* triggerReason*/ { kind: "invoked" }); const targetRange = rangeToExtract.targetRange!; // TODO:GH#18217 const parsedFunctionIndexMatch = /^function_scope_(\d+)$/.exec(actionName); @@ -186,18 +186,20 @@ 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): RangeToExtract { + export function getRangeToExtract(sourceFile: SourceFile, span: TextSpan, triggerReason?: RefactorTriggerReason): RangeToExtract { const { length } = span; - - if (length === 0) { + if (length === 0 && triggerReason?.kind !== "invoked") { return { errors: [createFileDiagnostic(sourceFile, span.start, length, Messages.cannotExtractEmpty)] }; } + const explicitCursorRequest = length === 0 && triggerReason?.kind === "invoked"; // 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) - const start = getParentNodeInSpan(getTokenAtPosition(sourceFile, span.start), sourceFile, span); + const startToken = getTokenAtPosition(sourceFile, span.start); + const start = explicitCursorRequest ? getExtractableParent(startToken): getParentNodeInSpan(startToken, sourceFile, span); // Do the same for the ending position - const end = getParentNodeInSpan(findTokenOnLeftOfPosition(sourceFile, textSpanEnd(span)), sourceFile, span); + const endToken = findTokenOnLeftOfPosition(sourceFile, textSpanEnd(span)); + const end = explicitCursorRequest ? start : getParentNodeInSpan(endToken, sourceFile, span); const declarations: Symbol[] = []; @@ -1832,6 +1834,10 @@ namespace ts.refactor.extractSymbol { } } + function getExtractableParent(node: Node | undefined): Node | undefined { + return findAncestor(node, node => node.parent && isExtractableExpression(node) && !isBinaryExpression(node.parent)); + } + /** * Computes whether or not a node represents an expression in a position where it could * be extracted.