test(extract): cover per-symbol fan-out and module-top-level drop for #2652

Adds two regression tests on top of #2684: each referencing symbol gets its
own INFERRED uses edge (guarding against the old every-class fan-out and
against source collapse), and a true module-top-level reference emits no edge
(the deliberate drop). 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-14 14:18:54 +01:00
co-authored by Claude Opus 4.8
parent c08d9afa93
commit bd1ede9584
2 changed files with 39 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.42 (unreleased)
- Fix: a cross-file INFERRED `uses` edge now binds to the symbol whose body actually references the imported name (a module-level function is a valid source; a co-located class that never touches the import gets no edge), instead of fanning out from the import line to every class in the importing file (#2652, thanks @ousamabenyounes). A reference at module top level, with no enclosing symbol, emits no edge.
- Feature: OCaml `.ml`/`.mli` extraction via tree-sitter-ocaml (optional `[ocaml]` extra). Extracts modules, top-level and module-level values/functions, types and their variant constructors, `open` imports, and function calls; qualified calls (`Geo.area`) resolve to the value, and cross-file `open`/call targets collapse onto the unique real definition via the corpus stub rewire.
- Fix: a JS/TS `for...of` / `for...in` loop binding is now shadowed, so passing it as a call argument no longer fabricates an `indirect_call` edge to an unrelated same-named callable (#2685, thanks @ousamabenyounes); completes the loop/closure/catch shadow family (#2568/#2569/#2517).
- Fix: graph provenance (`built_at_commit`) is stamped from the analysed repository rather than the shell's working directory, so `graphify extract` run from elsewhere records the target's commit, not the caller's (#2534 family; #2699, thanks @C0KERNEL).
+38
View File
@@ -3494,3 +3494,41 @@ def test_inferred_uses_edge_follows_an_import_alias(tmp_path):
result = extract([tmp_path / "api.py", tmp_path / "helpers.py"], cache_root=tmp_path)
assert ("api_handler", "helpers_helper") in _inferred_uses(result)
def test_inferred_uses_edge_emitted_once_per_referencing_symbol(tmp_path):
"""Each symbol that references the import gets its own edge, and only those
symbols do — guards against the old fan-out (every class in the file) and
against collapsing distinct sources into one (#2652)."""
(tmp_path / "helpers.py").write_text("class Helper:\n pass\n", encoding="utf-8")
(tmp_path / "api.py").write_text(
"from helpers import Helper\n\n\n"
"def a(x):\n return Helper()\n\n\n"
"def b(x):\n return Helper()\n\n\n"
"def c(x):\n return x\n", # references nothing -> no edge
encoding="utf-8",
)
result = extract([tmp_path / "api.py", tmp_path / "helpers.py"], cache_root=tmp_path)
uses = _inferred_uses(result)
assert ("api_a", "helpers_helper") in uses
assert ("api_b", "helpers_helper") in uses
assert ("api_c", "helpers_helper") not in uses
def test_inferred_uses_edge_dropped_for_module_top_level_reference(tmp_path):
"""A reference at true module top level has no enclosing symbol to anchor on,
so no INFERRED `uses` edge is emitted (rather than falling back to the file
node) — the deliberate drop documented for #2652."""
(tmp_path / "helpers.py").write_text("class Helper:\n pass\n", encoding="utf-8")
(tmp_path / "api.py").write_text(
"from helpers import Helper\n\n\n"
"SENTINEL = Helper()\n", # top-level, outside any def/class
encoding="utf-8",
)
result = extract([tmp_path / "api.py", tmp_path / "helpers.py"], cache_root=tmp_path)
uses = _inferred_uses(result)
assert not any(tgt == "helpers_helper" for _, tgt in uses)