Coverage for metrics/dtd/go.py: 85%

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

2

3 from __future__ import annotations

4

5 from parsers.go import is_named_func_literal

6

7 _GO_DTD_MAP_NAMES = {"Map", "Filter", "Reduce"}

8

9

10 def compute_dtd_go(func_node) -> int:

11 """Compute Data Transformation Density for a Go function node."""

12 body = func_node.child_by_field_name("body")

13 if body and body.type == "block":

14 return _dtd_walk_go(body, depth=0, top_func=func_node)

15 for child in func_node.children:

16 if child.type == "block":

17 return _dtd_walk_go(child, depth=0, top_func=func_node)

18 return 0

19

20

21 def _is_map_call_go(node) -> bool:

22 """Check if a call_expression is a Map/Filter/Reduce call."""

23 if node.type != "call_expression":

24 return False

25 func = node.child_by_field_name("function")

26 if func and func.type == "selector_expression":

27 field = func.child_by_field_name("field")

28 if field and field.text.decode() in _GO_DTD_MAP_NAMES:

29 return True

30 return False

31

32

33 _GO_DTD_NESTED_FUNCS = {"function_declaration", "method_declaration"}

34 _GO_DTD_LITERAL_ELEMENTS = {"keyed_element", "literal_element"}

35

36

37 def _dtd_walk_go(node, depth: int, top_func) -> int:

38 """Walk AST and accumulate DTD score."""

39 total = 0

40 is_literal = node.type == "literal_value"

41 for child in node.children:

42 if child.type in _GO_DTD_NESTED_FUNCS and child is not top_func:

43 continue

44 if child.type == "func_literal" and child is not top_func:

45 if not is_named_func_literal(child):

46 total += _dtd_walk_go(child, depth, top_func)

47 continue

48

49 if child.type == "literal_value" or _is_map_call_go(child):

50 total += _dtd_walk_go(child, depth + 1, top_func)

51 continue

52

53 if is_literal and child.type in _GO_DTD_LITERAL_ELEMENTS:

54 total += depth

55

56 total += _dtd_walk_go(child, depth, top_func)

57

58 return total