Skip to content

Commit 974e71d

Browse files
sena-annyOmri Luzon
authored and
Omri Luzon
committed
feat(eslint-plugin): [no-floating-promises] add suggestion fixer to add an 'await' (typescript-eslint#5943)
1 parent 5804a4f commit 974e71d

File tree

2 files changed

+190
-2
lines changed

2 files changed

+190
-2
lines changed

Diff for: packages/eslint-plugin/src/rules/no-floating-promises.ts

+49-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type { TSESLint, TSESTree } from '@typescript-eslint/utils';
22
import { AST_NODE_TYPES } from '@typescript-eslint/utils';
33
import * as tsutils from 'tsutils';
4-
import type * as ts from 'typescript';
4+
import * as ts from 'typescript';
55

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

@@ -12,7 +12,11 @@ type Options = [
1212
},
1313
];
1414

15-
type MessageId = 'floating' | 'floatingVoid' | 'floatingFixVoid';
15+
type MessageId =
16+
| 'floating'
17+
| 'floatingVoid'
18+
| 'floatingFixVoid'
19+
| 'floatingFixAwait';
1620

1721
export default util.createRule<Options, MessageId>({
1822
name: 'no-floating-promises',
@@ -27,6 +31,7 @@ export default util.createRule<Options, MessageId>({
2731
messages: {
2832
floating:
2933
'Promises must be awaited, end with a call to .catch, or end with a call to .then with a rejection handler.',
34+
floatingFixAwait: 'Add await operator.',
3035
floatingVoid:
3136
'Promises must be awaited, end with a call to .catch, end with a call to .then with a rejection handler' +
3237
' or be explicitly marked as ignored with the `void` operator.',
@@ -95,12 +100,54 @@ export default util.createRule<Options, MessageId>({
95100
context.report({
96101
node,
97102
messageId: 'floating',
103+
suggest: [
104+
{
105+
messageId: 'floatingFixAwait',
106+
fix(fixer): TSESLint.RuleFix | TSESLint.RuleFix[] {
107+
if (
108+
expression.type === AST_NODE_TYPES.UnaryExpression &&
109+
expression.operator === 'void'
110+
) {
111+
return fixer.replaceTextRange(
112+
[expression.range[0], expression.range[0] + 4],
113+
'await',
114+
);
115+
}
116+
const tsNode = parserServices.esTreeNodeToTSNodeMap.get(
117+
node.expression,
118+
);
119+
if (isHigherPrecedenceThanAwait(tsNode)) {
120+
return fixer.insertTextBefore(node, 'await ');
121+
} else {
122+
return [
123+
fixer.insertTextBefore(node, 'await ('),
124+
fixer.insertTextAfterRange(
125+
[expression.range[1], expression.range[1]],
126+
')',
127+
),
128+
];
129+
}
130+
},
131+
},
132+
],
98133
});
99134
}
100135
}
101136
},
102137
};
103138

139+
function isHigherPrecedenceThanAwait(node: ts.Node): boolean {
140+
const operator = tsutils.isBinaryExpression(node)
141+
? node.operatorToken.kind
142+
: ts.SyntaxKind.Unknown;
143+
const nodePrecedence = util.getOperatorPrecedence(node.kind, operator);
144+
const awaitPrecedence = util.getOperatorPrecedence(
145+
ts.SyntaxKind.AwaitExpression,
146+
ts.SyntaxKind.Unknown,
147+
);
148+
return nodePrecedence > awaitPrecedence;
149+
}
150+
104151
function isAsyncIife(node: TSESTree.ExpressionStatement): boolean {
105152
if (node.expression.type !== AST_NODE_TYPES.CallExpression) {
106153
return false;

Diff for: packages/eslint-plugin/tests/rules/no-floating-promises.test.ts

+141
Original file line numberDiff line numberDiff line change
@@ -656,6 +656,147 @@ async function test() {
656656
{
657657
line: 3,
658658
messageId: 'floating',
659+
suggestions: [
660+
{
661+
messageId: 'floatingFixAwait',
662+
output: `
663+
async function test() {
664+
await Promise.resolve();
665+
}
666+
`,
667+
},
668+
],
669+
},
670+
],
671+
},
672+
{
673+
code: `
674+
async function test() {
675+
const promise = new Promise((resolve, reject) => resolve('value'));
676+
promise;
677+
}
678+
`,
679+
options: [{ ignoreVoid: false }],
680+
errors: [
681+
{
682+
line: 4,
683+
messageId: 'floating',
684+
suggestions: [
685+
{
686+
messageId: 'floatingFixAwait',
687+
output: `
688+
async function test() {
689+
const promise = new Promise((resolve, reject) => resolve('value'));
690+
await promise;
691+
}
692+
`,
693+
},
694+
],
695+
},
696+
],
697+
},
698+
{
699+
code: `
700+
async function returnsPromise() {
701+
return 'value';
702+
}
703+
void returnsPromise();
704+
`,
705+
options: [{ ignoreVoid: false }],
706+
errors: [
707+
{
708+
line: 5,
709+
messageId: 'floating',
710+
suggestions: [
711+
{
712+
messageId: 'floatingFixAwait',
713+
output: `
714+
async function returnsPromise() {
715+
return 'value';
716+
}
717+
await returnsPromise();
718+
`,
719+
},
720+
],
721+
},
722+
],
723+
},
724+
{
725+
// eslint-disable-next-line @typescript-eslint/internal/plugin-test-formatting
726+
code: `
727+
async function returnsPromise() {
728+
return 'value';
729+
}
730+
void /* ... */ returnsPromise();
731+
`,
732+
options: [{ ignoreVoid: false }],
733+
errors: [
734+
{
735+
line: 5,
736+
messageId: 'floating',
737+
suggestions: [
738+
{
739+
messageId: 'floatingFixAwait',
740+
output: `
741+
async function returnsPromise() {
742+
return 'value';
743+
}
744+
await /* ... */ returnsPromise();
745+
`,
746+
},
747+
],
748+
},
749+
],
750+
},
751+
{
752+
code: `
753+
async function returnsPromise() {
754+
return 'value';
755+
}
756+
1, returnsPromise();
757+
`,
758+
options: [{ ignoreVoid: false }],
759+
errors: [
760+
{
761+
line: 5,
762+
messageId: 'floating',
763+
suggestions: [
764+
{
765+
messageId: 'floatingFixAwait',
766+
output: `
767+
async function returnsPromise() {
768+
return 'value';
769+
}
770+
await (1, returnsPromise());
771+
`,
772+
},
773+
],
774+
},
775+
],
776+
},
777+
{
778+
code: `
779+
async function returnsPromise() {
780+
return 'value';
781+
}
782+
bool ? returnsPromise() : null;
783+
`,
784+
options: [{ ignoreVoid: false }],
785+
errors: [
786+
{
787+
line: 5,
788+
messageId: 'floating',
789+
suggestions: [
790+
{
791+
messageId: 'floatingFixAwait',
792+
output: `
793+
async function returnsPromise() {
794+
return 'value';
795+
}
796+
await (bool ? returnsPromise() : null);
797+
`,
798+
},
799+
],
659800
},
660801
],
661802
},

0 commit comments

Comments
 (0)