From 6b334a212ebbcb8838168015fc3cfec38713a6d0 Mon Sep 17 00:00:00 2001 From: Alexander Kornienko Date: Thu, 23 Nov 2017 12:08:53 +0000 Subject: [PATCH] clang-tidy/rename_check.py: support for moving between modules llvm-svn: 318905 --- clang-tools-extra/clang-tidy/rename_check.py | 177 ++++++++++++++++++++++++--- 1 file changed, 160 insertions(+), 17 deletions(-) diff --git a/clang-tools-extra/clang-tidy/rename_check.py b/clang-tools-extra/clang-tidy/rename_check.py index e739a38..ac4c058 100755 --- a/clang-tools-extra/clang-tidy/rename_check.py +++ b/clang-tools-extra/clang-tidy/rename_check.py @@ -9,9 +9,10 @@ # #===------------------------------------------------------------------------===# -import os -import glob import argparse +import glob +import os +import re def replaceInFile(fileName, sFrom, sTo): @@ -25,7 +26,7 @@ def replaceInFile(fileName, sFrom, sTo): return txt = txt.replace(sFrom, sTo) - print("Replace '%s' -> '%s' in '%s'" % (sFrom, sTo, fileName)) + print("Replacing '%s' -> '%s' in '%s'..." % (sFrom, sTo, fileName)) with open(fileName, "w") as f: f.write(txt) @@ -47,13 +48,28 @@ def generateCommentLineSource(filename): def fileRename(fileName, sFrom, sTo): - if sFrom not in fileName: + if sFrom not in fileName or sFrom == sTo: return fileName newFileName = fileName.replace(sFrom, sTo) - print("Rename '%s' -> '%s'" % (fileName, newFileName)) + print("Renaming '%s' -> '%s'..." % (fileName, newFileName)) os.rename(fileName, newFileName) return newFileName +def deleteMatchingLines(fileName, pattern): + lines = None + with open(fileName, "r") as f: + lines = f.readlines() + + not_matching_lines = [l for l in lines if not re.search(pattern, l)] + if not_matching_lines.count == lines.count: + return False + + print("Removing lines matching '%s' in '%s'..." % (pattern, fileName)) + print(' ' + ' '.join([l for l in lines if re.search(pattern, l)])) + with open(fileName, "w") as f: + f.writelines(not_matching_lines) + + return True def getListOfFiles(clang_tidy_path): files = glob.glob(os.path.join(clang_tidy_path, '*')) @@ -66,43 +82,170 @@ def getListOfFiles(clang_tidy_path): 'clang-tidy', 'checks', '*')) return [filename for filename in files if os.path.isfile(filename)] +# Adapts the module's CMakelist file. Returns 'True' if it could add a new entry +# and 'False' if the entry already existed. +def adapt_cmake(module_path, check_name_camel): + filename = os.path.join(module_path, 'CMakeLists.txt') + with open(filename, 'r') as f: + lines = f.readlines() + + cpp_file = check_name_camel + '.cpp' + + # Figure out whether this check already exists. + for line in lines: + if line.strip() == cpp_file: + return False + + print('Updating %s...' % filename) + with open(filename, 'wb') as f: + cpp_found = False + file_added = False + for line in lines: + cpp_line = line.strip().endswith('.cpp') + if (not file_added) and (cpp_line or cpp_found): + cpp_found = True + if (line.strip() > cpp_file) or (not cpp_line): + f.write(' ' + cpp_file + '\n') + file_added = True + f.write(line) + + return True + +# Modifies the module to include the new check. +def adapt_module(module_path, module, check_name, check_name_camel): + modulecpp = filter(lambda p: p.lower() == module.lower() + 'tidymodule.cpp', + os.listdir(module_path))[0] + filename = os.path.join(module_path, modulecpp) + with open(filename, 'r') as f: + lines = f.readlines() + + print('Updating %s...' % filename) + with open(filename, 'wb') as f: + header_added = False + header_found = False + check_added = False + check_decl = (' CheckFactories.registerCheck<' + check_name_camel + + '>(\n "' + check_name + '");\n') + + for line in lines: + if not header_added: + match = re.search('#include "(.*)"', line) + if match: + header_found = True + if match.group(1) > check_name_camel: + header_added = True + f.write('#include "' + check_name_camel + '.h"\n') + elif header_found: + header_added = True + f.write('#include "' + check_name_camel + '.h"\n') + + if not check_added: + if line.strip() == '}': + check_added = True + f.write(check_decl) + else: + match = re.search('registerCheck<(.*)>', line) + if match and match.group(1) > check_name_camel: + check_added = True + f.write(check_decl) + f.write(line) + + +# Adds a release notes entry. +def add_release_notes(clang_tidy_path, old_check_name, new_check_name): + filename = os.path.normpath(os.path.join(clang_tidy_path, + '../docs/ReleaseNotes.rst')) + with open(filename, 'r') as f: + lines = f.readlines() + + print('Updating %s...' % filename) + with open(filename, 'wb') as f: + note_added = False + header_found = False + + for line in lines: + if not note_added: + match = re.search('Improvements to clang-tidy', line) + if match: + header_found = True + elif header_found: + if not line.startswith('----'): + f.write(""" +- The '%s' check was renamed to `%s + `_ +""" % (old_check_name, new_check_name, new_check_name)) + note_added = True + + f.write(line) def main(): parser = argparse.ArgumentParser(description='Rename clang-tidy check.') - parser.add_argument('module', type=str, - help='Module where the renamed check is defined') parser.add_argument('old_check_name', type=str, help='Old check name.') parser.add_argument('new_check_name', type=str, help='New check name.') args = parser.parse_args() - args.module = args.module.lower() + old_module = args.old_check_name.split('-')[0] + new_module = args.new_check_name.split('-')[0] check_name_camel = ''.join(map(lambda elem: elem.capitalize(), - args.old_check_name.split('-'))) + 'Check' - check_name_new_camel = (''.join(map(lambda elem: elem.capitalize(), - args.new_check_name.split('-'))) + + args.old_check_name.split('-')[1:])) + 'Check' + new_check_name_camel = (''.join(map(lambda elem: elem.capitalize(), + args.new_check_name.split('-')[1:])) + 'Check') clang_tidy_path = os.path.dirname(__file__) - header_guard_old = (args.module.upper() + '_' + - args.old_check_name.upper().replace('-', '_')) - header_guard_new = (args.module.upper() + '_' + - args.new_check_name.upper().replace('-', '_')) + header_guard_old = args.old_check_name.upper().replace('-', '_') + header_guard_new = args.new_check_name.upper().replace('-', '_') + + old_module_path = os.path.join(clang_tidy_path, old_module) + new_module_path = os.path.join(clang_tidy_path, new_module) + + # Remove the check from the old module. + cmake_lists = os.path.join(old_module_path, 'CMakeLists.txt') + check_found = deleteMatchingLines(cmake_lists, check_name_camel) + if not check_found: + print("Check name '%s' not found in %s. Exiting." % + (check_name_camel, cmake_lists)) + return 1 + + modulecpp = filter( + lambda p: p.lower() == old_module.lower() + 'tidymodule.cpp', + os.listdir(old_module_path))[0] + deleteMatchingLines(os.path.join(old_module_path, modulecpp), + check_name_camel + '|' + args.old_check_name) for filename in getListOfFiles(clang_tidy_path): originalName = filename filename = fileRename(filename, args.old_check_name, args.new_check_name) - filename = fileRename(filename, check_name_camel, check_name_new_camel) + filename = fileRename(filename, check_name_camel, new_check_name_camel) replaceInFile(filename, generateCommentLineHeader(originalName), generateCommentLineHeader(filename)) replaceInFile(filename, generateCommentLineSource(originalName), generateCommentLineSource(filename)) replaceInFile(filename, header_guard_old, header_guard_new) replaceInFile(filename, args.old_check_name, args.new_check_name) - replaceInFile(filename, check_name_camel, check_name_new_camel) + replaceInFile(filename, check_name_camel, new_check_name_camel) + + if old_module != new_module: + check_implementation_files = glob.glob( + os.path.join(old_module_path, new_check_name_camel + '*')) + for filename in check_implementation_files: + # Move check implementation to the directory of the new module. + filename = fileRename(filename, old_module_path, new_module_path) + replaceInFile(filename, 'namespace ' + old_module, + 'namespace ' + new_module) + + # Add check to the new module. + adapt_cmake(new_module_path, new_check_name_camel) + adapt_module(new_module_path, new_module, args.new_check_name, + new_check_name_camel) + + os.system(os.path.join(clang_tidy_path, 'add_new_check.py') + + ' --update-docs') + add_release_notes(clang_tidy_path, args.old_check_name, args.new_check_name) if __name__ == '__main__': main() -- 2.7.4