From ed252114efdb24cbea9c00fe65589f029bbc4793 Mon Sep 17 00:00:00 2001 From: neriyarden Date: Sat, 27 Jul 2024 18:09:06 +0300 Subject: [PATCH 1/4] feat: add autofix --- lib/rules/no-await-sync-queries.ts | 18 ++++++- tests/lib/rules/no-await-sync-queries.test.ts | 53 +++++++++++++++++++ 2 files changed, 69 insertions(+), 2 deletions(-) diff --git a/lib/rules/no-await-sync-queries.ts b/lib/rules/no-await-sync-queries.ts index c6f4a70c..6104b5a3 100644 --- a/lib/rules/no-await-sync-queries.ts +++ b/lib/rules/no-await-sync-queries.ts @@ -1,4 +1,4 @@ -import { TSESTree } from '@typescript-eslint/utils'; +import { ASTUtils, TSESTree } from '@typescript-eslint/utils'; import { createTestingLibraryRule } from '../create-testing-library-rule'; import { getDeepestIdentifierNode } from '../node-utils'; @@ -26,15 +26,20 @@ export default createTestingLibraryRule({ '`{{ name }}` query is sync so it does not need to be awaited', }, schema: [], + fixable: 'code', }, defaultOptions: [], create(context, _, helpers) { return { 'AwaitExpression > CallExpression'(node: TSESTree.CallExpression) { + const awaitExpression = node.parent; const deepestIdentifierNode = getDeepestIdentifierNode(node); - if (!deepestIdentifierNode) { + if ( + !ASTUtils.isAwaitExpression(awaitExpression) || + !deepestIdentifierNode + ) { return; } @@ -45,6 +50,15 @@ export default createTestingLibraryRule({ data: { name: deepestIdentifierNode.name, }, + fix: (fixer) => { + const awaitRangeStart = awaitExpression.range[0]; + const awaitRangeEnd = awaitExpression.range[0] + 'await '.length; + + return fixer.replaceTextRange( + [awaitRangeStart, awaitRangeEnd], + '' + ); + }, }); } }, diff --git a/tests/lib/rules/no-await-sync-queries.test.ts b/tests/lib/rules/no-await-sync-queries.test.ts index 0896f817..37d2de3f 100644 --- a/tests/lib/rules/no-await-sync-queries.test.ts +++ b/tests/lib/rules/no-await-sync-queries.test.ts @@ -142,6 +142,10 @@ ruleTester.run(RULE_NAME, rule, { column: 31, }, ], + output: `async () => { + const element = ${query}('foo') + } + `, }) as const ), // custom sync queries with await operator are not valid @@ -152,6 +156,11 @@ ruleTester.run(RULE_NAME, rule, { } `, errors: [{ messageId: 'noAwaitSyncQuery', line: 3, column: 31 }], + output: ` + async () => { + const element = getByIcon('search') + } + `, }, { code: ` @@ -160,6 +169,11 @@ ruleTester.run(RULE_NAME, rule, { } `, errors: [{ messageId: 'noAwaitSyncQuery', line: 3, column: 31 }], + output: ` + async () => { + const element = queryByIcon('search') + } + `, }, { code: ` @@ -168,6 +182,11 @@ ruleTester.run(RULE_NAME, rule, { } `, errors: [{ messageId: 'noAwaitSyncQuery', line: 3, column: 38 }], + output: ` + async () => { + const element = screen.getAllByIcon('search') + } + `, }, { code: ` @@ -176,6 +195,11 @@ ruleTester.run(RULE_NAME, rule, { } `, errors: [{ messageId: 'noAwaitSyncQuery', line: 3, column: 38 }], + output: ` + async () => { + const element = screen.queryAllByIcon('search') + } + `, }, // sync queries with await operator inside assert are not valid ...SYNC_QUERIES_COMBINATIONS.map( @@ -192,6 +216,10 @@ ruleTester.run(RULE_NAME, rule, { column: 22, }, ], + output: `async () => { + expect(${query}('foo')).toBeEnabled() + } + `, }) as const ), @@ -210,6 +238,10 @@ ruleTester.run(RULE_NAME, rule, { column: 38, }, ], + output: `async () => { + const element = screen.${query}('foo') + } + `, }) as const ), @@ -228,6 +260,10 @@ ruleTester.run(RULE_NAME, rule, { column: 29, }, ], + output: `async () => { + expect(screen.${query}('foo')).toBeEnabled() + } + `, }) as const ), @@ -244,6 +280,12 @@ ruleTester.run(RULE_NAME, rule, { } `, errors: [{ messageId: 'noAwaitSyncQuery', line: 4, column: 38 }], + output: ` + import { screen } from '${testingFramework}' + () => { + const element = screen.getByRole('button') + } + `, }) as const ), // sync query awaited and related to custom module is not valid @@ -256,6 +298,12 @@ ruleTester.run(RULE_NAME, rule, { } `, errors: [{ messageId: 'noAwaitSyncQuery', line: 4, column: 38 }], + output: ` + import { screen } from 'test-utils' + () => { + const element = screen.getByRole('button') + } + `, }, // awaited custom sync query matching custom-queries setting is invalid @@ -269,6 +317,11 @@ ruleTester.run(RULE_NAME, rule, { }) `, errors: [{ messageId: 'noAwaitSyncQuery', line: 3, column: 31 }], + output: ` + test('A valid example test', async () => { + const element = queryByIcon('search') + }) + `, }, ], }); From 768af6ab8c7d532a77399d8753151c2b4d62e16c Mon Sep 17 00:00:00 2001 From: neriyarden Date: Sat, 27 Jul 2024 18:09:53 +0300 Subject: [PATCH 2/4] docs: update docs --- README.md | 2 +- docs/rules/no-await-sync-queries.md | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 0fa46197..20b8322c 100644 --- a/README.md +++ b/README.md @@ -299,7 +299,7 @@ module.exports = [ | [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-vue][] | | | | [consistent-data-testid](docs/rules/consistent-data-testid.md) | Ensures consistent usage of `data-testid` | | | | | [no-await-sync-events](docs/rules/no-await-sync-events.md) | Disallow unnecessary `await` for sync events | ![badge-angular][] ![badge-dom][] ![badge-react][] | | | -| [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-vue][] | | | +| [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-vue][] | | 🔧 | | [no-container](docs/rules/no-container.md) | Disallow the use of `container` methods | ![badge-angular][] ![badge-marko][] ![badge-react][] ![badge-vue][] | | | | [no-debugging-utils](docs/rules/no-debugging-utils.md) | Disallow the use of debugging utilities like `debug` | | ![badge-angular][] ![badge-marko][] ![badge-react][] ![badge-vue][] | | | [no-dom-import](docs/rules/no-dom-import.md) | Disallow importing from DOM Testing Library | ![badge-angular][] ![badge-marko][] ![badge-react][] ![badge-vue][] | | 🔧 | diff --git a/docs/rules/no-await-sync-queries.md b/docs/rules/no-await-sync-queries.md index a4538d71..9724b712 100644 --- a/docs/rules/no-await-sync-queries.md +++ b/docs/rules/no-await-sync-queries.md @@ -2,6 +2,8 @@ 💼 This rule is enabled in the following configs: `angular`, `dom`, `marko`, `react`, `vue`. +🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). + Ensure that sync queries are not awaited unnecessarily. From 50742f6648d4d8b66aa0a3dfb7bf5ece1655b1c2 Mon Sep 17 00:00:00 2001 From: neriyarden Date: Sat, 12 Oct 2024 12:46:52 +0300 Subject: [PATCH 3/4] fix(fixer): remove space from replaced text in fixer --- lib/rules/no-await-sync-queries.ts | 2 +- tests/lib/rules/no-await-sync-queries.test.ts | 21 ++++++++++--------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/lib/rules/no-await-sync-queries.ts b/lib/rules/no-await-sync-queries.ts index 6104b5a3..a8c341c3 100644 --- a/lib/rules/no-await-sync-queries.ts +++ b/lib/rules/no-await-sync-queries.ts @@ -52,7 +52,7 @@ export default createTestingLibraryRule({ }, fix: (fixer) => { const awaitRangeStart = awaitExpression.range[0]; - const awaitRangeEnd = awaitExpression.range[0] + 'await '.length; + const awaitRangeEnd = awaitExpression.range[0] + 'await'.length; return fixer.replaceTextRange( [awaitRangeStart, awaitRangeEnd], diff --git a/tests/lib/rules/no-await-sync-queries.test.ts b/tests/lib/rules/no-await-sync-queries.test.ts index 37d2de3f..488e4732 100644 --- a/tests/lib/rules/no-await-sync-queries.test.ts +++ b/tests/lib/rules/no-await-sync-queries.test.ts @@ -143,7 +143,7 @@ ruleTester.run(RULE_NAME, rule, { }, ], output: `async () => { - const element = ${query}('foo') + const element = ${query}('foo') } `, }) as const @@ -158,6 +158,7 @@ ruleTester.run(RULE_NAME, rule, { errors: [{ messageId: 'noAwaitSyncQuery', line: 3, column: 31 }], output: ` async () => { + const element = getByIcon('search') const element = getByIcon('search') } `, @@ -171,7 +172,7 @@ ruleTester.run(RULE_NAME, rule, { errors: [{ messageId: 'noAwaitSyncQuery', line: 3, column: 31 }], output: ` async () => { - const element = queryByIcon('search') + const element = queryByIcon('search') } `, }, @@ -184,7 +185,7 @@ ruleTester.run(RULE_NAME, rule, { errors: [{ messageId: 'noAwaitSyncQuery', line: 3, column: 38 }], output: ` async () => { - const element = screen.getAllByIcon('search') + const element = screen.getAllByIcon('search') } `, }, @@ -197,7 +198,7 @@ ruleTester.run(RULE_NAME, rule, { errors: [{ messageId: 'noAwaitSyncQuery', line: 3, column: 38 }], output: ` async () => { - const element = screen.queryAllByIcon('search') + const element = screen.queryAllByIcon('search') } `, }, @@ -217,7 +218,7 @@ ruleTester.run(RULE_NAME, rule, { }, ], output: `async () => { - expect(${query}('foo')).toBeEnabled() + expect( ${query}('foo')).toBeEnabled() } `, }) as const @@ -239,7 +240,7 @@ ruleTester.run(RULE_NAME, rule, { }, ], output: `async () => { - const element = screen.${query}('foo') + const element = screen.${query}('foo') } `, }) as const @@ -261,7 +262,7 @@ ruleTester.run(RULE_NAME, rule, { }, ], output: `async () => { - expect(screen.${query}('foo')).toBeEnabled() + expect( screen.${query}('foo')).toBeEnabled() } `, }) as const @@ -283,7 +284,7 @@ ruleTester.run(RULE_NAME, rule, { output: ` import { screen } from '${testingFramework}' () => { - const element = screen.getByRole('button') + const element = screen.getByRole('button') } `, }) as const @@ -301,7 +302,7 @@ ruleTester.run(RULE_NAME, rule, { output: ` import { screen } from 'test-utils' () => { - const element = screen.getByRole('button') + const element = screen.getByRole('button') } `, }, @@ -319,7 +320,7 @@ ruleTester.run(RULE_NAME, rule, { errors: [{ messageId: 'noAwaitSyncQuery', line: 3, column: 31 }], output: ` test('A valid example test', async () => { - const element = queryByIcon('search') + const element = queryByIcon('search') }) `, }, From 6120e73e546214fdcffcafbe33660bfeb75d3740 Mon Sep 17 00:00:00 2001 From: neriyarden Date: Sat, 12 Oct 2024 12:48:25 +0300 Subject: [PATCH 4/4] test: add a test for the await(...) case --- tests/lib/rules/no-await-sync-queries.test.ts | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/tests/lib/rules/no-await-sync-queries.test.ts b/tests/lib/rules/no-await-sync-queries.test.ts index 488e4732..3d91625b 100644 --- a/tests/lib/rules/no-await-sync-queries.test.ts +++ b/tests/lib/rules/no-await-sync-queries.test.ts @@ -159,7 +159,19 @@ ruleTester.run(RULE_NAME, rule, { output: ` async () => { const element = getByIcon('search') - const element = getByIcon('search') + } + `, + }, + { + code: ` + async () => { + const element = await(getByIcon('search')) + } + `, + errors: [{ messageId: 'noAwaitSyncQuery', line: 3, column: 31 }], + output: ` + async () => { + const element = (getByIcon('search')) } `, },