clang-tools  10.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 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 
46  return ''.join(['//===--- ',
47  os.path.basename(filename),
48  ' - clang-tidy ',
49  '-' * max(0, 42 - len(os.path.basename(filename))),
50  '*- C++ -*-===//'])
51 
52 
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:
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()
def generateCommentLineHeader(filename)
Definition: rename_check.py:45
def replaceInFile(fileName, sFrom, sTo)
Definition: rename_check.py:29
def fileRename(fileName, sFrom, sTo)
Definition: rename_check.py:61
def add_release_notes(clang_tidy_path, old_check_name, new_check_name)
def replaceInFileRegex(fileName, sFrom, sTo)
Definition: rename_check.py:17
def generateCommentLineSource(filename)
Definition: rename_check.py:53
def adapt_cmake(module_path, check_name_camel)
Definition: rename_check.py:98
def adapt_module(module_path, module, check_name, check_name_camel)
def getListOfFiles(clang_tidy_path)
Definition: rename_check.py:85
def deleteMatchingLines(fileName, pattern)
Definition: rename_check.py:69
static std::string join(ArrayRef< SpecialMemberFunctionsCheck::SpecialMemberFunctionKind > SMFS, llvm::StringRef AndOr)