Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 63 additions & 45 deletions crates/oxc_linter/src/rules/react/exhaustive_deps.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1348,66 +1348,65 @@ impl<'a> Visit<'a> for ExhaustiveDepsVisitor<'a, '_> {

let is_parent_call_expr = self.is_callee_of_call_expr;

match analyze_property_chain(&it.object, self.semantic) {
Ok(source) => {
if let Some(source) = source {
if is_parent_call_expr {
self.found_dependencies.insert(source);
} else {
let new_chain = Vec::from([it.property.name]);

let mut destructured_props: Vec<Atom<'a>> = vec![];
let mut did_see_ref = false;
let needs_full_chain = self
.iter_destructure_bindings(|id| {
if let Cow::Borrowed(id) = id {
if id == "current" {
did_see_ref = true;
} else {
destructured_props.push(id.into());
}
if let Ok(source) = analyze_property_chain(&it.object, self.semantic) {
if let Some(source) = source {
if is_parent_call_expr {
self.found_dependencies.insert(source);
} else {
let new_chain = Vec::from([it.property.name]);

let mut destructured_props: Vec<Atom<'a>> = vec![];
let mut did_see_ref = false;
let needs_full_chain = self
.iter_destructure_bindings(|id| {
if let Cow::Borrowed(id) = id {
if id == "current" {
did_see_ref = true;
} else {
// todo
destructured_props.push(id.into());
}
})
.unwrap_or(true);

let symbol_id =
self.semantic.scoping().get_reference(source.reference_id).symbol_id();
if needs_full_chain || (destructured_props.is_empty() && !did_see_ref) {
} else {
// todo
}
})
.unwrap_or(true);

let symbol_id =
self.semantic.scoping().get_reference(source.reference_id).symbol_id();
if needs_full_chain || (destructured_props.is_empty() && !did_see_ref) {
self.found_dependencies.insert(Dependency {
name: source.name,
reference_id: source.reference_id,
span: source.span,
chain: [source.chain.clone(), new_chain].concat(),
symbol_id,
});
} else {
for prop in destructured_props {
self.found_dependencies.insert(Dependency {
name: source.name,
reference_id: source.reference_id,
span: source.span,
chain: [source.chain.clone(), new_chain].concat(),
chain: [source.chain.clone(), new_chain.clone(), vec![prop]]
.concat(),
symbol_id,
});
} else {
for prop in destructured_props {
self.found_dependencies.insert(Dependency {
name: source.name,
reference_id: source.reference_id,
span: source.span,
chain: [source.chain.clone(), new_chain.clone(), vec![prop]]
.concat(),
symbol_id,
});
}
}
}
}

let cur_skip_reporting_dependency = self.skip_reporting_dependency;
self.skip_reporting_dependency = true;
self.visit_expression(&it.object);
self.skip_reporting_dependency = cur_skip_reporting_dependency;
}

let cur_skip_reporting_dependency = self.skip_reporting_dependency;
self.skip_reporting_dependency = true;
self.is_callee_of_call_expr = false;
self.visit_expression(&it.object);
self.skip_reporting_dependency = cur_skip_reporting_dependency;
} else {
// this means that some part of the chain could not be analyzed
// for example `foo.bar.baz().abc`. `baz()` cannot be statically analyzed
// instead, continue to go down, looking at the object to gather dependencies
Err(()) => {
self.visit_expression(&it.object);
}
self.is_callee_of_call_expr = false;
self.visit_expression(&it.object);
}
}

Expand Down Expand Up @@ -2656,6 +2655,25 @@ fn test() {
onStuff();
}, []);
}",
// Issue #15796 - object property access should work correctly
r"export const FileSize = ({ file, showSize = true }) => {
const fileSizeInMB = useMemo(
() => (showSize ? (file.size / (1024 * 1024)).toFixed(2) : undefined),
[showSize, file.size],
);
return fileSizeInMB;
}",
// Additional tests for nested property access within expressions
r"function MyComponent({ obj }) {
useMemo(() => {
return (obj.value * 2).toFixed(2);
}, [obj.value]);
}",
r"function MyComponent({ data }) {
useCallback(() => {
console.log((data.count + 1).toString());
}, [data.count]);
}",
];

let fail = vec![
Expand Down
6 changes: 3 additions & 3 deletions crates/oxc_linter/src/snapshots/react_exhaustive_deps.snap
Original file line number Diff line number Diff line change
Expand Up @@ -393,13 +393,13 @@ source: crates/oxc_linter/src/tester.rs
╰────
help: Either include it or remove the dependency array.

⚠ eslint-plugin-react-hooks(exhaustive-deps): React Hook useEffect has a missing dependency: 'history.foo'
⚠ eslint-plugin-react-hooks(exhaustive-deps): React Hook useEffect has missing dependencies: 'history.foo', and 'history.foo.bar'
╭─[exhaustive_deps.tsx:7:14]
3 │ return [
4 │ history.foo.bar[2].dobedo.listen(),
· ─────┬─────
· ╰── useEffect uses `history.foo` here
· ───────────
5 │ history.foo.bar().dobedo.listen[2]
· ───────────
6 │ ];
7 │ }, []);
· ──
Expand Down
Loading