From 9d3c77114104e379ec447179ab1b7d534713a924 Mon Sep 17 00:00:00 2001 From: Hasegawa-Yukihiro Date: Sat, 13 Sep 2025 11:54:02 +0900 Subject: [PATCH 1/4] refactor: enable code completion --- tests/lib/rules/no-await-sync-queries.test.ts | 122 +++++++++--------- 1 file changed, 59 insertions(+), 63 deletions(-) diff --git a/tests/lib/rules/no-await-sync-queries.test.ts b/tests/lib/rules/no-await-sync-queries.test.ts index 0896f817..85b4df12 100644 --- a/tests/lib/rules/no-await-sync-queries.test.ts +++ b/tests/lib/rules/no-await-sync-queries.test.ts @@ -5,8 +5,17 @@ import { } from '../../../lib/utils'; import { createRuleTester } from '../test-utils'; +import type { MessageIds } from '../../../lib/rules/no-await-sync-queries'; +import type { + InvalidTestCase, + ValidTestCase, +} from '@typescript-eslint/rule-tester'; + const ruleTester = createRuleTester(); +type RuleValidTestCase = ValidTestCase<[]>; +type RuleInvalidTestCase = InvalidTestCase; + const SUPPORTED_TESTING_FRAMEWORKS = [ '@testing-library/dom', '@testing-library/angular', @@ -18,7 +27,7 @@ const SUPPORTED_TESTING_FRAMEWORKS = [ ruleTester.run(RULE_NAME, rule, { valid: [ // sync queries without await are valid - ...SYNC_QUERIES_COMBINATIONS.map((query) => ({ + ...SYNC_QUERIES_COMBINATIONS.map((query) => ({ code: `() => { const element = ${query}('foo') } @@ -61,7 +70,7 @@ ruleTester.run(RULE_NAME, rule, { }, // sync queries without await inside assert are valid - ...SYNC_QUERIES_COMBINATIONS.map((query) => ({ + ...SYNC_QUERIES_COMBINATIONS.map((query) => ({ code: `() => { expect(${query}('foo')).toBeEnabled() } @@ -69,7 +78,7 @@ ruleTester.run(RULE_NAME, rule, { })), // async queries with await operator are valid - ...ASYNC_QUERIES_COMBINATIONS.map((query) => ({ + ...ASYNC_QUERIES_COMBINATIONS.map((query) => ({ code: `async () => { const element = await ${query}('foo') } @@ -77,7 +86,7 @@ ruleTester.run(RULE_NAME, rule, { })), // async queries with then method are valid - ...ASYNC_QUERIES_COMBINATIONS.map((query) => ({ + ...ASYNC_QUERIES_COMBINATIONS.map((query) => ({ code: `() => { ${query}('foo').then(() => {}); } @@ -128,22 +137,19 @@ ruleTester.run(RULE_NAME, rule, { invalid: [ // sync queries with await operator are not valid - ...SYNC_QUERIES_COMBINATIONS.map( - (query) => - ({ - code: `async () => { + ...SYNC_QUERIES_COMBINATIONS.map((query) => ({ + code: `async () => { const element = await ${query}('foo') } `, - errors: [ - { - messageId: 'noAwaitSyncQuery', - line: 2, - column: 31, - }, - ], - }) as const - ), + errors: [ + { + messageId: 'noAwaitSyncQuery', + line: 2, + column: 31, + }, + ], + })), // custom sync queries with await operator are not valid { code: ` @@ -178,73 +184,63 @@ ruleTester.run(RULE_NAME, rule, { errors: [{ messageId: 'noAwaitSyncQuery', line: 3, column: 38 }], }, // sync queries with await operator inside assert are not valid - ...SYNC_QUERIES_COMBINATIONS.map( - (query) => - ({ - code: `async () => { + ...SYNC_QUERIES_COMBINATIONS.map((query) => ({ + code: `async () => { expect(await ${query}('foo')).toBeEnabled() } `, - errors: [ - { - messageId: 'noAwaitSyncQuery', - line: 2, - column: 22, - }, - ], - }) as const - ), + errors: [ + { + messageId: 'noAwaitSyncQuery', + line: 2, + column: 22, + }, + ], + })), // sync queries in screen with await operator are not valid - ...SYNC_QUERIES_COMBINATIONS.map( - (query) => - ({ - code: `async () => { + ...SYNC_QUERIES_COMBINATIONS.map((query) => ({ + code: `async () => { const element = await screen.${query}('foo') } `, - errors: [ - { - messageId: 'noAwaitSyncQuery', - line: 2, - column: 38, - }, - ], - }) as const - ), + errors: [ + { + messageId: 'noAwaitSyncQuery', + line: 2, + column: 38, + }, + ], + })), // sync queries in screen with await operator inside assert are not valid - ...SYNC_QUERIES_COMBINATIONS.map( - (query) => - ({ - code: `async () => { + ...SYNC_QUERIES_COMBINATIONS.map((query) => ({ + code: `async () => { expect(await screen.${query}('foo')).toBeEnabled() } `, - errors: [ - { - messageId: 'noAwaitSyncQuery', - line: 2, - column: 29, - }, - ], - }) as const - ), + errors: [ + { + messageId: 'noAwaitSyncQuery', + line: 2, + column: 29, + }, + ], + })), // sync query awaited and related to testing library module // with custom module setting is not valid - ...SUPPORTED_TESTING_FRAMEWORKS.map( - (testingFramework) => - ({ - settings: { 'testing-library/utils-module': 'test-utils' }, - code: ` + ...SUPPORTED_TESTING_FRAMEWORKS.map( + (testingFramework) => ({ + settings: { 'testing-library/utils-module': 'test-utils' }, + code: ` import { screen } from '${testingFramework}' () => { const element = await screen.getByRole('button') } `, - errors: [{ messageId: 'noAwaitSyncQuery', line: 4, column: 38 }], - }) as const + errors: [{ messageId: 'noAwaitSyncQuery', line: 4, column: 38 }], + }) ), // sync query awaited and related to custom module is not valid { From 06443b8b257897fb6c65b2c0d700e4bd47bef9f6 Mon Sep 17 00:00:00 2001 From: Hasegawa-Yukihiro Date: Sat, 13 Sep 2025 12:28:09 +0900 Subject: [PATCH 2/4] feat: add auto-fix --- lib/rules/no-await-sync-queries.ts | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/lib/rules/no-await-sync-queries.ts b/lib/rules/no-await-sync-queries.ts index c9e6bf49..81b16dba 100644 --- a/lib/rules/no-await-sync-queries.ts +++ b/lib/rules/no-await-sync-queries.ts @@ -1,5 +1,6 @@ import { createTestingLibraryRule } from '../create-testing-library-rule'; import { getDeepestIdentifierNode } from '../node-utils'; +import { getSourceCode } from '../utils'; import type { TSESTree } from '@typescript-eslint/utils'; @@ -27,12 +28,16 @@ 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) { + 'AwaitExpression > CallExpression'( + node: TSESTree.CallExpression & { parent: TSESTree.AwaitExpression } + ) { + const awaitExpression = node.parent; const deepestIdentifierNode = getDeepestIdentifierNode(node); if (!deepestIdentifierNode) { @@ -46,6 +51,12 @@ export default createTestingLibraryRule({ data: { name: deepestIdentifierNode.name, }, + fix: (fixer) => { + const awaitToken = + getSourceCode(context).getFirstToken(awaitExpression); + + return awaitToken ? fixer.remove(awaitToken) : null; + }, }); } }, From 2930e92746530297e4a8b4b65db0ad695f2b8274 Mon Sep 17 00:00:00 2001 From: Hasegawa-Yukihiro Date: Sat, 13 Sep 2025 12:28:19 +0900 Subject: [PATCH 3/4] test: add tests --- tests/lib/rules/no-await-sync-queries.test.ts | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/tests/lib/rules/no-await-sync-queries.test.ts b/tests/lib/rules/no-await-sync-queries.test.ts index 85b4df12..999f3262 100644 --- a/tests/lib/rules/no-await-sync-queries.test.ts +++ b/tests/lib/rules/no-await-sync-queries.test.ts @@ -149,6 +149,10 @@ ruleTester.run(RULE_NAME, rule, { column: 31, }, ], + output: `async () => { + const element = ${query}('foo') + } + `, })), // custom sync queries with await operator are not valid { @@ -158,6 +162,11 @@ ruleTester.run(RULE_NAME, rule, { } `, errors: [{ messageId: 'noAwaitSyncQuery', line: 3, column: 31 }], + output: ` + async () => { + const element = getByIcon('search') + } + `, }, { code: ` @@ -166,6 +175,11 @@ ruleTester.run(RULE_NAME, rule, { } `, errors: [{ messageId: 'noAwaitSyncQuery', line: 3, column: 31 }], + output: ` + async () => { + const element = queryByIcon('search') + } + `, }, { code: ` @@ -174,6 +188,11 @@ ruleTester.run(RULE_NAME, rule, { } `, errors: [{ messageId: 'noAwaitSyncQuery', line: 3, column: 38 }], + output: ` + async () => { + const element = screen.getAllByIcon('search') + } + `, }, { code: ` @@ -182,6 +201,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((query) => ({ @@ -196,6 +220,10 @@ ruleTester.run(RULE_NAME, rule, { column: 22, }, ], + output: `async () => { + expect( ${query}('foo')).toBeEnabled() + } + `, })), // sync queries in screen with await operator are not valid @@ -211,6 +239,10 @@ ruleTester.run(RULE_NAME, rule, { column: 38, }, ], + output: `async () => { + const element = screen.${query}('foo') + } + `, })), // sync queries in screen with await operator inside assert are not valid @@ -226,6 +258,10 @@ ruleTester.run(RULE_NAME, rule, { column: 29, }, ], + output: `async () => { + expect( screen.${query}('foo')).toBeEnabled() + } + `, })), // sync query awaited and related to testing library module @@ -240,6 +276,12 @@ ruleTester.run(RULE_NAME, rule, { } `, errors: [{ messageId: 'noAwaitSyncQuery', line: 4, column: 38 }], + output: ` + import { screen } from '${testingFramework}' + () => { + const element = screen.getByRole('button') + } + `, }) ), // sync query awaited and related to custom module is not valid @@ -252,6 +294,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 @@ -265,6 +313,24 @@ ruleTester.run(RULE_NAME, rule, { }) `, errors: [{ messageId: 'noAwaitSyncQuery', line: 3, column: 31 }], + output: ` + test('A valid example test', async () => { + const element = queryByIcon('search') + }) + `, + }, + { + code: ` + test('A valid example test', async () => { + const element = await(screen.getByRole('button')) + }) + `, + errors: [{ messageId: 'noAwaitSyncQuery', line: 3, column: 38 }], + output: ` + test('A valid example test', async () => { + const element = (screen.getByRole('button')) + }) + `, }, ], }); From 9491e6fd0a41f208bf1e27c66d774ff31f141123 Mon Sep 17 00:00:00 2001 From: Hasegawa-Yukihiro Date: Sat, 13 Sep 2025 12:29:04 +0900 Subject: [PATCH 4/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 b74697af..3f1cf488 100644 --- a/README.md +++ b/README.md @@ -329,7 +329,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-svelte][] ![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-svelte][] ![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-svelte][] ![badge-vue][] | | 🔧 | | [no-container](docs/rules/no-container.md) | Disallow the use of `container` methods | ![badge-angular][] ![badge-marko][] ![badge-react][] ![badge-svelte][] ![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-svelte][] ![badge-vue][] | | | [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][] | | 🔧 | diff --git a/docs/rules/no-await-sync-queries.md b/docs/rules/no-await-sync-queries.md index 50f639b5..1c66e228 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`, `svelte`, `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.