Skip to content

Commit 6dd47ff

Browse files
committed
fix(native): skip callback-reference calls for this()/super() call expressions
In handle_call_expr, when the callee node is `this` or `super` (e.g. `this(b)`, `super(a)`), extract_callback_reference_calls was running and emitting identifier arguments as spurious dynamic calls. The pts resolver then matched those names globally, producing false cross-file call edges in the jelly-micro fixture: fun/fun.js:foo → spread/spread.js:b, fun/fun.js:bar → prototypes3/prototypes3.js:c, etc. Fix: add an early return for fn_node.kind() == "this" || fn_node.kind() == "super", mirroring the existing guard in the TypeScript handleCallExpr (javascript.ts:1135). Add two regression tests covering both cases. Closes #1543
1 parent ce510f3 commit 6dd47ff

1 file changed

Lines changed: 58 additions & 0 deletions

File tree

crates/codegraph-core/src/extractors/javascript.rs

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1215,6 +1215,19 @@ fn handle_call_expr(node: &Node, source: &[u8], symbols: &mut FileSymbols) {
12151215
handle_dynamic_import(node, &fn_node, source, symbols);
12161216
return;
12171217
}
1218+
// `this(args)` and `super(args)` — the callee is `this`/`super` used as a
1219+
// function, not a named identifier. The `this` call record is emitted by
1220+
// collect_this_call_and_bindings (called from match_js_pts_bindings).
1221+
// Neither case should emit callback-reference calls for the arguments, because
1222+
// those arguments are values passed *to* the rebound function — not callbacks
1223+
// of the enclosing scope. Without this guard, identifier arguments like `b`
1224+
// in `this(b)` or `a` in `super(a)` become spurious dynamic calls that the
1225+
// pts resolver resolves to globally-defined functions with the same name in
1226+
// other files, producing false cross-file call edges.
1227+
// Mirrors the early-return guard in the TS handleCallExpr (javascript.ts:1135).
1228+
if fn_node.kind() == "this" || fn_node.kind() == "super" {
1229+
return;
1230+
}
12181231
if let Some(call_info) = extract_call_info(&fn_node, node, source) {
12191232
symbols.calls.push(call_info);
12201233
}
@@ -4090,6 +4103,51 @@ mod tests {
40904103
assert_eq!(b.unwrap().this_arg, "handler");
40914104
}
40924105

4106+
/// `this(b)` must NOT emit `b` as a dynamic callback-reference call.
4107+
/// Without the early-return guard, `b` would be emitted as a dynamic call
4108+
/// and the pts resolver would match any globally-defined function named `b`,
4109+
/// producing false cross-file call edges (issue #1543).
4110+
#[test]
4111+
fn this_call_args_do_not_emit_callback_reference_calls() {
4112+
let s = parse_js(
4113+
"function foo(b) { return this(b); }\n\
4114+
foo.call((a) => a, () => {});",
4115+
);
4116+
assert!(
4117+
s.calls.iter().any(|c| c.name == "this"),
4118+
"this() must be recorded; got: {:?}",
4119+
s.calls.iter().map(|c| &c.name).collect::<Vec<_>>()
4120+
);
4121+
assert!(
4122+
!s.calls.iter().any(|c| c.name == "b"),
4123+
"argument `b` of this(b) must not become a callback-reference call; got: {:?}",
4124+
s.calls.iter().map(|c| (&c.name, c.dynamic)).collect::<Vec<_>>()
4125+
);
4126+
}
4127+
4128+
/// `super(a)` must NOT emit `a` as a dynamic callback-reference call.
4129+
/// Same root cause as this(b): the callee `super` is not a named identifier,
4130+
/// so extract_callback_reference_calls must not run on the arguments.
4131+
#[test]
4132+
fn super_call_args_do_not_emit_callback_reference_calls() {
4133+
let s = parse_js(
4134+
"class E { constructor(c) { this.cc = c; } }\n\
4135+
class G extends E {\n\
4136+
constructor(a, b) { super(a); this.bb = b; }\n\
4137+
}",
4138+
);
4139+
assert!(
4140+
!s.calls.iter().any(|c| c.name == "a"),
4141+
"argument `a` of super(a) must not become a callback-reference call; got: {:?}",
4142+
s.calls.iter().map(|c| (&c.name, c.dynamic)).collect::<Vec<_>>()
4143+
);
4144+
assert!(
4145+
!s.calls.iter().any(|c| c.name == "b"),
4146+
"argument `b` of this.bb = b must not become a callback-reference call; got: {:?}",
4147+
s.calls.iter().map(|c| (&c.name, c.dynamic)).collect::<Vec<_>>()
4148+
);
4149+
}
4150+
40934151
#[test]
40944152
fn array_elem_bindings_recorded() {
40954153
let s = parse_js(

0 commit comments

Comments
 (0)