test(js): cover dynamic-require skip and no-double-count for lazy requires (#2700)

Adds the two invariants the deep-dive flagged: require(variable) must not
fabricate an edge, and a module-scope require still yields exactly one
imports_from edge (guards the module-vs-body pass partition against future
double-counting). 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:46:04 +01:00
co-authored by Claude Opus 4.8
parent 259bb6acfb
commit e5d662eb63
2 changed files with 33 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 lazy CommonJS `require(...)` inside a function body (the idiom for breaking circular dependencies) now emits the same `imports_from`/`imports` dependency edges as a top-level require, attributed to the enclosing function, instead of being silently dropped; a dynamic `require(variable)` is still skipped (#2700, thanks @rajanpanth).
- 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`.
+32
View File
@@ -801,6 +801,38 @@ def test_extract_js_function_scoped_require_emits_import_edge(tmp_path):
assert lazy_edges[0]["confidence"] == "EXTRACTED"
def test_extract_js_dynamic_require_variable_is_not_fabricated(tmp_path):
"""A lazy `require(someVar)` has no static string target, so the body pass
must skip it rather than fabricate an edge to a guessed path (#2700)."""
caller = tmp_path / "dyn.js"
caller.write_text(
"function load(name) {\n"
" const mod = require(name);\n"
" return mod;\n"
"}\n",
encoding="utf-8",
)
result = extract([caller], cache_root=tmp_path, root=tmp_path, parallel=False)
assert not [e for e in result["edges"] if e["relation"] in ("imports_from", "imports")]
def test_extract_js_module_scope_require_still_single_edge(tmp_path):
"""No-double-count regression: the module-level and body require passes must
never both emit for the same require — a top-level require stays exactly one
imports_from edge (#2700)."""
target = tmp_path / "target.js"
target.write_text("exports.helper = () => 42;\n", encoding="utf-8")
caller = tmp_path / "top.js"
caller.write_text("const { helper } = require('./target');\n", encoding="utf-8")
result = extract([caller, target], cache_root=tmp_path, root=tmp_path, parallel=False)
lazy_edges = [
e for e in result["edges"]
if e["relation"] == "imports_from" and "target" in e["target"]
]
assert len(lazy_edges) == 1
def test_extract_js_arrow_function_still_extracted():
"""Regression: arrow functions in lexical_declaration must still produce nodes."""
from graphify.extract import extract_js