From 5c6f7272cd7d50c0528bd9b7f44da437c87d97c3 Mon Sep 17 00:00:00 2001 From: safishamsi Date: Wed, 8 Jul 2026 12:09:40 +0100 Subject: [PATCH] test(extract): cover builtin static-call shape (Date.now()) for #1726 Adds a regression test for the Date.now()/static-call shape (credit PR #1727 / @2loch-ness6, who independently found the same fix): a capitalized builtin receiver must not bind cross-file to a same-spelled user const/class. The same guard added in 67f4f83 already covers it (verified); this locks the static-call shape in, while allowing the legitimate same-file const reference. Co-Authored-By: Claude Opus 4.8 (1M context) --- tests/test_builtin_global_type_refs.py | 31 ++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/tests/test_builtin_global_type_refs.py b/tests/test_builtin_global_type_refs.py index 0fe1fd72..25ecb151 100644 --- a/tests/test_builtin_global_type_refs.py +++ b/tests/test_builtin_global_type_refs.py @@ -50,3 +50,34 @@ def test_nonbuiltin_receiver_type_still_resolves(tmp_path): } assert any(t and "charge" in str(t).lower() for _, t, _ in resolved), \ f"user member-call must still resolve; got {resolved}" + + +def test_builtin_static_call_does_not_bind_to_user_symbol(tmp_path): + # #1726 (static-call shape, credit PR #1727 / @2loch-ness6): `Date.now()` treats + # the capitalized receiver `Date` as a type name; without the builtin guard it + # binds to a same-spelled user `const DATE`, a false god node. A typed param in + # the same class arms the cross-file member-call resolver (the real service shape). + (tmp_path / "format.ts").write_text( + "const DATE = new Intl.DateTimeFormat('en-US', {});\n" + "export function fmt(x: number): string { return DATE.format(x); }\n") + (tmp_path / "svc.ts").write_text( + "export class Svc {\n" + " expiry(d: Date): Date { return d; }\n" + " stamp(): number { return Date.now(); }\n" + " when(): string { return new Date().toISOString(); }\n" + "}\n") + r = extract(sorted(tmp_path.glob("*.ts")), cache_root=tmp_path, parallel=False) + lbl = _labels_by_id(r) + by_id = {n["id"]: n for n in r["nodes"]} + date_ids = [n["id"] for n in r["nodes"] if n.get("label") == "DATE"] + date_sf = {str(by_id[i].get("source_file", "")) for i in date_ids} + # A same-file reference to the real const DATE (fmt() -> DATE in format.ts) is + # legitimate. The bug is a CROSS-FILE bind: svc.ts's Date.now()/new Date() + # resolving to format.ts's const DATE. Assert no such cross-file phantom. + for e in r["edges"]: + if e.get("target") in date_ids and e.get("relation") == "references": + src_sf = str(by_id.get(e["source"], {}).get("source_file", "")) + assert src_sf in date_sf, ( + f"cross-file builtin phantom: {lbl.get(e['source'])!r} in {src_sf} " + f"bound to user DATE" + )