Coverage for src / rejuvenation / batch_process_examples.py: 94%

66 statements  

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

1# use clang to load and walk a compilation database 

2import textwrap 

3from collections.abc import Callable, Iterable 

4from dataclasses import dataclass 

5 

6from renaissance.integrations.clang import ClangASTNode 

7from renaissance.integrations.clang.clang_json_ast_node import ClangJsonASTNode 

8from renaissance.integrations.types import Call 

9from renaissance.recipes import CleanupRefactoring 

10from renaissance.syntax_tree import ( 

11 ASTFactory, 

12 ASTNode, 

13 ASTProcessor, 

14 BatchASTProcessor, 

15 TextUtils, 

16) 

17from renaissance.syntax_tree.recipe_ast_processor import ( 

18 RecipeASTProcessor, 

19 after_step, 

20 final_action, 

21 recipe_step, 

22) 

23 

24example_1 = textwrap.dedent(""" 

25 void x(int a) {} 

26 void x1(int a) {} 

27 void x2(int a) {} 

28 

29 void f1(int a){ 

30 int unused = 0; 

31 int unused2 = 0; //must be removed 

32 if (a==1) { 

33 int unused = 0; 

34 int unused2 = 0; //should be kept 

35 int c = unused2; 

36 x1(c); 

37 } 

38 } 

39 """) 

40 

41example_2 = textwrap.dedent(""" 

42 void x(int a) {} 

43 void x1(int a) {} 

44 void x2(int a) {} 

45 void f2(int a){ 

46 int unused = 0; 

47 if (a==1) { 

48 int unused = 0; 

49 int another_unused = 0; 

50 int used2 = 0; //should be kept 

51 int c = used2; 

52 x2(c); 

53 } 

54 } 

55 """) 

56 

57 

58# generate a simple code base provider in real life use a compilation database 

59def simple_codebase_provider() -> Iterable[tuple[ASTFactory, ASTNode]]: 

60 for impl_type in [ClangASTNode, ClangJsonASTNode]: 

61 factory = ASTFactory(impl_type) 

62 atu1 = factory.create_from_text(example_1, impl_type.__name__ + "1.c") 

63 yield factory, atu1 

64 atu2 = factory.create_from_text(example_2, impl_type.__name__ + "2.c") 

65 yield factory, atu2 

66 

67 

68def print_results(title, batch_processor): 

69 print(title + ":") 

70 for file, code in batch_processor.in_memory_files.items(): 

71 print(TextUtils.shift_right(file, 4) + "\n") 

72 print(TextUtils.shift_right(code, 8) + "\n") 

73 

74 

75def batch_remove_unused_variable_once_example(): 

76 """This function demonstrates a batch processing example using different AST node implementations. 

77 It iterates over a list of AST node implementations (`ClangASTNode` and `ClangJsonASTNode`), 

78 and for each implementation, it generates a codebase provider that yields tuples of 

79 `ASTFactory` and `ASTNode` created from example source texts (`example_1` and `example_2`). 

80 The function then creates a `BatchASTProcessor` with in-memory storage enabled and processes 

81 the codebase using the `CleanupRefactoring.remove_unused_variables` refactoring operation. 

82 Finally, it prints the rewritten code stored in memory. 

83 """ 

84 # generate a batch processor for testing purposes we store into memory 

85 batch_processor = BatchASTProcessor(in_memory=True) 

86 batch_processor.once(simple_codebase_provider, CleanupRefactoring.remove_unused_variables) 

87 # print the rewritten code normally you would write to a file 

88 print_results("example batch remove unused variable once", batch_processor) 

89 

90 

91def batch_repeat_example(): 

92 """Demonstrates the use of a batch processor to perform multiple refactoring operations on a codebase. 

93 This example creates an in-memory batch processor and applies two refactoring operations: 

94 1. CleanupRefactoring.remove_unused_variables: Removes unused variables from the codebase. 

95 2. remove_function: Removes all function calls from the codebase. 

96 The results of the refactoring operations are printed to the console. 

97 

98 Repeat is in action here: 

99 the first time the codebase is processed, the unused variables are removed. 

100 and the function calls are removed. 

101 the second time the codebase is processed, the new unused variables are removed again. 

102 

103 Note: 

104 In a real-world scenario, the rewritten code would typically be written to a file instead of being printed. 

105 

106 """ 

107 # generate a batch processor for testing purposes we store into memory 

108 batch_processor = BatchASTProcessor(in_memory=True) 

109 

110 # remove a function to create more unused variables 

111 def remove_function(ast_processor: ASTProcessor): 

112 [ast_processor.insert_before("// ", node, False, False) for node in ast_processor.find_ast_type(Call)] 

113 

114 # batch_processor.repeat(simple_codebase_provider, [remove_function]) 

115 batch_processor.repeat( 

116 simple_codebase_provider, 

117 [CleanupRefactoring.remove_unused_variables, remove_function], 

118 ) 

119 # print the rewritten code normally you would write to a file 

120 print_results("example batch repeat", batch_processor) 

121 

122 

123@dataclass 

124class CallInfo: 

125 callee: str 

126 calls: str 

127 

128 

129class AnalysisRecipe: 

130 def __init__(self): 

131 self._calls = [] 

132 

133 @recipe_step(order=0) 

134 def store_function_call(self, ast_processor: ASTProcessor) -> Callable[[], None] | None: 

135 # find all function calls and store them, this routing is invoked in parallel! 

136 calls: list[CallInfo] = [] 

137 [AnalysisRecipe._add_function_call(node, calls) for node in ast_processor.find_ast_type(Call)] 

138 # the resulting lambda is invoked single threaded 

139 # this kind of mechanism is mainly used to store results from multiple processors 

140 # for refactoring operations this is not needed as a refactoring operation is single threaded 

141 if calls: 

142 return lambda: self._calls.extend(calls) 

143 return None 

144 

145 @after_step("store_function_call") 

146 def just_show_the_method(self): 

147 print("called after store_function_call") 

148 

149 @final_action() 

150 def final_action(self): 

151 print("Calls:") 

152 for call in self._calls: 

153 print(" " + call.callee + " -- calls --> " + call.calls) 

154 

155 @staticmethod 

156 def _add_function_call(call: ASTNode, calls: list[CallInfo]): 

157 callee = call.get_ancestor("(?i)Function_?Decl") 

158 if callee: 

159 calls.append(CallInfo(callee.name, call.children[0].name)) 

160 

161 

162def batch_recipe_example(): 

163 print("example batch analysis using recipe:\n") 

164 recipe_ast_processor = RecipeASTProcessor(AnalysisRecipe(), simple_codebase_provider, r".*", in_memory=True) 

165 recipe_ast_processor.run() 

166 

167 

168if __name__ == "__main__": 

169 # a list of example to show batch processing of a code base 

170 batch_remove_unused_variable_once_example() 

171 batch_repeat_example() 

172 batch_recipe_example()