Skip to content

Commit 39ca108

Browse files
committed
fix(native): skip callback-reference calls for this()/super() call expressions
In handle_call_expr, when the callee is `this` or `super` used as a function (e.g. `this(b)` or `super(a)`), extract_callback_reference_calls was being invoked and emitting identifier arguments as spurious dynamic calls. The pts resolver then resolved those names globally, producing false cross-file call edges (e.g. fun/fun.js:foo → spread/spread.js:b). Add an early return for fn_node.kind() == "this" || "super", mirroring the JS extractor's guard (javascript.ts:1135). Add regression tests for both this(b) and super(a) argument non-emission. Closes #1511
1 parent e5ea649 commit 39ca108

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
@@ -1222,6 +1222,19 @@ fn handle_call_expr(node: &Node, source: &[u8], symbols: &mut FileSymbols) {
12221222
handle_dynamic_import(node, &fn_node, source, symbols);
12231223
return;
12241224
}
1225+
// `this(args)` and `super(args)` — the callee is `this`/`super` used as a
1226+
// function, not a named identifier. The `this` call record is emitted by
1227+
// collect_this_call_and_bindings (called from match_js_pts_bindings).
1228+
// Neither case should emit callback-reference calls for the arguments, because
1229+
// those arguments are values passed *to* the rebound function — not callbacks
1230+
// of the enclosing scope. Without this guard, identifier arguments like `b`
1231+
// in `this(b)` or `a` in `super(a)` become spurious dynamic calls that the
1232+
// pts resolver resolves to globally-defined functions with the same name in
1233+
// other files, producing false cross-file call edges.
1234+
// Mirrors the early-return guard in the TS handleCallExpr (javascript.ts:1135).
1235+
if fn_node.kind() == "this" || fn_node.kind() == "super" {
1236+
return;
1237+
}
12251238
if let Some(call_info) = extract_call_info(&fn_node, node, source) {
12261239
symbols.calls.push(call_info);
12271240
}
@@ -4097,6 +4110,51 @@ mod tests {
40974110
assert_eq!(b.unwrap().this_arg, "handler");
40984111
}
40994112

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

0 commit comments

Comments
 (0)