Skip to content

Commit

Permalink
Add no-await-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 719547d
Show file tree
Hide file tree
Showing 6 changed files with 304 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 @@ -20,6 +20,7 @@ module.exports = {
'unicorn/no-array-push-push': 'error',
'unicorn/no-array-reduce': 'error',
'unicorn/no-await-expression-member': 'error',
'unicorn/no-await-in-promise-methods': 'error',
'unicorn/no-console-spaces': 'error',
'unicorn/no-document-cookie': 'error',
'unicorn/no-empty-file': 'error',
Expand Down
44 changes: 44 additions & 0 deletions docs/rules/no-await-in-promise-methods.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Disallow using `await` in `Promise` method parameters

💼 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` -->

Awaited parameters in a Promise.all(), Promise.allSettled(), Promise.any() or Promise.race() method is probably a mistake.

## Fail

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

Promise.allSettled([promise, await promise, await promise, promise])
Promise['allSettled']([promise, await promise, await promise, promise])

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

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

## Pass

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

Promise.allSettled([promise, promise, promise, promise])
Promise['allSettled']([promise, promise, promise, promise])

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

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

Promise.resolve([await promise])
```
1 change: 1 addition & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ If you don't use the preset, ensure you use the same `env` and `parserOptions` c
| [no-array-push-push](docs/rules/no-array-push-push.md) | Enforce combining multiple `Array#push()` into one call. || 🔧 | 💡 |
| [no-array-reduce](docs/rules/no-array-reduce.md) | Disallow `Array#reduce()` and `Array#reduceRight()`. || | |
| [no-await-expression-member](docs/rules/no-await-expression-member.md) | Disallow member access from await expression. || 🔧 | |
| [no-await-in-promise-methods](docs/rules/no-await-in-promise-methods.md) | Disallow using `await` in `Promise` method parameters. || 🔧 | |
| [no-console-spaces](docs/rules/no-console-spaces.md) | Do not use leading/trailing space between `console.log` parameters. || 🔧 | |
| [no-document-cookie](docs/rules/no-document-cookie.md) | Do not use `document.cookie` directly. || | |
| [no-empty-file](docs/rules/no-empty-file.md) | Disallow empty files. || | |
Expand Down
55 changes: 55 additions & 0 deletions rules/no-await-in-promise-methods.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
'use strict';
const isPromiseMethodWithArray = require('./utils/is-promise-method-with-array.js');

const MESSAGE_ID = 'no-await-in-promise-methods';
const messages = {
[MESSAGE_ID]: 'Parameters in `Promise.{{method}}` should not be awaited.',
};
const METHODS = ['all', 'allSettled', 'any', 'race'];

const getArrayElements = node => node.arguments[0].elements;

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

const getFixer = ({sourceCode}, element) => fixer => {
const {argument} = element;
const text = sourceCode.getText(argument);
const replacement = argument.type === 'SequenceExpression' ? `(${text})` : text;

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

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

for (const element of getArrayElements(node)) {
if (element?.type === 'AwaitExpression') {
context.report({
node: element,
messageId: MESSAGE_ID,
data: {
method: getMethodName(node),
},
fix: getFixer(context, element),
});
}
}
},
});

/** @type {import('eslint').Rule.RuleModule} */
module.exports = {
create,
meta: {
type: 'suggestion',
docs: {
description: 'Disallow using `await` in `Promise` method parameters.',
},
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;
187 changes: 187 additions & 0 deletions test/no-await-in-promise-methods.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
import outdent from 'outdent';
import {getTester} from './utils/test.mjs';

const {test} = getTester(import.meta);

const error = {
messageId: 'no-await-in-promise-methods',
};

test({
valid: [
outdent`
Promise.all([promise, promise, promise, promise])
`,
outdent`
Promise[all]([promise, promise, promise, promise])
`,
outdent`
Promise.allSettled([promise, promise, promise, promise])
`,
outdent`
Promise[allSettled]([promise, promise, promise, promise])
`,
outdent`
Promise.any([promise, promise, promise, promise])
`,
outdent`
Promise[any]([promise, promise, promise, promise])
`,
outdent`
Promise.race([promise, promise, promise, promise])
`,
outdent`
Promise[race]([promise, promise, promise, promise])
`,
outdent`
Promise.resolve([await promise])
`,
],

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

0 comments on commit 719547d

Please sign in to comment.