Skip to content

Commit 0967ee1

Browse files
committed
feat(schema): re-identify call_graph endpoints onto can:// ids
1 parent 38ab63c commit 0967ee1

3 files changed

Lines changed: 31 additions & 1 deletion

File tree

codeanalyzer/core.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
from codeanalyzer.schema.assign_ids import assign_ids
2222
from codeanalyzer.schema.l1_body import populate_l1_body
2323
from codeanalyzer.schema.l2_callees import backfill_callees
24+
from codeanalyzer.schema.call_graph_ids import reidentify_call_graph
2425
from codeanalyzer.schema.py_schema import PyCallEdge
2526
from codeanalyzer.semantic_analysis.call_graph import (
2627
filter_external_edges,
@@ -465,6 +466,7 @@ def analyze(self) -> Analysis:
465466
populate_l1_body(app)
466467
if self.analysis_level >= 2:
467468
backfill_callees(app, sig_to_id)
469+
reidentify_call_graph(app, sig_to_id)
468470

469471
# L3/L4 dataflow emission rebuilt on the v2 tree in Stage 3+
470472

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
"""Re-identify call-graph edge endpoints onto canonical can:// ids so the JSON
2+
call_graph agrees with the Neo4j PY_CALLS projection. Declared endpoints map
3+
through sig_to_id; external/library endpoints keep their dotted signature
4+
(they have no can:// id)."""
5+
from __future__ import annotations
6+
from codeanalyzer.schema.py_schema import PyApplication
7+
8+
9+
def reidentify_call_graph(app: PyApplication, sig_to_id: dict) -> None:
10+
for edge in app.call_graph or []:
11+
edge.source = sig_to_id.get(edge.source, edge.source)
12+
edge.target = sig_to_id.get(edge.target, edge.target)

test/test_v2_l2.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from codeanalyzer.schema.l2_callees import backfill_callees
2+
from codeanalyzer.schema.call_graph_ids import reidentify_call_graph
23
from codeanalyzer.schema.py_schema import (
3-
PyApplication, PyModule, PyCallable, PyCallsite, BodyNode,
4+
PyApplication, PyModule, PyCallable, PyCallsite, BodyNode, PyCallEdge,
45
)
56

67
def _app_with_one_call(callee_sig):
@@ -25,3 +26,18 @@ def test_unresolved_callsite_leaves_callee_absent():
2526
app, fn = _app_with_one_call(None)
2627
backfill_callees(app, {})
2728
assert fn.body["2:4"].callee is None
29+
30+
def test_call_graph_endpoints_reidentified():
31+
edge = PyCallEdge(source="m.f", target="m.g")
32+
app = PyApplication(symbol_table={}, call_graph=[edge])
33+
reidentify_call_graph(app, {"m.f": "can://python/app/m.py/f()",
34+
"m.g": "can://python/app/m.py/g()"})
35+
assert app.call_graph[0].source == "can://python/app/m.py/f()"
36+
assert app.call_graph[0].target == "can://python/app/m.py/g()"
37+
38+
def test_call_graph_external_target_unchanged():
39+
edge = PyCallEdge(source="m.f", target="requests.get")
40+
app = PyApplication(symbol_table={}, call_graph=[edge])
41+
reidentify_call_graph(app, {"m.f": "can://python/app/m.py/f()"})
42+
assert app.call_graph[0].source == "can://python/app/m.py/f()"
43+
assert app.call_graph[0].target == "requests.get"

0 commit comments

Comments
 (0)