Skip to content

Commit 1bf94fc

Browse files
committed
fix(linter/no-standaline-expect): false positive with expect in callback
1 parent 5978224 commit 1bf94fc

File tree

2 files changed

+94
-51
lines changed
  • crates/oxc_linter/src/rules/jest/no_standalone_expect

2 files changed

+94
-51
lines changed

crates/oxc_linter/src/rules/jest/no_standalone_expect/mod.rs

Lines changed: 52 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -143,58 +143,71 @@ fn is_correct_place_to_call_expect<'a>(
143143
id_nodes_mapping: &FxHashMap<NodeId, &PossibleJestNode<'a, '_>>,
144144
ctx: &LintContext<'a>,
145145
) -> Option<()> {
146-
let mut parent = ctx.nodes().parent_node(node.id());
146+
let mut current_node = node;
147147

148-
// loop until find the closest function body
149-
while !matches!(parent.kind(), AstKind::FunctionBody(_) | AstKind::Program(_)) {
150-
parent = ctx.nodes().parent_node(parent.id());
151-
}
148+
// Walk up the tree, checking each function scope
149+
loop {
150+
let mut current = ctx.nodes().parent_node(current_node.id());
152151

153-
let parent = ctx.nodes().parent_node(parent.id());
152+
// loop until find the closest function body
153+
while !matches!(current.kind(), AstKind::FunctionBody(_) | AstKind::Program(_)) {
154+
current = ctx.nodes().parent_node(current.id());
155+
}
154156

155-
match parent.kind() {
156-
AstKind::Function(function) => {
157-
// `function foo() { expect(1).toBe(1); }`
158-
if function.is_function_declaration() {
159-
return Some(());
160-
}
157+
// If we reached the program root without finding a test block, it's invalid
158+
if matches!(current.kind(), AstKind::Program(_)) {
159+
return None;
160+
}
161161

162-
if function.is_expression() {
163-
let grandparent = ctx.nodes().parent_node(parent.id());
162+
let parent = ctx.nodes().parent_node(current.id());
163+
164+
match parent.kind() {
165+
AstKind::Function(function) => {
166+
// `function foo() { expect(1).toBe(1); }`
167+
if function.is_function_declaration() {
168+
return Some(());
169+
}
170+
171+
if function.is_expression() {
172+
let grandparent = ctx.nodes().parent_node(parent.id());
173+
174+
// `test('foo', function () { expect(1).toBe(1) })`
175+
// `const foo = function() {expect(1).toBe(1)}`
176+
if is_var_declarator_or_test_block(
177+
grandparent,
178+
additional_test_block_functions,
179+
id_nodes_mapping,
180+
ctx,
181+
) {
182+
return Some(());
183+
}
164184

165-
// `test('foo', function () { expect(1).toBe(1) })`
166-
// `const foo = function() {expect(1).toBe(1)}`
167-
return if is_var_declarator_or_test_block(
185+
// Continue checking parent scopes
186+
current_node = parent;
187+
} else {
188+
// Function that's neither a declaration nor expression - shouldn't reach here
189+
return None;
190+
}
191+
}
192+
AstKind::ArrowFunctionExpression(_) => {
193+
let grandparent = ctx.nodes().parent_node(parent.id());
194+
// `test('foo', () => expect(1).toBe(1))`
195+
// `const foo = () => expect(1).toBe(1)`
196+
if is_var_declarator_or_test_block(
168197
grandparent,
169198
additional_test_block_functions,
170199
id_nodes_mapping,
171200
ctx,
172201
) {
173-
Some(())
174-
} else {
175-
None
176-
};
202+
return Some(());
203+
}
204+
205+
// Continue checking parent scopes
206+
current_node = parent;
177207
}
208+
_ => return None,
178209
}
179-
AstKind::ArrowFunctionExpression(_) => {
180-
let grandparent = ctx.nodes().parent_node(parent.id());
181-
// `test('foo', () => expect(1).toBe(1))`
182-
// `const foo = () => expect(1).toBe(1)`
183-
return if is_var_declarator_or_test_block(
184-
grandparent,
185-
additional_test_block_functions,
186-
id_nodes_mapping,
187-
ctx,
188-
) {
189-
Some(())
190-
} else {
191-
None
192-
};
193-
}
194-
_ => {}
195210
}
196-
197-
None
198211
}
199212

200213
fn is_var_declarator_or_test_block<'a>(

crates/oxc_linter/src/rules/jest/no_standalone_expect/tests/jest.rs

Lines changed: 42 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -61,18 +61,48 @@ fn test() {
6161
",
6262
Some(serde_json::json!([{ "additionalTestBlockFunctions": ["each.test"] }])),
6363
),
64-
// (
65-
// r"function funcWithCallback(callback) { callback(5); }
66-
// describe('testWithCallback', () => {
67-
// it('should call the callback', (done) => {
68-
// funcWithCallback((result) => {
69-
// expect(result).toBe(5);
70-
// done();
71-
// });
72-
// });
73-
// });",
74-
// None,
75-
// ),
64+
(
65+
r"function funcWithCallback(callback) { callback(5); }
66+
describe('testWithCallback', () => {
67+
it('should call the callback', (done) => {
68+
funcWithCallback((result) => {
69+
expect(result).toBe(5);
70+
done();
71+
});
72+
});
73+
});",
74+
None,
75+
),
76+
(
77+
r"it('should do something', async () => {
78+
render();
79+
80+
await waitFor(() => {
81+
expect(screen.getByText('Option 2')).toBeInTheDocument();
82+
});
83+
});",
84+
None,
85+
),
86+
(
87+
r"it('should do something', () => {
88+
waitFor(() => {
89+
expect(screen.getByText('Option 2')).toBeInTheDocument();
90+
});
91+
});",
92+
None,
93+
),
94+
(
95+
r"describe('test suite', () => {
96+
it('should work with nested callbacks', () => {
97+
someFunction(() => {
98+
anotherFunction(() => {
99+
expect(true).toBe(true);
100+
});
101+
});
102+
});
103+
});",
104+
None,
105+
),
76106
];
77107

78108
let fail = vec![

0 commit comments

Comments
 (0)