clang-tools 22.0.0git
rename_check.py
Go to the documentation of this file.
1#!/usr/bin/env python3
2#
3# ===-----------------------------------------------------------------------===#
4#
5# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
6# See https://llvm.org/LICENSE.txt for license information.
7# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
8#
9# ===-----------------------------------------------------------------------===#
10
11import argparse
12import glob
13import io
14import os
15import re
16import sys
17from typing import List
18
19
20def replaceInFileRegex(fileName: str, sFrom: str, sTo: str) -> None:
21 if sFrom == sTo:
22 return
23
24 # The documentation files are encoded using UTF-8, however on Windows the
25 # default encoding might be different (e.g. CP-1252). To make sure UTF-8 is
26 # always used, use `io.open(filename, mode, encoding='utf8')` for reading and
27 # writing files here and elsewhere.
28 txt = None
29 with io.open(fileName, "r", encoding="utf8") as f:
30 txt = f.read()
31
32 txt = re.sub(sFrom, sTo, txt)
33 print("Replacing '%s' -> '%s' in '%s'..." % (sFrom, sTo, fileName))
34 with io.open(fileName, "w", encoding="utf8") as f:
35 f.write(txt)
36
37
38def replaceInFile(fileName: str, sFrom: str, sTo: str) -> None:
39 if sFrom == sTo:
40 return
41 txt = None
42 with io.open(fileName, "r", encoding="utf8") as f:
43 txt = f.read()
44
45 if sFrom not in txt:
46 return
47
48 txt = txt.replace(sFrom, sTo)
49 print("Replacing '%s' -> '%s' in '%s'..." % (sFrom, sTo, fileName))
50 with io.open(fileName, "w", encoding="utf8") as f:
51 f.write(txt)
52
53
54def fileRename(fileName: str, sFrom: str, sTo: str) -> str:
55 if sFrom not in fileName or sFrom == sTo:
56 return fileName
57 newFileName = fileName.replace(sFrom, sTo)
58 print("Renaming '%s' -> '%s'..." % (fileName, newFileName))
59 os.rename(fileName, newFileName)
60 return newFileName
61
62
63def deleteMatchingLines(fileName: str, pattern: str) -> bool:
64 lines = None
65 with io.open(fileName, "r", encoding="utf8") as f:
66 lines = f.readlines()
67
68 not_matching_lines = [l for l in lines if not re.search(pattern, l)]
69 if len(not_matching_lines) == len(lines):
70 return False
71
72 print("Removing lines matching '%s' in '%s'..." % (pattern, fileName))
73 print(" " + " ".join([l for l in lines if re.search(pattern, l)]))
74 with io.open(fileName, "w", encoding="utf8") as f:
75 f.writelines(not_matching_lines)
76
77 return True
78
79
80def getListOfFiles(clang_tidy_path: str) -> List[str]:
81 files = glob.glob(os.path.join(clang_tidy_path, "**"), recursive=True)
82 files += [
83 os.path.normpath(os.path.join(clang_tidy_path, "../docs/ReleaseNotes.rst"))
84 ]
85 files += glob.glob(
86 os.path.join(clang_tidy_path, "..", "test", "clang-tidy", "checkers", "**"),
87 recursive=True,
88 )
89 files += glob.glob(
90 os.path.join(clang_tidy_path, "..", "docs", "clang-tidy", "checks", "*.rst")
91 )
92 files += glob.glob(
93 os.path.join(
94 clang_tidy_path, "..", "docs", "clang-tidy", "checks", "*", "*.rst"
95 ),
96 recursive=True,
97 )
98 return [filename for filename in files if os.path.isfile(filename)]
99
100
101# Adapts the module's CMakelist file. Returns 'True' if it could add a new
102# entry and 'False' if the entry already existed.
103def adapt_cmake(module_path: str, check_name_camel: str) -> bool:
104 filename = os.path.join(module_path, "CMakeLists.txt")
105 with io.open(filename, "r", encoding="utf8") as f:
106 lines = f.readlines()
107
108 cpp_file = check_name_camel + ".cpp"
109
110 # Figure out whether this check already exists.
111 for line in lines:
112 if line.strip() == cpp_file:
113 return False
114
115 print("Updating %s..." % filename)
116 with io.open(filename, "w", encoding="utf8") as f:
117 cpp_found = False
118 file_added = False
119 for line in lines:
120 cpp_line = line.strip().endswith(".cpp")
121 if (not file_added) and (cpp_line or cpp_found):
122 cpp_found = True
123 if (line.strip() > cpp_file) or (not cpp_line):
124 f.write(" " + cpp_file + "\n")
125 file_added = True
126 f.write(line)
127
128 return True
129
130
131# Modifies the module to include the new check.
133 module_path: str, module: str, check_name: str, check_name_camel: str
134) -> None:
135 modulecpp = next(
136 iter(
137 filter(
138 lambda p: p.lower() == module.lower() + "tidymodule.cpp",
139 os.listdir(module_path),
140 )
141 )
142 )
143 filename = os.path.join(module_path, modulecpp)
144 with io.open(filename, "r", encoding="utf8") as f:
145 lines = f.readlines()
146
147 print("Updating %s..." % filename)
148 with io.open(filename, "w", encoding="utf8") as f:
149 header_added = False
150 header_found = False
151 check_added = False
152 check_decl = (
153 " CheckFactories.registerCheck<"
154 + check_name_camel
155 + '>(\n "'
156 + check_name
157 + '");\n'
158 )
159
160 for line in lines:
161 if not header_added:
162 match = re.search('#include "(.*)"', line)
163 if match:
164 header_found = True
165 if match.group(1) > check_name_camel:
166 header_added = True
167 f.write('#include "' + check_name_camel + '.h"\n')
168 elif header_found:
169 header_added = True
170 f.write('#include "' + check_name_camel + '.h"\n')
171
172 if not check_added:
173 if line.strip() == "}":
174 check_added = True
175 f.write(check_decl)
176 else:
177 match = re.search("registerCheck<(.*)>", line)
178 if match and match.group(1) > check_name_camel:
179 check_added = True
180 f.write(check_decl)
181 f.write(line)
182
183
184# Adds a release notes entry.
186 clang_tidy_path: str, old_check_name: str, new_check_name: str
187) -> None:
188 filename = os.path.normpath(
189 os.path.join(clang_tidy_path, "../docs/ReleaseNotes.rst")
190 )
191 with io.open(filename, "r", encoding="utf8") as f:
192 lines = f.readlines()
193
194 lineMatcher = re.compile("Renamed checks")
195 nextSectionMatcher = re.compile("Improvements to include-fixer")
196 checkMatcher = re.compile("- The '(.*)")
197
198 print("Updating %s..." % filename)
199 with io.open(filename, "w", encoding="utf8") as f:
200 note_added = False
201 header_found = False
202 add_note_here = False
203
204 for line in lines:
205 if not note_added:
206 match = lineMatcher.match(line)
207 match_next = nextSectionMatcher.match(line)
208 match_check = checkMatcher.match(line)
209 if match_check:
210 last_check = match_check.group(1)
211 if last_check > old_check_name:
212 add_note_here = True
213
214 if match_next:
215 add_note_here = True
216
217 if match:
218 header_found = True
219 f.write(line)
220 continue
221
222 if line.startswith("^^^^"):
223 f.write(line)
224 continue
225
226 if header_found and add_note_here:
227 if not line.startswith("^^^^"):
228 f.write(
229 """- The '%s' check was renamed to :doc:`%s
230 <clang-tidy/checks/%s/%s>`
231
232 """
233 % (
234 old_check_name,
235 new_check_name,
236 new_check_name.split("-", 1)[0],
237 "-".join(new_check_name.split("-")[1:]),
238 )
239 )
240 note_added = True
241
242 f.write(line)
243
244
245def main() -> None:
246 parser = argparse.ArgumentParser(description="Rename clang-tidy check.")
247 parser.add_argument("old_check_name", type=str, help="Old check name.")
248 parser.add_argument("new_check_name", type=str, help="New check name.")
249 parser.add_argument(
250 "--check_class_name",
251 type=str,
252 help="Old name of the class implementing the check.",
253 )
254 args = parser.parse_args()
255
256 old_module = args.old_check_name.split("-")[0]
257 new_module = args.new_check_name.split("-")[0]
258 old_name = "-".join(args.old_check_name.split("-")[1:])
259 new_name = "-".join(args.new_check_name.split("-")[1:])
260
261 if args.check_class_name:
262 check_name_camel = args.check_class_name
263 else:
264 check_name_camel = (
265 "".join(map(lambda elem: elem.capitalize(), old_name.split("-"))) + "Check"
266 )
267
268 new_check_name_camel = (
269 "".join(map(lambda elem: elem.capitalize(), new_name.split("-"))) + "Check"
270 )
271
272 clang_tidy_path = os.path.dirname(__file__)
273
274 header_guard_variants = [
275 (args.old_check_name.replace("-", "_")).upper() + "_CHECK",
276 (old_module + "_" + check_name_camel).upper(),
277 (old_module + "_" + new_check_name_camel).upper(),
278 args.old_check_name.replace("-", "_").upper(),
279 ]
280 header_guard_new = (new_module + "_" + new_check_name_camel).upper()
281
282 old_module_path = os.path.join(clang_tidy_path, old_module)
283 new_module_path = os.path.join(clang_tidy_path, new_module)
284
285 if old_module != new_module:
286 # Remove the check from the old module.
287 cmake_lists = os.path.join(old_module_path, "CMakeLists.txt")
288 check_found = deleteMatchingLines(cmake_lists, "\\b" + check_name_camel)
289 if not check_found:
290 print(
291 "Check name '%s' not found in %s. Exiting."
292 % (check_name_camel, cmake_lists)
293 )
294 sys.exit(1)
295
296 modulecpp = next(
297 iter(
298 filter(
299 lambda p: p.lower() == old_module.lower() + "tidymodule.cpp",
300 os.listdir(old_module_path),
301 )
302 )
303 )
305 os.path.join(old_module_path, modulecpp),
306 "\\b" + check_name_camel + "|\\b" + args.old_check_name,
307 )
308
309 for filename in getListOfFiles(clang_tidy_path):
310 originalName = filename
311 filename = fileRename(
312 filename, old_module + "/" + old_name, new_module + "/" + new_name
313 )
314 filename = fileRename(filename, args.old_check_name, args.new_check_name)
315 filename = fileRename(filename, check_name_camel, new_check_name_camel)
316 for header_guard in header_guard_variants:
317 replaceInFile(filename, header_guard, header_guard_new)
318
319 if new_module + "/" + new_name + ".rst" in filename:
321 filename,
322 args.old_check_name + "\n" + "=" * len(args.old_check_name) + "\n",
323 args.new_check_name + "\n" + "=" * len(args.new_check_name) + "\n",
324 )
325
326 replaceInFile(filename, args.old_check_name, args.new_check_name)
328 filename,
329 old_module + "::" + check_name_camel,
330 new_module + "::" + new_check_name_camel,
331 )
333 filename,
334 old_module + "/" + check_name_camel,
335 new_module + "/" + new_check_name_camel,
336 )
338 filename, old_module + "/" + old_name, new_module + "/" + new_name
339 )
340 replaceInFile(filename, check_name_camel, new_check_name_camel)
341
342 if old_module != new_module or new_module == "llvm":
343 if new_module == "llvm":
344 new_namespace = new_module + "_check"
345 else:
346 new_namespace = new_module
347 check_implementation_files = glob.glob(
348 os.path.join(old_module_path, new_check_name_camel + "*")
349 )
350 for filename in check_implementation_files:
351 # Move check implementation to the directory of the new module.
352 filename = fileRename(filename, old_module_path, new_module_path)
354 filename,
355 "namespace clang::tidy::" + old_module + "[^ \n]*",
356 "namespace clang::tidy::" + new_namespace,
357 )
358
359 if old_module != new_module:
360
361 # Add check to the new module.
362 adapt_cmake(new_module_path, new_check_name_camel)
364 new_module_path, new_module, args.new_check_name, new_check_name_camel
365 )
366
367 os.system(os.path.join(clang_tidy_path, "add_new_check.py") + " --update-docs")
368 add_release_notes(clang_tidy_path, args.old_check_name, args.new_check_name)
369
370
371if __name__ == "__main__":
372 main()
List[str] getListOfFiles(str clang_tidy_path)
None adapt_module(str module_path, str module, str check_name, str check_name_camel)
str fileRename(str fileName, str sFrom, str sTo)
None replaceInFileRegex(str fileName, str sFrom, str sTo)
None add_release_notes(str clang_tidy_path, str old_check_name, str new_check_name)
bool adapt_cmake(str module_path, str check_name_camel)
bool deleteMatchingLines(str fileName, str pattern)
None replaceInFile(str fileName, str sFrom, str sTo)