diff --git a/graphify/extractors/commonlisp.py b/graphify/extractors/commonlisp.py index f43e72c01..2ed9d9fbd 100644 --- a/graphify/extractors/commonlisp.py +++ b/graphify/extractors/commonlisp.py @@ -4,7 +4,7 @@ import warnings from pathlib import Path -from graphify.extractors.base import _make_id +from graphify.extractors.base import _file_stem, _make_id # Standard CL definer forms that introduce data/type/variable bindings @@ -84,7 +84,11 @@ def extract_commonlisp(path: Path) -> dict: except Exception as e: return {"nodes": [], "edges": [], "error": str(e)} - stem = path.stem + # Path-qualified, not the bare `path.stem`: same-named .lisp files in + # different directories must not collide (#1504). Pre-collapsed through + # `_make_id` because `_cl_id` would otherwise map the `/` separators to + # `_slash` via _CL_CHAR_MAP. + stem = _make_id(_file_stem(path)) str_path = str(path) nodes: list[dict] = [] edges: list[dict] = [] diff --git a/tests/test_languages.py b/tests/test_languages.py index 6fd3a45c5..0f5c079ec 100644 --- a/tests/test_languages.py +++ b/tests/test_languages.py @@ -3974,3 +3974,25 @@ def test_zig_enum_and_union_methods_are_extracted(tmp_path): for e in r["edges"] if e["relation"] == "calls" } assert (".area()", "helper()") in calls, "call from union method body dropped" + + +@_needs_commonlisp +def test_cl_ids_are_path_qualified_across_directories(tmp_path): + """Two same-named .lisp files in DIFFERENT directories must mint distinct + ids (#1504). The prefix was derived from the bare `path.stem`, so both + `a/sample.lisp` and `b/sample.lisp` minted `sample` / `sample_init`; when + they land in separate extract batches (what `graphify update` does) build() + merges them and one file's nodes are dropped.""" + a = tmp_path / "a" / "sample.lisp" + b = tmp_path / "b" / "sample.lisp" + for p in (a, b): + p.parent.mkdir(parents=True) + p.write_text("(defun init (x) (+ x 1))\n") + + ids_a = {n["id"] for n in extract_commonlisp(a)["nodes"]} + ids_b = {n["id"] for n in extract_commonlisp(b)["nodes"]} + + assert not (ids_a & ids_b), ( + f"same-named .lisp files in different dirs must not share ids, " + f"got overlap {sorted(ids_a & ids_b)}" + )