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 16, 2024
1 parent eb5af8b commit f530229
Show file tree
Hide file tree
Showing 6 changed files with 271 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
51 changes: 51 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,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).

<!-- 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.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()]);
```
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
45 changes: 45 additions & 0 deletions rules/no-single-promise-in-promise-methods.js
Original file line number Diff line number Diff line change
@@ -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,
},
};
13 changes: 13 additions & 0 deletions rules/utils/is-promise-method-with-array.js
Original file line number Diff line number Diff line change
@@ -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;
160 changes: 160 additions & 0 deletions test/no-single-promise-in-promise-methods.mjs
Original file line number Diff line number Diff line change
@@ -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;
`,
},
],
});

0 comments on commit f530229

Please sign in to comment.