3 # ===- rename_check.py - clang-tidy check renamer ------------*- python -*--===#
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
9 # ===-----------------------------------------------------------------------===#
11 from __future__
import unicode_literals
20 def replaceInFileRegex(fileName
, sFrom
, sTo
):
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.
29 with io
.open(fileName
, "r", encoding
="utf8") as f
:
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
:
38 def replaceInFile(fileName
, sFrom
, sTo
):
42 with io
.open(fileName
, "r", encoding
="utf8") as f
:
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
:
54 def generateCommentLineHeader(filename
):
58 os
.path
.basename(filename
),
60 "-" * max(0, 42 - len(os
.path
.basename(filename
))),
66 def generateCommentLineSource(filename
):
70 os
.path
.basename(filename
),
72 "-" * max(0, 52 - len(os
.path
.basename(filename
))),
78 def fileRename(fileName
, sFrom
, sTo
):
79 if sFrom
not in fileName
or sFrom
== sTo
:
81 newFileName
= fileName
.replace(sFrom
, sTo
)
82 print("Renaming '%s' -> '%s'..." % (fileName
, newFileName
))
83 os
.rename(fileName
, newFileName
)
87 def deleteMatchingLines(fileName
, pattern
):
89 with io
.open(fileName
, "r", encoding
="utf8") as f
:
92 not_matching_lines
= [l
for l
in lines
if not re
.search(pattern
, l
)]
93 if len(not_matching_lines
) == len(lines
):
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
)
104 def getListOfFiles(clang_tidy_path
):
105 files
= glob
.glob(os
.path
.join(clang_tidy_path
, "**"), recursive
=True)
107 os
.path
.normpath(os
.path
.join(clang_tidy_path
, "../docs/ReleaseNotes.rst"))
110 os
.path
.join(clang_tidy_path
, "..", "test", "clang-tidy", "checkers", "**"),
114 os
.path
.join(clang_tidy_path
, "..", "docs", "clang-tidy", "checks", "*.rst")
118 clang_tidy_path
, "..", "docs", "clang-tidy", "checks", "*", "*.rst"
122 return [filename
for filename
in files
if os
.path
.isfile(filename
)]
125 # Adapts the module's CMakelist file. Returns 'True' if it could add a new
126 # entry and 'False' if the entry already existed.
127 def adapt_cmake(module_path
, check_name_camel
):
128 filename
= os
.path
.join(module_path
, "CMakeLists.txt")
129 with io
.open(filename
, "r", encoding
="utf8") as f
:
130 lines
= f
.readlines()
132 cpp_file
= check_name_camel
+ ".cpp"
134 # Figure out whether this check already exists.
136 if line
.strip() == cpp_file
:
139 print("Updating %s..." % filename
)
140 with io
.open(filename
, "w", encoding
="utf8") as f
:
144 cpp_line
= line
.strip().endswith(".cpp")
145 if (not file_added
) and (cpp_line
or cpp_found
):
147 if (line
.strip() > cpp_file
) or (not cpp_line
):
148 f
.write(" " + cpp_file
+ "\n")
155 # Modifies the module to include the new check.
156 def adapt_module(module_path
, module
, check_name
, check_name_camel
):
160 lambda p
: p
.lower() == module
.lower() + "tidymodule.cpp",
161 os
.listdir(module_path
),
165 filename
= os
.path
.join(module_path
, modulecpp
)
166 with io
.open(filename
, "r", encoding
="utf8") as f
:
167 lines
= f
.readlines()
169 print("Updating %s..." % filename
)
170 with io
.open(filename
, "w", encoding
="utf8") as f
:
175 " CheckFactories.registerCheck<"
184 match
= re
.search('#include "(.*)"', line
)
187 if match
.group(1) > check_name_camel
:
189 f
.write('#include "' + check_name_camel
+ '.h"\n')
192 f
.write('#include "' + check_name_camel
+ '.h"\n')
195 if line
.strip() == "}":
199 match
= re
.search("registerCheck<(.*)>", line
)
200 if match
and match
.group(1) > check_name_camel
:
206 # Adds a release notes entry.
207 def add_release_notes(clang_tidy_path
, old_check_name
, new_check_name
):
208 filename
= os
.path
.normpath(
209 os
.path
.join(clang_tidy_path
, "../docs/ReleaseNotes.rst")
211 with io
.open(filename
, "r", encoding
="utf8") as f
:
212 lines
= f
.readlines()
214 lineMatcher
= re
.compile("Renamed checks")
215 nextSectionMatcher
= re
.compile("Improvements to include-fixer")
216 checkMatcher
= re
.compile("- The '(.*)")
218 print("Updating %s..." % filename
)
219 with io
.open(filename
, "w", encoding
="utf8") as f
:
222 add_note_here
= False
226 match
= lineMatcher
.match(line
)
227 match_next
= nextSectionMatcher
.match(line
)
228 match_check
= checkMatcher
.match(line
)
230 last_check
= match_check
.group(1)
231 if last_check
> old_check_name
:
242 if line
.startswith("^^^^"):
246 if header_found
and add_note_here
:
247 if not line
.startswith("^^^^"):
249 """- The '%s' check was renamed to :doc:`%s
250 <clang-tidy/checks/%s/%s>`
256 new_check_name
.split("-", 1)[0],
257 "-".join(new_check_name
.split("-")[1:]),
266 parser
= argparse
.ArgumentParser(description
="Rename clang-tidy check.")
267 parser
.add_argument("old_check_name", type=str, help="Old check name.")
268 parser
.add_argument("new_check_name", type=str, help="New check name.")
270 "--check_class_name",
272 help="Old name of the class implementing the check.",
274 args
= parser
.parse_args()
276 old_module
= args
.old_check_name
.split("-")[0]
277 new_module
= args
.new_check_name
.split("-")[0]
278 old_name
= "-".join(args
.old_check_name
.split("-")[1:])
279 new_name
= "-".join(args
.new_check_name
.split("-")[1:])
281 if args
.check_class_name
:
282 check_name_camel
= args
.check_class_name
285 "".join(map(lambda elem
: elem
.capitalize(), old_name
.split("-"))) + "Check"
288 new_check_name_camel
= (
289 "".join(map(lambda elem
: elem
.capitalize(), new_name
.split("-"))) + "Check"
292 clang_tidy_path
= os
.path
.dirname(__file__
)
294 header_guard_variants
= [
295 (args
.old_check_name
.replace("-", "_")).upper() + "_CHECK",
296 (old_module
+ "_" + check_name_camel
).upper(),
297 (old_module
+ "_" + new_check_name_camel
).upper(),
298 args
.old_check_name
.replace("-", "_").upper(),
300 header_guard_new
= (new_module
+ "_" + new_check_name_camel
).upper()
302 old_module_path
= os
.path
.join(clang_tidy_path
, old_module
)
303 new_module_path
= os
.path
.join(clang_tidy_path
, new_module
)
305 if old_module
!= new_module
:
306 # Remove the check from the old module.
307 cmake_lists
= os
.path
.join(old_module_path
, "CMakeLists.txt")
308 check_found
= deleteMatchingLines(cmake_lists
, "\\b" + check_name_camel
)
311 "Check name '%s' not found in %s. Exiting."
312 % (check_name_camel
, cmake_lists
)
319 lambda p
: p
.lower() == old_module
.lower() + "tidymodule.cpp",
320 os
.listdir(old_module_path
),
325 os
.path
.join(old_module_path
, modulecpp
),
326 "\\b" + check_name_camel
+ "|\\b" + args
.old_check_name
,
329 for filename
in getListOfFiles(clang_tidy_path
):
330 originalName
= filename
331 filename
= fileRename(
332 filename
, old_module
+ "/" + old_name
, new_module
+ "/" + new_name
334 filename
= fileRename(filename
, args
.old_check_name
, args
.new_check_name
)
335 filename
= fileRename(filename
, check_name_camel
, new_check_name_camel
)
338 generateCommentLineHeader(originalName
),
339 generateCommentLineHeader(filename
),
343 generateCommentLineSource(originalName
),
344 generateCommentLineSource(filename
),
346 for header_guard
in header_guard_variants
:
347 replaceInFile(filename
, header_guard
, header_guard_new
)
349 if new_module
+ "/" + new_name
+ ".rst" in filename
:
352 args
.old_check_name
+ "\n" + "=" * len(args
.old_check_name
) + "\n",
353 args
.new_check_name
+ "\n" + "=" * len(args
.new_check_name
) + "\n",
356 replaceInFile(filename
, args
.old_check_name
, args
.new_check_name
)
359 old_module
+ "::" + check_name_camel
,
360 new_module
+ "::" + new_check_name_camel
,
364 old_module
+ "/" + check_name_camel
,
365 new_module
+ "/" + new_check_name_camel
,
368 filename
, old_module
+ "/" + old_name
, new_module
+ "/" + new_name
370 replaceInFile(filename
, check_name_camel
, new_check_name_camel
)
372 if old_module
!= new_module
or new_module
== "llvm":
373 if new_module
== "llvm":
374 new_namespace
= new_module
+ "_check"
376 new_namespace
= new_module
377 check_implementation_files
= glob
.glob(
378 os
.path
.join(old_module_path
, new_check_name_camel
+ "*")
380 for filename
in check_implementation_files
:
381 # Move check implementation to the directory of the new module.
382 filename
= fileRename(filename
, old_module_path
, new_module_path
)
385 "namespace clang::tidy::" + old_module
+ "[^ \n]*",
386 "namespace clang::tidy::" + new_namespace
,
389 if old_module
!= new_module
:
391 # Add check to the new module.
392 adapt_cmake(new_module_path
, new_check_name_camel
)
394 new_module_path
, new_module
, args
.new_check_name
, new_check_name_camel
397 os
.system(os
.path
.join(clang_tidy_path
, "add_new_check.py") + " --update-docs")
398 add_release_notes(clang_tidy_path
, args
.old_check_name
, args
.new_check_name
)
401 if __name__
== "__main__":