Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions graphify/extractors/json_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,10 +90,10 @@ def extract_json(path: Path) -> dict:
"optionalDependencies", "bundleDependencies", "bundledDependencies",
})

def add_node(nid: str, label: str, line: int) -> None:
def add_node(nid: str, label: str, line: int, file_type: str = "code") -> None:
if nid and nid not in seen_ids:
seen_ids.add(nid)
nodes.append({"id": nid, "label": label, "file_type": "code",
nodes.append({"id": nid, "label": label, "file_type": file_type,
"source_file": str_path, "source_location": f"L{line}"})

def add_edge(src: str, tgt: str, relation: str, line: int,
Expand Down Expand Up @@ -165,6 +165,7 @@ def walk_object(obj_node, parent_nid: str, parent_key: str | None,
if ref:
ref_nid = _make_id("ref", ref)
if ref_nid:
add_node(ref_nid, ref, line, file_type="concept")
add_edge(key_nid, ref_nid, "extends", line, context="import")

elif val.type == "string":
Expand All @@ -175,6 +176,7 @@ def walk_object(obj_node, parent_nid: str, parent_key: str | None,
# Namespace external refs to avoid ID collision with file nodes (J-4)
ref_nid = _make_id("ref", val_text)
if ref_nid:
add_node(ref_nid, val_text, line, file_type="concept")
add_edge(file_nid, ref_nid, "extends", line, context="import")

elif key == "$ref" and val_text:
Expand All @@ -186,6 +188,7 @@ def walk_object(obj_node, parent_nid: str, parent_key: str | None,
elif parent_key in _DEP_KEYS and val_text:
dep_nid = _make_id(key)
if dep_nid:
add_node(dep_nid, key, line, file_type="concept")
add_edge(key_nid, dep_nid, "imports", line, context="import")

# Entry: find root document → object
Expand Down
50 changes: 50 additions & 0 deletions tests/test_extract.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import pytest

from graphify.build import build_from_json
from graphify.extract import extract_python, extract, collect_files, _make_id, extract_bash, extract_json, _DISPATCH

FIXTURES = Path(__file__).parent / "fixtures"
Expand Down Expand Up @@ -1468,6 +1469,55 @@ def test_extract_json_extends_resolved():
assert extends_edges[0].get("context") == "import"


def test_extract_json_import_and_extends_targets_are_real_nodes(tmp_path):
package_json = tmp_path / "package.json"
package_json.write_text(json.dumps({
"name": "demo",
"dependencies": {"left-pad": "^1.3.0"},
"devDependencies": {"bats": "^1.11.0"},
}))
tsconfig = tmp_path / "tsconfig.json"
tsconfig.write_text(json.dumps({
"extends": "./tsconfig.base.json",
"compilerOptions": {"strict": True},
}))

results = [extract_json(package_json), extract_json(tsconfig)]
combined = {
"nodes": [node for result in results for node in result["nodes"]],
"edges": [edge for result in results for edge in result["edges"]],
}
node_ids = {node["id"] for node in combined["nodes"]}
dangling = [
edge for edge in combined["edges"]
if edge["source"] not in node_ids or edge["target"] not in node_ids
]
assert dangling == []
assert {"left-pad", "bats", "./tsconfig.base.json"} <= {
node["label"] for node in combined["nodes"] if node["file_type"] == "concept"
}

extracted = extract([package_json, tsconfig], cache_root=tmp_path, parallel=False)
graph = build_from_json(extracted, directed=True)
import_targets = {
graph.nodes[data["_tgt"]]["label"]
for _, _, data in graph.edges(data=True)
if data.get("relation") == "imports"
}
extends_targets = {
graph.nodes[data["_tgt"]]["label"]
for _, _, data in graph.edges(data=True)
if data.get("relation") == "extends"
}
self_loops = [
data for _, _, data in graph.edges(data=True)
if data.get("relation") in {"imports", "extends"} and data["_src"] == data["_tgt"]
]
assert self_loops == []
assert {"left-pad", "bats"} <= import_targets
assert extends_targets == {"./tsconfig.base.json"}


def test_extract_json_large_file_skipped(tmp_path):
big = tmp_path / "big.json"
# Write a JSON file just over 1 MiB
Expand Down
Loading