Skip to content

Commit

Permalink
Add no-single-promise-in-promise-methods rule
Browse files Browse the repository at this point in the history
  • Loading branch information
Clement398 committed Jan 19, 2024
1 parent 331b306 commit a8c6d19
Show file tree
Hide file tree
Showing 6 changed files with 252 additions and 0 deletions.
1 change: 1 addition & 0 deletions configs/recommended.js
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
44 changes: 44 additions & 0 deletions docs/rules/no-single-promise-in-promise-methods.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# 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).

<!-- end auto-generated rule header -->
<!-- Do not manually modify this header. Run: `npm run fix:eslint-docs` -->

Single element array parameter in a Promise.all(), Promise.any() or Promise.race() method is probably a mistake.

## Fail

```js
Promise.all([promise])
Promise['all']([promise])

Promise.any([promise])
Promise['any']([promise])

Promise.race([promise])
Promise['race']([promise])
```

## Pass

```js
Promise.all([promise, anotherPromise])
Promise['all']([promise, anotherPromise])
Promise.all(notArrayLiteral)
Promise.all([...promises])

Promise.any([promise, anotherPromise])
Promise['any']([promise, anotherPromise])
Promise.any(notArrayLiteral)
Promise.any([...promises])

Promise.race([promise, anotherPromise])
Promise['race']([promise, anotherPromise])
Promise.race(notArrayLiteral)
Promise.race([...promises])

Promise.allSettled([promise])
```
1 change: 1 addition & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -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. || | |
Expand Down
54 changes: 54 additions & 0 deletions rules/no-single-promise-in-promise-methods.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
'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 METHODS = ['all', 'any', 'race'];

const isPromiseMethodWithSinglePromise = (node, methods) =>
isPromiseMethodWithArray(node, methods)
&& node.arguments[0].elements.length === 1
&& node.arguments[0].elements[0].type !== 'SpreadElement';

const getMethodName = node => node.callee.property.name;

const getFixer = ({sourceCode}, node) => fixer => {
const [element] = node.arguments[0].elements;
const text = sourceCode.getText(element);
const replacement = element.type === 'SequenceExpression' ? `(${text})` : text;

return fixer.replaceText(node, replacement);
};

/** @param {import('eslint').Rule.RuleContext} context */
const create = context => ({
CallExpression(node) {
if (!isPromiseMethodWithSinglePromise(node, METHODS)) {
return;
}

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,
},
};
16 changes: 16 additions & 0 deletions rules/utils/is-promise-method-with-array.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
'use strict';
const isMethodCall = (property, methods) =>
(property.type === 'Identifier' && methods.includes(property.name))
|| (property.type === 'Literal' && methods.includes(property.value));

const isPromiseMethodWithArray = (node, methods) =>
node.callee.type === 'MemberExpression'
&& node.callee.object.type === 'Identifier'
&& node.callee.object.name === 'Promise'
&& node.callee.property.type === 'Identifier'
&& isMethodCall(node.callee.property, methods)
&& node.arguments.length === 1
&& node.arguments[0].type === 'ArrayExpression'
&& node.arguments[0].elements.some(element => element !== null);

module.exports = isPromiseMethodWithArray;
136 changes: 136 additions & 0 deletions test/no-single-promise-in-promise-methods.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
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`
Promise.all([promise, anotherPromise])
`,
outdent`
Promise[all]([promise, anotherPromise])
`,
outdent`
Promise.all(notArrayLiteral)
`,
outdent`
Promise.all([...promises])
`,
outdent`
Promise.any([promise, anotherPromise])
`,
outdent`
Promise[any]([promise, anotherPromise])
`,
outdent`
Promise.any(notArrayLiteral)
`,
outdent`
Promise.any([...promises])
`,
outdent`
Promise.race([promise, anotherPromise])
`,
outdent`
Promise[race]([promise, anotherPromise])
`,
outdent`
Promise.race(notArrayLiteral)
`,
outdent`
Promise.race([...promises])
`,
outdent`
Promise.allSettled([promise])
`,
],

invalid: [
{
code: outdent`
Promise.all([promise])
`,
errors: [error],
output: outdent`
promise
`,
},
{
code: outdent`
Promise[all]([promise])
`,
errors: [error],
output: outdent`
promise
`,
},
{
code: outdent`
Promise.all([(promise, anotherPromise)])
`,
errors: [error],
output: outdent`
(promise, anotherPromise)
`,
},
{
code: outdent`
Promise.any([promise])
`,
errors: [error],
output: outdent`
promise
`,
},
{
code: outdent`
Promise[any]([promise])
`,
errors: [error],
output: outdent`
promise
`,
},
{
code: outdent`
Promise.any([(promise, anotherPromise)])
`,
errors: [error],
output: outdent`
(promise, anotherPromise)
`,
},
{
code: outdent`
Promise.race([promise])
`,
errors: [error],
output: outdent`
promise
`,
},
{
code: outdent`
Promise[race]([promise])
`,
errors: [error],
output: outdent`
promise
`,
},
{
code: outdent`
Promise.race([(promise, anotherPromise)])
`,
errors: [error],
output: outdent`
(promise, anotherPromise)
`,
},
],
});

0 comments on commit a8c6d19

Please sign in to comment.