Skip to content

Commit a400f4f

Browse files
committed
feat(rules): add service/container kind fields and rename node kind to symbol_kind
Support `node.symbol_kind`, `node.kind` (container kind), and `node.service` in node field extraction; add `from/to.service` and `from/to.kind` for edge fields; update docs and tests accordingly.
1 parent 89c53b5 commit a400f4f

5 files changed

Lines changed: 48 additions & 8 deletions

File tree

pacta/rules/ast.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,13 +134,16 @@ class FieldAst(ExprAst):
134134
A field reference.
135135
136136
Node targets:
137-
- "node.kind", "node.path", "node.name", "node.layer",
137+
- "node.symbol_kind", "node.kind", "node.service",
138+
"node.path", "node.name", "node.layer",
138139
"node.context", "node.container", "node.tags", "node.fqname"
139140
140141
Dependency targets:
141142
- "from.layer", "to.layer"
142143
- "from.context", "to.context"
143144
- "from.container", "to.container"
145+
- "from.service", "to.service"
146+
- "from.kind", "to.kind"
144147
- "from.fqname", "to.fqname"
145148
- "dep.type"
146149
- "loc.file" (optional if you want)

pacta/rules/builtins.py

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,10 @@ def get_node_field(node: IRNode, path: str) -> Any:
9090
Runtime field extraction for node predicates.
9191
9292
Supported paths:
93-
- kind, path, name, layer, context, container, tags
93+
- symbol_kind (SymbolKind — file, module, class, etc.)
94+
- kind (container kind — service, module, library)
95+
- service (top-level container ancestor)
96+
- path, name, layer, context, container, tags
9497
- fqname (node.id.fqname)
9598
- id (str(node.id))
9699
- code_root (node.id.code_root)
@@ -100,8 +103,12 @@ def get_node_field(node: IRNode, path: str) -> Any:
100103
if p.startswith("node."):
101104
p = p[len("node.") :]
102105

103-
if p == "kind":
106+
if p == "symbol_kind":
104107
return node.kind.value
108+
if p == "kind":
109+
return node.container_kind
110+
if p == "service":
111+
return node.service
105112
if p == "path":
106113
return node.path
107114
if p == "name":
@@ -135,6 +142,8 @@ def get_edge_field(edge: IREdge, path: str) -> Any:
135142
- from.layer / to.layer
136143
- from.context / to.context
137144
- from.container / to.container
145+
- from.service / to.service
146+
- from.kind / to.kind (container kind)
138147
- from.fqname / to.fqname
139148
- from.id / to.id
140149
- dep.type
@@ -157,6 +166,16 @@ def get_edge_field(edge: IREdge, path: str) -> Any:
157166
if p == "to.container":
158167
return edge.dst_container
159168

169+
if p == "from.service":
170+
return edge.src_service
171+
if p == "to.service":
172+
return edge.dst_service
173+
174+
if p == "from.kind":
175+
return edge.src_container_kind
176+
if p == "to.kind":
177+
return edge.dst_container_kind
178+
160179
if p == "from.fqname":
161180
return edge.src.fqname
162181
if p == "to.fqname":

pacta/rules/compiler.py

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,9 @@ class _CompiledWhen:
3434
def _get_node_field(n: IRNode, field: str) -> Any:
3535
"""
3636
Supported node fields (frontends may use either node.<x> or <x>):
37-
- kind (SymbolKind.value)
37+
- symbol_kind (SymbolKind.value — file, module, class, etc.)
38+
- kind (container kind — service, module, library)
39+
- service (top-level container ancestor)
3840
- path
3941
- name
4042
- layer
@@ -50,8 +52,12 @@ def _get_node_field(n: IRNode, field: str) -> Any:
5052
if f.startswith("node."):
5153
f = f[len("node.") :]
5254

53-
if f == "kind":
55+
if f == "symbol_kind":
5456
return n.kind.value
57+
if f == "kind":
58+
return n.container_kind
59+
if f == "service":
60+
return n.service
5561
if f == "path":
5662
return n.path
5763
if f == "name":
@@ -82,6 +88,8 @@ def _get_edge_field(e: IREdge, field: str) -> Any:
8288
- from.layer / to.layer
8389
- from.context / to.context
8490
- from.container / to.container
91+
- from.service / to.service
92+
- from.kind / to.kind (container kind)
8593
- from.fqname / to.fqname
8694
- from.id / to.id (full canonical id string)
8795
- dep.type (DepType.value)
@@ -104,6 +112,16 @@ def _get_edge_field(e: IREdge, field: str) -> Any:
104112
if f == "to.container":
105113
return e.dst_container
106114

115+
if f == "from.service":
116+
return e.src_service
117+
if f == "to.service":
118+
return e.dst_service
119+
120+
if f == "from.kind":
121+
return e.src_container_kind
122+
if f == "to.kind":
123+
return e.dst_container_kind
124+
107125
if f == "from.fqname":
108126
return e.src.fqname
109127
if f == "to.fqname":

tests/rules/test_builtins.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -171,8 +171,8 @@ def test_get_node_field_supported_fields():
171171
tags=("internal", "critical"),
172172
)
173173

174-
assert get_node_field(n, "kind") == SymbolKind.CLASS.value
175-
assert get_node_field(n, "node.kind") == SymbolKind.CLASS.value
174+
assert get_node_field(n, "symbol_kind") == SymbolKind.CLASS.value
175+
assert get_node_field(n, "node.symbol_kind") == SymbolKind.CLASS.value
176176
assert get_node_field(n, "path") == "services/billing/domain/invoice.py"
177177
assert get_node_field(n, "name") == "Invoice"
178178
assert get_node_field(n, "container") == "billing"

tests/rules/test_compiler.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ def test_compile_in_operator_with_list_literal():
170170
action="forbid",
171171
when=NodeWhenAst(
172172
predicate=CompareAst(
173-
left=FieldAst(path="kind"),
173+
left=FieldAst(path="symbol_kind"),
174174
op="in",
175175
right=lit_list(["module", "package"]),
176176
)

0 commit comments

Comments
 (0)