[docs] Add LICENSE.txt to the root of the mono-repo
[llvm-project.git] / clang-tools-extra / clang-tidy / rename_check.py
blob9c2021751e0e598f5707e8ef5899720dfc2a91b0
1 #!/usr/bin/env python
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
13 import argparse
14 import glob
15 import io
16 import os
17 import re
19 def replaceInFileRegex(fileName, sFrom, sTo):
20 if sFrom == sTo:
21 return
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.
27 txt = None
28 with io.open(fileName, 'r', encoding='utf8') as f:
29 txt = f.read()
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:
34 f.write(txt)
37 def replaceInFile(fileName, sFrom, sTo):
38 if sFrom == sTo:
39 return
40 txt = None
41 with io.open(fileName, 'r', encoding='utf8') as f:
42 txt = f.read()
44 if sFrom not in txt:
45 return
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:
50 f.write(txt)
53 def generateCommentLineHeader(filename):
54 return ''.join(['//===--- ',
55 os.path.basename(filename),
56 ' - clang-tidy ',
57 '-' * max(0, 42 - len(os.path.basename(filename))),
58 '*- C++ -*-===//'])
61 def generateCommentLineSource(filename):
62 return ''.join(['//===--- ',
63 os.path.basename(filename),
64 ' - clang-tidy',
65 '-' * max(0, 52 - len(os.path.basename(filename))),
66 '-===//'])
69 def fileRename(fileName, sFrom, sTo):
70 if sFrom not in fileName or sFrom == sTo:
71 return fileName
72 newFileName = fileName.replace(sFrom, sTo)
73 print("Renaming '%s' -> '%s'..." % (fileName, newFileName))
74 os.rename(fileName, newFileName)
75 return newFileName
78 def deleteMatchingLines(fileName, pattern):
79 lines = None
80 with io.open(fileName, 'r', encoding='utf8') as f:
81 lines = f.readlines()
83 not_matching_lines = [l for l in lines if not re.search(pattern, l)]
84 if len(not_matching_lines) == len(lines):
85 return False
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)
92 return True
95 def getListOfFiles(clang_tidy_path):
96 files = glob.glob(os.path.join(clang_tidy_path, '*'))
97 for dirname in files:
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',
101 'clang-tidy', '*'))
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.
117 for line in lines:
118 if line.strip() == cpp_file:
119 return False
121 print('Updating %s...' % filename)
122 with io.open(filename, 'w', encoding='utf8') as f:
123 cpp_found = False
124 file_added = False
125 for line in lines:
126 cpp_line = line.strip().endswith('.cpp')
127 if (not file_added) and (cpp_line or cpp_found):
128 cpp_found = True
129 if (line.strip() > cpp_file) or (not cpp_line):
130 f.write(' ' + cpp_file + '\n')
131 file_added = True
132 f.write(line)
134 return True
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:
147 header_added = False
148 header_found = False
149 check_added = False
150 check_decl = (' CheckFactories.registerCheck<' + check_name_camel +
151 '>(\n "' + check_name + '");\n')
153 for line in lines:
154 if not header_added:
155 match = re.search('#include "(.*)"', line)
156 if match:
157 header_found = True
158 if match.group(1) > check_name_camel:
159 header_added = True
160 f.write('#include "' + check_name_camel + '.h"\n')
161 elif header_found:
162 header_added = True
163 f.write('#include "' + check_name_camel + '.h"\n')
165 if not check_added:
166 if line.strip() == '}':
167 check_added = True
168 f.write(check_decl)
169 else:
170 match = re.search('registerCheck<(.*)>', line)
171 if match and match.group(1) > check_name_camel:
172 check_added = True
173 f.write(check_decl)
174 f.write(line)
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:
190 note_added = False
191 header_found = False
192 add_note_here = False
194 for line in lines:
195 if not note_added:
196 match = lineMatcher.match(line)
197 match_next = nextSectionMatcher.match(line)
198 match_check = checkMatcher.match(line)
199 if match_check:
200 last_check = match_check.group(1)
201 if last_check > old_check_name:
202 add_note_here = True
204 if match_next:
205 add_note_here = True
207 if match:
208 header_found = True
209 f.write(line)
210 continue
212 if line.startswith('^^^^'):
213 f.write(line)
214 continue
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))
222 note_added = True
224 f.write(line)
226 def main():
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
240 else:
241 check_name_camel = (''.join(map(lambda elem: elem.capitalize(),
242 args.old_check_name.split('-')[1:])) +
243 'Check')
245 new_check_name_camel = (''.join(map(lambda elem: elem.capitalize(),
246 args.new_check_name.split('-')[1:])) +
247 'Check')
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)
265 if not check_found:
266 print("Check name '%s' not found in %s. Exiting." %
267 (check_name_camel, cmake_lists))
268 return 1
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,
279 args.new_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:
289 replaceInFile(
290 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'
304 else:
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):
315 return
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')
323 + ' --update-docs')
324 add_release_notes(clang_tidy_path, args.old_check_name, args.new_check_name)
327 if __name__ == '__main__':
328 main()