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

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

2

3 from __future__ import annotations

4

5 _TS_DTD_CONTAINERS = {"object", "array"}

6

7 _TS_DTD_PAYLOADS = {"pair", "shorthand_property_identifier"}

8

9 _TS_DTD_MAP_NAMES = {"map", "filter", "reduce", "flatMap", "forEach"}

10

11 _TS_DTD_PUNCTUATION = {"[", "]", "{", "}", ",", "comment"}

12

13

14 def compute_dtd_ts(func_node) -> int:

15 """Compute Data Transformation Density for a TypeScript function node."""

16 for child in func_node.children:

17 if child.type == "statement_block":

18 return _dtd_walk_ts(child, depth=0, top_func=func_node)

19 if func_node.type == "arrow_function":

20 children = [c for c in func_node.children if c.type not in ("=>", "formal_parameters", "identifier")]

21 for child in children:

22 return _dtd_walk_ts(child, depth=0, top_func=func_node)

23 return 0

24

25

26 def _is_template_container(node) -> bool:

27 """Check if a template_string has template_substitution children (making it a container)."""

28 if node.type != "template_string":

29 return False

30 return any(c.type == "template_substitution" for c in node.children)

31

32

33 def _is_map_call_ts(node) -> bool:

34 """Check if a call_expression is a map/filter/reduce/flatMap/forEach call."""

35 if node.type != "call_expression":

36 return False

37 if not node.children:

38 return False

39 first = node.children[0]

40 if first.type == "member_expression":

41 prop = first.child_by_field_name("property")

42 if prop and prop.text.decode() in _TS_DTD_MAP_NAMES:

43 return True

44 return False

45

46

47 _TS_DTD_NESTED_FUNCS = {"function_declaration", "function_expression",

48 "method_definition", "generator_function_declaration"}

49

50 def _dtd_is_nested_func(child, top_func) -> bool:

51 """Check if a child is a nested function that should be skipped or handled specially."""

52 if child.type in _TS_DTD_NESTED_FUNCS and child is not top_func:

53 return True

54 if child.type == "arrow_function" and child is not top_func:

55 return True

56 return False

57

58

59 def _dtd_increases_depth(child) -> bool:

60 """Check if a child node should recurse with depth + 1."""

61 if child.type in _TS_DTD_CONTAINERS:

62 return True

63 if _is_template_container(child):

64 return True

65 if _is_map_call_ts(child):

66 return True

67 return False

68

69

70 def _dtd_is_payload(child, node) -> bool:

71 """Check if a child is a payload node that scores at current depth."""

72 if child.type in _TS_DTD_PAYLOADS:

73 return True

74 if child.type == "template_substitution" and node.type == "template_string":

75 return True

76 return False

77

78

79 def _dtd_walk_ts(node, depth: int, top_func) -> int:

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

81 total = 0

82 is_array = node.type == "array"

83 for child in node.children:

84 if _dtd_is_nested_func(child, top_func):

85 if child.type == "arrow_function" and not (child.parent and child.parent.type == "variable_declarator"):

86 total += _dtd_walk_ts(child, depth, top_func)

87 continue

88

89 is_element = is_array and child.type not in _TS_DTD_PUNCTUATION

90 if is_element:

91 total += depth

92

93 if _dtd_increases_depth(child):

94 total += _dtd_walk_ts(child, depth + 1, top_func)

95 elif _dtd_is_payload(child, node):

96 if not is_element:

97 total += depth

98 total += _dtd_walk_ts(child, depth, top_func)

99 else:

100 total += _dtd_walk_ts(child, depth, top_func)

101

102 return total