Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 9edd863

Browse files
authored
feat(eslint-plugin): [require-await] add --fix support (typescript-eslint#1561)
1 parent 1aa7135 commit 9edd863

File tree

4 files changed

+190
-2
lines changed

4 files changed

+190
-2
lines changed

packages/eslint-plugin/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,7 @@ In these cases, we create what we call an extension rule; a rule within our plug
185185
| [`@typescript-eslint/no-useless-constructor`](./docs/rules/no-useless-constructor.md) | Disallow unnecessary constructors | | | |
186186
| [`@typescript-eslint/quotes`](./docs/rules/quotes.md) | Enforce the consistent use of either backticks, double, or single quotes | | :wrench: | |
187187
| [`@typescript-eslint/require-await`](./docs/rules/require-await.md) | Disallow async functions which have no `await` expression | :heavy_check_mark: | | :thought_balloon: |
188-
| [`@typescript-eslint/return-await`](./docs/rules/return-await.md) | Enforces consistent returning of awaited values | | | :thought_balloon: |
188+
| [`@typescript-eslint/return-await`](./docs/rules/return-await.md) | Enforces consistent returning of awaited values | | :wrench: | :thought_balloon: |
189189
| [`@typescript-eslint/semi`](./docs/rules/semi.md) | Require or disallow semicolons instead of ASI | | :wrench: | |
190190
| [`@typescript-eslint/space-before-function-paren`](./docs/rules/space-before-function-paren.md) | Enforces consistent spacing before function parenthesis | | :wrench: | |
191191

packages/eslint-plugin/src/rules/return-await.ts

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {
22
AST_NODE_TYPES,
33
TSESTree,
4+
TSESLint,
45
} from '@typescript-eslint/experimental-utils';
56
import * as tsutils from 'tsutils';
67
import * as ts from 'typescript';
@@ -16,6 +17,7 @@ export default util.createRule({
1617
requiresTypeChecking: true,
1718
extendsBaseRule: 'no-return-await',
1819
},
20+
fixable: 'code',
1921
type: 'problem',
2022
messages: {
2123
nonPromiseAwait:
@@ -36,6 +38,7 @@ export default util.createRule({
3638
create(context, [option]) {
3739
const parserServices = util.getParserServices(context);
3840
const checker = parserServices.program.getTypeChecker();
41+
const sourceCode = context.getSourceCode();
3942

4043
function inTryCatch(node: ts.Node): boolean {
4144
let ancestor = node.parent;
@@ -54,13 +57,66 @@ export default util.createRule({
5457
return false;
5558
}
5659

60+
// function findTokensToRemove()
61+
62+
function removeAwait(
63+
fixer: TSESLint.RuleFixer,
64+
node: TSESTree.ReturnStatement | TSESTree.ArrowFunctionExpression,
65+
): TSESLint.RuleFix | null {
66+
const awaitNode =
67+
node.type === AST_NODE_TYPES.ReturnStatement
68+
? node.argument
69+
: node.body;
70+
// Should always be an await node; but let's be safe.
71+
/* istanbul ignore if */ if (!util.isAwaitExpression(awaitNode)) {
72+
return null;
73+
}
74+
75+
const awaitToken = sourceCode.getFirstToken(
76+
awaitNode,
77+
util.isAwaitKeyword,
78+
);
79+
// Should always be the case; but let's be safe.
80+
/* istanbul ignore if */ if (!awaitToken) {
81+
return null;
82+
}
83+
84+
const startAt = awaitToken.range[0];
85+
let endAt = awaitToken.range[1];
86+
// Also remove any extraneous whitespace after `await`, if there is any.
87+
const nextToken = sourceCode.getTokenAfter(awaitToken, {
88+
includeComments: true,
89+
});
90+
if (nextToken) {
91+
endAt = nextToken.range[0];
92+
}
93+
94+
return fixer.removeRange([startAt, endAt]);
95+
}
96+
97+
function insertAwait(
98+
fixer: TSESLint.RuleFixer,
99+
node: TSESTree.ReturnStatement | TSESTree.ArrowFunctionExpression,
100+
): TSESLint.RuleFix | null {
101+
const targetNode =
102+
node.type === AST_NODE_TYPES.ReturnStatement
103+
? node.argument
104+
: node.body;
105+
// There should always be a target node; but let's be safe.
106+
/* istanbul ignore if */ if (!targetNode) {
107+
return null;
108+
}
109+
110+
return fixer.insertTextBefore(targetNode, 'await ');
111+
}
112+
57113
function test(
58114
node: TSESTree.ReturnStatement | TSESTree.ArrowFunctionExpression,
59115
expression: ts.Node,
60116
): void {
61117
let child: ts.Node;
62118

63-
const isAwait = expression.kind === ts.SyntaxKind.AwaitExpression;
119+
const isAwait = tsutils.isAwaitExpression(expression);
64120

65121
if (isAwait) {
66122
child = expression.getChildAt(1);
@@ -79,6 +135,7 @@ export default util.createRule({
79135
context.report({
80136
messageId: 'nonPromiseAwait',
81137
node,
138+
fix: fixer => removeAwait(fixer, node),
82139
});
83140
return;
84141
}
@@ -88,6 +145,7 @@ export default util.createRule({
88145
context.report({
89146
messageId: 'requiredPromiseAwait',
90147
node,
148+
fix: fixer => insertAwait(fixer, node),
91149
});
92150
}
93151

@@ -99,6 +157,7 @@ export default util.createRule({
99157
context.report({
100158
messageId: 'disallowedPromiseAwait',
101159
node,
160+
fix: fixer => removeAwait(fixer, node),
102161
});
103162
}
104163

@@ -111,11 +170,13 @@ export default util.createRule({
111170
context.report({
112171
messageId: 'disallowedPromiseAwait',
113172
node,
173+
fix: fixer => removeAwait(fixer, node),
114174
});
115175
} else if (!isAwait && isInTryCatch) {
116176
context.report({
117177
messageId: 'requiredPromiseAwait',
118178
node,
179+
fix: fixer => insertAwait(fixer, node),
119180
});
120181
}
121182

packages/eslint-plugin/src/util/astUtils.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,27 @@ function isIdentifier(
114114
return node?.type === AST_NODE_TYPES.Identifier;
115115
}
116116

117+
/**
118+
* Checks if a node represents an `await …` expression.
119+
*/
120+
function isAwaitExpression(
121+
node: TSESTree.Node | undefined | null,
122+
): node is TSESTree.AwaitExpression {
123+
return node?.type === AST_NODE_TYPES.AwaitExpression;
124+
}
125+
126+
/**
127+
* Checks if a possible token is the `await` keyword.
128+
*/
129+
function isAwaitKeyword(
130+
node: TSESTree.Token | TSESTree.Comment | undefined | null,
131+
): node is TSESTree.KeywordToken & { value: 'await' } {
132+
return node?.type === AST_TOKEN_TYPES.Identifier && node.value === 'await';
133+
}
134+
117135
export {
136+
isAwaitExpression,
137+
isAwaitKeyword,
118138
isConstructor,
119139
isIdentifier,
120140
isLogicalOrOperator,

packages/eslint-plugin/tests/rules/return-await.test.ts

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,15 +164,62 @@ ruleTester.run('return-await', rule, {
164164
code: `async function test() {
165165
return await 1;
166166
}`,
167+
output: `async function test() {
168+
return 1;
169+
}`,
167170
errors: [
168171
{
169172
line: 2,
170173
messageId: 'nonPromiseAwait',
171174
},
172175
],
173176
},
177+
{
178+
code: `async function test() {
179+
const foo = 1;
180+
return await{foo};
181+
}`,
182+
output: `async function test() {
183+
const foo = 1;
184+
return {foo};
185+
}`,
186+
errors: [
187+
{
188+
line: 3,
189+
messageId: 'nonPromiseAwait',
190+
},
191+
],
192+
},
193+
{
194+
code: `async function test() {
195+
const foo = 1;
196+
return await
197+
foo;
198+
}`,
199+
output: `async function test() {
200+
const foo = 1;
201+
return foo;
202+
}`,
203+
errors: [
204+
{
205+
line: 3,
206+
messageId: 'nonPromiseAwait',
207+
},
208+
],
209+
},
174210
{
175211
code: `const test = async () => await 1;`,
212+
output: `const test = async () => 1;`,
213+
errors: [
214+
{
215+
line: 1,
216+
messageId: 'nonPromiseAwait',
217+
},
218+
],
219+
},
220+
{
221+
code: `const test = async () => await/* comment */1;`,
222+
output: `const test = async () => /* comment */1;`,
176223
errors: [
177224
{
178225
line: 1,
@@ -182,6 +229,7 @@ ruleTester.run('return-await', rule, {
182229
},
183230
{
184231
code: `const test = async () => await Promise.resolve(1);`,
232+
output: `const test = async () => Promise.resolve(1);`,
185233
errors: [
186234
{
187235
line: 1,
@@ -199,6 +247,15 @@ ruleTester.run('return-await', rule, {
199247
console.log('cleanup');
200248
}
201249
}`,
250+
output: `async function test() {
251+
try {
252+
return await Promise.resolve(1);
253+
} catch (e) {
254+
return await Promise.resolve(2);
255+
} finally {
256+
console.log('cleanup');
257+
}
258+
}`,
202259
errors: [
203260
{
204261
line: 3,
@@ -214,6 +271,9 @@ ruleTester.run('return-await', rule, {
214271
code: `async function test() {
215272
return await Promise.resolve(1);
216273
}`,
274+
output: `async function test() {
275+
return Promise.resolve(1);
276+
}`,
217277
errors: [
218278
{
219279
line: 2,
@@ -226,6 +286,9 @@ ruleTester.run('return-await', rule, {
226286
code: `async function test() {
227287
return await 1;
228288
}`,
289+
output: `async function test() {
290+
return 1;
291+
}`,
229292
errors: [
230293
{
231294
line: 2,
@@ -236,6 +299,7 @@ ruleTester.run('return-await', rule, {
236299
{
237300
options: ['in-try-catch'],
238301
code: `const test = async () => await 1;`,
302+
output: `const test = async () => 1;`,
239303
errors: [
240304
{
241305
line: 1,
@@ -246,6 +310,7 @@ ruleTester.run('return-await', rule, {
246310
{
247311
options: ['in-try-catch'],
248312
code: `const test = async () => await Promise.resolve(1);`,
313+
output: `const test = async () => Promise.resolve(1);`,
249314
errors: [
250315
{
251316
line: 1,
@@ -264,6 +329,15 @@ ruleTester.run('return-await', rule, {
264329
console.log('cleanup');
265330
}
266331
}`,
332+
output: `async function test() {
333+
try {
334+
return await Promise.resolve(1);
335+
} catch (e) {
336+
return await Promise.resolve(2);
337+
} finally {
338+
console.log('cleanup');
339+
}
340+
}`,
267341
errors: [
268342
{
269343
line: 3,
@@ -280,6 +354,9 @@ ruleTester.run('return-await', rule, {
280354
code: `async function test() {
281355
return await Promise.resolve(1);
282356
}`,
357+
output: `async function test() {
358+
return Promise.resolve(1);
359+
}`,
283360
errors: [
284361
{
285362
line: 2,
@@ -292,6 +369,9 @@ ruleTester.run('return-await', rule, {
292369
code: `async function test() {
293370
return await 1;
294371
}`,
372+
output: `async function test() {
373+
return 1;
374+
}`,
295375
errors: [
296376
{
297377
line: 2,
@@ -310,6 +390,15 @@ ruleTester.run('return-await', rule, {
310390
console.log('cleanup');
311391
}
312392
}`,
393+
output: `async function test() {
394+
try {
395+
return Promise.resolve(1);
396+
} catch (e) {
397+
return Promise.resolve(2);
398+
} finally {
399+
console.log('cleanup');
400+
}
401+
}`,
313402
errors: [
314403
{
315404
line: 3,
@@ -326,6 +415,9 @@ ruleTester.run('return-await', rule, {
326415
code: `async function test() {
327416
return await Promise.resolve(1);
328417
}`,
418+
output: `async function test() {
419+
return Promise.resolve(1);
420+
}`,
329421
errors: [
330422
{
331423
line: 2,
@@ -338,6 +430,9 @@ ruleTester.run('return-await', rule, {
338430
code: `async function test() {
339431
return await 1;
340432
}`,
433+
output: `async function test() {
434+
return 1;
435+
}`,
341436
errors: [
342437
{
343438
line: 2,
@@ -356,6 +451,15 @@ ruleTester.run('return-await', rule, {
356451
console.log('cleanup');
357452
}
358453
}`,
454+
output: `async function test() {
455+
try {
456+
return await Promise.resolve(1);
457+
} catch (e) {
458+
return await Promise.resolve(2);
459+
} finally {
460+
console.log('cleanup');
461+
}
462+
}`,
359463
errors: [
360464
{
361465
line: 3,
@@ -372,6 +476,9 @@ ruleTester.run('return-await', rule, {
372476
code: `async function test() {
373477
return Promise.resolve(1);
374478
}`,
479+
output: `async function test() {
480+
return await Promise.resolve(1);
481+
}`,
375482
errors: [
376483
{
377484
line: 2,

0 commit comments

Comments
 (0)