clang-tools  14.0.0git
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 from __future__ import unicode_literals
12 
13 import argparse
14 import glob
15 import io
16 import os
17 import re
18 
19 def replaceInFileRegex(fileName, sFrom, sTo):
20  if sFrom == sTo:
21  return
22 
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()
30 
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)
35 
36 
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()
43 
44  if sFrom not in txt:
45  return
46 
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)
51 
52 
54  return ''.join(['//===--- ',
55  os.path.basename(filename),
56  ' - clang-tidy ',
57  '-' * max(0, 42 - len(os.path.basename(filename))),
58  '*- C++ -*-===//'])
59 
60 
62  return ''.join(['//===--- ',
63  os.path.basename(filename),
64  ' - clang-tidy',
65  '-' * max(0, 52 - len(os.path.basename(filename))),
66  '-===//'])
67 
68 
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
76 
77 
78 def deleteMatchingLines(fileName, pattern):
79  lines = None
80  with io.open(fileName, 'r', encoding='utf8') as f:
81  lines = f.readlines()
82 
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
86 
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)
91 
92  return True
93 
94 
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)]
105 
106 
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()
113 
114  cpp_file = check_name_camel + '.cpp'
115 
116  # Figure out whether this check already exists.
117  for line in lines:
118  if line.strip() == cpp_file:
119  return False
120 
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)
133 
134  return True
135 
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()
144 
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')
152 
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')
164 
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)
175 
176 
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()
183 
184  lineMatcher = re.compile('Renamed checks')
185  nextSectionMatcher = re.compile('Improvements to include-fixer')
186  checkMatcher = re.compile('- The \'(.*)')
187 
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
193 
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
203 
204  if match_next:
205  add_note_here = True
206 
207  if match:
208  header_found = True
209  f.write(line)
210  continue
211 
212  if line.startswith('^^^^'):
213  f.write(line)
214  continue
215 
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>`
220 
221 """ % (old_check_name, new_check_name, new_check_name))
222  note_added = True
223 
224  f.write(line)
225 
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()
235 
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')
244 
245  new_check_name_camel = (''.join(map(lambda elem: elem.capitalize(),
246  args.new_check_name.split('-')[1:])) +
247  'Check')
248 
249  clang_tidy_path = os.path.dirname(__file__)
250 
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()
257 
258  old_module_path = os.path.join(clang_tidy_path, old_module)
259  new_module_path = os.path.join(clang_tidy_path, new_module)
260 
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
269 
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)
275 
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)
287 
288  if args.new_check_name + '.rst' in filename:
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')
293 
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)
300 
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)
313 
314  if (args.old_check_name == args.new_check_name):
315  return
316 
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)
321 
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)
325 
326 
327 if __name__ == '__main__':
328  main()
rename_check.deleteMatchingLines
def deleteMatchingLines(fileName, pattern)
Definition: rename_check.py:78
rename_check.replaceInFile
def replaceInFile(fileName, sFrom, sTo)
Definition: rename_check.py:37
rename_check.replaceInFileRegex
def replaceInFileRegex(fileName, sFrom, sTo)
Definition: rename_check.py:19
clang::tidy::cppcoreguidelines::join
static std::string join(ArrayRef< SpecialMemberFunctionsCheck::SpecialMemberFunctionKind > SMFS, llvm::StringRef AndOr)
Definition: SpecialMemberFunctionsCheck.cpp:78
rename_check.add_release_notes
def add_release_notes(clang_tidy_path, old_check_name, new_check_name)
Definition: rename_check.py:178
rename_check.generateCommentLineSource
def generateCommentLineSource(filename)
Definition: rename_check.py:61
rename_check.getListOfFiles
def getListOfFiles(clang_tidy_path)
Definition: rename_check.py:95
rename_check.fileRename
def fileRename(fileName, sFrom, sTo)
Definition: rename_check.py:69
rename_check.main
def main()
Definition: rename_check.py:226
rename_check.adapt_module
def adapt_module(module_path, module, check_name, check_name_camel)
Definition: rename_check.py:137
rename_check.generateCommentLineHeader
def generateCommentLineHeader(filename)
Definition: rename_check.py:53
rename_check.adapt_cmake
def adapt_cmake(module_path, check_name_camel)
Definition: rename_check.py:109