Skip to content

Commit 3e90351

Browse files
authored
feat(no-await-sync-queries): add auto-fix (#1079)
Reference #202
1 parent 01165d9 commit 3e90351

File tree

4 files changed

+140
-65
lines changed

4 files changed

+140
-65
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -329,7 +329,7 @@ module.exports = [
329329
| [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][] | | |
332-
| [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][] | | |
332+
| [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][] | | 🔧 |
333333
| [no-container](docs/rules/no-container.md) | Disallow the use of `container` methods | ![badge-angular][] ![badge-marko][] ![badge-react][] ![badge-svelte][] ![badge-vue][] | | |
334334
| [no-debugging-utils](docs/rules/no-debugging-utils.md) | Disallow the use of debugging utilities like `debug` | | ![badge-angular][] ![badge-marko][] ![badge-react][] ![badge-svelte][] ![badge-vue][] | |
335335
| [no-dom-import](docs/rules/no-dom-import.md) | Disallow importing from DOM Testing Library | ![badge-angular][] ![badge-marko][] ![badge-react][] ![badge-svelte][] ![badge-vue][] | | 🔧 |

docs/rules/no-await-sync-queries.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 sync queries are not awaited unnecessarily.

lib/rules/no-await-sync-queries.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { createTestingLibraryRule } from '../create-testing-library-rule';
22
import { getDeepestIdentifierNode } from '../node-utils';
3+
import { getSourceCode } from '../utils';
34

45
import type { TSESTree } from '@typescript-eslint/utils';
56

@@ -27,12 +28,16 @@ export default createTestingLibraryRule<Options, MessageIds>({
2728
'`{{ name }}` query is sync so it does not need to be awaited',
2829
},
2930
schema: [],
31+
fixable: 'code',
3032
},
3133
defaultOptions: [],
3234

3335
create(context, _, helpers) {
3436
return {
35-
'AwaitExpression > CallExpression'(node: TSESTree.CallExpression) {
37+
'AwaitExpression > CallExpression'(
38+
node: TSESTree.CallExpression & { parent: TSESTree.AwaitExpression }
39+
) {
40+
const awaitExpression = node.parent;
3641
const deepestIdentifierNode = getDeepestIdentifierNode(node);
3742

3843
if (!deepestIdentifierNode) {
@@ -46,6 +51,12 @@ export default createTestingLibraryRule<Options, MessageIds>({
4651
data: {
4752
name: deepestIdentifierNode.name,
4853
},
54+
fix: (fixer) => {
55+
const awaitToken =
56+
getSourceCode(context).getFirstToken(awaitExpression);
57+
58+
return awaitToken ? fixer.remove(awaitToken) : null;
59+
},
4960
});
5061
}
5162
},

tests/lib/rules/no-await-sync-queries.test.ts

Lines changed: 125 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,17 @@ import {
55
} from '../../../lib/utils';
66
import { createRuleTester } from '../test-utils';
77

8+
import type { MessageIds } from '../../../lib/rules/no-await-sync-queries';
9+
import type {
10+
InvalidTestCase,
11+
ValidTestCase,
12+
} from '@typescript-eslint/rule-tester';
13+
814
const ruleTester = createRuleTester();
915

16+
type RuleValidTestCase = ValidTestCase<[]>;
17+
type RuleInvalidTestCase = InvalidTestCase<MessageIds, []>;
18+
1019
const SUPPORTED_TESTING_FRAMEWORKS = [
1120
'@testing-library/dom',
1221
'@testing-library/angular',
@@ -18,7 +27,7 @@ const SUPPORTED_TESTING_FRAMEWORKS = [
1827
ruleTester.run(RULE_NAME, rule, {
1928
valid: [
2029
// sync queries without await are valid
21-
...SYNC_QUERIES_COMBINATIONS.map((query) => ({
30+
...SYNC_QUERIES_COMBINATIONS.map<RuleValidTestCase>((query) => ({
2231
code: `() => {
2332
const element = ${query}('foo')
2433
}
@@ -61,23 +70,23 @@ ruleTester.run(RULE_NAME, rule, {
6170
},
6271

6372
// sync queries without await inside assert are valid
64-
...SYNC_QUERIES_COMBINATIONS.map((query) => ({
73+
...SYNC_QUERIES_COMBINATIONS.map<RuleValidTestCase>((query) => ({
6574
code: `() => {
6675
expect(${query}('foo')).toBeEnabled()
6776
}
6877
`,
6978
})),
7079

7180
// async queries with await operator are valid
72-
...ASYNC_QUERIES_COMBINATIONS.map((query) => ({
81+
...ASYNC_QUERIES_COMBINATIONS.map<RuleValidTestCase>((query) => ({
7382
code: `async () => {
7483
const element = await ${query}('foo')
7584
}
7685
`,
7786
})),
7887

7988
// async queries with then method are valid
80-
...ASYNC_QUERIES_COMBINATIONS.map((query) => ({
89+
...ASYNC_QUERIES_COMBINATIONS.map<RuleValidTestCase>((query) => ({
8190
code: `() => {
8291
${query}('foo').then(() => {});
8392
}
@@ -128,22 +137,23 @@ ruleTester.run(RULE_NAME, rule, {
128137

129138
invalid: [
130139
// sync queries with await operator are not valid
131-
...SYNC_QUERIES_COMBINATIONS.map(
132-
(query) =>
133-
({
134-
code: `async () => {
140+
...SYNC_QUERIES_COMBINATIONS.map<RuleInvalidTestCase>((query) => ({
141+
code: `async () => {
135142
const element = await ${query}('foo')
136143
}
137144
`,
138-
errors: [
139-
{
140-
messageId: 'noAwaitSyncQuery',
141-
line: 2,
142-
column: 31,
143-
},
144-
],
145-
}) as const
146-
),
145+
errors: [
146+
{
147+
messageId: 'noAwaitSyncQuery',
148+
line: 2,
149+
column: 31,
150+
},
151+
],
152+
output: `async () => {
153+
const element = ${query}('foo')
154+
}
155+
`,
156+
})),
147157
// custom sync queries with await operator are not valid
148158
{
149159
code: `
@@ -152,6 +162,11 @@ ruleTester.run(RULE_NAME, rule, {
152162
}
153163
`,
154164
errors: [{ messageId: 'noAwaitSyncQuery', line: 3, column: 31 }],
165+
output: `
166+
async () => {
167+
const element = getByIcon('search')
168+
}
169+
`,
155170
},
156171
{
157172
code: `
@@ -160,6 +175,11 @@ ruleTester.run(RULE_NAME, rule, {
160175
}
161176
`,
162177
errors: [{ messageId: 'noAwaitSyncQuery', line: 3, column: 31 }],
178+
output: `
179+
async () => {
180+
const element = queryByIcon('search')
181+
}
182+
`,
163183
},
164184
{
165185
code: `
@@ -168,6 +188,11 @@ ruleTester.run(RULE_NAME, rule, {
168188
}
169189
`,
170190
errors: [{ messageId: 'noAwaitSyncQuery', line: 3, column: 38 }],
191+
output: `
192+
async () => {
193+
const element = screen.getAllByIcon('search')
194+
}
195+
`,
171196
},
172197
{
173198
code: `
@@ -176,75 +201,88 @@ ruleTester.run(RULE_NAME, rule, {
176201
}
177202
`,
178203
errors: [{ messageId: 'noAwaitSyncQuery', line: 3, column: 38 }],
204+
output: `
205+
async () => {
206+
const element = screen.queryAllByIcon('search')
207+
}
208+
`,
179209
},
180210
// sync queries with await operator inside assert are not valid
181-
...SYNC_QUERIES_COMBINATIONS.map(
182-
(query) =>
183-
({
184-
code: `async () => {
211+
...SYNC_QUERIES_COMBINATIONS.map<RuleInvalidTestCase>((query) => ({
212+
code: `async () => {
185213
expect(await ${query}('foo')).toBeEnabled()
186214
}
187215
`,
188-
errors: [
189-
{
190-
messageId: 'noAwaitSyncQuery',
191-
line: 2,
192-
column: 22,
193-
},
194-
],
195-
}) as const
196-
),
216+
errors: [
217+
{
218+
messageId: 'noAwaitSyncQuery',
219+
line: 2,
220+
column: 22,
221+
},
222+
],
223+
output: `async () => {
224+
expect( ${query}('foo')).toBeEnabled()
225+
}
226+
`,
227+
})),
197228

198229
// sync queries in screen with await operator are not valid
199-
...SYNC_QUERIES_COMBINATIONS.map(
200-
(query) =>
201-
({
202-
code: `async () => {
230+
...SYNC_QUERIES_COMBINATIONS.map<RuleInvalidTestCase>((query) => ({
231+
code: `async () => {
203232
const element = await screen.${query}('foo')
204233
}
205234
`,
206-
errors: [
207-
{
208-
messageId: 'noAwaitSyncQuery',
209-
line: 2,
210-
column: 38,
211-
},
212-
],
213-
}) as const
214-
),
235+
errors: [
236+
{
237+
messageId: 'noAwaitSyncQuery',
238+
line: 2,
239+
column: 38,
240+
},
241+
],
242+
output: `async () => {
243+
const element = screen.${query}('foo')
244+
}
245+
`,
246+
})),
215247

216248
// sync queries in screen with await operator inside assert are not valid
217-
...SYNC_QUERIES_COMBINATIONS.map(
218-
(query) =>
219-
({
220-
code: `async () => {
249+
...SYNC_QUERIES_COMBINATIONS.map<RuleInvalidTestCase>((query) => ({
250+
code: `async () => {
221251
expect(await screen.${query}('foo')).toBeEnabled()
222252
}
223253
`,
224-
errors: [
225-
{
226-
messageId: 'noAwaitSyncQuery',
227-
line: 2,
228-
column: 29,
229-
},
230-
],
231-
}) as const
232-
),
254+
errors: [
255+
{
256+
messageId: 'noAwaitSyncQuery',
257+
line: 2,
258+
column: 29,
259+
},
260+
],
261+
output: `async () => {
262+
expect( screen.${query}('foo')).toBeEnabled()
263+
}
264+
`,
265+
})),
233266

234267
// sync query awaited and related to testing library module
235268
// with custom module setting is not valid
236-
...SUPPORTED_TESTING_FRAMEWORKS.map(
237-
(testingFramework) =>
238-
({
239-
settings: { 'testing-library/utils-module': 'test-utils' },
240-
code: `
269+
...SUPPORTED_TESTING_FRAMEWORKS.map<RuleInvalidTestCase>(
270+
(testingFramework) => ({
271+
settings: { 'testing-library/utils-module': 'test-utils' },
272+
code: `
241273
import { screen } from '${testingFramework}'
242274
() => {
243275
const element = await screen.getByRole('button')
244276
}
245277
`,
246-
errors: [{ messageId: 'noAwaitSyncQuery', line: 4, column: 38 }],
247-
}) as const
278+
errors: [{ messageId: 'noAwaitSyncQuery', line: 4, column: 38 }],
279+
output: `
280+
import { screen } from '${testingFramework}'
281+
() => {
282+
const element = screen.getByRole('button')
283+
}
284+
`,
285+
})
248286
),
249287
// sync query awaited and related to custom module is not valid
250288
{
@@ -256,6 +294,12 @@ ruleTester.run(RULE_NAME, rule, {
256294
}
257295
`,
258296
errors: [{ messageId: 'noAwaitSyncQuery', line: 4, column: 38 }],
297+
output: `
298+
import { screen } from 'test-utils'
299+
() => {
300+
const element = screen.getByRole('button')
301+
}
302+
`,
259303
},
260304

261305
// awaited custom sync query matching custom-queries setting is invalid
@@ -269,6 +313,24 @@ ruleTester.run(RULE_NAME, rule, {
269313
})
270314
`,
271315
errors: [{ messageId: 'noAwaitSyncQuery', line: 3, column: 31 }],
316+
output: `
317+
test('A valid example test', async () => {
318+
const element = queryByIcon('search')
319+
})
320+
`,
321+
},
322+
{
323+
code: `
324+
test('A valid example test', async () => {
325+
const element = await(screen.getByRole('button'))
326+
})
327+
`,
328+
errors: [{ messageId: 'noAwaitSyncQuery', line: 3, column: 38 }],
329+
output: `
330+
test('A valid example test', async () => {
331+
const element = (screen.getByRole('button'))
332+
})
333+
`,
272334
},
273335
],
274336
});

0 commit comments

Comments
 (0)