clang-tools 10.0.0

rename_check.py
Go to the documentation of this file.
1 #!/usr/bin/env python
2 #
3 #===- rename_check.py - clang-tidy check renamer -------------*- python -*--===#
4 #
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
8 #
9 #===------------------------------------------------------------------------===#
10
11 import argparse
12 import glob
13 import os
14 import re
15
16
17 def replaceInFileRegex(fileName, sFrom, sTo):
18 if sFrom == sTo:
19 return
20 txt = None
21 with open(fileName, "r") as f:
22 txt = f.read()
23
24 txt = re.sub(sFrom, sTo, txt)
25 print("Replacing '%s' -> '%s' in '%s'..." % (sFrom, sTo, fileName))
26 with open(fileName, "w") as f:
27 f.write(txt)
28
29 def replaceInFile(fileName, sFrom, sTo):
30 if sFrom == sTo:
31 return
32 txt = None
33 with open(fileName, "r") as f:
34 txt = f.read()
35
36 if sFrom not in txt:
37 return
38
39 txt = txt.replace(sFrom, sTo)
40 print("Replacing '%s' -> '%s' in '%s'..." % (sFrom, sTo, fileName))
41 with open(fileName, "w") as f:
42 f.write(txt)
43
44
45 def generateCommentLineHeader(filename):
46 return ''.join(['//===--- ',
47 os.path.basename(filename),
48 ' - clang-tidy ',
49 '-' * max(0, 42 - len(os.path.basename(filename))),
50 '*- C++ -*-===//'])
51
52
53 def generateCommentLineSource(filename):
54 return ''.join(['//===--- ',
55 os.path.basename(filename),
56 ' - clang-tidy',
57 '-' * max(0, 52 - len(os.path.basename(filename))),
58 '-===//'])
59
60
61 def fileRename(fileName, sFrom, sTo):
62 if sFrom not in fileName or sFrom == sTo:
63 return fileName
64 newFileName = fileName.replace(sFrom, sTo)
65 print("Renaming '%s' -> '%s'..." % (fileName, newFileName))
66 os.rename(fileName, newFileName)
67 return newFileName
68
69 def deleteMatchingLines(fileName, pattern):
70 lines = None
71 with open(fileName, "r") as f:
72 lines = f.readlines()
73
74 not_matching_lines = [l for l in lines if not re.search(pattern, l)]
75 if len(not_matching_lines) == len(lines):
76 return False
77
78 print("Removing lines matching '%s' in '%s'..." % (pattern, fileName))
79 print(' ' + ' '.join([l for l in lines if re.search(pattern, l)]))
80 with open(fileName, "w") as f:
81 f.writelines(not_matching_lines)
82
83 return True
84
85 def getListOfFiles(clang_tidy_path):
86 files = glob.glob(os.path.join(clang_tidy_path, '*'))
87 for dirname in files:
88 if os.path.isdir(dirname):
89 files += glob.glob(os.path.join(dirname, '*'))
90 files += glob.glob(os.path.join(clang_tidy_path, '..', 'test',
91 'clang-tidy', '*'))
92 files += glob.glob(os.path.join(clang_tidy_path, '..', 'docs',
93 'clang-tidy', 'checks', '*'))
94 return [filename for filename in files if os.path.isfile(filename)]
95
96 # Adapts the module's CMakelist file. Returns 'True' if it could add a new entry
97 # and 'False' if the entry already existed.
98 def adapt_cmake(module_path, check_name_camel):
99 filename = os.path.join(module_path, 'CMakeLists.txt')
100 with open(filename, 'r') as f:
101 lines = f.readlines()
102
103 cpp_file = check_name_camel + '.cpp'
104
105 # Figure out whether this check already exists.
106 for line in lines:
107 if line.strip() == cpp_file:
108 return False
109
110 print('Updating %s...' % filename)
111 with open(filename, 'wb') as f:
112 cpp_found = False
113 file_added = False
114 for line in lines:
115 cpp_line = line.strip().endswith('.cpp')
116 if (not file_added) and (cpp_line or cpp_found):
117 cpp_found = True
118 if (line.strip() > cpp_file) or (not cpp_line):
119 f.write(' ' + cpp_file + '\n')
120 file_added = True
121 f.write(line)
122
123 return True
124
125 # Modifies the module to include the new check.
126 def adapt_module(module_path, module, check_name, check_name_camel):
127 modulecpp = filter(lambda p: p.lower() == module.lower() + 'tidymodule.cpp',
128 os.listdir(module_path))[0]
129 filename = os.path.join(module_path, modulecpp)
130 with open(filename, 'r') as f:
131 lines = f.readlines()
132
133 print('Updating %s...' % filename)
134 with open(filename, 'wb') as f:
135 header_added = False
136 header_found = False
137 check_added = False
138 check_decl = (' CheckFactories.registerCheck<' + check_name_camel +
139 '>(\n "' + check_name + '");\n')
140
141 for line in lines:
142 if not header_added:
143 match = re.search('#include "(.*)"', line)
144 if match:
145 header_found = True
146 if match.group(1) > check_name_camel:
147 header_added = True
148 f.write('#include "' + check_name_camel + '.h"\n')
149 elif header_found:
150 header_added = True
151 f.write('#include "' + check_name_camel + '.h"\n')
152
153 if not check_added:
154 if line.strip() == '}':
155 check_added = True
156 f.write(check_decl)
157 else:
158 match = re.search('registerCheck<(.*)>', line)
159 if match and match.group(1) > check_name_camel:
160 check_added = True
161 f.write(check_decl)
162 f.write(line)
163
164
165 # Adds a release notes entry.
166 def add_release_notes(clang_tidy_path, old_check_name, new_check_name):
167 filename = os.path.normpath(os.path.join(clang_tidy_path,
168 '../docs/ReleaseNotes.rst'))
169 with open(filename, 'r') as f:
170 lines = f.readlines()
171
172 print('Updating %s...' % filename)
173 with open(filename, 'wb') as f:
174 note_added = False
175 header_found = False
176
177 for line in lines:
178 if not note_added:
179 match = re.search('Improvements to clang-tidy', line)
180 if match:
181 header_found = True
182 elif header_found:
183 if not line.startswith('----'):
184 f.write("""
185 - The '%s' check was renamed to :doc:`%s
186 <clang-tidy/checks/%s>`
187 """ % (old_check_name, new_check_name, new_check_name))
188 note_added = True
189
190 f.write(line)
191
192 def main():
193 parser = argparse.ArgumentParser(description='Rename clang-tidy check.')
194 parser.add_argument('old_check_name', type=str,
195 help='Old check name.')
196 parser.add_argument('new_check_name', type=str,
197 help='New check name.')
198 parser.add_argument('--check_class_name', type=str,
199 help='Old name of the class implementing the check.')
200 args = parser.parse_args()
201
202 old_module = args.old_check_name.split('-')[0]
203 new_module = args.new_check_name.split('-')[0]
204 if args.check_class_name:
205 check_name_camel = args.check_class_name
206 else:
207 check_name_camel = (''.join(map(lambda elem: elem.capitalize(),
208 args.old_check_name.split('-')[1:])) +
209 'Check')
210
211 new_check_name_camel = (''.join(map(lambda elem: elem.capitalize(),
212 args.new_check_name.split('-')[1:])) +
213 'Check')
214
215 clang_tidy_path = os.path.dirname(__file__)
216
217 header_guard_variants = [
218 (args.old_check_name.replace('-', '_')).upper() + '_CHECK',
219 (old_module + '_' + check_name_camel).upper(),
220 (old_module + '_' + new_check_name_camel).upper(),
221 args.old_check_name.replace('-', '_').upper()]
222 header_guard_new = (new_module + '_' + new_check_name_camel).upper()
223
224 old_module_path = os.path.join(clang_tidy_path, old_module)
225 new_module_path = os.path.join(clang_tidy_path, new_module)
226
227 if (args.old_check_name != args.new_check_name):
228 # Remove the check from the old module.
229 cmake_lists = os.path.join(old_module_path, 'CMakeLists.txt')
230 check_found = deleteMatchingLines(cmake_lists, '\\b' + check_name_camel)
231 if not check_found:
232 print("Check name '%s' not found in %s. Exiting." %
233 (check_name_camel, cmake_lists))
234 return 1
235
236 modulecpp = filter(
237 lambda p: p.lower() == old_module.lower() + 'tidymodule.cpp',
238 os.listdir(old_module_path))[0]
239 deleteMatchingLines(os.path.join(old_module_path, modulecpp),
240 '\\b' + check_name_camel + '|\\b' + args.old_check_name)
241
242 for filename in getListOfFiles(clang_tidy_path):
243 originalName = filename
244 filename = fileRename(filename, args.old_check_name,
245 args.new_check_name)
246 filename = fileRename(filename, check_name_camel, new_check_name_camel)
247 replaceInFile(filename, generateCommentLineHeader(originalName),
248 generateCommentLineHeader(filename))
249 replaceInFile(filename, generateCommentLineSource(originalName),
250 generateCommentLineSource(filename))
251 for header_guard in header_guard_variants:
252 replaceInFile(filename, header_guard, header_guard_new)
253
254 if args.new_check_name + '.rst' in filename:
255 replaceInFile(
256 filename,
257 args.old_check_name + '\n' + '=' * len(args.old_check_name) + '\n',
258 args.new_check_name + '\n' + '=' * len(args.new_check_name) + '\n')
259
260 replaceInFile(filename, args.old_check_name, args.new_check_name)
261 replaceInFile(filename, old_module + '::' + check_name_camel,
262 new_module + '::' + new_check_name_camel)
263 replaceInFile(filename, old_module + '/' + check_name_camel,
264 new_module + '/' + new_check_name_camel)
265 replaceInFile(filename, check_name_camel, new_check_name_camel)
266
267 if old_module != new_module or new_module == 'llvm':
268 if new_module == 'llvm':
269 new_namespace = new_module + '_check'
270 else:
271 new_namespace = new_module
272 check_implementation_files = glob.glob(
273 os.path.join(old_module_path, new_check_name_camel + '*'))
274 for filename in check_implementation_files:
275 # Move check implementation to the directory of the new module.
276 filename = fileRename(filename, old_module_path, new_module_path)
277 replaceInFileRegex(filename, 'namespace ' + old_module + '[^ \n]*',
278 'namespace ' + new_namespace)
279
280 if (args.old_check_name == args.new_check_name):
281 return
282
283 # Add check to the new module.
284 adapt_cmake(new_module_path, new_check_name_camel)
285 adapt_module(new_module_path, new_module, args.new_check_name,
286 new_check_name_camel)
287
288 os.system(os.path.join(clang_tidy_path, 'add_new_check.py')
289 + ' --update-docs')
290 add_release_notes(clang_tidy_path, args.old_check_name, args.new_check_name)
291
292 if __name__ == '__main__':
293 main()
rename_check.main
def main()
Definition: rename_check.py:192
rename_check.generateCommentLineHeader
def generateCommentLineHeader(filename)
Definition: rename_check.py:45
rename_check.replaceInFile
def replaceInFile(fileName, sFrom, sTo)
Definition: rename_check.py:29
rename_check.fileRename
def fileRename(fileName, sFrom, sTo)
Definition: rename_check.py:61
rename_check.add_release_notes
def add_release_notes(clang_tidy_path, old_check_name, new_check_name)
Definition: rename_check.py:166
rename_check.replaceInFileRegex
def replaceInFileRegex(fileName, sFrom, sTo)
Definition: rename_check.py:17
rename_check.generateCommentLineSource
def generateCommentLineSource(filename)
Definition: rename_check.py:53
rename_check.adapt_cmake
def adapt_cmake(module_path, check_name_camel)
Definition: rename_check.py:98
rename_check.adapt_module
def adapt_module(module_path, module, check_name, check_name_camel)
Definition: rename_check.py:126
rename_check.getListOfFiles
def getListOfFiles(clang_tidy_path)
Definition: rename_check.py:85
rename_check.deleteMatchingLines
def deleteMatchingLines(fileName, pattern)
Definition: rename_check.py:69
clang::tidy::cppcoreguidelines::join
static std::string join(ArrayRef< SpecialMemberFunctionsCheck::SpecialMemberFunctionKind > SMFS, llvm::StringRef AndOr)
Definition: SpecialMemberFunctionsCheck.cpp:81