From 4c73b2e7bd35a75ef45be9a4cf9b71608b8e138b Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Wed, 17 Apr 2019 14:45:41 -0700 Subject: [PATCH 01/11] Basic support for binding patterns in async/await code fix --- .../codefixes/convertToAsyncFunction.ts | 101 +++++++++++++----- .../services/convertToAsyncFunction.ts | 8 ++ 2 files changed, 80 insertions(+), 29 deletions(-) diff --git a/src/services/codefixes/convertToAsyncFunction.ts b/src/services/codefixes/convertToAsyncFunction.ts index aae1d205de3..4a6071caf62 100644 --- a/src/services/codefixes/convertToAsyncFunction.ts +++ b/src/services/codefixes/convertToAsyncFunction.ts @@ -14,6 +14,14 @@ namespace ts.codefix { getAllCodeActions: context => codeFixAll(context, errorCodes, (changes, err) => convertToAsyncFunction(changes, err.file, err.start, context.program.getTypeChecker(), context)), }); + type SynthBindingName = SynthBindingPattern | SynthIdentifier; + + interface SynthBindingPattern { + readonly elements: ReadonlyArray; + readonly bindingPattern: BindingPattern; + readonly types: Type[]; + } + interface SynthIdentifier { readonly identifier: Identifier; readonly types: Type[]; @@ -266,7 +274,7 @@ namespace ts.codefix { // dispatch function to recursively build the refactoring // should be kept up to date with isFixablePromiseHandler in suggestionDiagnostics.ts - function transformExpression(node: Expression, transformer: Transformer, outermostParent: CallExpression, prevArgName?: SynthIdentifier): ReadonlyArray { + function transformExpression(node: Expression, transformer: Transformer, outermostParent: CallExpression, prevArgName?: SynthBindingName): ReadonlyArray { if (!node) { return emptyArray; } @@ -277,7 +285,7 @@ namespace ts.codefix { if (isCallExpression(node) && hasPropertyAccessExpressionWithName(node, "then") && nodeType && !!transformer.checker.getPromisedTypeOfPromise(nodeType)) { return transformThen(node, transformer, outermostParent, prevArgName); } - else if (isCallExpression(node) && hasPropertyAccessExpressionWithName(node, "catch") && nodeType && !!transformer.checker.getPromisedTypeOfPromise(nodeType)) { + else if (isCallExpression(node) && hasPropertyAccessExpressionWithName(node, "catch") && nodeType && !!transformer.checker.getPromisedTypeOfPromise(nodeType) && (!prevArgName || "identifier" in prevArgName)) { return transformCatch(node, transformer, prevArgName); } else if (isPropertyAccessExpression(node)) { @@ -293,7 +301,7 @@ namespace ts.codefix { function transformCatch(node: CallExpression, transformer: Transformer, prevArgName?: SynthIdentifier): ReadonlyArray { const func = node.arguments[0]; - const argName = getArgName(func, transformer); + const argName = getArgBindingName(func, transformer); const shouldReturn = transformer.setOfExpressionsToReturn.get(getNodeId(node).toString()); /* @@ -319,8 +327,9 @@ namespace ts.codefix { const tryBlock = createBlock(transformExpression(node.expression, transformer, node, prevArgName)); const transformationBody = getTransformationBody(func, prevArgName, argName, node, transformer); - const catchArg = argName ? argName.identifier.text : "e"; - const catchClause = createCatchClause(catchArg, createBlock(transformationBody)); + const catchArg = argName ? "identifier" in argName ? argName.identifier.text : argName.bindingPattern : "e"; + const catchVariableDeclaration = createVariableDeclaration(catchArg); + const catchClause = createCatchClause(catchVariableDeclaration, createBlock(transformationBody)); /* In order to avoid an implicit any, we will synthesize a type for the declaration using the unions of the types of both paths (try block and catch block) @@ -338,31 +347,40 @@ namespace ts.codefix { return varDeclList ? [varDeclList, tryStatement] : [tryStatement]; } + function getIdentifierTextsFromBindingName(bindingName: BindingName): ReadonlyArray { + if (isIdentifier(bindingName)) return [bindingName.text]; + return flatMap(bindingName.elements, element => { + if (isOmittedExpression(element)) return []; + return getIdentifierTextsFromBindingName(element.name); + }); + } + function createUniqueSynthName(prevArgName: SynthIdentifier) { const renamedPrevArg = createOptimisticUniqueName(prevArgName.identifier.text); const newSynthName = { identifier: renamedPrevArg, types: [], numberOfAssignmentsOriginal: 0 }; return newSynthName; } - function transformThen(node: CallExpression, transformer: Transformer, outermostParent: CallExpression, prevArgName?: SynthIdentifier): ReadonlyArray { + function transformThen(node: CallExpression, transformer: Transformer, outermostParent: CallExpression, prevArgName?: SynthBindingName): ReadonlyArray { const [res, rej] = node.arguments; if (!res) { return transformExpression(node.expression, transformer, outermostParent); } - const argNameRes = getArgName(res, transformer); + const argNameRes = getArgBindingName(res, transformer); const transformationBody = getTransformationBody(res, prevArgName, argNameRes, node, transformer); if (rej) { - const argNameRej = getArgName(rej, transformer); + const argNameRej = getArgBindingName(rej, transformer); const tryBlock = createBlock(transformExpression(node.expression, transformer, node, argNameRes).concat(transformationBody)); const transformationBody2 = getTransformationBody(rej, prevArgName, argNameRej, node, transformer); - const catchArg = argNameRej ? argNameRej.identifier.text : "e"; - const catchClause = createCatchClause(catchArg, createBlock(transformationBody2)); + const catchArg = argNameRej ? "identifier" in argNameRej ? argNameRej.identifier.text : argNameRej.bindingPattern : "e"; + const catchVariableDeclaration = createVariableDeclaration(catchArg); + const catchClause = createCatchClause(catchVariableDeclaration, createBlock(transformationBody2)); return [createTry(tryBlock, catchClause, /* finallyBlock */ undefined)]; } @@ -370,12 +388,13 @@ namespace ts.codefix { return transformExpression(node.expression, transformer, node, argNameRes).concat(transformationBody); } - function getFlagOfIdentifier(node: Identifier, constIdentifiers: ReadonlyArray): NodeFlags { - const inArr: boolean = constIdentifiers.some(elem => elem.text === node.text); + function getFlagOfBindingName(bindingName: SynthBindingName, constIdentifiers: ReadonlyArray): NodeFlags { + const identifiers = getIdentifierTextsFromBindingName(getNode(bindingName)); + const inArr: boolean = constIdentifiers.some(elem => contains(identifiers, elem.text)); return inArr ? NodeFlags.Const : NodeFlags.Let; } - function transformPromiseCall(node: Expression, transformer: Transformer, prevArgName?: SynthIdentifier): ReadonlyArray { + function transformPromiseCall(node: Expression, transformer: Transformer, prevArgName?: SynthBindingName): ReadonlyArray { const shouldReturn = transformer.setOfExpressionsToReturn.get(getNodeId(node).toString()); // the identifier is empty when the handler (.then()) ignores the argument - In this situation we do not need to save the result of the promise returning call const originalNodeParent = node.original ? node.original.parent : node.parent; @@ -389,23 +408,23 @@ namespace ts.codefix { return [createReturn(getSynthesizedDeepClone(node))]; } - function createTransformedStatement(prevArgName: SynthIdentifier | undefined, rightHandSide: Expression, transformer: Transformer): ReadonlyArray { - if (!prevArgName || prevArgName.identifier.text.length === 0) { + function createTransformedStatement(prevArgName: SynthBindingName | undefined, rightHandSide: Expression, transformer: Transformer): ReadonlyArray { + if (!prevArgName || isEmpty(prevArgName)) { // if there's no argName to assign to, there still might be side effects return [createStatement(rightHandSide)]; } - if (prevArgName.types.length < prevArgName.numberOfAssignmentsOriginal) { + if ("identifier" in prevArgName && prevArgName.types.length < prevArgName.numberOfAssignmentsOriginal) { // if the variable has already been declared, we don't need "let" or "const" return [createStatement(createAssignment(getSynthesizedDeepClone(prevArgName.identifier), rightHandSide))]; } return [createVariableStatement(/*modifiers*/ undefined, - (createVariableDeclarationList([createVariableDeclaration(getSynthesizedDeepClone(prevArgName.identifier), /*type*/ undefined, rightHandSide)], getFlagOfIdentifier(prevArgName.identifier, transformer.constIdentifiers))))]; + (createVariableDeclarationList([createVariableDeclaration(getSynthesizedDeepClone(getNode(prevArgName)), /*type*/ undefined, rightHandSide)], getFlagOfBindingName(prevArgName, transformer.constIdentifiers))))]; } // should be kept up to date with isFixablePromiseArgument in suggestionDiagnostics.ts - function getTransformationBody(func: Expression, prevArgName: SynthIdentifier | undefined, argName: SynthIdentifier | undefined, parent: CallExpression, transformer: Transformer): ReadonlyArray { + function getTransformationBody(func: Expression, prevArgName: SynthBindingName | undefined, argName: SynthBindingName | undefined, parent: CallExpression, transformer: Transformer): ReadonlyArray { const shouldReturn = transformer.setOfExpressionsToReturn.get(getNodeId(parent).toString()); switch (func.kind) { @@ -418,7 +437,7 @@ namespace ts.codefix { break; } - const synthCall = createCall(getSynthesizedDeepClone(func as Identifier), /*typeArguments*/ undefined, [argName.identifier]); + const synthCall = createCall(getSynthesizedDeepClone(func as Identifier), /*typeArguments*/ undefined, "identifier" in argName ? [argName.identifier] : []); if (shouldReturn) { return [createReturn(synthCall)]; } @@ -461,7 +480,7 @@ namespace ts.codefix { return shouldReturn ? refactoredStmts.map(s => getSynthesizedDeepClone(s)) : removeReturns( refactoredStmts, - prevArgName === undefined ? undefined : prevArgName.identifier, + prevArgName, transformer, seenReturnStatement); } @@ -503,7 +522,7 @@ namespace ts.codefix { } - function removeReturns(stmts: ReadonlyArray, prevArgName: Identifier | undefined, transformer: Transformer, seenReturnStatement: boolean): ReadonlyArray { + function removeReturns(stmts: ReadonlyArray, prevArgName: SynthBindingName | undefined, transformer: Transformer, seenReturnStatement: boolean): ReadonlyArray { const ret: Statement[] = []; for (const stmt of stmts) { if (isReturnStatement(stmt)) { @@ -514,7 +533,7 @@ namespace ts.codefix { } else { ret.push(createVariableStatement(/*modifiers*/ undefined, - (createVariableDeclarationList([createVariableDeclaration(prevArgName, /*type*/ undefined, possiblyAwaitedExpression)], getFlagOfIdentifier(prevArgName, transformer.constIdentifiers))))); + (createVariableDeclarationList([createVariableDeclaration(getNode(prevArgName), /*type*/ undefined, possiblyAwaitedExpression)], getFlagOfBindingName(prevArgName, transformer.constIdentifiers))))); } } } @@ -526,14 +545,14 @@ namespace ts.codefix { // if block has no return statement, need to define prevArgName as undefined to prevent undeclared variables if (!seenReturnStatement && prevArgName !== undefined) { ret.push(createVariableStatement(/*modifiers*/ undefined, - (createVariableDeclarationList([createVariableDeclaration(prevArgName, /*type*/ undefined, createIdentifier("undefined"))], getFlagOfIdentifier(prevArgName, transformer.constIdentifiers))))); + (createVariableDeclarationList([createVariableDeclaration(getNode(prevArgName), /*type*/ undefined, createIdentifier("undefined"))], getFlagOfBindingName(prevArgName, transformer.constIdentifiers))))); } return ret; } - function getInnerTransformationBody(transformer: Transformer, innerRetStmts: ReadonlyArray, prevArgName?: SynthIdentifier) { + function getInnerTransformationBody(transformer: Transformer, innerRetStmts: ReadonlyArray, prevArgName?: SynthBindingName) { let innerCbBody: Statement[] = []; for (const stmt of innerRetStmts) { @@ -553,17 +572,17 @@ namespace ts.codefix { return innerCbBody; } - function getArgName(funcNode: Expression, transformer: Transformer): SynthIdentifier | undefined { + function getArgBindingName(funcNode: Expression, transformer: Transformer): SynthBindingName | undefined { const numberOfAssignmentsOriginal = 0; const types: Type[] = []; - let name: SynthIdentifier | undefined; + let name: SynthBindingName | undefined; if (isFunctionLikeDeclaration(funcNode)) { if (funcNode.parameters.length > 0) { - const param = funcNode.parameters[0].name as Identifier; - name = getMapEntryOrDefault(param); + const param = funcNode.parameters[0].name; + name = getMappedBindingNameOrDefault(param); } } else if (isIdentifier(funcNode)) { @@ -571,12 +590,22 @@ namespace ts.codefix { } // return undefined argName when arg is null or undefined - if (!name || name.identifier.text === "undefined") { + if (!name || "identifier" in name && name.identifier.text === "undefined") { return undefined; } return name; + function getMappedBindingNameOrDefault(bindingName: BindingName): SynthBindingName { + if (isIdentifier(bindingName)) return getMapEntryOrDefault(bindingName); + const elements = flatMap(bindingName.elements, element => { + if (isOmittedExpression(element)) return []; + return [getMappedBindingNameOrDefault(element.name)]; + }); + + return { elements, bindingPattern: bindingName, types: [] }; + } + function getMapEntryOrDefault(identifier: Identifier): SynthIdentifier { const originalNode = getOriginalNode(identifier); const symbol = getSymbol(originalNode); @@ -597,4 +626,18 @@ namespace ts.codefix { return node.original ? node.original : node; } } + + function isEmpty(bindingName: SynthBindingName | undefined): boolean { + if (!bindingName) { + return true; + } + if ("identifier" in bindingName) { + return !bindingName.identifier.text; + } + return every(bindingName.elements, isEmpty); + } + + function getNode(bindingName: SynthBindingName) { + return "identifier" in bindingName ? bindingName.identifier : bindingName.bindingPattern; + } } diff --git a/src/testRunner/unittests/services/convertToAsyncFunction.ts b/src/testRunner/unittests/services/convertToAsyncFunction.ts index 8c426effa4c..12b24c832d0 100644 --- a/src/testRunner/unittests/services/convertToAsyncFunction.ts +++ b/src/testRunner/unittests/services/convertToAsyncFunction.ts @@ -347,6 +347,14 @@ interface Array {}` _testConvertToAsyncFunction("convertToAsyncFunction_basic", ` function [#|f|](): Promise{ return fetch('https://typescriptlang.org').then(result => { console.log(result) }); +}`); + _testConvertToAsyncFunction("convertToAsyncFunction_arrayBindingPattern", ` +function [#|f|](): Promise{ + return fetch('https://typescriptlang.org').then(([result]) => { console.log(result) }); +}`); + _testConvertToAsyncFunction("convertToAsyncFunction_objectBindingPattern", ` +function [#|f|](): Promise{ + return fetch('https://typescriptlang.org').then(({ result }) => { console.log(result) }); }`); _testConvertToAsyncFunction("convertToAsyncFunction_basicNoReturnTypeAnnotation", ` function [#|f|]() { From 06c8506f96c28bf9b4634c00db9c5e905dfb20bd Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Wed, 17 Apr 2019 14:50:45 -0700 Subject: [PATCH 02/11] Add new baselines --- .../convertToAsyncFunction_arrayBindingPattern.ts | 11 +++++++++++ .../convertToAsyncFunction_objectBindingPattern.ts | 11 +++++++++++ 2 files changed, 22 insertions(+) create mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_arrayBindingPattern.ts create mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_objectBindingPattern.ts diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_arrayBindingPattern.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_arrayBindingPattern.ts new file mode 100644 index 00000000000..7250945aaa1 --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_arrayBindingPattern.ts @@ -0,0 +1,11 @@ +// ==ORIGINAL== + +function /*[#|*/f/*|]*/(): Promise{ + return fetch('https://typescriptlang.org').then(([result]) => { console.log(result) }); +} +// ==ASYNC FUNCTION::Convert to async function== + +async function f(): Promise{ + let [result] = await fetch('https://typescriptlang.org'); + console.log(result); +} \ No newline at end of file diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_objectBindingPattern.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_objectBindingPattern.ts new file mode 100644 index 00000000000..844aaad6d74 --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_objectBindingPattern.ts @@ -0,0 +1,11 @@ +// ==ORIGINAL== + +function /*[#|*/f/*|]*/(): Promise{ + return fetch('https://typescriptlang.org').then(({ result }) => { console.log(result) }); +} +// ==ASYNC FUNCTION::Convert to async function== + +async function f(): Promise{ + let { result } = await fetch('https://typescriptlang.org'); + console.log(result); +} \ No newline at end of file From 42c9b47add2860a5826e8943c0838170225f3586 Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Wed, 17 Apr 2019 15:25:00 -0700 Subject: [PATCH 03/11] Fix let/const selection for binding patterns --- src/services/codefixes/convertToAsyncFunction.ts | 4 ++-- .../convertToAsyncFunction_arrayBindingPattern.ts | 2 +- .../convertToAsyncFunction_objectBindingPattern.ts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/services/codefixes/convertToAsyncFunction.ts b/src/services/codefixes/convertToAsyncFunction.ts index 4a6071caf62..2b455ded873 100644 --- a/src/services/codefixes/convertToAsyncFunction.ts +++ b/src/services/codefixes/convertToAsyncFunction.ts @@ -196,8 +196,8 @@ namespace ts.codefix { allVarNames.push({ identifier: synthName.identifier, symbol }); addNameToFrequencyMap(collidingSymbolMap, ident.text, symbol); } - // we only care about identifiers that are parameters and declarations (don't care about other uses) - else if (node.parent && (isParameter(node.parent) || isVariableDeclaration(node.parent))) { + // we only care about identifiers that are parameters, declarations, or binding elements (don't care about other uses) + else if (node.parent && (isParameter(node.parent) || isVariableDeclaration(node.parent) || isBindingElement(node.parent))) { const originalName = node.text; const collidingSymbols = collidingSymbolMap.get(originalName); diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_arrayBindingPattern.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_arrayBindingPattern.ts index 7250945aaa1..a2aa01edade 100644 --- a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_arrayBindingPattern.ts +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_arrayBindingPattern.ts @@ -6,6 +6,6 @@ function /*[#|*/f/*|]*/(): Promise{ // ==ASYNC FUNCTION::Convert to async function== async function f(): Promise{ - let [result] = await fetch('https://typescriptlang.org'); + const [result] = await fetch('https://typescriptlang.org'); console.log(result); } \ No newline at end of file diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_objectBindingPattern.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_objectBindingPattern.ts index 844aaad6d74..2f5e1991093 100644 --- a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_objectBindingPattern.ts +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_objectBindingPattern.ts @@ -6,6 +6,6 @@ function /*[#|*/f/*|]*/(): Promise{ // ==ASYNC FUNCTION::Convert to async function== async function f(): Promise{ - let { result } = await fetch('https://typescriptlang.org'); + const { result } = await fetch('https://typescriptlang.org'); console.log(result); } \ No newline at end of file From ef18453166ab3e0ab709bf50e69d559fbf5c7a08 Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Wed, 17 Apr 2019 15:28:57 -0700 Subject: [PATCH 04/11] Add tests for binding elements that need to be renamed --- .../unittests/services/convertToAsyncFunction.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/testRunner/unittests/services/convertToAsyncFunction.ts b/src/testRunner/unittests/services/convertToAsyncFunction.ts index 12b24c832d0..81d847f31af 100644 --- a/src/testRunner/unittests/services/convertToAsyncFunction.ts +++ b/src/testRunner/unittests/services/convertToAsyncFunction.ts @@ -355,6 +355,16 @@ function [#|f|](): Promise{ _testConvertToAsyncFunction("convertToAsyncFunction_objectBindingPattern", ` function [#|f|](): Promise{ return fetch('https://typescriptlang.org').then(({ result }) => { console.log(result) }); +}`); + _testConvertToAsyncFunction("convertToAsyncFunction_arrayBindingPatternRename", ` +function [#|f|](): Promise{ + const result = getResult(); + return fetch('https://typescriptlang.org').then(([result]) => { console.log(result) }); +}`); + _testConvertToAsyncFunction("convertToAsyncFunction_objectBindingPatternRename", ` +function [#|f|](): Promise{ + const result = getResult(); + return fetch('https://typescriptlang.org').then(({ result }) => { console.log(result) }); }`); _testConvertToAsyncFunction("convertToAsyncFunction_basicNoReturnTypeAnnotation", ` function [#|f|]() { From 08bb58b0c1d246fa31e514e535234ff6101a4354 Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Wed, 17 Apr 2019 18:35:47 -0700 Subject: [PATCH 05/11] Enable renaming object binding patterns when needed --- src/services/utilities.ts | 14 +++++++++++++- ...ertToAsyncFunction_arrayBindingPatternRename.ts | 13 +++++++++++++ ...rtToAsyncFunction_objectBindingPatternRename.ts | 13 +++++++++++++ 3 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_arrayBindingPatternRename.ts create mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_objectBindingPatternRename.ts diff --git a/src/services/utilities.ts b/src/services/utilities.ts index 7dc96c10804..9e30d23caa1 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -1717,7 +1717,19 @@ namespace ts { export function getSynthesizedDeepCloneWithRenames(node: T, includeTrivia = true, renameMap?: Map, checker?: TypeChecker, callback?: (originalNode: Node, clone: Node) => any): T { let clone; - if (isIdentifier(node) && renameMap && checker) { + if (renameMap && checker && isBindingElement(node) && isIdentifier(node.name) && isObjectBindingPattern(node.parent)) { + const symbol = checker.getSymbolAtLocation(node.name); + const renameInfo = symbol && renameMap.get(String(getSymbolId(symbol))); + + if (renameInfo && renameInfo.text !== (node.name || node.propertyName).getText()) { + clone = createBindingElement( + node.dotDotDotToken, + node.propertyName || node.name, + renameInfo, + node.initializer); + } + } + else if (renameMap && checker && isIdentifier(node) && !(node.parent && node.parent.parent && isBindingElement(node.parent) && isObjectBindingPattern(node.parent.parent))) { const symbol = checker.getSymbolAtLocation(node); const renameInfo = symbol && renameMap.get(String(getSymbolId(symbol))); diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_arrayBindingPatternRename.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_arrayBindingPatternRename.ts new file mode 100644 index 00000000000..5847e01d57d --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_arrayBindingPatternRename.ts @@ -0,0 +1,13 @@ +// ==ORIGINAL== + +function /*[#|*/f/*|]*/(): Promise{ + const result = getResult(); + return fetch('https://typescriptlang.org').then(([result]) => { console.log(result) }); +} +// ==ASYNC FUNCTION::Convert to async function== + +async function f(): Promise{ + const result = getResult(); + const [result_1] = await fetch('https://typescriptlang.org'); + console.log(result_1); +} \ No newline at end of file diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_objectBindingPatternRename.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_objectBindingPatternRename.ts new file mode 100644 index 00000000000..98b736f788d --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_objectBindingPatternRename.ts @@ -0,0 +1,13 @@ +// ==ORIGINAL== + +function /*[#|*/f/*|]*/(): Promise{ + const result = getResult(); + return fetch('https://typescriptlang.org').then(({ result }) => { console.log(result) }); +} +// ==ASYNC FUNCTION::Convert to async function== + +async function f(): Promise{ + const result = getResult(); + const { result: result_1 } = await fetch('https://typescriptlang.org'); + console.log(result_1); +} \ No newline at end of file From 92f0ac7efc5104665c4bd440bd55eb3e3e07c4c1 Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Wed, 17 Apr 2019 18:48:01 -0700 Subject: [PATCH 06/11] Remove unnecessary change --- src/services/utilities.ts | 2 +- .../cases/user/TypeScript-Node-Starter/TypeScript-Node-Starter | 2 +- tests/cases/user/create-react-app/create-react-app | 2 +- tests/cases/user/prettier/prettier | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/services/utilities.ts b/src/services/utilities.ts index 9e30d23caa1..5506b45d380 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -1729,7 +1729,7 @@ namespace ts { node.initializer); } } - else if (renameMap && checker && isIdentifier(node) && !(node.parent && node.parent.parent && isBindingElement(node.parent) && isObjectBindingPattern(node.parent.parent))) { + else if (renameMap && checker && isIdentifier(node)) { const symbol = checker.getSymbolAtLocation(node); const renameInfo = symbol && renameMap.get(String(getSymbolId(symbol))); diff --git a/tests/cases/user/TypeScript-Node-Starter/TypeScript-Node-Starter b/tests/cases/user/TypeScript-Node-Starter/TypeScript-Node-Starter index 40bdb4eadab..46971a84547 160000 --- a/tests/cases/user/TypeScript-Node-Starter/TypeScript-Node-Starter +++ b/tests/cases/user/TypeScript-Node-Starter/TypeScript-Node-Starter @@ -1 +1 @@ -Subproject commit 40bdb4eadabc9fbed7d83e3f26817a931c0763b6 +Subproject commit 46971a8454761f1a11d8fde4d96ff8d29bc4e754 diff --git a/tests/cases/user/create-react-app/create-react-app b/tests/cases/user/create-react-app/create-react-app index 1a61db58d43..9514cb88ab9 160000 --- a/tests/cases/user/create-react-app/create-react-app +++ b/tests/cases/user/create-react-app/create-react-app @@ -1 +1 @@ -Subproject commit 1a61db58d434d33603f20e73ca643ec83c561b73 +Subproject commit 9514cb88ab92fec7f5df2914702ef23a62c0a249 diff --git a/tests/cases/user/prettier/prettier b/tests/cases/user/prettier/prettier index 1e471a00796..0b07e108333 160000 --- a/tests/cases/user/prettier/prettier +++ b/tests/cases/user/prettier/prettier @@ -1 +1 @@ -Subproject commit 1e471a007968b7490563b91ed6909ae6046f3fe8 +Subproject commit 0b07e1083339e28a8239df3f5245f530cc4fd7f8 From e7fb18e395b3e0874c22d96b0a2c94fec2eac1d0 Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Thu, 18 Apr 2019 11:34:08 -0700 Subject: [PATCH 07/11] Handle case where you have to add a destructuring after a try/catch block --- .../codefixes/convertToAsyncFunction.ts | 73 ++++++++++++------- .../services/convertToAsyncFunction.ts | 33 +++++++++ ...ToAsyncFunction_InnerPromiseRetBinding1.ts | 24 ++++++ ...ToAsyncFunction_InnerPromiseRetBinding2.ts | 25 +++++++ ...ToAsyncFunction_InnerPromiseRetBinding3.ts | 25 +++++++ 5 files changed, 155 insertions(+), 25 deletions(-) create mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_InnerPromiseRetBinding1.ts create mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_InnerPromiseRetBinding2.ts create mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_InnerPromiseRetBinding3.ts diff --git a/src/services/codefixes/convertToAsyncFunction.ts b/src/services/codefixes/convertToAsyncFunction.ts index 2b455ded873..77a364b7750 100644 --- a/src/services/codefixes/convertToAsyncFunction.ts +++ b/src/services/codefixes/convertToAsyncFunction.ts @@ -285,7 +285,7 @@ namespace ts.codefix { if (isCallExpression(node) && hasPropertyAccessExpressionWithName(node, "then") && nodeType && !!transformer.checker.getPromisedTypeOfPromise(nodeType)) { return transformThen(node, transformer, outermostParent, prevArgName); } - else if (isCallExpression(node) && hasPropertyAccessExpressionWithName(node, "catch") && nodeType && !!transformer.checker.getPromisedTypeOfPromise(nodeType) && (!prevArgName || "identifier" in prevArgName)) { + else if (isCallExpression(node) && hasPropertyAccessExpressionWithName(node, "catch") && nodeType && !!transformer.checker.getPromisedTypeOfPromise(nodeType)) { return transformCatch(node, transformer, prevArgName); } else if (isPropertyAccessExpression(node)) { @@ -299,10 +299,11 @@ namespace ts.codefix { return emptyArray; } - function transformCatch(node: CallExpression, transformer: Transformer, prevArgName?: SynthIdentifier): ReadonlyArray { + function transformCatch(node: CallExpression, transformer: Transformer, prevArgName?: SynthBindingName): ReadonlyArray { const func = node.arguments[0]; const argName = getArgBindingName(func, transformer); const shouldReturn = transformer.setOfExpressionsToReturn.get(getNodeId(node).toString()); + let possibleNameForVarDecl: SynthIdentifier | undefined; /* If there is another call in the chain after the .catch() we are transforming, we will need to save the result of both paths (try block and catch block) @@ -310,41 +311,55 @@ namespace ts.codefix { We will use the prevArgName and then update the synthNamesMap with a new variable name for the next transformation step */ if (prevArgName && !shouldReturn) { - prevArgName.numberOfAssignmentsOriginal = 2; // Try block and catch block - transformer.synthNamesMap.forEach((val, key) => { - if (val.identifier.text === prevArgName.identifier.text) { - const newSynthName = createUniqueSynthName(prevArgName); - transformer.synthNamesMap.set(key, newSynthName); - } - }); + if (isSynthIdentifier(prevArgName)) { + possibleNameForVarDecl = prevArgName; + transformer.synthNamesMap.forEach((val, key) => { + if (val.identifier.text === prevArgName.identifier.text) { + const newSynthName = createUniqueSynthName(prevArgName); + transformer.synthNamesMap.set(key, newSynthName); + } + }); + } + else { + possibleNameForVarDecl = createUniqueSynthName({ + identifier: createOptimisticUniqueName("result"), + types: prevArgName.types, + numberOfAssignmentsOriginal: 0 + }); + } + possibleNameForVarDecl.numberOfAssignmentsOriginal = 2; // Try block and catch block // update the constIdentifiers list - if (transformer.constIdentifiers.some(elem => elem.text === prevArgName.identifier.text)) { - transformer.constIdentifiers.push(createUniqueSynthName(prevArgName).identifier); + if (transformer.constIdentifiers.some(elem => elem.text === possibleNameForVarDecl!.identifier.text)) { + transformer.constIdentifiers.push(createUniqueSynthName(possibleNameForVarDecl).identifier); } } - const tryBlock = createBlock(transformExpression(node.expression, transformer, node, prevArgName)); + const tryBlock = createBlock(transformExpression(node.expression, transformer, node, possibleNameForVarDecl)); - const transformationBody = getTransformationBody(func, prevArgName, argName, node, transformer); - const catchArg = argName ? "identifier" in argName ? argName.identifier.text : argName.bindingPattern : "e"; + const transformationBody = getTransformationBody(func, possibleNameForVarDecl, argName, node, transformer); + const catchArg = argName ? isSynthIdentifier(argName) ? argName.identifier.text : argName.bindingPattern : "e"; const catchVariableDeclaration = createVariableDeclaration(catchArg); const catchClause = createCatchClause(catchVariableDeclaration, createBlock(transformationBody)); /* In order to avoid an implicit any, we will synthesize a type for the declaration using the unions of the types of both paths (try block and catch block) */ - let varDeclList; - if (prevArgName && !shouldReturn) { - const typeArray: Type[] = prevArgName.types; + let varDeclList: VariableStatement | undefined; + let varDeclIdentifier: Identifier | undefined; + if (possibleNameForVarDecl && !shouldReturn) { + varDeclIdentifier = getSynthesizedDeepClone(possibleNameForVarDecl.identifier); + const typeArray: Type[] = possibleNameForVarDecl.types; const unionType = transformer.checker.getUnionType(typeArray, UnionReduction.Subtype); const unionTypeNode = transformer.isInJSFile ? undefined : transformer.checker.typeToTypeNode(unionType); - const varDecl = [createVariableDeclaration(getSynthesizedDeepClone(prevArgName.identifier), unionTypeNode)]; + const varDecl = [createVariableDeclaration(varDeclIdentifier, unionTypeNode)]; varDeclList = createVariableStatement(/*modifiers*/ undefined, createVariableDeclarationList(varDecl, NodeFlags.Let)); } const tryStatement = createTry(tryBlock, catchClause, /*finallyBlock*/ undefined); - return varDeclList ? [varDeclList, tryStatement] : [tryStatement]; + const destructuredResult = prevArgName && varDeclIdentifier && isSynthBindingPattern(prevArgName) + && createVariableStatement(/* modifiers */ undefined, createVariableDeclarationList([createVariableDeclaration(getSynthesizedDeepCloneWithRenames(prevArgName.bindingPattern), /* type */ undefined, varDeclIdentifier)], NodeFlags.Const)); + return compact([varDeclList, tryStatement, destructuredResult]); } function getIdentifierTextsFromBindingName(bindingName: BindingName): ReadonlyArray { @@ -355,7 +370,7 @@ namespace ts.codefix { }); } - function createUniqueSynthName(prevArgName: SynthIdentifier) { + function createUniqueSynthName(prevArgName: SynthIdentifier): SynthIdentifier { const renamedPrevArg = createOptimisticUniqueName(prevArgName.identifier.text); const newSynthName = { identifier: renamedPrevArg, types: [], numberOfAssignmentsOriginal: 0 }; return newSynthName; @@ -378,7 +393,7 @@ namespace ts.codefix { const transformationBody2 = getTransformationBody(rej, prevArgName, argNameRej, node, transformer); - const catchArg = argNameRej ? "identifier" in argNameRej ? argNameRej.identifier.text : argNameRej.bindingPattern : "e"; + const catchArg = argNameRej ? isSynthIdentifier(argNameRej) ? argNameRej.identifier.text : argNameRej.bindingPattern : "e"; const catchVariableDeclaration = createVariableDeclaration(catchArg); const catchClause = createCatchClause(catchVariableDeclaration, createBlock(transformationBody2)); @@ -414,7 +429,7 @@ namespace ts.codefix { return [createStatement(rightHandSide)]; } - if ("identifier" in prevArgName && prevArgName.types.length < prevArgName.numberOfAssignmentsOriginal) { + if (isSynthIdentifier(prevArgName) && prevArgName.types.length < prevArgName.numberOfAssignmentsOriginal) { // if the variable has already been declared, we don't need "let" or "const" return [createStatement(createAssignment(getSynthesizedDeepClone(prevArgName.identifier), rightHandSide))]; } @@ -437,7 +452,7 @@ namespace ts.codefix { break; } - const synthCall = createCall(getSynthesizedDeepClone(func as Identifier), /*typeArguments*/ undefined, "identifier" in argName ? [argName.identifier] : []); + const synthCall = createCall(getSynthesizedDeepClone(func as Identifier), /*typeArguments*/ undefined, isSynthIdentifier(argName) ? [argName.identifier] : []); if (shouldReturn) { return [createReturn(synthCall)]; } @@ -631,13 +646,21 @@ namespace ts.codefix { if (!bindingName) { return true; } - if ("identifier" in bindingName) { + if (isSynthIdentifier(bindingName)) { return !bindingName.identifier.text; } return every(bindingName.elements, isEmpty); } function getNode(bindingName: SynthBindingName) { - return "identifier" in bindingName ? bindingName.identifier : bindingName.bindingPattern; + return isSynthIdentifier(bindingName) ? bindingName.identifier : bindingName.bindingPattern; + } + + function isSynthIdentifier(bindingName: SynthBindingName): bindingName is SynthIdentifier { + return "identifier" in bindingName; + } + + function isSynthBindingPattern(bindingName: SynthBindingName): bindingName is SynthBindingPattern { + return "elements" in bindingName; } } diff --git a/src/testRunner/unittests/services/convertToAsyncFunction.ts b/src/testRunner/unittests/services/convertToAsyncFunction.ts index 81d847f31af..dadde325a14 100644 --- a/src/testRunner/unittests/services/convertToAsyncFunction.ts +++ b/src/testRunner/unittests/services/convertToAsyncFunction.ts @@ -622,6 +622,39 @@ function [#|innerPromise|](): Promise { ` ); + _testConvertToAsyncFunction("convertToAsyncFunction_InnerPromiseRetBinding1", ` +function [#|innerPromise|](): Promise { + return fetch("https://typescriptlang.org").then(resp => { + return resp.blob().then(({ blob }) => blob.byteOffset).catch(({ message }) => 'Error ' + message); + }).then(blob => { + return blob.toString(); + }); +} +` + ); + + _testConvertToAsyncFunction("convertToAsyncFunction_InnerPromiseRetBinding2", ` +function [#|innerPromise|](): Promise { + return fetch("https://typescriptlang.org").then(resp => { + return resp.blob().then(blob => blob.byteOffset).catch(err => 'Error'); + }).then(({ x }) => { + return x.toString(); + }); +} +` + ); + + _testConvertToAsyncFunction("convertToAsyncFunction_InnerPromiseRetBinding3", ` +function [#|innerPromise|](): Promise { + return fetch("https://typescriptlang.org").then(resp => { + return resp.blob().then(({ blob }) => blob.byteOffset).catch(({ message }) => 'Error ' + message); + }).then(([x, y]) => { + return (x || y).toString(); + }); +} +` + ); + _testConvertToAsyncFunctionFailed("convertToAsyncFunction_VarReturn01", ` function [#|f|]() { let blob = fetch("https://typescriptlang.org").then(resp => console.log(resp)); diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_InnerPromiseRetBinding1.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_InnerPromiseRetBinding1.ts new file mode 100644 index 00000000000..7f1559ff7cb --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_InnerPromiseRetBinding1.ts @@ -0,0 +1,24 @@ +// ==ORIGINAL== + +function /*[#|*/innerPromise/*|]*/(): Promise { + return fetch("https://typescriptlang.org").then(resp => { + return resp.blob().then(({ blob }) => blob.byteOffset).catch(({ message }) => 'Error ' + message); + }).then(blob => { + return blob.toString(); + }); +} + +// ==ASYNC FUNCTION::Convert to async function== + +async function innerPromise(): Promise { + const resp = await fetch("https://typescriptlang.org"); + let blob: any; + try { + const { blob } = await resp.blob(); + blob = blob.byteOffset; + } + catch ({ message }) { + blob = 'Error ' + message; + } + return blob.toString(); +} diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_InnerPromiseRetBinding2.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_InnerPromiseRetBinding2.ts new file mode 100644 index 00000000000..fb8c32c5efd --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_InnerPromiseRetBinding2.ts @@ -0,0 +1,25 @@ +// ==ORIGINAL== + +function /*[#|*/innerPromise/*|]*/(): Promise { + return fetch("https://typescriptlang.org").then(resp => { + return resp.blob().then(blob => blob.byteOffset).catch(err => 'Error'); + }).then(({ x }) => { + return x.toString(); + }); +} + +// ==ASYNC FUNCTION::Convert to async function== + +async function innerPromise(): Promise { + const resp = await fetch("https://typescriptlang.org"); + let result: any; + try { + const blob = await resp.blob(); + result = blob.byteOffset; + } + catch (err) { + result = 'Error'; + } + const { x } = result; + return x.toString(); +} diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_InnerPromiseRetBinding3.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_InnerPromiseRetBinding3.ts new file mode 100644 index 00000000000..f55184aa10f --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_InnerPromiseRetBinding3.ts @@ -0,0 +1,25 @@ +// ==ORIGINAL== + +function /*[#|*/innerPromise/*|]*/(): Promise { + return fetch("https://typescriptlang.org").then(resp => { + return resp.blob().then(({ blob }) => blob.byteOffset).catch(({ message }) => 'Error ' + message); + }).then(([x, y]) => { + return (x || y).toString(); + }); +} + +// ==ASYNC FUNCTION::Convert to async function== + +async function innerPromise(): Promise { + const resp = await fetch("https://typescriptlang.org"); + let result: any; + try { + const { blob } = await resp.blob(); + result = blob.byteOffset; + } + catch ({ message }) { + result = 'Error ' + message; + } + const [x, y] = result; + return (x || y).toString(); +} From af498eb6ca6a40557928c67ca4f2eacfb7f1bb5d Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Thu, 18 Apr 2019 11:50:26 -0700 Subject: [PATCH 08/11] Clean up synthetic binding name creation and discriminating --- .../codefixes/convertToAsyncFunction.ts | 38 ++++++++++++------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/src/services/codefixes/convertToAsyncFunction.ts b/src/services/codefixes/convertToAsyncFunction.ts index 77a364b7750..3842c30d6aa 100644 --- a/src/services/codefixes/convertToAsyncFunction.ts +++ b/src/services/codefixes/convertToAsyncFunction.ts @@ -14,15 +14,22 @@ namespace ts.codefix { getAllCodeActions: context => codeFixAll(context, errorCodes, (changes, err) => convertToAsyncFunction(changes, err.file, err.start, context.program.getTypeChecker(), context)), }); + const enum SynthBindingNameKind { + Identifier, + BindingPattern, + } + type SynthBindingName = SynthBindingPattern | SynthIdentifier; interface SynthBindingPattern { + readonly kind: SynthBindingNameKind.BindingPattern; readonly elements: ReadonlyArray; readonly bindingPattern: BindingPattern; readonly types: Type[]; } interface SynthIdentifier { + readonly kind: SynthBindingNameKind.Identifier; readonly identifier: Identifier; readonly types: Type[]; numberOfAssignmentsOriginal: number; // number of times the variable should be assigned in the refactor @@ -212,7 +219,7 @@ namespace ts.codefix { else { const identifier = getSynthesizedDeepClone(node); identsToRenameMap.set(symbolIdString, identifier); - synthNamesMap.set(symbolIdString, { identifier, types: [], numberOfAssignmentsOriginal: allVarNames.filter(elem => elem.identifier.text === node.text).length/*, numberOfAssignmentsSynthesized: 0*/ }); + synthNamesMap.set(symbolIdString, createSynthIdentifier(identifier, [], allVarNames.filter(elem => elem.identifier.text === node.text).length/*, numberOfAssignmentsSynthesized: 0*/)); if ((isParameter(node.parent) && isExpressionOrCallOnTypePromise(node.parent.parent)) || isVariableDeclaration(node.parent)) { allVarNames.push({ identifier, symbol }); addNameToFrequencyMap(collidingSymbolMap, originalName, symbol); @@ -269,7 +276,7 @@ namespace ts.codefix { const numVarsSameName = (originalNames.get(name.text) || emptyArray).length; const numberOfAssignmentsOriginal = 0; const identifier = numVarsSameName === 0 ? name : createIdentifier(name.text + "_" + numVarsSameName); - return { identifier, types: [], numberOfAssignmentsOriginal }; + return createSynthIdentifier(identifier, [], numberOfAssignmentsOriginal); } // dispatch function to recursively build the refactoring @@ -321,11 +328,7 @@ namespace ts.codefix { }); } else { - possibleNameForVarDecl = createUniqueSynthName({ - identifier: createOptimisticUniqueName("result"), - types: prevArgName.types, - numberOfAssignmentsOriginal: 0 - }); + possibleNameForVarDecl = createSynthIdentifier(createOptimisticUniqueName("result"), prevArgName.types); } possibleNameForVarDecl.numberOfAssignmentsOriginal = 2; // Try block and catch block @@ -372,8 +375,7 @@ namespace ts.codefix { function createUniqueSynthName(prevArgName: SynthIdentifier): SynthIdentifier { const renamedPrevArg = createOptimisticUniqueName(prevArgName.identifier.text); - const newSynthName = { identifier: renamedPrevArg, types: [], numberOfAssignmentsOriginal: 0 }; - return newSynthName; + return createSynthIdentifier(renamedPrevArg); } function transformThen(node: CallExpression, transformer: Transformer, outermostParent: CallExpression, prevArgName?: SynthBindingName): ReadonlyArray { @@ -618,7 +620,7 @@ namespace ts.codefix { return [getMappedBindingNameOrDefault(element.name)]; }); - return { elements, bindingPattern: bindingName, types: [] }; + return createSynthBindingPattern(bindingName, elements); } function getMapEntryOrDefault(identifier: Identifier): SynthIdentifier { @@ -626,11 +628,11 @@ namespace ts.codefix { const symbol = getSymbol(originalNode); if (!symbol) { - return { identifier, types, numberOfAssignmentsOriginal }; + return createSynthIdentifier(identifier, types, numberOfAssignmentsOriginal); } const mapEntry = transformer.synthNamesMap.get(getSymbolId(symbol).toString()); - return mapEntry || { identifier, types, numberOfAssignmentsOriginal }; + return mapEntry || createSynthIdentifier(identifier, types, numberOfAssignmentsOriginal); } function getSymbol(node: Node): Symbol | undefined { @@ -656,11 +658,19 @@ namespace ts.codefix { return isSynthIdentifier(bindingName) ? bindingName.identifier : bindingName.bindingPattern; } + function createSynthIdentifier(identifier: Identifier, types: Type[] = [], numberOfAssignmentsOriginal = 0): SynthIdentifier { + return { kind: SynthBindingNameKind.Identifier, identifier, types, numberOfAssignmentsOriginal }; + } + + function createSynthBindingPattern(bindingPattern: BindingPattern, elements: ReadonlyArray = emptyArray, types: Type[] = []): SynthBindingPattern { + return { kind: SynthBindingNameKind.BindingPattern, bindingPattern, elements, types }; + } + function isSynthIdentifier(bindingName: SynthBindingName): bindingName is SynthIdentifier { - return "identifier" in bindingName; + return bindingName.kind === SynthBindingNameKind.Identifier; } function isSynthBindingPattern(bindingName: SynthBindingName): bindingName is SynthBindingPattern { - return "elements" in bindingName; + return bindingName.kind === SynthBindingNameKind.BindingPattern; } } From 680d18207c71b74ac4e99af11c09f7ec74d22690 Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Thu, 18 Apr 2019 12:01:11 -0700 Subject: [PATCH 09/11] Add tests for destructuring from variable with inferrable type --- .../services/convertToAsyncFunction.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/testRunner/unittests/services/convertToAsyncFunction.ts b/src/testRunner/unittests/services/convertToAsyncFunction.ts index dadde325a14..b5eb5cf37d0 100644 --- a/src/testRunner/unittests/services/convertToAsyncFunction.ts +++ b/src/testRunner/unittests/services/convertToAsyncFunction.ts @@ -655,6 +655,17 @@ function [#|innerPromise|](): Promise { ` ); + _testConvertToAsyncFunction("convertToAsyncFunction_InnerPromiseRetBinding4", ` +function [#|innerPromise|](): Promise { + return fetch("https://typescriptlang.org").then(resp => { + return resp.blob().then(({ blob }: { blob: { byteOffset: number } }) => [0, blob.byteOffset]).catch(({ message }: Error) => ['Error ', message]); + }).then(([x, y]) => { + return (x || y).toString(); + }); +} +` + ); + _testConvertToAsyncFunctionFailed("convertToAsyncFunction_VarReturn01", ` function [#|f|]() { let blob = fetch("https://typescriptlang.org").then(resp => console.log(resp)); @@ -1221,6 +1232,12 @@ const { length } = [#|function|] () { function [#|f|]() { return Promise.resolve().then(x => 1).catch(x => "a").then(x => !!x); } +`); + + _testConvertToAsyncFunction("convertToAsyncFunction_catchBlockUniqueParamsBindingPattern", ` +function [#|f|]() { + return Promise.resolve().then(() => ({ x: 3 })).catch(() => ({ x: "a" })).then(({ x }) => !!x); +} `); _testConvertToAsyncFunction("convertToAsyncFunction_bindingPattern", ` From b472e9a9c415bd1a33214730f30c2fac3c781c6c Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Thu, 18 Apr 2019 18:08:37 -0700 Subject: [PATCH 10/11] Add baselines for new tests --- ...ToAsyncFunction_InnerPromiseRetBinding4.ts | 25 +++++++++++++++++++ ...on_catchBlockUniqueParamsBindingPattern.js | 20 +++++++++++++++ ...on_catchBlockUniqueParamsBindingPattern.ts | 20 +++++++++++++++ .../TypeScript-Node-Starter | 2 +- .../user/create-react-app/create-react-app | 2 +- tests/cases/user/prettier/prettier | 2 +- 6 files changed, 68 insertions(+), 3 deletions(-) create mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_InnerPromiseRetBinding4.ts create mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_catchBlockUniqueParamsBindingPattern.js create mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_catchBlockUniqueParamsBindingPattern.ts diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_InnerPromiseRetBinding4.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_InnerPromiseRetBinding4.ts new file mode 100644 index 00000000000..e4dff86ffd9 --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_InnerPromiseRetBinding4.ts @@ -0,0 +1,25 @@ +// ==ORIGINAL== + +function /*[#|*/innerPromise/*|]*/(): Promise { + return fetch("https://typescriptlang.org").then(resp => { + return resp.blob().then(({ blob }: { blob: { byteOffset: number } }) => [0, blob.byteOffset]).catch(({ message }: Error) => ['Error ', message]); + }).then(([x, y]) => { + return (x || y).toString(); + }); +} + +// ==ASYNC FUNCTION::Convert to async function== + +async function innerPromise(): Promise { + const resp = await fetch("https://typescriptlang.org"); + let result: any[]; + try { + const { blob } = await resp.blob(); + result = [0, blob.byteOffset]; + } + catch ({ message }) { + result = ['Error ', message]; + } + const [x, y] = result; + return (x || y).toString(); +} diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_catchBlockUniqueParamsBindingPattern.js b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_catchBlockUniqueParamsBindingPattern.js new file mode 100644 index 00000000000..f1c5974ebc3 --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_catchBlockUniqueParamsBindingPattern.js @@ -0,0 +1,20 @@ +// ==ORIGINAL== + +function /*[#|*/f/*|]*/() { + return Promise.resolve().then(() => ({ x: 3 })).catch(() => ({ x: "a" })).then(({ x }) => !!x); +} + +// ==ASYNC FUNCTION::Convert to async function== + +async function f() { + let result; + try { + await Promise.resolve(); + result = ({ x: 3 }); + } + catch (e) { + result = ({ x: "a" }); + } + const { x } = result; + return !!x; +} diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_catchBlockUniqueParamsBindingPattern.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_catchBlockUniqueParamsBindingPattern.ts new file mode 100644 index 00000000000..96ac53d5a8f --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_catchBlockUniqueParamsBindingPattern.ts @@ -0,0 +1,20 @@ +// ==ORIGINAL== + +function /*[#|*/f/*|]*/() { + return Promise.resolve().then(() => ({ x: 3 })).catch(() => ({ x: "a" })).then(({ x }) => !!x); +} + +// ==ASYNC FUNCTION::Convert to async function== + +async function f() { + let result: { x: number; } | { x: string; }; + try { + await Promise.resolve(); + result = ({ x: 3 }); + } + catch (e) { + result = ({ x: "a" }); + } + const { x } = result; + return !!x; +} diff --git a/tests/cases/user/TypeScript-Node-Starter/TypeScript-Node-Starter b/tests/cases/user/TypeScript-Node-Starter/TypeScript-Node-Starter index 46971a84547..40bdb4eadab 160000 --- a/tests/cases/user/TypeScript-Node-Starter/TypeScript-Node-Starter +++ b/tests/cases/user/TypeScript-Node-Starter/TypeScript-Node-Starter @@ -1 +1 @@ -Subproject commit 46971a8454761f1a11d8fde4d96ff8d29bc4e754 +Subproject commit 40bdb4eadabc9fbed7d83e3f26817a931c0763b6 diff --git a/tests/cases/user/create-react-app/create-react-app b/tests/cases/user/create-react-app/create-react-app index 9514cb88ab9..1a61db58d43 160000 --- a/tests/cases/user/create-react-app/create-react-app +++ b/tests/cases/user/create-react-app/create-react-app @@ -1 +1 @@ -Subproject commit 9514cb88ab92fec7f5df2914702ef23a62c0a249 +Subproject commit 1a61db58d434d33603f20e73ca643ec83c561b73 diff --git a/tests/cases/user/prettier/prettier b/tests/cases/user/prettier/prettier index 0b07e108333..1e471a00796 160000 --- a/tests/cases/user/prettier/prettier +++ b/tests/cases/user/prettier/prettier @@ -1 +1 @@ -Subproject commit 0b07e1083339e28a8239df3f5245f530cc4fd7f8 +Subproject commit 1e471a007968b7490563b91ed6909ae6046f3fe8 From 95fb694eedb5465596ab0408f235f8f49bf7b6c5 Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Fri, 19 Apr 2019 08:58:29 -0700 Subject: [PATCH 11/11] Consistify tabs/spaces in test and baselines --- src/testRunner/unittests/services/convertToAsyncFunction.ts | 4 ++-- .../convertToAsyncFunction_catchBlockUniqueParams.js | 4 ++-- .../convertToAsyncFunction_catchBlockUniqueParams.ts | 4 ++-- ...ertToAsyncFunction_catchBlockUniqueParamsBindingPattern.js | 4 ++-- ...ertToAsyncFunction_catchBlockUniqueParamsBindingPattern.ts | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/testRunner/unittests/services/convertToAsyncFunction.ts b/src/testRunner/unittests/services/convertToAsyncFunction.ts index b5eb5cf37d0..82a404297a3 100644 --- a/src/testRunner/unittests/services/convertToAsyncFunction.ts +++ b/src/testRunner/unittests/services/convertToAsyncFunction.ts @@ -1230,13 +1230,13 @@ const { length } = [#|function|] () { _testConvertToAsyncFunction("convertToAsyncFunction_catchBlockUniqueParams", ` function [#|f|]() { - return Promise.resolve().then(x => 1).catch(x => "a").then(x => !!x); + return Promise.resolve().then(x => 1).catch(x => "a").then(x => !!x); } `); _testConvertToAsyncFunction("convertToAsyncFunction_catchBlockUniqueParamsBindingPattern", ` function [#|f|]() { - return Promise.resolve().then(() => ({ x: 3 })).catch(() => ({ x: "a" })).then(({ x }) => !!x); + return Promise.resolve().then(() => ({ x: 3 })).catch(() => ({ x: "a" })).then(({ x }) => !!x); } `); diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_catchBlockUniqueParams.js b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_catchBlockUniqueParams.js index fa5d3babf72..8bf267e91b5 100644 --- a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_catchBlockUniqueParams.js +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_catchBlockUniqueParams.js @@ -1,13 +1,13 @@ // ==ORIGINAL== function /*[#|*/f/*|]*/() { - return Promise.resolve().then(x => 1).catch(x => "a").then(x => !!x); + return Promise.resolve().then(x => 1).catch(x => "a").then(x => !!x); } // ==ASYNC FUNCTION::Convert to async function== async function f() { - let x_2; + let x_2; try { const x = await Promise.resolve(); x_2 = 1; diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_catchBlockUniqueParams.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_catchBlockUniqueParams.ts index 15ec7ce93cf..20807ef140c 100644 --- a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_catchBlockUniqueParams.ts +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_catchBlockUniqueParams.ts @@ -1,13 +1,13 @@ // ==ORIGINAL== function /*[#|*/f/*|]*/() { - return Promise.resolve().then(x => 1).catch(x => "a").then(x => !!x); + return Promise.resolve().then(x => 1).catch(x => "a").then(x => !!x); } // ==ASYNC FUNCTION::Convert to async function== async function f() { - let x_2: string | number; + let x_2: string | number; try { const x = await Promise.resolve(); x_2 = 1; diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_catchBlockUniqueParamsBindingPattern.js b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_catchBlockUniqueParamsBindingPattern.js index f1c5974ebc3..628a16da1a9 100644 --- a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_catchBlockUniqueParamsBindingPattern.js +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_catchBlockUniqueParamsBindingPattern.js @@ -1,13 +1,13 @@ // ==ORIGINAL== function /*[#|*/f/*|]*/() { - return Promise.resolve().then(() => ({ x: 3 })).catch(() => ({ x: "a" })).then(({ x }) => !!x); + return Promise.resolve().then(() => ({ x: 3 })).catch(() => ({ x: "a" })).then(({ x }) => !!x); } // ==ASYNC FUNCTION::Convert to async function== async function f() { - let result; + let result; try { await Promise.resolve(); result = ({ x: 3 }); diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_catchBlockUniqueParamsBindingPattern.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_catchBlockUniqueParamsBindingPattern.ts index 96ac53d5a8f..ff7ac4bc944 100644 --- a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_catchBlockUniqueParamsBindingPattern.ts +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_catchBlockUniqueParamsBindingPattern.ts @@ -1,13 +1,13 @@ // ==ORIGINAL== function /*[#|*/f/*|]*/() { - return Promise.resolve().then(() => ({ x: 3 })).catch(() => ({ x: "a" })).then(({ x }) => !!x); + return Promise.resolve().then(() => ({ x: 3 })).catch(() => ({ x: "a" })).then(({ x }) => !!x); } // ==ASYNC FUNCTION::Convert to async function== async function f() { - let result: { x: number; } | { x: string; }; + let result: { x: number; } | { x: string; }; try { await Promise.resolve(); result = ({ x: 3 });