Skip to content

Commit dd87f93

Browse files
committed
fix(linter): stack overflow in react/exhaustive-deps (#11613)
fixes #11609
1 parent 5a55a58 commit dd87f93

File tree

1 file changed

+39
-4
lines changed

1 file changed

+39
-4
lines changed

crates/oxc_linter/src/rules/react/exhaustive_deps.rs

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -836,6 +836,24 @@ fn is_identifier_a_dependency<'a>(
836836
ident_span: Span,
837837
ctx: &'_ LintContext<'a>,
838838
component_scope_id: ScopeId,
839+
) -> bool {
840+
let mut visited = FxHashSet::default();
841+
is_identifier_a_dependency_impl(
842+
ident_name,
843+
ident_reference_id,
844+
ident_span,
845+
ctx,
846+
component_scope_id,
847+
&mut visited,
848+
)
849+
}
850+
fn is_identifier_a_dependency_impl<'a>(
851+
ident_name: Atom<'a>,
852+
ident_reference_id: ReferenceId,
853+
ident_span: Span,
854+
ctx: &'_ LintContext<'a>,
855+
component_scope_id: ScopeId,
856+
visited: &mut FxHashSet<SymbolId>,
839857
) -> bool {
840858
// if it is a global e.g. `console` or `window`, then it's not a dependency
841859
if ctx.scoping().root_unresolved_references().contains_key(ident_name.as_str()) {
@@ -885,8 +903,14 @@ fn is_identifier_a_dependency<'a>(
885903
return false;
886904
}
887905

888-
// if the value is stable (for example the setter returned by useState), then it's not a dependency
889-
if is_stable_value(declaration, ident_name, ident_reference_id, ctx, component_scope_id) {
906+
if is_stable_value(
907+
declaration,
908+
ident_name,
909+
ident_reference_id,
910+
ctx,
911+
component_scope_id,
912+
visited,
913+
) {
890914
return false;
891915
}
892916

@@ -911,7 +935,14 @@ fn is_stable_value<'a, 'b>(
911935
ident_reference_id: ReferenceId,
912936
ctx: &'b LintContext<'a>,
913937
component_scope_id: ScopeId,
938+
visited: &mut FxHashSet<SymbolId>,
914939
) -> bool {
940+
if let Some(symbol_id) = ctx.scoping().get_reference(ident_reference_id).symbol_id() {
941+
if !visited.insert(symbol_id) {
942+
return true;
943+
}
944+
}
945+
915946
match node.kind() {
916947
AstKind::VariableDeclaration(declaration) => {
917948
if declaration.kind == VariableDeclarationKind::Const {
@@ -942,6 +973,7 @@ fn is_stable_value<'a, 'b>(
942973
.map(oxc_ast::ast::BindingIdentifier::symbol_id),
943974
ctx,
944975
component_scope_id,
976+
visited,
945977
);
946978
}
947979
}
@@ -1015,7 +1047,7 @@ fn is_stable_value<'a, 'b>(
10151047

10161048
let Some(function_body) = function_body else { return false };
10171049

1018-
is_function_stable(function_body, None, ctx, component_scope_id)
1050+
is_function_stable(function_body, None, ctx, component_scope_id, visited)
10191051
}
10201052
_ => false,
10211053
}
@@ -1026,6 +1058,7 @@ fn is_function_stable<'a, 'b>(
10261058
function_symbol_id: Option<SymbolId>,
10271059
ctx: &'b LintContext<'a>,
10281060
component_scope_id: ScopeId,
1061+
visited: &mut FxHashSet<SymbolId>,
10291062
) -> bool {
10301063
let deps = {
10311064
let mut collector = ExhaustiveDepsVisitor::new(ctx.semantic());
@@ -1035,12 +1068,13 @@ fn is_function_stable<'a, 'b>(
10351068

10361069
deps.iter().all(|dep| {
10371070
dep.symbol_id.zip(function_symbol_id).is_none_or(|(l, r)| l != r)
1038-
&& !is_identifier_a_dependency(
1071+
&& !is_identifier_a_dependency_impl(
10391072
dep.name,
10401073
dep.reference_id,
10411074
dep.span,
10421075
ctx,
10431076
component_scope_id,
1077+
visited,
10441078
)
10451079
})
10461080
}
@@ -2434,6 +2468,7 @@ fn test() {
24342468
}
24352469
"#,
24362470
r"function MyComponent() { const recursive = useCallback((n: number): number => (n <= 0 ? 0 : n + recursive(n - 1)), []); return recursive }",
2471+
r"function Foo2() { useEffect(() => { foo() }, []); const foo = () => { bar() }; function bar () { foo() } }",
24372472
];
24382473

24392474
let fail = vec![

0 commit comments

Comments
 (0)