Coverage for src / renaissance / integrations / python / ast / factory.py: 98%

110 statements  

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

1import ast 

2import re 

3from collections.abc import Sequence 

4from pathlib import Path 

5 

6import tree_sitter_python 

7from libcst import SimpleStatementLine 

8 

9from renaissance.integrations import MATCH_ALL, MATCH_ONE 

10from renaissance.integrations.python.ast.ast_node import ASTExtension 

11from renaissance.integrations.python.ast.cst_node import PythonCstNode 

12from renaissance.integrations.python.ast.rst_node import PythonRstNode 

13from renaissance.integrations.tree_sitter.adapter import TreeSitterAdapter 

14from renaissance.integrations.tree_sitter.lst import LSTNode 

15from renaissance.integrations.types import Arg, DeclarationExpression, ExpressionStatement, MatchAll, MatchOne, Name, Type 

16from renaissance.syntax_tree.match_finder import AstProtocol, is_match 

17from renaissance.utils.ast_utils import replace_dollar, use_dollar 

18 

19_MATCH_ALL_RE = re.compile(r"^" + re.escape(MATCH_ALL) + r"\w+$") 

20_MATCH_ONE_RE = re.compile(r"^" + re.escape(MATCH_ONE) + r"\w+$") 

21 

22SHOW_NODE = False 

23 

24 

25class PythonPattern(AstProtocol): 

26 def __init__(self, node): 

27 self.node: PythonRstNode = node 

28 if type(node) is str: 

29 print(node) 

30 return 

31 self.ast_type: Type = self.derive_type(node) 

32 

33 self.properties: dict = node.properties 

34 self.children: list[PythonPattern] = [PythonPattern(node) for node in node.children] 

35 self.signature: str = node.signature 

36 if hasattr(node, "name") and node.name: 

37 self.name: str = use_dollar(node.name) 

38 else: 

39 self.name = "" 

40 

41 def __eq__(self, other: AstProtocol) -> bool: 

42 return is_match(other, self) 

43 

44 def __repr__(self): 

45 return use_dollar(str(self.node)) 

46 

47 def derive_type(self, node) -> str: 

48 # signature = "" 

49 # if isinstance(node.ast_type(), Argument): 

50 # signature = node.node.arg 

51 # elif isinstance(node.ast_type(), Name): 

52 # signature = node.node.value 

53 # elif isinstance(node.ast_type(), ExpressionStatement) and isinstance(node.node.value, ast.Name): 

54 # signature = node.node.value.id 

55 # if _MATCH_ALL_RE.match(signature): 

56 # return MatchAll 

57 # elif _MATCH_ONE_RE.match(signature): 

58 # return MatchOne 

59 # if isinstance(node, LSTNode): 

60 # return node.ast_type 

61 # else: 

62 # return node.ast_type 

63 if isinstance(node, ast.arg): 

64 signature = node.arg 

65 elif isinstance(node, ast.Name): 

66 signature = node.id 

67 elif isinstance(node, ast.Expr) and isinstance(node.value, ast.Name): 

68 signature = node.value.id 

69 elif isinstance(node, ast.AST): 

70 signature = str(node) 

71 else: 

72 signature = node.name 

73 

74 if node.ast_type in [DeclarationExpression, ExpressionStatement, Name, Arg]: 

75 if _MATCH_ALL_RE.match(signature): 

76 return MatchAll 

77 if _MATCH_ONE_RE.match(signature): 

78 return MatchOne 

79 

80 return node.ast_type 

81 

82 

83class PythonFactory: 

84 def __init__(self, clazz: type[PythonRstNode | PythonCstNode | LSTNode | ast.AST]) -> None: 

85 self.clazz = clazz 

86 if clazz == LSTNode: 

87 clazz.load_from_text = self.load_from_lst 

88 elif clazz == ast.AST: 

89 clazz.load_from_text = ASTExtension.load_from_ast 

90 # matcher 

91 clazz.node = ASTExtension.ast_node 

92 

93 # clazz.name = ASTExtension.ast_name 

94 clazz.ast_type = ASTExtension.ast_type 

95 clazz.properties = ASTExtension.ast_properties 

96 clazz.children = ASTExtension.ast_children 

97 clazz.signature = ASTExtension.ast_signature 

98 

99 # writer 

100 clazz.text = ASTExtension.ast_signature 

101 clazz.filename = "dummy.py" 

102 

103 # shower 

104 clazz.is_implicit = True 

105 clazz.show_props = False 

106 clazz.indent = "" 

107 

108 def create(self, file_path: Path) -> PythonRstNode | PythonCstNode: 

109 atu = self.clazz.load(file_path=file_path) 

110 assert isinstance(atu, self.clazz) 

111 return atu 

112 

113 def create_from_text(self, text: str, file_name: str = "snippet.py") -> PythonRstNode | PythonCstNode | LSTNode | ast.AST: 

114 atu = self.clazz.load_from_text(text, file_name) 

115 assert isinstance(atu, self.clazz) 

116 return atu 

117 

118 @staticmethod 

119 def load_from_lst(text, file): 

120 adapter = TreeSitterAdapter(tree_sitter_python) 

121 tree = adapter.parse_code(text) 

122 return adapter.to_lst(text, tree).root 

123 

124 

125class PythonPatternFactory: 

126 def __init__(self, factory: PythonFactory): 

127 self.factory = factory 

128 

129 def _create(self, text: str) -> PythonPattern: 

130 return PythonPattern(self.factory.create_from_text(text, "pattern.py")) 

131 

132 def create(self, text: str) -> PythonPattern: 

133 text = replace_dollar(text) 

134 return self._create(text) 

135 

136 def create_statements(self, text: str) -> Sequence[PythonPattern]: 

137 atu = self.create(text) 

138 return atu.children 

139 

140 def create_statement(self, text: str) -> PythonPattern: 

141 stmt = self.create_statements(text)[-1] 

142 if isinstance(stmt.node.node, SimpleStatementLine): 

143 return stmt.children[0] 

144 return stmt 

145 # return stmt 

146 

147 def create_expression(self, text: str) -> PythonPattern: 

148 my_pattern = self.create_statement(text) 

149 if isinstance(my_pattern.node, PythonRstNode): 

150 return PythonPattern(my_pattern.node.expression) 

151 if isinstance(my_pattern.node, (LSTNode, PythonCstNode)): 

152 return PythonPattern(my_pattern.node.children[-1]) 

153 return PythonPattern(my_pattern.node.children[0]) 

154 

155 def create_decorators(self, param): 

156 return self.create_statement(param + "\ndef test(): pass").children[2] 

157 

158 @staticmethod 

159 def create_kwargs(kw_str) -> Sequence[PythonPattern]: 

160 call = ast.parse(f"fun({replace_dollar(kw_str)})", "kwarg_pattern.py", type_comments=True).body[0].value 

161 return [PythonPattern(PythonRstNode(kwarg)) for kwarg in call.keywords]