Coverage for src/rejuvenation/recipe_example.py: 31%

54 statements  

« prev     ^ index     » next       coverage.py v7.16.1, created at 2026-10-02 13:12 +0000

1"""AI: Example script demonstrating a C++ refactoring recipe applied across a Clang compilation database.""" 

2 

3# use clang to load and walk a compilation database 

4import textwrap 

5from collections.abc import Iterable 

6 

7from more_itertools import last 

8 

9from renaissance.integrations.clang import ClangASTNode, CPPPatternFactory 

10from renaissance.integrations.clang.clang_json_ast_node import ClangJsonASTNode 

11from renaissance.integrations.clang.predicates import is_clang_constructor, is_clang_method, is_clang_type_reference 

12from renaissance.syntax_tree import ( 

13 ASTFactory, 

14 ASTNode, 

15 ASTProcessor, 

16 ASTRefactorActions, 

17 RecipeASTProcessor, 

18 TextUtils, 

19 recipe_step, 

20) 

21from renaissance.syntax_tree.ast_finder import matches_node 

22 

23example_1 = textwrap.dedent(""" 

24#include <vector> 

25struct Size 

26{ 

27 double length; 

28 double width; 

29 

30 // Constructor to initialize the Rectangle object with length and width 

31 Size() : length(0), width(0) {} 

32 Size(double len, double wid) : length(len), width(wid) {} 

33 

34 Size size() 

35 { 

36 return Size(this->length, this->width); 

37 } 

38}; 

39 

40typedef const char* string; 

41 

42int main(){ 

43 // do nothing 

44} 

45void setItemLayout(int, Size size){ 

46} 

47class aClass{ 

48 void main1(std::vector<int> m_items){ 

49 std::vector<int> idToBeReplaced; 

50 idToBeReplaced.push_back((int)m_items.size()); 

51 setItemLayout(1, Size(this->getBounds().size().width, 30)); 

52 } 

53 Size getBounds(){ 

54 return Size(10, 30); 

55 } 

56}; 

57 

58class ListView_LEGACY{ 

59 public: 

60 ListView_LEGACY(); 

61 ListView_LEGACY(string container, int val); 

62 Size size; 

63}; 

64 

65ListView_LEGACY::ListView_LEGACY(string container, int val){ 

66 

67} 

68ListView_LEGACY::ListView_LEGACY(){ 

69 

70} 

71 

72class derived : public ListView_LEGACY{ 

73 public: 

74 derived(string cont) : ListView_LEGACY(cont, 5) { 

75 // something 

76 }; 

77 void another_func(int s); 

78}; 

79 

80void derived::another_func(int s){ 

81 int a = 0; 

82 // another_func 0 

83 // another_func 1 

84} 

85 

86void main2(string container){ 

87 /* hahaha*/ 

88 ListView_LEGACY listview(container, 3); 

89 int b; 

90 int a; 

91 listview.size = Size(4, 5); 

92} 

93 

94void main3() 

95{ 

96 int b; 

97 string container, foo; 

98 ListView_LEGACY listview(container, 3); 

99 derived d(foo); 

100 listview.size = Size(4, 5); 

101} 

102 

103void main4(std::vector<int> m_items) 

104{ 

105 /** 

106 * multi-line comments 

107 * in my code; 

108 * do this wrack my indent algo? 

109 */ 

110 std::vector<int> idToBeReplaced; 

111 /** 

112 * multi-line comments 

113 * in my code; 

114 * do this wrack my indent algo? 

115 */ 

116 idToBeReplaced.push_back((int)m_items.size()); 

117 /** 

118 * multi-line comments 

119 * in my code; 

120 * do this wrack my indent algo? 

121 */ 

122} 

123""") 

124expected_output = textwrap.dedent(""" 

125 void main(){ 

126 std::vector<int> NEW_ID; 

127 NEW_ID.push_back((int)m_items.size()); 

128 setItemLayout(1, Size(this->getBounds().size().width,30)); 

129} 

130 

131void main() { 

132 /*ahah*/ 

133 ListViewCustom listview; 

134 ListViewHeader listviewHeader0 

135 /* Conversion note: give header appropriate name */ 

136 ListViewHeader listviewHeader1 

137 /* Conversion note: give header appropriate name */ 

138 ListViewHeader listviewHeader2 /* Conversion note: give header appropriate name */ 

139 bool b; bool a; 

140 listview(container), 

141 listviewHeader0(listview), 

142 listviewHeader1(listview), 

143 listviewHeader2(listview); 

144 listview.size = Size(4, 5); 

145 listviewHeader0.name = L"listviewHeader0";/* Conversion note: give header appropriate name */ 

146 listviewHeader0.size = Size(256, 30); /* Conversion note: provide correct sizes */ 

147 listviewHeader1.name = L"listviewHeader1";/* Conversion note: give header appropriate name */ 

148 listviewHeader1.size = Size(256, 30); /* Conversion note: provide correct sizes */ 

149 listviewHeader2.name = L"listviewHeader2";/* Conversion note: give header appropriate name */ 

150 listviewHeader2.size = Size(256, 30); /* Conversion note: provide correct sizes */ 

151} 

152 

153class ListView_LEGACY { 

154 ListView_LEGACY(string container, int val); 

155}; 

156class derived: public ListView_LEGACY { 

157 derived(string cont):ListViewCustom(cont), m_headers {, 

158 std:make_unique<ListViewHeader>(*this), 

159 std:make_unique<ListViewHeader>(*this), 

160 std:make_unique<ListViewHeader>(*this), 

161 std:make_unique<ListViewHeader>(*this), 

162 std:make_unique<ListViewHeader>(*this)}{ 

163 //something 

164 }; 

165 void another_func(int s ); 

166}; 

167void __REPLACEMENT__(){} 

168 

169void main(){ 

170 ListViewCustom listview; 

171 ListViewHeader listviewHeader0 

172 /* Conversion note: give header appropriate name */ 

173 ListViewHeader listviewHeader1 

174 /* Conversion note: give header appropriate name */ 

175 ListViewHeader listviewHeader2 /* Conversion note: give header appropriate name */ 

176 bool b; 

177 string container; 

178 listview(container), 

179 listviewHeader0(listview), 

180 listviewHeader1(listview), 

181 listviewHeader2(listview); 

182 derived d(); 

183 listview.size = Size(4, 5); 

184 listviewHeader0.name = L"listviewHeader0";/* Conversion note: give header appropriate name */ 

185 listviewHeader0.size = Size(256, 30); /* Conversion note: provide correct sizes */ 

186 listviewHeader1.name = L"listviewHeader1";/* Conversion note: give header appropriate name */ 

187 listviewHeader1.size = Size(256, 30); /* Conversion note: provide correct sizes */ 

188 listviewHeader2.name = L"listviewHeader2";/* Conversion note: give header appropriate name */ 

189 listviewHeader2.size = Size(256, 30); /* Conversion note: provide correct sizes */ 

190} 

191 

192void main(){ 

193 /** 

194 * multi-line comments 

195 * in my code; 

196 * do this wrack my indent algo? 

197 */ 

198 std::vector<int> NEW_ID; 

199 /** 

200 * multi-line comments 

201 * in my code; 

202 * do this wrack my indent algo? 

203 */ 

204 NEW_ID.push_back((int)m_items.size()); 

205 /** 

206 * multi-line comments 

207 * in my code; 

208 * do this wrack my indent algo? 

209 */ 

210} 

211""") 

212 

213 

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

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

216 """AI: Yield a (factory, ATU) pair for the example C++ snippet using the Clang integration.""" 

217 for impl_type in [ClangASTNode, ClangJsonASTNode][0:1]: 

218 factory = ASTFactory(impl_type) 

219 atu1 = factory.create_from_text(example_1, impl_type.__name__ + "1.cpp") 

220 yield factory, atu1 

221 

222 

223class MyRefactor: 

224 """AI: Example recipe demonstrating text/name/declaration replacement via ASTRefactorActions.""" 

225 

226 def __init__(self): 

227 """AI: Initialize an empty recipe for collecting refactoring actions.""" 

228 self._calls = [] 

229 

230 @recipe_step(order=0) 

231 def recipe(self, ast_processor: ASTProcessor): 

232 """AI: Apply the example text, name, and declaration replacements to the processed AST.""" 

233 pattern = CPPPatternFactory(ast_processor.factory) 

234 actions = ASTRefactorActions(ast_processor, pattern) 

235 actions.replace_text("ListView_LEGACY", "ListViewCustom", skip_kind=is_clang_type_reference) 

236 actions.replace_name("another_func", "__REPLACEMENT__", is_clang_method) 

237 actions.replace_text("idToBeReplaced", "NEW_ID") 

238 # TODO debate the way to replace this the options are: 

239 # 1. make a match of the consecutive nodes. 

240 # 2. find a neat construction for the current backtick replacement 

241 actions.replace_declaration("int $var;", r"bool $var`int\s+(.+)`;") 

242 # create a constructor pattern 

243 constructor_pattern = pattern.create( 

244 "typedef int string; class ListView_LEGACY { ListView_LEGACY(string container, int val); };", 

245 kind="Constructor", 

246 ) 

247 # create a pattern to match a call to a constructor in both declarations and derived classes 

248 constructor_call_pattern = pattern.create_constructor_call("$var($container, $headerCount)") 

249 # search for the constructor pattern 

250 for constructor_match in ast_processor.find_match(constructor_pattern): 

251 # and then search for the referenced by calls to the constructor 

252 for constructor_call in constructor_match.match_referenced_by([constructor_call_pattern]): 

253 var_node = constructor_call.nodes["$var"][0] 

254 parent = var_node.parent 

255 assert isinstance(parent, ASTNode), f"{parent} is not an ASTNode" 

256 header_count = int(constructor_call.expansions["$headerCount"]) 

257 # remove the count argument from the constructor call 

258 # TODO it would be a lot easier if ast rewrite would support removal of the second argument 

259 # but currently (I guess) that would lead to a dangling comma 

260 # TODO the items between the backtick represent a regex where all groups are the used replacements 

261 # this might need some investigation what is the best way to handle this 

262 if matches_node(parent, is_clang_constructor): 

263 # remove constructor header count argument 

264 ast_processor.replace(r"ListViewCustom($container)", constructor_call) 

265 repl = ",\n ".join("std:make_unique<ListViewHeader>(*this)" for _ in range(header_count)) 

266 ast_processor.insert_after(", m_headers {" + repl + "}", constructor_call, True, False) 

267 else: 

268 var = parent.name 

269 container = constructor_call.expansions["$container"] 

270 # replace the constructor call with a ListViewCustom object 

271 ast_processor.replace(f"ListViewCustom {var}({container});", parent) 

272 # find reference to the declaration 

273 size_match = last(ref.node.get_ancestor("Call_?Expr") for ref in parent.referenced_by) 

274 

275 for h in range(header_count): 

276 ast_processor.insert_after( 

277 f"\n/* Conversion note: give header appropriate name */\nListViewHeader listviewHeader{h}({var});", 

278 parent, 

279 True, 

280 False, 

281 ) 

282 if size_match: 

283 text = TextUtils.strip_indent(f""" 

284 listviewHeader{h}.name = L"listviewHeader{h}";/* Conversion note: give header appropriate name */ 

285 listviewHeader{h}.size = Size(256, 30); /* Conversion note: provide correct sizes */ 

286 """) 

287 ast_processor.insert_after(text, size_match, True, False) 

288 # for idx, line in enumerate(ast_processor.apply_to_string().split('\n')): 

289 # print(f'{idx+1}: {line}') 

290 TextUtils.to_clipboard(ast_processor.apply_to_string()) 

291 

292 

293def batch_recipe_example(): 

294 """AI: Run `MyRefactor` over the example codebase and copy the rewritten source to the clipboard.""" 

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

296 recipe_ast_processor = RecipeASTProcessor(MyRefactor(), simple_codebase_provider, r".*", in_memory=True) 

297 recipe_ast_processor.run() 

298 

299 

300if __name__ == "__main__": 

301 batch_recipe_example()