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

54 statements  

« prev     ^ index     » next       coverage.py v7.16.2, created at 2026-10-15 10:34 +0000

1# Copyright (C) 2024-2026 TNO 

2# 

3# Licensed under the terms specified in the LICENSE file in the root directory 

4# of this repository, also available at: 

5# https://github.com/TNO/Renaissance.Py/blob/main/LICENSE 

6 

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

8 

9# use clang to load and walk a compilation database 

10import textwrap 

11from collections.abc import Iterable 

12 

13from more_itertools import last 

14 

15from renaissance.integrations.clang import ClangASTNode, CPPPatternFactory 

16from renaissance.integrations.clang.clang_json_ast_node import ClangJsonASTNode 

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

18from renaissance.syntax_tree import ( 

19 ASTFactory, 

20 ASTNode, 

21 ASTProcessor, 

22 ASTRefactorActions, 

23 RecipeASTProcessor, 

24 TextUtils, 

25 recipe_step, 

26) 

27from renaissance.syntax_tree.ast_finder import matches_node 

28 

29example_1 = textwrap.dedent(""" 

30#include <vector> 

31struct Size 

32{ 

33 double length; 

34 double width; 

35 

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

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

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

39 

40 Size size() 

41 { 

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

43 } 

44}; 

45 

46typedef const char* string; 

47 

48int main(){ 

49 // do nothing 

50} 

51void setItemLayout(int, Size size){ 

52} 

53class aClass{ 

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

55 std::vector<int> idToBeReplaced; 

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

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

58 } 

59 Size getBounds(){ 

60 return Size(10, 30); 

61 } 

62}; 

63 

64class ListView_LEGACY{ 

65 public: 

66 ListView_LEGACY(); 

67 ListView_LEGACY(string container, int val); 

68 Size size; 

69}; 

70 

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

72 

73} 

74ListView_LEGACY::ListView_LEGACY(){ 

75 

76} 

77 

78class derived : public ListView_LEGACY{ 

79 public: 

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

81 // something 

82 }; 

83 void another_func(int s); 

84}; 

85 

86void derived::another_func(int s){ 

87 int a = 0; 

88 // another_func 0 

89 // another_func 1 

90} 

91 

92void main2(string container){ 

93 /* hahaha*/ 

94 ListView_LEGACY listview(container, 3); 

95 int b; 

96 int a; 

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

98} 

99 

100void main3() 

101{ 

102 int b; 

103 string container, foo; 

104 ListView_LEGACY listview(container, 3); 

105 derived d(foo); 

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

107} 

108 

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

110{ 

111 /** 

112 * multi-line comments 

113 * in my code; 

114 * do this wrack my indent algo? 

115 */ 

116 std::vector<int> idToBeReplaced; 

117 /** 

118 * multi-line comments 

119 * in my code; 

120 * do this wrack my indent algo? 

121 */ 

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

123 /** 

124 * multi-line comments 

125 * in my code; 

126 * do this wrack my indent algo? 

127 */ 

128} 

129""") 

130expected_output = textwrap.dedent(""" 

131 void main(){ 

132 std::vector<int> NEW_ID; 

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

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

135} 

136 

137void main() { 

138 /*ahah*/ 

139 ListViewCustom listview; 

140 ListViewHeader listviewHeader0 

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

142 ListViewHeader listviewHeader1 

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

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

145 bool b; bool a; 

146 listview(container), 

147 listviewHeader0(listview), 

148 listviewHeader1(listview), 

149 listviewHeader2(listview); 

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

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

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

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

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

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

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

157} 

158 

159class ListView_LEGACY { 

160 ListView_LEGACY(string container, int val); 

161}; 

162class derived: public ListView_LEGACY { 

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

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

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

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

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

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

169 //something 

170 }; 

171 void another_func(int s ); 

172}; 

173void __REPLACEMENT__(){} 

174 

175void main(){ 

176 ListViewCustom listview; 

177 ListViewHeader listviewHeader0 

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

179 ListViewHeader listviewHeader1 

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

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

182 bool b; 

183 string container; 

184 listview(container), 

185 listviewHeader0(listview), 

186 listviewHeader1(listview), 

187 listviewHeader2(listview); 

188 derived d(); 

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

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

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

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

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

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

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

196} 

197 

198void main(){ 

199 /** 

200 * multi-line comments 

201 * in my code; 

202 * do this wrack my indent algo? 

203 */ 

204 std::vector<int> NEW_ID; 

205 /** 

206 * multi-line comments 

207 * in my code; 

208 * do this wrack my indent algo? 

209 */ 

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

211 /** 

212 * multi-line comments 

213 * in my code; 

214 * do this wrack my indent algo? 

215 */ 

216} 

217""") 

218 

219 

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

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

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

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

224 factory = ASTFactory(impl_type) 

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

226 yield factory, atu1 

227 

228 

229class MyRefactor: 

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

231 

232 def __init__(self) -> None: 

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

234 self._calls = [] 

235 

236 @recipe_step(order=0) 

237 def recipe(self, ast_processor: ASTProcessor) -> None: 

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

239 pattern = CPPPatternFactory(ast_processor.factory) 

240 actions = ASTRefactorActions(ast_processor, pattern) 

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

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

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

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

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

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

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

248 # create a constructor pattern 

249 constructor_pattern = pattern.create( 

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

251 kind="Constructor", 

252 ) 

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

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

255 # search for the constructor pattern 

256 for constructor_match in ast_processor.find_match(constructor_pattern): 

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

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

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

260 parent = var_node.parent 

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

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

263 # remove the count argument from the constructor call 

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

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

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

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

268 if matches_node(parent, is_clang_constructor): 

269 # remove constructor header count argument 

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

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

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

273 else: 

274 var = parent.name 

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

276 # replace the constructor call with a ListViewCustom object 

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

278 # find reference to the declaration 

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

280 

281 for h in range(header_count): 

282 ast_processor.insert_after( 

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

284 parent, 

285 True, 

286 False, 

287 ) 

288 if size_match: 

289 text = TextUtils.strip_indent(f""" 

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

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

292 """) 

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

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

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

296 TextUtils.to_clipboard(ast_processor.apply_to_string()) 

297 

298 

299def batch_recipe_example() -> None: 

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

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

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

303 recipe_ast_processor.run() 

304 

305 

306if __name__ == "__main__": 

307 batch_recipe_example()