|
1 |
| -use itertools::{FoldWhile, Itertools}; |
2 | 1 | use oxc_ast::{
|
3 | 2 | ast::{ArrowFunctionExpression, Function},
|
4 | 3 | AstKind,
|
5 | 4 | };
|
6 | 5 | use oxc_macros::declare_oxc_lint;
|
7 | 6 | use oxc_semantic::{
|
8 |
| - petgraph::{self}, |
9 |
| - pg::neighbors_filtered_by_edge_weight, |
10 |
| - AstNodeId, AstNodes, BasicBlockId, EdgeType, Instruction, InstructionKind, |
| 7 | + algo, petgraph::visit::Control, AstNodeId, AstNodes, BasicBlockId, EdgeType, InstructionKind, |
11 | 8 | };
|
12 | 9 | use oxc_span::{Atom, CompactStr};
|
13 | 10 | use oxc_syntax::operator::AssignmentOperator;
|
@@ -241,87 +238,43 @@ impl Rule for RulesOfHooks {
|
241 | 238 | return ctx.diagnostic(diagnostics::loop_hook(span, hook_name));
|
242 | 239 | }
|
243 | 240 |
|
244 |
| - if Self::is_conditional(ctx, func_cfg_id, node_cfg_id) |
245 |
| - || Self::breaks_early(ctx, func_cfg_id, node_cfg_id) |
246 |
| - { |
| 241 | + if has_conditional_path_accept_throw(ctx, func_cfg_id, node_cfg_id) { |
247 | 242 | #[allow(clippy::needless_return)]
|
248 | 243 | return ctx.diagnostic(diagnostics::conditional_hook(span, hook_name));
|
249 | 244 | }
|
250 | 245 | }
|
251 | 246 | }
|
252 | 247 |
|
253 |
| -// TODO: all `dijkstra` algorithms can be merged together for better performance. |
254 |
| -impl RulesOfHooks { |
255 |
| - #[inline] |
256 |
| - fn is_conditional( |
257 |
| - ctx: &LintContext, |
258 |
| - func_cfg_id: BasicBlockId, |
259 |
| - node_cfg_id: BasicBlockId, |
260 |
| - ) -> bool { |
261 |
| - let cfg = ctx.semantic().cfg(); |
262 |
| - let graph = &cfg.graph; |
263 |
| - // All nodes should be reachable from our hook, Otherwise we have a conditional/branching flow. |
264 |
| - petgraph::algo::dijkstra(graph, func_cfg_id, Some(node_cfg_id), |e| match e.weight() { |
265 |
| - EdgeType::NewFunction => 1, |
266 |
| - EdgeType::Jump | EdgeType::Unreachable | EdgeType::Backedge | EdgeType::Normal => 0, |
| 248 | +fn has_conditional_path_accept_throw( |
| 249 | + ctx: &LintContext<'_>, |
| 250 | + from: BasicBlockId, |
| 251 | + to: BasicBlockId, |
| 252 | +) -> bool { |
| 253 | + let cfg = ctx.semantic().cfg(); |
| 254 | + let graph = &cfg.graph; |
| 255 | + // All nodes should be able to reach the hook node, Otherwise we have a conditional/branching flow. |
| 256 | + algo::dijkstra(graph, from, Some(to), |e| match e.weight() { |
| 257 | + EdgeType::NewFunction => 1, |
| 258 | + EdgeType::Jump | EdgeType::Unreachable | EdgeType::Backedge | EdgeType::Normal => 0, |
| 259 | + }) |
| 260 | + .into_iter() |
| 261 | + .filter(|(_, val)| *val == 0) |
| 262 | + .any(|(f, _)| { |
| 263 | + !cfg.is_reachabale_filtered(f, to, |it| { |
| 264 | + if cfg |
| 265 | + .basic_block(it) |
| 266 | + .instructions() |
| 267 | + .iter() |
| 268 | + .any(|i| matches!(i.kind, InstructionKind::Throw)) |
| 269 | + { |
| 270 | + Control::Break(true) |
| 271 | + } else { |
| 272 | + Control::Continue |
| 273 | + } |
267 | 274 | })
|
268 |
| - .into_iter() |
269 |
| - .filter(|(_, val)| *val == 0) |
270 |
| - .any(|(f, _)| !cfg.is_reachabale(f, node_cfg_id)) |
271 |
| - } |
272 |
| - |
273 |
| - #[inline] |
274 |
| - fn breaks_early( |
275 |
| - ctx: &LintContext, |
276 |
| - func_cfg_id: BasicBlockId, |
277 |
| - node_cfg_id: BasicBlockId, |
278 |
| - ) -> bool { |
279 |
| - let cfg = ctx.semantic().cfg(); |
280 |
| - neighbors_filtered_by_edge_weight( |
281 |
| - &cfg.graph, |
282 |
| - func_cfg_id, |
283 |
| - &|e| match e { |
284 |
| - EdgeType::Jump | EdgeType::Normal => None, |
285 |
| - EdgeType::Unreachable | EdgeType::Backedge | EdgeType::NewFunction => { |
286 |
| - Some(State::default()) |
287 |
| - } |
288 |
| - }, |
289 |
| - &mut |id: &BasicBlockId, mut state: State| { |
290 |
| - if node_cfg_id == *id { |
291 |
| - return (state, false); |
292 |
| - } |
293 |
| - |
294 |
| - let (push, keep_walking) = cfg |
295 |
| - .basic_block(*id) |
296 |
| - .instructions |
297 |
| - .iter() |
298 |
| - .fold_while((false, true), |acc, Instruction { kind, .. }| match kind { |
299 |
| - InstructionKind::Break(_) => FoldWhile::Done((true, false)), |
300 |
| - InstructionKind::Unreachable |
301 |
| - | InstructionKind::Throw |
302 |
| - | InstructionKind::Return(_) => FoldWhile::Continue((acc.0, false)), |
303 |
| - InstructionKind::Statement | InstructionKind::Continue(_) => { |
304 |
| - FoldWhile::Continue(acc) |
305 |
| - } |
306 |
| - }) |
307 |
| - .into_inner(); |
308 |
| - |
309 |
| - if push { |
310 |
| - state.0.push(*id); |
311 |
| - } |
312 |
| - (state, keep_walking) |
313 |
| - }, |
314 |
| - ) |
315 |
| - .iter() |
316 |
| - .flat_map(|it| it.0.iter()) |
317 |
| - .next() |
318 |
| - .is_some() |
319 |
| - } |
| 275 | + }) |
320 | 276 | }
|
321 | 277 |
|
322 |
| -#[derive(Debug, Default, Clone)] |
323 |
| -struct State(Vec<BasicBlockId>); |
324 |
| - |
325 | 278 | fn parent_func<'a>(nodes: &'a AstNodes<'a>, node: &AstNode) -> Option<&'a AstNode<'a>> {
|
326 | 279 | nodes.ancestors(node.id()).map(|id| nodes.get_node(id)).find(|it| it.kind().is_function_like())
|
327 | 280 | }
|
|
0 commit comments