Piotr Zegar | 2e5bbce | 2015-10-11 07:58:34 +0000 | [diff] [blame] | 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 | |
Kirill Bobyrev | b0cf6a3 | 2016-11-08 11:43:50 +0000 | [diff] [blame] | 12 | import argparse |
Alexander Kornienko | 6b334a2 | 2017-11-23 12:08:53 +0000 | [diff] [blame] | 13 | import glob |
| 14 | import os |
| 15 | import re |
Kirill Bobyrev | b0cf6a3 | 2016-11-08 11:43:50 +0000 | [diff] [blame] | 16 | |
Piotr Zegar | 2e5bbce | 2015-10-11 07:58:34 +0000 | [diff] [blame] | 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) |
Alexander Kornienko | 6b334a2 | 2017-11-23 12:08:53 +0000 | [diff] [blame] | 29 | print("Replacing '%s' -> '%s' in '%s'..." % (sFrom, sTo, fileName)) |
Piotr Zegar | 2e5bbce | 2015-10-11 07:58:34 +0000 | [diff] [blame] | 30 | with open(fileName, "w") as f: |
| 31 | f.write(txt) |
| 32 | |
Kirill Bobyrev | b0cf6a3 | 2016-11-08 11:43:50 +0000 | [diff] [blame] | 33 | |
Piotr Zegar | 2e5bbce | 2015-10-11 07:58:34 +0000 | [diff] [blame] | 34 | def generateCommentLineHeader(filename): |
| 35 | return ''.join(['//===--- ', |
| 36 | os.path.basename(filename), |
| 37 | ' - clang-tidy ', |
| 38 | '-' * max(0, 42 - len(os.path.basename(filename))), |
| 39 | '*- C++ -*-===//']) |
| 40 | |
Kirill Bobyrev | b0cf6a3 | 2016-11-08 11:43:50 +0000 | [diff] [blame] | 41 | |
Piotr Zegar | 2e5bbce | 2015-10-11 07:58:34 +0000 | [diff] [blame] | 42 | def generateCommentLineSource(filename): |
| 43 | return ''.join(['//===--- ', |
| 44 | os.path.basename(filename), |
| 45 | ' - clang-tidy', |
| 46 | '-' * max(0, 52 - len(os.path.basename(filename))), |
| 47 | '-===//']) |
| 48 | |
Kirill Bobyrev | b0cf6a3 | 2016-11-08 11:43:50 +0000 | [diff] [blame] | 49 | |
Piotr Zegar | 2e5bbce | 2015-10-11 07:58:34 +0000 | [diff] [blame] | 50 | def fileRename(fileName, sFrom, sTo): |
Alexander Kornienko | 6b334a2 | 2017-11-23 12:08:53 +0000 | [diff] [blame] | 51 | if sFrom not in fileName or sFrom == sTo: |
Piotr Zegar | 2e5bbce | 2015-10-11 07:58:34 +0000 | [diff] [blame] | 52 | return fileName |
| 53 | newFileName = fileName.replace(sFrom, sTo) |
Alexander Kornienko | 6b334a2 | 2017-11-23 12:08:53 +0000 | [diff] [blame] | 54 | print("Renaming '%s' -> '%s'..." % (fileName, newFileName)) |
Piotr Zegar | 2e5bbce | 2015-10-11 07:58:34 +0000 | [diff] [blame] | 55 | os.rename(fileName, newFileName) |
| 56 | return newFileName |
| 57 | |
Alexander Kornienko | 6b334a2 | 2017-11-23 12:08:53 +0000 | [diff] [blame] | 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)] |
Alexander Kornienko | 1c81d91 | 2017-11-23 14:59:19 +0000 | [diff] [blame] | 64 | if len(not_matching_lines) == len(lines): |
Alexander Kornienko | 6b334a2 | 2017-11-23 12:08:53 +0000 | [diff] [blame] | 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 |
Kirill Bobyrev | b0cf6a3 | 2016-11-08 11:43:50 +0000 | [diff] [blame] | 73 | |
Piotr Zegar | 2e5bbce | 2015-10-11 07:58:34 +0000 | [diff] [blame] | 74 | def getListOfFiles(clang_tidy_path): |
Kirill Bobyrev | b0cf6a3 | 2016-11-08 11:43:50 +0000 | [diff] [blame] | 75 | files = glob.glob(os.path.join(clang_tidy_path, '*')) |
Piotr Zegar | 2e5bbce | 2015-10-11 07:58:34 +0000 | [diff] [blame] | 76 | for dirname in files: |
| 77 | if os.path.isdir(dirname): |
Kirill Bobyrev | b0cf6a3 | 2016-11-08 11:43:50 +0000 | [diff] [blame] | 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', '*')) |
Piotr Zegar | 2e5bbce | 2015-10-11 07:58:34 +0000 | [diff] [blame] | 83 | return [filename for filename in files if os.path.isfile(filename)] |
| 84 | |
Alexander Kornienko | 6b334a2 | 2017-11-23 12:08:53 +0000 | [diff] [blame] | 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 `%s |
| 175 | <http://clang.llvm.org/extra/clang-tidy/checks/%s.html>`_ |
| 176 | """ % (old_check_name, new_check_name, new_check_name)) |
| 177 | note_added = True |
| 178 | |
| 179 | f.write(line) |
Kirill Bobyrev | b0cf6a3 | 2016-11-08 11:43:50 +0000 | [diff] [blame] | 180 | |
Piotr Zegar | 2e5bbce | 2015-10-11 07:58:34 +0000 | [diff] [blame] | 181 | def main(): |
Kirill Bobyrev | b0cf6a3 | 2016-11-08 11:43:50 +0000 | [diff] [blame] | 182 | parser = argparse.ArgumentParser(description='Rename clang-tidy check.') |
Kirill Bobyrev | b0cf6a3 | 2016-11-08 11:43:50 +0000 | [diff] [blame] | 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 | args = parser.parse_args() |
Piotr Zegar | 2e5bbce | 2015-10-11 07:58:34 +0000 | [diff] [blame] | 188 | |
Alexander Kornienko | 6b334a2 | 2017-11-23 12:08:53 +0000 | [diff] [blame] | 189 | old_module = args.old_check_name.split('-')[0] |
| 190 | new_module = args.new_check_name.split('-')[0] |
Piotr Zegar | 2e5bbce | 2015-10-11 07:58:34 +0000 | [diff] [blame] | 191 | check_name_camel = ''.join(map(lambda elem: elem.capitalize(), |
Alexander Kornienko | 6b334a2 | 2017-11-23 12:08:53 +0000 | [diff] [blame] | 192 | args.old_check_name.split('-')[1:])) + 'Check' |
| 193 | new_check_name_camel = (''.join(map(lambda elem: elem.capitalize(), |
| 194 | args.new_check_name.split('-')[1:])) + |
Kirill Bobyrev | b0cf6a3 | 2016-11-08 11:43:50 +0000 | [diff] [blame] | 195 | 'Check') |
Piotr Zegar | 2e5bbce | 2015-10-11 07:58:34 +0000 | [diff] [blame] | 196 | |
Kirill Bobyrev | b0cf6a3 | 2016-11-08 11:43:50 +0000 | [diff] [blame] | 197 | clang_tidy_path = os.path.dirname(__file__) |
Piotr Zegar | 2e5bbce | 2015-10-11 07:58:34 +0000 | [diff] [blame] | 198 | |
Alexander Kornienko | a616a55 | 2017-11-24 14:33:06 +0000 | [diff] [blame^] | 199 | header_guard_old = (old_module + '_' + check_name_camel).upper() |
| 200 | header_guard_new = (new_module + '_' + new_check_name_camel).upper() |
Alexander Kornienko | 6b334a2 | 2017-11-23 12:08:53 +0000 | [diff] [blame] | 201 | |
| 202 | old_module_path = os.path.join(clang_tidy_path, old_module) |
| 203 | new_module_path = os.path.join(clang_tidy_path, new_module) |
| 204 | |
| 205 | # Remove the check from the old module. |
| 206 | cmake_lists = os.path.join(old_module_path, 'CMakeLists.txt') |
| 207 | check_found = deleteMatchingLines(cmake_lists, check_name_camel) |
| 208 | if not check_found: |
| 209 | print("Check name '%s' not found in %s. Exiting." % |
| 210 | (check_name_camel, cmake_lists)) |
| 211 | return 1 |
| 212 | |
| 213 | modulecpp = filter( |
| 214 | lambda p: p.lower() == old_module.lower() + 'tidymodule.cpp', |
| 215 | os.listdir(old_module_path))[0] |
| 216 | deleteMatchingLines(os.path.join(old_module_path, modulecpp), |
| 217 | check_name_camel + '|' + args.old_check_name) |
Piotr Zegar | 2e5bbce | 2015-10-11 07:58:34 +0000 | [diff] [blame] | 218 | |
| 219 | for filename in getListOfFiles(clang_tidy_path): |
| 220 | originalName = filename |
Kirill Bobyrev | b0cf6a3 | 2016-11-08 11:43:50 +0000 | [diff] [blame] | 221 | filename = fileRename(filename, args.old_check_name, |
| 222 | args.new_check_name) |
Alexander Kornienko | 6b334a2 | 2017-11-23 12:08:53 +0000 | [diff] [blame] | 223 | filename = fileRename(filename, check_name_camel, new_check_name_camel) |
Kirill Bobyrev | b0cf6a3 | 2016-11-08 11:43:50 +0000 | [diff] [blame] | 224 | replaceInFile(filename, generateCommentLineHeader(originalName), |
| 225 | generateCommentLineHeader(filename)) |
| 226 | replaceInFile(filename, generateCommentLineSource(originalName), |
| 227 | generateCommentLineSource(filename)) |
Piotr Zegar | 2e5bbce | 2015-10-11 07:58:34 +0000 | [diff] [blame] | 228 | replaceInFile(filename, header_guard_old, header_guard_new) |
Alexander Kornienko | ab0b81c | 2017-11-23 14:05:32 +0000 | [diff] [blame] | 229 | |
| 230 | if args.new_check_name + '.rst' in filename: |
| 231 | replaceInFile( |
| 232 | filename, |
| 233 | args.old_check_name + '\n' + '=' * len(args.old_check_name) + '\n', |
| 234 | args.new_check_name + '\n' + '=' * len(args.new_check_name) + '\n') |
| 235 | |
Kirill Bobyrev | b0cf6a3 | 2016-11-08 11:43:50 +0000 | [diff] [blame] | 236 | replaceInFile(filename, args.old_check_name, args.new_check_name) |
Alexander Kornienko | 6b334a2 | 2017-11-23 12:08:53 +0000 | [diff] [blame] | 237 | replaceInFile(filename, check_name_camel, new_check_name_camel) |
| 238 | |
| 239 | if old_module != new_module: |
| 240 | check_implementation_files = glob.glob( |
| 241 | os.path.join(old_module_path, new_check_name_camel + '*')) |
| 242 | for filename in check_implementation_files: |
| 243 | # Move check implementation to the directory of the new module. |
| 244 | filename = fileRename(filename, old_module_path, new_module_path) |
| 245 | replaceInFile(filename, 'namespace ' + old_module, |
| 246 | 'namespace ' + new_module) |
| 247 | |
| 248 | # Add check to the new module. |
| 249 | adapt_cmake(new_module_path, new_check_name_camel) |
| 250 | adapt_module(new_module_path, new_module, args.new_check_name, |
| 251 | new_check_name_camel) |
| 252 | |
| 253 | os.system(os.path.join(clang_tidy_path, 'add_new_check.py') |
| 254 | + ' --update-docs') |
| 255 | add_release_notes(clang_tidy_path, args.old_check_name, args.new_check_name) |
Piotr Zegar | 2e5bbce | 2015-10-11 07:58:34 +0000 | [diff] [blame] | 256 | |
| 257 | if __name__ == '__main__': |
| 258 | main() |