-
Notifications
You must be signed in to change notification settings - Fork 13.3k
convertToAsyncFunction: Fix two-argument then #37588
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
Changes from all commits
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 |
|---|---|---|
|
|
@@ -282,7 +282,7 @@ namespace ts.codefix { | |
| // We are about to write a 'let' variable declaration, but `transformExpression` for both | ||
| // the try block and catch block will assign to this name. Setting this flag indicates | ||
| // that future assignments should be written as `name = value` instead of `const name = value`. | ||
| possibleNameForVarDecl.hasBeenDeclared = true; | ||
| setHasBeenDeclared(possibleNameForVarDecl); | ||
| } | ||
|
|
||
| const tryBlock = createBlock(transformExpression(node.expression, transformer, possibleNameForVarDecl)); | ||
|
|
@@ -319,22 +319,44 @@ namespace ts.codefix { | |
| function transformThen(node: CallExpression, transformer: Transformer, prevArgName?: SynthBindingName): readonly Statement[] { | ||
| const [onFulfilled, onRejected] = node.arguments; | ||
| const onFulfilledArgumentName = getArgBindingName(onFulfilled, transformer); | ||
| const transformationBody = getTransformationBody(onFulfilled, prevArgName, onFulfilledArgumentName, node, transformer); | ||
|
|
||
| if (onRejected) { | ||
| // From: | ||
| // x().then(onFulfilledArg => { /* on fulfilled */ }, onRejectedArg => { /* on rejected */ }) | ||
| // | ||
| // To: | ||
| // let onFulfilledArg; | ||
| // try { | ||
| // onFulfilledArg = await x(); | ||
| // } catch (onRejectedArg) { | ||
| // /* on rejected */ | ||
| // return; | ||
| // } | ||
| // /* on fulfilled */ | ||
| const onRejectedArgumentName = getArgBindingName(onRejected, transformer); | ||
| const tryBlock = createBlock(transformExpression(node.expression, transformer, onFulfilledArgumentName).concat(transformationBody)); | ||
| const transformationBody2 = getTransformationBody(onRejected, prevArgName, onRejectedArgumentName, node, transformer); | ||
| const onFulfilledArgType = onFulfilledArgumentName && !transformer.isInJSFile && transformer.checker.getTypeAtLocation(getNode(onFulfilledArgumentName)); | ||
| const onFulfilledArgTypeNode = onFulfilledArgType && !(onFulfilledArgType.flags & TypeFlags.Any) ? transformer.checker.typeToTypeNode(onFulfilledArgType) : undefined; | ||
| const onFulfilledArgVariableDeclaration = createVariableOrAssignmentOrExpressionStatement(onFulfilledArgumentName, /*rightHandSide*/ undefined, onFulfilledArgTypeNode); | ||
|
Member
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. Does this name need to be unique-ified? |
||
| const onFulfilledBody = getTransformationBody(onFulfilled, prevArgName, onFulfilledArgumentName, node, transformer); | ||
| const tryBlock = createBlock(transformExpression(node.expression, transformer, onFulfilledArgumentName)); | ||
| const onRejectedBody = ensureReturnOrThrow(getTransformationBody(onRejected, prevArgName, onRejectedArgumentName, node, transformer)); | ||
| const catchArg = onRejectedArgumentName ? isSynthIdentifier(onRejectedArgumentName) ? onRejectedArgumentName.identifier.text : onRejectedArgumentName.bindingPattern : "e"; | ||
| const catchVariableDeclaration = createVariableDeclaration(catchArg); | ||
| const catchClause = createCatchClause(catchVariableDeclaration, createBlock(transformationBody2)); | ||
|
|
||
| return [createTry(tryBlock, catchClause, /* finallyBlock */ undefined)]; | ||
| const catchClause = createCatchClause(catchVariableDeclaration, createBlock(onRejectedBody)); | ||
| return onFulfilledArgVariableDeclaration.concat(createTry(tryBlock, catchClause, /* finallyBlock */ undefined), onFulfilledBody); | ||
| } | ||
|
|
||
| const transformationBody = getTransformationBody(onFulfilled, prevArgName, onFulfilledArgumentName, node, transformer); | ||
| return transformExpression(node.expression, transformer, onFulfilledArgumentName).concat(transformationBody); | ||
| } | ||
|
|
||
| function ensureReturnOrThrow(statements: readonly Statement[]): readonly Statement[] { | ||
| const lastStatement = lastOrUndefined(statements); | ||
| if (lastStatement?.kind !== SyntaxKind.ReturnStatement && lastStatement?.kind !== SyntaxKind.ThrowStatement) { | ||
| return statements.concat(createReturn()); | ||
|
Member
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. Looks like this could be unreachable (e.g. if the last statement is actually an if-else in which both branches return)?
Member
Author
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. Hm, you’re right. I’m not sure there’s an efficient way to know what to do here. |
||
| } | ||
| return statements; | ||
| } | ||
|
|
||
| /** | ||
| * Transforms the 'x' part of `x.then(...)`, or the 'y()' part of `y().catch(...)`, where 'x' and 'y()' are Promises. | ||
| */ | ||
|
|
@@ -346,17 +368,19 @@ namespace ts.codefix { | |
| return createVariableOrAssignmentOrExpressionStatement(prevArgName, createAwait(node), /*typeAnnotation*/ undefined); | ||
| } | ||
|
|
||
| function createVariableOrAssignmentOrExpressionStatement(variableName: SynthBindingName | undefined, rightHandSide: Expression, typeAnnotation: TypeNode | undefined): readonly Statement[] { | ||
| function createVariableOrAssignmentOrExpressionStatement(variableName: SynthBindingName | undefined, rightHandSide: Expression | undefined, typeAnnotation: TypeNode | undefined): readonly Statement[] { | ||
| if (!variableName || isEmptyBindingName(variableName)) { | ||
| // if there's no argName to assign to, there still might be side effects | ||
| return [createExpressionStatement(rightHandSide)]; | ||
| return rightHandSide ? [createExpressionStatement(rightHandSide)] : emptyArray; | ||
| } | ||
|
|
||
| if (isSynthIdentifier(variableName) && variableName.hasBeenDeclared) { | ||
| // if the variable has already been declared, we don't need "let" or "const" | ||
| Debug.assertIsDefined(rightHandSide); | ||
| return [createExpressionStatement(createAssignment(getSynthesizedDeepClone(variableName.identifier), rightHandSide))]; | ||
| } | ||
|
|
||
| setHasBeenDeclared(variableName); | ||
| return [ | ||
| createVariableStatement( | ||
| /*modifiers*/ undefined, | ||
|
|
@@ -365,7 +389,7 @@ namespace ts.codefix { | |
| getSynthesizedDeepClone(getNode(variableName)), | ||
| typeAnnotation, | ||
| rightHandSide)], | ||
| NodeFlags.Const))]; | ||
| rightHandSide ? NodeFlags.Const : NodeFlags.Let))]; | ||
| } | ||
|
|
||
| function maybeAnnotateAndReturn(expressionToReturn: Expression | undefined, typeAnnotation: TypeNode | undefined): readonly Statement[] { | ||
|
|
@@ -589,6 +613,18 @@ namespace ts.codefix { | |
| return every(bindingName.elements, isEmptyBindingName); | ||
| } | ||
|
|
||
| function setHasBeenDeclared(bindingName: SynthBindingName) { | ||
| if (isSynthIdentifier(bindingName)) { | ||
| setIdentifierHasBeenDeclared(bindingName); | ||
| } | ||
| else { | ||
| bindingName.elements.forEach(setHasBeenDeclared); | ||
| } | ||
| function setIdentifierHasBeenDeclared(identifier: SynthIdentifier) { | ||
| identifier.hasBeenDeclared = true; | ||
| } | ||
| } | ||
|
|
||
| function getNode(bindingName: SynthBindingName) { | ||
| return isSynthIdentifier(bindingName) ? bindingName.identifier : bindingName.bindingPattern; | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,9 +9,9 @@ | |
| async function f() { | ||
| try { | ||
| await Promise.resolve(); | ||
| return 1; | ||
| } | ||
| catch (e) { | ||
| return "a"; | ||
| } | ||
| return 1; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,9 +9,9 @@ | |
| async function f() { | ||
| try { | ||
| await Promise.resolve(); | ||
| return 1; | ||
| } | ||
| catch (e) { | ||
| return "a"; | ||
| } | ||
| return 1; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| // ==ORIGINAL== | ||
|
|
||
| function /*[#|*/fSync/*|]*/() { | ||
| return Promise.resolve(0).then(x => { | ||
| console.log(x); // note: added to illustrate refactor | ||
| throw new Error('Failure!'); | ||
| }, () => null); | ||
| } | ||
|
|
||
| // ==ASYNC FUNCTION::Convert to async function== | ||
|
|
||
| async function fSync() { | ||
| let x; | ||
| try { | ||
| x = await Promise.resolve(0); | ||
| } | ||
| catch (e) { | ||
| return null; | ||
| } | ||
| console.log(x); // note: added to illustrate refactor | ||
| throw new Error('Failure!'); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| // ==ORIGINAL== | ||
|
|
||
| function /*[#|*/fSync/*|]*/() { | ||
| return Promise.resolve(0).then(x => { | ||
| console.log(x); // note: added to illustrate refactor | ||
| throw new Error('Failure!'); | ||
| }, () => null); | ||
| } | ||
|
|
||
| // ==ASYNC FUNCTION::Convert to async function== | ||
|
|
||
| async function fSync() { | ||
| let x: number; | ||
| try { | ||
| x = await Promise.resolve(0); | ||
| } | ||
| catch (e) { | ||
| return null; | ||
| } | ||
| console.log(x); // note: added to illustrate refactor | ||
| throw new Error('Failure!'); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| // ==ORIGINAL== | ||
|
|
||
| function /*[#|*/fSync/*|]*/() { | ||
| return Promise.resolve(0).then(x => { | ||
| console.log(x); // note: added to illustrate refactor | ||
| throw new Error('Failure!'); | ||
| }, () => {}); | ||
| } | ||
|
|
||
| // ==ASYNC FUNCTION::Convert to async function== | ||
|
|
||
| async function fSync() { | ||
| let x; | ||
| try { | ||
| x = await Promise.resolve(0); | ||
| } | ||
| catch (e) { | ||
| return; | ||
| } | ||
| console.log(x); // note: added to illustrate refactor | ||
| throw new Error('Failure!'); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| // ==ORIGINAL== | ||
|
|
||
| function /*[#|*/fSync/*|]*/() { | ||
| return Promise.resolve(0).then(x => { | ||
| console.log(x); // note: added to illustrate refactor | ||
| throw new Error('Failure!'); | ||
| }, () => {}); | ||
| } | ||
|
|
||
| // ==ASYNC FUNCTION::Convert to async function== | ||
|
|
||
| async function fSync() { | ||
| let x: number; | ||
| try { | ||
| x = await Promise.resolve(0); | ||
| } | ||
| catch (e) { | ||
| return; | ||
| } | ||
| console.log(x); // note: added to illustrate refactor | ||
| throw new Error('Failure!'); | ||
| } |
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.
This is great. I wish more of our transformations had comments like these.