Skip to content

Commit

Permalink
feat(eslint-plugin): [no-floating-promises] add suggestion fixer to a…
Browse files Browse the repository at this point in the history
…dd an 'await' (#5943)
  • Loading branch information
sena-anny committed Dec 16, 2022
1 parent c50b89e commit 9e35ef9
Show file tree
Hide file tree
Showing 2 changed files with 190 additions and 2 deletions.
51 changes: 49 additions & 2 deletions packages/eslint-plugin/src/rules/no-floating-promises.ts
@@ -1,7 +1,7 @@
import type { TSESLint, TSESTree } from '@typescript-eslint/utils';
import { AST_NODE_TYPES } from '@typescript-eslint/utils';
import * as tsutils from 'tsutils';
import type * as ts from 'typescript';
import * as ts from 'typescript';

import * as util from '../util';

Expand All @@ -12,7 +12,11 @@ type Options = [
},
];

type MessageId = 'floating' | 'floatingVoid' | 'floatingFixVoid';
type MessageId =
| 'floating'
| 'floatingVoid'
| 'floatingFixVoid'
| 'floatingFixAwait';

export default util.createRule<Options, MessageId>({
name: 'no-floating-promises',
Expand All @@ -27,6 +31,7 @@ export default util.createRule<Options, MessageId>({
messages: {
floating:
'Promises must be awaited, end with a call to .catch, or end with a call to .then with a rejection handler.',
floatingFixAwait: 'Add await operator.',
floatingVoid:
'Promises must be awaited, end with a call to .catch, end with a call to .then with a rejection handler' +
' or be explicitly marked as ignored with the `void` operator.',
Expand Down Expand Up @@ -95,12 +100,54 @@ export default util.createRule<Options, MessageId>({
context.report({
node,
messageId: 'floating',
suggest: [
{
messageId: 'floatingFixAwait',
fix(fixer): TSESLint.RuleFix | TSESLint.RuleFix[] {
if (
expression.type === AST_NODE_TYPES.UnaryExpression &&
expression.operator === 'void'
) {
return fixer.replaceTextRange(
[expression.range[0], expression.range[0] + 4],
'await',
);
}
const tsNode = parserServices.esTreeNodeToTSNodeMap.get(
node.expression,
);
if (isHigherPrecedenceThanAwait(tsNode)) {
return fixer.insertTextBefore(node, 'await ');
} else {
return [
fixer.insertTextBefore(node, 'await ('),
fixer.insertTextAfterRange(
[expression.range[1], expression.range[1]],
')',
),
];
}
},
},
],
});
}
}
},
};

function isHigherPrecedenceThanAwait(node: ts.Node): boolean {
const operator = tsutils.isBinaryExpression(node)
? node.operatorToken.kind
: ts.SyntaxKind.Unknown;
const nodePrecedence = util.getOperatorPrecedence(node.kind, operator);
const awaitPrecedence = util.getOperatorPrecedence(
ts.SyntaxKind.AwaitExpression,
ts.SyntaxKind.Unknown,
);
return nodePrecedence > awaitPrecedence;
}

function isAsyncIife(node: TSESTree.ExpressionStatement): boolean {
if (node.expression.type !== AST_NODE_TYPES.CallExpression) {
return false;
Expand Down
141 changes: 141 additions & 0 deletions packages/eslint-plugin/tests/rules/no-floating-promises.test.ts
Expand Up @@ -656,6 +656,147 @@ async function test() {
{
line: 3,
messageId: 'floating',
suggestions: [
{
messageId: 'floatingFixAwait',
output: `
async function test() {
await Promise.resolve();
}
`,
},
],
},
],
},
{
code: `
async function test() {
const promise = new Promise((resolve, reject) => resolve('value'));
promise;
}
`,
options: [{ ignoreVoid: false }],
errors: [
{
line: 4,
messageId: 'floating',
suggestions: [
{
messageId: 'floatingFixAwait',
output: `
async function test() {
const promise = new Promise((resolve, reject) => resolve('value'));
await promise;
}
`,
},
],
},
],
},
{
code: `
async function returnsPromise() {
return 'value';
}
void returnsPromise();
`,
options: [{ ignoreVoid: false }],
errors: [
{
line: 5,
messageId: 'floating',
suggestions: [
{
messageId: 'floatingFixAwait',
output: `
async function returnsPromise() {
return 'value';
}
await returnsPromise();
`,
},
],
},
],
},
{
// eslint-disable-next-line @typescript-eslint/internal/plugin-test-formatting
code: `
async function returnsPromise() {
return 'value';
}
void /* ... */ returnsPromise();
`,
options: [{ ignoreVoid: false }],
errors: [
{
line: 5,
messageId: 'floating',
suggestions: [
{
messageId: 'floatingFixAwait',
output: `
async function returnsPromise() {
return 'value';
}
await /* ... */ returnsPromise();
`,
},
],
},
],
},
{
code: `
async function returnsPromise() {
return 'value';
}
1, returnsPromise();
`,
options: [{ ignoreVoid: false }],
errors: [
{
line: 5,
messageId: 'floating',
suggestions: [
{
messageId: 'floatingFixAwait',
output: `
async function returnsPromise() {
return 'value';
}
await (1, returnsPromise());
`,
},
],
},
],
},
{
code: `
async function returnsPromise() {
return 'value';
}
bool ? returnsPromise() : null;
`,
options: [{ ignoreVoid: false }],
errors: [
{
line: 5,
messageId: 'floating',
suggestions: [
{
messageId: 'floatingFixAwait',
output: `
async function returnsPromise() {
return 'value';
}
await (bool ? returnsPromise() : null);
`,
},
],
},
],
},
Expand Down

0 comments on commit 9e35ef9

Please sign in to comment.