blob: 49798f7f55544bb356f12a05dbaa5d4808f4dbdc [file] [log] [blame]
Piotr Zegar2e5bbce2015-10-11 07:58:34 +00001#!/usr/bin/env python
2#
3#===- rename_check.py - clang-tidy check renamer -------------*- python -*--===#
4#
5# The LLVM Compiler Infrastructure
6#
7# This file is distributed under the University of Illinois Open Source
8# License. See LICENSE.TXT for details.
9#
10#===------------------------------------------------------------------------===#
11
Kirill Bobyrevb0cf6a32016-11-08 11:43:50 +000012import argparse
Alexander Kornienko6b334a22017-11-23 12:08:53 +000013import glob
14import os
15import re
Kirill Bobyrevb0cf6a32016-11-08 11:43:50 +000016
Piotr Zegar2e5bbce2015-10-11 07:58:34 +000017
18def replaceInFile(fileName, sFrom, sTo):
19 if sFrom == sTo:
20 return
21 txt = None
22 with open(fileName, "r") as f:
23 txt = f.read()
24
25 if sFrom not in txt:
26 return
27
28 txt = txt.replace(sFrom, sTo)
Alexander Kornienko6b334a22017-11-23 12:08:53 +000029 print("Replacing '%s' -> '%s' in '%s'..." % (sFrom, sTo, fileName))
Piotr Zegar2e5bbce2015-10-11 07:58:34 +000030 with open(fileName, "w") as f:
31 f.write(txt)
32
Kirill Bobyrevb0cf6a32016-11-08 11:43:50 +000033
Piotr Zegar2e5bbce2015-10-11 07:58:34 +000034def generateCommentLineHeader(filename):
35 return ''.join(['//===--- ',
36 os.path.basename(filename),
37 ' - clang-tidy ',
38 '-' * max(0, 42 - len(os.path.basename(filename))),
39 '*- C++ -*-===//'])
40
Kirill Bobyrevb0cf6a32016-11-08 11:43:50 +000041
Piotr Zegar2e5bbce2015-10-11 07:58:34 +000042def generateCommentLineSource(filename):
43 return ''.join(['//===--- ',
44 os.path.basename(filename),
45 ' - clang-tidy',
46 '-' * max(0, 52 - len(os.path.basename(filename))),
47 '-===//'])
48
Kirill Bobyrevb0cf6a32016-11-08 11:43:50 +000049
Piotr Zegar2e5bbce2015-10-11 07:58:34 +000050def fileRename(fileName, sFrom, sTo):
Alexander Kornienko6b334a22017-11-23 12:08:53 +000051 if sFrom not in fileName or sFrom == sTo:
Piotr Zegar2e5bbce2015-10-11 07:58:34 +000052 return fileName
53 newFileName = fileName.replace(sFrom, sTo)
Alexander Kornienko6b334a22017-11-23 12:08:53 +000054 print("Renaming '%s' -> '%s'..." % (fileName, newFileName))
Piotr Zegar2e5bbce2015-10-11 07:58:34 +000055 os.rename(fileName, newFileName)
56 return newFileName
57
Alexander Kornienko6b334a22017-11-23 12:08:53 +000058def deleteMatchingLines(fileName, pattern):
59 lines = None
60 with open(fileName, "r") as f:
61 lines = f.readlines()
62
63 not_matching_lines = [l for l in lines if not re.search(pattern, l)]
Alexander Kornienko1c81d912017-11-23 14:59:19 +000064 if len(not_matching_lines) == len(lines):
Alexander Kornienko6b334a22017-11-23 12:08:53 +000065 return False
66
67 print("Removing lines matching '%s' in '%s'..." % (pattern, fileName))
68 print(' ' + ' '.join([l for l in lines if re.search(pattern, l)]))
69 with open(fileName, "w") as f:
70 f.writelines(not_matching_lines)
71
72 return True
Kirill Bobyrevb0cf6a32016-11-08 11:43:50 +000073
Piotr Zegar2e5bbce2015-10-11 07:58:34 +000074def getListOfFiles(clang_tidy_path):
Kirill Bobyrevb0cf6a32016-11-08 11:43:50 +000075 files = glob.glob(os.path.join(clang_tidy_path, '*'))
Piotr Zegar2e5bbce2015-10-11 07:58:34 +000076 for dirname in files:
77 if os.path.isdir(dirname):
Kirill Bobyrevb0cf6a32016-11-08 11:43:50 +000078 files += glob.glob(os.path.join(dirname, '*'))
79 files += glob.glob(os.path.join(clang_tidy_path, '..', 'test',
80 'clang-tidy', '*'))
81 files += glob.glob(os.path.join(clang_tidy_path, '..', 'docs',
82 'clang-tidy', 'checks', '*'))
Piotr Zegar2e5bbce2015-10-11 07:58:34 +000083 return [filename for filename in files if os.path.isfile(filename)]
84
Alexander Kornienko6b334a22017-11-23 12:08:53 +000085# Adapts the module's CMakelist file. Returns 'True' if it could add a new entry
86# and 'False' if the entry already existed.
87def adapt_cmake(module_path, check_name_camel):
88 filename = os.path.join(module_path, 'CMakeLists.txt')
89 with open(filename, 'r') as f:
90 lines = f.readlines()
91
92 cpp_file = check_name_camel + '.cpp'
93
94 # Figure out whether this check already exists.
95 for line in lines:
96 if line.strip() == cpp_file:
97 return False
98
99 print('Updating %s...' % filename)
100 with open(filename, 'wb') as f:
101 cpp_found = False
102 file_added = False
103 for line in lines:
104 cpp_line = line.strip().endswith('.cpp')
105 if (not file_added) and (cpp_line or cpp_found):
106 cpp_found = True
107 if (line.strip() > cpp_file) or (not cpp_line):
108 f.write(' ' + cpp_file + '\n')
109 file_added = True
110 f.write(line)
111
112 return True
113
114# Modifies the module to include the new check.
115def adapt_module(module_path, module, check_name, check_name_camel):
116 modulecpp = filter(lambda p: p.lower() == module.lower() + 'tidymodule.cpp',
117 os.listdir(module_path))[0]
118 filename = os.path.join(module_path, modulecpp)
119 with open(filename, 'r') as f:
120 lines = f.readlines()
121
122 print('Updating %s...' % filename)
123 with open(filename, 'wb') as f:
124 header_added = False
125 header_found = False
126 check_added = False
127 check_decl = (' CheckFactories.registerCheck<' + check_name_camel +
128 '>(\n "' + check_name + '");\n')
129
130 for line in lines:
131 if not header_added:
132 match = re.search('#include "(.*)"', line)
133 if match:
134 header_found = True
135 if match.group(1) > check_name_camel:
136 header_added = True
137 f.write('#include "' + check_name_camel + '.h"\n')
138 elif header_found:
139 header_added = True
140 f.write('#include "' + check_name_camel + '.h"\n')
141
142 if not check_added:
143 if line.strip() == '}':
144 check_added = True
145 f.write(check_decl)
146 else:
147 match = re.search('registerCheck<(.*)>', line)
148 if match and match.group(1) > check_name_camel:
149 check_added = True
150 f.write(check_decl)
151 f.write(line)
152
153
154# Adds a release notes entry.
155def add_release_notes(clang_tidy_path, old_check_name, new_check_name):
156 filename = os.path.normpath(os.path.join(clang_tidy_path,
157 '../docs/ReleaseNotes.rst'))
158 with open(filename, 'r') as f:
159 lines = f.readlines()
160
161 print('Updating %s...' % filename)
162 with open(filename, 'wb') as f:
163 note_added = False
164 header_found = False
165
166 for line in lines:
167 if not note_added:
168 match = re.search('Improvements to clang-tidy', line)
169 if match:
170 header_found = True
171 elif header_found:
172 if not line.startswith('----'):
173 f.write("""
174- The '%s' check was renamed to `%s
175 <http://clang.llvm.org/extra/clang-tidy/checks/%s.html>`_
176""" % (old_check_name, new_check_name, new_check_name))
177 note_added = True
178
179 f.write(line)
Kirill Bobyrevb0cf6a32016-11-08 11:43:50 +0000180
Piotr Zegar2e5bbce2015-10-11 07:58:34 +0000181def main():
Kirill Bobyrevb0cf6a32016-11-08 11:43:50 +0000182 parser = argparse.ArgumentParser(description='Rename clang-tidy check.')
Kirill Bobyrevb0cf6a32016-11-08 11:43:50 +0000183 parser.add_argument('old_check_name', type=str,
184 help='Old check name.')
185 parser.add_argument('new_check_name', type=str,
186 help='New check name.')
Alexander Kornienko1f71b912017-11-27 17:59:26 +0000187 parser.add_argument('--check_class_name', type=str,
188 help='Old name of the class implementing the check.')
Kirill Bobyrevb0cf6a32016-11-08 11:43:50 +0000189 args = parser.parse_args()
Piotr Zegar2e5bbce2015-10-11 07:58:34 +0000190
Alexander Kornienko6b334a22017-11-23 12:08:53 +0000191 old_module = args.old_check_name.split('-')[0]
192 new_module = args.new_check_name.split('-')[0]
Alexander Kornienko1f71b912017-11-27 17:59:26 +0000193 if args.check_class_name:
194 check_name_camel = args.check_class_name
195 else:
196 check_name_camel = (''.join(map(lambda elem: elem.capitalize(),
197 args.old_check_name.split('-')[1:])) +
198 'Check')
199
Alexander Kornienko6b334a22017-11-23 12:08:53 +0000200 new_check_name_camel = (''.join(map(lambda elem: elem.capitalize(),
201 args.new_check_name.split('-')[1:])) +
Kirill Bobyrevb0cf6a32016-11-08 11:43:50 +0000202 'Check')
Piotr Zegar2e5bbce2015-10-11 07:58:34 +0000203
Kirill Bobyrevb0cf6a32016-11-08 11:43:50 +0000204 clang_tidy_path = os.path.dirname(__file__)
Piotr Zegar2e5bbce2015-10-11 07:58:34 +0000205
Alexander Kornienkob43950d2017-11-25 08:49:04 +0000206 header_guard_variants = [
207 (old_module + '_' + new_check_name_camel).upper(),
208 args.old_check_name.replace('-', '_').upper()]
Alexander Kornienkoa616a552017-11-24 14:33:06 +0000209 header_guard_new = (new_module + '_' + new_check_name_camel).upper()
Alexander Kornienko6b334a22017-11-23 12:08:53 +0000210
211 old_module_path = os.path.join(clang_tidy_path, old_module)
212 new_module_path = os.path.join(clang_tidy_path, new_module)
213
214 # Remove the check from the old module.
215 cmake_lists = os.path.join(old_module_path, 'CMakeLists.txt')
Alexander Kornienko4170f042018-01-30 14:55:39 +0000216 check_found = deleteMatchingLines(cmake_lists, '\\b' + check_name_camel)
Alexander Kornienko6b334a22017-11-23 12:08:53 +0000217 if not check_found:
218 print("Check name '%s' not found in %s. Exiting." %
219 (check_name_camel, cmake_lists))
220 return 1
221
222 modulecpp = filter(
223 lambda p: p.lower() == old_module.lower() + 'tidymodule.cpp',
224 os.listdir(old_module_path))[0]
225 deleteMatchingLines(os.path.join(old_module_path, modulecpp),
Alexander Kornienko4170f042018-01-30 14:55:39 +0000226 '\\b' + check_name_camel + '|\\b' + args.old_check_name)
Piotr Zegar2e5bbce2015-10-11 07:58:34 +0000227
228 for filename in getListOfFiles(clang_tidy_path):
229 originalName = filename
Kirill Bobyrevb0cf6a32016-11-08 11:43:50 +0000230 filename = fileRename(filename, args.old_check_name,
231 args.new_check_name)
Alexander Kornienko6b334a22017-11-23 12:08:53 +0000232 filename = fileRename(filename, check_name_camel, new_check_name_camel)
Kirill Bobyrevb0cf6a32016-11-08 11:43:50 +0000233 replaceInFile(filename, generateCommentLineHeader(originalName),
234 generateCommentLineHeader(filename))
235 replaceInFile(filename, generateCommentLineSource(originalName),
236 generateCommentLineSource(filename))
Alexander Kornienkob43950d2017-11-25 08:49:04 +0000237 for header_guard in header_guard_variants:
238 replaceInFile(filename, header_guard, header_guard_new)
Alexander Kornienkoab0b81c2017-11-23 14:05:32 +0000239
240 if args.new_check_name + '.rst' in filename:
241 replaceInFile(
242 filename,
243 args.old_check_name + '\n' + '=' * len(args.old_check_name) + '\n',
244 args.new_check_name + '\n' + '=' * len(args.new_check_name) + '\n')
245
Kirill Bobyrevb0cf6a32016-11-08 11:43:50 +0000246 replaceInFile(filename, args.old_check_name, args.new_check_name)
Alexander Kornienko1f71b912017-11-27 17:59:26 +0000247 replaceInFile(filename, old_module + '::' + check_name_camel,
248 new_module + '::' + new_check_name_camel)
249 replaceInFile(filename, old_module + '/' + check_name_camel,
250 new_module + '/' + new_check_name_camel)
Alexander Kornienko6b334a22017-11-23 12:08:53 +0000251 replaceInFile(filename, check_name_camel, new_check_name_camel)
252
253 if old_module != new_module:
254 check_implementation_files = glob.glob(
255 os.path.join(old_module_path, new_check_name_camel + '*'))
256 for filename in check_implementation_files:
257 # Move check implementation to the directory of the new module.
258 filename = fileRename(filename, old_module_path, new_module_path)
259 replaceInFile(filename, 'namespace ' + old_module,
260 'namespace ' + new_module)
261
262 # Add check to the new module.
263 adapt_cmake(new_module_path, new_check_name_camel)
264 adapt_module(new_module_path, new_module, args.new_check_name,
265 new_check_name_camel)
266
267 os.system(os.path.join(clang_tidy_path, 'add_new_check.py')
268 + ' --update-docs')
269 add_release_notes(clang_tidy_path, args.old_check_name, args.new_check_name)
Piotr Zegar2e5bbce2015-10-11 07:58:34 +0000270
271if __name__ == '__main__':
272 main()