Skip to content

Commit 9ba0041

Browse files
authored
feat(await-async-utils): add auto-fix (#1086)
Closes #1083
1 parent 7676f18 commit 9ba0041

File tree

7 files changed

+383
-16
lines changed

7 files changed

+383
-16
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -326,7 +326,7 @@ module.exports = [
326326
| :------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------ | :-- |
327327
| [await-async-events](docs/rules/await-async-events.md) | Enforce promises from async event methods are handled | ![badge-angular][] ![badge-dom][] ![badge-marko][] ![badge-react][] ![badge-svelte][] ![badge-vue][] | | 🔧 |
328328
| [await-async-queries](docs/rules/await-async-queries.md) | Enforce promises from async queries to be handled | ![badge-angular][] ![badge-dom][] ![badge-marko][] ![badge-react][] ![badge-svelte][] ![badge-vue][] | | 🔧 |
329-
| [await-async-utils](docs/rules/await-async-utils.md) | Enforce promises from async utils to be awaited properly | ![badge-angular][] ![badge-dom][] ![badge-marko][] ![badge-react][] ![badge-svelte][] ![badge-vue][] | | |
329+
| [await-async-utils](docs/rules/await-async-utils.md) | Enforce promises from async utils to be awaited properly | ![badge-angular][] ![badge-dom][] ![badge-marko][] ![badge-react][] ![badge-svelte][] ![badge-vue][] | | 🔧 |
330330
| [consistent-data-testid](docs/rules/consistent-data-testid.md) | Ensures consistent usage of `data-testid` | | | |
331331
| [no-await-sync-events](docs/rules/no-await-sync-events.md) | Disallow unnecessary `await` for sync events | ![badge-angular][] ![badge-dom][] ![badge-react][] | | |
332332
| [no-await-sync-queries](docs/rules/no-await-sync-queries.md) | Disallow unnecessary `await` for sync queries | ![badge-angular][] ![badge-dom][] ![badge-marko][] ![badge-react][] ![badge-svelte][] ![badge-vue][] | | 🔧 |

docs/rules/await-async-utils.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
💼 This rule is enabled in the following configs: `angular`, `dom`, `marko`, `react`, `svelte`, `vue`.
44

5+
🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).
6+
57
<!-- end auto-generated rule header -->
68

79
Ensure that promises returned by async utils are handled properly.

lib/rules/await-async-queries.ts

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
isMemberExpression,
1212
isPromiseHandled,
1313
} from '../node-utils';
14+
import { addAsyncToFunctionFix } from '../utils/add-async-to-function-fix';
1415

1516
import type { TSESTree } from '@typescript-eslint/utils';
1617

@@ -164,20 +165,11 @@ export default createTestingLibraryRule<Options, MessageIds>({
164165
);
165166
}
166167

167-
const ruleFixes = [IdentifierNodeFixer];
168-
if (!functionExpression.async) {
169-
/**
170-
* Mutate the actual node so if other nodes exist in this
171-
* function expression body they don't also try to fix it.
172-
*/
173-
functionExpression.async = true;
174-
175-
ruleFixes.push(
176-
fixer.insertTextBefore(functionExpression, 'async ')
177-
);
178-
}
179-
180-
return ruleFixes;
168+
return addAsyncToFunctionFix(
169+
fixer,
170+
IdentifierNodeFixer,
171+
functionExpression
172+
);
181173
},
182174
});
183175
}

lib/rules/await-async-utils.ts

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,20 @@ import { ASTUtils } from '@typescript-eslint/utils';
33
import { createTestingLibraryRule } from '../create-testing-library-rule';
44
import {
55
findClosestCallExpressionNode,
6+
findClosestFunctionExpressionNode,
67
getDeepestIdentifierNode,
78
getFunctionName,
89
getInnermostReturningFunction,
10+
getReferenceNode,
911
getVariableReferences,
1012
isCallExpression,
1113
isObjectPattern,
1214
isPromiseHandled,
1315
isProperty,
1416
} from '../node-utils';
17+
import { addAsyncToFunctionFix } from '../utils/add-async-to-function-fix';
1518

16-
import type { TSESTree } from '@typescript-eslint/utils';
19+
import type { TSESLint, TSESTree } from '@typescript-eslint/utils';
1720

1821
export const RULE_NAME = 'await-async-utils';
1922
export type MessageIds = 'asyncUtilWrapper' | 'awaitAsyncUtil';
@@ -40,6 +43,7 @@ export default createTestingLibraryRule<Options, MessageIds>({
4043
'Promise returned from {{ name }} wrapper over async util must be handled',
4144
},
4245
schema: [],
46+
fixable: 'code',
4347
},
4448
defaultOptions: [],
4549

@@ -91,6 +95,13 @@ export default createTestingLibraryRule<Options, MessageIds>({
9195
}
9296
}
9397

98+
function insertAwaitBeforeNode(
99+
fixer: TSESLint.RuleFixer,
100+
node: TSESTree.Node
101+
) {
102+
return fixer.insertTextBefore(node, 'await ');
103+
}
104+
94105
/*
95106
Either we report a direct usage of an async util or a usage of a wrapper
96107
around an async util
@@ -155,6 +166,7 @@ export default createTestingLibraryRule<Options, MessageIds>({
155166
context,
156167
closestCallExpression.parent
157168
);
169+
const functionExpression = findClosestFunctionExpressionNode(node);
158170

159171
if (references.length === 0) {
160172
if (!isPromiseHandled(callExpressionIdentifier)) {
@@ -164,6 +176,17 @@ export default createTestingLibraryRule<Options, MessageIds>({
164176
data: {
165177
name: callExpressionIdentifier.name,
166178
},
179+
fix: (fixer) => {
180+
const referenceNode = getReferenceNode(
181+
callExpressionIdentifier
182+
);
183+
const awaitFix = insertAwaitBeforeNode(fixer, referenceNode);
184+
return addAsyncToFunctionFix(
185+
fixer,
186+
awaitFix,
187+
functionExpression
188+
);
189+
},
167190
});
168191
}
169192
} else {
@@ -176,6 +199,14 @@ export default createTestingLibraryRule<Options, MessageIds>({
176199
data: {
177200
name: callExpressionIdentifier.name,
178201
},
202+
fix: (fixer) => {
203+
const awaitFix = insertAwaitBeforeNode(fixer, referenceNode);
204+
return addAsyncToFunctionFix(
205+
fixer,
206+
awaitFix,
207+
functionExpression
208+
);
209+
},
179210
});
180211
return;
181212
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import type { TSESLint, TSESTree } from '@typescript-eslint/utils';
2+
3+
export const addAsyncToFunctionFix = (
4+
fixer: TSESLint.RuleFixer,
5+
ruleFix: TSESLint.RuleFix,
6+
functionExpression:
7+
| TSESTree.ArrowFunctionExpression
8+
| TSESTree.FunctionDeclaration
9+
| TSESTree.FunctionExpression
10+
| null
11+
) => {
12+
if (functionExpression && !functionExpression.async) {
13+
/**
14+
* Mutate the actual node so if other nodes exist in this
15+
* function expression body they don't also try to fix it.
16+
*/
17+
functionExpression.async = true;
18+
19+
return [ruleFix, fixer.insertTextBefore(functionExpression, 'async ')];
20+
}
21+
return ruleFix;
22+
};

0 commit comments

Comments
 (0)