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
« 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
7"""AI: Example script demonstrating a C++ refactoring recipe applied across a Clang compilation database."""
9# use clang to load and walk a compilation database
10import textwrap
11from collections.abc import Iterable
13from more_itertools import last
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
29example_1 = textwrap.dedent("""
30#include <vector>
31struct Size
32{
33 double length;
34 double width;
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) {}
40 Size size()
41 {
42 return Size(this->length, this->width);
43 }
44};
46typedef const char* string;
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};
64class ListView_LEGACY{
65 public:
66 ListView_LEGACY();
67 ListView_LEGACY(string container, int val);
68 Size size;
69};
71ListView_LEGACY::ListView_LEGACY(string container, int val){
73}
74ListView_LEGACY::ListView_LEGACY(){
76}
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};
86void derived::another_func(int s){
87 int a = 0;
88 // another_func 0
89 // another_func 1
90}
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}
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}
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}
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}
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__(){}
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}
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""")
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
229class MyRefactor:
230 """AI: Example recipe demonstrating text/name/declaration replacement via ASTRefactorActions."""
232 def __init__(self) -> None:
233 """AI: Initialize an empty recipe for collecting refactoring actions."""
234 self._calls = []
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)
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())
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()
306if __name__ == "__main__":
307 batch_recipe_example()