Skip to content

Commit 55575c9

Browse files
fix(await-async-utils): false positives (#137)
Closes #118
1 parent fef6445 commit 55575c9

File tree

4 files changed

+100
-25
lines changed

4 files changed

+100
-25
lines changed

lib/rules/await-async-utils.ts

+40-8
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { ESLintUtils, TSESTree } from '@typescript-eslint/experimental-utils';
22

3-
import { getDocsUrl, ASYNC_UTILS } from '../utils';
3+
import { getDocsUrl, ASYNC_UTILS, LIBRARY_MODULES } from '../utils';
44
import { isCallExpression, hasThenProperty } from '../node-utils';
55

66
export const RULE_NAME = 'await-async-utils';
@@ -49,17 +49,49 @@ export default ESLintUtils.RuleCreator(getDocsUrl)<Options, MessageIds>({
4949
defaultOptions: [],
5050

5151
create(context) {
52-
const testingLibraryUtilUsage: TSESTree.Identifier[] = [];
52+
const asyncUtilsUsage: Array<{ node: TSESTree.Identifier | TSESTree.MemberExpression, name: string }> = [];
53+
const importedAsyncUtils: string[] = [];
54+
5355
return {
56+
'ImportDeclaration > ImportSpecifier,ImportNamespaceSpecifier'(node: TSESTree.Node) {
57+
const parent = (node.parent as TSESTree.ImportDeclaration);
58+
59+
if (!LIBRARY_MODULES.includes(parent.source.value.toString())) return;
60+
61+
if (node.type === 'ImportSpecifier') {
62+
importedAsyncUtils.push(node.imported.name);
63+
}
64+
65+
if (node.type === 'ImportNamespaceSpecifier') {
66+
importedAsyncUtils.push(node.local.name);
67+
}
68+
},
5469
[`CallExpression > Identifier[name=${ASYNC_UTILS_REGEXP}]`](
5570
node: TSESTree.Identifier
5671
) {
57-
if (!isAwaited(node.parent.parent) && !isPromiseResolved(node)) {
58-
testingLibraryUtilUsage.push(node);
59-
}
72+
asyncUtilsUsage.push({ node, name: node.name });
73+
},
74+
[`CallExpression > MemberExpression > Identifier[name=${ASYNC_UTILS_REGEXP}]`](
75+
node: TSESTree.Identifier
76+
) {
77+
const memberExpression = node.parent as TSESTree.MemberExpression;
78+
const identifier = memberExpression.object as TSESTree.Identifier;
79+
const memberExpressionName = identifier.name;
80+
81+
asyncUtilsUsage.push({ node: memberExpression, name: memberExpressionName });
6082
},
6183
'Program:exit'() {
62-
testingLibraryUtilUsage.forEach(node => {
84+
const testingLibraryUtilUsage = asyncUtilsUsage.filter(usage => {
85+
if (usage.node.type === 'MemberExpression') {
86+
const object = usage.node.object as TSESTree.Identifier;
87+
88+
return importedAsyncUtils.includes(object.name)
89+
}
90+
91+
return importedAsyncUtils.includes(usage.name)
92+
});
93+
94+
testingLibraryUtilUsage.forEach(({ node, name }) => {
6395
const variableDeclaratorParent = node.parent.parent;
6496

6597
const references =
@@ -79,7 +111,7 @@ export default ESLintUtils.RuleCreator(getDocsUrl)<Options, MessageIds>({
79111
node,
80112
messageId: 'awaitAsyncUtil',
81113
data: {
82-
name: node.name,
114+
name,
83115
},
84116
});
85117
} else {
@@ -93,7 +125,7 @@ export default ESLintUtils.RuleCreator(getDocsUrl)<Options, MessageIds>({
93125
node,
94126
messageId: 'awaitAsyncUtil',
95127
data: {
96-
name: node.name,
128+
name,
97129
},
98130
});
99131

lib/rules/no-debug.ts

+3-12
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { ESLintUtils, TSESTree } from '@typescript-eslint/experimental-utils';
2-
import { getDocsUrl } from '../utils';
2+
import { getDocsUrl, LIBRARY_MODULES } from '../utils';
33
import {
44
isObjectPattern,
55
isProperty,
@@ -12,15 +12,6 @@ import {
1212

1313
export const RULE_NAME = 'no-debug';
1414

15-
const LIBRARY_MODULES_WITH_SCREEN = [
16-
'@testing-library/dom',
17-
'@testing-library/angular',
18-
'@testing-library/react',
19-
'@testing-library/preact',
20-
'@testing-library/vue',
21-
'@testing-library/svelte',
22-
];
23-
2415
function isRenderFunction(
2516
callNode: TSESTree.CallExpression,
2617
renderFunctions: string[]
@@ -58,7 +49,7 @@ function hasTestingLibraryImportModule(
5849
importDeclarationNode: TSESTree.ImportDeclaration
5950
) {
6051
const literal = importDeclarationNode.source;
61-
return LIBRARY_MODULES_WITH_SCREEN.some(module => module === literal.value);
52+
return LIBRARY_MODULES.some(module => module === literal.value);
6253
}
6354

6455
export default ESLintUtils.RuleCreator(getDocsUrl)({
@@ -129,7 +120,7 @@ export default ESLintUtils.RuleCreator(getDocsUrl)({
129120
args =>
130121
isLiteral(args) &&
131122
typeof args.value === 'string' &&
132-
LIBRARY_MODULES_WITH_SCREEN.includes(args.value)
123+
LIBRARY_MODULES.includes(args.value)
133124
);
134125

135126
if (!literalNodeScreenModuleName) {

lib/utils.ts

+10
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,15 @@ const combineQueries = (variants: string[], methods: string[]) => {
1313
const getDocsUrl = (ruleName: string) =>
1414
`https://github.com/testing-library/eslint-plugin-testing-library/tree/master/docs/rules/${ruleName}.md`;
1515

16+
const LIBRARY_MODULES = [
17+
'@testing-library/dom',
18+
'@testing-library/angular',
19+
'@testing-library/react',
20+
'@testing-library/preact',
21+
'@testing-library/vue',
22+
'@testing-library/svelte',
23+
];
24+
1625
const SYNC_QUERIES_VARIANTS = ['getBy', 'getAllBy', 'queryBy', 'queryAllBy'];
1726
const ASYNC_QUERIES_VARIANTS = ['findBy', 'findAllBy'];
1827
const ALL_QUERIES_VARIANTS = [
@@ -64,4 +73,5 @@ export {
6473
ASYNC_QUERIES_COMBINATIONS,
6574
ALL_QUERIES_COMBINATIONS,
6675
ASYNC_UTILS,
76+
LIBRARY_MODULES,
6777
};

tests/lib/rules/await-async-utils.test.ts

+47-5
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ ruleTester.run(RULE_NAME, rule, {
88
valid: [
99
...ASYNC_UTILS.map(asyncUtil => ({
1010
code: `
11+
import { ${asyncUtil} } from '@testing-library/dom';
1112
test('${asyncUtil} util directly waited with await operator is valid', async () => {
1213
doSomethingElse();
1314
await ${asyncUtil}(() => getByLabelText('email'));
@@ -17,6 +18,7 @@ ruleTester.run(RULE_NAME, rule, {
1718

1819
...ASYNC_UTILS.map(asyncUtil => ({
1920
code: `
21+
import { ${asyncUtil} } from '@testing-library/dom';
2022
test('${asyncUtil} util promise saved in var and waited with await operator is valid', async () => {
2123
doSomethingElse();
2224
const aPromise = ${asyncUtil}(() => getByLabelText('email'));
@@ -27,6 +29,7 @@ ruleTester.run(RULE_NAME, rule, {
2729

2830
...ASYNC_UTILS.map(asyncUtil => ({
2931
code: `
32+
import { ${asyncUtil} } from '@testing-library/dom';
3033
test('${asyncUtil} util directly chained with then is valid', () => {
3134
doSomethingElse();
3235
${asyncUtil}(() => getByLabelText('email')).then(() => { console.log('done') });
@@ -36,6 +39,7 @@ ruleTester.run(RULE_NAME, rule, {
3639

3740
...ASYNC_UTILS.map(asyncUtil => ({
3841
code: `
42+
import { ${asyncUtil} } from '@testing-library/dom';
3943
test('${asyncUtil} util promise saved in var and chained with then is valid', () => {
4044
doSomethingElse();
4145
const aPromise = ${asyncUtil}(() => getByLabelText('email'));
@@ -46,6 +50,7 @@ ruleTester.run(RULE_NAME, rule, {
4650

4751
...ASYNC_UTILS.map(asyncUtil => ({
4852
code: `
53+
import { ${asyncUtil} } from '@testing-library/dom';
4954
test('${asyncUtil} util directly returned in arrow function is valid', async () => {
5055
const makeCustomWait = () =>
5156
${asyncUtil}(() =>
@@ -57,6 +62,7 @@ ruleTester.run(RULE_NAME, rule, {
5762

5863
...ASYNC_UTILS.map(asyncUtil => ({
5964
code: `
65+
import { ${asyncUtil} } from '@testing-library/dom';
6066
test('${asyncUtil} util explicitly returned in arrow function is valid', async () => {
6167
const makeCustomWait = () => {
6268
return ${asyncUtil}(() =>
@@ -69,6 +75,7 @@ ruleTester.run(RULE_NAME, rule, {
6975

7076
...ASYNC_UTILS.map(asyncUtil => ({
7177
code: `
78+
import { ${asyncUtil} } from '@testing-library/dom';
7279
test('${asyncUtil} util returned in regular function is valid', async () => {
7380
function makeCustomWait() {
7481
return ${asyncUtil}(() =>
@@ -81,6 +88,7 @@ ruleTester.run(RULE_NAME, rule, {
8188

8289
...ASYNC_UTILS.map(asyncUtil => ({
8390
code: `
91+
import { ${asyncUtil} } from '@testing-library/dom';
8492
test('${asyncUtil} util promise saved in var and returned in function is valid', async () => {
8593
const makeCustomWait = () => {
8694
const aPromise = ${asyncUtil}(() =>
@@ -94,8 +102,29 @@ ruleTester.run(RULE_NAME, rule, {
94102
});
95103
`,
96104
})),
105+
...ASYNC_UTILS.map(asyncUtil => ({
106+
code: `
107+
import { ${asyncUtil} } from 'some-other-library';
108+
test('util "${asyncUtil}" which is not related to testing library is valid', async () => {
109+
doSomethingElse();
110+
${asyncUtil}();
111+
});
112+
`,
113+
})),
114+
...ASYNC_UTILS.map(asyncUtil => ({
115+
code: `
116+
import * as asyncUtils from 'some-other-library';
117+
test('util "asyncUtils.${asyncUtil}" which is not related to testing library is valid', async () => {
118+
doSomethingElse();
119+
asyncUtils.${asyncUtil}();
120+
});
121+
`,
122+
})),
123+
97124
{
98-
code: `test('waitForElementToBeRemoved receiving element rather than callback is valid', async () => {
125+
code: `
126+
import { waitForElementToBeRemoved } from '@testing-library/dom';
127+
test('waitForElementToBeRemoved receiving element rather than callback is valid', async () => {
99128
doSomethingElse();
100129
const emailInput = getByLabelText('email');
101130
await waitForElementToBeRemoved(emailInput);
@@ -114,33 +143,46 @@ ruleTester.run(RULE_NAME, rule, {
114143
invalid: [
115144
...ASYNC_UTILS.map(asyncUtil => ({
116145
code: `
146+
import { ${asyncUtil} } from '@testing-library/dom';
117147
test('${asyncUtil} util not waited', () => {
118148
doSomethingElse();
119149
${asyncUtil}(() => getByLabelText('email'));
120150
});
121151
`,
122-
errors: [{ line: 4, messageId: 'awaitAsyncUtil' }],
152+
errors: [{ line: 5, messageId: 'awaitAsyncUtil' }],
153+
})),
154+
...ASYNC_UTILS.map(asyncUtil => ({
155+
code: `
156+
import * as asyncUtil from '@testing-library/dom';
157+
test('asyncUtil.${asyncUtil} util not waited', () => {
158+
doSomethingElse();
159+
asyncUtil.${asyncUtil}(() => getByLabelText('email'));
160+
});
161+
`,
162+
errors: [{ line: 5, messageId: 'awaitAsyncUtil' }],
123163
})),
124164
...ASYNC_UTILS.map(asyncUtil => ({
125165
code: `
166+
import { ${asyncUtil} } from '@testing-library/dom';
126167
test('${asyncUtil} util promise saved not waited', () => {
127168
doSomethingElse();
128169
const aPromise = ${asyncUtil}(() => getByLabelText('email'));
129170
});
130171
`,
131-
errors: [{ line: 4, column: 28, messageId: 'awaitAsyncUtil' }],
172+
errors: [{ line: 5, column: 28, messageId: 'awaitAsyncUtil' }],
132173
})),
133174
...ASYNC_UTILS.map(asyncUtil => ({
134175
code: `
176+
import { ${asyncUtil} } from '@testing-library/dom';
135177
test('several ${asyncUtil} utils not waited', () => {
136178
const aPromise = ${asyncUtil}(() => getByLabelText('username'));
137179
doSomethingElse(aPromise);
138180
${asyncUtil}(() => getByLabelText('email'));
139181
});
140182
`,
141183
errors: [
142-
{ line: 3, column: 28, messageId: 'awaitAsyncUtil' },
143-
{ line: 5, column: 11, messageId: 'awaitAsyncUtil' },
184+
{ line: 4, column: 28, messageId: 'awaitAsyncUtil' },
185+
{ line: 6, column: 11, messageId: 'awaitAsyncUtil' },
144186
],
145187
})),
146188
],

0 commit comments

Comments
 (0)