test(js): cover external-import-shadow vs same-named local callable (#2757)

Adds the collision case the fix must survive: a name both imported externally
and defined as a callable in another corpus file. The external use must not
fabricate a cross-file indirect_call while a genuine local by-name reference
still binds. Also adds the CHANGELOG entry.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
safishamsi
2026-08-15 16:43:11 +01:00
co-authored by Claude Opus 4.8
parent ceeafb05ce
commit 94c2050f93
2 changed files with 22 additions and 0 deletions
+1
View File
@@ -4,6 +4,7 @@ Full release notes with details on each version: [GitHub Releases](https://githu
## 0.9.44 (unreleased)
- Fix: a JS/TS identifier bound by an import whose target resolves outside the scanned corpus (e.g. a `lucide-react` icon) is now shadowed, so using it as a value no longer fabricates an INFERRED `indirect_call` onto an unrelated same-named callable elsewhere in the corpus; a relative/in-corpus import still resolves to its real target (#2757, thanks @phudayyy).
- Fix: an OCaml qualified call `M.f` to an external module (one not defined in the same file, e.g. Hardcaml's `Reg_spec.create`) no longer binds to a same-named local `let f` — which produced a false `calls` edge and, when the caller was that local `f`, a `f -> f` self-loop. External qualified calls are kept as a distinct target labelled by the full path; unqualified calls and calls into a locally-defined module still resolve locally, and cross-file `Geo.area` still collapses onto another file's `area`.
## 0.9.43 (2026-08-14)
@@ -152,3 +152,24 @@ def test_unimported_same_file_callable_still_emits(tmp_path):
)})
indirect = _rels(r, "indirect_call")
assert (nid["run"], nid["handler"]) in indirect
def test_external_import_shadow_does_not_bind_to_a_same_named_local_callable(tmp_path):
"""The precise collision the fix must survive: a name that is BOTH imported
externally AND defined as a callable in another corpus file. Using the
external `Filter` value must not fabricate an indirect_call onto the unrelated
corpus `Filter` — while a genuine by-name use of a local callable still binds."""
r, nid = _extract_js_dir(tmp_path, {
"table.ts": "export function Filter() { return null; }\n", # unrelated corpus callable
"toolbar.tsx": (
"import { Filter } from 'lucide-react';\n" # external, same name
"function onClick(x) { return x; }\n"
"export function build(sink, pool) {\n"
" sink.push(Filter);\n" # external -> must NOT bind to table.ts Filter
" pool.submit(onClick);\n" # local -> must still bind
"}\n"
),
})
indirect = _rels(r, "indirect_call")
assert (nid["build"], nid["Filter"]) not in indirect # no cross-file phantom
assert (nid["build"], nid["onClick"]) in indirect # real local reference preserved