clang-tools 20.0.0git
rename_check.py
Go to the documentation of this file.
1#!/usr/bin/env python3
2#
3# ===- rename_check.py - clang-tidy check renamer ------------*- python -*--===#
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 generateCommentLineHeader(filename: str) -> str:
55 return "".join(
56 [
57 "//===--- ",
58 os.path.basename(filename),
59 " - clang-tidy ",
60 "-" * max(0, 42 - len(os.path.basename(filename))),
61 "*- C++ -*-===//",
62 ]
63 )
64
65
66def generateCommentLineSource(filename: str) -> str:
67 return "".join(
68 [
69 "//===--- ",
70 os.path.basename(filename),
71 " - clang-tidy",
72 "-" * max(0, 52 - len(os.path.basename(filename))),
73 "-===//",
74 ]
75 )
76
77
78def fileRename(fileName: str, sFrom: str, sTo: str) -> str:
79 if sFrom not in fileName or sFrom == sTo:
80 return fileName
81 newFileName = fileName.replace(sFrom, sTo)
82 print("Renaming '%s' -> '%s'..." % (fileName, newFileName))
83 os.rename(fileName, newFileName)
84 return newFileName
85
86
87def deleteMatchingLines(fileName: str, pattern: str) -> bool:
88 lines = None
89 with io.open(fileName, "r", encoding="utf8") as f:
90 lines = f.readlines()
91
92 not_matching_lines = [l for l in lines if not re.search(pattern, l)]
93 if len(not_matching_lines) == len(lines):
94 return False
95
96 print("Removing lines matching '%s' in '%s'..." % (pattern, fileName))
97 print(" " + " ".join([l for l in lines if re.search(pattern, l)]))
98 with io.open(fileName, "w", encoding="utf8") as f:
99 f.writelines(not_matching_lines)
100
101 return True
102
103
104def getListOfFiles(clang_tidy_path: str) -> List[str]:
105 files = glob.glob(os.path.join(clang_tidy_path, "**"), recursive=True)
106 files += [
107 os.path.normpath(os.path.join(clang_tidy_path, "../docs/ReleaseNotes.rst"))
108 ]
109 files += glob.glob(
110 os.path.join(clang_tidy_path, "..", "test", "clang-tidy", "checkers", "**"),
111 recursive=True,
112 )
113 files += glob.glob(
114 os.path.join(clang_tidy_path, "..", "docs", "clang-tidy", "checks", "*.rst")
115 )
116 files += glob.glob(
117 os.path.join(
118 clang_tidy_path, "..", "docs", "clang-tidy", "checks", "*", "*.rst"
119 ),
120 recursive=True,
121 )
122 return [filename for filename in files if os.path.isfile(filename)]
123
124
125# Adapts the module's CMakelist file. Returns 'True' if it could add a new
126# entry and 'False' if the entry already existed.
127def adapt_cmake(module_path: str, check_name_camel: str) -> bool:
128 filename = os.path.join(module_path, "CMakeLists.txt")
129 with io.open(filename, "r", encoding="utf8") as f:
130 lines = f.readlines()
131
132 cpp_file = check_name_camel + ".cpp"
133
134 # Figure out whether this check already exists.
135 for line in lines:
136 if line.strip() == cpp_file:
137 return False
138
139 print("Updating %s..." % filename)
140 with io.open(filename, "w", encoding="utf8") as f:
141 cpp_found = False
142 file_added = False
143 for line in lines:
144 cpp_line = line.strip().endswith(".cpp")
145 if (not file_added) and (cpp_line or cpp_found):
146 cpp_found = True
147 if (line.strip() > cpp_file) or (not cpp_line):
148 f.write(" " + cpp_file + "\n")
149 file_added = True
150 f.write(line)
151
152 return True
153
154
155# Modifies the module to include the new check.
157 module_path: str, module: str, check_name: str, check_name_camel: str
158) -> None:
159 modulecpp = next(
160 iter(
161 filter(
162 lambda p: p.lower() == module.lower() + "tidymodule.cpp",
163 os.listdir(module_path),
164 )
165 )
166 )
167 filename = os.path.join(module_path, modulecpp)
168 with io.open(filename, "r", encoding="utf8") as f:
169 lines = f.readlines()
170
171 print("Updating %s..." % filename)
172 with io.open(filename, "w", encoding="utf8") as f:
173 header_added = False
174 header_found = False
175 check_added = False
176 check_decl = (
177 " CheckFactories.registerCheck<"
178 + check_name_camel
179 + '>(\n "'
180 + check_name
181 + '");\n'
182 )
183
184 for line in lines:
185 if not header_added:
186 match = re.search('#include "(.*)"', line)
187 if match:
188 header_found = True
189 if match.group(1) > check_name_camel:
190 header_added = True
191 f.write('#include "' + check_name_camel + '.h"\n')
192 elif header_found:
193 header_added = True
194 f.write('#include "' + check_name_camel + '.h"\n')
195
196 if not check_added:
197 if line.strip() == "}":
198 check_added = True
199 f.write(check_decl)
200 else:
201 match = re.search("registerCheck<(.*)>", line)
202 if match and match.group(1) > check_name_camel:
203 check_added = True
204 f.write(check_decl)
205 f.write(line)
206
207
208# Adds a release notes entry.
210 clang_tidy_path: str, old_check_name: str, new_check_name: str
211) -> None:
212 filename = os.path.normpath(
213 os.path.join(clang_tidy_path, "../docs/ReleaseNotes.rst")
214 )
215 with io.open(filename, "r", encoding="utf8") as f:
216 lines = f.readlines()
217
218 lineMatcher = re.compile("Renamed checks")
219 nextSectionMatcher = re.compile("Improvements to include-fixer")
220 checkMatcher = re.compile("- The '(.*)")
221
222 print("Updating %s..." % filename)
223 with io.open(filename, "w", encoding="utf8") as f:
224 note_added = False
225 header_found = False
226 add_note_here = False
227
228 for line in lines:
229 if not note_added:
230 match = lineMatcher.match(line)
231 match_next = nextSectionMatcher.match(line)
232 match_check = checkMatcher.match(line)
233 if match_check:
234 last_check = match_check.group(1)
235 if last_check > old_check_name:
236 add_note_here = True
237
238 if match_next:
239 add_note_here = True
240
241 if match:
242 header_found = True
243 f.write(line)
244 continue
245
246 if line.startswith("^^^^"):
247 f.write(line)
248 continue
249
250 if header_found and add_note_here:
251 if not line.startswith("^^^^"):
252 f.write(
253 """- The '%s' check was renamed to :doc:`%s
254 <clang-tidy/checks/%s/%s>`
255
256 """
257 % (
258 old_check_name,
259 new_check_name,
260 new_check_name.split("-", 1)[0],
261 "-".join(new_check_name.split("-")[1:]),
262 )
263 )
264 note_added = True
265
266 f.write(line)
267
268
269def main() -> None:
270 parser = argparse.ArgumentParser(description="Rename clang-tidy check.")
271 parser.add_argument("old_check_name", type=str, help="Old check name.")
272 parser.add_argument("new_check_name", type=str, help="New check name.")
273 parser.add_argument(
274 "--check_class_name",
275 type=str,
276 help="Old name of the class implementing the check.",
277 )
278 args = parser.parse_args()
279
280 old_module = args.old_check_name.split("-")[0]
281 new_module = args.new_check_name.split("-")[0]
282 old_name = "-".join(args.old_check_name.split("-")[1:])
283 new_name = "-".join(args.new_check_name.split("-")[1:])
284
285 if args.check_class_name:
286 check_name_camel = args.check_class_name
287 else:
288 check_name_camel = (
289 "".join(map(lambda elem: elem.capitalize(), old_name.split("-"))) + "Check"
290 )
291
292 new_check_name_camel = (
293 "".join(map(lambda elem: elem.capitalize(), new_name.split("-"))) + "Check"
294 )
295
296 clang_tidy_path = os.path.dirname(__file__)
297
298 header_guard_variants = [
299 (args.old_check_name.replace("-", "_")).upper() + "_CHECK",
300 (old_module + "_" + check_name_camel).upper(),
301 (old_module + "_" + new_check_name_camel).upper(),
302 args.old_check_name.replace("-", "_").upper(),
303 ]
304 header_guard_new = (new_module + "_" + new_check_name_camel).upper()
305
306 old_module_path = os.path.join(clang_tidy_path, old_module)
307 new_module_path = os.path.join(clang_tidy_path, new_module)
308
309 if old_module != new_module:
310 # Remove the check from the old module.
311 cmake_lists = os.path.join(old_module_path, "CMakeLists.txt")
312 check_found = deleteMatchingLines(cmake_lists, "\\b" + check_name_camel)
313 if not check_found:
314 print(
315 "Check name '%s' not found in %s. Exiting."
316 % (check_name_camel, cmake_lists)
317 )
318 sys.exit(1)
319
320 modulecpp = next(
321 iter(
322 filter(
323 lambda p: p.lower() == old_module.lower() + "tidymodule.cpp",
324 os.listdir(old_module_path),
325 )
326 )
327 )
329 os.path.join(old_module_path, modulecpp),
330 "\\b" + check_name_camel + "|\\b" + args.old_check_name,
331 )
332
333 for filename in getListOfFiles(clang_tidy_path):
334 originalName = filename
335 filename = fileRename(
336 filename, old_module + "/" + old_name, new_module + "/" + new_name
337 )
338 filename = fileRename(filename, args.old_check_name, args.new_check_name)
339 filename = fileRename(filename, check_name_camel, new_check_name_camel)
341 filename,
342 generateCommentLineHeader(originalName),
344 )
346 filename,
347 generateCommentLineSource(originalName),
349 )
350 for header_guard in header_guard_variants:
351 replaceInFile(filename, header_guard, header_guard_new)
352
353 if new_module + "/" + new_name + ".rst" in filename:
355 filename,
356 args.old_check_name + "\n" + "=" * len(args.old_check_name) + "\n",
357 args.new_check_name + "\n" + "=" * len(args.new_check_name) + "\n",
358 )
359
360 replaceInFile(filename, args.old_check_name, args.new_check_name)
362 filename,
363 old_module + "::" + check_name_camel,
364 new_module + "::" + new_check_name_camel,
365 )
367 filename,
368 old_module + "/" + check_name_camel,
369 new_module + "/" + new_check_name_camel,
370 )
372 filename, old_module + "/" + old_name, new_module + "/" + new_name
373 )
374 replaceInFile(filename, check_name_camel, new_check_name_camel)
375
376 if old_module != new_module or new_module == "llvm":
377 if new_module == "llvm":
378 new_namespace = new_module + "_check"
379 else:
380 new_namespace = new_module
381 check_implementation_files = glob.glob(
382 os.path.join(old_module_path, new_check_name_camel + "*")
383 )
384 for filename in check_implementation_files:
385 # Move check implementation to the directory of the new module.
386 filename = fileRename(filename, old_module_path, new_module_path)
388 filename,
389 "namespace clang::tidy::" + old_module + "[^ \n]*",
390 "namespace clang::tidy::" + new_namespace,
391 )
392
393 if old_module != new_module:
394
395 # Add check to the new module.
396 adapt_cmake(new_module_path, new_check_name_camel)
398 new_module_path, new_module, args.new_check_name, new_check_name_camel
399 )
400
401 os.system(os.path.join(clang_tidy_path, "add_new_check.py") + " --update-docs")
402 add_release_notes(clang_tidy_path, args.old_check_name, args.new_check_name)
403
404
405if __name__ == "__main__":
406 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)
Definition: rename_check.py:78
str generateCommentLineSource(str filename)
Definition: rename_check.py:66
None replaceInFileRegex(str fileName, str sFrom, str sTo)
Definition: rename_check.py:20
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)
str generateCommentLineHeader(str filename)
Definition: rename_check.py:54
bool deleteMatchingLines(str fileName, str pattern)
Definition: rename_check.py:87
None replaceInFile(str fileName, str sFrom, str sTo)
Definition: rename_check.py:38