Skip to content

Commit 4d878d1

Browse files
committed
fix: silence false positive await-async-events reports
1 parent 6b39e60 commit 4d878d1

File tree

3 files changed

+124
-9
lines changed

3 files changed

+124
-9
lines changed

lib/node-utils/index.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,46 @@ export function isPromiseHandled(nodeIdentifier: TSESTree.Identifier): boolean {
258258
return false;
259259
}
260260

261+
/**
262+
* For an expression in a parent expression that evaluates to the expression or another child returns the parent node recursively.
263+
*/
264+
function getRootExpression(
265+
expression: TSESTree.Expression
266+
): TSESTree.Expression {
267+
const { parent } = expression;
268+
if (parent == null) return expression;
269+
switch (parent.type) {
270+
case AST_NODE_TYPES.ConditionalExpression:
271+
case AST_NODE_TYPES.LogicalExpression:
272+
return getRootExpression(parent);
273+
case AST_NODE_TYPES.SequenceExpression:
274+
return parent.expressions[parent.expressions.length - 1] === expression
275+
? getRootExpression(parent)
276+
: expression;
277+
default:
278+
return expression;
279+
}
280+
}
281+
282+
/**
283+
* Determines whether a given promise expression is considered unhandled.
284+
*
285+
* It will be considered unhandled if an ancestor voids the expression.
286+
*/
287+
export function isPromiseUnhandled(expression: TSESTree.Expression): boolean {
288+
const { parent } = getRootExpression(expression);
289+
if (parent == null) return false;
290+
switch (parent.type) {
291+
case AST_NODE_TYPES.ExpressionStatement:
292+
case AST_NODE_TYPES.SequenceExpression:
293+
case AST_NODE_TYPES.UnaryExpression:
294+
case AST_NODE_TYPES.VariableDeclarator:
295+
return true;
296+
default:
297+
return false;
298+
}
299+
}
300+
261301
export function getVariableReferences(
262302
context: TSESLint.RuleContext<string, unknown[]>,
263303
node: TSESTree.Node

lib/rules/await-async-events.ts

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {
88
getInnermostReturningFunction,
99
getVariableReferences,
1010
isMemberExpression,
11-
isPromiseHandled,
11+
isPromiseUnhandled,
1212
} from '../node-utils';
1313
import { EVENTS_SIMULATORS } from '../utils';
1414

@@ -91,7 +91,7 @@ export default createTestingLibraryRule<Options, MessageIds>({
9191
messageId?: MessageIds;
9292
fix?: TSESLint.ReportFixFunction;
9393
}): void {
94-
if (!isPromiseHandled(node)) {
94+
if (isPromiseUnhandled(closestCallExpression)) {
9595
context.report({
9696
node: closestCallExpression.callee,
9797
messageId,
@@ -176,13 +176,17 @@ export default createTestingLibraryRule<Options, MessageIds>({
176176
},
177177
});
178178
} else {
179-
for (const reference of references) {
180-
if (ASTUtils.isIdentifier(reference.identifier)) {
181-
reportUnhandledNode({
182-
node: reference.identifier,
183-
closestCallExpression,
184-
});
185-
}
179+
const referenceIdentifiers = references
180+
.map(({ identifier }) => identifier)
181+
.filter(ASTUtils.isIdentifier);
182+
if (referenceIdentifiers.every(isPromiseUnhandled)) {
183+
referenceIdentifiers.forEach(
184+
(id) =>
185+
void reportUnhandledNode({
186+
node: id,
187+
closestCallExpression,
188+
})
189+
);
186190
}
187191
}
188192
} else if (functionWrappersNames.includes(node.name)) {

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

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,17 @@ ruleTester.run(RULE_NAME, rule, {
311311
312312
await triggerEvent()
313313
})
314+
`,
315+
options: [{ eventModule: 'userEvent' }] as const,
316+
})),
317+
...USER_EVENT_ASYNC_FUNCTIONS.map((eventMethod) => ({
318+
code: `
319+
import userEvent from '${testingFramework}'
320+
test('await expression that evaluates to promise is valid', async () => {
321+
await (null, userEvent.${eventMethod}(getByLabelText('username')));
322+
await (condition ? null : userEvent.${eventMethod}(getByLabelText('username')));
323+
await (condition && userEvent.${eventMethod}(getByLabelText('username')));
324+
})
314325
`,
315326
options: [{ eventModule: 'userEvent' }] as const,
316327
})),
@@ -960,6 +971,66 @@ ruleTester.run(RULE_NAME, rule, {
960971
}
961972
962973
triggerEvent()
974+
`,
975+
} as const)
976+
),
977+
...USER_EVENT_ASYNC_FUNCTIONS.map(
978+
(eventMethod) =>
979+
({
980+
code: `
981+
import userEvent from '${testingFramework}'
982+
test('unhandled expression that evaluates to promise is invalid', () => {
983+
condition ? null : (null, true && userEvent.${eventMethod}(getByLabelText('username')));
984+
});
985+
`,
986+
errors: [
987+
{
988+
line: 4,
989+
column: 38,
990+
messageId: 'awaitAsyncEvent',
991+
data: { name: eventMethod },
992+
},
993+
],
994+
options: [{ eventModule: 'userEvent' }],
995+
output: `
996+
import userEvent from '${testingFramework}'
997+
test('unhandled expression that evaluates to promise is invalid', async () => {
998+
condition ? null : (null, true && await userEvent.${eventMethod}(getByLabelText('username')));
999+
});
1000+
`,
1001+
} as const)
1002+
),
1003+
...USER_EVENT_ASYNC_FUNCTIONS.map(
1004+
(eventMethod) =>
1005+
({
1006+
code: `
1007+
import userEvent from '${testingFramework}'
1008+
test('voided promise is invalid', async () => {
1009+
await void userEvent.${eventMethod}(getByLabelText('username'));
1010+
await (userEvent.${eventMethod}(getByLabelText('username')), null);
1011+
});
1012+
`,
1013+
errors: [
1014+
{
1015+
line: 4,
1016+
column: 15,
1017+
messageId: 'awaitAsyncEvent',
1018+
data: { name: eventMethod },
1019+
},
1020+
{
1021+
line: 5,
1022+
column: 11,
1023+
messageId: 'awaitAsyncEvent',
1024+
data: { name: eventMethod },
1025+
},
1026+
],
1027+
options: [{ eventModule: 'userEvent' }],
1028+
output: `
1029+
import userEvent from '${testingFramework}'
1030+
test('voided promise is invalid', async () => {
1031+
await void await userEvent.${eventMethod}(getByLabelText('username'));
1032+
await (await userEvent.${eventMethod}(getByLabelText('username')), null);
1033+
});
9631034
`,
9641035
} as const)
9651036
),

0 commit comments

Comments
 (0)