Skip to content

Commit 239b621

Browse files
committed
feat(schema): stamp can:// ids across the symbol-table tree
1 parent ec8a806 commit 239b621

3 files changed

Lines changed: 55 additions & 0 deletions

File tree

codeanalyzer/core.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
model_dump_json,
1818
model_validate_json,
1919
)
20+
from codeanalyzer.schema.assign_ids import assign_ids
2021
from codeanalyzer.schema.py_schema import PyCallEdge
2122
from codeanalyzer.semantic_analysis.call_graph import (
2223
filter_external_edges,
@@ -455,6 +456,8 @@ def analyze(self) -> PyApplication:
455456
.build()
456457
)
457458

459+
assign_ids(app, self.options.app_name or self.project_dir.name)
460+
458461
# L3/L4 dataflow emission rebuilt on the v2 tree in Stage 3+
459462

460463
# Save to cache

codeanalyzer/schema/assign_ids.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
"""Walk the symbol-table tree and stamp every node with its can:// id."""
2+
from __future__ import annotations
3+
from typing import Dict
4+
from codeanalyzer.schema import ids
5+
from codeanalyzer.schema.py_schema import PyApplication, PyModule, PyClass, PyCallable
6+
7+
8+
def assign_ids(app: PyApplication, app_name: str) -> Dict[str, str]:
9+
"""Sets `.id` on the app + every module/class/callable. Returns a
10+
`signature -> can://id` map for later stages (identity layer input)."""
11+
app.id = ids.application_id(app_name); app.kind = "application"
12+
sig_to_id: Dict[str, str] = {}
13+
14+
def do_callable(parent_id: str, c: PyCallable) -> None:
15+
seg = ids.callable_sig_segment(c.name, [p.name for p in c.parameters])
16+
c.id = ids.child_id(parent_id, seg)
17+
sig_to_id[c.signature] = c.id
18+
for ic in (c.inner_callables or {}).values():
19+
do_callable(c.id, ic)
20+
for icl in (c.inner_classes or {}).values():
21+
do_class(c.id, icl)
22+
23+
def do_class(parent_id: str, cl: PyClass) -> None:
24+
cl.id = ids.child_id(parent_id, cl.name); cl.kind = "class"
25+
sig_to_id[cl.signature] = cl.id
26+
for m in (cl.methods or {}).values():
27+
do_callable(cl.id, m)
28+
for ic in (cl.inner_classes or {}).values():
29+
do_class(cl.id, ic)
30+
31+
for file_key, mod in app.symbol_table.items():
32+
mod.id = ids.module_id(app_name, file_key); mod.kind = "module"
33+
for fn in (mod.functions or {}).values():
34+
do_callable(mod.id, fn)
35+
for cl in (mod.classes or {}).values():
36+
do_class(mod.id, cl)
37+
return sig_to_id

test/test_v2_conformance.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
from codeanalyzer.schema.assign_ids import assign_ids
2+
from codeanalyzer.schema.py_schema import PyApplication, PyModule, PyClass, PyCallable
3+
4+
5+
def test_ids_assigned_down_the_tree():
6+
fn = PyCallable(name="hash", path="m.py", signature="m.Hasher.hash",
7+
parameters=[])
8+
cl = PyClass(name="Hasher", signature="m.Hasher", methods={"hash": fn})
9+
mod = PyModule(file_path="pkg/m.py", module_name="m", classes={"m.Hasher": cl})
10+
app = PyApplication(symbol_table={"pkg/m.py": mod})
11+
assign_ids(app, "myapp")
12+
assert app.id == "can://python/myapp"
13+
assert mod.id == "can://python/myapp/pkg/m.py"
14+
assert cl.id == "can://python/myapp/pkg/m.py/Hasher"
15+
assert fn.id == "can://python/myapp/pkg/m.py/Hasher/hash()"

0 commit comments

Comments
 (0)