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
« prev ^ index » next coverage.py v7.13.4, created at 2026-09-09 14:04 +0000
1from collections import deque
3from renaissance.integrations import MATCH_ALL, MATCH_ONE
6def replace_dollar(text: str) -> str:
7 """Replace dollar-sign placeholders in the given text with their corresponding match symbols.
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)
15def use_dollar(text: str) -> str:
16 """Replace match symbols in the given text with their corresponding dollar-sign placeholders.
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, "$")
24def detect_placeholder(signature: str, original_node_type: str) -> tuple[bool, str, str]:
25 """Detect if the given signature represents a placeholder symbol.
27 Returns:
28 (is_placeholder, coerced_node_type, placeholder_name_or_signature)
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, "-"
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
51def process_node(node, action) -> None:
52 action(node)
53 if node.children:
54 for child in node.children:
55 process_node(child, action)
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
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
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)
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))
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 )