Index .psm1, anchor Swift import targets, dedupe no-cluster edges

- #1315: add .psm1 to CODE_EXTENSIONS + _DISPATCH so PowerShell modules are indexed
- #1327: synthesize a module node for Swift import targets (new LanguageConfig
  flag synthesize_import_module_nodes) so imports edges survive build.py pruning;
  strengthen the Swift dangling-edge test to also assert edge targets
- #1317: dedupe parallel edges by (source,target,relation) in the --no-cluster
  and incremental update write paths so edge counts are deterministic and
  `update` is idempotent

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Safi
2026-06-16 02:28:16 +01:00
co-authored by Claude Opus 4.8
parent 09da5294e4
commit adb52a1ea0
8 changed files with 131 additions and 4 deletions
+27 -1
View File
@@ -2,10 +2,36 @@ import json
from pathlib import Path
import networkx as nx
from networkx.readwrite import json_graph
from graphify.build import build_from_json, build, build_merge, edge_data, edge_datas
from graphify.build import build_from_json, build, build_merge, edge_data, edge_datas, dedupe_edges
FIXTURES = Path(__file__).parent / "fixtures"
def test_dedupe_edges_collapses_exact_parallels():
# #1317: --no-cluster / incremental update concatenate edge lists raw.
edges = [
{"source": "a", "target": "b", "relation": "calls", "source_location": "L1"},
{"source": "a", "target": "b", "relation": "calls", "source_location": "L9"}, # dup
{"source": "a", "target": "b", "relation": "imports"}, # different relation: kept
{"source": "b", "target": "c", "relation": "calls"},
]
out = dedupe_edges(edges)
keys = [(e["source"], e["target"], e["relation"]) for e in out]
assert keys == [("a", "b", "calls"), ("a", "b", "imports"), ("b", "c", "calls")]
# first occurrence wins (keeps L1, not L9)
assert out[0]["source_location"] == "L1"
def test_dedupe_edges_is_idempotent():
edges = [
{"source": "a", "target": "b", "relation": "calls"},
{"source": "a", "target": "b", "relation": "calls"},
]
once = dedupe_edges(edges)
twice = dedupe_edges(once + edges) # simulate a second `update` re-concatenating
assert len(once) == 1
assert len(twice) == 1
def load_extraction():
return json.loads((FIXTURES / "extraction.json").read_text())
+4
View File
@@ -11,6 +11,10 @@ def test_classify_python():
def test_classify_typescript():
assert classify_file(Path("bar.ts")) == FileType.CODE
def test_classify_powershell_module():
# #1315: .psm1 modules were never indexed (CODE_EXTENSIONS gap).
assert classify_file(Path("Utils.psm1")) == FileType.CODE
def test_classify_markdown():
assert classify_file(Path("README.md")) == FileType.DOCUMENT
+36
View File
@@ -596,6 +596,28 @@ def test_swift_no_dangling_edges():
node_ids = {n["id"] for n in r["nodes"]}
for e in r["edges"]:
assert e["source"] in node_ids
# #1327: targets must resolve to a node too, else build.py prunes the edge.
assert e["target"] in node_ids, f"dangling target {e['target']} ({e['relation']})"
def test_swift_imports_survive_build():
# #1327: `import Foundation` / `import UIKit` previously emitted edges to bare
# module ids with no backing node, so build.py dropped 100% of Swift imports.
from graphify.build import build_from_json
r = extract_swift(FIXTURES / "sample.swift")
import_edges = [e for e in r["edges"] if e["relation"] == "imports"]
assert import_edges, "extractor should emit Swift import edges"
node_ids = {n["id"] for n in r["nodes"]}
for e in import_edges:
assert e["target"] in node_ids # synthesized module node exists
# No private bookkeeping key should leak into output edges.
assert all("_import_label" not in e for e in r["edges"])
# Edges must survive the build (which prunes edges with unknown endpoints).
G = build_from_json(r)
surviving = [
(u, v) for u, v, d in G.edges(data=True) if d.get("relation") == "imports"
]
assert surviving, "Swift import edges must survive build_from_json (#1327)"
def test_swift_finds_actor():
r = extract_swift(FIXTURES / "sample.swift")
@@ -1005,6 +1027,20 @@ def test_powershell_no_error():
assert "error" not in r
def test_powershell_psm1_dispatched_and_extracted(tmp_path):
# #1315: .psm1 modules were never indexed — no dispatch entry, no CODE_EXTENSIONS.
from graphify.extract import _get_extractor
mod = tmp_path / "Utils.psm1"
mod.write_text(
"function Get-Greeting { param([string]$Name) return \"Hi $Name\" }\n",
encoding="utf-8",
)
assert _get_extractor(mod) is extract_powershell
r = extract_powershell(mod)
assert "error" not in r
assert any("Get-Greeting" in n["label"] for n in r["nodes"])
def test_powershell_finds_class_and_method():
r = extract_powershell(FIXTURES / "sample.ps1")
labels = [n["label"] for n in r["nodes"]]