blob: 5076c0b2313f0b3ddabfd95b2769f957f5a28c4f [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)]
64 if not_matching_lines.count == lines.count:
65 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.')
187 args = parser.parse_args()
Piotr Zegar2e5bbce2015-10-11 07:58:34 +0000188
Alexander Kornienko6b334a22017-11-23 12:08:53 +0000189 old_module = args.old_check_name.split('-')[0]
190 new_module = args.new_check_name.split('-')[0]
Piotr Zegar2e5bbce2015-10-11 07:58:34 +0000191 check_name_camel = ''.join(map(lambda elem: elem.capitalize(),
Alexander Kornienko6b334a22017-11-23 12:08:53 +0000192 args.old_check_name.split('-')[1:])) + 'Check'
193 new_check_name_camel = (''.join(map(lambda elem: elem.capitalize(),
194 args.new_check_name.split('-')[1:])) +
Kirill Bobyrevb0cf6a32016-11-08 11:43:50 +0000195 'Check')
Piotr Zegar2e5bbce2015-10-11 07:58:34 +0000196
Kirill Bobyrevb0cf6a32016-11-08 11:43:50 +0000197 clang_tidy_path = os.path.dirname(__file__)
Piotr Zegar2e5bbce2015-10-11 07:58:34 +0000198
Alexander Kornienko6b334a22017-11-23 12:08:53 +0000199 header_guard_old = args.old_check_name.upper().replace('-', '_')
200 header_guard_new = args.new_check_name.upper().replace('-', '_')
201
202 old_module_path = os.path.join(clang_tidy_path, old_module)
203 new_module_path = os.path.join(clang_tidy_path, new_module)
204
205 # Remove the check from the old module.
206 cmake_lists = os.path.join(old_module_path, 'CMakeLists.txt')
207 check_found = deleteMatchingLines(cmake_lists, check_name_camel)
208 if not check_found:
209 print("Check name '%s' not found in %s. Exiting." %
210 (check_name_camel, cmake_lists))
211 return 1
212
213 modulecpp = filter(
214 lambda p: p.lower() == old_module.lower() + 'tidymodule.cpp',
215 os.listdir(old_module_path))[0]
216 deleteMatchingLines(os.path.join(old_module_path, modulecpp),
217 check_name_camel + '|' + args.old_check_name)
Piotr Zegar2e5bbce2015-10-11 07:58:34 +0000218
219 for filename in getListOfFiles(clang_tidy_path):
220 originalName = filename
Kirill Bobyrevb0cf6a32016-11-08 11:43:50 +0000221 filename = fileRename(filename, args.old_check_name,
222 args.new_check_name)
Alexander Kornienko6b334a22017-11-23 12:08:53 +0000223 filename = fileRename(filename, check_name_camel, new_check_name_camel)
Kirill Bobyrevb0cf6a32016-11-08 11:43:50 +0000224 replaceInFile(filename, generateCommentLineHeader(originalName),
225 generateCommentLineHeader(filename))
226 replaceInFile(filename, generateCommentLineSource(originalName),
227 generateCommentLineSource(filename))
Piotr Zegar2e5bbce2015-10-11 07:58:34 +0000228 replaceInFile(filename, header_guard_old, header_guard_new)
Alexander Kornienkoab0b81c2017-11-23 14:05:32 +0000229
230 if args.new_check_name + '.rst' in filename:
231 replaceInFile(
232 filename,
233 args.old_check_name + '\n' + '=' * len(args.old_check_name) + '\n',
234 args.new_check_name + '\n' + '=' * len(args.new_check_name) + '\n')
235
Kirill Bobyrevb0cf6a32016-11-08 11:43:50 +0000236 replaceInFile(filename, args.old_check_name, args.new_check_name)
Alexander Kornienko6b334a22017-11-23 12:08:53 +0000237 replaceInFile(filename, check_name_camel, new_check_name_camel)
238
239 if old_module != new_module:
240 check_implementation_files = glob.glob(
241 os.path.join(old_module_path, new_check_name_camel + '*'))
242 for filename in check_implementation_files:
243 # Move check implementation to the directory of the new module.
244 filename = fileRename(filename, old_module_path, new_module_path)
245 replaceInFile(filename, 'namespace ' + old_module,
246 'namespace ' + new_module)
247
248 # Add check to the new module.
249 adapt_cmake(new_module_path, new_check_name_camel)
250 adapt_module(new_module_path, new_module, args.new_check_name,
251 new_check_name_camel)
252
253 os.system(os.path.join(clang_tidy_path, 'add_new_check.py')
254 + ' --update-docs')
255 add_release_notes(clang_tidy_path, args.old_check_name, args.new_check_name)
Piotr Zegar2e5bbce2015-10-11 07:58:34 +0000256
257if __name__ == '__main__':
258 main()