-
Notifications
You must be signed in to change notification settings - Fork 12.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
add support to convert lambda to function and vice-versa #28250
Changes from 25 commits
8cb019d
6cfbee7
f558f2f
4c80de9
367f47e
e697856
cc07d68
05c44f1
d771865
62b9fcc
c25726e
7c78cd5
152e362
6922f6c
39c3928
a9cb623
6bd26cd
3e7dcad
649b53c
5951318
d97e073
0fa2fad
1c74d0e
8bc7f43
6b80047
739e1e9
dc81a7f
5dfc71c
9a466f4
dfb86ac
e1dc52f
52e94d2
dbd5859
5ec1201
e6dc8f6
fcb8e87
52dad73
fb3a84c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,192 @@ | ||
/* @internal */ | ||
namespace ts.refactor.convertArrowFunctionOrFunctionExpression { | ||
const refactorName = "Convert arrow function or function expression"; | ||
const refactorDescription = getLocaleSpecificMessage(Diagnostics.Convert_arrow_function_or_function_expression); | ||
|
||
const toAnonymousFunctionActionName = "Convert to anonymous function"; | ||
const toNamedFunctionActionName = "Convert to named function"; | ||
const toArrowFunctionActionName = "Convert to arrow function"; | ||
|
||
const toAnonymousFunctionActionDescription = getLocaleSpecificMessage(Diagnostics.Convert_to_anonymous_function); | ||
const toNamedFunctionActionDescription = getLocaleSpecificMessage(Diagnostics.Convert_to_named_function); | ||
const toArrowFunctionActionDescription = getLocaleSpecificMessage(Diagnostics.Convert_to_arrow_function); | ||
|
||
registerRefactor(refactorName, { getEditsForAction, getAvailableActions }); | ||
|
||
interface FunctionInfo { | ||
readonly selectedVariableDeclaration: boolean; | ||
readonly func: FunctionExpression | ArrowFunction; | ||
} | ||
|
||
interface VariableInfo { | ||
readonly variableDeclaration: VariableDeclaration; | ||
readonly variableDeclarationList: VariableDeclarationList; | ||
readonly statement: VariableStatement; | ||
readonly name: Identifier; | ||
} | ||
|
||
function getAvailableActions(context: RefactorContext): ApplicableRefactorInfo[] | undefined { | ||
const { file, startPosition } = context; | ||
const info = getFunctionInfo(file, startPosition); | ||
|
||
if (!info) return undefined; | ||
const { selectedVariableDeclaration, func } = info; | ||
const possibleActions: RefactorActionInfo[] = []; | ||
|
||
if (selectedVariableDeclaration || (isArrowFunction(func) && isVariableDeclaration(func.parent))) { | ||
possibleActions.push({ | ||
name: toNamedFunctionActionName, | ||
description: toNamedFunctionActionDescription | ||
}); | ||
} | ||
|
||
if (!selectedVariableDeclaration && isArrowFunction(func)) { | ||
possibleActions.push({ | ||
name: toAnonymousFunctionActionName, | ||
description: toAnonymousFunctionActionDescription | ||
}); | ||
} | ||
|
||
if (isFunctionExpression(func)) { | ||
possibleActions.push({ | ||
name: toArrowFunctionActionName, | ||
description: toArrowFunctionActionDescription | ||
}); | ||
} | ||
|
||
return [{ | ||
name: refactorName, | ||
description: refactorDescription, | ||
actions: possibleActions | ||
}]; | ||
} | ||
|
||
function getEditsForAction(context: RefactorContext, actionName: string): RefactorEditInfo | undefined { | ||
const { file, startPosition } = context; | ||
const info = getFunctionInfo(file, startPosition); | ||
|
||
if (!info) return undefined; | ||
const { func } = info; | ||
|
||
switch (actionName) { | ||
case toAnonymousFunctionActionName: | ||
return getEditInfoForConvertToAnonymousFunction(context, func); | ||
|
||
case toNamedFunctionActionName: | ||
return getEditInfoForConvertToNamedFunction(context, func); | ||
|
||
case toArrowFunctionActionName: | ||
return getEditInfoForConvertToArrowFunction(context, func); | ||
|
||
default: | ||
Debug.fail("invalid action"); | ||
break; | ||
} | ||
|
||
return undefined; | ||
} | ||
|
||
function getFunctionInfo(file: SourceFile, startPosition: number): FunctionInfo | undefined { | ||
const token = getTokenAtPosition(file, startPosition); | ||
let maybeFunc; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This could just be two different variables, one for each time it's assigned to. |
||
|
||
maybeFunc = getArrowFunctionFromVariableDeclaration(token.parent); | ||
if (!!maybeFunc) return { selectedVariableDeclaration: true, func: maybeFunc }; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No need to have |
||
|
||
maybeFunc = getContainingFunction(token); | ||
if (!!maybeFunc && (isFunctionExpression(maybeFunc) || isArrowFunction(maybeFunc)) && !rangeContainsRange(maybeFunc.body, token)) { | ||
return { selectedVariableDeclaration: false, func: maybeFunc }; | ||
} | ||
|
||
return undefined; | ||
} | ||
|
||
function isSingleVariableDeclaration(parent: Node): parent is VariableDeclarationList { | ||
return isVariableDeclaration(parent) || (isVariableDeclarationList(parent) && parent.declarations.length === 1); | ||
} | ||
|
||
function getArrowFunctionFromVariableDeclaration(parent: Node): ArrowFunction | undefined { | ||
if (!isSingleVariableDeclaration(parent)) return undefined; | ||
const variableDeclaration = isVariableDeclaration(parent) ? parent : parent.declarations[0]; | ||
|
||
const initializer = variableDeclaration.initializer; | ||
if (!initializer || !isArrowFunction(initializer)) return undefined; | ||
return initializer; | ||
} | ||
|
||
function convertToBlock(body: ConciseBody): Block { | ||
if (isExpression(body)) { | ||
return createBlock([createReturn(body)], /* multiLine */ true); | ||
} | ||
else { | ||
return body; | ||
} | ||
} | ||
|
||
function getVariableInfo(func: FunctionExpression | ArrowFunction): VariableInfo | undefined { | ||
const variableDeclaration = func.parent; | ||
if (!isVariableDeclaration(variableDeclaration) || !isVariableDeclarationInVariableStatement(variableDeclaration)) return undefined; | ||
|
||
const variableDeclarationList = variableDeclaration.parent; | ||
const statement = variableDeclarationList.parent; | ||
if (!isVariableDeclarationList(variableDeclarationList) || !isVariableStatement(statement) || !isIdentifier(variableDeclaration.name)) return undefined; | ||
|
||
return { variableDeclaration, variableDeclarationList, statement, name: variableDeclaration.name }; | ||
} | ||
|
||
function getEditInfoForConvertToAnonymousFunction(context: RefactorContext, func: FunctionExpression | ArrowFunction): RefactorEditInfo { | ||
const { file } = context; | ||
const body = convertToBlock(func.body); | ||
const newNode = createFunctionExpression(func.modifiers, func.asteriskToken, /* name */ undefined, func.typeParameters, func.parameters, func.type, body); | ||
const edits = textChanges.ChangeTracker.with(context, t => t.replaceNode(file, func, newNode)); | ||
return { renameFilename: undefined, renameLocation: undefined, edits }; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It may be better for these functions to just return the edits, and some outer function to wrap them in |
||
} | ||
|
||
function getEditInfoForConvertToNamedFunction(context: RefactorContext, func: FunctionExpression | ArrowFunction): RefactorEditInfo | undefined { | ||
const { file } = context; | ||
const body = convertToBlock(func.body); | ||
const variableInfo = getVariableInfo(func); | ||
if (!variableInfo) return undefined; | ||
|
||
const { variableDeclaration, variableDeclarationList, statement, name } = variableInfo; | ||
const newNode = createFunctionDeclaration(func.decorators, statement.modifiers, func.asteriskToken, name, func.typeParameters, func.parameters, func.type, body); | ||
let edits: FileTextChanges[]; | ||
|
||
if (variableDeclarationList.declarations.length === 1) { | ||
edits = textChanges.ChangeTracker.with(context, t => t.replaceNode(file, statement, newNode)); | ||
} | ||
else { | ||
edits = textChanges.ChangeTracker.with(context, t => { | ||
t.delete(file, variableDeclaration); | ||
t.insertNodeAfter(file, statement, newNode); | ||
}); | ||
} | ||
return { renameFilename: undefined, renameLocation: undefined, edits }; | ||
} | ||
|
||
function getEditInfoForConvertToArrowFunction(context: RefactorContext, func: FunctionExpression | ArrowFunction): RefactorEditInfo | undefined { | ||
const { file } = context; | ||
if (!isFunctionExpression(func)) return undefined; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The parameter type should just be |
||
|
||
const statements = func.body.statements; | ||
const head = statements[0]; | ||
let body: ConciseBody; | ||
|
||
if (canBeConvertedToExpression(func.body, head)) { | ||
body = head.expression!; | ||
suppressLeadingAndTrailingTrivia(body); | ||
copyComments(head, body, file, SyntaxKind.MultiLineCommentTrivia, /* hasTrailingNewLine */ false); | ||
} | ||
else { | ||
body = func.body; | ||
} | ||
|
||
const newNode = createArrowFunction(func.modifiers, func.typeParameters, func.parameters, func.type, /* equalsGreaterThanToken */ undefined, body); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Suprised this works without the token -- seems like it would be better to |
||
const edits = textChanges.ChangeTracker.with(context, t => t.replaceNode(file, func, newNode)); | ||
return { renameFilename: undefined, renameLocation: undefined, edits }; | ||
} | ||
|
||
function canBeConvertedToExpression(body: Block, head: Statement): head is ReturnStatement | ExpressionStatement { | ||
return body.statements.length === 1 && ((isReturnStatement(head) && !!head.expression) || isExpressionStatement(head)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure about the last condition there. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. you're right.
|
||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
/// <reference path='fourslash.ts' /> | ||
|
||
//// /*z*/c/*y*/onst /*x*/f/*w*/oo = /*v*/f/*u*/unction() /*t*/{/*s*/ /*r*/r/*q*/eturn 42;}; | ||
|
||
goTo.select("z", "y"); | ||
verify.not.refactorAvailable("Convert arrow function or function expression", "Convert to named function"); | ||
verify.not.refactorAvailable("Convert arrow function or function expression", "Convert to anonymous function"); | ||
verify.not.refactorAvailable("Convert arrow function or function expression", "Convert to arrow function"); | ||
|
||
goTo.select("x", "w"); | ||
verify.not.refactorAvailable("Convert arrow function or function expression", "Convert to named function"); | ||
verify.not.refactorAvailable("Convert arrow function or function expression", "Convert to anonymous function"); | ||
verify.not.refactorAvailable("Convert arrow function or function expression", "Convert to arrow function"); | ||
|
||
goTo.select("v", "u"); | ||
verify.not.refactorAvailable("Convert arrow function or function expression", "Convert to named function"); | ||
verify.not.refactorAvailable("Convert arrow function or function expression", "Convert to anonymous function"); | ||
verify.refactorAvailable("Convert arrow function or function expression", "Convert to arrow function"); | ||
|
||
goTo.select("t", "s"); | ||
verify.not.refactorAvailable("Convert arrow function or function expression", "Convert to named function"); | ||
verify.not.refactorAvailable("Convert arrow function or function expression", "Convert to anonymous function"); | ||
verify.not.refactorAvailable("Convert arrow function or function expression", "Convert to arrow function"); | ||
|
||
goTo.select("r", "q"); | ||
verify.not.refactorAvailable("Convert arrow function or function expression", "Convert to named function"); | ||
verify.not.refactorAvailable("Convert arrow function or function expression", "Convert to anonymous function"); | ||
verify.not.refactorAvailable("Convert arrow function or function expression", "Convert to arrow function"); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
/// <reference path='fourslash.ts' /> | ||
|
||
//// function foo(a){} | ||
//// /*z*/f/*y*/oo/*x*/(/*w*//*v*/f/*u*/unction/*t*/(/*s*//*r*/b/*q*/,c){/*p*/r/*o*/eturn 42;}) | ||
|
||
goTo.select("z", "y"); | ||
verify.not.refactorAvailable("Convert arrow function or function expression", "Convert to named function"); | ||
verify.not.refactorAvailable("Convert arrow function or function expression", "Convert to anonymous function"); | ||
verify.not.refactorAvailable("Convert arrow function or function expression", "Convert to arrow function"); | ||
|
||
goTo.select("x", "w"); | ||
verify.not.refactorAvailable("Convert arrow function or function expression", "Convert to named function"); | ||
verify.not.refactorAvailable("Convert arrow function or function expression", "Convert to anonymous function"); | ||
verify.not.refactorAvailable("Convert arrow function or function expression", "Convert to arrow function"); | ||
|
||
goTo.select("v", "u"); | ||
verify.not.refactorAvailable("Convert arrow function or function expression", "Convert to named function"); | ||
verify.not.refactorAvailable("Convert arrow function or function expression", "Convert to anonymous function"); | ||
verify.refactorAvailable("Convert arrow function or function expression", "Convert to arrow function"); | ||
|
||
goTo.select("t", "s"); | ||
verify.not.refactorAvailable("Convert arrow function or function expression", "Convert to named function"); | ||
verify.not.refactorAvailable("Convert arrow function or function expression", "Convert to anonymous function"); | ||
verify.refactorAvailable("Convert arrow function or function expression", "Convert to arrow function"); | ||
|
||
goTo.select("r", "q"); | ||
verify.not.refactorAvailable("Convert arrow function or function expression", "Convert to named function"); | ||
verify.not.refactorAvailable("Convert arrow function or function expression", "Convert to anonymous function"); | ||
verify.refactorAvailable("Convert arrow function or function expression", "Convert to arrow function"); | ||
|
||
goTo.select("p", "o"); | ||
verify.not.refactorAvailable("Convert arrow function or function expression", "Convert to named function"); | ||
verify.not.refactorAvailable("Convert arrow function or function expression", "Convert to anonymous function"); | ||
verify.not.refactorAvailable("Convert arrow function or function expression", "Convert to arrow function"); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
/// <reference path='fourslash.ts' /> | ||
|
||
//// /*z*/c/*y*/onst /*x*/f/*w*/oo = /*v*/(/*u*//*t*/a/*s*/, b) /*r*/=/*q*/> /*p*/4/*o*/2; | ||
|
||
goTo.select("z", "y"); | ||
verify.refactorAvailable("Convert arrow function or function expression", "Convert to named function"); | ||
verify.not.refactorAvailable("Convert arrow function or function expression", "Convert to anonymous function"); | ||
verify.not.refactorAvailable("Convert arrow function or function expression", "Convert to arrow function"); | ||
|
||
goTo.select("x", "w"); | ||
verify.refactorAvailable("Convert arrow function or function expression", "Convert to named function"); | ||
verify.not.refactorAvailable("Convert arrow function or function expression", "Convert to anonymous function"); | ||
verify.not.refactorAvailable("Convert arrow function or function expression", "Convert to arrow function"); | ||
|
||
goTo.select("v", "u"); | ||
verify.refactorAvailable("Convert arrow function or function expression", "Convert to named function"); | ||
verify.refactorAvailable("Convert arrow function or function expression", "Convert to anonymous function"); | ||
verify.not.refactorAvailable("Convert arrow function or function expression", "Convert to arrow function"); | ||
|
||
goTo.select("t", "s"); | ||
verify.refactorAvailable("Convert arrow function or function expression", "Convert to named function"); | ||
verify.refactorAvailable("Convert arrow function or function expression", "Convert to anonymous function"); | ||
verify.not.refactorAvailable("Convert arrow function or function expression", "Convert to arrow function"); | ||
|
||
goTo.select("r", "q"); | ||
verify.refactorAvailable("Convert arrow function or function expression", "Convert to named function"); | ||
verify.refactorAvailable("Convert arrow function or function expression", "Convert to anonymous function"); | ||
verify.not.refactorAvailable("Convert arrow function or function expression", "Convert to arrow function"); | ||
|
||
goTo.select("p", "o"); | ||
verify.not.refactorAvailable("Convert arrow function or function expression", "Convert to named function"); | ||
verify.not.refactorAvailable("Convert arrow function or function expression", "Convert to anonymous function"); | ||
verify.not.refactorAvailable("Convert arrow function or function expression", "Convert to arrow function"); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
/// <reference path='fourslash.ts' /> | ||
|
||
//// function foo(a){} | ||
//// /*z*/f/*y*/oo/*x*/(/*w*//*v*/(/*u*//*t*/a/*s*/, b) => /*r*/a/*q*/ + b) | ||
|
||
|
||
goTo.select("z", "y"); | ||
verify.not.refactorAvailable("Convert arrow function or function expression", "Convert to named function"); | ||
verify.not.refactorAvailable("Convert arrow function or function expression", "Convert to anonymous function"); | ||
verify.not.refactorAvailable("Convert arrow function or function expression", "Convert to arrow function"); | ||
|
||
goTo.select("x", "w"); | ||
verify.not.refactorAvailable("Convert arrow function or function expression", "Convert to named function"); | ||
verify.not.refactorAvailable("Convert arrow function or function expression", "Convert to anonymous function"); | ||
verify.not.refactorAvailable("Convert arrow function or function expression", "Convert to arrow function"); | ||
|
||
goTo.select("v", "u"); | ||
verify.not.refactorAvailable("Convert arrow function or function expression", "Convert to named function"); | ||
verify.refactorAvailable("Convert arrow function or function expression", "Convert to anonymous function"); | ||
verify.not.refactorAvailable("Convert arrow function or function expression", "Convert to arrow function"); | ||
|
||
goTo.select("t", "s"); | ||
verify.not.refactorAvailable("Convert arrow function or function expression", "Convert to named function"); | ||
verify.refactorAvailable("Convert arrow function or function expression", "Convert to anonymous function"); | ||
verify.not.refactorAvailable("Convert arrow function or function expression", "Convert to arrow function"); | ||
|
||
goTo.select("r", "q"); | ||
verify.not.refactorAvailable("Convert arrow function or function expression", "Convert to named function"); | ||
verify.not.refactorAvailable("Convert arrow function or function expression", "Convert to anonymous function"); | ||
verify.not.refactorAvailable("Convert arrow function or function expression", "Convert to arrow function"); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
/// <reference path='fourslash.ts' /> | ||
|
||
//// /*z*/l/*y*/et /*x*/f/*w*/oo, /*v*/b/*u*/ar = /*t*/(/*s*/) => 42; | ||
|
||
goTo.select("z", "y"); | ||
verify.not.refactorAvailable("Convert arrow function or function expression", "Convert to named function"); | ||
verify.not.refactorAvailable("Convert arrow function or function expression", "Convert to anonymous function"); | ||
verify.not.refactorAvailable("Convert arrow function or function expression", "Convert to arrow function"); | ||
|
||
goTo.select("x", "w"); | ||
verify.not.refactorAvailable("Convert arrow function or function expression", "Convert to named function"); | ||
verify.not.refactorAvailable("Convert arrow function or function expression", "Convert to anonymous function"); | ||
verify.not.refactorAvailable("Convert arrow function or function expression", "Convert to arrow function"); | ||
|
||
goTo.select("v", "u"); | ||
verify.refactorAvailable("Convert arrow function or function expression", "Convert to named function"); | ||
verify.not.refactorAvailable("Convert arrow function or function expression", "Convert to anonymous function"); | ||
verify.not.refactorAvailable("Convert arrow function or function expression", "Convert to arrow function"); | ||
|
||
goTo.select("t", "s"); | ||
verify.refactorAvailable("Convert arrow function or function expression", "Convert to named function"); | ||
verify.refactorAvailable("Convert arrow function or function expression", "Convert to anonymous function"); | ||
verify.not.refactorAvailable("Convert arrow function or function expression", "Convert to arrow function"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can just
return Debug.fail("Invalid action");
since it returnsnever
.