diff --git a/configs/recommended.js b/configs/recommended.js index 42ea8e9730..33f5374e58 100644 --- a/configs/recommended.js +++ b/configs/recommended.js @@ -38,6 +38,7 @@ module.exports = { 'unicorn/no-null': 'error', 'unicorn/no-object-as-default-parameter': 'error', 'unicorn/no-process-exit': 'error', + 'unicorn/no-single-promise-in-promise-methods': 'error', 'unicorn/no-static-only-class': 'error', 'unicorn/no-thenable': 'error', 'unicorn/no-this-assignment': 'error', diff --git a/docs/rules/no-single-promise-in-promise-methods.md b/docs/rules/no-single-promise-in-promise-methods.md new file mode 100644 index 0000000000..f49fe6ef35 --- /dev/null +++ b/docs/rules/no-single-promise-in-promise-methods.md @@ -0,0 +1,51 @@ +# Disallow using `Promise` method with a single element array as parameter + +💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs). + +🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). + + + + +Single element array parameter in a Promise.all(), Promise.allSettled(), Promise.any() or Promise.race() method is probably a mistake. + +## Fail + +```js +const func = () => Promise.resolve('promise'); +const promise = func(); + +await Promise.all([func()]); +await Promise.all([promise]); + +await Promise.allSettled([func()]); +await Promise.allSettled([promise]); + +await Promise.any([func()]); +await Promise.any([promise]); + +await Promise.race([func()]); +await Promise.race([promise]); +``` + +## Pass + +```js +const func = () => Promise.resolve('promise'); +const promise = func(); +const tab = [func(), promise]; + +await Promise.all([func(), promise]); +await Promise.all(tab); + +await Promise.allSettled([func(), promise]); +await Promise.allSettled(tab); + +await Promise.any([func(), promise]); +await Promise.any(tab); + +await Promise.race([func(), promise]); +await Promise.race(tab); + +await Promise.resolve([func()]); +``` diff --git a/readme.md b/readme.md index e1a70df5a8..56178bd647 100644 --- a/readme.md +++ b/readme.md @@ -146,6 +146,7 @@ If you don't use the preset, ensure you use the same `env` and `parserOptions` c | [no-null](docs/rules/no-null.md) | Disallow the use of the `null` literal. | ✅ | 🔧 | 💡 | | [no-object-as-default-parameter](docs/rules/no-object-as-default-parameter.md) | Disallow the use of objects as default parameters. | ✅ | | | | [no-process-exit](docs/rules/no-process-exit.md) | Disallow `process.exit()`. | ✅ | | | +| [no-single-promise-in-promise-methods](docs/rules/no-single-promise-in-promise-methods.md) | Disallow using `Promise` method with a single element array as parameter. | ✅ | 🔧 | | | [no-static-only-class](docs/rules/no-static-only-class.md) | Disallow classes that only have static members. | ✅ | 🔧 | | | [no-thenable](docs/rules/no-thenable.md) | Disallow `then` property. | ✅ | | | | [no-this-assignment](docs/rules/no-this-assignment.md) | Disallow assigning `this` to a variable. | ✅ | | | diff --git a/rules/no-single-promise-in-promise-methods.js b/rules/no-single-promise-in-promise-methods.js new file mode 100644 index 0000000000..9b7cdc483e --- /dev/null +++ b/rules/no-single-promise-in-promise-methods.js @@ -0,0 +1,45 @@ +'use strict'; +const isPromiseMethodWithArray = require('./utils/is-promise-method-with-array.js'); + +const MESSAGE_ID = 'no-single-promise-in-promise-methods'; +const messages = { + [MESSAGE_ID]: 'Parameter in `Promise.{{method}}` should not be a single element array.', +}; + +const isPromiseMethodWithSinglePromise = node => + isPromiseMethodWithArray(node) + && node.arguments[0].elements.length === 1; + +const getMethodName = node => node.callee.property.name; + +const getFixer = ({sourceCode}, node) => fixer => + fixer.replaceText(node, sourceCode.getText(node.arguments[0].elements[0])); + +/** @param {import('eslint').Rule.RuleContext} context */ +const create = context => ({ + CallExpression(node) { + if (isPromiseMethodWithSinglePromise(node)) { + context.report({ + node, + messageId: MESSAGE_ID, + data: { + method: getMethodName(node), + }, + fix: getFixer(context, node), + }); + } + }, +}); + +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + create, + meta: { + type: 'suggestion', + docs: { + description: 'Disallow using `Promise` method with a single element array as parameter.', + }, + fixable: 'code', + messages, + }, +}; diff --git a/rules/utils/is-promise-method-with-array.js b/rules/utils/is-promise-method-with-array.js new file mode 100644 index 0000000000..6e4e55a4bb --- /dev/null +++ b/rules/utils/is-promise-method-with-array.js @@ -0,0 +1,13 @@ +'use strict'; +const METHODS = new Set(['all', 'allSettled', 'any', 'race']); + +const isPromiseMethodWithArray = node => + node.callee.type === 'MemberExpression' + && node.callee.object.type === 'Identifier' + && node.callee.object.name === 'Promise' + && node.callee.property.type === 'Identifier' + && METHODS.has(node.callee.property.name) + && node.arguments.length === 1 + && node.arguments[0].type === 'ArrayExpression'; + +module.exports = isPromiseMethodWithArray; diff --git a/test/no-single-promise-in-promise-methods.mjs b/test/no-single-promise-in-promise-methods.mjs new file mode 100644 index 0000000000..cabfdbac7c --- /dev/null +++ b/test/no-single-promise-in-promise-methods.mjs @@ -0,0 +1,160 @@ +import outdent from 'outdent'; +import {getTester} from './utils/test.mjs'; + +const {test} = getTester(import.meta); + +const error = { + messageId: 'no-single-promise-in-promise-methods', +}; + +test({ + valid: [ + outdent` + const func = () => Promise.resolve('promise'); + const promise = func(); + await Promise.all([func(), promise]); + `, + outdent` + const func = () => Promise.resolve('promise'); + const promise = func(); + const tab = [func(), promise]; + await Promise.all(tab); + `, + outdent` + const func = () => Promise.resolve('promise'); + const promise = func(); + await Promise.allSettled([func(), promise]); + `, + outdent` + const func = () => Promise.resolve('promise'); + const promise = func(); + const tab = [func(), promise]; + await Promise.allSettled(tab); + `, + outdent` + const func = () => Promise.resolve('promise'); + const promise = func(); + await Promise.any([func(), promise]); + `, + outdent` + const func = () => Promise.resolve('promise'); + const promise = func(); + const tab = [func(), promise]; + await Promise.any(tab); + `, + outdent` + const func = () => Promise.resolve('promise'); + const promise = func(); + await Promise.race([func(), promise]); + `, + outdent` + const func = () => Promise.resolve('promise'); + const promise = func(); + const tab = [func(), promise]; + await Promise.race(tab); + `, + outdent` + const func = () => Promise.resolve('promise'); + await Promise.resolve([func()]); + `, + ], + + invalid: [ + { + code: outdent` + const func = () => Promise.resolve('promise'); + await Promise.all([func()]); + `, + errors: [error], + output: outdent` + const func = () => Promise.resolve('promise'); + await func(); + `, + }, + { + code: outdent` + const func = () => Promise.resolve('promise'); + const promise = func(); + await Promise.all([promise]); + `, + errors: [error], + output: outdent` + const func = () => Promise.resolve('promise'); + const promise = func(); + await promise; + `, + }, + { + code: outdent` + const func = () => Promise.resolve('promise'); + await Promise.allSettled([func()]); + `, + errors: [error], + output: outdent` + const func = () => Promise.resolve('promise'); + await func(); + `, + }, + { + code: outdent` + const func = () => Promise.resolve('promise'); + const promise = func(); + await Promise.allSettled([promise]); + `, + errors: [error], + output: outdent` + const func = () => Promise.resolve('promise'); + const promise = func(); + await promise; + `, + }, + { + code: outdent` + const func = () => Promise.resolve('promise'); + await Promise.any([func()]); + `, + errors: [error], + output: outdent` + const func = () => Promise.resolve('promise'); + await func(); + `, + }, + { + code: outdent` + const func = () => Promise.resolve('promise'); + const promise = func(); + await Promise.any([promise]); + `, + errors: [error], + output: outdent` + const func = () => Promise.resolve('promise'); + const promise = func(); + await promise; + `, + }, + { + code: outdent` + const func = () => Promise.resolve('promise'); + await Promise.race([func()]); + `, + errors: [error], + output: outdent` + const func = () => Promise.resolve('promise'); + await func(); + `, + }, + { + code: outdent` + const func = () => Promise.resolve('promise'); + const promise = func(); + await Promise.race([promise]); + `, + errors: [error], + output: outdent` + const func = () => Promise.resolve('promise'); + const promise = func(); + await promise; + `, + }, + ], +});