Coverage for src / renaissance / utils / ast_utils.py: 94%

53 statements  

« prev     ^ index     » next       coverage.py v7.13.4, created at 2026-09-09 14:04 +0000

1from collections import deque 

2 

3from renaissance.integrations import MATCH_ALL, MATCH_ONE 

4 

5 

6def replace_dollar(text: str) -> str: 

7 """Replace dollar-sign placeholders in the given text with their corresponding match symbols. 

8 

9 Two literal dollar-sign characters (`$$`) are replaced with MATCH_ALL; a single literal dollar-sign 

10 character (`$`) is replaced with MATCH_ONE. 

11 """ 

12 return text.replace("$$", MATCH_ALL).replace("$", MATCH_ONE) 

13 

14 

15def use_dollar(text: str) -> str: 

16 """Replace match symbols in the given text with their corresponding dollar-sign placeholders. 

17 

18 MATCH_ALL is replaced with two literal dollar-sign characters (`$$`); MATCH_ONE is replaced with a 

19 single literal dollar-sign character (`$`). 

20 """ 

21 return text.replace(MATCH_ALL, "$$").replace(MATCH_ONE, "$") 

22 

23 

24def detect_placeholder(signature: str, original_node_type: str) -> tuple[bool, str, str]: 

25 """Detect if the given signature represents a placeholder symbol. 

26 

27 Returns: 

28 (is_placeholder, coerced_node_type, placeholder_name_or_signature) 

29 

30 """ 

31 if not signature: 

32 return False, original_node_type, "" 

33 if signature.startswith((MATCH_ALL, "$$")) and " " not in signature and "(" not in signature: 

34 # legacy compatibility 

35 return True, MATCH_ALL, signature 

36 if signature.startswith((MATCH_ONE, "$")) and " " not in signature and "(" not in signature: 

37 return True, MATCH_ONE, signature 

38 return False, original_node_type, "-" 

39 

40 

41# duplicate of ast node process 

42def traverse(node): 

43 todo = deque([node]) 

44 while todo: 

45 node = todo.popleft() 

46 if hasattr(node, "children"): 

47 todo.extend(node.children) 

48 yield node 

49 

50 

51def process_node(node, action) -> None: 

52 action(node) 

53 if node.children: 

54 for child in node.children: 

55 process_node(child, action) 

56 

57 

58def preceding_sibling(node): 

59 parent = node.parent 

60 if not parent: 

61 return None 

62 siblings = parent.children 

63 preceding_index = siblings.index(node) - 1 

64 return siblings[preceding_index] if preceding_index >= 0 else None 

65 

66 

67def next_sibling(self): 

68 parent = self.parent 

69 if not parent: 

70 return None 

71 siblings = parent.children 

72 next_index = siblings.index(self) + 1 

73 return siblings[next_index] if next_index < len(siblings) else None 

74 

75 

76def match_props(mine, other, irrelevant_props) -> bool: 

77 all_keys = (mine.keys() | other.keys()) - irrelevant_props 

78 return all(mine.get(n) == other.get(n) for n in all_keys) 

79 

80 

81def match_children(mine, other, irrelevant_kinds) -> bool: 

82 if mine is None or other is None: 

83 return mine == other 

84 return all((i < len(mine) and mine[i] == child) or child.ast_type.__name__ in irrelevant_kinds for i, child in enumerate(other)) 

85 

86 

87def format_node(node) -> str: 

88 raw_lines = node.signature.splitlines() 

89 properties_text = "" if not node.show_props else node.properties 

90 prefix = " " if len(raw_lines) < 2 else f"\n {node.indent}" 

91 formatted_lines = [f"{prefix}|{line}|" for line in raw_lines] 

92 return ( 

93 f"{node.indent}({node.ast_type.__name__}, {node.name}, " 

94 f"{node.filename}[{node.offset}:{node.offset + node.length}])" 

95 f"{properties_text}:{''.join(formatted_lines)}\n" 

96 )