Coverage for metrics / dtd / python.py: 93%
45 statements
« prev ^ index » next coverage.py v7.13.3, created at 2026-02-08 15:04 -0800
« prev ^ index » next coverage.py v7.13.3, created at 2026-02-08 15:04 -0800
1"""Data Transformation Density (DTD) for Python."""
3from __future__ import annotations
5_PY_DTD_CONTAINERS = {
6 "dictionary", "list", "set",
7 "list_comprehension", "dictionary_comprehension",
8 "set_comprehension", "generator_expression",
9}
11_PY_DTD_PAYLOADS = {"pair", "keyword_argument"}
13_PY_DTD_PUNCTUATION = {"[", "]", "{", "}", ",", "comment"}
15_PY_DTD_MAP_NAMES = {"map", "filter", "reduce"}
18def compute_dtd_py(func_node) -> int:
19 """Compute Data Transformation Density for a Python function_definition node."""
20 for child in func_node.children:
21 if child.type == "block":
22 return _dtd_walk_py(child, depth=0, top_func=func_node)
23 return 0
26def _is_map_call_py(node) -> bool:
27 """Check if a call node is a map/filter/reduce call."""
28 if node.type != "call":
29 return False
30 if not node.children:
31 return False
32 first = node.children[0]
33 if first.type == "attribute":
34 for child in first.children:
35 if child.type == "identifier" and child.text.decode() in _PY_DTD_MAP_NAMES:
36 return True
37 if first.type == "identifier" and first.text.decode() in _PY_DTD_MAP_NAMES:
38 return True
39 return False
42def _dtd_walk_py(node, depth: int, top_func) -> int:
43 """Walk AST and accumulate DTD score."""
44 total = 0
45 is_list_or_set = node.type in ("list", "set")
46 for child in node.children:
47 if child.type == "function_definition" and child is not top_func:
48 continue
50 is_element = is_list_or_set and child.type not in _PY_DTD_PUNCTUATION
51 if is_element:
52 total += depth
54 if child.type in _PY_DTD_CONTAINERS:
55 total += _dtd_walk_py(child, depth + 1, top_func)
56 continue
58 if _is_map_call_py(child):
59 total += _dtd_walk_py(child, depth + 1, top_func)
60 continue
62 if child.type in _PY_DTD_PAYLOADS:
63 if not is_element:
64 total += depth
65 total += _dtd_walk_py(child, depth, top_func)
66 continue
68 total += _dtd_walk_py(child, depth, top_func)
70 return total