Coverage for metrics/dtd/python.py: 93%

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

2

3 from __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

18 def 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

26 def _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

42 def _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