-
-
Notifications
You must be signed in to change notification settings - Fork 353
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add
no-unnecessary-await
rule (#1904)
- Loading branch information
Showing
12 changed files
with
1,002 additions
and
38 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
# Disallow awaiting non-promise values | ||
|
||
<!-- Do not manually modify RULE_NOTICE part. Run: `npm run generate-rule-notices` --> | ||
<!-- RULE_NOTICE --> | ||
✅ *This rule is part of the [recommended](https://github.com/sindresorhus/eslint-plugin-unicorn#recommended-config) config.* | ||
|
||
🔧 *This rule is [auto-fixable](https://eslint.org/docs/user-guide/command-line-interface#fixing-problems).* | ||
<!-- /RULE_NOTICE --> | ||
|
||
The [`await` operator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await) should only be used on [`Promise`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) values. | ||
|
||
## Fail | ||
|
||
```js | ||
await await promise; | ||
``` | ||
|
||
```js | ||
await [promise1, promise2]; | ||
``` | ||
|
||
## Pass | ||
|
||
```js | ||
await promise; | ||
``` | ||
|
||
```js | ||
await Promise.allSettled([promise1, promise2]); | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
21 changes: 21 additions & 0 deletions
21
rules/fix/add-parenthesizes-to-return-or-throw-expression.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
'use strict'; | ||
const {isSemicolonToken} = require('eslint-utils'); | ||
|
||
function * addParenthesizesToReturnOrThrowExpression(fixer, node, sourceCode) { | ||
if (node.type !== 'ReturnStatement' && node.type !== 'ThrowStatement') { | ||
return; | ||
} | ||
|
||
const returnOrThrowToken = sourceCode.getFirstToken(node); | ||
yield fixer.insertTextAfter(returnOrThrowToken, ' ('); | ||
|
||
const lastToken = sourceCode.getLastToken(node); | ||
if (!isSemicolonToken(lastToken)) { | ||
yield fixer.insertTextAfter(node, ')'); | ||
return; | ||
} | ||
|
||
yield fixer.insertTextBefore(lastToken, ')'); | ||
} | ||
|
||
module.exports = addParenthesizesToReturnOrThrowExpression; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
'use strict'; | ||
const { | ||
addParenthesizesToReturnOrThrowExpression, | ||
removeSpacesAfter, | ||
} = require('./fix/index.js'); | ||
const {isParenthesized} = require('./utils/parentheses.js'); | ||
const needsSemicolon = require('./utils/needs-semicolon.js'); | ||
const isOnSameLine = require('./utils/is-on-same-line.js'); | ||
|
||
const MESSAGE_ID = 'no-unnecessary-await'; | ||
const messages = { | ||
[MESSAGE_ID]: 'Do not `await` non-promise value.', | ||
}; | ||
|
||
function notPromise(node) { | ||
switch (node.type) { | ||
case 'ArrayExpression': | ||
case 'ArrowFunctionExpression': | ||
case 'AwaitExpression': | ||
case 'BinaryExpression': | ||
case 'ClassExpression': | ||
case 'FunctionExpression': | ||
case 'JSXElement': | ||
case 'JSXFragment': | ||
case 'Literal': | ||
case 'TemplateLiteral': | ||
case 'UnaryExpression': | ||
case 'UpdateExpression': | ||
return true; | ||
case 'SequenceExpression': | ||
return notPromise(node.expressions[node.expressions.length - 1]); | ||
// No default | ||
} | ||
|
||
return false; | ||
} | ||
|
||
/** @param {import('eslint').Rule.RuleContext} context */ | ||
const create = context => ({ | ||
AwaitExpression(node) { | ||
if (!notPromise(node.argument)) { | ||
return; | ||
} | ||
|
||
const sourceCode = context.getSourceCode(); | ||
const awaitToken = sourceCode.getFirstToken(node); | ||
const problem = { | ||
node, | ||
loc: awaitToken.loc, | ||
messageId: MESSAGE_ID, | ||
}; | ||
|
||
const valueNode = node.argument; | ||
if ( | ||
// Removing `await` may change them to a declaration, if there is no `id` will cause SyntaxError | ||
valueNode.type === 'FunctionExpression' | ||
|| valueNode.type === 'ClassExpression' | ||
// `+await +1` -> `++1` | ||
|| ( | ||
node.parent.type === 'UnaryExpression' | ||
&& valueNode.type === 'UnaryExpression' | ||
&& node.parent.operator === valueNode.operator | ||
) | ||
) { | ||
return problem; | ||
} | ||
|
||
return Object.assign(problem, { | ||
/** @param {import('eslint').Rule.RuleFixer} fixer */ | ||
* fix(fixer) { | ||
if ( | ||
!isOnSameLine(awaitToken, valueNode) | ||
&& !isParenthesized(node, sourceCode) | ||
) { | ||
yield * addParenthesizesToReturnOrThrowExpression(fixer, node.parent, sourceCode); | ||
} | ||
|
||
yield fixer.remove(awaitToken); | ||
yield removeSpacesAfter(awaitToken, sourceCode, fixer); | ||
|
||
const nextToken = sourceCode.getTokenAfter(awaitToken); | ||
const tokenBefore = sourceCode.getTokenBefore(awaitToken); | ||
if (needsSemicolon(tokenBefore, sourceCode, nextToken.value)) { | ||
yield fixer.insertTextBefore(nextToken, ';'); | ||
} | ||
}, | ||
}); | ||
}, | ||
}); | ||
|
||
/** @type {import('eslint').Rule.RuleModule} */ | ||
module.exports = { | ||
create, | ||
meta: { | ||
type: 'suggestion', | ||
docs: { | ||
description: 'Disallow awaiting non-promise values.', | ||
}, | ||
fixable: 'code', | ||
|
||
messages, | ||
}, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
'use strict'; | ||
|
||
function isOnSameLine(nodeOrTokenA, nodeOrTokenB) { | ||
return nodeOrTokenA.loc.start.line === nodeOrTokenB.loc.start.line; | ||
} | ||
|
||
module.exports = isOnSameLine; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
import outdent from 'outdent'; | ||
import {getTester} from './utils/test.mjs'; | ||
|
||
const {test} = getTester(import.meta); | ||
|
||
test.snapshot({ | ||
testerOptions: { | ||
parserOptions: { | ||
ecmaFeatures: { | ||
jsx: true, | ||
}, | ||
}, | ||
}, | ||
valid: [ | ||
'await {then}', | ||
'await a ? b : c', | ||
'await a || b', | ||
'await a && b', | ||
'await a ?? b', | ||
'await new Foo()', | ||
'await tagged``', | ||
'class A { async foo() { await this }}', | ||
'async function * foo() {await (yield bar);}', | ||
'await (1, Promise.resolve())', | ||
], | ||
invalid: [ | ||
'await []', | ||
'await [Promise.resolve()]', | ||
'await (() => {})', | ||
'await (() => Promise.resolve())', | ||
'await (a === b)', | ||
'await (a instanceof Promise)', | ||
'await (a > b)', | ||
'await class {}', | ||
'await class extends Promise {}', | ||
'await function() {}', | ||
'await function name() {}', | ||
'await function() { return Promise.resolve() }', | ||
'await (<></>)', | ||
'await (<a></a>)', | ||
'await 0', | ||
'await 1', | ||
'await ""', | ||
'await "string"', | ||
'await true', | ||
'await false', | ||
'await null', | ||
'await 0n', | ||
'await 1n', | ||
// eslint-disable-next-line no-template-curly-in-string | ||
'await `${Promise.resolve()}`', | ||
'await !Promise.resolve()', | ||
'await void Promise.resolve()', | ||
'await +Promise.resolve()', | ||
'await ~1', | ||
'await ++foo', | ||
'await foo--', | ||
'await (Promise.resolve(), 1)', | ||
outdent` | ||
async function foo() { | ||
return await | ||
// comment | ||
1; | ||
} | ||
`, | ||
outdent` | ||
async function foo() { | ||
return await | ||
// comment | ||
1 | ||
} | ||
`, | ||
outdent` | ||
async function foo() { | ||
return( await | ||
// comment | ||
1); | ||
} | ||
`, | ||
outdent` | ||
foo() | ||
await [] | ||
`, | ||
outdent` | ||
foo() | ||
await +1 | ||
`, | ||
outdent` | ||
async function foo() { | ||
return await | ||
// comment | ||
[]; | ||
} | ||
`, | ||
outdent` | ||
async function foo() { | ||
throw await | ||
// comment | ||
1; | ||
} | ||
`, | ||
outdent` | ||
console.log( | ||
await | ||
// comment | ||
[] | ||
); | ||
`, | ||
'async function foo() {+await +1}', | ||
'async function foo() {-await-1}', | ||
'async function foo() {+await -1}', | ||
], | ||
}); |
Oops, something went wrong.