From c011a1a9f208efe35f1940b8a09db6023200625b Mon Sep 17 00:00:00 2001 From: Brett Zamir Date: Sat, 20 Jul 2024 14:33:24 +0800 Subject: [PATCH 1/8] fix: remove `name` from eslintrc config; fixes #489 (#490) --- index.js | 1 - 1 file changed, 1 deletion(-) diff --git a/index.js b/index.js index a063a7cd..41ff2f3a 100644 --- a/index.js +++ b/index.js @@ -43,7 +43,6 @@ const pluginPromise = { } pluginPromise.configs = { recommended: { - name: 'promise/recommended', plugins: ['promise'], rules: recommendedRules, }, From 36d13f5266158eef615d3801117e40000c29dd21 Mon Sep 17 00:00:00 2001 From: Brett Zamir Date: Mon, 22 Jul 2024 07:48:52 +0800 Subject: [PATCH 2/8] docs: add for `prefer-await-to-callbacks`; fixes #118 (#491) --- README.md | 2 +- docs/rules/prefer-await-to-callbacks.md | 36 ++++++++++++++++++++++++- rules/prefer-await-to-callbacks.js | 2 +- 3 files changed, 37 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 44a38b1e..5a64283c 100644 --- a/README.md +++ b/README.md @@ -112,7 +112,7 @@ or start with the recommended rule set: | [no-return-in-finally](docs/rules/no-return-in-finally.md) | Disallow return statements in `finally()`. | | ✅ | | | | [no-return-wrap](docs/rules/no-return-wrap.md) | Disallow wrapping values in `Promise.resolve` or `Promise.reject` when not needed. | ✅ | | | | | [param-names](docs/rules/param-names.md) | Enforce consistent param names and ordering when creating new promises. | ✅ | | | | -| [prefer-await-to-callbacks](docs/rules/prefer-await-to-callbacks.md) | Prefer async/await to the callback pattern. | | | | | +| [prefer-await-to-callbacks](docs/rules/prefer-await-to-callbacks.md) | Prefer `async`/`await` to the callback pattern. | | | | | | [prefer-await-to-then](docs/rules/prefer-await-to-then.md) | Prefer `await` to `then()`/`catch()`/`finally()` for reading Promise values. | | | | | | [valid-params](docs/rules/valid-params.md) | Enforces the proper number of arguments are passed to Promise functions. | | ✅ | | | diff --git a/docs/rules/prefer-await-to-callbacks.md b/docs/rules/prefer-await-to-callbacks.md index dc6a2203..72584b0d 100644 --- a/docs/rules/prefer-await-to-callbacks.md +++ b/docs/rules/prefer-await-to-callbacks.md @@ -1,3 +1,37 @@ -# Prefer async/await to the callback pattern (`promise/prefer-await-to-callbacks`) +# Prefer `async`/`await` to the callback pattern (`promise/prefer-await-to-callbacks`) + +`async`/`await` is a clearer pattern to follow than using callbacks. + +## Rule details + +ES2017's `async`/`await` makes it easier to deal with asynchronous code than the +callback pattern. + +Examples of **incorrect** code for this rule: + +```js +cb() +callback() +doSomething(arg, (err) => {}) +function doSomethingElse(cb) {} +``` + +Examples of **correct** code for this rule: + +```js +await doSomething(arg) +async function doSomethingElse() {} +yield yieldValue(err => {}) +eventEmitter.on('error', err => {}) +``` + +## When Not To Use It + +If you are not targeting an ES2017 or higher environment and cannot transpile +`async`/`await`, you should disable this rule. + +## Further Reading + +- [Making asynchronous programming easier with async and await on MDN](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Asynchronous/Promises) diff --git a/rules/prefer-await-to-callbacks.js b/rules/prefer-await-to-callbacks.js index ca5f9ff9..65b411ef 100644 --- a/rules/prefer-await-to-callbacks.js +++ b/rules/prefer-await-to-callbacks.js @@ -7,7 +7,7 @@ module.exports = { meta: { type: 'suggestion', docs: { - description: 'Prefer async/await to the callback pattern.', + description: 'Prefer `async`/`await` to the callback pattern.', url: getDocsUrl('prefer-await-to-callbacks'), }, messages: { From f368c5a7e4a1c1f40cafbf038b629e6054d2027e Mon Sep 17 00:00:00 2001 From: Brett Zamir Date: Mon, 22 Jul 2024 07:51:38 +0800 Subject: [PATCH 3/8] fix(`always-return`): treat process.exit() or process.abort() as an acceptable "return" (#493) --- __tests__/always-return.js | 8 ++++++++ rules/always-return.js | 4 ++++ 2 files changed, 12 insertions(+) diff --git a/__tests__/always-return.js b/__tests__/always-return.js index f9411f19..cb888fd6 100644 --- a/__tests__/always-return.js +++ b/__tests__/always-return.js @@ -17,6 +17,8 @@ ruleTester.run('always-return', rule, { 'hey.then(x => { return; })', 'hey.then(x => { return x ? x.id : null })', 'hey.then(x => { return x * 10 })', + 'hey.then(x => { process.exit(0); })', + 'hey.then(x => { process.abort(); })', 'hey.then(function() { return 42; })', 'hey.then(function() { return new Promise(); })', 'hey.then(function() { return "x"; }).then(doSomethingWicked)', @@ -24,6 +26,8 @@ ruleTester.run('always-return', rule, { 'hey.then(function() { throw new Error("msg"); })', 'hey.then(function(x) { if (!x) { throw new Error("no x"); } return x; })', 'hey.then(function(x) { if (x) { return x; } throw new Error("no x"); })', + 'hey.then(function(x) { if (x) { process.exit(0); } throw new Error("no x"); })', + 'hey.then(function(x) { if (x) { process.abort(); } throw new Error("no x"); })', 'hey.then(x => { throw new Error("msg"); })', 'hey.then(x => { if (!x) { throw new Error("no x"); } return x; })', 'hey.then(x => { if (x) { return x; } throw new Error("no x"); })', @@ -140,6 +144,10 @@ ruleTester.run('always-return', rule, { code: 'hey.then(function() { if (x) { } else { return x; }})', errors: [{ message }], }, + { + code: 'hey.then(function() { if (x) { process.chdir(); } else { return x; }})', + errors: [{ message }], + }, { code: 'hey.then(function() { if (x) { return you.then(function() { return x; }); } })', errors: [{ message }], diff --git a/rules/always-return.js b/rules/always-return.js index 57d497d0..be74b5c2 100644 --- a/rules/always-return.js +++ b/rules/always-return.js @@ -202,6 +202,10 @@ module.exports = { return { 'ReturnStatement:exit': markCurrentBranchAsGood, 'ThrowStatement:exit': markCurrentBranchAsGood, + 'ExpressionStatement > CallExpression > MemberExpression[object.name="process"][property.name="exit"]:exit': + markCurrentBranchAsGood, + 'ExpressionStatement > CallExpression > MemberExpression[object.name="process"][property.name="abort"]:exit': + markCurrentBranchAsGood, /** * @param {CodePathSegment} segment From fa482cc1134f5669b2dd9f56ea2ef9e96c3c30a0 Mon Sep 17 00:00:00 2001 From: Brett Zamir Date: Mon, 22 Jul 2024 07:51:46 +0800 Subject: [PATCH 4/8] feat: add `strict` option to disallow `then` or `catch` following `await` or `yield` (#494) --- __tests__/prefer-await-to-then.js | 38 ++++++++++++++++++++++++++++++ docs/rules/prefer-await-to-then.md | 15 ++++++++++++ rules/prefer-await-to-then.js | 15 ++++++++++-- 3 files changed, 66 insertions(+), 2 deletions(-) diff --git a/__tests__/prefer-await-to-then.js b/__tests__/prefer-await-to-then.js index 3bdf5bd8..2770c7b2 100644 --- a/__tests__/prefer-await-to-then.js +++ b/__tests__/prefer-await-to-then.js @@ -15,6 +15,8 @@ ruleTester.run('prefer-await-to-then', rule, { 'async function hi() { await thing() }', 'async function hi() { await thing().then() }', 'async function hi() { await thing().catch() }', + 'async function hi() { await thing().finally() }', + 'function * hi() { yield thing().then() }', 'a = async () => (await something())', `a = async () => { try { await something() } catch (error) { somethingElse() } @@ -54,5 +56,41 @@ ruleTester.run('prefer-await-to-then', rule, { code: 'function foo() { hey.finally(x => {}) }', errors: [{ message }], }, + { + code: 'async function hi() { await thing().then() }', + errors: [{ message }], + options: [ + { + strict: true, + }, + ], + }, + { + code: 'async function hi() { await thing().catch() }', + errors: [{ message }], + options: [ + { + strict: true, + }, + ], + }, + { + code: 'async function hi() { await thing().finally() }', + errors: [{ message }], + options: [ + { + strict: true, + }, + ], + }, + { + code: 'function * hi() { yield thing().then() }', + errors: [{ message }], + options: [ + { + strict: true, + }, + ], + }, ], }) diff --git a/docs/rules/prefer-await-to-then.md b/docs/rules/prefer-await-to-then.md index 7fce076a..be9ba170 100644 --- a/docs/rules/prefer-await-to-then.md +++ b/docs/rules/prefer-await-to-then.md @@ -44,3 +44,18 @@ function exampleFour() { return myPromise.finally(cleanup) } ``` + +## Options + +### `strict` + +Normally, this rule allows `then` or `catch` following an `await` (or `yield`) +expression. Setting this option to `true` will err on such cases: + +This will fail with the `strict` option: + +```js +async function hi() { + await thing().then() +} +``` diff --git a/rules/prefer-await-to-then.js b/rules/prefer-await-to-then.js index 0f3de93b..3c2fab76 100644 --- a/rules/prefer-await-to-then.js +++ b/rules/prefer-await-to-then.js @@ -16,7 +16,16 @@ module.exports = { 'Prefer `await` to `then()`/`catch()`/`finally()` for reading Promise values.', url: getDocsUrl('prefer-await-to-then'), }, - schema: [], + schema: [ + { + type: 'object', + properties: { + strict: { + type: 'boolean', + }, + }, + }, + ], messages: { preferAwaitToCallback: 'Prefer await to then()/catch()/finally().', }, @@ -40,9 +49,11 @@ module.exports = { return getScope(context, node).block.type === 'Program' } + const { strict } = context.options[0] || {} + return { 'CallExpression > MemberExpression.callee'(node) { - if (isTopLevelScoped(node) || isInsideYieldOrAwait(node)) { + if (isTopLevelScoped(node) || (!strict && isInsideYieldOrAwait(node))) { return } From 9d0e44733d32a0b22fd604b758386bd51f9da6df Mon Sep 17 00:00:00 2001 From: Brett Zamir Date: Mon, 22 Jul 2024 19:15:04 +0800 Subject: [PATCH 5/8] docs: recommend `util.promisify` instead of pify and `util.callbackify` over nodeify (#492) --- README.md | 40 +++++++++++++++------------- docs/rules/avoid-new.md | 5 ++-- docs/rules/no-callback-in-promise.md | 5 ++-- rules/avoid-new.js | 2 +- rules/no-callback-in-promise.js | 2 +- 5 files changed, 29 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index 5a64283c..85d6f281 100644 --- a/README.md +++ b/README.md @@ -98,23 +98,23 @@ or start with the recommended rule set: ✅ Set in the `recommended` configuration.\ 🔧 Automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/user-guide/command-line-interface#--fix). -| Name                      | Description | 💼 | ⚠️ | 🚫 | 🔧 | -| :------------------------------------------------------------------- | :------------------------------------------------------------------------------------- | :-- | :-- | :-- | :-- | -| [always-return](docs/rules/always-return.md) | Require returning inside each `then()` to create readable and reusable Promise chains. | ✅ | | | | -| [avoid-new](docs/rules/avoid-new.md) | Disallow creating `new` promises outside of utility libs (use [pify][] instead). | | | ✅ | | -| [catch-or-return](docs/rules/catch-or-return.md) | Enforce the use of `catch()` on un-returned promises. | ✅ | | | | -| [no-callback-in-promise](docs/rules/no-callback-in-promise.md) | Disallow calling `cb()` inside of a `then()` (use [nodeify][] instead). | | ✅ | | | -| [no-multiple-resolved](docs/rules/no-multiple-resolved.md) | Disallow creating new promises with paths that resolve multiple times. | | | | | -| [no-native](docs/rules/no-native.md) | Require creating a `Promise` constructor before using it in an ES5 environment. | | | ✅ | | -| [no-nesting](docs/rules/no-nesting.md) | Disallow nested `then()` or `catch()` statements. | | ✅ | | | -| [no-new-statics](docs/rules/no-new-statics.md) | Disallow calling `new` on a Promise static method. | ✅ | | | 🔧 | -| [no-promise-in-callback](docs/rules/no-promise-in-callback.md) | Disallow using promises inside of callbacks. | | ✅ | | | -| [no-return-in-finally](docs/rules/no-return-in-finally.md) | Disallow return statements in `finally()`. | | ✅ | | | -| [no-return-wrap](docs/rules/no-return-wrap.md) | Disallow wrapping values in `Promise.resolve` or `Promise.reject` when not needed. | ✅ | | | | -| [param-names](docs/rules/param-names.md) | Enforce consistent param names and ordering when creating new promises. | ✅ | | | | -| [prefer-await-to-callbacks](docs/rules/prefer-await-to-callbacks.md) | Prefer `async`/`await` to the callback pattern. | | | | | -| [prefer-await-to-then](docs/rules/prefer-await-to-then.md) | Prefer `await` to `then()`/`catch()`/`finally()` for reading Promise values. | | | | | -| [valid-params](docs/rules/valid-params.md) | Enforces the proper number of arguments are passed to Promise functions. | | ✅ | | | +| Name                      | Description | 💼 | ⚠️ | 🚫 | 🔧 | +| :------------------------------------------------------------------- | :----------------------------------------------------------------------------------------- | :-- | :-- | :-- | :-- | +| [always-return](docs/rules/always-return.md) | Require returning inside each `then()` to create readable and reusable Promise chains. | ✅ | | | | +| [avoid-new](docs/rules/avoid-new.md) | Disallow creating `new` promises outside of utility libs (use [util.promisify][] instead). | | | ✅ | | +| [catch-or-return](docs/rules/catch-or-return.md) | Enforce the use of `catch()` on un-returned promises. | ✅ | | | | +| [no-callback-in-promise](docs/rules/no-callback-in-promise.md) | Disallow calling `cb()` inside of a `then()` (use [util.callbackify][] instead). | | ✅ | | | +| [no-multiple-resolved](docs/rules/no-multiple-resolved.md) | Disallow creating new promises with paths that resolve multiple times. | | | | | +| [no-native](docs/rules/no-native.md) | Require creating a `Promise` constructor before using it in an ES5 environment. | | | ✅ | | +| [no-nesting](docs/rules/no-nesting.md) | Disallow nested `then()` or `catch()` statements. | | ✅ | | | +| [no-new-statics](docs/rules/no-new-statics.md) | Disallow calling `new` on a Promise static method. | ✅ | | | 🔧 | +| [no-promise-in-callback](docs/rules/no-promise-in-callback.md) | Disallow using promises inside of callbacks. | | ✅ | | | +| [no-return-in-finally](docs/rules/no-return-in-finally.md) | Disallow return statements in `finally()`. | | ✅ | | | +| [no-return-wrap](docs/rules/no-return-wrap.md) | Disallow wrapping values in `Promise.resolve` or `Promise.reject` when not needed. | ✅ | | | | +| [param-names](docs/rules/param-names.md) | Enforce consistent param names and ordering when creating new promises. | ✅ | | | | +| [prefer-await-to-callbacks](docs/rules/prefer-await-to-callbacks.md) | Prefer `async`/`await` to the callback pattern. | | | | | +| [prefer-await-to-then](docs/rules/prefer-await-to-then.md) | Prefer `await` to `then()`/`catch()`/`finally()` for reading Promise values. | | | | | +| [valid-params](docs/rules/valid-params.md) | Enforces the proper number of arguments are passed to Promise functions. | | ✅ | | | @@ -129,8 +129,10 @@ or start with the recommended rule set: - (c) MMXV jden - ISC license. - (c) 2016 Jamund Ferguson - ISC license. -[nodeify]: https://www.npmjs.com/package/nodeify -[pify]: https://www.npmjs.com/package/pify +[util.callbackify]: + https://nodejs.org/docs/latest/api/util.html#utilcallbackifyoriginal +[util.promisify]: + https://nodejs.org/dist/latest-v8.x/docs/api/util.html#util_util_promisify_original [@aaditmshah]: https://github.com/aaditmshah [@macklinu]: https://github.com/macklinu [@xjamundx]: https://github.com/xjamundx diff --git a/docs/rules/avoid-new.md b/docs/rules/avoid-new.md index a03c6a19..3e8919e2 100644 --- a/docs/rules/avoid-new.md +++ b/docs/rules/avoid-new.md @@ -1,8 +1,9 @@ -# Disallow creating `new` promises outside of utility libs (use [pify][] instead) (`promise/avoid-new`) +# Disallow creating `new` promises outside of utility libs (use [util.promisify][] instead) (`promise/avoid-new`) 🚫 This rule is _disabled_ in the following configs: ✅ `flat/recommended`, ✅ `recommended`. -[pify]: https://www.npmjs.com/package/pify +[util.promisify]: + https://nodejs.org/dist/latest-v8.x/docs/api/util.html#util_util_promisify_original diff --git a/docs/rules/no-callback-in-promise.md b/docs/rules/no-callback-in-promise.md index 0188b1a2..779f9466 100644 --- a/docs/rules/no-callback-in-promise.md +++ b/docs/rules/no-callback-in-promise.md @@ -1,4 +1,4 @@ -# Disallow calling `cb()` inside of a `then()` (use [nodeify][] instead) (`promise/no-callback-in-promise`) +# Disallow calling `cb()` inside of a `then()` (use [util.callbackify][] instead) (`promise/no-callback-in-promise`) ⚠️ This rule _warns_ in the following configs: ✅ `flat/recommended`, ✅ `recommended`. @@ -80,4 +80,5 @@ callback code instead of combining the approaches. String list of callback function names to exempt. -[nodeify]: https://www.npmjs.com/package/nodeify +[util.callbackify]: + https://nodejs.org/docs/latest/api/util.html#utilcallbackifyoriginal diff --git a/rules/avoid-new.js b/rules/avoid-new.js index c5742aec..71cf4f7c 100644 --- a/rules/avoid-new.js +++ b/rules/avoid-new.js @@ -12,7 +12,7 @@ module.exports = { type: 'suggestion', docs: { description: - 'Disallow creating `new` promises outside of utility libs (use [pify][] instead).', + 'Disallow creating `new` promises outside of utility libs (use [util.promisify][] instead).', url: getDocsUrl('avoid-new'), }, schema: [], diff --git a/rules/no-callback-in-promise.js b/rules/no-callback-in-promise.js index cd86c472..bc786749 100644 --- a/rules/no-callback-in-promise.js +++ b/rules/no-callback-in-promise.js @@ -18,7 +18,7 @@ module.exports = { type: 'suggestion', docs: { description: - 'Disallow calling `cb()` inside of a `then()` (use [nodeify][] instead).', + 'Disallow calling `cb()` inside of a `then()` (use [util.callbackify][] instead).', url: getDocsUrl('no-callback-in-promise'), }, messages: { From aea6d94f8391da80270949e76bf39c5ec2c17baf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Jul 2024 14:54:08 +0200 Subject: [PATCH 6/8] chore(deps): bump actions/checkout from 3 to 4 (#500) --- .github/workflows/format.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml index 0578b546..de93b6db 100644 --- a/.github/workflows/format.yml +++ b/.github/workflows/format.yml @@ -16,7 +16,7 @@ jobs: steps: - name: ⬇️ Checkout repo - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: ⎔ Setup node uses: actions/setup-node@v3 From 84334f4ba5b2d8301ed6c41dc82f55c01f981b6c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Jul 2024 14:56:19 +0200 Subject: [PATCH 7/8] chore(deps): bump actions/setup-node from 3 to 4 (#499) --- .github/workflows/format.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml index de93b6db..584d4ef4 100644 --- a/.github/workflows/format.yml +++ b/.github/workflows/format.yml @@ -19,7 +19,7 @@ jobs: uses: actions/checkout@v4 - name: ⎔ Setup node - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: 18 From 8a981d293565e4e1c1fcedd52c18e2809322ed4e Mon Sep 17 00:00:00 2001 From: Sebastian Good <2230835+scagood@users.noreply.github.com> Date: Wed, 24 Jul 2024 05:46:27 +0100 Subject: [PATCH 8/8] chore!: Update node versions to align with eslint v9 (#484) --- .github/workflows/ci.yml | 14 +++++--------- package-lock.json | 2 +- package.json | 2 +- 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 614ba4fa..7903ca7a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -41,27 +41,23 @@ jobs: strategy: matrix: eslint: [8] - node: [12.22.0, 12, 14.17.0, 14, 16.0.0, 16, 18, 20] + node: [18.18.0, 20.9.0, 21.1.0, 22] os: [ubuntu-latest] include: # ESLint v9 - eslint: 9 - node: 20 + node: 22 os: ubuntu-latest # On other platforms - os: windows-latest eslint: 8 - node: 18 + node: 22 - os: macos-latest eslint: 8 - node: 18 - # On old ESLint versions - - eslint: 7 - node: 18 - os: ubuntu-latest + node: 22 # On the minimum supported ESLint/Node.js version - eslint: 7.0.0 - node: 12.22.0 + node: 18.18.0 os: ubuntu-latest runs-on: ${{ matrix.os }} steps: diff --git a/package-lock.json b/package-lock.json index b5099197..f63a11e5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,7 +27,7 @@ "typescript": "^4.9.3" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://opencollective.com/eslint" diff --git a/package.json b/package.json index a9e9856d..0accdfb3 100644 --- a/package.json +++ b/package.json @@ -87,7 +87,7 @@ "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": "https://opencollective.com/eslint" }