fix(extraction): index const-bound functions inside a body as symbols (#1669) (#1774)

`const handleClear = () => {…}` inside a component — every React handler
that skips useCallback — was never a symbol: the body walker only named
nested function declarations and hook-bound arrows, so the handler was
absent from callers/impact ("Symbol not found", indistinguishable from
"no callers") and its calls attributed to the component. Bind the arrow
or function expression to its declarator the way module scope already
does, in both the wasm walker and the kernel.

A navigation such a handler makes is now the handler's own edge and a hop
in the Screens `via` chain — the shape a useCallback handler already has —
so the react-router and expo-router expectations follow that convention.

Co-authored-by: danusha2345 <ewidusoc498@gmail.com>
Co-authored-by: Colby McHenry <colbymchenry@users.noreply.github.com>
This commit is contained in:
Colby Mchenry
2026-09-08 10:07:46 -05:00
committed by GitHub
co-authored by danusha2345 Colby McHenry
parent 85550eb2ce
commit 8733c2880f
7 changed files with 173 additions and 7 deletions
+28
View File
@@ -718,6 +718,13 @@ impl<'t> Walker<'t> {
self.extract_function(node, Some(bound));
return;
}
// `const handleClear = () => {…}` inside a body (#1669): named by
// its declarator, like at module scope. Mirrors
// TreeSitterExtractor's declaratorBoundFunction.
if self.declarator_bound_function(node) {
self.extract_function(node, None);
return;
}
}
if is_class_type(self.variant, kind) {
@@ -742,6 +749,27 @@ impl<'t> Walker<'t> {
// --- name / signature / modifier helpers ------------------------------------
/// Whether an anonymous function is the whole value of a
/// `variable_declarator` with a plain identifier name —
/// `const NAME = () => {…}` / `= function () {…}`.
fn declarator_bound_function(&self, node: Node<'t>) -> bool {
if !matches!(node.kind(), "arrow_function" | "function_expression") {
return false;
}
let Some(declarator) = node.parent() else { return false };
if declarator.kind() != "variable_declarator" {
return false;
}
let Some(value) = declarator.child_by_field_name("value") else { return false };
if value.start_byte() != node.start_byte() || value.end_byte() != node.end_byte() {
return false;
}
declarator
.child_by_field_name("name")
.map(|n| n.kind() == "identifier")
.unwrap_or(false)
}
/// The declarator name a React handler hook binds an anonymous function
/// to — `const NAME = useCallback(<node>, [...])` (also `React.useCallback`,
/// `useEffectEvent`, `useEvent`) — or None for any other shape. The node