Skip to content

Commit cbdfd5f

Browse files
fix(await-async-query): false positives (#208)
* fix: false positive for await-async-query * refactor: async utils used in different async rules * test: fix coverage by including rejects
1 parent 6005415 commit cbdfd5f

File tree

5 files changed

+224
-246
lines changed

5 files changed

+224
-246
lines changed

lib/node-utils.ts

+33-8
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,12 @@
11
import { AST_NODE_TYPES, TSESTree } from '@typescript-eslint/experimental-utils';
2+
import { RuleContext } from '@typescript-eslint/experimental-utils/dist/ts-eslint';
23

34
export function isCallExpression(
45
node: TSESTree.Node
56
): node is TSESTree.CallExpression {
67
return node && node.type === AST_NODE_TYPES.CallExpression;
78
}
89

9-
export function isAwaitExpression(
10-
node: TSESTree.Node
11-
): node is TSESTree.AwaitExpression {
12-
return node && node.type === AST_NODE_TYPES.AwaitExpression;
13-
}
14-
1510
export function isIdentifier(node: TSESTree.Node): node is TSESTree.Identifier {
1611
return node && node.type === AST_NODE_TYPES.Identifier;
1712
}
@@ -95,6 +90,10 @@ export function findClosestCallNode(
9590
}
9691
}
9792

93+
export function isObjectExpression(node: TSESTree.Expression): node is TSESTree.ObjectExpression {
94+
return node?.type === AST_NODE_TYPES.ObjectExpression
95+
}
96+
9897
export function hasThenProperty(node: TSESTree.Node) {
9998
return (
10099
isMemberExpression(node) &&
@@ -103,10 +102,36 @@ export function hasThenProperty(node: TSESTree.Node) {
103102
);
104103
}
105104

105+
export function isAwaitExpression(
106+
node: TSESTree.Node
107+
): node is TSESTree.AwaitExpression {
108+
return node && node.type === AST_NODE_TYPES.AwaitExpression;
109+
}
110+
106111
export function isArrowFunctionExpression(node: TSESTree.Node): node is TSESTree.ArrowFunctionExpression {
107112
return node && node.type === AST_NODE_TYPES.ArrowFunctionExpression
108113
}
109114

110-
export function isObjectExpression(node: TSESTree.Expression): node is TSESTree.ObjectExpression {
111-
return node?.type === AST_NODE_TYPES.ObjectExpression
115+
export function isReturnStatement(node: TSESTree.Node): node is TSESTree.ReturnStatement {
116+
return node && node.type === AST_NODE_TYPES.ReturnStatement
117+
}
118+
119+
export function isAwaited(node: TSESTree.Node) {
120+
return isAwaitExpression(node) || isArrowFunctionExpression(node) || isReturnStatement(node)
121+
}
122+
123+
export function isPromiseResolved(node: TSESTree.Node) {
124+
const parent = node.parent;
125+
126+
// wait(...).then(...)
127+
if (isCallExpression(parent)) {
128+
return hasThenProperty(parent.parent);
129+
}
130+
131+
// promise.then(...)
132+
return hasThenProperty(parent);
133+
}
134+
135+
export function getVariableReferences(context: RuleContext<string, []>, node: TSESTree.Node) {
136+
return (isVariableDeclarator(node) && context.getDeclaredVariables(node)[0].references.slice(1)) || [];
112137
}

lib/rules/await-async-query.ts

+27-35
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,21 @@
11
import { ESLintUtils, TSESTree } from '@typescript-eslint/experimental-utils';
2-
import { getDocsUrl } from '../utils';
2+
import { getDocsUrl, LIBRARY_MODULES } from '../utils';
33
import {
4-
isVariableDeclarator,
5-
hasThenProperty,
64
isCallExpression,
75
isIdentifier,
86
isMemberExpression,
7+
isAwaited,
8+
isPromiseResolved,
9+
getVariableReferences,
910
} from '../node-utils';
11+
import { ReportDescriptor } from '@typescript-eslint/experimental-utils/dist/ts-eslint';
1012

1113
export const RULE_NAME = 'await-async-query';
1214
export type MessageIds = 'awaitAsyncQuery';
1315
type Options = [];
1416

15-
const VALID_PARENTS = [
16-
'AwaitExpression',
17-
'ArrowFunctionExpression',
18-
'ReturnStatement',
19-
];
20-
2117
const ASYNC_QUERIES_REGEXP = /^find(All)?By(LabelText|PlaceholderText|Text|AltText|Title|DisplayValue|Role|TestId)$/;
2218

23-
function isAwaited(node: TSESTree.Node) {
24-
return VALID_PARENTS.includes(node.type);
25-
}
26-
27-
function isPromiseResolved(node: TSESTree.Node) {
28-
const parent = node.parent;
29-
30-
// findByText("foo").then(...)
31-
if (isCallExpression(parent)) {
32-
return hasThenProperty(parent.parent);
33-
}
34-
35-
// promise.then(...)
36-
return hasThenProperty(parent);
37-
}
38-
3919
function hasClosestExpectResolvesRejects(node: TSESTree.Node): boolean {
4020
if (!node.parent) {
4121
return false;
@@ -79,14 +59,33 @@ export default ESLintUtils.RuleCreator(getDocsUrl)<Options, MessageIds>({
7959
node: TSESTree.Identifier | TSESTree.MemberExpression;
8060
queryName: string;
8161
}[] = [];
62+
8263
const isQueryUsage = (
8364
node: TSESTree.Identifier | TSESTree.MemberExpression
8465
) =>
8566
!isAwaited(node.parent.parent) &&
8667
!isPromiseResolved(node) &&
8768
!hasClosestExpectResolvesRejects(node);
8869

70+
let hasImportedFromTestingLibraryModule = false;
71+
72+
function report(params: ReportDescriptor<'awaitAsyncQuery'>) {
73+
if (hasImportedFromTestingLibraryModule) {
74+
context.report(params);
75+
}
76+
}
77+
8978
return {
79+
'ImportDeclaration > ImportSpecifier,ImportNamespaceSpecifier'(
80+
node: TSESTree.Node
81+
) {
82+
const importDeclaration = node.parent as TSESTree.ImportDeclaration;
83+
const module = importDeclaration.source.value.toString();
84+
85+
if (LIBRARY_MODULES.includes(module)) {
86+
hasImportedFromTestingLibraryModule = true;
87+
}
88+
},
9089
[`CallExpression > Identifier[name=${ASYNC_QUERIES_REGEXP}]`](
9190
node: TSESTree.Identifier
9291
) {
@@ -105,17 +104,10 @@ export default ESLintUtils.RuleCreator(getDocsUrl)<Options, MessageIds>({
105104
},
106105
'Program:exit'() {
107106
testingLibraryQueryUsage.forEach(({ node, queryName }) => {
108-
const variableDeclaratorParent = node.parent.parent;
109-
110-
const references =
111-
(isVariableDeclarator(variableDeclaratorParent) &&
112-
context
113-
.getDeclaredVariables(variableDeclaratorParent)[0]
114-
.references.slice(1)) ||
115-
[];
107+
const references = getVariableReferences(context, node.parent.parent);
116108

117109
if (references && references.length === 0) {
118-
context.report({
110+
report({
119111
node,
120112
messageId: 'awaitAsyncQuery',
121113
data: {
@@ -129,7 +121,7 @@ export default ESLintUtils.RuleCreator(getDocsUrl)<Options, MessageIds>({
129121
!isAwaited(referenceNode.parent) &&
130122
!isPromiseResolved(referenceNode)
131123
) {
132-
context.report({
124+
report({
133125
node,
134126
messageId: 'awaitAsyncQuery',
135127
data: {

lib/rules/await-async-utils.ts

+20-37
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,18 @@
11
import { ESLintUtils, TSESTree } from '@typescript-eslint/experimental-utils';
22

33
import { getDocsUrl, ASYNC_UTILS, LIBRARY_MODULES } from '../utils';
4-
import { isCallExpression, hasThenProperty } from '../node-utils';
4+
import {
5+
isAwaited,
6+
isPromiseResolved,
7+
getVariableReferences,
8+
} from '../node-utils';
59

610
export const RULE_NAME = 'await-async-utils';
711
export type MessageIds = 'awaitAsyncUtil';
812
type Options = [];
913

10-
const VALID_PARENTS = [
11-
'AwaitExpression',
12-
'ArrowFunctionExpression',
13-
'ReturnStatement',
14-
];
15-
1614
const ASYNC_UTILS_REGEXP = new RegExp(`^(${ASYNC_UTILS.join('|')})$`);
1715

18-
function isAwaited(node: TSESTree.Node) {
19-
return VALID_PARENTS.includes(node.type);
20-
}
21-
22-
function isPromiseResolved(node: TSESTree.Node) {
23-
const parent = node.parent;
24-
25-
// wait(...).then(...)
26-
if (isCallExpression(parent)) {
27-
return hasThenProperty(parent.parent);
28-
}
29-
30-
// promise.then(...)
31-
return hasThenProperty(parent);
32-
}
33-
3416
export default ESLintUtils.RuleCreator(getDocsUrl)<Options, MessageIds>({
3517
name: RULE_NAME,
3618
meta: {
@@ -49,12 +31,17 @@ export default ESLintUtils.RuleCreator(getDocsUrl)<Options, MessageIds>({
4931
defaultOptions: [],
5032

5133
create(context) {
52-
const asyncUtilsUsage: Array<{ node: TSESTree.Identifier | TSESTree.MemberExpression, name: string }> = [];
34+
const asyncUtilsUsage: Array<{
35+
node: TSESTree.Identifier | TSESTree.MemberExpression;
36+
name: string;
37+
}> = [];
5338
const importedAsyncUtils: string[] = [];
5439

5540
return {
56-
'ImportDeclaration > ImportSpecifier,ImportNamespaceSpecifier'(node: TSESTree.Node) {
57-
const parent = (node.parent as TSESTree.ImportDeclaration);
41+
'ImportDeclaration > ImportSpecifier,ImportNamespaceSpecifier'(
42+
node: TSESTree.Node
43+
) {
44+
const parent = node.parent as TSESTree.ImportDeclaration;
5845

5946
if (!LIBRARY_MODULES.includes(parent.source.value.toString())) return;
6047

@@ -78,28 +65,24 @@ export default ESLintUtils.RuleCreator(getDocsUrl)<Options, MessageIds>({
7865
const identifier = memberExpression.object as TSESTree.Identifier;
7966
const memberExpressionName = identifier.name;
8067

81-
asyncUtilsUsage.push({ node: memberExpression, name: memberExpressionName });
68+
asyncUtilsUsage.push({
69+
node: memberExpression,
70+
name: memberExpressionName,
71+
});
8272
},
8373
'Program:exit'() {
8474
const testingLibraryUtilUsage = asyncUtilsUsage.filter(usage => {
8575
if (usage.node.type === 'MemberExpression') {
8676
const object = usage.node.object as TSESTree.Identifier;
8777

88-
return importedAsyncUtils.includes(object.name)
78+
return importedAsyncUtils.includes(object.name);
8979
}
9080

91-
return importedAsyncUtils.includes(usage.name)
81+
return importedAsyncUtils.includes(usage.name);
9282
});
9383

9484
testingLibraryUtilUsage.forEach(({ node, name }) => {
95-
const variableDeclaratorParent = node.parent.parent;
96-
97-
const references =
98-
(variableDeclaratorParent.type === 'VariableDeclarator' &&
99-
context
100-
.getDeclaredVariables(variableDeclaratorParent)[0]
101-
.references.slice(1)) ||
102-
[];
85+
const references = getVariableReferences(context, node.parent.parent);
10386

10487
if (
10588
references &&

lib/rules/await-fire-event.ts

+2-20
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,10 @@
11
import { ESLintUtils, TSESTree } from '@typescript-eslint/experimental-utils';
22
import { getDocsUrl } from '../utils';
3-
import { isIdentifier, isCallExpression, hasThenProperty } from '../node-utils';
3+
import { isIdentifier, isAwaited, isPromiseResolved } from '../node-utils';
44

55
export const RULE_NAME = 'await-fire-event';
66
export type MessageIds = 'awaitFireEvent';
77
type Options = [];
8-
9-
const VALID_PARENTS = [
10-
'AwaitExpression',
11-
'ArrowFunctionExpression',
12-
'ReturnStatement',
13-
];
14-
15-
function isAwaited(node: TSESTree.Node) {
16-
return VALID_PARENTS.includes(node.type);
17-
}
18-
19-
function isPromiseResolved(node: TSESTree.Node) {
20-
const parent = node.parent.parent;
21-
22-
// fireEvent.click().then(...)
23-
return isCallExpression(parent) && hasThenProperty(parent.parent);
24-
}
25-
268
export default ESLintUtils.RuleCreator(getDocsUrl)<Options, MessageIds>({
279
name: RULE_NAME,
2810
meta: {
@@ -51,7 +33,7 @@ export default ESLintUtils.RuleCreator(getDocsUrl)<Options, MessageIds>({
5133
if (
5234
isIdentifier(fireEventMethodNode) &&
5335
!isAwaited(node.parent.parent.parent) &&
54-
!isPromiseResolved(fireEventMethodNode)
36+
!isPromiseResolved(fireEventMethodNode.parent)
5537
) {
5638
context.report({
5739
node: fireEventMethodNode,

0 commit comments

Comments
 (0)