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

1"""Data Transformation Density (DTD) for Python.""" 

2 

3from __future__ import annotations 

4 

5_PY_DTD_CONTAINERS = { 

6 "dictionary", "list", "set", 

7 "list_comprehension", "dictionary_comprehension", 

8 "set_comprehension", "generator_expression", 

9} 

10 

11_PY_DTD_PAYLOADS = {"pair", "keyword_argument"} 

12 

13_PY_DTD_PUNCTUATION = {"[", "]", "{", "}", ",", "comment"} 

14 

15_PY_DTD_MAP_NAMES = {"map", "filter", "reduce"} 

16 

17 

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 

24 

25 

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 

40 

41 

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 

49 

50 is_element = is_list_or_set and child.type not in _PY_DTD_PUNCTUATION 

51 if is_element: 

52 total += depth 

53 

54 if child.type in _PY_DTD_CONTAINERS: 

55 total += _dtd_walk_py(child, depth + 1, top_func) 

56 continue 

57 

58 if _is_map_call_py(child): 

59 total += _dtd_walk_py(child, depth + 1, top_func) 

60 continue 

61 

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 

67 

68 total += _dtd_walk_py(child, depth, top_func) 

69 

70 return total