clang-tools  7.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 # 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 
12 import argparse
13 import glob
14 import os
15 import re
16 
17 
18 def 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)
29  print("Replacing '%s' -> '%s' in '%s'..." % (sFrom, sTo, fileName))
30  with open(fileName, "w") as f:
31  f.write(txt)
32 
33 
35  return ''.join(['//===--- ',
36  os.path.basename(filename),
37  ' - clang-tidy ',
38  '-' * max(0, 42 - len(os.path.basename(filename))),
39  '*- C++ -*-===//'])
40 
41 
43  return ''.join(['//===--- ',
44  os.path.basename(filename),
45  ' - clang-tidy',
46  '-' * max(0, 52 - len(os.path.basename(filename))),
47  '-===//'])
48 
49 
50 def fileRename(fileName, sFrom, sTo):
51  if sFrom not in fileName or sFrom == sTo:
52  return fileName
53  newFileName = fileName.replace(sFrom, sTo)
54  print("Renaming '%s' -> '%s'..." % (fileName, newFileName))
55  os.rename(fileName, newFileName)
56  return newFileName
57 
58 def 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 len(not_matching_lines) == len(lines):
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
73 
74 def getListOfFiles(clang_tidy_path):
75  files = glob.glob(os.path.join(clang_tidy_path, '*'))
76  for dirname in files:
77  if os.path.isdir(dirname):
78  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', '*'))
83  return [filename for filename in files if os.path.isfile(filename)]
84 
85 # Adapts the module's CMakelist file. Returns 'True' if it could add a new entry
86 # and 'False' if the entry already existed.
87 def 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.
115 def 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.
155 def 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 :doc:`%s
175  <clang-tidy/checks/%s>`
176 """ % (old_check_name, new_check_name, new_check_name))
177  note_added = True
178 
179  f.write(line)
180 
181 def main():
182  parser = argparse.ArgumentParser(description='Rename clang-tidy check.')
183  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  parser.add_argument('--check_class_name', type=str,
188  help='Old name of the class implementing the check.')
189  args = parser.parse_args()
190 
191  old_module = args.old_check_name.split('-')[0]
192  new_module = args.new_check_name.split('-')[0]
193  if args.check_class_name:
194  check_name_camel = args.check_class_name
195  else:
196  check_name_camel = (''.join(map(lambda elem: elem.capitalize(),
197  args.old_check_name.split('-')[1:])) +
198  'Check')
199 
200  new_check_name_camel = (''.join(map(lambda elem: elem.capitalize(),
201  args.new_check_name.split('-')[1:])) +
202  'Check')
203 
204  clang_tidy_path = os.path.dirname(__file__)
205 
206  header_guard_variants = [
207  (old_module + '_' + new_check_name_camel).upper(),
208  args.old_check_name.replace('-', '_').upper()]
209  header_guard_new = (new_module + '_' + new_check_name_camel).upper()
210 
211  old_module_path = os.path.join(clang_tidy_path, old_module)
212  new_module_path = os.path.join(clang_tidy_path, new_module)
213 
214  # Remove the check from the old module.
215  cmake_lists = os.path.join(old_module_path, 'CMakeLists.txt')
216  check_found = deleteMatchingLines(cmake_lists, '\\b' + check_name_camel)
217  if not check_found:
218  print("Check name '%s' not found in %s. Exiting." %
219  (check_name_camel, cmake_lists))
220  return 1
221 
222  modulecpp = filter(
223  lambda p: p.lower() == old_module.lower() + 'tidymodule.cpp',
224  os.listdir(old_module_path))[0]
225  deleteMatchingLines(os.path.join(old_module_path, modulecpp),
226  '\\b' + check_name_camel + '|\\b' + args.old_check_name)
227 
228  for filename in getListOfFiles(clang_tidy_path):
229  originalName = filename
230  filename = fileRename(filename, args.old_check_name,
231  args.new_check_name)
232  filename = fileRename(filename, check_name_camel, new_check_name_camel)
233  replaceInFile(filename, generateCommentLineHeader(originalName),
234  generateCommentLineHeader(filename))
235  replaceInFile(filename, generateCommentLineSource(originalName),
236  generateCommentLineSource(filename))
237  for header_guard in header_guard_variants:
238  replaceInFile(filename, header_guard, header_guard_new)
239 
240  if args.new_check_name + '.rst' in filename:
242  filename,
243  args.old_check_name + '\n' + '=' * len(args.old_check_name) + '\n',
244  args.new_check_name + '\n' + '=' * len(args.new_check_name) + '\n')
245 
246  replaceInFile(filename, args.old_check_name, args.new_check_name)
247  replaceInFile(filename, old_module + '::' + check_name_camel,
248  new_module + '::' + new_check_name_camel)
249  replaceInFile(filename, old_module + '/' + check_name_camel,
250  new_module + '/' + new_check_name_camel)
251  replaceInFile(filename, check_name_camel, new_check_name_camel)
252 
253  if old_module != new_module:
254  check_implementation_files = glob.glob(
255  os.path.join(old_module_path, new_check_name_camel + '*'))
256  for filename in check_implementation_files:
257  # Move check implementation to the directory of the new module.
258  filename = fileRename(filename, old_module_path, new_module_path)
259  replaceInFile(filename, 'namespace ' + old_module,
260  'namespace ' + new_module)
261 
262  # Add check to the new module.
263  adapt_cmake(new_module_path, new_check_name_camel)
264  adapt_module(new_module_path, new_module, args.new_check_name,
265  new_check_name_camel)
266 
267  os.system(os.path.join(clang_tidy_path, 'add_new_check.py')
268  + ' --update-docs')
269  add_release_notes(clang_tidy_path, args.old_check_name, args.new_check_name)
270 
271 if __name__ == '__main__':
272  main()
def generateCommentLineHeader(filename)
Definition: rename_check.py:34
def replaceInFile(fileName, sFrom, sTo)
Definition: rename_check.py:18
def fileRename(fileName, sFrom, sTo)
Definition: rename_check.py:50
def add_release_notes(clang_tidy_path, old_check_name, new_check_name)
def generateCommentLineSource(filename)
Definition: rename_check.py:42
def adapt_cmake(module_path, check_name_camel)
Definition: rename_check.py:87
def adapt_module(module_path, module, check_name, check_name_camel)
def getListOfFiles(clang_tidy_path)
Definition: rename_check.py:74
def deleteMatchingLines(fileName, pattern)
Definition: rename_check.py:58
static std::string join(ArrayRef< SpecialMemberFunctionsCheck::SpecialMemberFunctionKind > SMFS, llvm::StringRef AndOr)