├── .babelrc.js ├── .circleci └── config.yml ├── .eslintrc ├── .gitignore ├── .npmignore ├── .vscode └── snippets.code-snippets ├── LICENSE.md ├── README.md ├── bin └── index.js ├── package.json ├── scripts ├── example.babel.js └── example.ts ├── src ├── asyncify.ts ├── index.ts ├── noRecastWorkaround.ts └── util │ ├── babelBugWorkarounds.ts │ ├── builders.ts │ ├── canDefinitelyInvoke.ts │ ├── canUnwindAsIs.ts │ ├── codeLength.ts │ ├── convertBodyToBlockStatement.ts │ ├── convertConditionalReturns.ts │ ├── finalCleanup.ts │ ├── findNode.ts │ ├── findPromiseChains.ts │ ├── getCatchHandler.ts │ ├── getFinallyHandler.ts │ ├── getOutputIdentifier.ts │ ├── getPreceedingLink.ts │ ├── getThenHandler.ts │ ├── hasMutableIdentifiers.ts │ ├── hasReturnStatements.ts │ ├── insertStatementsBefore.ts │ ├── isGetterOrSetter.ts │ ├── iterateChain.ts │ ├── mergeCatchIntoFinally.ts │ ├── mergeStatementsIntoTryFinally.ts │ ├── parentStatement.ts │ ├── predicates.ts │ ├── prependBodyStatement.ts │ ├── recastBugWorkarounds.ts │ ├── removeRestOfBlockStatement.ts │ ├── renameBoundIdentifiers.ts │ ├── replaceLink.ts │ ├── replaceReturnStatements.ts │ ├── replaceWithImmediatelyInvokedAsyncArrowFunction.ts │ ├── replaceWithStatements.ts │ ├── returnsOrAwaitsPromises.ts │ ├── shouldIgnoreChain.ts │ ├── unboundIdentifier.ts │ ├── unwindCatch.ts │ ├── unwindFinally.ts │ ├── unwindPromiseChain.ts │ └── unwindThen.ts ├── test ├── .eslintrc ├── clearConsole.ts ├── configure.js ├── dump.ts ├── fixtures │ ├── bugs_BelongsToMany.create.ts │ ├── bugs_GetterAndSetterCannotBeAsync.ts │ ├── bugs_PostgresConnectionManager.connect.ts │ ├── bugs_Sequelize.query.ts │ ├── bugs_Sequelize.transaction.ts │ ├── bugs_canDefinitelyInvoke_infiniteLoop.ts │ ├── bugs_findAll.test.ts │ ├── bugs_promiseProps.ts │ ├── bugs_reassignedHandlerArgument.ts │ ├── bugs_validateAndRunHooks.ts │ ├── catchExpressionBody_IgnoredError.ts │ ├── catchExpressionBody_Returned.ts │ ├── catch_AssignedToDestructuring.ts │ ├── catch_AssignedToIdentifier.ts │ ├── catch_AssignedToVariableDeclarator.ts │ ├── catch_NestedExpression.ts │ ├── catch_Returned.ts │ ├── catch_Swallow.ts │ ├── catch_Unconsumed.ts │ ├── catch_handlersThatCanBeUnwound_Returned.ts │ ├── catch_handlersThatCantBeUnwound.ts │ ├── catch_lastHandlerCanAlwaysBeUnwound.ts │ ├── conditionalReturnCatch_AssignedToIdentifier.ts │ ├── conditionalReturnCatch_ElseFallsThrough.ts │ ├── conditionalReturnCatch_FirstConsequentAndMissingElseFallThrough.ts │ ├── conditionalReturnCatch_FirstConsequentFallsThrough.ts │ ├── conditionalReturnCatch_FirstConsequentFallsThrough_flow.ts │ ├── conditionalReturnCatch_FirstConsequentFallsThrough_ts.ts │ ├── conditionalReturnCatch_MissingElseFallsThrough.ts │ ├── conditionalReturnCatch_Returned.ts │ ├── conditionalReturnCatch_SecondConsequentFallsThrough.ts │ ├── conditionalReturnCatch_SeveralBranchesFallThrough copy.ts │ ├── finalReturnUndefined.ts │ ├── finallyIdentifier_Returned.ts │ ├── finally_AssignedToDestructuring.ts │ ├── finally_AssignedToIdentifier.ts │ ├── finally_AssignedToVariableDeclarator.ts │ ├── finally_NestedExpression.ts │ ├── finally_Returned.ts │ ├── finally_Unconsumed.ts │ ├── finally_handlersThatCanBeUnwound_Returned.ts │ ├── finally_handlersThatCantBeUnwound.ts │ ├── finally_lastHandlerCantAlwaysBeUnwound.ts │ ├── ignoreChainsShorterThan.ts │ ├── ignoredSimpleChain.ts │ ├── leadingComment.ts │ ├── middleReturnUndefined.ts │ ├── moreScopeTests.ts │ ├── nonAwaitedChain.ts │ ├── replacePromise.reject.ts │ ├── replacePromise.resolve.ts │ ├── thenCatchFinally.ts │ ├── thenCatchFinally_flow.ts │ ├── thenCatchFinally_ts.ts │ ├── thenCatch_AssignedToDestructuring.ts │ ├── thenCatch_AssignedToDestructuring_flow.ts │ ├── thenCatch_AssignedToDestructuring_ts.ts │ ├── thenCatch_AssignedToIdentifier.ts │ ├── thenCatch_AssignedToVariableDeclarator.ts │ ├── thenCatch_NestedExpression.ts │ ├── thenCatch_Returned.ts │ ├── thenCatch_Unconsumed.ts │ ├── thenChain.ts │ ├── thenExpressionBody_AssignedToDestructuring.ts │ ├── thenExpressionBody_AssignedToIdentifier.ts │ ├── thenExpressionBody_AssignedToVariableDeclarator.ts │ ├── thenExpressionBody_IgnoredInput.ts │ ├── thenExpressionBody_Returned.ts │ ├── thenExpressionBody_Unconsumed.ts │ ├── thenExpression_Unconsumed.ts │ ├── thenIdentifier_AssignedToVariableDeclarator.ts │ ├── thenIdentifier_Returned.ts │ ├── thenIdentifier_Unconsumed.ts │ ├── thenUndefinedCatch_Returned.ts │ ├── then_AssignedToDestructuring.ts │ ├── then_AssignedToIdentifier.ts │ ├── then_AssignedToVariableDeclarator.ts │ ├── then_DestructuredInput.ts │ ├── then_DestructuredInput_flow.ts │ ├── then_DestructuredInput_ts.ts │ ├── then_FinalHandlerAsIs.ts │ ├── then_MutableDestructuredInput.ts │ ├── then_NestedExpression.ts │ ├── then_NestedFunction.ts │ ├── then_RenamingDestructuredInput.ts │ ├── then_Returned.ts │ ├── then_ReturningFunction.ts │ ├── then_Unconsumed.ts │ ├── then_handlersThatCanBeUnwound_AssignedToIdentifier.ts │ ├── then_handlersThatCanBeUnwound_Returned.ts │ ├── then_handlersThatCantBeUnwound.ts │ ├── then_lastHandlerCanAlwaysBeUnwound.ts │ └── then_tryCatchInHandler.ts ├── index.spec.ts └── testFixtures.ts ├── tsconfig.json └── yarn.lock /.babelrc.js: -------------------------------------------------------------------------------- 1 | module.exports = function(api) { 2 | const plugins = [ 3 | '@babel/plugin-syntax-dynamic-import', 4 | '@babel/plugin-proposal-class-properties', 5 | '@babel/plugin-proposal-export-default-from', 6 | '@babel/plugin-proposal-export-namespace-from', 7 | '@babel/plugin-proposal-object-rest-spread', 8 | '@babel/plugin-proposal-optional-chaining', 9 | '@babel/plugin-proposal-nullish-coalescing-operator', 10 | ] 11 | const presets = [ 12 | [ 13 | '@babel/preset-env', 14 | api.env('es5') 15 | ? { forceAllTransforms: true } 16 | : { targets: { node: 'current' } }, 17 | ], 18 | '@babel/preset-typescript', 19 | ] 20 | 21 | if (api.env(['test', 'coverage', 'es5'])) { 22 | plugins.push('@babel/plugin-transform-runtime') 23 | } 24 | if (api.env('coverage')) { 25 | plugins.push('babel-plugin-istanbul') 26 | } 27 | 28 | return { plugins, presets } 29 | } 30 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | jobs: 3 | build: 4 | docker: 5 | - image: circleci/node:10 6 | 7 | steps: 8 | - checkout 9 | - restore_cache: 10 | name: Restore Yarn Package Cache 11 | keys: 12 | - v1-yarn-packages-{{ checksum "yarn.lock" }} 13 | 14 | - run: 15 | name: Setup NPM Token 16 | command: | 17 | yarn config set registry "https://registry.npmjs.org/" 18 | echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" > .npmrc 19 | echo "registry=https://registry.npmjs.org/" >> .npmrc 20 | 21 | - run: 22 | name: Install Dependencies 23 | command: yarn install --frozen-lockfile 24 | - save_cache: 25 | name: Save Yarn Package Cache 26 | key: v1-yarn-packages-{{ checksum "yarn.lock" }} 27 | paths: 28 | - ~/.cache/yarn 29 | 30 | - run: 31 | name: build 32 | command: yarn run prepublishOnly 33 | - run: 34 | name: upload test coverage 35 | command: yarn codecov || true 36 | - run: 37 | name: release 38 | command: yarn run semantic-release || true 39 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "@jedwards1211/eslint-config-typescript", 4 | "eslint-config-prettier" 5 | ], 6 | "env": { 7 | "node": true 8 | }, 9 | "rules": { 10 | "@typescript-eslint/no-explicit-any": 0 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | coverage 2 | .nyc_output 3 | node_modules 4 | es 5 | .eslintcache 6 | /*.js 7 | /*.js.flow 8 | /*.d.ts 9 | !/.babelrc.js 10 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | ** 2 | !**/*.js 3 | !**/*.js.flow 4 | !**/*.d.ts 5 | !/*.md 6 | !yarn.lock 7 | /src 8 | /test 9 | /scripts 10 | /coverage 11 | /flow-typed 12 | __tests__ 13 | /.* 14 | -------------------------------------------------------------------------------- /.vscode/snippets.code-snippets: -------------------------------------------------------------------------------- 1 | { 2 | "transform": { 3 | "prefix": "transform", 4 | "description": "JSCodeshift transform", 5 | "body": [ 6 | "import { ASTPath, Node, FileInfo, API, Options } from 'jscodeshift'", 7 | "import pathsInRange from 'jscodeshift-paths-in-range'", 8 | "", 9 | "type Filter = (", 10 | " path: ASTPath,", 11 | " index: number,", 12 | " paths: Array>", 13 | ") => boolean", 14 | "", 15 | "module.exports = function ${TM_FILENAME_BASE}(", 16 | " fileInfo: FileInfo,", 17 | " api: API,", 18 | " options: Options", 19 | "): string | null | undefined | void {", 20 | " const j = api.jscodeshift", 21 | "", 22 | " const root = j(fileInfo.source)", 23 | "", 24 | " let filter: Filter", 25 | " if (options.selectionStart) {", 26 | " const selectionStart = parseInt(options.selectionStart)", 27 | " const selectionEnd = options.selectionEnd", 28 | " ? parseInt(options.selectionEnd)", 29 | " : selectionStart", 30 | " filter = pathsInRange(selectionStart, selectionEnd)", 31 | " } else {", 32 | " filter = (): boolean => true", 33 | " }", 34 | "", 35 | " return root.toSource()", 36 | "}", 37 | "" 38 | ] 39 | }, 40 | "fixture": { 41 | "prefix": "fixture", 42 | "description": "JSCodeshift transform test fixture", 43 | "body": [ 44 | "export const input = `", 45 | "`", 46 | "", 47 | "export const options = {", 48 | "}", 49 | "", 50 | "export const expected = `", 51 | "`" 52 | ] 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016-present Andy Edwards 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # @codemodsquad/asyncify 2 | 3 | [![CircleCI](https://circleci.com/gh/codemodsquad/asyncify.svg?style=svg)](https://circleci.com/gh/codemodsquad/asyncify) 4 | [![Coverage Status](https://codecov.io/gh/codemodsquad/asyncify/branch/master/graph/badge.svg)](https://codecov.io/gh/codemodsquad/asyncify) 5 | [![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release) 6 | [![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg)](http://commitizen.github.io/cz-cli/) 7 | [![npm version](https://badge.fury.io/js/%40codemodsquad%2Fasyncify.svg)](https://badge.fury.io/js/%40codemodsquad%2Fasyncify) 8 | 9 | Transforms promise chains into `async`/`await`. I wrote this to refactor the 5000+ `.then`/`.catch`/`.finally` calls in the 10 | `sequelize` codebase. This is slightly inspired by [async-await-codemod](https://github.com/sgilroy/async-await-codemod), 11 | but written from scratch to guarantee that it doesn't change the behavior of the transformed code, and keeps the code reasonably tidy. 12 | 13 | ## Usage 14 | 15 | ``` 16 | npx @codemodsquad/asyncify path/to/your/project/**/*.js 17 | ``` 18 | 19 | This command just forwards to `jscodeshift`, you can pass other `jscodeshift` CLI options. 20 | 21 | ## Support table 22 | 23 | | | `asyncify` | 24 | | ------------------------------------------------------------------ | ---------- | 25 | | Converts `.then` | ✅ | 26 | | Converts `.catch` | ✅ | 27 | | Converts `.finally` | ✅ | 28 | | Renames identifiers in handlers that would conflict | ✅ | 29 | | Converts promise chains that aren't returned/awaited into IIAAFs | ✅ | 30 | | Converts `return Promise.resolve()`/`return Promise.reject()` | ✅ | 31 | | Removes unnecessary `Promise.resolve()` wrappers | ✅ | 32 | | Warns when the original function could return/throw a non-promise | Planned | 33 | | **Refactoring/inlining handlers that contain conditional returns** | | 34 | | All but one if/else/switch branch return | ✅ | 35 | | All branches return, even nested ones | ✅ | 36 | | All but one nested if/else/switch branch return | 🚫 | 37 | | More than one if/else/switch branch doesn't return | 🚫 | 38 | | Return inside loop | 🚫 | 39 | 40 | ## Warnings 41 | 42 | Comments can sometimes get deleted due to an impedance mismatch between `@babel` and `recast` 43 | ASTs. If you use the `--commentWorkarounds=true` option it will try to prevent more comments 44 | from getting deleted but it sometimes causes an assertion to fail in `recast`. 45 | 46 | There are a few edge cases where `asyncify` produces funky output. It's intended to not break 47 | any existing behavior (I know of no cases where it does, and I have fixed several such issues) 48 | but sometimes the output will be be semantically wrong even if it behaves 49 | correctly. For example, I've seen a case where doing an async operation several times in a row: 50 | 51 | ```js 52 | it('test', () => { 53 | const doSomething = () => { 54 | // ... 55 | } 56 | 57 | return doSomething() 58 | .then(doSomething) 59 | .then(doSomething) 60 | }) 61 | ``` 62 | 63 | Gets converted to: 64 | 65 | ```js 66 | it('test', async () => { 67 | const doSomething = () => { 68 | // ... 69 | } 70 | await doSomething(await doSomething(await doSomething())) 71 | }) 72 | ``` 73 | 74 | This works even though it initially seems like it wouldn't and is obviously not what you want: 75 | 76 | ```js 77 | it('test', async () => { 78 | const doSomething = () => { 79 | // ... 80 | } 81 | await doSomething() 82 | await doSomething() 83 | await doSomething() 84 | }) 85 | ``` 86 | 87 | Although I could possibly fix this for cases where it's easy to determine that the function has 88 | no parameters, there could be cases where it's impossible to determine whether the identifier 89 | `doSomething` is even a function or whether it has parameters. 90 | 91 | ## Disabling `recast` workaround 92 | 93 | At the time I wrote `asyncify`, there were some show-stopping bugs in old version of `recast` that 94 | `jscodeshift` depended on. To avoid this problem, `asyncify` parses with a newer version of `recast` in its 95 | own dependencies, instead of parsing with the `jscodeshift` API. The author of `putout` has asked to be able 96 | to parse with the injected `jscodeshift` API for performance, so you can access that version of the 97 | `jscodeshift` transform as: 98 | 99 | ```js 100 | import transform from '@codemodsquad/asyncify/noRecastWorkaround' 101 | ``` 102 | 103 | Or there are two ways you can do it when running via `jscodeshift`: 104 | 105 | ``` 106 | jscodeshift -t path/to/asyncify/noRecastWorkaround.js 107 | jscodeshift -t path/to/asyncify/index.js --noRecastWorkaround=true 108 | ``` 109 | -------------------------------------------------------------------------------- /bin/index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const child = require('child_process').spawn( 4 | 'jscodeshift', 5 | ['-t', require.resolve('..'), ...process.argv.slice(2)], 6 | { stdio: 'inherit' } 7 | ) 8 | 9 | child.on('error', error => { 10 | // eslint-disable-next-line no-console 11 | console.error(error.stack) 12 | process.exit(1) 13 | }) 14 | child.on('close', (code, signal) => { 15 | if (code != 0) { 16 | process.exit(code) 17 | } 18 | if (signal) { 19 | process.exit(1) 20 | } 21 | process.exit(0) 22 | }) 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@codemodsquad/asyncify", 3 | "version": "0.0.0-development", 4 | "description": "transforms promise chains into async/await", 5 | "sideEffects": false, 6 | "bin": "./bin/index.js", 7 | "scripts": { 8 | "lint": "eslint $npm_package_config_lint", 9 | "lint:fix": "eslint $npm_package_config_lint", 10 | "lint:watch": "esw --watch $npm_package_config_lint", 11 | "prettier": "prettier --write .babelrc.js *.json *.md *.ts '{src,test}/**/*.{js,ts}'", 12 | "prettier:check": "prettier --list-different .babelrc.js *.json *.md *.ts '{src,test}/**/*.{js,ts}'", 13 | "tsc": "tsc --noEmit", 14 | "tsc:watch": "npm run tsc -- --watch", 15 | "clean": "rimraf es lib $(cd src; ls) *.js *.d.ts", 16 | "build": "npm run clean && npm run build:types && npm run build:js", 17 | "build:types": "tsc --emitDeclarationOnly", 18 | "build:js": "babel src --out-dir es --extensions \".ts\" --source-maps inline && cross-env BABEL_ENV=es5 babel src --out-dir . --extensions \".ts\"", 19 | "build:watch": "babel ./src ./test --out-dir dist/ --extensions \".ts\" --source-maps inline --watch", 20 | "test": "cross-env NODE_ENV=test BABEL_ENV=es5 mocha $npm_package_config_mocha && cross-env NODE_ENV=test BABEL_ENV=coverage nyc --reporter=lcov --reporter=text mocha $npm_package_config_mocha", 21 | "test:coverage": "BABEL_ENV=coverage nyc --reporter=lcov --reporter=text mocha $npm_package_config_mocha", 22 | "test:watch": "cross-env NODE_ENV=test BABEL_ENV=test mocha $npm_package_config_mocha --watch --watch-extensions js,ts", 23 | "test:debug": "cross-env NODE_ENV=test BABEL_ENV=test mocha --inspect-brk $npm_package_config_mocha", 24 | "codecov": "nyc report --reporter=text-lcov > coverage.lcov; codecov", 25 | "prepublishOnly": "npm run clean && npm run prettier:check && npm run lint && npm test && npm run build", 26 | "open:coverage": "open coverage/lcov-report/index.html", 27 | "semantic-release": "semantic-release", 28 | "example": "node scripts/example.babel.js" 29 | }, 30 | "config": { 31 | "lint": "--cache --ext .js,.ts src test", 32 | "mocha": "test/configure.js 'src/**/*.spec.ts' 'test/**/*.spec.ts'", 33 | "commitizen": { 34 | "path": "cz-conventional-changelog" 35 | } 36 | }, 37 | "publishConfig": { 38 | "access": "public" 39 | }, 40 | "husky": { 41 | "hooks": { 42 | "pre-commit": "lint-staged && npm run lint && npm run tsc", 43 | "commit-msg": "commitlint -e $GIT_PARAMS", 44 | "pre-push": "npm test" 45 | } 46 | }, 47 | "lint-staged": { 48 | "*.{js,ts,json,css,md}": [ 49 | "prettier --write", 50 | "git add" 51 | ] 52 | }, 53 | "commitlint": { 54 | "extends": [ 55 | "@jedwards1211/commitlint-config" 56 | ] 57 | }, 58 | "prettier": { 59 | "semi": false, 60 | "singleQuote": true, 61 | "trailingComma": "es5" 62 | }, 63 | "nyc": { 64 | "include": [ 65 | "src/**/*.ts" 66 | ], 67 | "exclude": [ 68 | "src/**/*.spec.ts" 69 | ], 70 | "require": [ 71 | "@babel/register" 72 | ], 73 | "sourceMap": false, 74 | "instrument": false 75 | }, 76 | "repository": { 77 | "type": "git", 78 | "url": "https://github.com/codemodsquad/asyncify.git" 79 | }, 80 | "keywords": [ 81 | "async", 82 | "await", 83 | "promise", 84 | "chain", 85 | "then", 86 | "thenable", 87 | "codemod", 88 | "refactoring" 89 | ], 90 | "author": "Andy Edwards", 91 | "license": "MIT", 92 | "bugs": { 93 | "url": "https://github.com/codemodsquad/asyncify/issues" 94 | }, 95 | "homepage": "https://github.com/codemodsquad/asyncify#readme", 96 | "devDependencies": { 97 | "@babel/cli": "^7.12.13", 98 | "@babel/core": "^7.12.13", 99 | "@babel/plugin-proposal-class-properties": "^7.12.13", 100 | "@babel/plugin-proposal-export-default-from": "^7.12.13", 101 | "@babel/plugin-proposal-export-namespace-from": "^7.12.13", 102 | "@babel/plugin-proposal-nullish-coalescing-operator": "^7.12.13", 103 | "@babel/plugin-proposal-object-rest-spread": "^7.12.13", 104 | "@babel/plugin-proposal-optional-chaining": "^7.12.13", 105 | "@babel/plugin-syntax-dynamic-import": "^7.0.0", 106 | "@babel/plugin-transform-runtime": "^7.12.15", 107 | "@babel/preset-env": "^7.12.13", 108 | "@babel/preset-typescript": "^7.12.13", 109 | "@babel/register": "^7.12.13", 110 | "@commitlint/cli": "^6.0.2", 111 | "@commitlint/config-conventional": "^6.0.2", 112 | "@jedwards1211/commitlint-config": "^1.0.0", 113 | "@jedwards1211/eslint-config-typescript": "^1.0.0", 114 | "@types/babel__generator": "^7.6.2", 115 | "@types/babel__template": "^7.4.0", 116 | "@types/babel__traverse": "^7.11.0", 117 | "@types/chai": "^4.2.0", 118 | "@types/chai-subset": "^1.3.3", 119 | "@types/chalk": "^2.2.0", 120 | "@types/find-root": "^1.1.1", 121 | "@types/jscodeshift": "^0.6.3", 122 | "@types/mocha": "^5.2.7", 123 | "@types/node": "^13.7.4", 124 | "@types/prettier": "^1.19.0", 125 | "babel-plugin-istanbul": "^6.0.0", 126 | "chai": "^4.2.0", 127 | "chai-subset": "^1.6.0", 128 | "chalk": "^4.0.0", 129 | "codecov": "^3.1.0", 130 | "copy": "^0.3.2", 131 | "cross-env": "^5.2.0", 132 | "eslint": "^5.9.0", 133 | "eslint-config-prettier": "^3.3.0", 134 | "eslint-watch": "^4.0.2", 135 | "husky": "^1.1.4", 136 | "istanbul": "^0.4.5", 137 | "jscodeshift": "^0.7.0", 138 | "lint-staged": "^8.0.4", 139 | "mocha": "^6.2.1", 140 | "nyc": "^13.1.0", 141 | "pkg-conf": "^3.1.0", 142 | "prettier": "^1.15.2", 143 | "prettier-eslint": "^8.8.2", 144 | "print-highlighted-ast": "^1.0.0", 145 | "promisify-child-process": "^3.1.4", 146 | "require-glob": "^3.2.0", 147 | "rimraf": "^2.6.0", 148 | "semantic-release": "^15.1.4", 149 | "typescript": "^3.7.2" 150 | }, 151 | "dependencies": { 152 | "@babel/generator": "^7.12.15", 153 | "@babel/runtime": "^7.12.13", 154 | "@babel/template": "^7.12.13", 155 | "@babel/traverse": "^7.12.13", 156 | "@babel/types": "^7.12.13", 157 | "recast": "^0.19.0" 158 | }, 159 | "renovate": { 160 | "extends": [ 161 | ":separateMajorReleases", 162 | ":combinePatchMinorReleases", 163 | ":ignoreUnstable", 164 | ":prImmediately", 165 | ":renovatePrefix", 166 | ":updateNotScheduled", 167 | ":preserveSemverRanges", 168 | ":semanticPrefixFixDepsChoreOthers", 169 | ":automergeDisabled", 170 | "group:monorepos" 171 | ], 172 | "automerge": true, 173 | "major": { 174 | "automerge": false 175 | } 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /scripts/example.babel.js: -------------------------------------------------------------------------------- 1 | require('@babel/register')({ extensions: ['.js', '.jsx', '.ts', '.tsx'] }) 2 | require('./example.ts') 3 | -------------------------------------------------------------------------------- /scripts/example.ts: -------------------------------------------------------------------------------- 1 | /* eslint disable */ 2 | 3 | const { name: pkgName } = require('../package.json') 4 | const [transformName, fixture] = process.argv.slice(2) 5 | if (!transformName || !fixture) { 6 | console.error('Usage: yarn example ') 7 | process.exit(1) 8 | } 9 | 10 | const { input, expected, file } = require(require.resolve( 11 | `../test/${transformName}/${fixture}.ts` 12 | )) 13 | 14 | const ext = file ? /^\.([^.]+)$/.exec(file)?.[1] || 'ts' : 'ts' 15 | 16 | console.log(`### Before 17 | 18 | \`\`\`${ext} 19 | ${input} 20 | \`\`\` 21 | 22 | ### Command 23 | \`\`\` 24 | jscodeshift -t path/to/${pkgName}/${transformName}.js 25 | \`\`\` 26 | 27 | ### After 28 | 29 | \`\`\`${ext} 30 | ${expected} 31 | \`\`\` 32 | `) 33 | -------------------------------------------------------------------------------- /src/asyncify.ts: -------------------------------------------------------------------------------- 1 | import * as t from '@babel/types' 2 | import { NodePath } from '@babel/traverse' 3 | 4 | import returnsOrAwaitsPromises from './util/returnsOrAwaitsPromises' 5 | import { isPromiseMethodCall } from './util/predicates' 6 | import findPromiseChains from './util/findPromiseChains' 7 | import unwindPromiseChain from './util/unwindPromiseChain' 8 | import finalCleanup from './util/finalCleanup' 9 | import codeLength from './util/codeLength' 10 | import babelBugWorkarounds from './util/babelBugWorkarounds' 11 | import isGetterOrSetter from './util/isGetterOrSetter' 12 | 13 | function asyncifyFunction(path: NodePath): void { 14 | if (returnsOrAwaitsPromises(path) && !isGetterOrSetter(path)) { 15 | path.node.async = true 16 | } 17 | const chains = findPromiseChains(path) 18 | for (const chain of chains) { 19 | unwindPromiseChain(chain) 20 | } 21 | if (chains.length || path.node.async) { 22 | finalCleanup(path) 23 | babelBugWorkarounds(path) 24 | } 25 | } 26 | 27 | export default function asyncify(path: NodePath): void { 28 | const functions: NodePath[] = [] 29 | const { ignoreChainsShorterThan } = path.state 30 | path.traverse( 31 | { 32 | Function(path: NodePath): void { 33 | functions.push(path) 34 | }, 35 | CallExpression(path: NodePath) { 36 | if (isPromiseMethodCall(path.node)) { 37 | if (codeLength(path) < ignoreChainsShorterThan) { 38 | path.skip() 39 | } 40 | } 41 | }, 42 | }, 43 | path.state 44 | ) 45 | let fn 46 | while ((fn = functions.pop())) asyncifyFunction(fn) 47 | } 48 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { FileInfo, API, Options } from 'jscodeshift' 2 | import traverse, { NodePath } from '@babel/traverse' 3 | import * as t from '@babel/types' 4 | import asyncify from './asyncify' 5 | import * as recast from 'recast' 6 | import recastBugWorkarounds from './util/recastBugWorkarounds' 7 | 8 | module.exports = function index( 9 | fileInfo: FileInfo, 10 | api: API, 11 | options: Options 12 | ): string | null | undefined | void { 13 | const ast = options.noRecastWorkaround 14 | ? api.jscodeshift(fileInfo.source).get().value 15 | : recast.parse(fileInfo.source, { 16 | parser: require('recast/parsers/babel'), 17 | }) 18 | 19 | const ignoreChainsShorterThan = parseInt(options.ignoreChainsShorterThan) 20 | const commentWorkarounds = options.commentWorkarounds 21 | 22 | let program: NodePath | undefined 23 | traverse( 24 | ast, 25 | { 26 | Program(path: NodePath) { 27 | program = path 28 | path.stop() 29 | }, 30 | }, 31 | undefined, 32 | { ignoreChainsShorterThan, commentWorkarounds } 33 | ) 34 | if (!program) throw new Error('failed to find Program node') 35 | asyncify(program) 36 | recastBugWorkarounds(program) 37 | return recast.print(ast).code 38 | } 39 | -------------------------------------------------------------------------------- /src/noRecastWorkaround.ts: -------------------------------------------------------------------------------- 1 | import { API, FileInfo, Options } from 'jscodeshift' 2 | 3 | module.exports = function( 4 | fileInfo: FileInfo, 5 | api: API, 6 | options: Options 7 | ): string | null | undefined | void { 8 | // eslint-disable-next-line @typescript-eslint/no-var-requires 9 | return require('./index')(fileInfo, api, { 10 | ...options, 11 | noRecastWorkaround: true, 12 | }) 13 | } 14 | -------------------------------------------------------------------------------- /src/util/babelBugWorkarounds.ts: -------------------------------------------------------------------------------- 1 | import * as t from '@babel/types' 2 | import { NodePath } from '@babel/traverse' 3 | 4 | export default function babelBugWorkarounds(path: NodePath): void { 5 | path.traverse({ 6 | ObjectProperty(path: NodePath) { 7 | const { node } = path 8 | if ( 9 | node.shorthand && 10 | node.key.type === 'Identifier' && 11 | node.value.type === 'Identifier' && 12 | node.key.name !== node.value.name 13 | ) { 14 | node.shorthand = false 15 | } 16 | }, 17 | }) 18 | } 19 | -------------------------------------------------------------------------------- /src/util/builders.ts: -------------------------------------------------------------------------------- 1 | import * as t from '@babel/types' 2 | import { needsAwait } from './predicates' 3 | 4 | export function awaited(node: T): t.Expression { 5 | return needsAwait(node) ? t.awaitExpression(node) : node 6 | } 7 | -------------------------------------------------------------------------------- /src/util/canDefinitelyInvoke.ts: -------------------------------------------------------------------------------- 1 | import * as t from '@babel/types' 2 | import { NodePath } from '@babel/traverse' 3 | 4 | export default function canDefinitelyInvoke( 5 | expr: NodePath 6 | ): boolean { 7 | if (expr.isIdentifier()) { 8 | let target: NodePath | undefined = expr as NodePath 9 | while (target) { 10 | if (target.isIdentifier()) { 11 | const nextTarget: NodePath | undefined = target.scope.getBinding( 12 | target.node.name 13 | )?.path 14 | if (nextTarget === target) break 15 | target = nextTarget 16 | } else if (target.isVariableDeclarator()) { 17 | target = (target as NodePath).get('init') 18 | } else { 19 | break 20 | } 21 | } 22 | return target ? target.isFunction() : false 23 | } 24 | return expr.isFunction() 25 | } 26 | -------------------------------------------------------------------------------- /src/util/canUnwindAsIs.ts: -------------------------------------------------------------------------------- 1 | import * as t from '@babel/types' 2 | import { NodePath } from '@babel/traverse' 3 | 4 | export default function canUnwindAsIs( 5 | path: NodePath 6 | ): boolean { 7 | let parent: NodePath = path.parentPath 8 | let child: NodePath = path 9 | while (parent && !parent.isFunction()) { 10 | if (parent.isBlockStatement()) { 11 | const body = (parent as NodePath).get('body') 12 | if (child !== body[body.length - 1]) return false 13 | } else if (parent.isLoop()) { 14 | return false 15 | } else if (parent.isSwitchCase()) { 16 | if ((parent.node as t.SwitchCase).test != null) return false 17 | } else if (!parent.isStatement() && !parent.isAwaitExpression()) { 18 | return false 19 | } 20 | child = parent 21 | parent = child.parentPath 22 | } 23 | return true 24 | } 25 | -------------------------------------------------------------------------------- /src/util/codeLength.ts: -------------------------------------------------------------------------------- 1 | import * as t from '@babel/types' 2 | import { NodePath } from '@babel/traverse' 3 | 4 | export default function codeLength( 5 | what: T | NodePath 6 | ): number { 7 | if (what instanceof NodePath) what = what.node 8 | const { start, end } = what 9 | return start != null && end != null ? end - start : NaN 10 | } 11 | -------------------------------------------------------------------------------- /src/util/convertBodyToBlockStatement.ts: -------------------------------------------------------------------------------- 1 | import * as t from '@babel/types' 2 | import { NodePath } from '@babel/traverse' 3 | 4 | export default function convertBodyToBlockStatement( 5 | func: NodePath 6 | ): NodePath { 7 | const body = func.get('body') as NodePath 8 | if (body.isBlockStatement()) return body 9 | return (body.replaceWith( 10 | t.blockStatement([t.returnStatement(body.node as t.Expression)]) 11 | ) as any)[0] 12 | } 13 | -------------------------------------------------------------------------------- /src/util/convertConditionalReturns.ts: -------------------------------------------------------------------------------- 1 | import * as t from '@babel/types' 2 | import { NodePath } from '@babel/traverse' 3 | import replaceWithStatements from './replaceWithStatements' 4 | import removeRestOfBlockStatement from './removeRestOfBlockStatement' 5 | import { isLastStatementInBlock } from './predicates' 6 | 7 | function hasReturn(path: NodePath): boolean { 8 | if (path.isReturnStatement()) return true 9 | if (path.isBlockStatement()) { 10 | for (const child of (path as NodePath).get('body')) { 11 | if (child.isReturnStatement()) return true 12 | } 13 | } 14 | return false 15 | } 16 | 17 | function splitBranches( 18 | path: NodePath 19 | ): { 20 | returning: NodePath[] 21 | notReturning: (NodePath | NodePath)[] 22 | } { 23 | const returning: NodePath[] = [] 24 | const notReturning: (NodePath | NodePath)[] = [] 25 | let p: NodePath | NodePath = path 26 | while (p.isIfStatement()) { 27 | const consequent = (p as NodePath).get('consequent') 28 | const alternate: NodePath = (p as NodePath).get( 29 | 'alternate' 30 | ) 31 | ;(hasReturn(consequent) ? returning : notReturning).push(consequent) 32 | if (!alternate.isIfStatement()) { 33 | ;(hasReturn(alternate) ? returning : notReturning).push(alternate) 34 | } 35 | 36 | p = alternate 37 | } 38 | return { returning, notReturning } 39 | } 40 | 41 | export default function convertConditionalReturns( 42 | parent: NodePath 43 | ): boolean { 44 | let ifDepth = 0 45 | let isUnwindable = true 46 | const ifStatements: NodePath[] = [] 47 | const returnStatements: NodePath[] = [] 48 | parent.traverse( 49 | { 50 | IfStatement: { 51 | enter(path: NodePath): void { 52 | if (path.parentPath.isIfStatement()) return 53 | ifDepth++ 54 | const { returning, notReturning } = splitBranches(path) 55 | if (returning.length > 0) { 56 | if (notReturning.length === 1) { 57 | ifStatements.push(path) 58 | } else if (notReturning.length > 1) { 59 | isUnwindable = false 60 | path.stop() 61 | return 62 | } 63 | } 64 | }, 65 | exit(path: NodePath): void { 66 | if (path.parentPath.isIfStatement()) return 67 | ifDepth-- 68 | }, 69 | }, 70 | ReturnStatement(path: NodePath) { 71 | let { parentPath } = path 72 | let loopDepth = 0 73 | while (parentPath && parentPath !== parent) { 74 | if (parentPath.isLoop()) loopDepth++ 75 | if ( 76 | loopDepth > 1 || 77 | (!isLastStatementInBlock(parentPath) && 78 | (!parentPath.isIfStatement() || ifDepth > 1)) 79 | ) { 80 | isUnwindable = false 81 | path.stop() 82 | return 83 | } 84 | ;({ parentPath } = parentPath) 85 | } 86 | returnStatements.push(path) 87 | }, 88 | Function(path: NodePath) { 89 | path.skip() 90 | }, 91 | }, 92 | parent.state 93 | ) 94 | if (!isUnwindable) return false 95 | let ifStatement 96 | while ((ifStatement = ifStatements.pop())) { 97 | const { 98 | notReturning: [branch], 99 | } = splitBranches(ifStatement) 100 | const rest = removeRestOfBlockStatement(ifStatement) 101 | if (branch.isBlockStatement()) 102 | (branch as NodePath).pushContainer('body', rest) 103 | else replaceWithStatements(branch, rest) 104 | } 105 | return true 106 | } 107 | -------------------------------------------------------------------------------- /src/util/finalCleanup.ts: -------------------------------------------------------------------------------- 1 | import * as t from '@babel/types' 2 | import { NodePath } from '@babel/traverse' 3 | import { 4 | isPromiseResolveCall, 5 | needsAwait, 6 | isPromiseRejectCall, 7 | isInTryBlock, 8 | isLastStatementInFunction, 9 | } from './predicates' 10 | import { awaited } from './builders' 11 | 12 | function unwrapPromiseResolves( 13 | node: t.Node | undefined 14 | ): t.Expression | undefined { 15 | while (node && isPromiseResolveCall(node)) { 16 | node = (node as t.CallExpression).arguments[0] 17 | } 18 | if (node && node.type === 'Identifier' && node.name === 'undefined') 19 | return undefined 20 | return node as t.Expression 21 | } 22 | 23 | function isEmptyBlock(path: NodePath): boolean { 24 | return path.isBlockStatement() && path.node.body.length === 0 25 | } 26 | 27 | export default function finalCleanup(path: NodePath): void { 28 | path.traverse( 29 | { 30 | IfStatement: { 31 | exit(path: NodePath): void { 32 | const consequent = path.get('consequent') 33 | const alternate = path.get('alternate') 34 | if (isEmptyBlock(consequent)) { 35 | if (alternate.node == null) { 36 | path.remove() 37 | } else if (isEmptyBlock(alternate)) { 38 | path.remove() 39 | } else { 40 | path.replaceWith( 41 | t.ifStatement( 42 | t.unaryExpression('!', path.node.test), 43 | alternate.node 44 | ) 45 | ) 46 | } 47 | } else if (isEmptyBlock(alternate)) { 48 | path.node.alternate = null 49 | alternate.remove() 50 | } 51 | }, 52 | }, 53 | AwaitExpression(path: NodePath) { 54 | const argument = path.get('argument') 55 | const { parentPath } = path 56 | if (argument.isCallExpression() && isPromiseResolveCall(argument)) { 57 | const value = unwrapPromiseResolves(argument.node) 58 | if ( 59 | parentPath.isExpressionStatement() && 60 | (!value || (!isInTryBlock(path) && !needsAwait(value as any))) 61 | ) { 62 | parentPath.remove() 63 | } else if (value) { 64 | argument.replaceWith(isInTryBlock(path) ? awaited(value) : value) 65 | } 66 | } else if (argument.isIdentifier()) { 67 | const binding = path.scope.getBinding(argument.node.name) 68 | if ( 69 | binding && 70 | binding.constant && 71 | binding.path.isVariableDeclarator() && 72 | (binding.path as NodePath) 73 | .get('init') 74 | .isAwaitExpression() 75 | ) { 76 | if (parentPath.isExpressionStatement()) { 77 | path.remove() 78 | } else { 79 | path.replaceWith(argument.node) 80 | } 81 | } 82 | } 83 | }, 84 | ReturnStatement(path: NodePath) { 85 | const argument = path.get('argument') 86 | const value = argument.isAwaitExpression() 87 | ? (argument as NodePath).get('argument') 88 | : argument 89 | if (value.isIdentifier() && value.node.name === 'undefined') { 90 | argument.remove() 91 | } else if (value.isCallExpression() && isPromiseResolveCall(value)) { 92 | const unwrapped = unwrapPromiseResolves(value.node) 93 | if (unwrapped) { 94 | value.replaceWith( 95 | isInTryBlock(path) && argument.isAwaitExpression() 96 | ? awaited(unwrapped) 97 | : unwrapped 98 | ) 99 | } else if (isLastStatementInFunction(path)) { 100 | path.remove() 101 | } else { 102 | argument.remove() 103 | } 104 | } else if (value.isCallExpression() && isPromiseRejectCall(value)) { 105 | const argument = value.node.arguments[0] 106 | if (t.isExpression(argument)) { 107 | path.replaceWith(t.throwStatement(argument)) 108 | } 109 | } else if (argument.isAwaitExpression() && !isInTryBlock(path)) { 110 | argument.replaceWith(argument.node.argument) 111 | } 112 | }, 113 | Function(path: NodePath) { 114 | path.skip() 115 | }, 116 | }, 117 | path.state 118 | ) 119 | } 120 | -------------------------------------------------------------------------------- /src/util/findNode.ts: -------------------------------------------------------------------------------- 1 | import * as t from '@babel/types' 2 | import { NodePath } from '@babel/traverse' 3 | 4 | export default function findNode( 5 | path: NodePath | NodePath[], 6 | node: N 7 | ): NodePath | null { 8 | if (Array.isArray(path)) { 9 | for (const p of path) { 10 | const result = findNode(p, node) 11 | if (result) return result 12 | } 13 | return null 14 | } 15 | 16 | let result: NodePath | null = null 17 | 18 | path.traverse({ 19 | [node.type](path: NodePath) { 20 | if (path.node === node) { 21 | result = path 22 | path.stop() 23 | } 24 | }, 25 | Function(path: NodePath) { 26 | path.skip() 27 | }, 28 | }) 29 | 30 | return result 31 | } 32 | -------------------------------------------------------------------------------- /src/util/findPromiseChains.ts: -------------------------------------------------------------------------------- 1 | import * as t from '@babel/types' 2 | import { NodePath } from '@babel/traverse' 3 | 4 | import { isPromiseMethodCall } from './predicates' 5 | import shouldIgnoreChain from './shouldIgnoreChain' 6 | 7 | export default function findPromiseChains( 8 | path: NodePath 9 | ): NodePath[] { 10 | const chains: NodePath[] = [] 11 | path.traverse( 12 | { 13 | CallExpression(path: NodePath) { 14 | if (isPromiseMethodCall(path.node)) { 15 | if (!shouldIgnoreChain(path)) chains.push(path) 16 | path.skip() 17 | } 18 | }, 19 | Function(path: NodePath) { 20 | path.skip() 21 | }, 22 | }, 23 | path.state 24 | ) 25 | return chains 26 | } 27 | -------------------------------------------------------------------------------- /src/util/getCatchHandler.ts: -------------------------------------------------------------------------------- 1 | import * as t from '@babel/types' 2 | import { NodePath } from '@babel/traverse' 3 | 4 | export default function getCatchHandler( 5 | path: NodePath 6 | ): NodePath | null { 7 | const { callee } = path.node 8 | if ( 9 | callee.type !== 'MemberExpression' || 10 | callee.property.type !== 'Identifier' || 11 | (callee.property.name !== 'then' && callee.property.name !== 'catch') 12 | ) { 13 | return null 14 | } 15 | const handler = path.get('arguments')[callee.property.name === 'then' ? 1 : 0] 16 | return handler && handler.isExpression() ? handler : null 17 | } 18 | -------------------------------------------------------------------------------- /src/util/getFinallyHandler.ts: -------------------------------------------------------------------------------- 1 | import * as t from '@babel/types' 2 | import { NodePath } from '@babel/traverse' 3 | 4 | export default function getFinallyHandler( 5 | path: NodePath 6 | ): NodePath | null { 7 | const { callee } = path.node 8 | if ( 9 | callee.type !== 'MemberExpression' || 10 | callee.property.type !== 'Identifier' || 11 | callee.property.name !== 'finally' 12 | ) { 13 | return null 14 | } 15 | const handler = path.get('arguments')[0] 16 | return handler && handler.isExpression() ? handler : null 17 | } 18 | -------------------------------------------------------------------------------- /src/util/getOutputIdentifier.ts: -------------------------------------------------------------------------------- 1 | import * as t from '@babel/types' 2 | import { NodePath } from '@babel/traverse' 3 | import unboundIdentifier from './unboundIdentifier' 4 | 5 | export default function getOutputIdentifier( 6 | link: NodePath 7 | ): t.Identifier { 8 | const { parentPath } = link 9 | if (parentPath.isAwaitExpression()) { 10 | const grandparentPath = parentPath.parentPath 11 | if (grandparentPath.isVariableDeclarator()) { 12 | const id = (grandparentPath as NodePath).get('id') 13 | if (id.isIdentifier()) return id.node 14 | } 15 | } 16 | return unboundIdentifier(link) 17 | } 18 | -------------------------------------------------------------------------------- /src/util/getPreceedingLink.ts: -------------------------------------------------------------------------------- 1 | import * as t from '@babel/types' 2 | import { NodePath } from '@babel/traverse' 3 | 4 | export default function getPreceedingLink( 5 | link: NodePath 6 | ): NodePath { 7 | const callee = link.get('callee') 8 | if (!callee.isMemberExpression()) { 9 | throw new Error(`code that uses V8 intrinsic identifiers isn't supported`) 10 | } 11 | return callee.get('object') as NodePath 12 | } 13 | -------------------------------------------------------------------------------- /src/util/getThenHandler.ts: -------------------------------------------------------------------------------- 1 | import * as t from '@babel/types' 2 | import { NodePath } from '@babel/traverse' 3 | 4 | export default function getThenHandler( 5 | path: NodePath 6 | ): NodePath | null { 7 | const { callee } = path.node 8 | if ( 9 | callee.type !== 'MemberExpression' || 10 | callee.property.type !== 'Identifier' || 11 | callee.property.name !== 'then' 12 | ) { 13 | return null 14 | } 15 | const handler = path.get('arguments')[0] 16 | return handler && handler.isExpression() ? handler : null 17 | } 18 | -------------------------------------------------------------------------------- /src/util/hasMutableIdentifiers.ts: -------------------------------------------------------------------------------- 1 | import * as t from '@babel/types' 2 | import { NodePath } from '@babel/traverse' 3 | 4 | export default function hasMutableIdentifiers< 5 | T extends t.PatternLike | t.TSParameterProperty 6 | >(path: NodePath): boolean { 7 | let result = false 8 | if (path.isIdentifier()) { 9 | const binding = path.scope.getBinding(path.node.name) 10 | return !binding?.constant 11 | } 12 | path.traverse( 13 | { 14 | Identifier(path: NodePath) { 15 | if (path.isBindingIdentifier()) { 16 | const binding = path.scope.getBinding(path.node.name) 17 | if (!binding) return 18 | if (!binding.constant) { 19 | path.stop() 20 | result = true 21 | } 22 | } 23 | }, 24 | }, 25 | path.state 26 | ) 27 | return result 28 | } 29 | -------------------------------------------------------------------------------- /src/util/hasReturnStatements.ts: -------------------------------------------------------------------------------- 1 | import * as t from '@babel/types' 2 | import { NodePath } from '@babel/traverse' 3 | 4 | export default function hasReturnStatements( 5 | path: NodePath 6 | ): boolean { 7 | let found = false 8 | path.traverse({ 9 | ReturnStatement(path: NodePath) { 10 | found = true 11 | path.stop() 12 | }, 13 | Function(path: NodePath) { 14 | path.skip() 15 | }, 16 | }) 17 | return found 18 | } 19 | -------------------------------------------------------------------------------- /src/util/insertStatementsBefore.ts: -------------------------------------------------------------------------------- 1 | import * as t from '@babel/types' 2 | import { NodePaths, NodePath } from '@babel/traverse' 3 | 4 | import parentStatement from './parentStatement' 5 | 6 | export default function insertStatementsBefore< 7 | Statements extends t.Statement | t.Statement[] 8 | >(path: NodePath, statements: Statements): NodePaths { 9 | let target = parentStatement(path) 10 | const { parentPath } = target 11 | const firstStatement = Array.isArray(statements) ? statements[0] : statements 12 | if (target.node && firstStatement) { 13 | t.inheritLeadingComments(firstStatement, target.node) 14 | target.node.leadingComments = null 15 | } 16 | if ( 17 | (parentPath.isBlockParent() || parentPath.isIfStatement()) && 18 | !parentPath.isBlockStatement() 19 | ) { 20 | const [newBlock] = target.replaceWith( 21 | t.blockStatement([ 22 | target.isStatement() 23 | ? target.node 24 | : parentPath.isFunction() 25 | ? t.returnStatement((target as NodePath).node) 26 | : t.expressionStatement((target as NodePath).node), 27 | ]) 28 | ) as any 29 | target = newBlock.get('body.0') 30 | } 31 | return target.insertBefore(statements) 32 | } 33 | -------------------------------------------------------------------------------- /src/util/isGetterOrSetter.ts: -------------------------------------------------------------------------------- 1 | import * as t from '@babel/types' 2 | import { NodePath } from '@babel/traverse' 3 | 4 | export default function isGetterOrSetter( 5 | path: NodePath | null 6 | ): boolean { 7 | return ( 8 | path !== null && 9 | (path.isObjectMethod() || path.isClassMethod()) && 10 | (path.node.kind === 'get' || path.node.kind === 'set') 11 | ) 12 | } 13 | -------------------------------------------------------------------------------- /src/util/iterateChain.ts: -------------------------------------------------------------------------------- 1 | import * as t from '@babel/types' 2 | import { NodePath } from '@babel/traverse' 3 | import { isPromiseMethodCall } from './predicates' 4 | 5 | export default function* iterateChain( 6 | path: NodePath 7 | ): Iterable> { 8 | while (isPromiseMethodCall(path.node)) { 9 | yield path 10 | const callee = path.get('callee') 11 | if (callee.isMemberExpression()) { 12 | const object = (callee as NodePath).get('object') 13 | if (object.isCallExpression()) path = object 14 | else break 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/util/mergeCatchIntoFinally.ts: -------------------------------------------------------------------------------- 1 | import * as t from '@babel/types' 2 | import { NodePath } from '@babel/traverse' 3 | 4 | export default function mergeCatchIntoTryFinally( 5 | link: NodePath, 6 | tryStatement: NodePath 7 | ): NodePath | NodePath[] | null { 8 | let parent = link.parentPath 9 | if (!parent.isAwaitExpression()) return null 10 | parent = parent.parentPath 11 | if (!parent.isStatement()) return null 12 | const statement = parent 13 | parent = parent.parentPath 14 | if (!parent.isBlockStatement()) return null 15 | const body = (parent as NodePath).node.body 16 | if (body[body.length - 1] !== statement.node) return null 17 | parent = parent.parentPath 18 | if (!parent.isTryStatement() || parent.node.handler) return null 19 | const { handler, block } = tryStatement.node 20 | if (!handler || !block || block.type !== 'BlockStatement') return null 21 | ;(parent as NodePath).get('handler').replaceWith(handler) 22 | return statement.replaceWithMultiple(block.body) as any 23 | } 24 | -------------------------------------------------------------------------------- /src/util/mergeStatementsIntoTryFinally.ts: -------------------------------------------------------------------------------- 1 | import * as t from '@babel/types' 2 | import { NodePath } from '@babel/traverse' 3 | 4 | export default function mergeStatementsIntoTryFinally( 5 | link: NodePath, 6 | tryStatement: NodePath 7 | ): NodePath | NodePath[] | null { 8 | let parent = link.parentPath 9 | if (!parent.isAwaitExpression()) return null 10 | parent = parent.parentPath 11 | if (!parent.isStatement()) return null 12 | const statement = parent 13 | parent = parent.parentPath 14 | if (!parent.isBlockStatement()) return null 15 | const body = (parent as NodePath).node.body 16 | if (body[body.length - 1] !== statement.node) return null 17 | parent = parent.parentPath 18 | if (!parent.isTryStatement() || !parent.node.finalizer) return null 19 | const { finalizer, block } = tryStatement.node 20 | if (!finalizer || !block || block.type !== 'BlockStatement') return null 21 | const destFinalizer = parent.get('finalizer') as NodePath 22 | destFinalizer.unshiftContainer('body', finalizer.body) 23 | return statement.replaceWithMultiple(block.body) as any 24 | } 25 | -------------------------------------------------------------------------------- /src/util/parentStatement.ts: -------------------------------------------------------------------------------- 1 | import * as t from '@babel/types' 2 | import { NodePath } from '@babel/traverse' 3 | 4 | export default function parentStatement( 5 | path: NodePath 6 | ): NodePath { 7 | return path.find( 8 | p => 9 | p.isStatement() || 10 | p.parentPath.isBlockParent() || 11 | p.parentPath.isIfStatement() || 12 | p.parentPath.isSwitchCase() 13 | ) as NodePath 14 | } 15 | -------------------------------------------------------------------------------- /src/util/predicates.ts: -------------------------------------------------------------------------------- 1 | import * as t from '@babel/types' 2 | import { NodePath } from '@babel/traverse' 3 | 4 | export function isNullish(node: t.Node): boolean { 5 | return ( 6 | node.type === 'NullLiteral' || 7 | (node.type === 'Identifier' && node.name === 'undefined') 8 | ) 9 | } 10 | 11 | export function isPromiseMethodCall(node: t.Node): boolean { 12 | return ( 13 | node.type === 'CallExpression' && 14 | node.callee.type === 'MemberExpression' && 15 | node.callee.property.type === 'Identifier' && 16 | (node.callee.property.name === 'then' || 17 | node.callee.property.name === 'catch' || 18 | node.callee.property.name === 'finally') 19 | ) 20 | } 21 | 22 | export function isPromiseValued(node: t.Node): boolean { 23 | return ( 24 | node.type === 'AwaitExpression' || 25 | (node.type === 'CallExpression' && 26 | node.callee.type === 'MemberExpression' && 27 | node.callee.property.type === 'Identifier' && 28 | ((node.callee.object.type === 'Identifier' && 29 | node.callee.object.name === 'Promise') || 30 | node.callee.property.name === 'then' || 31 | node.callee.property.name === 'catch' || 32 | node.callee.property.name === 'finally')) 33 | ) 34 | } 35 | 36 | export function isPromiseHandler(path: NodePath): boolean { 37 | return isPromiseValued(path.parentPath.node) 38 | } 39 | 40 | function getPromiseStaticMethodCall( 41 | thing: T | NodePath 42 | ): string | null { 43 | if (thing instanceof NodePath) return getPromiseStaticMethodCall(thing.node) 44 | if (thing.type !== 'CallExpression') return null 45 | const { callee } = thing as t.CallExpression 46 | if (callee.type !== 'MemberExpression') return null 47 | const { object, property } = callee as t.MemberExpression 48 | if ( 49 | object.type !== 'Identifier' || 50 | object.name !== 'Promise' || 51 | (property.type !== 'Identifier' && property.type !== 'StringLiteral') 52 | ) 53 | return null 54 | return property.type === 'Identifier' ? property.name : property.value 55 | } 56 | 57 | export function isPromiseResolveCall( 58 | thing: T | NodePath 59 | ): boolean { 60 | return getPromiseStaticMethodCall(thing) === 'resolve' 61 | } 62 | 63 | export function isPromiseRejectCall( 64 | thing: T | NodePath 65 | ): boolean { 66 | return getPromiseStaticMethodCall(thing) === 'reject' 67 | } 68 | 69 | export function needsAwait(node: T): boolean { 70 | if ( 71 | t.isLiteral(node) || 72 | t.isArrayExpression(node) || 73 | t.isObjectExpression(node) || 74 | t.isFunctionExpression(node) || 75 | t.isArrowFunctionExpression(node) || 76 | t.isBinaryExpression(node) || 77 | t.isUnaryExpression(node) || 78 | t.isThisExpression(node) || 79 | t.isJSX(node) || 80 | t.isAwaitExpression(node) || 81 | isNullish(node) 82 | ) { 83 | return false 84 | } else { 85 | return true 86 | } 87 | } 88 | 89 | export function isIdentifierDeclarator( 90 | path: NodePath 91 | ): boolean { 92 | return ( 93 | path.isVariableDeclarator() && 94 | (path as NodePath).get('id').isIdentifier() 95 | ) 96 | } 97 | 98 | export function isIdentifierAssignmentExpression( 99 | path: NodePath 100 | ): boolean { 101 | return ( 102 | path.isAssignmentExpression() && 103 | (path as NodePath).get('left').isIdentifier() 104 | ) 105 | } 106 | 107 | export function isInSwitchCase(path: NodePath): boolean { 108 | let { parentPath } = path 109 | while (parentPath && !parentPath.isFunction()) { 110 | if (parentPath.isSwitchCase()) return true 111 | ;({ parentPath } = parentPath) 112 | } 113 | return false 114 | } 115 | 116 | export function isInLoop(path: NodePath): boolean { 117 | let { parentPath } = path 118 | while (parentPath && !parentPath.isFunction()) { 119 | if (parentPath.isLoop()) return true 120 | ;({ parentPath } = parentPath) 121 | } 122 | return false 123 | } 124 | 125 | export function isInTryBlock(path: NodePath): boolean { 126 | let { parentPath } = path 127 | while (parentPath && !parentPath.isFunction()) { 128 | if (parentPath.isTryStatement() && parentPath.get('block') === path) 129 | return true 130 | path = parentPath 131 | ;({ parentPath } = path) 132 | } 133 | return false 134 | } 135 | 136 | export function isLastStatementInBlock(path: NodePath): boolean { 137 | const { parentPath } = path 138 | if (!parentPath.isBlockStatement()) return true 139 | const body = (parentPath as NodePath).get('body') 140 | return (path as NodePath) === body[body.length - 1] 141 | } 142 | 143 | export function isLastStatementInFunction(path: NodePath): boolean { 144 | while (!path.isFunction()) { 145 | if (!isLastStatementInBlock(path)) return false 146 | path = path.parentPath 147 | } 148 | return true 149 | } 150 | -------------------------------------------------------------------------------- /src/util/prependBodyStatement.ts: -------------------------------------------------------------------------------- 1 | import * as t from '@babel/types' 2 | import { NodePath, NodePaths } from '@babel/traverse' 3 | import convertBodyToBlockStatement from './convertBodyToBlockStatement' 4 | 5 | export default function prependBodyStatement< 6 | T extends t.Function, 7 | S extends t.Statement 8 | >(func: NodePath, statement: S): NodePaths { 9 | return convertBodyToBlockStatement(func).unshiftContainer('body', [statement]) 10 | } 11 | -------------------------------------------------------------------------------- /src/util/recastBugWorkarounds.ts: -------------------------------------------------------------------------------- 1 | import * as t from '@babel/types' 2 | import { NodePath } from '@babel/traverse' 3 | 4 | export default function recastBugWorkarounds(path: NodePath): void { 5 | const visitedComments: Set = new Set() 6 | path.traverse({ 7 | AwaitExpression(path: NodePath) { 8 | const argument = path.get('argument') 9 | const { parentPath } = path 10 | if ( 11 | argument.isConditionalExpression() || 12 | (parentPath.isMemberExpression() && path === parentPath.get('object')) 13 | ) { 14 | argument.replaceWith(t.parenthesizedExpression(argument.node)) 15 | } 16 | }, 17 | }) 18 | if (path.state.commentWorkarounds) { 19 | path.traverse({ 20 | exit(path: NodePath) { 21 | const { leadingComments } = path.node 22 | const anyNode = path.node as any 23 | if (leadingComments) { 24 | anyNode.comments = [] 25 | if (leadingComments) { 26 | for (const comment of leadingComments) { 27 | if (visitedComments.has(comment)) continue 28 | visitedComments.add(comment) 29 | anyNode.comments.push({ 30 | ...comment, 31 | leading: true, 32 | trailing: false, 33 | }) 34 | } 35 | } 36 | } else { 37 | anyNode.comments = null 38 | } 39 | }, 40 | }) 41 | path.traverse({ 42 | exit(path: NodePath) { 43 | const { trailingComments } = path.node 44 | const anyNode = path.node as any 45 | if (trailingComments) { 46 | if (!anyNode.comments) anyNode.comments = [] 47 | for (const comment of trailingComments) { 48 | if (visitedComments.has(comment)) continue 49 | visitedComments.add(comment) 50 | anyNode.comments.push({ 51 | ...comment, 52 | leading: false, 53 | trailing: true, 54 | }) 55 | } 56 | } 57 | }, 58 | }) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/util/removeRestOfBlockStatement.ts: -------------------------------------------------------------------------------- 1 | import * as t from '@babel/types' 2 | import { NodePath } from '@babel/traverse' 3 | 4 | import parentStatement from './parentStatement' 5 | 6 | export default function removeRestOfBlockStatement( 7 | path: NodePath 8 | ): t.Statement[] { 9 | const statement = parentStatement(path) 10 | const blockStatement = statement.parentPath 11 | if (!blockStatement.isBlockStatement()) 12 | throw new Error('failed to get BlockStatement') 13 | const body = (blockStatement as NodePath).get('body') 14 | const index = body.indexOf(statement) 15 | if (index < 0) 16 | throw new Error('failed to get index of Statement within BlockStatement') 17 | const rest = body.slice(index + 1) 18 | const statements = rest.map(p => p.node) 19 | rest.forEach(p => p.remove()) 20 | return statements 21 | } 22 | -------------------------------------------------------------------------------- /src/util/renameBoundIdentifiers.ts: -------------------------------------------------------------------------------- 1 | import * as t from '@babel/types' 2 | import { NodePath, Scope } from '@babel/traverse' 3 | 4 | export default function renameBoundIdentifiers( 5 | parent: NodePath, 6 | destScope: Scope 7 | ): void { 8 | function isBound(name: string): boolean { 9 | return parent.scope.hasBinding(name) || destScope.hasBinding(name) 10 | } 11 | 12 | function rename(path: NodePath): void { 13 | let newName = path.node.name 14 | let counter = 0 15 | while (isBound(newName)) newName = `${path.node.name}${counter++}` 16 | path.scope.rename(path.node.name, newName) 17 | } 18 | 19 | function mustRename(path: NodePath): boolean { 20 | const { name } = path.node 21 | return ( 22 | destScope.hasBinding(name) && 23 | path.isBindingIdentifier() && 24 | ((destScope.hasBinding(name) && 25 | parent.scope.getBindingIdentifier(name) === path.node) || 26 | path.scope.getBinding(name)?.kind === 'var') 27 | ) 28 | } 29 | 30 | if (parent.isIdentifier() && mustRename(parent)) rename(parent) 31 | 32 | parent.traverse( 33 | { 34 | Identifier: (path: NodePath) => { 35 | if (mustRename(path)) rename(path) 36 | }, 37 | Function(path: NodePath) { 38 | path.skipKey('body') 39 | }, 40 | }, 41 | parent.state 42 | ) 43 | } 44 | -------------------------------------------------------------------------------- /src/util/replaceLink.ts: -------------------------------------------------------------------------------- 1 | import * as t from '@babel/types' 2 | import { NodePath } from '@babel/traverse' 3 | import template from '@babel/template' 4 | 5 | import { 6 | isIdentifierAssignmentExpression, 7 | isIdentifierDeclarator, 8 | isInTryBlock, 9 | isLastStatementInFunction, 10 | } from './predicates' 11 | import renameBoundIdentifiers from './renameBoundIdentifiers' 12 | import unboundIdentifier from './unboundIdentifier' 13 | import replaceReturnStatements from './replaceReturnStatements' 14 | import { awaited } from './builders' 15 | import insertStatementsBefore from './insertStatementsBefore' 16 | import replaceWithStatements from './replaceWithStatements' 17 | import hasReturnStatements from './hasReturnStatements' 18 | 19 | function findReplaceTarget(link: NodePath): NodePath { 20 | const { parentPath } = link 21 | if (parentPath.isAwaitExpression()) return findReplaceTarget(parentPath) 22 | if ( 23 | parentPath.isReturnStatement() || 24 | parentPath.isExpressionStatement() || 25 | isIdentifierAssignmentExpression(parentPath) 26 | ) { 27 | return parentPath 28 | } 29 | if (isIdentifierDeclarator(parentPath)) { 30 | const declaration = parentPath.parentPath 31 | if ( 32 | declaration.isVariableDeclaration() && 33 | declaration.node.declarations.length === 1 34 | ) { 35 | return declaration as NodePath 36 | } 37 | } 38 | return link 39 | } 40 | 41 | function findOnlyFinalReturn( 42 | path: NodePath 43 | ): NodePath | null { 44 | let count = 0 45 | path.traverse( 46 | { 47 | ReturnStatement(path: NodePath) { 48 | if (count++) path.stop() 49 | }, 50 | Function(path: NodePath) { 51 | path.skip() 52 | }, 53 | }, 54 | path.state 55 | ) 56 | if (count !== 1) return null 57 | const body = path.get('body') 58 | const last = body[body.length - 1] 59 | return last.isReturnStatement() ? last : null 60 | } 61 | 62 | export default function replaceLink( 63 | link: NodePath, 64 | replacement: t.Expression | NodePath 65 | ): NodePath | NodePath[] { 66 | if (!(replacement instanceof NodePath)) { 67 | const { parentPath } = link 68 | return (parentPath.isAwaitExpression() ? parentPath : link).replaceWith( 69 | awaited(replacement) 70 | ) as any 71 | } 72 | if (replacement.isBlockStatement()) { 73 | renameBoundIdentifiers(replacement, link.scope) 74 | const onlyFinalReturn = findOnlyFinalReturn(replacement) 75 | if (onlyFinalReturn) { 76 | const value = onlyFinalReturn.node.argument || t.identifier('undefined') 77 | onlyFinalReturn.remove() 78 | const { parentPath } = link 79 | const target = parentPath.isAwaitExpression() ? parentPath : link 80 | target.replaceWith(awaited(value)) 81 | return insertStatementsBefore( 82 | target as NodePath, 83 | replacement.node.body 84 | ) as any 85 | } 86 | const target = findReplaceTarget(link) 87 | if ( 88 | target.parentPath.isBlockParent() && 89 | !target.parentPath.isBlockStatement() 90 | ) { 91 | return replaceWithStatements(target, replacement.node.body) as any 92 | } else if (target.isReturnStatement()) { 93 | if (isInTryBlock(target)) { 94 | replaceReturnStatements(replacement, argument => 95 | t.returnStatement(awaited(argument)) 96 | ) 97 | } 98 | if ( 99 | hasReturnStatements(replacement) || 100 | isLastStatementInFunction(target) 101 | ) { 102 | return replaceWithStatements(target, replacement.node.body) as any 103 | } else { 104 | ;(target as NodePath).get('argument').remove() 105 | return insertStatementsBefore(target, replacement.node.body) as any 106 | } 107 | } else if (target.isExpressionStatement()) { 108 | replaceReturnStatements(replacement, argument => 109 | t.expressionStatement(awaited(argument)) 110 | ) 111 | return replaceWithStatements(target, replacement.node.body) as any 112 | } else if (target.isVariableDeclaration()) { 113 | const { 114 | declarations: [{ id }], 115 | } = target.node 116 | replacement.unshiftContainer('body', template.statements.ast`let ${id}`) 117 | replaceReturnStatements(replacement, argument => 118 | t.expressionStatement( 119 | t.assignmentExpression('=', id, awaited(argument)) 120 | ) 121 | ) 122 | return replaceWithStatements(target, replacement.node.body) as any 123 | } else if (target.isAssignmentExpression()) { 124 | const { left, operator } = target.node 125 | replaceReturnStatements(replacement, argument => 126 | t.expressionStatement( 127 | t.assignmentExpression(operator, left, awaited(argument)) 128 | ) 129 | ) 130 | return target.replaceWithMultiple(replacement.node.body) as any 131 | } else { 132 | const result = unboundIdentifier(replacement, 'result') 133 | const declarator = t.variableDeclarator(result) 134 | replacement.unshiftContainer( 135 | 'body', 136 | t.variableDeclaration('let', [declarator]) 137 | ) 138 | replaceReturnStatements(replacement, argument => 139 | t.expressionStatement( 140 | t.assignmentExpression('=', result, awaited(argument)) 141 | ) 142 | ) 143 | target.replaceWith(result) 144 | const output = insertStatementsBefore( 145 | target, 146 | replacement.node.body 147 | ) as any 148 | return output 149 | } 150 | } else { 151 | const { parentPath } = link 152 | return (parentPath.isAwaitExpression() ? parentPath : link).replaceWith( 153 | awaited(replacement.node as t.Expression) 154 | ) as any 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /src/util/replaceReturnStatements.ts: -------------------------------------------------------------------------------- 1 | import * as t from '@babel/types' 2 | import { NodePath } from '@babel/traverse' 3 | import { isInSwitchCase, isInLoop } from './predicates' 4 | import replaceWithStatements from './replaceWithStatements' 5 | import parentStatement from './parentStatement' 6 | 7 | export default function replaceReturnStatements( 8 | path: NodePath, 9 | getReplacement: (argument: t.Expression) => T | null | undefined 10 | ): NodePath { 11 | path.traverse( 12 | { 13 | ReturnStatement(path: NodePath) { 14 | const replacement = getReplacement( 15 | path.node.argument || t.identifier('undefined') 16 | ) 17 | if (!replacement) { 18 | if (isInLoop(path) || isInSwitchCase(path)) { 19 | path.replaceWith(t.breakStatement()) 20 | } else { 21 | path.remove() 22 | } 23 | } else if (replacement.type === 'ReturnStatement') { 24 | const { argument } = replacement as t.ReturnStatement 25 | if (argument) path.get('argument').replaceWith(argument) 26 | else path.get('argument').remove() 27 | } else { 28 | if (isInLoop(path) || isInSwitchCase(path)) { 29 | replaceWithStatements(parentStatement(path), [ 30 | replacement, 31 | t.breakStatement(), 32 | ]) 33 | } else { 34 | path.replaceWith(replacement) 35 | } 36 | } 37 | }, 38 | Function(path: NodePath) { 39 | path.skip() 40 | }, 41 | }, 42 | path.state 43 | ) 44 | return path 45 | } 46 | -------------------------------------------------------------------------------- /src/util/replaceWithImmediatelyInvokedAsyncArrowFunction.ts: -------------------------------------------------------------------------------- 1 | import * as t from '@babel/types' 2 | import { NodePath } from '@babel/traverse' 3 | import { awaited } from './builders' 4 | 5 | export default function replaceWithImmediatelyInvokedAsyncArrowFunction< 6 | T extends t.Expression 7 | >(path: NodePath): [NodePath, NodePath] { 8 | let nodepath = 'callee.body.body.0.argument' 9 | const argument = awaited(path.node) 10 | if (argument !== path.node) nodepath += '.argument' 11 | const fn = t.arrowFunctionExpression( 12 | [], 13 | t.blockStatement([t.returnStatement(argument)]) 14 | ) 15 | fn.async = true 16 | const [replacement] = path.replaceWith(t.callExpression(fn, [])) as any 17 | return [replacement, replacement.get(nodepath)] 18 | } 19 | -------------------------------------------------------------------------------- /src/util/replaceWithStatements.ts: -------------------------------------------------------------------------------- 1 | import * as t from '@babel/types' 2 | import { NodePaths, NodePath } from '@babel/traverse' 3 | 4 | export default function replaceWithStatements( 5 | path: NodePath, 6 | statements: Statements 7 | ): NodePaths { 8 | const { parentPath } = path 9 | if ( 10 | !parentPath.isBlockParent() && 11 | !parentPath.isIfStatement() && 12 | !parentPath.isSwitchCase() 13 | ) { 14 | throw new Error( 15 | 'path must be a child of a BlockParent, SwitchCase, or IfStatement' 16 | ) 17 | } 18 | const oldNode = path.node 19 | if (oldNode) { 20 | if (oldNode && statements[0]) { 21 | t.inheritLeadingComments(statements[0], oldNode) 22 | } 23 | if (oldNode && statements[statements.length - 1]) { 24 | t.inheritTrailingComments(statements[statements.length - 1], oldNode) 25 | } 26 | t.removeComments(oldNode) 27 | } 28 | if (!parentPath.isBlockStatement()) { 29 | return (path.replaceWith(t.blockStatement(statements)) as any)[0].get( 30 | 'body' 31 | ) as NodePaths 32 | } 33 | return path.replaceWithMultiple(statements) as NodePaths 34 | } 35 | -------------------------------------------------------------------------------- /src/util/returnsOrAwaitsPromises.ts: -------------------------------------------------------------------------------- 1 | import * as t from '@babel/types' 2 | import { NodePath } from '@babel/traverse' 3 | import { isPromiseValued } from './predicates' 4 | 5 | export default function returnsOrAwaitsPromises( 6 | path: NodePath 7 | ): boolean { 8 | if (path.node.async) return true 9 | let result = false 10 | const body = path.get('body') 11 | if (!body.isBlockStatement()) { 12 | return isPromiseValued(body.node) 13 | } 14 | body.traverse( 15 | { 16 | ReturnStatement(path: NodePath) { 17 | const { 18 | node: { argument }, 19 | } = path 20 | if (argument && isPromiseValued(argument)) { 21 | result = true 22 | path.stop() 23 | } 24 | }, 25 | Function(path: NodePath) { 26 | path.skip() 27 | }, 28 | }, 29 | body.state 30 | ) 31 | return result 32 | } 33 | -------------------------------------------------------------------------------- /src/util/shouldIgnoreChain.ts: -------------------------------------------------------------------------------- 1 | import * as t from '@babel/types' 2 | import { NodePath } from '@babel/traverse' 3 | import generate from '@babel/generator' 4 | import iterateChain from './iterateChain' 5 | import getThenHandler from './getThenHandler' 6 | import getCatchHandler from './getCatchHandler' 7 | import getFinallyHandler from './getFinallyHandler' 8 | import isGetterOrSetter from './isGetterOrSetter' 9 | 10 | function chainLength(path: NodePath): number { 11 | let length = 0 12 | for (const link of iterateChain(path)) length++ // eslint-disable-line @typescript-eslint/no-unused-vars 13 | return length 14 | } 15 | 16 | function isComplexHandler(path: NodePath | null): boolean { 17 | if (!path || !path.isFunction()) return false 18 | const body = (path as NodePath).get('body') 19 | if (!body.isBlockStatement()) return false 20 | return (body as NodePath).node.body.length > 1 21 | } 22 | 23 | function hasComplexHandlers(path: NodePath): boolean { 24 | for (const link of iterateChain(path)) { 25 | if ( 26 | isComplexHandler(getThenHandler(link)) || 27 | isComplexHandler(getCatchHandler(link)) || 28 | isComplexHandler(getFinallyHandler(link)) 29 | ) 30 | return true 31 | } 32 | return false 33 | } 34 | 35 | export default function shouldIgnoreChain( 36 | path: NodePath 37 | ): boolean { 38 | const { parentPath } = path 39 | if ( 40 | (!parentPath.isReturnStatement() && 41 | !parentPath.isAwaitExpression() && 42 | !parentPath.isFunction()) || 43 | isGetterOrSetter(path.getFunctionParent()) 44 | ) { 45 | if (chainLength(path) <= 2 && !hasComplexHandlers(path)) return true 46 | } 47 | const { ignoreChainsShorterThan } = path.state 48 | return ( 49 | ignoreChainsShorterThan != null && 50 | generate(path.node as any).code.length < ignoreChainsShorterThan 51 | ) 52 | } 53 | -------------------------------------------------------------------------------- /src/util/unboundIdentifier.ts: -------------------------------------------------------------------------------- 1 | import * as t from '@babel/types' 2 | import { NodePath } from '@babel/traverse' 3 | 4 | export default function unboundIdentifier( 5 | path: NodePath, 6 | prefix?: string 7 | ): t.Identifier { 8 | let counter = 0 9 | let name = prefix || `_ASYNCIFY_${counter++}` 10 | while (path.scope.hasBinding(name)) 11 | name = `${prefix || '_ASYNCIFY_'}${counter++}` 12 | return t.identifier(name) 13 | } 14 | -------------------------------------------------------------------------------- /src/util/unwindCatch.ts: -------------------------------------------------------------------------------- 1 | import * as t from '@babel/types' 2 | import { NodePath } from '@babel/traverse' 3 | 4 | import getPreceedingLink from './getPreceedingLink' 5 | import { isNullish, isPromiseMethodCall } from './predicates' 6 | import canUnwindAsIs from './canUnwindAsIs' 7 | import replaceLink from './replaceLink' 8 | import renameBoundIdentifiers from './renameBoundIdentifiers' 9 | import unboundIdentifier from './unboundIdentifier' 10 | import convertBodyToBlockStatement from './convertBodyToBlockStatement' 11 | import mergeCatchIntoTryFinally from './mergeCatchIntoFinally' 12 | import { awaited } from './builders' 13 | import convertConditionalReturns from './convertConditionalReturns' 14 | import findNode from './findNode' 15 | import canDefinitelyInvoke from './canDefinitelyInvoke' 16 | 17 | export default function unwindCatch( 18 | handler: NodePath 19 | ): NodePath | null { 20 | const link = handler.parentPath as NodePath 21 | let preceedingLink 22 | let preceeding 23 | if (link.node.arguments.length === 2) { 24 | preceedingLink = t.callExpression(link.node.callee, [ 25 | link.node.arguments[0], 26 | ]) 27 | preceeding = t.awaitExpression(preceedingLink) 28 | } else { 29 | preceedingLink = getPreceedingLink(link).node 30 | preceeding = awaited(preceedingLink) 31 | } 32 | 33 | if (isNullish(handler.node)) { 34 | return findNode(replaceLink(link, preceeding), preceedingLink) 35 | } 36 | 37 | if (!handler.isFunction()) { 38 | if (!canDefinitelyInvoke(handler)) { 39 | return getPreceedingLink(link) 40 | } 41 | const callee = handler.node 42 | ;[handler] = handler.replaceWith( 43 | t.arrowFunctionExpression( 44 | [t.identifier('err')], 45 | t.callExpression(callee, [t.identifier('err')]) 46 | ) 47 | ) as any 48 | } 49 | const handlerFunction = handler as NodePath 50 | const body = handlerFunction.get('body') 51 | if ( 52 | body.isBlockStatement() && 53 | ((body.node.body.length === 0 && 54 | !isPromiseMethodCall(getPreceedingLink(link).node)) || 55 | (!canUnwindAsIs(link) && !convertConditionalReturns(body))) 56 | ) { 57 | return null 58 | } 59 | 60 | handlerFunction.node.async = true 61 | const input = handlerFunction.get('params')[0] 62 | if (input) renameBoundIdentifiers(input, link.scope) 63 | const inputNode = input?.node 64 | if (input) input.remove() 65 | if ( 66 | inputNode?.type === 'AssignmentPattern' || 67 | inputNode?.type === 'RestElement' || 68 | inputNode?.type === 'TSParameterProperty' 69 | ) { 70 | throw new Error( 71 | 'TODO: these catch parameter node types are not supported yet' 72 | ) 73 | } 74 | if (inputNode) delete inputNode.typeAnnotation 75 | const catchClause = t.catchClause( 76 | inputNode || unboundIdentifier(handler, 'err'), 77 | convertBodyToBlockStatement(handlerFunction).node 78 | ) 79 | 80 | body.replaceWith( 81 | t.blockStatement([ 82 | t.tryStatement( 83 | t.blockStatement([t.returnStatement(preceeding)]), 84 | catchClause 85 | ), 86 | ]) 87 | ) 88 | const finalBody = handlerFunction.get('body') as NodePath 89 | ;(finalBody.scope as any).crawl() 90 | const tryStatement = finalBody.get('body')[0] as NodePath 91 | return findNode( 92 | mergeCatchIntoTryFinally(link, tryStatement) || 93 | (replaceLink(link, finalBody) as any), 94 | preceedingLink 95 | ) 96 | } 97 | -------------------------------------------------------------------------------- /src/util/unwindFinally.ts: -------------------------------------------------------------------------------- 1 | import * as t from '@babel/types' 2 | import { NodePath } from '@babel/traverse' 3 | 4 | import { awaited } from './builders' 5 | import getPreceedingLink from './getPreceedingLink' 6 | import { isNullish } from './predicates' 7 | import replaceLink from './replaceLink' 8 | import replaceReturnStatements from './replaceReturnStatements' 9 | import convertBodyToBlockStatement from './convertBodyToBlockStatement' 10 | import convertConditionalReturns from './convertConditionalReturns' 11 | import mergeStatementsIntoTryFinally from './mergeStatementsIntoTryFinally' 12 | import findNode from './findNode' 13 | import canDefinitelyInvoke from './canDefinitelyInvoke' 14 | 15 | export default function unwindFinally( 16 | handler: NodePath 17 | ): NodePath | null { 18 | const link = handler.parentPath as NodePath 19 | const preceedingLink = getPreceedingLink(link) 20 | const preceeding = awaited(preceedingLink.node) 21 | 22 | if (isNullish(handler.node)) { 23 | return findNode(replaceLink(link, preceeding), preceedingLink.node) 24 | } 25 | 26 | if (!handler.isFunction()) { 27 | if (!canDefinitelyInvoke(handler)) { 28 | return preceedingLink 29 | } 30 | const callee = handler.node 31 | ;[handler] = handler.replaceWith( 32 | t.arrowFunctionExpression([], t.callExpression(callee, [])) 33 | ) as any 34 | } 35 | const handlerFunction = handler as NodePath 36 | handlerFunction.node.async = true 37 | const body = handlerFunction.get('body') 38 | if (body.isBlockStatement() && !convertConditionalReturns(body)) { 39 | return preceedingLink 40 | } 41 | body.replaceWith( 42 | t.blockStatement([ 43 | t.tryStatement( 44 | t.blockStatement([t.returnStatement(preceeding)]), 45 | null, 46 | replaceReturnStatements( 47 | convertBodyToBlockStatement(handlerFunction), 48 | (argument: t.Expression) => 49 | isNullish(argument) 50 | ? null 51 | : t.expressionStatement(awaited(argument)) 52 | ).node 53 | ), 54 | ]) 55 | ) 56 | const finalBody = handlerFunction.get('body') 57 | ;(finalBody.scope as any).crawl() 58 | const tryStatement = finalBody.get('body.0') as NodePath 59 | return findNode( 60 | mergeStatementsIntoTryFinally(link, tryStatement) || 61 | (replaceLink(link, finalBody) as any), 62 | preceedingLink.node 63 | ) 64 | } 65 | -------------------------------------------------------------------------------- /src/util/unwindPromiseChain.ts: -------------------------------------------------------------------------------- 1 | import * as t from '@babel/types' 2 | import { NodePath } from '@babel/traverse' 3 | 4 | import getThenHandler from './getThenHandler' 5 | import getCatchHandler from './getCatchHandler' 6 | import getFinallyHandler from './getFinallyHandler' 7 | import unwindCatch from './unwindCatch' 8 | import { unwindThen } from './unwindThen' 9 | import unwindFinally from './unwindFinally' 10 | import parentStatement from './parentStatement' 11 | import replaceWithImmediatelyInvokedAsyncArrowFunction from './replaceWithImmediatelyInvokedAsyncArrowFunction' 12 | import isGetterOrSetter from './isGetterOrSetter' 13 | 14 | export default function unwindPromiseChain( 15 | path: NodePath 16 | ): void { 17 | if ( 18 | (!path.parentPath.isAwaitExpression() && 19 | !path.parentPath.isReturnStatement() && 20 | !path.parentPath.isFunction()) || 21 | isGetterOrSetter(path.getFunctionParent()) 22 | ) { 23 | path = replaceWithImmediatelyInvokedAsyncArrowFunction(path)[1] 24 | } 25 | 26 | const { scope } = parentStatement(path) 27 | 28 | let link: NodePath | null = path as any 29 | 30 | while (link) { 31 | const callee = link.get('callee') 32 | if (!callee.isMemberExpression()) break 33 | 34 | const thenHandler = getThenHandler(link) 35 | const catchHandler = getCatchHandler(link) 36 | const finallyHandler = getFinallyHandler(link) 37 | 38 | if (catchHandler) { 39 | link = unwindCatch(catchHandler) 40 | } else if (thenHandler) { 41 | link = unwindThen(thenHandler) 42 | } else if (finallyHandler) { 43 | link = unwindFinally(finallyHandler) 44 | } else { 45 | link = null 46 | } 47 | ;(scope as any).crawl() 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/util/unwindThen.ts: -------------------------------------------------------------------------------- 1 | import * as t from '@babel/types' 2 | import { NodePath } from '@babel/traverse' 3 | 4 | import getPreceedingLink from './getPreceedingLink' 5 | import { awaited } from './builders' 6 | import { isNullish } from './predicates' 7 | import canUnwindAsIs from './canUnwindAsIs' 8 | import renameBoundIdentifiers from './renameBoundIdentifiers' 9 | import hasMutableIdentifiers from './hasMutableIdentifiers' 10 | import prependBodyStatement from './prependBodyStatement' 11 | import replaceLink from './replaceLink' 12 | import convertConditionalReturns from './convertConditionalReturns' 13 | import findNode from './findNode' 14 | import canDefinitelyInvoke from './canDefinitelyInvoke' 15 | 16 | export function unwindThen( 17 | handler: NodePath 18 | ): NodePath | null { 19 | const link = handler.parentPath as NodePath 20 | const preceedingLink = getPreceedingLink(link) 21 | const preceeding = awaited(preceedingLink.node) 22 | 23 | if (isNullish(handler.node)) { 24 | return findNode(replaceLink(link, preceeding), preceedingLink.node) 25 | } 26 | 27 | if (handler.isFunction()) { 28 | handler.node.async = true 29 | const handlerFunction = handler as NodePath 30 | const input = handlerFunction.get('params')[0] 31 | const body = handlerFunction.get('body') 32 | if ( 33 | body.isBlockStatement() && 34 | !canUnwindAsIs(link) && 35 | !convertConditionalReturns(body) 36 | ) { 37 | return preceedingLink 38 | } 39 | 40 | if (input) renameBoundIdentifiers(input, link.scope) 41 | const kind = input && hasMutableIdentifiers(input) ? 'let' : 'const' 42 | const inputNode = input?.node 43 | if (input) input.remove() 44 | const [prepended] = prependBodyStatement( 45 | handler, 46 | inputNode && !isNullish(inputNode) 47 | ? t.variableDeclaration(kind, [ 48 | t.variableDeclarator(inputNode, preceeding), 49 | ]) 50 | : t.expressionStatement(preceeding) 51 | ) 52 | if (prepended.isVariableDeclaration()) { 53 | prepended.scope.registerBinding( 54 | prepended.node.kind, 55 | prepended.get('declarations.0.id') as any 56 | ) 57 | } 58 | return findNode( 59 | replaceLink(link, handlerFunction.get('body')) as any, 60 | preceedingLink.node 61 | ) 62 | } 63 | if (canDefinitelyInvoke(handler)) { 64 | return findNode( 65 | replaceLink(link, t.callExpression(handler.node, [preceeding])) as any, 66 | preceedingLink.node 67 | ) 68 | } 69 | return preceedingLink 70 | } 71 | -------------------------------------------------------------------------------- /test/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "mocha": true 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /test/clearConsole.ts: -------------------------------------------------------------------------------- 1 | before(() => process.stdout.write('\u001b[2J\u001b[1;1H\u001b[3J')) 2 | -------------------------------------------------------------------------------- /test/configure.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | 3 | const chai = require('chai') 4 | chai.use(require('chai-subset')) 5 | require('@babel/register')({ extensions: ['.js', '.ts'] }) 6 | 7 | if (process.argv.indexOf('--watch') >= 0) { 8 | require('./clearConsole') 9 | } 10 | -------------------------------------------------------------------------------- /test/dump.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | import * as t from '@babel/types' 3 | import { NodePath } from '@babel/traverse' 4 | import generate from '@babel/generator' 5 | 6 | export default function dump( 7 | what: T | NodePath | Array> 8 | ): void { 9 | if (Array.isArray(what)) { 10 | what.forEach(el => dump(el)) 11 | if (!what.length) console.log('') 12 | return 13 | } 14 | if (what instanceof NodePath) what = what.node 15 | console.log(generate(what as any).code) 16 | } 17 | -------------------------------------------------------------------------------- /test/fixtures/bugs_BelongsToMany.create.ts: -------------------------------------------------------------------------------- 1 | export const input = ` 2 | class BelongsToMany { 3 | create(sourceInstance, values, options) { 4 | const association = this; 5 | 6 | options = options || {}; 7 | values = values || {}; 8 | 9 | if (Array.isArray(options)) { 10 | options = { 11 | fields: options 12 | }; 13 | } 14 | 15 | if (association.scope) { 16 | Object.assign(values, association.scope); 17 | if (options.fields) { 18 | options.fields = options.fields.concat(Object.keys(association.scope)); 19 | } 20 | } 21 | 22 | // Create the related model instance 23 | return association.target.create(values, options).then(newAssociatedObject => 24 | sourceInstance[association.accessors.add](newAssociatedObject, _.omit(options, ['fields'])).then(() => newAssociatedObject) 25 | ); 26 | } 27 | } 28 | ` 29 | 30 | export const options = { 31 | commentWorkarounds: true, 32 | } 33 | 34 | export const expected = ` 35 | class BelongsToMany { 36 | async create(sourceInstance, values, options) { 37 | const association = this; 38 | 39 | options = options || {}; 40 | values = values || {}; 41 | 42 | if (Array.isArray(options)) { 43 | options = { 44 | fields: options 45 | }; 46 | } 47 | 48 | if (association.scope) { 49 | Object.assign(values, association.scope); 50 | if (options.fields) { 51 | options.fields = options.fields.concat(Object.keys(association.scope)); 52 | } 53 | } 54 | 55 | // Create the related model instance 56 | const newAssociatedObject = await association.target.create(values, options); 57 | await sourceInstance[association.accessors.add](newAssociatedObject, _.omit(options, ['fields'])); 58 | return newAssociatedObject; 59 | } 60 | } 61 | ` 62 | -------------------------------------------------------------------------------- /test/fixtures/bugs_GetterAndSetterCannotBeAsync.ts: -------------------------------------------------------------------------------- 1 | export const input = ` 2 | class A { 3 | method() {return p.then(x => f(x))} 4 | get prop() {return p.then(x => f(x))} 5 | set prop(val) {return p.then(x => f(x))} 6 | get longchain() {return p.then(x => f(x)).then(y => g(y)).then(z => h(z))} 7 | } 8 | const obj = { 9 | method() {return p.then(x => f(x))}, 10 | get prop() {return p.then(x => f(x))}, 11 | set prop(val) {return p.then(x => f(x))} 12 | }; 13 | ` 14 | export const expected = ` 15 | class A { 16 | async method() { 17 | const x = await p; 18 | return f(x); 19 | } 20 | get prop() { 21 | return p.then(x => f(x)) 22 | } 23 | set prop(val) { 24 | return p.then(x => f(x)) 25 | } 26 | get longchain() { 27 | return (async () => { 28 | const x = await p 29 | const y = await f(x) 30 | const z = await g(y) 31 | return await h(z) 32 | })() 33 | } 34 | } 35 | const obj = { 36 | async method() { 37 | const x = await p; 38 | return f(x); 39 | }, 40 | get prop() { 41 | return p.then(x => f(x)) 42 | }, 43 | set prop(val) { 44 | return p.then(x => f(x)) 45 | } 46 | }; 47 | ` 48 | -------------------------------------------------------------------------------- /test/fixtures/bugs_PostgresConnectionManager.connect.ts: -------------------------------------------------------------------------------- 1 | export const input = ` 2 | class ConnectionManager { 3 | connect(config) { 4 | config.user = config.username; 5 | const connectionConfig = _.pick(config, [ 6 | 'user', 'password', 'host', 'database', 'port' 7 | ]); 8 | 9 | connectionConfig.types = { 10 | getTypeParser: ConnectionManager.prototype.getTypeParser.bind(this) 11 | }; 12 | 13 | if (config.dialectOptions) { 14 | _.merge(connectionConfig, 15 | _.pick(config.dialectOptions, [ 16 | 'application_name', 17 | 'ssl', 18 | 'client_encoding', 19 | 'binary', 20 | 'keepAlive', 21 | 'statement_timeout', 22 | 'idle_in_transaction_session_timeout' 23 | ])); 24 | } 25 | 26 | return new Promise((resolve, reject) => { 27 | let responded = false; 28 | 29 | const connection = new this.lib.Client(connectionConfig); 30 | 31 | const parameterHandler = message => { 32 | switch (message.parameterName) { 33 | case 'server_version': 34 | if (this.sequelize.options.databaseVersion === 0) { 35 | const version = semver.coerce(message.parameterValue).version; 36 | this.sequelize.options.databaseVersion = semver.valid(version) 37 | ? version 38 | : this.defaultVersion; 39 | } 40 | break; 41 | case 'standard_conforming_strings': 42 | connection['standard_conforming_strings'] = message.parameterValue; 43 | break; 44 | } 45 | }; 46 | 47 | const endHandler = () => { 48 | debug('connection timeout'); 49 | if (!responded) { 50 | reject(new sequelizeErrors.ConnectionTimedOutError(new Error('Connection timed out'))); 51 | } 52 | }; 53 | 54 | // If we didn't ever hear from the client.connect() callback the connection timeout 55 | // node-postgres does not treat this as an error since no active query was ever emitted 56 | connection.once('end', endHandler); 57 | 58 | if (!this.sequelize.config.native) { 59 | // Receive various server parameters for further configuration 60 | connection.connection.on('parameterStatus', parameterHandler); 61 | } 62 | 63 | connection.connect(err => { 64 | responded = true; 65 | 66 | if (!this.sequelize.config.native) { 67 | // remove parameter handler 68 | connection.connection.removeListener('parameterStatus', parameterHandler); 69 | } 70 | 71 | if (err) { 72 | if (err.code) { 73 | switch (err.code) { 74 | case 'ECONNREFUSED': 75 | reject(new sequelizeErrors.ConnectionRefusedError(err)); 76 | break; 77 | case 'ENOTFOUND': 78 | reject(new sequelizeErrors.HostNotFoundError(err)); 79 | break; 80 | case 'EHOSTUNREACH': 81 | reject(new sequelizeErrors.HostNotReachableError(err)); 82 | break; 83 | case 'EINVAL': 84 | reject(new sequelizeErrors.InvalidConnectionError(err)); 85 | break; 86 | default: 87 | reject(new sequelizeErrors.ConnectionError(err)); 88 | break; 89 | } 90 | } else { 91 | reject(new sequelizeErrors.ConnectionError(err)); 92 | } 93 | } else { 94 | debug('connection acquired'); 95 | connection.removeListener('end', endHandler); 96 | resolve(connection); 97 | } 98 | }); 99 | }).then(connection => { 100 | let query = ''; 101 | 102 | if (this.sequelize.options.standardConformingStrings !== false && connection['standard_conforming_strings'] !== 'on') { 103 | // Disable escape characters in strings 104 | // see https://github.com/sequelize/sequelize/issues/3545 (security issue) 105 | // see https://www.postgresql.org/docs/current/static/runtime-config-compatible.html#GUC-STANDARD-CONFORMING-STRINGS 106 | query += 'SET standard_conforming_strings=on;'; 107 | } 108 | 109 | if (this.sequelize.options.clientMinMessages !== false) { 110 | query += \`SET client_min_messages TO \${this.sequelize.options.clientMinMessages};\`; 111 | } 112 | 113 | if (!this.sequelize.config.keepDefaultTimezone) { 114 | const isZone = !!moment.tz.zone(this.sequelize.options.timezone); 115 | if (isZone) { 116 | query += \`SET TIME ZONE '\${this.sequelize.options.timezone}';\`; 117 | } else { 118 | query += \`SET TIME ZONE INTERVAL '\${this.sequelize.options.timezone}' HOUR TO MINUTE;\`; 119 | } 120 | } 121 | 122 | if (query) { 123 | return Promise.resolve(connection.query(query)).then(() => connection); 124 | } 125 | return connection; 126 | }).then(connection => { 127 | if (Object.keys(this.nameOidMap).length === 0 && 128 | this.enumOids.oids.length === 0 && 129 | this.enumOids.arrayOids.length === 0) { 130 | return Promise.resolve(this._refreshDynamicOIDs(connection)).then(() => connection); 131 | } 132 | return connection; 133 | }).then(connection => { 134 | // Don't let a Postgres restart (or error) to take down the whole app 135 | connection.on('error', error => { 136 | connection._invalid = true; 137 | debug(\`connection error \${error.code || error.message}\`); 138 | this.pool.destroy(connection); 139 | }); 140 | return connection; 141 | }); 142 | } 143 | } 144 | ` 145 | 146 | export const options = {} 147 | 148 | export const expected = ` 149 | class ConnectionManager { 150 | async connect(config) { 151 | config.user = config.username; 152 | const connectionConfig = _.pick(config, [ 153 | 'user', 'password', 'host', 'database', 'port' 154 | ]); 155 | 156 | connectionConfig.types = { 157 | getTypeParser: ConnectionManager.prototype.getTypeParser.bind(this) 158 | }; 159 | 160 | if (config.dialectOptions) { 161 | _.merge(connectionConfig, 162 | _.pick(config.dialectOptions, [ 163 | 'application_name', 164 | 'ssl', 165 | 'client_encoding', 166 | 'binary', 167 | 'keepAlive', 168 | 'statement_timeout', 169 | 'idle_in_transaction_session_timeout' 170 | ])); 171 | } 172 | 173 | let connection 174 | let connection0 175 | const connection1 = await new Promise((resolve, reject) => { 176 | let responded = false; 177 | 178 | const connection = new this.lib.Client(connectionConfig); 179 | 180 | const parameterHandler = message => { 181 | switch (message.parameterName) { 182 | case 'server_version': 183 | if (this.sequelize.options.databaseVersion === 0) { 184 | const version = semver.coerce(message.parameterValue).version; 185 | this.sequelize.options.databaseVersion = semver.valid(version) 186 | ? version 187 | : this.defaultVersion; 188 | } 189 | break; 190 | case 'standard_conforming_strings': 191 | connection['standard_conforming_strings'] = message.parameterValue; 192 | break; 193 | } 194 | }; 195 | 196 | const endHandler = () => { 197 | debug('connection timeout'); 198 | if (!responded) { 199 | reject(new sequelizeErrors.ConnectionTimedOutError(new Error('Connection timed out'))); 200 | } 201 | }; 202 | 203 | // If we didn't ever hear from the client.connect() callback the connection timeout 204 | // node-postgres does not treat this as an error since no active query was ever emitted 205 | connection.once('end', endHandler); 206 | 207 | if (!this.sequelize.config.native) { 208 | // Receive various server parameters for further configuration 209 | connection.connection.on('parameterStatus', parameterHandler); 210 | } 211 | 212 | connection.connect(err => { 213 | responded = true; 214 | 215 | if (!this.sequelize.config.native) { 216 | // remove parameter handler 217 | connection.connection.removeListener('parameterStatus', parameterHandler); 218 | } 219 | 220 | if (err) { 221 | if (err.code) { 222 | switch (err.code) { 223 | case 'ECONNREFUSED': 224 | reject(new sequelizeErrors.ConnectionRefusedError(err)); 225 | break; 226 | case 'ENOTFOUND': 227 | reject(new sequelizeErrors.HostNotFoundError(err)); 228 | break; 229 | case 'EHOSTUNREACH': 230 | reject(new sequelizeErrors.HostNotReachableError(err)); 231 | break; 232 | case 'EINVAL': 233 | reject(new sequelizeErrors.InvalidConnectionError(err)); 234 | break; 235 | default: 236 | reject(new sequelizeErrors.ConnectionError(err)); 237 | break; 238 | } 239 | } else { 240 | reject(new sequelizeErrors.ConnectionError(err)); 241 | } 242 | } else { 243 | debug('connection acquired'); 244 | connection.removeListener('end', endHandler); 245 | resolve(connection); 246 | } 247 | }); 248 | }); 249 | 250 | let query = ''; 251 | 252 | if (this.sequelize.options.standardConformingStrings !== false && connection1['standard_conforming_strings'] !== 'on') { 253 | // Disable escape characters in strings 254 | // see https://github.com/sequelize/sequelize/issues/3545 (security issue) 255 | // see https://www.postgresql.org/docs/current/static/runtime-config-compatible.html#GUC-STANDARD-CONFORMING-STRINGS 256 | query += 'SET standard_conforming_strings=on;'; 257 | } 258 | 259 | if (this.sequelize.options.clientMinMessages !== false) { 260 | query += \`SET client_min_messages TO \${this.sequelize.options.clientMinMessages};\`; 261 | } 262 | 263 | if (!this.sequelize.config.keepDefaultTimezone) { 264 | const isZone = !!moment.tz.zone(this.sequelize.options.timezone); 265 | if (isZone) { 266 | query += \`SET TIME ZONE '\${this.sequelize.options.timezone}';\`; 267 | } else { 268 | query += \`SET TIME ZONE INTERVAL '\${this.sequelize.options.timezone}' HOUR TO MINUTE;\`; 269 | } 270 | } 271 | 272 | if (query) { 273 | await connection1.query(query); 274 | connection0 = connection1; 275 | } else { 276 | connection0 = connection1; 277 | } 278 | if (Object.keys(this.nameOidMap).length === 0 && 279 | this.enumOids.oids.length === 0 && 280 | this.enumOids.arrayOids.length === 0) { 281 | await this._refreshDynamicOIDs(connection0); 282 | connection = await connection0; 283 | } else { 284 | connection = await connection0; 285 | } 286 | // Don't let a Postgres restart (or error) to take down the whole app 287 | connection.on('error', error => { 288 | connection._invalid = true; 289 | debug(\`connection error \${error.code || error.message}\`); 290 | this.pool.destroy(connection); 291 | }); 292 | return connection; 293 | } 294 | } 295 | ` 296 | -------------------------------------------------------------------------------- /test/fixtures/bugs_Sequelize.query.ts: -------------------------------------------------------------------------------- 1 | export const input = ` 2 | class Sequelize { 3 | query(sql, options) { 4 | options = Object.assign({}, this.options.query, options); 5 | 6 | if (options.instance && !options.model) { 7 | options.model = options.instance.constructor; 8 | } 9 | 10 | if (!options.instance && !options.model) { 11 | options.raw = true; 12 | } 13 | 14 | // map raw fields to model attributes 15 | if (options.mapToModel) { 16 | options.fieldMap = _.get(options, 'model.fieldAttributeMap', {}); 17 | } 18 | 19 | options = _.defaults(options, { 20 | // eslint-disable-next-line no-console 21 | logging: Object.prototype.hasOwnProperty.call(this.options, 'logging') ? this.options.logging : console.log, 22 | searchPath: Object.prototype.hasOwnProperty.call(this.options, 'searchPath') ? this.options.searchPath : 'DEFAULT' 23 | }); 24 | 25 | if (!options.type) { 26 | if (options.model || options.nest || options.plain) { 27 | options.type = QueryTypes.SELECT; 28 | } else { 29 | options.type = QueryTypes.RAW; 30 | } 31 | } 32 | 33 | //if dialect doesn't support search_path or dialect option 34 | //to prepend searchPath is not true delete the searchPath option 35 | if ( 36 | !this.dialect.supports.searchPath || 37 | !this.options.dialectOptions || 38 | !this.options.dialectOptions.prependSearchPath || 39 | options.supportsSearchPath === false 40 | ) { 41 | delete options.searchPath; 42 | } else if (!options.searchPath) { 43 | //if user wants to always prepend searchPath (dialectOptions.preprendSearchPath = true) 44 | //then set to DEFAULT if none is provided 45 | options.searchPath = 'DEFAULT'; 46 | } 47 | 48 | return Promise.resolve().then(() => { 49 | if (typeof sql === 'object') { 50 | if (sql.values !== undefined) { 51 | if (options.replacements !== undefined) { 52 | throw new Error('Both \`sql.values\` and \`options.replacements\` cannot be set at the same time'); 53 | } 54 | options.replacements = sql.values; 55 | } 56 | 57 | if (sql.bind !== undefined) { 58 | if (options.bind !== undefined) { 59 | throw new Error('Both \`sql.bind\` and \`options.bind\` cannot be set at the same time'); 60 | } 61 | options.bind = sql.bind; 62 | } 63 | 64 | if (sql.query !== undefined) { 65 | sql = sql.query; 66 | } 67 | } 68 | 69 | sql = sql.trim(); 70 | 71 | if (options.replacements && options.bind) { 72 | throw new Error('Both \`replacements\` and \`bind\` cannot be set at the same time'); 73 | } 74 | 75 | if (options.replacements) { 76 | if (Array.isArray(options.replacements)) { 77 | sql = Utils.format([sql].concat(options.replacements), this.options.dialect); 78 | } else { 79 | sql = Utils.formatNamedParameters(sql, options.replacements, this.options.dialect); 80 | } 81 | } 82 | 83 | let bindParameters; 84 | 85 | if (options.bind) { 86 | [sql, bindParameters] = this.dialect.Query.formatBindParameters(sql, options.bind, this.options.dialect); 87 | } 88 | 89 | const checkTransaction = () => { 90 | if (options.transaction && options.transaction.finished && !options.completesTransaction) { 91 | const error = new Error(\`\${options.transaction.finished} has been called on this transaction(\${options.transaction.id}), you can no longer use it. (The rejected query is attached as the 'sql' property of this error)\`); 92 | error.sql = sql; 93 | throw error; 94 | } 95 | }; 96 | 97 | const retryOptions = Object.assign({}, this.options.retry, options.retry || {}); 98 | 99 | return Promise.resolve(retry(() => Promise.resolve().then(() => { 100 | if (options.transaction === undefined && Sequelize._cls) { 101 | options.transaction = Sequelize._cls.get('transaction'); 102 | } 103 | 104 | checkTransaction(); 105 | 106 | return options.transaction 107 | ? options.transaction.connection 108 | : this.connectionManager.getConnection(options); 109 | }).then(connection => { 110 | const query = new this.dialect.Query(connection, this, options); 111 | return this.runHooks('beforeQuery', options, query) 112 | .then(() => checkTransaction()) 113 | .then(() => query.run(sql, bindParameters)) 114 | .finally(() => this.runHooks('afterQuery', options, query)) 115 | .finally(() => { 116 | if (!options.transaction) { 117 | return this.connectionManager.releaseConnection(connection); 118 | } 119 | }); 120 | }), retryOptions)); 121 | }); 122 | } 123 | } 124 | ` 125 | 126 | export const options = {} 127 | 128 | export const expected = ` 129 | class Sequelize { 130 | async query(sql, options) { 131 | options = Object.assign({}, this.options.query, options); 132 | 133 | if (options.instance && !options.model) { 134 | options.model = options.instance.constructor; 135 | } 136 | 137 | if (!options.instance && !options.model) { 138 | options.raw = true; 139 | } 140 | 141 | // map raw fields to model attributes 142 | if (options.mapToModel) { 143 | options.fieldMap = _.get(options, 'model.fieldAttributeMap', {}); 144 | } 145 | 146 | options = _.defaults(options, { 147 | // eslint-disable-next-line no-console 148 | logging: Object.prototype.hasOwnProperty.call(this.options, 'logging') ? this.options.logging : console.log, 149 | searchPath: Object.prototype.hasOwnProperty.call(this.options, 'searchPath') ? this.options.searchPath : 'DEFAULT' 150 | }); 151 | 152 | if (!options.type) { 153 | if (options.model || options.nest || options.plain) { 154 | options.type = QueryTypes.SELECT; 155 | } else { 156 | options.type = QueryTypes.RAW; 157 | } 158 | } 159 | 160 | //if dialect doesn't support search_path or dialect option 161 | //to prepend searchPath is not true delete the searchPath option 162 | if ( 163 | !this.dialect.supports.searchPath || 164 | !this.options.dialectOptions || 165 | !this.options.dialectOptions.prependSearchPath || 166 | options.supportsSearchPath === false 167 | ) { 168 | delete options.searchPath; 169 | } else if (!options.searchPath) { 170 | //if user wants to always prepend searchPath (dialectOptions.preprendSearchPath = true) 171 | //then set to DEFAULT if none is provided 172 | options.searchPath = 'DEFAULT'; 173 | } 174 | 175 | if (typeof sql === 'object') { 176 | if (sql.values !== undefined) { 177 | if (options.replacements !== undefined) { 178 | throw new Error('Both \`sql.values\` and \`options.replacements\` cannot be set at the same time'); 179 | } 180 | options.replacements = sql.values; 181 | } 182 | 183 | if (sql.bind !== undefined) { 184 | if (options.bind !== undefined) { 185 | throw new Error('Both \`sql.bind\` and \`options.bind\` cannot be set at the same time'); 186 | } 187 | options.bind = sql.bind; 188 | } 189 | 190 | if (sql.query !== undefined) { 191 | sql = sql.query; 192 | } 193 | } 194 | 195 | sql = sql.trim(); 196 | 197 | if (options.replacements && options.bind) { 198 | throw new Error('Both \`replacements\` and \`bind\` cannot be set at the same time'); 199 | } 200 | 201 | if (options.replacements) { 202 | if (Array.isArray(options.replacements)) { 203 | sql = Utils.format([sql].concat(options.replacements), this.options.dialect); 204 | } else { 205 | sql = Utils.formatNamedParameters(sql, options.replacements, this.options.dialect); 206 | } 207 | } 208 | 209 | let bindParameters; 210 | 211 | if (options.bind) { 212 | [sql, bindParameters] = this.dialect.Query.formatBindParameters(sql, options.bind, this.options.dialect); 213 | } 214 | 215 | const checkTransaction = () => { 216 | if (options.transaction && options.transaction.finished && !options.completesTransaction) { 217 | const error = new Error(\`\${options.transaction.finished} has been called on this transaction(\${options.transaction.id}), you can no longer use it. (The rejected query is attached as the 'sql' property of this error)\`); 218 | error.sql = sql; 219 | throw error; 220 | } 221 | }; 222 | 223 | const retryOptions = Object.assign({}, this.options.retry, options.retry || {}); 224 | 225 | return retry(async () => { 226 | if (options.transaction === undefined && Sequelize._cls) { 227 | options.transaction = Sequelize._cls.get('transaction'); 228 | } 229 | 230 | checkTransaction(); 231 | 232 | const connection = await (options.transaction 233 | ? options.transaction.connection 234 | : this.connectionManager.getConnection(options)); 235 | const query = new this.dialect.Query(connection, this, options); 236 | try { 237 | await this.runHooks('beforeQuery', options, query) 238 | await checkTransaction() 239 | return await query.run(sql, bindParameters) 240 | } finally { 241 | await this.runHooks('afterQuery', options, query) 242 | if (!options.transaction) { 243 | await this.connectionManager.releaseConnection(connection); 244 | } 245 | } 246 | }, retryOptions); 247 | } 248 | } 249 | ` 250 | -------------------------------------------------------------------------------- /test/fixtures/bugs_Sequelize.transaction.ts: -------------------------------------------------------------------------------- 1 | export const input = ` 2 | class Sequelize { 3 | transaction(options, autoCallback) { 4 | if (typeof options === 'function') { 5 | autoCallback = options; 6 | options = undefined; 7 | } 8 | 9 | const transaction = new Transaction(this, options); 10 | 11 | if (!autoCallback) return transaction.prepareEnvironment(false).then(() => transaction); 12 | 13 | // autoCallback provided 14 | return Sequelize._clsRun(() => { 15 | return transaction.prepareEnvironment() 16 | .then(() => autoCallback(transaction)) 17 | .then(result => Promise.resolve(transaction.commit()).then(() => result)) 18 | .catch(err => { 19 | // Rollback transaction if not already finished (commit, rollback, etc) 20 | // and reject with original error (ignore any error in rollback) 21 | return Promise.resolve().then(() => { 22 | if (!transaction.finished) return transaction.rollback().catch(() => {}); 23 | }).then(() => { throw err; }); 24 | }); 25 | }); 26 | } 27 | } 28 | ` 29 | 30 | export const options = { 31 | commentWorkarounds: true, 32 | } 33 | 34 | export const expected = ` 35 | class Sequelize { 36 | async transaction(options, autoCallback) { 37 | if (typeof options === 'function') { 38 | autoCallback = options; 39 | options = undefined; 40 | } 41 | 42 | const transaction = new Transaction(this, options); 43 | 44 | if (!autoCallback) { 45 | await transaction.prepareEnvironment(false) 46 | return transaction; 47 | } 48 | 49 | // autoCallback provided 50 | return Sequelize._clsRun(async () => { 51 | try { 52 | await transaction.prepareEnvironment(); 53 | const result = await autoCallback(transaction); 54 | await transaction.commit(); 55 | return result; 56 | } catch (err) { 57 | // Rollback transaction if not already finished (commit, rollback, etc) 58 | // and reject with original error (ignore any error in rollback) 59 | if (!transaction.finished) await transaction.rollback().catch(() => {}); 60 | throw err; 61 | } 62 | }); 63 | } 64 | } 65 | ` 66 | -------------------------------------------------------------------------------- /test/fixtures/bugs_canDefinitelyInvoke_infiniteLoop.ts: -------------------------------------------------------------------------------- 1 | export const input = ` 2 | /** 3 | * Methods that runs all checks one by one and returns a result of checks 4 | * as an array of Requirement objects. This method intended to be used by cordova-lib check_reqs method 5 | * 6 | * @return Promise Array of requirements. Due to implementation, promise is always fulfilled. 7 | */ 8 | module.exports.check_all = function () { 9 | var requirements = [ 10 | new Requirement('java', 'Java JDK'), 11 | new Requirement('androidSdk', 'Android SDK'), 12 | new Requirement('androidTarget', 'Android target'), 13 | new Requirement('gradle', 'Gradle') 14 | ]; 15 | 16 | var checkFns = [ 17 | this.check_java, 18 | this.check_android, 19 | this.check_android_target, 20 | this.check_gradle 21 | ]; 22 | 23 | // Then execute requirement checks one-by-one 24 | return checkFns.reduce(function (promise, checkFn, idx) { 25 | // Update each requirement with results 26 | var requirement = requirements[idx]; 27 | return promise.then(checkFn).then(function (version) { 28 | requirement.installed = true; 29 | requirement.metadata.version = version; 30 | }, function (err) { 31 | requirement.metadata.reason = err instanceof Error ? err.message : err; 32 | }); 33 | }, Promise.resolve()).then(function () { 34 | // When chain is completed, return requirements array to upstream API 35 | return requirements; 36 | }); 37 | }; 38 | ` 39 | 40 | export const options = {} 41 | 42 | export const expected = ` 43 | /** 44 | * Methods that runs all checks one by one and returns a result of checks 45 | * as an array of Requirement objects. This method intended to be used by cordova-lib check_reqs method 46 | * 47 | * @return Promise Array of requirements. Due to implementation, promise is always fulfilled. 48 | */ 49 | module.exports.check_all = async function() { 50 | var requirements = [ 51 | new Requirement('java', 'Java JDK'), 52 | new Requirement('androidSdk', 'Android SDK'), 53 | new Requirement('androidTarget', 'Android target'), 54 | new Requirement('gradle', 'Gradle'), 55 | ] 56 | var checkFns = [ 57 | this.check_java, 58 | this.check_android, 59 | this.check_android_target, 60 | this.check_gradle, 61 | ] 62 | await checkFns.reduce(async function(promise, checkFn, idx) { 63 | // Update each requirement with results 64 | var requirement = requirements[idx] 65 | try { 66 | const version = await promise.then(checkFn) 67 | requirement.installed = true 68 | requirement.metadata.version = version 69 | } catch (err) { 70 | requirement.metadata.reason = err instanceof Error ? err.message : err 71 | } 72 | }, Promise.resolve()) 73 | // Then execute requirement checks one-by-one 74 | return requirements 75 | } 76 | ` 77 | -------------------------------------------------------------------------------- /test/fixtures/bugs_findAll.test.ts: -------------------------------------------------------------------------------- 1 | export const input = ` 2 | it('should support many levels of belongsTo', function() { 3 | const A = this.sequelize.define('a', {}), 4 | B = this.sequelize.define('b', {}), 5 | C = this.sequelize.define('c', {}), 6 | D = this.sequelize.define('d', {}), 7 | E = this.sequelize.define('e', {}), 8 | F = this.sequelize.define('f', {}), 9 | G = this.sequelize.define('g', {}), 10 | H = this.sequelize.define('h', {}); 11 | 12 | A.belongsTo(B); 13 | B.belongsTo(C); 14 | C.belongsTo(D); 15 | D.belongsTo(E); 16 | E.belongsTo(F); 17 | F.belongsTo(G); 18 | G.belongsTo(H); 19 | 20 | return this.sequelize.sync({ force: true }).then(() => { 21 | return Promise.all([A.bulkCreate([ 22 | {}, 23 | {}, 24 | {}, 25 | {}, 26 | {}, 27 | {}, 28 | {}, 29 | {} 30 | ]).then(() => { 31 | return A.findAll(); 32 | }), (function(singles) { 33 | let promise = Promise.resolve(), 34 | previousInstance, 35 | b; 36 | 37 | singles.forEach(model => { 38 | promise = promise.then(() => { 39 | return model.create({}).then(instance => { 40 | if (previousInstance) { 41 | return previousInstance[\`set\${_.upperFirst(model.name)}\`](instance).then(() => { 42 | previousInstance = instance; 43 | }); 44 | } 45 | previousInstance = b = instance; 46 | }); 47 | }); 48 | }); 49 | 50 | promise = promise.then(() => { 51 | return b; 52 | }); 53 | 54 | return promise; 55 | })([B, C, D, E, F, G, H])]).then(([as, b]) => { 56 | return Promise.all(as.map(a => { 57 | return a.setB(b); 58 | })); 59 | }).then(() => { 60 | return A.findAll({ 61 | include: [ 62 | { model: B, include: [ 63 | { model: C, include: [ 64 | { model: D, include: [ 65 | { model: E, include: [ 66 | { model: F, include: [ 67 | { model: G, include: [ 68 | { model: H } 69 | ] } 70 | ] } 71 | ] } 72 | ] } 73 | ] } 74 | ] } 75 | ] 76 | }).then(as => { 77 | expect(as.length).to.be.ok; 78 | 79 | as.forEach(a => { 80 | expect(a.b.c.d.e.f.g.h).to.be.ok; 81 | }); 82 | }); 83 | }); 84 | }); 85 | }); 86 | ` 87 | 88 | export const options = {} 89 | 90 | export const expected = ` 91 | it('should support many levels of belongsTo', async function() { 92 | const A = this.sequelize.define('a', {}), 93 | B = this.sequelize.define('b', {}), 94 | C = this.sequelize.define('c', {}), 95 | D = this.sequelize.define('d', {}), 96 | E = this.sequelize.define('e', {}), 97 | F = this.sequelize.define('f', {}), 98 | G = this.sequelize.define('g', {}), 99 | H = this.sequelize.define('h', {}); 100 | 101 | A.belongsTo(B); 102 | B.belongsTo(C); 103 | C.belongsTo(D); 104 | D.belongsTo(E); 105 | E.belongsTo(F); 106 | F.belongsTo(G); 107 | G.belongsTo(H); 108 | 109 | await this.sequelize.sync({ force: true }); 110 | 111 | const [as0, b] = await Promise.all([ 112 | A.bulkCreate([{}, {}, {}, {}, {}, {}, {}, {}]).then(() => { 113 | return A.findAll() 114 | }), 115 | (function(singles) { 116 | let promise = Promise.resolve(), 117 | previousInstance, 118 | b; 119 | 120 | singles.forEach(model => { 121 | promise = (async () => { 122 | await promise; 123 | const instance = await model.create({}); 124 | if (previousInstance) { 125 | await previousInstance[\`set\${_.upperFirst(model.name)}\`](instance); 126 | previousInstance = instance; 127 | return 128 | } 129 | previousInstance = b = instance; 130 | })(); 131 | }); 132 | 133 | promise = promise.then(() => { 134 | return b; 135 | }); 136 | 137 | return promise; 138 | })([B, C, D, E, F, G, H])]); 139 | 140 | await Promise.all(as0.map(a => { 141 | return a.setB(b); 142 | })); 143 | 144 | const as = await A.findAll({ 145 | include: [ 146 | { model: B, include: [ 147 | { model: C, include: [ 148 | { model: D, include: [ 149 | { model: E, include: [ 150 | { model: F, include: [ 151 | { model: G, include: [ 152 | { model: H } 153 | ] } 154 | ] } 155 | ] } 156 | ] } 157 | ] } 158 | ] } 159 | ] 160 | }); 161 | 162 | expect(as.length).to.be.ok; 163 | 164 | as.forEach(a => { 165 | expect(a.b.c.d.e.f.g.h).to.be.ok; 166 | }); 167 | }); 168 | ` 169 | -------------------------------------------------------------------------------- /test/fixtures/bugs_promiseProps.ts: -------------------------------------------------------------------------------- 1 | export const input = ` 2 | it('should support ordering with only belongsTo includes', function() { 3 | const User = this.sequelize.define('User', {}), 4 | Item = this.sequelize.define('Item', { 'test': DataTypes.STRING }), 5 | Order = this.sequelize.define('Order', { 'position': DataTypes.INTEGER }); 6 | 7 | User.belongsTo(Item, { 'as': 'itemA', foreignKey: 'itemA_id' }); 8 | User.belongsTo(Item, { 'as': 'itemB', foreignKey: 'itemB_id' }); 9 | User.belongsTo(Order); 10 | 11 | return this.sequelize.sync().then(() => { 12 | return promiseProps({ 13 | users: User.bulkCreate([{}, {}, {}]).then(() => { 14 | return User.findAll(); 15 | }), 16 | items: Item.bulkCreate([ 17 | { 'test': 'abc' }, 18 | { 'test': 'def' }, 19 | { 'test': 'ghi' }, 20 | { 'test': 'jkl' } 21 | ]).then(() => { 22 | return Item.findAll({ order: ['id'] }); 23 | }), 24 | orders: Order.bulkCreate([ 25 | { 'position': 2 }, 26 | { 'position': 3 }, 27 | { 'position': 1 } 28 | ]).then(() => { 29 | return Order.findAll({ order: ['id'] }); 30 | }) 31 | }).then(results => { 32 | const user1 = results.users[0]; 33 | const user2 = results.users[1]; 34 | const user3 = results.users[2]; 35 | 36 | const item1 = results.items[0]; 37 | const item2 = results.items[1]; 38 | const item3 = results.items[2]; 39 | const item4 = results.items[3]; 40 | 41 | const order1 = results.orders[0]; 42 | const order2 = results.orders[1]; 43 | const order3 = results.orders[2]; 44 | 45 | return Promise.all([ 46 | user1.setItemA(item1), 47 | user1.setItemB(item2), 48 | user1.setOrder(order3), 49 | user2.setItemA(item3), 50 | user2.setItemB(item4), 51 | user2.setOrder(order2), 52 | user3.setItemA(item1), 53 | user3.setItemB(item4), 54 | user3.setOrder(order1) 55 | ]); 56 | }).then(() => { 57 | return User.findAll({ 58 | 'include': [ 59 | { 'model': Item, 'as': 'itemA', where: { test: 'abc' } }, 60 | { 'model': Item, 'as': 'itemB' }, 61 | Order], 62 | 'order': [ 63 | [Order, 'position'] 64 | ] 65 | }).then(as => { 66 | expect(as.length).to.eql(2); 67 | 68 | expect(as[0].itemA.test).to.eql('abc'); 69 | expect(as[1].itemA.test).to.eql('abc'); 70 | 71 | expect(as[0].Order.position).to.eql(1); 72 | expect(as[1].Order.position).to.eql(2); 73 | }); 74 | }); 75 | }); 76 | }); 77 | ` 78 | 79 | export const options = {} 80 | 81 | export const expected = ` 82 | it('should support ordering with only belongsTo includes', async function() { 83 | const User = this.sequelize.define('User', {}), 84 | Item = this.sequelize.define('Item', { 'test': DataTypes.STRING }), 85 | Order = this.sequelize.define('Order', { 'position': DataTypes.INTEGER }); 86 | 87 | User.belongsTo(Item, { 'as': 'itemA', foreignKey: 'itemA_id' }); 88 | User.belongsTo(Item, { 'as': 'itemB', foreignKey: 'itemB_id' }); 89 | User.belongsTo(Order); 90 | 91 | await this.sequelize.sync(); 92 | 93 | const results = await promiseProps({ 94 | users: User.bulkCreate([{}, {}, {}]).then(() => { 95 | return User.findAll() 96 | }), 97 | items: Item.bulkCreate([ 98 | { 'test': 'abc' }, 99 | { 'test': 'def' }, 100 | { 'test': 'ghi' }, 101 | { 'test': 'jkl' } 102 | ]).then(() => { 103 | return Item.findAll({ order: ['id'] }) 104 | }), 105 | orders: Order.bulkCreate([ 106 | { 'position': 2 }, 107 | { 'position': 3 }, 108 | { 'position': 1 } 109 | ]).then(() => { 110 | return Order.findAll({ order: ['id'] }); 111 | }) 112 | }); 113 | 114 | const user1 = results.users[0]; 115 | const user2 = results.users[1]; 116 | const user3 = results.users[2]; 117 | 118 | const item1 = results.items[0]; 119 | const item2 = results.items[1]; 120 | const item3 = results.items[2]; 121 | const item4 = results.items[3]; 122 | 123 | const order1 = results.orders[0]; 124 | const order2 = results.orders[1]; 125 | const order3 = results.orders[2]; 126 | 127 | await Promise.all([ 128 | user1.setItemA(item1), 129 | user1.setItemB(item2), 130 | user1.setOrder(order3), 131 | user2.setItemA(item3), 132 | user2.setItemB(item4), 133 | user2.setOrder(order2), 134 | user3.setItemA(item1), 135 | user3.setItemB(item4), 136 | user3.setOrder(order1) 137 | ]); 138 | 139 | const as = await User.findAll({ 140 | 'include': [ 141 | { 'model': Item, 'as': 'itemA', where: { test: 'abc' } }, 142 | { 'model': Item, 'as': 'itemB' }, 143 | Order], 144 | 'order': [ 145 | [Order, 'position'] 146 | ] 147 | }); 148 | 149 | expect(as.length).to.eql(2); 150 | 151 | expect(as[0].itemA.test).to.eql('abc'); 152 | expect(as[1].itemA.test).to.eql('abc'); 153 | 154 | expect(as[0].Order.position).to.eql(1); 155 | expect(as[1].Order.position).to.eql(2); 156 | }); 157 | ` 158 | -------------------------------------------------------------------------------- /test/fixtures/bugs_reassignedHandlerArgument.ts: -------------------------------------------------------------------------------- 1 | export const input = ` 2 | function foo() { 3 | return bar.then(baz => { 4 | baz = 3 5 | }) 6 | } 7 | ` 8 | 9 | export const options = {} 10 | 11 | export const expected = ` 12 | async function foo() { 13 | let baz = await bar 14 | baz = 3 15 | } 16 | ` 17 | -------------------------------------------------------------------------------- /test/fixtures/bugs_validateAndRunHooks.ts: -------------------------------------------------------------------------------- 1 | export const input = ` 2 | class Test { 3 | _validateAndRunHooks() { 4 | const runHooks = this.modelInstance.constructor.runHooks.bind(this.modelInstance.constructor); 5 | return runHooks('beforeValidate', this.modelInstance, this.options) 6 | .then(() => 7 | this._validate() 8 | .catch(error => runHooks('validationFailed', this.modelInstance, this.options, error) 9 | .then(newError => { throw newError || error; })) 10 | ) 11 | .then(() => runHooks('afterValidate', this.modelInstance, this.options)).then(() => this.modelInstance); 12 | } 13 | } 14 | ` 15 | 16 | export const options = {} 17 | 18 | export const expected = ` 19 | class Test { 20 | async _validateAndRunHooks() { 21 | const runHooks = this.modelInstance.constructor.runHooks.bind(this.modelInstance.constructor); 22 | await runHooks('beforeValidate', this.modelInstance, this.options) 23 | try { 24 | await this._validate() 25 | } catch (error) { 26 | const newError = await runHooks('validationFailed', this.modelInstance, this.options, error) 27 | throw newError || error 28 | } 29 | await runHooks('afterValidate', this.modelInstance, this.options) 30 | return this.modelInstance 31 | } 32 | } 33 | ` 34 | -------------------------------------------------------------------------------- /test/fixtures/catchExpressionBody_IgnoredError.ts: -------------------------------------------------------------------------------- 1 | export const input = ` 2 | function foo(err) { 3 | return baz.catch(() => err.message) 4 | } 5 | ` 6 | 7 | export const options = {} 8 | 9 | export const expected = ` 10 | async function foo(err) { 11 | try { 12 | return await baz 13 | } catch (err0) { 14 | return err.message 15 | } 16 | } 17 | ` 18 | -------------------------------------------------------------------------------- /test/fixtures/catchExpressionBody_Returned.ts: -------------------------------------------------------------------------------- 1 | export const input = ` 2 | const handleError = () => {} 3 | function foo() { 4 | return baz.catch(handleError) 5 | } 6 | ` 7 | 8 | export const options = {} 9 | 10 | export const expected = ` 11 | const handleError = () => {} 12 | async function foo() { 13 | try { 14 | return await baz 15 | } catch (err) { 16 | return handleError(err) 17 | } 18 | } 19 | ` 20 | -------------------------------------------------------------------------------- /test/fixtures/catch_AssignedToDestructuring.ts: -------------------------------------------------------------------------------- 1 | export const input = ` 2 | async function foo() { 3 | const {stuff} = await baz.catch(value => { 4 | if (value instanceof Blargh) return processBlargh(value) 5 | else return processOther(value) 6 | }) 7 | } 8 | ` 9 | 10 | export const options = {} 11 | 12 | export const expected = ` 13 | async function foo() { 14 | let result 15 | try { 16 | result = await baz 17 | } catch (value) { 18 | if (value instanceof Blargh) result = await processBlargh(value) 19 | else result = await processOther(value) 20 | } 21 | const {stuff} = result 22 | } 23 | ` 24 | -------------------------------------------------------------------------------- /test/fixtures/catch_AssignedToIdentifier.ts: -------------------------------------------------------------------------------- 1 | export const input = ` 2 | async function foo() { 3 | let bar = 3 4 | bar = await baz.catch(value => { 5 | if (value instanceof Blargh) return processBlargh(value) 6 | else return processOther(value) 7 | }) 8 | } 9 | ` 10 | 11 | export const options = {} 12 | 13 | export const expected = ` 14 | async function foo() { 15 | let bar = 3 16 | try { 17 | bar = await baz 18 | } catch (value) { 19 | if (value instanceof Blargh) bar = await processBlargh(value) 20 | else bar = await processOther(value) 21 | } 22 | } 23 | ` 24 | -------------------------------------------------------------------------------- /test/fixtures/catch_AssignedToVariableDeclarator.ts: -------------------------------------------------------------------------------- 1 | export const input = ` 2 | async function foo() { 3 | const bar = await baz.catch(value => { 4 | if (value instanceof Blargh) return processBlargh(value) 5 | else return processOther(value) 6 | }) 7 | } 8 | ` 9 | 10 | export const options = {} 11 | 12 | export const expected = ` 13 | async function foo() { 14 | let bar 15 | try { 16 | bar = await baz 17 | } catch (value) { 18 | if (value instanceof Blargh) bar = await processBlargh(value) 19 | else bar = await processOther(value) 20 | } 21 | } 22 | ` 23 | -------------------------------------------------------------------------------- /test/fixtures/catch_NestedExpression.ts: -------------------------------------------------------------------------------- 1 | export const input = ` 2 | async function foo() { 3 | return process(await baz.catch(value => { 4 | if (value instanceof Blargh) return processBlargh(value) 5 | else return processOther(value) 6 | })) 7 | } 8 | ` 9 | 10 | export const options = {} 11 | 12 | export const expected = ` 13 | async function foo() { 14 | let result 15 | try { 16 | result = await baz 17 | } catch (value) { 18 | if (value instanceof Blargh) result = await processBlargh(value) 19 | else result = await processOther(value) 20 | } 21 | return process(result) 22 | } 23 | ` 24 | -------------------------------------------------------------------------------- /test/fixtures/catch_Returned.ts: -------------------------------------------------------------------------------- 1 | export const input = ` 2 | function foo() { 3 | return baz.catch(value => { 4 | if (value instanceof Blargh) return processBlargh(value) 5 | else return processOther(value) 6 | }) 7 | } 8 | ` 9 | 10 | export const options = {} 11 | 12 | export const expected = ` 13 | async function foo() { 14 | try { 15 | return await baz 16 | } catch (value) { 17 | if (value instanceof Blargh) return processBlargh(value) 18 | else return processOther(value) 19 | } 20 | } 21 | ` 22 | -------------------------------------------------------------------------------- /test/fixtures/catch_Swallow.ts: -------------------------------------------------------------------------------- 1 | export const input = ` 2 | function foo() { 3 | return baz.catch(() => {}) 4 | } 5 | ` 6 | 7 | export const options = {} 8 | 9 | export const expected = ` 10 | async function foo() { 11 | return baz.catch(() => {}) 12 | } 13 | ` 14 | -------------------------------------------------------------------------------- /test/fixtures/catch_Unconsumed.ts: -------------------------------------------------------------------------------- 1 | export const input = ` 2 | async function foo() { 3 | await baz.catch(value => { 4 | if (value instanceof Blargh) return processBlargh(value) 5 | else return processOther(value) 6 | }) 7 | } 8 | ` 9 | 10 | export const options = {} 11 | 12 | export const expected = ` 13 | async function foo() { 14 | try { 15 | await baz 16 | } catch (value) { 17 | if (value instanceof Blargh) await processBlargh(value) 18 | else await processOther(value) 19 | } 20 | } 21 | ` 22 | -------------------------------------------------------------------------------- /test/fixtures/catch_handlersThatCanBeUnwound_Returned.ts: -------------------------------------------------------------------------------- 1 | export const input = ` 2 | function f1() { 3 | return bar.catch(baz => { 4 | switch (baz) { 5 | case 2: return 6 | } 7 | }) 8 | } 9 | function f2() { 10 | return a.catch(b => { 11 | for (const i of [1, 2, 3]) { 12 | return 13 | } 14 | }) 15 | } 16 | function f3() { 17 | return a.catch(b => { 18 | while (i) { 19 | return 20 | } 21 | }) 22 | } 23 | function f4() { 24 | return a.catch(b => { 25 | if (a) { 26 | if (b) { 27 | return 28 | } 29 | } 30 | }) 31 | } 32 | ` 33 | 34 | export const options = {} 35 | 36 | export const expected = ` 37 | async function f1() { 38 | try { 39 | return await bar 40 | } catch (baz) { 41 | switch (baz) { 42 | case 2: 43 | return 44 | } 45 | } 46 | } 47 | async function f2() { 48 | try { 49 | return await a 50 | } catch (b) { 51 | for (const i of [1, 2, 3]) { 52 | return 53 | } 54 | } 55 | } 56 | async function f3() { 57 | try { 58 | return await a 59 | } catch (b) { 60 | while (i) { 61 | return 62 | } 63 | } 64 | } 65 | async function f4() { 66 | try { 67 | return await a 68 | } catch (b) { 69 | if (a) { 70 | if (b) { 71 | return 72 | } 73 | } 74 | } 75 | } 76 | ` 77 | -------------------------------------------------------------------------------- /test/fixtures/catch_handlersThatCantBeUnwound.ts: -------------------------------------------------------------------------------- 1 | export const input = ` 2 | function foo() { 3 | return bar.catch(baz => { 4 | switch (baz) { 5 | case 2: return 6 | } 7 | console.log('test') 8 | }).then(String) 9 | } 10 | function bar() { 11 | return a.catch(b => { 12 | for (const i of [1, 2, 3]) { 13 | return 14 | } 15 | console.log('test') 16 | }).then(String) 17 | } 18 | function qux() { 19 | return a.catch(b => { 20 | while (i) { 21 | return 22 | } 23 | console.log('test') 24 | }).then(String) 25 | } 26 | function baz() { 27 | return a.catch(b => { 28 | if (a) { 29 | if (b) { 30 | return 31 | } 32 | } 33 | console.log('test') 34 | }).then(String) 35 | } 36 | ` 37 | 38 | export const options = {} 39 | 40 | export const expected = ` 41 | async function foo() { 42 | return bar.catch(baz => { 43 | switch (baz) { 44 | case 2: return 45 | } 46 | console.log('test') 47 | }).then(String) 48 | } 49 | async function bar() { 50 | return a.catch(b => { 51 | for (const i of [1, 2, 3]) { 52 | return 53 | } 54 | console.log('test') 55 | }).then(String) 56 | } 57 | async function qux() { 58 | return a.catch(b => { 59 | while (i) { 60 | return 61 | } 62 | console.log('test') 63 | }).then(String) 64 | } 65 | async function baz() { 66 | return a.catch(b => { 67 | if (a) { 68 | if (b) { 69 | return 70 | } 71 | } 72 | console.log('test') 73 | }).then(String) 74 | } 75 | ` 76 | -------------------------------------------------------------------------------- /test/fixtures/catch_lastHandlerCanAlwaysBeUnwound.ts: -------------------------------------------------------------------------------- 1 | export const input = ` 2 | function foo() { 3 | return bar.catch(baz => { 4 | switch (baz) { 5 | case 2: return 6 | } 7 | console.log('test') 8 | }) 9 | } 10 | function bar() { 11 | return a.catch(b => { 12 | for (const i of [1, 2, 3]) { 13 | return 14 | } 15 | console.log('test') 16 | }) 17 | } 18 | function qux() { 19 | return a.catch(b => { 20 | while (i) { 21 | return 22 | } 23 | console.log('test') 24 | }) 25 | } 26 | function baz() { 27 | return a.catch(b => { 28 | if (a) { 29 | if (b) { 30 | return 31 | } 32 | } 33 | console.log('test') 34 | }) 35 | } 36 | ` 37 | 38 | export const options = {} 39 | 40 | export const expected = ` 41 | async function foo() { 42 | try { 43 | return await bar 44 | } catch(baz0) { 45 | switch (baz0) { 46 | case 2: return 47 | } 48 | console.log('test') 49 | } 50 | } 51 | async function bar() { 52 | try { 53 | return await a 54 | } catch(b) { 55 | for (const i of [1, 2, 3]) { 56 | return 57 | } 58 | console.log('test') 59 | } 60 | } 61 | async function qux() { 62 | try { 63 | return await a 64 | } catch (b) { 65 | while (i) { 66 | return 67 | } 68 | console.log('test') 69 | } 70 | } 71 | async function baz() { 72 | try { 73 | return await a 74 | } catch (b) { 75 | if (a) { 76 | if (b) { 77 | return 78 | } 79 | } 80 | console.log('test') 81 | } 82 | } 83 | ` 84 | -------------------------------------------------------------------------------- /test/fixtures/conditionalReturnCatch_AssignedToIdentifier.ts: -------------------------------------------------------------------------------- 1 | export const input = ` 2 | async function createUser(args) { 3 | const {username, groups} = args 4 | const user = await Users.create({username}) 5 | .then(user => { 6 | if (groups) { 7 | return addUserToGroups(user, groups) 8 | } 9 | console.log('blah') 10 | return user 11 | }) 12 | .then(user => { 13 | if (groups) { 14 | console.log('test') 15 | } else { 16 | return 'noGroups' 17 | } 18 | return 'user' 19 | }) 20 | .catch(err => { 21 | console.error(err.stack) 22 | return dummyUser() 23 | }) 24 | } 25 | ` 26 | 27 | export const options = {} 28 | 29 | export const expected = ` 30 | async function createUser(args) { 31 | const {username, groups} = args 32 | let user 33 | try { 34 | let user0 35 | const user1 = await Users.create({ username }) 36 | if (groups) { 37 | user0 = await addUserToGroups(user1, groups) 38 | } else { 39 | console.log('blah') 40 | user0 = user1 41 | } 42 | if (groups) { 43 | console.log('test') 44 | user = 'user' 45 | } else { 46 | user = 'noGroups' 47 | } 48 | } catch (err) { 49 | console.error(err.stack) 50 | user = await dummyUser() 51 | } 52 | } 53 | ` 54 | -------------------------------------------------------------------------------- /test/fixtures/conditionalReturnCatch_ElseFallsThrough.ts: -------------------------------------------------------------------------------- 1 | export const input = ` 2 | async function createUser(args) { 3 | const {username, groups} = args 4 | const user = await Users.create({username}) 5 | .then(user => { 6 | if (groups) { 7 | console.log('a') 8 | return addUserToGroups(user, groups) 9 | } else if (foo) { 10 | console.log('b') 11 | return addUserToGroups(user, groups) 12 | } else if (bar) { 13 | console.log('c') 14 | return addUserToGroups(user, groups) 15 | } else { 16 | console.log('d') 17 | } 18 | return user 19 | }) 20 | .catch(err => { 21 | console.error(err.stack) 22 | return dummyUser() 23 | }) 24 | } 25 | ` 26 | 27 | export const options = {} 28 | 29 | export const expected = ` 30 | async function createUser(args) { 31 | const {username, groups} = args 32 | let user 33 | try { 34 | const user0 = await Users.create({ username }) 35 | if (groups) { 36 | console.log('a') 37 | user = await addUserToGroups(user0, groups) 38 | } else if (foo) { 39 | console.log('b') 40 | user = await addUserToGroups(user0, groups) 41 | } else if (bar) { 42 | console.log('c') 43 | user = await addUserToGroups(user0, groups) 44 | } else { 45 | console.log('d') 46 | user = user0 47 | } 48 | } catch (err) { 49 | console.error(err.stack) 50 | user = await dummyUser() 51 | } 52 | } 53 | ` 54 | -------------------------------------------------------------------------------- /test/fixtures/conditionalReturnCatch_FirstConsequentAndMissingElseFallThrough.ts: -------------------------------------------------------------------------------- 1 | export const input = ` 2 | async function createUser(args) { 3 | const {username, groups} = args 4 | const user = await Users.create({username}) 5 | .then(user => { 6 | if (groups) { 7 | console.log('a') 8 | } else if (foo) { 9 | console.log('b') 10 | return addUserToGroups(user, groups) 11 | } else if (bar) { 12 | console.log('c') 13 | return addUserToGroups(user, groups) 14 | } 15 | return user 16 | }) 17 | .catch(err => { 18 | console.error(err.stack) 19 | return dummyUser() 20 | }) 21 | } 22 | ` 23 | 24 | export const options = {} 25 | 26 | export const expected = ` 27 | async function createUser(args) { 28 | const {username, groups} = args 29 | let user 30 | try { 31 | user = await Users.create({ username }).then(async user => { 32 | if (groups) { 33 | console.log('a') 34 | } else if (foo) { 35 | console.log('b') 36 | return addUserToGroups(user, groups) 37 | } else if (bar) { 38 | console.log('c') 39 | return addUserToGroups(user, groups) 40 | } 41 | return user 42 | }) 43 | } catch (err) { 44 | console.error(err.stack) 45 | user = await dummyUser() 46 | } 47 | } 48 | ` 49 | -------------------------------------------------------------------------------- /test/fixtures/conditionalReturnCatch_FirstConsequentFallsThrough.ts: -------------------------------------------------------------------------------- 1 | export const input = ` 2 | async function createUser(args) { 3 | const {username, groups} = args 4 | const user = await Users.create({username}) 5 | .then(user => { 6 | if (groups) { 7 | console.log('a') 8 | } else if (foo) { 9 | console.log('b') 10 | return addUserToGroups(user, groups).then(() => user) 11 | } else { 12 | console.log('c') 13 | return addUserToGroups(user, groups).then(() => user) 14 | } 15 | return user 16 | }) 17 | .catch(err => { 18 | console.error(err.stack) 19 | return dummyUser() 20 | }) 21 | } 22 | ` 23 | 24 | export const options = {} 25 | 26 | export const expected = ` 27 | async function createUser(args) { 28 | const {username, groups} = args 29 | let user 30 | try { 31 | const user0 = await Users.create({ username }) 32 | if (groups) { 33 | console.log('a') 34 | user = user0 35 | } else if (foo) { 36 | console.log('b') 37 | await addUserToGroups(user0, groups) 38 | user = user0 39 | } else { 40 | console.log('c') 41 | await addUserToGroups(user0, groups) 42 | user = user0 43 | } 44 | } catch (err) { 45 | console.error(err.stack) 46 | user = await dummyUser() 47 | } 48 | } 49 | ` 50 | -------------------------------------------------------------------------------- /test/fixtures/conditionalReturnCatch_FirstConsequentFallsThrough_flow.ts: -------------------------------------------------------------------------------- 1 | export const input = ` 2 | async function createUser(args) { 3 | const {username, groups} = args 4 | const user = await Users.create({username}) 5 | .then((user: User) => { 6 | if (groups) { 7 | console.log('a') 8 | } else if (foo) { 9 | console.log('b') 10 | return addUserToGroups(user, groups).then(() => user) 11 | } else { 12 | console.log('c') 13 | return addUserToGroups(user, groups).then(() => user) 14 | } 15 | return user 16 | }) 17 | .catch((err: Error) => { 18 | console.error(err.stack) 19 | return dummyUser() 20 | }) 21 | } 22 | ` 23 | 24 | export const options = {} 25 | export const parser = 'babylon' 26 | 27 | export const expected = ` 28 | async function createUser(args) { 29 | const {username, groups} = args 30 | let user 31 | try { 32 | const user0: User = await Users.create({ username }) 33 | if (groups) { 34 | console.log('a') 35 | user = user0 36 | } else if (foo) { 37 | console.log('b') 38 | await addUserToGroups(user0, groups) 39 | user = user0 40 | } else { 41 | console.log('c') 42 | await addUserToGroups(user0, groups) 43 | user = user0 44 | } 45 | } catch (err) { 46 | console.error(err.stack) 47 | user = await dummyUser() 48 | } 49 | } 50 | ` 51 | -------------------------------------------------------------------------------- /test/fixtures/conditionalReturnCatch_FirstConsequentFallsThrough_ts.ts: -------------------------------------------------------------------------------- 1 | export const input = ` 2 | async function createUser(args) { 3 | const {username, groups} = args 4 | const user = await Users.create({username}) 5 | .then((user: User) => { 6 | if (groups) { 7 | console.log('a') 8 | } else if (foo) { 9 | console.log('b') 10 | return addUserToGroups(user, groups).then(() => user) 11 | } else { 12 | console.log('c') 13 | return addUserToGroups(user, groups).then(() => user) 14 | } 15 | return user 16 | }) 17 | .catch((err: Error) => { 18 | console.error(err.stack) 19 | return dummyUser() 20 | }) 21 | } 22 | ` 23 | 24 | export const options = {} 25 | export const parser = 'ts' 26 | 27 | export const expected = ` 28 | async function createUser(args) { 29 | const {username, groups} = args 30 | let user 31 | try { 32 | const user0: User = await Users.create({ username }) 33 | if (groups) { 34 | console.log('a') 35 | user = user0 36 | } else if (foo) { 37 | console.log('b') 38 | await addUserToGroups(user0, groups) 39 | user = user0 40 | } else { 41 | console.log('c') 42 | await addUserToGroups(user0, groups) 43 | user = user0 44 | } 45 | } catch (err) { 46 | console.error(err.stack) 47 | user = await dummyUser() 48 | } 49 | } 50 | ` 51 | -------------------------------------------------------------------------------- /test/fixtures/conditionalReturnCatch_MissingElseFallsThrough.ts: -------------------------------------------------------------------------------- 1 | export const input = ` 2 | async function createUser(args) { 3 | const {username, groups} = args 4 | const user = await Users.create({username}) 5 | .then(user => { 6 | if (groups) { 7 | console.log('a') 8 | return addUserToGroups(user, groups) 9 | } else if (foo) { 10 | console.log('b') 11 | return addUserToGroups(user, groups) 12 | } else if (bar) { 13 | console.log('c') 14 | return addUserToGroups(user, groups) 15 | } 16 | console.log('d') 17 | return user 18 | }) 19 | .catch(err => { 20 | console.error(err.stack) 21 | return dummyUser() 22 | }) 23 | } 24 | ` 25 | 26 | export const options = {} 27 | 28 | export const expected = ` 29 | async function createUser(args) { 30 | const {username, groups} = args 31 | let user 32 | try { 33 | const user0 = await Users.create({ username }) 34 | if (groups) { 35 | console.log('a') 36 | user = await addUserToGroups(user0, groups) 37 | } else if (foo) { 38 | console.log('b') 39 | user = await addUserToGroups(user0, groups) 40 | } else if (bar) { 41 | console.log('c') 42 | user = await addUserToGroups(user0, groups) 43 | } else { 44 | console.log('d') 45 | user = user0 46 | } 47 | } catch (err) { 48 | console.error(err.stack) 49 | user = await dummyUser() 50 | } 51 | } 52 | ` 53 | -------------------------------------------------------------------------------- /test/fixtures/conditionalReturnCatch_Returned.ts: -------------------------------------------------------------------------------- 1 | export const input = ` 2 | function createUser(args) { 3 | const {username, groups} = args 4 | return Users.create({username}) 5 | .then(user => { 6 | if (groups) { 7 | return addUserToGroups(user, groups) 8 | } 9 | return user 10 | }) 11 | .catch(err => { 12 | console.error(err.stack) 13 | return dummyUser() 14 | }) 15 | } 16 | ` 17 | 18 | export const options = {} 19 | 20 | export const expected = ` 21 | async function createUser(args) { 22 | const {username, groups} = args 23 | try { 24 | const user = await Users.create({ username }) 25 | if (groups) { 26 | return await addUserToGroups(user, groups) 27 | } 28 | return user 29 | } catch (err) { 30 | console.error(err.stack) 31 | return dummyUser() 32 | } 33 | } 34 | ` 35 | -------------------------------------------------------------------------------- /test/fixtures/conditionalReturnCatch_SecondConsequentFallsThrough.ts: -------------------------------------------------------------------------------- 1 | export const input = ` 2 | async function createUser(args) { 3 | const {username, groups} = args 4 | const user = await Users.create({username}) 5 | .then(user => { 6 | if (groups) { 7 | console.log('a') 8 | return addUserToGroups(user, groups) 9 | } else if (foo) { 10 | console.log('b') 11 | } else { 12 | console.log('c') 13 | return addUserToGroups(user, groups) 14 | } 15 | return user 16 | }) 17 | .catch(err => { 18 | console.error(err.stack) 19 | return dummyUser() 20 | }) 21 | } 22 | ` 23 | 24 | export const options = {} 25 | 26 | export const expected = ` 27 | async function createUser(args) { 28 | const {username, groups} = args 29 | let user 30 | try { 31 | const user0 = await Users.create({ username }) 32 | if (groups) { 33 | console.log('a') 34 | user = await addUserToGroups(user0, groups) 35 | } else if (foo) { 36 | console.log('b') 37 | user = user0 38 | } else { 39 | console.log('c') 40 | user = await addUserToGroups(user0, groups) 41 | } 42 | } catch (err) { 43 | console.error(err.stack) 44 | user = await dummyUser() 45 | } 46 | } 47 | ` 48 | -------------------------------------------------------------------------------- /test/fixtures/conditionalReturnCatch_SeveralBranchesFallThrough copy.ts: -------------------------------------------------------------------------------- 1 | export const input = ` 2 | async function createUser(args) { 3 | const {username, groups} = args 4 | const user = await Users.create({username}) 5 | .then(user => { 6 | if (groups) { 7 | return addUserToGroups(user, groups) 8 | } else if (foo) { 9 | console.log('test') 10 | } else if (bar) { 11 | console.log('test2') 12 | } 13 | console.log('blah') 14 | return user 15 | }) 16 | .then(user => { 17 | if (groups) { 18 | console.log('test') 19 | } else { 20 | return 'noGroups' 21 | } 22 | return 'user' 23 | }) 24 | .catch(err => { 25 | console.error(err.stack) 26 | return dummyUser() 27 | }) 28 | } 29 | ` 30 | 31 | export const options = {} 32 | 33 | export const expected = ` 34 | async function createUser(args) { 35 | const {username, groups} = args 36 | let user 37 | try { 38 | const user0 = await Users.create({ username }).then(async user => { 39 | if (groups) { 40 | return addUserToGroups(user, groups) 41 | } else if (foo) { 42 | console.log('test') 43 | } else if (bar) { 44 | console.log('test2') 45 | } 46 | console.log('blah') 47 | return user 48 | }) 49 | if (groups) { 50 | console.log('test') 51 | user = 'user' 52 | } else { 53 | user = 'noGroups' 54 | } 55 | } catch (err) { 56 | console.error(err.stack) 57 | user = await dummyUser() 58 | } 59 | } 60 | ` 61 | -------------------------------------------------------------------------------- /test/fixtures/finalReturnUndefined.ts: -------------------------------------------------------------------------------- 1 | export const input = ` 2 | async function foo() { 3 | await bar.then(baz => { 4 | if (qux) { 5 | return qux.then(() => baz) 6 | } 7 | return baz 8 | }) 9 | } 10 | ` 11 | 12 | export const options = {} 13 | 14 | export const expected = ` 15 | async function foo() { 16 | const baz = await bar 17 | if (qux) { 18 | await qux 19 | } 20 | } 21 | ` 22 | -------------------------------------------------------------------------------- /test/fixtures/finallyIdentifier_Returned.ts: -------------------------------------------------------------------------------- 1 | export const input = ` 2 | function cleanup() { 3 | } 4 | async function foo() { 5 | return await baz.finally(cleanup) 6 | } 7 | ` 8 | 9 | export const options = {} 10 | 11 | export const expected = ` 12 | function cleanup() { 13 | } 14 | async function foo() { 15 | try { 16 | return await baz 17 | } finally { 18 | await cleanup() 19 | } 20 | } 21 | ` 22 | -------------------------------------------------------------------------------- /test/fixtures/finally_AssignedToDestructuring.ts: -------------------------------------------------------------------------------- 1 | export const input = ` 2 | async function foo() { 3 | const {stuff} = await baz.finally(() => { 4 | if (condition) return processBlargh() 5 | else return processOther() 6 | }) 7 | } 8 | ` 9 | 10 | export const options = {} 11 | 12 | export const expected = ` 13 | async function foo() { 14 | let result 15 | try { 16 | result = await baz 17 | } finally { 18 | if (condition) await processBlargh() 19 | else await processOther() 20 | } 21 | const {stuff} = result 22 | } 23 | ` 24 | -------------------------------------------------------------------------------- /test/fixtures/finally_AssignedToIdentifier.ts: -------------------------------------------------------------------------------- 1 | export const input = ` 2 | async function foo() { 3 | let bar 4 | bar = await baz.finally(() => { 5 | if (condition) return processBlargh() 6 | else return processOther() 7 | }) 8 | } 9 | ` 10 | 11 | export const options = {} 12 | 13 | export const expected = ` 14 | async function foo() { 15 | let bar 16 | try { 17 | bar = await baz 18 | } finally { 19 | if (condition) await processBlargh() 20 | else await processOther() 21 | } 22 | } 23 | ` 24 | -------------------------------------------------------------------------------- /test/fixtures/finally_AssignedToVariableDeclarator.ts: -------------------------------------------------------------------------------- 1 | export const input = ` 2 | async function foo() { 3 | const bar = await baz.finally(() => { 4 | if (condition) return processBlargh() 5 | else return processOther() 6 | }) 7 | } 8 | ` 9 | 10 | export const options = {} 11 | 12 | export const expected = ` 13 | async function foo() { 14 | let bar 15 | try { 16 | bar = await baz 17 | } finally { 18 | if (condition) await processBlargh() 19 | else await processOther() 20 | } 21 | } 22 | ` 23 | -------------------------------------------------------------------------------- /test/fixtures/finally_NestedExpression.ts: -------------------------------------------------------------------------------- 1 | export const input = ` 2 | async function foo() { 3 | return process(await baz.finally(() => { 4 | if (condition) return processBlargh() 5 | else return processOther() 6 | })) 7 | } 8 | ` 9 | 10 | export const options = {} 11 | 12 | export const expected = ` 13 | async function foo() { 14 | let result 15 | try { 16 | result = await baz 17 | } finally { 18 | if (condition) await processBlargh() 19 | else await processOther() 20 | } 21 | return process(result) 22 | } 23 | ` 24 | -------------------------------------------------------------------------------- /test/fixtures/finally_Returned.ts: -------------------------------------------------------------------------------- 1 | export const input = ` 2 | async function foo() { 3 | return await baz.finally(() => { 4 | if (condition) return processBlargh() 5 | else return processOther() 6 | }) 7 | } 8 | ` 9 | 10 | export const options = {} 11 | 12 | export const expected = ` 13 | async function foo() { 14 | try { 15 | return await baz 16 | } finally { 17 | if (condition) await processBlargh() 18 | else await processOther() 19 | } 20 | } 21 | ` 22 | -------------------------------------------------------------------------------- /test/fixtures/finally_Unconsumed.ts: -------------------------------------------------------------------------------- 1 | export const input = ` 2 | async function foo() { 3 | await baz.finally(() => { 4 | if (condition) return processBlargh() 5 | else return processOther() 6 | }) 7 | } 8 | ` 9 | 10 | export const options = {} 11 | 12 | export const expected = ` 13 | async function foo() { 14 | try { 15 | await baz 16 | } finally { 17 | if (condition) await processBlargh() 18 | else await processOther() 19 | } 20 | } 21 | ` 22 | -------------------------------------------------------------------------------- /test/fixtures/finally_handlersThatCanBeUnwound_Returned.ts: -------------------------------------------------------------------------------- 1 | export const input = ` 2 | function f1() { 3 | return bar.finally(() => { 4 | switch (baz) { 5 | case 2: return 6 | } 7 | }) 8 | } 9 | function f2() { 10 | return a.finally(() => { 11 | for (const i of [1, 2, 3]) { 12 | return 13 | } 14 | }) 15 | } 16 | function f3() { 17 | return a.finally(() => { 18 | while (i) { 19 | return 20 | } 21 | }) 22 | } 23 | function f4() { 24 | return a.finally(() => { 25 | if (a) { 26 | if (b) { 27 | return process(b) 28 | } 29 | } 30 | }) 31 | } 32 | ` 33 | 34 | export const options = {} 35 | 36 | export const expected = ` 37 | async function f1() { 38 | try { 39 | return await bar 40 | } finally { 41 | switch (baz) { 42 | case 2: 43 | break 44 | } 45 | } 46 | } 47 | async function f2() { 48 | try { 49 | return await a 50 | } finally { 51 | for (const i of [1, 2, 3]) { 52 | break 53 | } 54 | } 55 | } 56 | async function f3() { 57 | try { 58 | return await a 59 | } finally { 60 | while (i) { 61 | break 62 | } 63 | } 64 | } 65 | async function f4() { 66 | try { 67 | return await a 68 | } finally { 69 | if (a) { 70 | if (b) { 71 | await process(b) 72 | } 73 | } 74 | } 75 | } 76 | ` 77 | -------------------------------------------------------------------------------- /test/fixtures/finally_handlersThatCantBeUnwound.ts: -------------------------------------------------------------------------------- 1 | export const input = ` 2 | function foo() { 3 | return bar.finally(() => { 4 | switch (baz) { 5 | case 2: return 6 | } 7 | console.log('test') 8 | }).then(String) 9 | } 10 | function bar() { 11 | return a.finally(() => { 12 | for (const i of [1, 2, 3]) { 13 | return 14 | } 15 | console.log('test') 16 | }).then(String) 17 | } 18 | function qux() { 19 | return a.finally(() => { 20 | while (i) { 21 | return 22 | } 23 | console.log('test') 24 | }).then(String) 25 | } 26 | function baz() { 27 | return a.finally(() => { 28 | if (a) { 29 | if (b) { 30 | return 31 | } 32 | } 33 | console.log('test') 34 | }).then(String) 35 | } 36 | ` 37 | 38 | export const options = {} 39 | 40 | export const expected = ` 41 | async function foo() { 42 | return bar.finally(async () => { 43 | switch (baz) { 44 | case 2: return 45 | } 46 | console.log('test') 47 | }).then(String) 48 | } 49 | async function bar() { 50 | return a.finally(async () => { 51 | for (const i of [1, 2, 3]) { 52 | return 53 | } 54 | console.log('test') 55 | }).then(String) 56 | } 57 | async function qux() { 58 | return a.finally(async () => { 59 | while (i) { 60 | return 61 | } 62 | console.log('test') 63 | }).then(String) 64 | } 65 | async function baz() { 66 | return a.finally(async () => { 67 | if (a) { 68 | if (b) { 69 | return 70 | } 71 | } 72 | console.log('test') 73 | }).then(String) 74 | } 75 | ` 76 | -------------------------------------------------------------------------------- /test/fixtures/finally_lastHandlerCantAlwaysBeUnwound.ts: -------------------------------------------------------------------------------- 1 | export const input = ` 2 | function foo() { 3 | return bar.finally(() => { 4 | switch (baz) { 5 | case 2: return 6 | } 7 | console.log('test') 8 | }) 9 | } 10 | function bar() { 11 | return a.finally(() => { 12 | for (const i of [1, 2, 3]) { 13 | return 14 | } 15 | console.log('test') 16 | }) 17 | } 18 | function qux() { 19 | return a.finally(() => { 20 | while (i) { 21 | return 22 | } 23 | console.log('test') 24 | }) 25 | } 26 | function baz() { 27 | return a.finally(() => { 28 | if (a) { 29 | if (b) { 30 | return 31 | } 32 | } 33 | console.log('test') 34 | }) 35 | } 36 | ` 37 | 38 | export const options = {} 39 | 40 | export const expected = ` 41 | async function foo() { 42 | return bar.finally(async () => { 43 | switch (baz) { 44 | case 2: return 45 | } 46 | console.log('test') 47 | }) 48 | } 49 | async function bar() { 50 | return a.finally(async () => { 51 | for (const i of [1, 2, 3]) { 52 | return 53 | } 54 | console.log('test') 55 | }) 56 | } 57 | async function qux() { 58 | return a.finally(async () => { 59 | while (i) { 60 | return 61 | } 62 | console.log('test') 63 | }) 64 | } 65 | async function baz() { 66 | return a.finally(async () => { 67 | if (a) { 68 | if (b) { 69 | return 70 | } 71 | } 72 | console.log('test') 73 | }) 74 | } 75 | ` 76 | -------------------------------------------------------------------------------- /test/fixtures/ignoreChainsShorterThan.ts: -------------------------------------------------------------------------------- 1 | export const input = ` 2 | function foo() { 3 | const bar = baz.then(x => x * 2) 4 | } 5 | ` 6 | 7 | export const options = { 8 | ignoreChainsShorterThan: 50, 9 | } 10 | 11 | export const expected = ` 12 | function foo() { 13 | const bar = baz.then(x => x * 2) 14 | } 15 | ` 16 | -------------------------------------------------------------------------------- /test/fixtures/ignoredSimpleChain.ts: -------------------------------------------------------------------------------- 1 | export const input = ` 2 | function foo() { 3 | const bar = baz.then(x => x * 2).catch(err => { 4 | console.error(err.stack) 5 | }) 6 | } 7 | ` 8 | 9 | export const options = {} 10 | 11 | export const expected = input 12 | -------------------------------------------------------------------------------- /test/fixtures/leadingComment.ts: -------------------------------------------------------------------------------- 1 | export const input = ` 2 | function foo() { 3 | // this is a test 4 | return bar.then(baz => { 5 | return process(baz) 6 | }).then(results => { 7 | console.log(results) 8 | }) 9 | } 10 | ` 11 | 12 | export const options = { 13 | commentWorkarounds: true, 14 | } 15 | 16 | export const expected = ` 17 | async function foo() { 18 | // this is a test 19 | const baz = await bar 20 | const results = await process(baz) 21 | console.log(results) 22 | } 23 | ` 24 | -------------------------------------------------------------------------------- /test/fixtures/middleReturnUndefined.ts: -------------------------------------------------------------------------------- 1 | export const input = ` 2 | function foo() { 3 | return bar.then(baz => { 4 | if (qux) { 5 | return qux.then(() => baz) 6 | } 7 | return baz 8 | }).then(() => { 9 | console.log('done') 10 | }) 11 | } 12 | ` 13 | 14 | export const options = {} 15 | 16 | export const expected = ` 17 | async function foo() { 18 | const baz = await bar 19 | if (qux) { 20 | await qux 21 | } 22 | console.log('done') 23 | } 24 | ` 25 | -------------------------------------------------------------------------------- /test/fixtures/moreScopeTests.ts: -------------------------------------------------------------------------------- 1 | export const input = ` 2 | let c 3 | let d 4 | function foo() { 5 | const a = 2 6 | const b = 3 7 | return bar().then(x => { 8 | const c = 4 9 | function d() {} 10 | d() 11 | for (const q of x) { 12 | var a = 5 13 | let b = 2 14 | } 15 | return a 16 | }) 17 | } 18 | ` 19 | 20 | export const options = {} 21 | 22 | export const expected = ` 23 | let c 24 | let d 25 | async function foo() { 26 | const a = 2 27 | const b = 3 28 | const x = await bar() 29 | const c0 = 4 30 | function d0() {} 31 | d0() 32 | for (const q of x) { 33 | var a0 = 5 34 | let b = 2 35 | } 36 | return a0 37 | } 38 | ` 39 | -------------------------------------------------------------------------------- /test/fixtures/nonAwaitedChain.ts: -------------------------------------------------------------------------------- 1 | export const input = ` 2 | function foo() { 3 | const bar = baz.then(x => x * 2).catch(err => { 4 | console.error(err.stack) 5 | return 2 6 | }) 7 | } 8 | ` 9 | 10 | export const options = {} 11 | 12 | export const expected = ` 13 | function foo() { 14 | const bar = (async () => { 15 | try { 16 | const x = await baz 17 | return x * 2 18 | } catch (err) { 19 | console.error(err.stack) 20 | return 2 21 | } 22 | })() 23 | } 24 | ` 25 | -------------------------------------------------------------------------------- /test/fixtures/replacePromise.reject.ts: -------------------------------------------------------------------------------- 1 | export const input = ` 2 | function foo() { 3 | return Promise.reject(new Error('test')) 4 | } 5 | ` 6 | 7 | export const options = {} 8 | 9 | export const expected = ` 10 | async function foo() { 11 | throw new Error('test') 12 | } 13 | ` 14 | -------------------------------------------------------------------------------- /test/fixtures/replacePromise.resolve.ts: -------------------------------------------------------------------------------- 1 | export const input = ` 2 | function foo() { 3 | return Promise.resolve(2) 4 | } 5 | function bar() { 6 | return Promise.resolve() 7 | } 8 | function baz() { 9 | if (qux) { 10 | return Promise.resolve() 11 | } 12 | return Promise.resolve(3) 13 | } 14 | ` 15 | 16 | export const options = {} 17 | 18 | export const expected = ` 19 | async function foo() { 20 | return 2 21 | } 22 | async function bar() { 23 | } 24 | async function baz() { 25 | if (qux) { 26 | return 27 | } 28 | return 3 29 | } 30 | ` 31 | -------------------------------------------------------------------------------- /test/fixtures/thenCatchFinally.ts: -------------------------------------------------------------------------------- 1 | export const input = ` 2 | function createUser(args) { 3 | const {username, organizationId} = args 4 | return Users.create({username}) 5 | .then(user => { 6 | return addUserToOrganization(user, organizationId) 7 | }) 8 | .catch(err => { 9 | console.error(err.stack) 10 | return failedUser() 11 | }) 12 | .finally(() => { 13 | return cleanup() 14 | }) 15 | } 16 | ` 17 | 18 | export const options = {} 19 | 20 | export const expected = ` 21 | async function createUser(args) { 22 | const {username, organizationId} = args 23 | try { 24 | const user = await Users.create({ username }) 25 | return await addUserToOrganization(user, organizationId) 26 | } 27 | catch (err) { 28 | console.error(err.stack) 29 | return failedUser() 30 | } 31 | finally { 32 | await cleanup() 33 | } 34 | } 35 | ` 36 | -------------------------------------------------------------------------------- /test/fixtures/thenCatchFinally_flow.ts: -------------------------------------------------------------------------------- 1 | export const input = ` 2 | function createUser(args) { 3 | const {username, organizationId} = args 4 | return Users.create({username}) 5 | .then((user: User) => { 6 | return addUserToOrganization(user, organizationId) 7 | }) 8 | .catch((err: Error) => { 9 | console.error(err.stack) 10 | return failedUser() 11 | }) 12 | .finally(() => { 13 | return cleanup() 14 | }) 15 | } 16 | ` 17 | 18 | export const options = {} 19 | export const parser = 'babylon' 20 | 21 | export const expected = ` 22 | async function createUser(args) { 23 | const {username, organizationId} = args 24 | try { 25 | const user: User = await Users.create({ username }) 26 | return await addUserToOrganization(user, organizationId) 27 | } 28 | catch (err) { 29 | console.error(err.stack) 30 | return failedUser() 31 | } 32 | finally { 33 | await cleanup() 34 | } 35 | } 36 | ` 37 | -------------------------------------------------------------------------------- /test/fixtures/thenCatchFinally_ts.ts: -------------------------------------------------------------------------------- 1 | export const input = ` 2 | function createUser(args) { 3 | const {username, organizationId} = args 4 | return Users.create({username}) 5 | .then((user: User) => { 6 | return addUserToOrganization(user, organizationId) 7 | }) 8 | .catch((err: Error) => { 9 | console.error(err.stack) 10 | return failedUser() 11 | }) 12 | .finally(() => { 13 | return cleanup() 14 | }) 15 | } 16 | ` 17 | 18 | export const options = {} 19 | export const parser = 'ts' 20 | 21 | export const expected = ` 22 | async function createUser(args) { 23 | const {username, organizationId} = args 24 | try { 25 | const user: User = await Users.create({ username }) 26 | return await addUserToOrganization(user, organizationId) 27 | } 28 | catch (err) { 29 | console.error(err.stack) 30 | return failedUser() 31 | } 32 | finally { 33 | await cleanup() 34 | } 35 | } 36 | ` 37 | -------------------------------------------------------------------------------- /test/fixtures/thenCatch_AssignedToDestructuring.ts: -------------------------------------------------------------------------------- 1 | export const input = ` 2 | async function foo() { 3 | const {stuff} = await baz.then( 4 | value => { 5 | if (value instanceof Blargh) return processBlargh(value) 6 | else return processOther(value) 7 | }, 8 | err => { 9 | if (err instanceof ConstraintViolation) return processConstraintViolation(err) 10 | else return processOther(err) 11 | } 12 | ) 13 | } 14 | ` 15 | 16 | export const options = {} 17 | 18 | export const expected = ` 19 | async function foo() { 20 | let result 21 | try { 22 | const value = await baz 23 | if (value instanceof Blargh) result = await processBlargh(value) 24 | else result = await processOther(value) 25 | } catch (err) { 26 | if (err instanceof ConstraintViolation) result = await processConstraintViolation(err) 27 | else result = await processOther(err) 28 | } 29 | const {stuff} = result 30 | } 31 | ` 32 | -------------------------------------------------------------------------------- /test/fixtures/thenCatch_AssignedToDestructuring_flow.ts: -------------------------------------------------------------------------------- 1 | export const input = ` 2 | async function foo() { 3 | const {stuff} = await baz.then( 4 | (value: Thing) => { 5 | if (value instanceof Blargh) return processBlargh(value) 6 | else return processOther(value) 7 | }, 8 | (err: Error) => { 9 | if (err instanceof ConstraintViolation) return processConstraintViolation(err) 10 | else return processOther(err) 11 | } 12 | ) 13 | } 14 | ` 15 | 16 | export const options = {} 17 | export const parser = 'babylon' 18 | 19 | export const expected = ` 20 | async function foo() { 21 | let result 22 | try { 23 | const value: Thing = await baz 24 | if (value instanceof Blargh) result = await processBlargh(value) 25 | else result = await processOther(value) 26 | } catch (err) { 27 | if (err instanceof ConstraintViolation) result = await processConstraintViolation(err) 28 | else result = await processOther(err) 29 | } 30 | const {stuff} = result 31 | } 32 | ` 33 | -------------------------------------------------------------------------------- /test/fixtures/thenCatch_AssignedToDestructuring_ts.ts: -------------------------------------------------------------------------------- 1 | export const input = ` 2 | async function foo() { 3 | const {stuff} = await baz.then( 4 | (value: Thing) => { 5 | if (value instanceof Blargh) return processBlargh(value) 6 | else return processOther(value) 7 | }, 8 | (err: Error) => { 9 | if (err instanceof ConstraintViolation) return processConstraintViolation(err) 10 | else return processOther(err) 11 | } 12 | ) 13 | } 14 | ` 15 | 16 | export const options = {} 17 | export const parser = 'ts' 18 | 19 | export const expected = ` 20 | async function foo() { 21 | let result 22 | try { 23 | const value: Thing = await baz 24 | if (value instanceof Blargh) result = await processBlargh(value) 25 | else result = await processOther(value) 26 | } catch (err) { 27 | if (err instanceof ConstraintViolation) result = await processConstraintViolation(err) 28 | else result = await processOther(err) 29 | } 30 | const {stuff} = result 31 | } 32 | ` 33 | -------------------------------------------------------------------------------- /test/fixtures/thenCatch_AssignedToIdentifier.ts: -------------------------------------------------------------------------------- 1 | export const input = ` 2 | async function foo() { 3 | let bar = 3 4 | bar = await baz.then( 5 | value => { 6 | if (value instanceof Blargh) return processBlargh(value) 7 | else return processOther(value) 8 | }, 9 | err => { 10 | if (err instanceof ConstraintViolation) return processConstraintViolation(err) 11 | else return processOther(err) 12 | } 13 | ) 14 | } 15 | ` 16 | 17 | export const options = {} 18 | 19 | export const expected = ` 20 | async function foo() { 21 | let bar = 3 22 | try { 23 | const value = await baz 24 | if (value instanceof Blargh) bar = await processBlargh(value) 25 | else bar = await processOther(value) 26 | } catch (err) { 27 | if (err instanceof ConstraintViolation) bar = await processConstraintViolation(err) 28 | else bar = await processOther(err) 29 | } 30 | } 31 | ` 32 | -------------------------------------------------------------------------------- /test/fixtures/thenCatch_AssignedToVariableDeclarator.ts: -------------------------------------------------------------------------------- 1 | export const input = ` 2 | async function foo() { 3 | const bar = await baz.then( 4 | value => { 5 | if (value instanceof Blargh) return processBlargh(value) 6 | else return processOther(value) 7 | }, 8 | err => { 9 | if (err instanceof ConstraintViolation) return processConstraintViolation(err) 10 | else return processOther(err) 11 | } 12 | ) 13 | } 14 | ` 15 | 16 | export const options = {} 17 | 18 | export const expected = ` 19 | async function foo() { 20 | let bar 21 | try { 22 | const value = await baz 23 | if (value instanceof Blargh) bar = await processBlargh(value) 24 | else bar = await processOther(value) 25 | } catch (err) { 26 | if (err instanceof ConstraintViolation) bar = await processConstraintViolation(err) 27 | else bar = await processOther(err) 28 | } 29 | } 30 | ` 31 | -------------------------------------------------------------------------------- /test/fixtures/thenCatch_NestedExpression.ts: -------------------------------------------------------------------------------- 1 | export const input = ` 2 | async function foo() { 3 | return process(await baz.then( 4 | value => { 5 | if (value instanceof Blargh) return processBlargh(value) 6 | else return processOther(value) 7 | }, 8 | err => { 9 | if (err instanceof ConstraintViolation) return processConstraintViolation(err) 10 | else return processOther(err) 11 | } 12 | )) 13 | } 14 | ` 15 | 16 | export const options = {} 17 | 18 | export const expected = ` 19 | async function foo() { 20 | let result 21 | try { 22 | const value = await baz 23 | if (value instanceof Blargh) result = await processBlargh(value) 24 | else result = await processOther(value) 25 | } catch (err) { 26 | if (err instanceof ConstraintViolation) result = await processConstraintViolation(err) 27 | else result = await processOther(err) 28 | } 29 | return process(result) 30 | } 31 | ` 32 | -------------------------------------------------------------------------------- /test/fixtures/thenCatch_Returned.ts: -------------------------------------------------------------------------------- 1 | export const input = ` 2 | function foo() { 3 | return baz.then( 4 | value => { 5 | if (value instanceof Blargh) return processBlargh(value) 6 | else return processOther(value) 7 | }, 8 | err => { 9 | if (err instanceof ConstraintViolation) return processConstraintViolation(err) 10 | else return processOther(err) 11 | } 12 | ) 13 | } 14 | ` 15 | 16 | export const options = {} 17 | 18 | export const expected = ` 19 | async function foo() { 20 | try { 21 | const value = await baz 22 | if (value instanceof Blargh) return await processBlargh(value) 23 | else return await processOther(value) 24 | } catch (err) { 25 | if (err instanceof ConstraintViolation) return processConstraintViolation(err) 26 | else return processOther(err) 27 | } 28 | } 29 | ` 30 | -------------------------------------------------------------------------------- /test/fixtures/thenCatch_Unconsumed.ts: -------------------------------------------------------------------------------- 1 | export const input = ` 2 | async function foo() { 3 | await baz.then( 4 | value => { 5 | if (value instanceof Blargh) return processBlargh(value) 6 | else return processOther(value) 7 | }, 8 | err => { 9 | if (err instanceof ConstraintViolation) return processConstraintViolation(err) 10 | else return processOther(err) 11 | } 12 | ) 13 | } 14 | ` 15 | 16 | export const options = {} 17 | 18 | export const expected = ` 19 | async function foo() { 20 | try { 21 | const value = await baz 22 | if (value instanceof Blargh) await processBlargh(value) 23 | else await processOther(value) 24 | } catch (err) { 25 | if (err instanceof ConstraintViolation) await processConstraintViolation(err) 26 | else await processOther(err) 27 | } 28 | } 29 | ` 30 | -------------------------------------------------------------------------------- /test/fixtures/thenChain.ts: -------------------------------------------------------------------------------- 1 | export const input = ` 2 | function foo() { 3 | const bar = () => 3 4 | return bar().then(bar => { 5 | return bar() 6 | }).then(bar => { 7 | return bar() 8 | }).then(baz => { 9 | return baz * 2 10 | }).then(([{foo: baz = 5}]) => { 11 | return baz + 3 12 | }) 13 | } 14 | ` 15 | 16 | export const options = {} 17 | 18 | export const expected = ` 19 | async function foo() { 20 | const bar = () => 3 21 | const bar1 = await bar() 22 | const bar0 = await bar1() 23 | const baz0 = await bar0() 24 | const [{ foo: baz = 5 }] = baz0 * 2 25 | return baz + 3 26 | } 27 | ` 28 | -------------------------------------------------------------------------------- /test/fixtures/thenExpressionBody_AssignedToDestructuring.ts: -------------------------------------------------------------------------------- 1 | export const input = ` 2 | async function foo() { 3 | const {stuff} = await baz.then(value => process(value)) 4 | } 5 | ` 6 | 7 | export const options = {} 8 | 9 | export const expected = ` 10 | async function foo() { 11 | const value = await baz 12 | const {stuff} = await process(value) 13 | } 14 | ` 15 | -------------------------------------------------------------------------------- /test/fixtures/thenExpressionBody_AssignedToIdentifier.ts: -------------------------------------------------------------------------------- 1 | export const input = ` 2 | async function foo() { 3 | let bar = 3 4 | bar = await baz.then(value => process(value)) 5 | } 6 | ` 7 | 8 | export const options = {} 9 | 10 | export const expected = ` 11 | async function foo() { 12 | let bar = 3 13 | const value = await baz 14 | bar = await process(value) 15 | } 16 | ` 17 | -------------------------------------------------------------------------------- /test/fixtures/thenExpressionBody_AssignedToVariableDeclarator.ts: -------------------------------------------------------------------------------- 1 | export const input = ` 2 | async function foo() { 3 | const bar = await baz.then(value => process(value)) 4 | } 5 | ` 6 | 7 | export const options = {} 8 | 9 | export const expected = ` 10 | async function foo() { 11 | const value = await baz 12 | const bar = await process(value) 13 | } 14 | ` 15 | -------------------------------------------------------------------------------- /test/fixtures/thenExpressionBody_IgnoredInput.ts: -------------------------------------------------------------------------------- 1 | export const input = ` 2 | function foo(value) { 3 | return baz.then(() => process(value)) 4 | } 5 | ` 6 | 7 | export const options = {} 8 | 9 | export const expected = ` 10 | async function foo(value) { 11 | await baz 12 | return process(value) 13 | } 14 | ` 15 | -------------------------------------------------------------------------------- /test/fixtures/thenExpressionBody_Returned.ts: -------------------------------------------------------------------------------- 1 | export const input = ` 2 | function foo() { 3 | return baz.then(value => process(value)) 4 | } 5 | ` 6 | 7 | export const options = {} 8 | 9 | export const expected = ` 10 | async function foo() { 11 | const value = await baz 12 | return process(value) 13 | } 14 | ` 15 | -------------------------------------------------------------------------------- /test/fixtures/thenExpressionBody_Unconsumed.ts: -------------------------------------------------------------------------------- 1 | export const input = ` 2 | async function foo() { 3 | await baz.then(value => process(value)) 4 | } 5 | ` 6 | 7 | export const options = {} 8 | 9 | export const expected = ` 10 | async function foo() { 11 | const value = await baz 12 | await process(value) 13 | } 14 | ` 15 | -------------------------------------------------------------------------------- /test/fixtures/thenExpression_Unconsumed.ts: -------------------------------------------------------------------------------- 1 | export const input = ` 2 | async function foo() { 3 | const handler = 2 4 | await baz.then(makeHandler(5)) 5 | } 6 | ` 7 | 8 | export const options = {} 9 | 10 | export const expected = ` 11 | async function foo() { 12 | const handler = 2 13 | await baz.then(makeHandler(5)) 14 | } 15 | ` 16 | -------------------------------------------------------------------------------- /test/fixtures/thenIdentifier_AssignedToVariableDeclarator.ts: -------------------------------------------------------------------------------- 1 | export const input = ` 2 | async function foo() { 3 | const bar = await baz.then(foo) 4 | } 5 | ` 6 | 7 | export const options = {} 8 | 9 | export const expected = ` 10 | async function foo() { 11 | const bar = await foo(await baz) 12 | } 13 | ` 14 | -------------------------------------------------------------------------------- /test/fixtures/thenIdentifier_Returned.ts: -------------------------------------------------------------------------------- 1 | export const input = ` 2 | function foo() { 3 | return baz.then(foo) 4 | } 5 | ` 6 | 7 | export const options = {} 8 | 9 | export const expected = ` 10 | async function foo() { 11 | return foo(await baz) 12 | } 13 | ` 14 | -------------------------------------------------------------------------------- /test/fixtures/thenIdentifier_Unconsumed.ts: -------------------------------------------------------------------------------- 1 | export const input = ` 2 | async function foo() { 3 | await baz.then(foo) 4 | } 5 | ` 6 | 7 | export const options = {} 8 | 9 | export const expected = ` 10 | async function foo() { 11 | await foo(await baz) 12 | } 13 | ` 14 | -------------------------------------------------------------------------------- /test/fixtures/thenUndefinedCatch_Returned.ts: -------------------------------------------------------------------------------- 1 | export const input = ` 2 | function foo() { 3 | return baz.then( 4 | undefined, 5 | err => { 6 | if (err instanceof ConstraintViolation) return processConstraintViolation(err) 7 | else return processOther(err) 8 | } 9 | ) 10 | } 11 | ` 12 | 13 | export const options = {} 14 | 15 | export const expected = ` 16 | async function foo() { 17 | try { 18 | return await baz 19 | } catch (err) { 20 | if (err instanceof ConstraintViolation) return processConstraintViolation(err) 21 | else return processOther(err) 22 | } 23 | } 24 | ` 25 | -------------------------------------------------------------------------------- /test/fixtures/then_AssignedToDestructuring.ts: -------------------------------------------------------------------------------- 1 | export const input = ` 2 | async function foo() { 3 | const {stuff} = await baz.then(value => { 4 | if (value instanceof Blargh) return processBlargh(value) 5 | else return processOther(value) 6 | }) 7 | } 8 | ` 9 | 10 | export const options = {} 11 | 12 | export const expected = ` 13 | async function foo() { 14 | let result 15 | const value = await baz 16 | if (value instanceof Blargh) result = await processBlargh(value) 17 | else result = await processOther(value) 18 | const {stuff} = result 19 | } 20 | ` 21 | -------------------------------------------------------------------------------- /test/fixtures/then_AssignedToIdentifier.ts: -------------------------------------------------------------------------------- 1 | export const input = ` 2 | async function foo() { 3 | let bar = 3 4 | bar = await baz.then(value => { 5 | if (value instanceof Blargh) return processBlargh(value) 6 | else return processOther(value) 7 | }) 8 | } 9 | ` 10 | 11 | export const options = {} 12 | 13 | export const expected = ` 14 | async function foo() { 15 | let bar = 3 16 | const value = await baz 17 | if (value instanceof Blargh) bar = await processBlargh(value) 18 | else bar = await processOther(value) 19 | } 20 | ` 21 | -------------------------------------------------------------------------------- /test/fixtures/then_AssignedToVariableDeclarator.ts: -------------------------------------------------------------------------------- 1 | export const input = ` 2 | async function foo() { 3 | const bar = await baz.then(value => { 4 | if (value instanceof Blargh) return processBlargh(value) 5 | else return processOther(value) 6 | }) 7 | } 8 | ` 9 | 10 | export const options = {} 11 | 12 | export const expected = ` 13 | async function foo() { 14 | let bar 15 | const value = await baz 16 | if (value instanceof Blargh) bar = await processBlargh(value) 17 | else bar = await processOther(value) 18 | } 19 | ` 20 | -------------------------------------------------------------------------------- /test/fixtures/then_DestructuredInput.ts: -------------------------------------------------------------------------------- 1 | export const input = ` 2 | function foo() { 3 | return baz.then(({value}) => { 4 | if (value instanceof Blargh) return processBlargh(value) 5 | else return processOther(value) 6 | }) 7 | } 8 | ` 9 | 10 | export const options = {} 11 | 12 | export const expected = ` 13 | async function foo() { 14 | const {value} = await baz 15 | if (value instanceof Blargh) return processBlargh(value) 16 | else return processOther(value) 17 | } 18 | ` 19 | -------------------------------------------------------------------------------- /test/fixtures/then_DestructuredInput_flow.ts: -------------------------------------------------------------------------------- 1 | export const input = ` 2 | function foo() { 3 | return baz.then(({value}: {value: string}) => { 4 | if (value instanceof Blargh) return processBlargh(value) 5 | else return processOther(value) 6 | }) 7 | } 8 | ` 9 | 10 | export const options = {} 11 | export const parser = 'babylon' 12 | 13 | export const expected = ` 14 | async function foo() { 15 | const {value}: {value: string} = await baz 16 | if (value instanceof Blargh) return processBlargh(value) 17 | else return processOther(value) 18 | } 19 | ` 20 | -------------------------------------------------------------------------------- /test/fixtures/then_DestructuredInput_ts.ts: -------------------------------------------------------------------------------- 1 | export const input = ` 2 | function foo() { 3 | return baz.then(({value}: {value: string}) => { 4 | if (value instanceof Blargh) return processBlargh(value) 5 | else return processOther(value) 6 | }) 7 | } 8 | ` 9 | 10 | export const options = {} 11 | export const parser = 'ts' 12 | 13 | export const expected = ` 14 | async function foo() { 15 | const {value}: {value: string} = await baz 16 | if (value instanceof Blargh) return processBlargh(value) 17 | else return processOther(value) 18 | } 19 | ` 20 | -------------------------------------------------------------------------------- /test/fixtures/then_FinalHandlerAsIs.ts: -------------------------------------------------------------------------------- 1 | export const input = ` 2 | function foo() { 3 | return baz.then(x => y).then(z => { 4 | for (const i = 0; i < 5; i++) { 5 | if (items[i] < z) return w; 6 | } 7 | switch (z) { 8 | case 0: 9 | return a; 10 | case 1: 11 | return b; 12 | case 2: 13 | case 3: 14 | return c; 15 | } 16 | console.log('test'); 17 | return d; 18 | }); 19 | } 20 | ` 21 | 22 | export const options = {} 23 | 24 | export const expected = ` 25 | async function foo() { 26 | const x = await baz 27 | const z = await y 28 | for (const i = 0; i < 5; i++) { 29 | if (items[i] < z) return w; 30 | } 31 | switch (z) { 32 | case 0: 33 | return a; 34 | case 1: 35 | return b; 36 | case 2: 37 | case 3: 38 | return c; 39 | } 40 | console.log('test'); 41 | return d; 42 | } 43 | ` 44 | -------------------------------------------------------------------------------- /test/fixtures/then_MutableDestructuredInput.ts: -------------------------------------------------------------------------------- 1 | export const input = ` 2 | function foo() { 3 | return baz.then(({value}) => { 4 | value = value || dummy() 5 | if (value instanceof Blargh) return processBlargh(value) 6 | else return processOther(value) 7 | }) 8 | } 9 | ` 10 | 11 | export const options = {} 12 | 13 | export const expected = ` 14 | async function foo() { 15 | let {value} = await baz 16 | value = value || dummy() 17 | if (value instanceof Blargh) return processBlargh(value) 18 | else return processOther(value) 19 | } 20 | ` 21 | -------------------------------------------------------------------------------- /test/fixtures/then_NestedExpression.ts: -------------------------------------------------------------------------------- 1 | export const input = ` 2 | async function foo() { 3 | return process(await baz.then(value => { 4 | if (value instanceof Blargh) return processBlargh(value) 5 | else return processOther(value) 6 | })) 7 | } 8 | ` 9 | 10 | export const options = {} 11 | 12 | export const expected = ` 13 | async function foo() { 14 | let result 15 | const value = await baz 16 | if (value instanceof Blargh) result = await processBlargh(value) 17 | else result = await processOther(value) 18 | return process(result) 19 | } 20 | ` 21 | -------------------------------------------------------------------------------- /test/fixtures/then_NestedFunction.ts: -------------------------------------------------------------------------------- 1 | export const input = ` 2 | function foo() { 3 | return baz.then(value => { 4 | function processBlargh(value) { 5 | return JSON.stringify(value) 6 | } 7 | if (value instanceof Blargh) return processBlargh(value) 8 | else return processOther(value) 9 | }) 10 | } 11 | ` 12 | 13 | export const options = {} 14 | 15 | export const expected = ` 16 | async function foo() { 17 | const value = await baz 18 | function processBlargh(value) { 19 | return JSON.stringify(value) 20 | } 21 | if (value instanceof Blargh) return processBlargh(value) 22 | else return processOther(value) 23 | } 24 | ` 25 | -------------------------------------------------------------------------------- /test/fixtures/then_RenamingDestructuredInput.ts: -------------------------------------------------------------------------------- 1 | export const input = ` 2 | function foo() { 3 | const value = 3 4 | return baz.then(({value}) => { 5 | if (value instanceof Blargh) return processBlargh(value) 6 | else return processOther(value) 7 | }) 8 | } 9 | ` 10 | 11 | export const options = {} 12 | 13 | export const expected = ` 14 | async function foo() { 15 | const value = 3 16 | const {value: value0} = await baz 17 | if (value0 instanceof Blargh) return processBlargh(value0) 18 | else return processOther(value0) 19 | } 20 | ` 21 | -------------------------------------------------------------------------------- /test/fixtures/then_Returned.ts: -------------------------------------------------------------------------------- 1 | export const input = ` 2 | function foo() { 3 | return baz.then(value => { 4 | if (value instanceof Blargh) return processBlargh(value) 5 | else return processOther(value) 6 | }) 7 | } 8 | ` 9 | 10 | export const options = {} 11 | 12 | export const expected = ` 13 | async function foo() { 14 | const value = await baz 15 | if (value instanceof Blargh) return processBlargh(value) 16 | else return processOther(value) 17 | } 18 | ` 19 | -------------------------------------------------------------------------------- /test/fixtures/then_ReturningFunction.ts: -------------------------------------------------------------------------------- 1 | export const input = ` 2 | function foo() { 3 | return baz.then(value => { 4 | if (value instanceof Blargh) return () => JSON.stringify(value) 5 | else return () => String(value) 6 | }) 7 | } 8 | ` 9 | 10 | export const options = {} 11 | 12 | export const expected = ` 13 | async function foo() { 14 | const value = await baz 15 | if (value instanceof Blargh) return () => JSON.stringify(value) 16 | else return () => String(value) 17 | } 18 | ` 19 | -------------------------------------------------------------------------------- /test/fixtures/then_Unconsumed.ts: -------------------------------------------------------------------------------- 1 | export const input = ` 2 | async function foo() { 3 | await baz.then(value => { 4 | if (value instanceof Blargh) return processBlargh(value) 5 | else return processOther(value) 6 | }) 7 | } 8 | ` 9 | 10 | export const options = {} 11 | 12 | export const expected = ` 13 | async function foo() { 14 | const value = await baz 15 | if (value instanceof Blargh) await processBlargh(value) 16 | else await processOther(value) 17 | } 18 | ` 19 | -------------------------------------------------------------------------------- /test/fixtures/then_handlersThatCanBeUnwound_AssignedToIdentifier.ts: -------------------------------------------------------------------------------- 1 | export const input = ` 2 | async function f1() { 3 | const x = await bar.then(baz => { 4 | switch (baz) { 5 | case 2: return 2 6 | } 7 | }) 8 | } 9 | async function f2() { 10 | const x = await a.then(b => { 11 | for (const i of [1, 2, 3]) { 12 | return 2 13 | } 14 | }) 15 | } 16 | async function f3() { 17 | const x = await a.then(b => { 18 | while (i) { 19 | return 2 20 | } 21 | }) 22 | } 23 | async function f4() { 24 | const x = await a.then(b => { 25 | if (a) { 26 | if (b) { 27 | return 2 28 | } 29 | } 30 | }) 31 | } 32 | ` 33 | 34 | export const options = {} 35 | 36 | export const expected = ` 37 | async function f1() { 38 | let x 39 | const baz = await bar 40 | switch (baz) { 41 | case 2: { 42 | x = 2 43 | break 44 | } 45 | } 46 | } 47 | async function f2() { 48 | let x 49 | const b = await a 50 | for (const i of [1, 2, 3]) { 51 | x = 2 52 | break 53 | } 54 | } 55 | async function f3() { 56 | let x 57 | const b = await a 58 | while (i) { 59 | x = 2 60 | break 61 | } 62 | } 63 | async function f4() { 64 | let x 65 | const b = await a 66 | if (a) { 67 | if (b) { 68 | x = 2 69 | } 70 | } 71 | } 72 | ` 73 | -------------------------------------------------------------------------------- /test/fixtures/then_handlersThatCanBeUnwound_Returned.ts: -------------------------------------------------------------------------------- 1 | export const input = ` 2 | function f1() { 3 | return bar.then(baz => { 4 | switch (baz) { 5 | case 2: return 6 | } 7 | }) 8 | } 9 | function f2() { 10 | return a.then(b => { 11 | for (const i of [1, 2, 3]) { 12 | return 13 | } 14 | }) 15 | } 16 | function f3() { 17 | return a.then(b => { 18 | while (i) { 19 | return 20 | } 21 | }) 22 | } 23 | function f4() { 24 | return a.then(b => { 25 | if (a) { 26 | if (b) { 27 | return 28 | } 29 | } 30 | }) 31 | } 32 | ` 33 | 34 | export const options = {} 35 | 36 | export const expected = ` 37 | async function f1() { 38 | const baz = await bar 39 | switch (baz) { 40 | case 2: 41 | return 42 | } 43 | } 44 | async function f2() { 45 | const b = await a 46 | for (const i of [1, 2, 3]) { 47 | return 48 | } 49 | } 50 | async function f3() { 51 | const b = await a 52 | while (i) { 53 | return 54 | } 55 | } 56 | async function f4() { 57 | const b = await a 58 | if (a) { 59 | if (b) { 60 | return 61 | } 62 | } 63 | } 64 | ` 65 | -------------------------------------------------------------------------------- /test/fixtures/then_handlersThatCantBeUnwound.ts: -------------------------------------------------------------------------------- 1 | export const input = ` 2 | function foo() { 3 | return bar.then(baz => { 4 | switch (baz) { 5 | case 2: return 6 | } 7 | console.log('test') 8 | }).then(String) 9 | } 10 | function bar() { 11 | return a.then(b => { 12 | for (const i of [1, 2, 3]) { 13 | return 14 | } 15 | console.log('test') 16 | }).then(String) 17 | } 18 | function qux() { 19 | return a.then(b => { 20 | while (i) { 21 | return 22 | } 23 | console.log('test') 24 | }).then(String) 25 | } 26 | function baz() { 27 | return a.then(b => { 28 | if (a) { 29 | if (b) { 30 | return 31 | } 32 | } 33 | console.log('test') 34 | }).then(String) 35 | } 36 | ` 37 | 38 | export const options = {} 39 | 40 | export const expected = ` 41 | async function foo() { 42 | return bar.then(async baz => { 43 | switch (baz) { 44 | case 2: return 45 | } 46 | console.log('test') 47 | }).then(String) 48 | } 49 | async function bar() { 50 | return a.then(async b => { 51 | for (const i of [1, 2, 3]) { 52 | return 53 | } 54 | console.log('test') 55 | }).then(String) 56 | } 57 | async function qux() { 58 | return a.then(async b => { 59 | while (i) { 60 | return 61 | } 62 | console.log('test') 63 | }).then(String) 64 | } 65 | async function baz() { 66 | return a.then(async b => { 67 | if (a) { 68 | if (b) { 69 | return 70 | } 71 | } 72 | console.log('test') 73 | }).then(String) 74 | } 75 | ` 76 | -------------------------------------------------------------------------------- /test/fixtures/then_lastHandlerCanAlwaysBeUnwound.ts: -------------------------------------------------------------------------------- 1 | export const input = ` 2 | function foo() { 3 | return bar.then(baz => { 4 | switch (baz) { 5 | case 2: return 6 | } 7 | console.log('test') 8 | }) 9 | } 10 | function bar() { 11 | return a.then(b => { 12 | for (const i of [1, 2, 3]) { 13 | return 14 | } 15 | console.log('test') 16 | }) 17 | } 18 | function qux() { 19 | return a.then(b => { 20 | while (i) { 21 | return 22 | } 23 | console.log('test') 24 | }) 25 | } 26 | function baz() { 27 | return a.then(b => { 28 | if (a) { 29 | if (b) { 30 | return 31 | } 32 | } 33 | console.log('test') 34 | }) 35 | } 36 | ` 37 | 38 | export const options = {} 39 | 40 | export const expected = ` 41 | async function foo() { 42 | const baz0 = await bar 43 | switch (baz0) { 44 | case 2: return 45 | } 46 | console.log('test') 47 | } 48 | async function bar() { 49 | const b = await a 50 | for (const i of [1, 2, 3]) { 51 | return 52 | } 53 | console.log('test') 54 | } 55 | async function qux() { 56 | const b = await a 57 | while (i) { 58 | return 59 | } 60 | console.log('test') 61 | } 62 | async function baz() { 63 | const b = await a 64 | if (a) { 65 | if (b) { 66 | return 67 | } 68 | } 69 | console.log('test') 70 | } 71 | ` 72 | -------------------------------------------------------------------------------- /test/fixtures/then_tryCatchInHandler.ts: -------------------------------------------------------------------------------- 1 | export const input = ` 2 | function foo() { 3 | return bar.then(() => { 4 | try { 5 | // this must not get awaited, because that would change the behavior 6 | return blah 7 | } catch (err) { 8 | return glab 9 | } 10 | }) 11 | } 12 | ` 13 | 14 | export const options = {} 15 | 16 | export const expected = ` 17 | async function foo() { 18 | await bar 19 | try { 20 | // this must not get awaited, because that would change the behavior 21 | return blah 22 | } catch (err) { 23 | return glab 24 | } 25 | } 26 | ` 27 | -------------------------------------------------------------------------------- /test/index.spec.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-var-requires */ 2 | 3 | import * as path from 'path' 4 | import testFixtures from './testFixtures' 5 | import dump from './dump' 6 | global.dump = dump 7 | 8 | describe('asyncify', function() { 9 | testFixtures({ 10 | glob: path.join(__dirname, 'fixtures', '*.ts'), 11 | transform: require(`../src`), 12 | }) 13 | }) 14 | -------------------------------------------------------------------------------- /test/testFixtures.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-var-requires */ 2 | /* eslint-disable @typescript-eslint/no-explicit-any */ 3 | 4 | import { expect } from 'chai' 5 | import requireGlob from 'require-glob' 6 | import jscodeshift, { Transform } from 'jscodeshift' 7 | import * as path from 'path' 8 | import pkgConf from 'pkg-conf' 9 | import * as prettier from 'prettier' 10 | 11 | const prettierOptions = { ...pkgConf.sync('prettier'), parser: 'babel' } 12 | const normalize = (code: string): string => 13 | prettier 14 | .format(code, prettierOptions) 15 | .replace(/^\s*(\r\n?|\n)/gm, '') 16 | .trim() 17 | 18 | export default function textFixtures({ 19 | glob, 20 | transform, 21 | transformOptions, 22 | defaultParser, 23 | }: { 24 | glob: string 25 | transform: Transform 26 | transformOptions?: Record 27 | defaultParser?: string 28 | }): void { 29 | if (!path.isAbsolute(glob)) { 30 | throw new Error('glob must be absolute') 31 | } 32 | const fixtures = requireGlob.sync(glob, { 33 | reducer: ( 34 | options: Record, 35 | result: Record, 36 | file: { path: string; exports: any } 37 | ) => { 38 | result[file.path] = file.exports 39 | return result 40 | }, 41 | }) 42 | for (const fixturePath in fixtures) { 43 | const fixture = fixtures[fixturePath] 44 | const { input, expected } = fixture 45 | const file = path.resolve( 46 | __dirname, 47 | fixture.file 48 | ? path.resolve(path.dirname(fixturePath), fixture.file) 49 | : fixturePath 50 | ) 51 | it(path.basename(fixturePath).replace(/\.js$/, ''), function() { 52 | const options = { ...transformOptions, ...fixture.options } 53 | 54 | const stats: Record = {} 55 | const report = [] 56 | const parser = fixture.parser || defaultParser 57 | const j = parser ? jscodeshift.withParser(parser) : jscodeshift 58 | const doTransform = (): string | null | void | undefined => 59 | transform( 60 | { path: file, source: input }, 61 | { 62 | j, 63 | jscodeshift: j, 64 | stats: (name: string, quantity = 1): void => { 65 | const total = stats[name] 66 | stats[name] = total != null ? total + quantity : quantity 67 | }, 68 | report: (msg: string) => report.push(msg), 69 | }, 70 | options 71 | ) 72 | if (fixture.expectedError) { 73 | expect(doTransform).to.throw(fixture.expectedError) 74 | } else { 75 | const result = doTransform() 76 | if (!result) expect(result).to.equal(fixture.expected) 77 | else expect(normalize(result)).to.equal(normalize(expected)) 78 | if (fixture.stats) expect(stats).to.deep.equal(fixture.stats) 79 | if (fixture.report) expect(report).to.deep.equal(fixture.report) 80 | } 81 | }) 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["./src"], 3 | "exclude": ["node_modules", "./src/**/*.spec.ts", "./test"], 4 | "compilerOptions": { 5 | /* Basic Options */ 6 | "target": "esnext" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */, 7 | "module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */, 8 | // "lib": [], /* Specify library files to be included in the compilation. */ 9 | // "allowJs": true, /* Allow javascript files to be compiled. */ 10 | // "checkJs": true, /* Report errors in .js files. */ 11 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 12 | "declaration": true /* Generates corresponding '.d.ts' file. */, 13 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 14 | // "sourceMap": true, /* Generates corresponding '.map' file. */ 15 | // "outFile": "./", /* Concatenate and emit output to single file. */ 16 | "outDir": "./" /* Redirect output structure to the directory. */, 17 | // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 18 | // "composite": true, /* Enable project compilation */ 19 | // "removeComments": true, /* Do not emit comments to output. */ 20 | // "noEmit": true, /* Do not emit outputs. */ 21 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 22 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 23 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 24 | 25 | /* Strict Type-Checking Options */ 26 | "strict": true /* Enable all strict type-checking options. */, 27 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 28 | // "strictNullChecks": true, /* Enable strict null checks. */ 29 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 30 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 31 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 32 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 33 | 34 | /* Additional Checks */ 35 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 36 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 37 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 38 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 39 | 40 | /* Module Resolution Options */ 41 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 42 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 43 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 44 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 45 | // "typeRoots": [], /* List of folders to include type definitions from. */ 46 | // "types": [], /* Type declaration files to be included in compilation. */ 47 | "allowSyntheticDefaultImports": true /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */, 48 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 49 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 50 | 51 | /* Source Map Options */ 52 | // "sourceRoot": "./", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 53 | // "mapRoot": "./", /* Specify the location where debugger should locate map files instead of generated locations. */ 54 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 55 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 56 | 57 | /* Experimental Options */ 58 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 59 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 60 | 61 | /* Advanced Options */ 62 | // "declarationDir": "lib" /* Output directory for generated declaration files. */ 63 | } 64 | } 65 | --------------------------------------------------------------------------------