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
19 def replaceInFileRegex(fileName
, sFrom
, sTo
):
23 # The documentation files are encoded using UTF-8, however on Windows the
24 # default encoding might be different (e.g. CP-1252). To make sure UTF-8 is
25 # always used, use `io.open(filename, mode, encoding='utf8')` for reading and
26 # writing files here and elsewhere.
28 with io
.open(fileName
, 'r', encoding
='utf8') as f
:
31 txt
= re
.sub(sFrom
, sTo
, txt
)
32 print("Replacing '%s' -> '%s' in '%s'..." % (sFrom
, sTo
, fileName
))
33 with io
.open(fileName
, 'w', encoding
='utf8') as f
:
37 def replaceInFile(fileName
, sFrom
, sTo
):
41 with io
.open(fileName
, 'r', encoding
='utf8') as f
:
47 txt
= txt
.replace(sFrom
, sTo
)
48 print("Replacing '%s' -> '%s' in '%s'..." % (sFrom
, sTo
, fileName
))
49 with io
.open(fileName
, 'w', encoding
='utf8') as f
:
53 def generateCommentLineHeader(filename
):
54 return ''.join(['//===--- ',
55 os
.path
.basename(filename
),
57 '-' * max(0, 42 - len(os
.path
.basename(filename
))),
61 def generateCommentLineSource(filename
):
62 return ''.join(['//===--- ',
63 os
.path
.basename(filename
),
65 '-' * max(0, 52 - len(os
.path
.basename(filename
))),
69 def fileRename(fileName
, sFrom
, sTo
):
70 if sFrom
not in fileName
or sFrom
== sTo
:
72 newFileName
= fileName
.replace(sFrom
, sTo
)
73 print("Renaming '%s' -> '%s'..." % (fileName
, newFileName
))
74 os
.rename(fileName
, newFileName
)
78 def deleteMatchingLines(fileName
, pattern
):
80 with io
.open(fileName
, 'r', encoding
='utf8') as f
:
83 not_matching_lines
= [l
for l
in lines
if not re
.search(pattern
, l
)]
84 if len(not_matching_lines
) == len(lines
):
87 print("Removing lines matching '%s' in '%s'..." % (pattern
, fileName
))
88 print(' ' + ' '.join([l
for l
in lines
if re
.search(pattern
, l
)]))
89 with io
.open(fileName
, 'w', encoding
='utf8') as f
:
90 f
.writelines(not_matching_lines
)
95 def getListOfFiles(clang_tidy_path
):
96 files
= glob
.glob(os
.path
.join(clang_tidy_path
, '*'))
98 if os
.path
.isdir(dirname
):
99 files
+= glob
.glob(os
.path
.join(dirname
, '*'))
100 files
+= glob
.glob(os
.path
.join(clang_tidy_path
, '..', 'test',
102 files
+= glob
.glob(os
.path
.join(clang_tidy_path
, '..', 'docs',
103 'clang-tidy', 'checks', '*'))
104 return [filename
for filename
in files
if os
.path
.isfile(filename
)]
107 # Adapts the module's CMakelist file. Returns 'True' if it could add a new
108 # entry and 'False' if the entry already existed.
109 def adapt_cmake(module_path
, check_name_camel
):
110 filename
= os
.path
.join(module_path
, 'CMakeLists.txt')
111 with io
.open(filename
, 'r', encoding
='utf8') as f
:
112 lines
= f
.readlines()
114 cpp_file
= check_name_camel
+ '.cpp'
116 # Figure out whether this check already exists.
118 if line
.strip() == cpp_file
:
121 print('Updating %s...' % filename
)
122 with io
.open(filename
, 'w', encoding
='utf8') as f
:
126 cpp_line
= line
.strip().endswith('.cpp')
127 if (not file_added
) and (cpp_line
or cpp_found
):
129 if (line
.strip() > cpp_file
) or (not cpp_line
):
130 f
.write(' ' + cpp_file
+ '\n')
136 # Modifies the module to include the new check.
137 def adapt_module(module_path
, module
, check_name
, check_name_camel
):
138 modulecpp
= next(iter(filter(
139 lambda p
: p
.lower() == module
.lower() + 'tidymodule.cpp',
140 os
.listdir(module_path
))))
141 filename
= os
.path
.join(module_path
, modulecpp
)
142 with io
.open(filename
, 'r', encoding
='utf8') as f
:
143 lines
= f
.readlines()
145 print('Updating %s...' % filename
)
146 with io
.open(filename
, 'w', encoding
='utf8') as f
:
150 check_decl
= (' CheckFactories.registerCheck<' + check_name_camel
+
151 '>(\n "' + check_name
+ '");\n')
155 match
= re
.search('#include "(.*)"', line
)
158 if match
.group(1) > check_name_camel
:
160 f
.write('#include "' + check_name_camel
+ '.h"\n')
163 f
.write('#include "' + check_name_camel
+ '.h"\n')
166 if line
.strip() == '}':
170 match
= re
.search('registerCheck<(.*)>', line
)
171 if match
and match
.group(1) > check_name_camel
:
177 # Adds a release notes entry.
178 def add_release_notes(clang_tidy_path
, old_check_name
, new_check_name
):
179 filename
= os
.path
.normpath(os
.path
.join(clang_tidy_path
,
180 '../docs/ReleaseNotes.rst'))
181 with io
.open(filename
, 'r', encoding
='utf8') as f
:
182 lines
= f
.readlines()
184 lineMatcher
= re
.compile('Renamed checks')
185 nextSectionMatcher
= re
.compile('Improvements to include-fixer')
186 checkMatcher
= re
.compile('- The \'(.*)')
188 print('Updating %s...' % filename
)
189 with io
.open(filename
, 'w', encoding
='utf8') as f
:
192 add_note_here
= False
196 match
= lineMatcher
.match(line
)
197 match_next
= nextSectionMatcher
.match(line
)
198 match_check
= checkMatcher
.match(line
)
200 last_check
= match_check
.group(1)
201 if last_check
> old_check_name
:
212 if line
.startswith('^^^^'):
216 if header_found
and add_note_here
:
217 if not line
.startswith('^^^^'):
218 f
.write("""- The '%s' check was renamed to :doc:`%s
219 <clang-tidy/checks/%s>`
221 """ % (old_check_name
, new_check_name
, new_check_name
))
227 parser
= argparse
.ArgumentParser(description
='Rename clang-tidy check.')
228 parser
.add_argument('old_check_name', type=str,
229 help='Old check name.')
230 parser
.add_argument('new_check_name', type=str,
231 help='New check name.')
232 parser
.add_argument('--check_class_name', type=str,
233 help='Old name of the class implementing the check.')
234 args
= parser
.parse_args()
236 old_module
= args
.old_check_name
.split('-')[0]
237 new_module
= args
.new_check_name
.split('-')[0]
238 if args
.check_class_name
:
239 check_name_camel
= args
.check_class_name
241 check_name_camel
= (''.join(map(lambda elem
: elem
.capitalize(),
242 args
.old_check_name
.split('-')[1:])) +
245 new_check_name_camel
= (''.join(map(lambda elem
: elem
.capitalize(),
246 args
.new_check_name
.split('-')[1:])) +
249 clang_tidy_path
= os
.path
.dirname(__file__
)
251 header_guard_variants
= [
252 (args
.old_check_name
.replace('-', '_')).upper() + '_CHECK',
253 (old_module
+ '_' + check_name_camel
).upper(),
254 (old_module
+ '_' + new_check_name_camel
).upper(),
255 args
.old_check_name
.replace('-', '_').upper()]
256 header_guard_new
= (new_module
+ '_' + new_check_name_camel
).upper()
258 old_module_path
= os
.path
.join(clang_tidy_path
, old_module
)
259 new_module_path
= os
.path
.join(clang_tidy_path
, new_module
)
261 if (args
.old_check_name
!= args
.new_check_name
):
262 # Remove the check from the old module.
263 cmake_lists
= os
.path
.join(old_module_path
, 'CMakeLists.txt')
264 check_found
= deleteMatchingLines(cmake_lists
, '\\b' + check_name_camel
)
266 print("Check name '%s' not found in %s. Exiting." %
267 (check_name_camel
, cmake_lists
))
270 modulecpp
= next(iter(filter(
271 lambda p
: p
.lower() == old_module
.lower() + 'tidymodule.cpp',
272 os
.listdir(old_module_path
))))
273 deleteMatchingLines(os
.path
.join(old_module_path
, modulecpp
),
274 '\\b' + check_name_camel
+ '|\\b' + args
.old_check_name
)
276 for filename
in getListOfFiles(clang_tidy_path
):
277 originalName
= filename
278 filename
= fileRename(filename
, args
.old_check_name
,
280 filename
= fileRename(filename
, check_name_camel
, new_check_name_camel
)
281 replaceInFile(filename
, generateCommentLineHeader(originalName
),
282 generateCommentLineHeader(filename
))
283 replaceInFile(filename
, generateCommentLineSource(originalName
),
284 generateCommentLineSource(filename
))
285 for header_guard
in header_guard_variants
:
286 replaceInFile(filename
, header_guard
, header_guard_new
)
288 if args
.new_check_name
+ '.rst' in filename
:
291 args
.old_check_name
+ '\n' + '=' * len(args
.old_check_name
) + '\n',
292 args
.new_check_name
+ '\n' + '=' * len(args
.new_check_name
) + '\n')
294 replaceInFile(filename
, args
.old_check_name
, args
.new_check_name
)
295 replaceInFile(filename
, old_module
+ '::' + check_name_camel
,
296 new_module
+ '::' + new_check_name_camel
)
297 replaceInFile(filename
, old_module
+ '/' + check_name_camel
,
298 new_module
+ '/' + new_check_name_camel
)
299 replaceInFile(filename
, check_name_camel
, new_check_name_camel
)
301 if old_module
!= new_module
or new_module
== 'llvm':
302 if new_module
== 'llvm':
303 new_namespace
= new_module
+ '_check'
305 new_namespace
= new_module
306 check_implementation_files
= glob
.glob(
307 os
.path
.join(old_module_path
, new_check_name_camel
+ '*'))
308 for filename
in check_implementation_files
:
309 # Move check implementation to the directory of the new module.
310 filename
= fileRename(filename
, old_module_path
, new_module_path
)
311 replaceInFileRegex(filename
, 'namespace ' + old_module
+ '[^ \n]*',
312 'namespace ' + new_namespace
)
314 if (args
.old_check_name
== args
.new_check_name
):
317 # Add check to the new module.
318 adapt_cmake(new_module_path
, new_check_name_camel
)
319 adapt_module(new_module_path
, new_module
, args
.new_check_name
,
320 new_check_name_camel
)
322 os
.system(os
.path
.join(clang_tidy_path
, 'add_new_check.py')
324 add_release_notes(clang_tidy_path
, args
.old_check_name
, args
.new_check_name
)
327 if __name__
== '__main__':