Coverage for src / renaissance / syntax_tree / ast_processor.py: 85%

67 statements  

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

1from collections.abc import Callable, Iterator, Sequence 

2from pathlib import Path 

3 

4import renaissance.syntax_tree.match_finder 

5from renaissance.integrations.types import Type 

6from renaissance.syntax_tree import ASTNode 

7from renaissance.syntax_tree.ast_factory import ASTFactory 

8from renaissance.syntax_tree.ast_finder import ASTFinder, find_ast_type 

9from renaissance.syntax_tree.ast_rewriter import ASTRewriter 

10from renaissance.syntax_tree.match_finder import PatternMatch 

11 

12 

13class ASTProcessor: 

14 def __init__( 

15 self, 

16 root: ASTNode, 

17 ast_factory: ASTFactory, 

18 in_memory: bool = False, 

19 ) -> None: 

20 self.__root_node = root 

21 self.__rewriter = ASTRewriter(root) 

22 self.__ast_factory = ast_factory 

23 self.in_memory = in_memory 

24 self.repeat_step = 0 

25 

26 @property 

27 def factory(self) -> ASTFactory: 

28 return self.__ast_factory 

29 

30 @property 

31 def node(self) -> ASTNode: 

32 return self.__root_node 

33 

34 @property 

35 def filename(self) -> str: 

36 return self.__rewriter.get_filename() 

37 

38 @property 

39 def root(self) -> ASTNode: 

40 return self.__root_node 

41 

42 def replace( 

43 self, 

44 new_content: str, 

45 target: ASTNode | Sequence[ASTNode] | PatternMatch | Sequence[PatternMatch], 

46 include_whitespace: bool = True, 

47 include_comments: bool = True, 

48 ) -> None: 

49 self.__rewriter.replace(new_content, target, include_whitespace, include_comments) 

50 

51 def remove( 

52 self, 

53 target: ASTNode | Sequence[ASTNode] | PatternMatch | Sequence[PatternMatch], 

54 include_whitespace: bool = True, 

55 include_comments: bool = True, 

56 ) -> None: 

57 self.__rewriter.remove(target, include_whitespace, include_comments) 

58 

59 def insert_before( 

60 self, 

61 new_content: str, 

62 target: ASTNode | Sequence[ASTNode] | PatternMatch | Sequence[PatternMatch], 

63 include_whitespace: bool = True, 

64 include_comments: bool = True, 

65 ) -> None: 

66 self.__rewriter.insert_before(new_content, target, include_whitespace, include_comments) 

67 

68 def insert_after( 

69 self, 

70 new_content: str, 

71 target: ASTNode | Sequence[ASTNode] | PatternMatch | Sequence[PatternMatch], 

72 include_whitespace: bool = True, 

73 include_comments: bool = True, 

74 ) -> None: 

75 self.__rewriter.insert_after(new_content, target, include_whitespace, include_comments) 

76 

77 def find_all(self, function: Callable[[ASTNode], Iterator[ASTNode] | bool]) -> Sequence[ASTNode]: 

78 return ASTFinder.find_all(self.__root_node, function) 

79 

80 def find_ast_type(self, kind: type[Type]) -> Sequence[ASTNode]: 

81 return find_ast_type(self.__root_node, kind) 

82 

83 def find_match(self, *patterns_list, recursive: bool = True) -> Sequence[PatternMatch]: 

84 return renaissance.syntax_tree.match_finder.find_all( 

85 self.__root_node.children, 

86 *patterns_list, 

87 recursive=recursive, 

88 ) 

89 

90 def has_changed(self) -> bool: 

91 return self.__rewriter.has_changed() 

92 

93 def apply_to_string(self) -> str: 

94 return self.__rewriter.apply_to_string() 

95 

96 def commit(self) -> ASTProcessor: 

97 """Commits the current changes to the AST (Abstract Syntax Tree) and returns a new ASTProcessor instance. 

98 

99 This method applies the current changes to the source code and creates a new ASTProcessor instance 

100 with the updated AST. If the changes are in-memory, it directly creates the new AST from the updated 

101 code string. Otherwise, it writes the changes to the file, reloads the file, and then creates the new AST. 

102 

103 Returns: 

104 ASTProcessor: A new instance of ASTProcessor with the updated AST. 

105 

106 Raises: 

107 IOError: If there is an error writing to the file. 

108 

109 """ 

110 if not self.__rewriter.has_changed(): 

111 return self 

112 self.__root_node, self.__rewriter = self._commit(self.__rewriter, self.__ast_factory, self.in_memory) 

113 return ASTProcessor(self.__root_node, self.__ast_factory, self.in_memory) 

114 

115 @staticmethod 

116 def _commit(rewriter: ASTRewriter, factory: ASTFactory, in_memory: bool = False): 

117 rewriter.apply_to_string() 

118 if in_memory: 

119 atu = factory.create_from_text(rewriter.apply_to_string(), rewriter.get_filename()) 

120 return atu, ASTRewriter(atu) 

121 # save file first then reload it 

122 with Path(rewriter.get_filename()).open("wb") as f: 

123 f.write(rewriter.apply()) 

124 atu = factory.create(Path(rewriter.get_filename())) 

125 return atu, ASTRewriter(atu) 

126 

127 

128# main 

129if __name__ == "__main__": 

130 

131 def test[T](_: str, factory: type[T]) -> T: 

132 result = factory() 

133 assert isinstance(result, factory) 

134 return result 

135 

136 test("key", str)