Skip to content

Commit 0e4668f

Browse files
committed
Add no-await-in-promise-methods rule
1 parent 331b306 commit 0e4668f

File tree

6 files changed

+164
-0
lines changed

6 files changed

+164
-0
lines changed

configs/recommended.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ module.exports = {
2020
'unicorn/no-array-push-push': 'error',
2121
'unicorn/no-array-reduce': 'error',
2222
'unicorn/no-await-expression-member': 'error',
23+
'unicorn/no-await-in-promise-methods': 'error',
2324
'unicorn/no-console-spaces': 'error',
2425
'unicorn/no-document-cookie': 'error',
2526
'unicorn/no-empty-file': 'error',
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# Disallow using `await` in `Promise` method parameters
2+
3+
💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs).
4+
5+
💡 This rule is manually fixable by [editor suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions).
6+
7+
<!-- end auto-generated rule header -->
8+
<!-- Do not manually modify this header. Run: `npm run fix:eslint-docs` -->
9+
10+
Awaited parameters in a Promise.all(), Promise.allSettled(), Promise.any() or Promise.race() method is probably a mistake.
11+
12+
## Fail
13+
14+
```js
15+
Promise.all([promise, await promise, await promise, promise])
16+
17+
Promise.allSettled([promise, await promise, await promise, promise])
18+
19+
Promise.any([promise, await promise, await promise, promise])
20+
21+
Promise.race([promise, await promise, await promise, promise])
22+
```
23+
24+
## Pass
25+
26+
```js
27+
Promise.all([promise, promise, promise, promise])
28+
29+
Promise.allSettled([promise, promise, promise, promise])
30+
31+
Promise.any([promise, promise, promise, promise])
32+
33+
Promise.race([promise, promise, promise, promise])
34+
35+
Promise.resolve([await promise])
36+
```

readme.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ If you don't use the preset, ensure you use the same `env` and `parserOptions` c
130130
| [no-array-push-push](docs/rules/no-array-push-push.md) | Enforce combining multiple `Array#push()` into one call. || 🔧 | 💡 |
131131
| [no-array-reduce](docs/rules/no-array-reduce.md) | Disallow `Array#reduce()` and `Array#reduceRight()`. || | |
132132
| [no-await-expression-member](docs/rules/no-await-expression-member.md) | Disallow member access from await expression. || 🔧 | |
133+
| [no-await-in-promise-methods](docs/rules/no-await-in-promise-methods.md) | Disallow using `await` in `Promise` method parameters. || | 💡 |
133134
| [no-console-spaces](docs/rules/no-console-spaces.md) | Do not use leading/trailing space between `console.log` parameters. || 🔧 | |
134135
| [no-document-cookie](docs/rules/no-document-cookie.md) | Do not use `document.cookie` directly. || | |
135136
| [no-empty-file](docs/rules/no-empty-file.md) | Disallow empty files. || | |

rules/no-await-in-promise-methods.js

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
'use strict';
2+
const isPromiseMethodWithArray = require('./utils/is-promise-method-with-array.js');
3+
4+
const MESSAGE_ID_ERROR = 'no-await-in-promise-methods/error';
5+
const MESSAGE_ID_SUGGESTION = 'no-await-in-promise-methods/suggestion';
6+
const messages = {
7+
[MESSAGE_ID_ERROR]: 'Parameters in `Promise.{{method}}` should not be awaited.',
8+
[MESSAGE_ID_SUGGESTION]: 'Remove `await`.',
9+
};
10+
const METHODS = ['all', 'allSettled', 'any', 'race'];
11+
12+
const getArrayElements = node => node.arguments[0].elements;
13+
14+
const getMethodName = node => node.callee.property.name;
15+
16+
const getFixer = ({sourceCode}, awaitedElements) => function * (fixer) {
17+
for (const awaitedElement of awaitedElements) {
18+
const firstToken = sourceCode.getFirstToken(awaitedElement);
19+
const secondToken = sourceCode.getTokenAfter(firstToken);
20+
21+
yield fixer.removeRange([firstToken.range[0], secondToken.range[0]]);
22+
}
23+
};
24+
25+
/** @param {import('eslint').Rule.RuleContext} context */
26+
const create = context => ({
27+
CallExpression(node) {
28+
if (!isPromiseMethodWithArray(node, METHODS)) {
29+
return;
30+
}
31+
32+
const awaitedElements = getArrayElements(node).filter(element => element?.type === 'AwaitExpression');
33+
34+
if (awaitedElements.length === 0) {
35+
return;
36+
}
37+
38+
context.report({
39+
node,
40+
messageId: MESSAGE_ID_ERROR,
41+
data: {
42+
method: getMethodName(node),
43+
},
44+
suggest: [
45+
{
46+
messageId: MESSAGE_ID_SUGGESTION,
47+
fix: getFixer(context, awaitedElements),
48+
},
49+
],
50+
});
51+
},
52+
});
53+
54+
/** @type {import('eslint').Rule.RuleModule} */
55+
module.exports = {
56+
create,
57+
meta: {
58+
type: 'suggestion',
59+
docs: {
60+
description: 'Disallow using `await` in `Promise` method parameters.',
61+
},
62+
hasSuggestions: true,
63+
messages,
64+
},
65+
};
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
'use strict';
2+
const {isMethodCall} = require('../ast/index.js');
3+
4+
const isPromiseMethodWithArray = (node, methods) =>
5+
node.callee.type === 'MemberExpression'
6+
&& node.callee.object.type === 'Identifier'
7+
&& node.callee.object.name === 'Promise'
8+
&& isMethodCall(node, methods)
9+
&& node.arguments.length === 1
10+
&& node.arguments[0].type === 'ArrayExpression'
11+
&& node.arguments[0].elements.some(element => element !== null);
12+
13+
module.exports = isPromiseMethodWithArray;

test/no-await-in-promise-methods.mjs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import {getTester} from './utils/test.mjs';
2+
3+
const {test} = getTester(import.meta);
4+
5+
const createSuggestionError = output => [{
6+
messageId: 'no-await-in-promise-methods/error',
7+
suggestions: [
8+
{
9+
messageId: 'no-await-in-promise-methods/suggestion',
10+
output,
11+
},
12+
],
13+
}];
14+
15+
test({
16+
valid: [
17+
'Promise.all([promise, promise, promise, promise])',
18+
'Promise.allSettled([promise, promise, promise, promise])',
19+
'Promise.any([promise, promise, promise, promise])',
20+
'Promise.race([promise, promise, promise, promise])',
21+
'Promise.resolve([await promise])',
22+
'Promise[all]([await promise])',
23+
'Promise.all([,])',
24+
],
25+
26+
invalid: [
27+
{
28+
code: 'Promise.all([promise, await promise, await promise, promise])',
29+
suggestion: 'Promise.all([promise, promise, promise, promise])',
30+
},
31+
{
32+
code: 'Promise.all([, await promise])',
33+
suggestion: 'Promise.all([, promise])',
34+
},
35+
{
36+
code: 'Promise.allSettled([promise, await promise, await promise, promise])',
37+
suggestion: 'Promise.allSettled([promise, promise, promise, promise])',
38+
},
39+
{
40+
code: 'Promise.any([promise, await promise, await promise, promise])',
41+
suggestion: 'Promise.any([promise, promise, promise, promise])',
42+
},
43+
{
44+
code: 'Promise.race([promise, await promise, await promise, promise])',
45+
suggestion: 'Promise.race([promise, promise, promise, promise])',
46+
},
47+
].map(({code, suggestion}) => ({code, errors: createSuggestionError(suggestion)})),
48+
});

0 commit comments

Comments
 (0)