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