| #!/usr/bin/env python3 |
| # |
| # ===- rename_check.py - clang-tidy check renamer ------------*- python -*--===# |
| # |
| # Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| # See https://llvm.org/LICENSE.txt for license information. |
| # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| # |
| # ===-----------------------------------------------------------------------===# |
| |
| from __future__ import unicode_literals |
| |
| import argparse |
| import glob |
| import io |
| import os |
| import re |
| |
| |
| def replaceInFileRegex(fileName, sFrom, sTo): |
| if sFrom == sTo: |
| return |
| |
| # The documentation files are encoded using UTF-8, however on Windows the |
| # default encoding might be different (e.g. CP-1252). To make sure UTF-8 is |
| # always used, use `io.open(filename, mode, encoding='utf8')` for reading and |
| # writing files here and elsewhere. |
| txt = None |
| with io.open(fileName, "r", encoding="utf8") as f: |
| txt = f.read() |
| |
| txt = re.sub(sFrom, sTo, txt) |
| print("Replacing '%s' -> '%s' in '%s'..." % (sFrom, sTo, fileName)) |
| with io.open(fileName, "w", encoding="utf8") as f: |
| f.write(txt) |
| |
| |
| def replaceInFile(fileName, sFrom, sTo): |
| if sFrom == sTo: |
| return |
| txt = None |
| with io.open(fileName, "r", encoding="utf8") as f: |
| txt = f.read() |
| |
| if sFrom not in txt: |
| return |
| |
| txt = txt.replace(sFrom, sTo) |
| print("Replacing '%s' -> '%s' in '%s'..." % (sFrom, sTo, fileName)) |
| with io.open(fileName, "w", encoding="utf8") as f: |
| f.write(txt) |
| |
| |
| def generateCommentLineHeader(filename): |
| return "".join( |
| [ |
| "//===--- ", |
| os.path.basename(filename), |
| " - clang-tidy ", |
| "-" * max(0, 42 - len(os.path.basename(filename))), |
| "*- C++ -*-===//", |
| ] |
| ) |
| |
| |
| def generateCommentLineSource(filename): |
| return "".join( |
| [ |
| "//===--- ", |
| os.path.basename(filename), |
| " - clang-tidy", |
| "-" * max(0, 52 - len(os.path.basename(filename))), |
| "-===//", |
| ] |
| ) |
| |
| |
| def fileRename(fileName, sFrom, sTo): |
| if sFrom not in fileName or sFrom == sTo: |
| return fileName |
| newFileName = fileName.replace(sFrom, sTo) |
| print("Renaming '%s' -> '%s'..." % (fileName, newFileName)) |
| os.rename(fileName, newFileName) |
| return newFileName |
| |
| |
| def deleteMatchingLines(fileName, pattern): |
| lines = None |
| with io.open(fileName, "r", encoding="utf8") as f: |
| lines = f.readlines() |
| |
| not_matching_lines = [l for l in lines if not re.search(pattern, l)] |
| if len(not_matching_lines) == len(lines): |
| return False |
| |
| print("Removing lines matching '%s' in '%s'..." % (pattern, fileName)) |
| print(" " + " ".join([l for l in lines if re.search(pattern, l)])) |
| with io.open(fileName, "w", encoding="utf8") as f: |
| f.writelines(not_matching_lines) |
| |
| return True |
| |
| |
| def getListOfFiles(clang_tidy_path): |
| files = glob.glob(os.path.join(clang_tidy_path, "**"), recursive=True) |
| files += [ |
| os.path.normpath(os.path.join(clang_tidy_path, "../docs/ReleaseNotes.rst")) |
| ] |
| files += glob.glob( |
| os.path.join(clang_tidy_path, "..", "test", "clang-tidy", "checkers", "**"), |
| recursive=True, |
| ) |
| files += glob.glob( |
| os.path.join(clang_tidy_path, "..", "docs", "clang-tidy", "checks", "*.rst") |
| ) |
| files += glob.glob( |
| os.path.join( |
| clang_tidy_path, "..", "docs", "clang-tidy", "checks", "*", "*.rst" |
| ), |
| recursive=True, |
| ) |
| 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 io.open(filename, "r", encoding="utf8") 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 io.open(filename, "w", encoding="utf8") 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 = next( |
| iter( |
| filter( |
| lambda p: p.lower() == module.lower() + "tidymodule.cpp", |
| os.listdir(module_path), |
| ) |
| ) |
| ) |
| filename = os.path.join(module_path, modulecpp) |
| with io.open(filename, "r", encoding="utf8") as f: |
| lines = f.readlines() |
| |
| print("Updating %s..." % filename) |
| with io.open(filename, "w", encoding="utf8") 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 io.open(filename, "r", encoding="utf8") as f: |
| lines = f.readlines() |
| |
| lineMatcher = re.compile("Renamed checks") |
| nextSectionMatcher = re.compile("Improvements to include-fixer") |
| checkMatcher = re.compile("- The '(.*)") |
| |
| print("Updating %s..." % filename) |
| with io.open(filename, "w", encoding="utf8") as f: |
| note_added = False |
| header_found = False |
| add_note_here = False |
| |
| for line in lines: |
| if not note_added: |
| match = lineMatcher.match(line) |
| match_next = nextSectionMatcher.match(line) |
| match_check = checkMatcher.match(line) |
| if match_check: |
| last_check = match_check.group(1) |
| if last_check > old_check_name: |
| add_note_here = True |
| |
| if match_next: |
| add_note_here = True |
| |
| if match: |
| header_found = True |
| f.write(line) |
| continue |
| |
| if line.startswith("^^^^"): |
| f.write(line) |
| continue |
| |
| if header_found and add_note_here: |
| if not line.startswith("^^^^"): |
| f.write( |
| """- The '%s' check was renamed to :doc:`%s |
| <clang-tidy/checks/%s/%s>` |
| |
| """ |
| % ( |
| old_check_name, |
| new_check_name, |
| new_check_name.split("-", 1)[0], |
| "-".join(new_check_name.split("-")[1:]), |
| ) |
| ) |
| note_added = True |
| |
| f.write(line) |
| |
| |
| def main(): |
| parser = argparse.ArgumentParser(description="Rename clang-tidy check.") |
| parser.add_argument("old_check_name", type=str, help="Old check name.") |
| parser.add_argument("new_check_name", type=str, help="New check name.") |
| parser.add_argument( |
| "--check_class_name", |
| type=str, |
| help="Old name of the class implementing the check.", |
| ) |
| args = parser.parse_args() |
| |
| old_module = args.old_check_name.split("-")[0] |
| new_module = args.new_check_name.split("-")[0] |
| old_name = "-".join(args.old_check_name.split("-")[1:]) |
| new_name = "-".join(args.new_check_name.split("-")[1:]) |
| |
| if args.check_class_name: |
| check_name_camel = args.check_class_name |
| else: |
| check_name_camel = ( |
| "".join(map(lambda elem: elem.capitalize(), old_name.split("-"))) + "Check" |
| ) |
| |
| new_check_name_camel = ( |
| "".join(map(lambda elem: elem.capitalize(), new_name.split("-"))) + "Check" |
| ) |
| |
| clang_tidy_path = os.path.dirname(__file__) |
| |
| header_guard_variants = [ |
| (args.old_check_name.replace("-", "_")).upper() + "_CHECK", |
| (old_module + "_" + check_name_camel).upper(), |
| (old_module + "_" + new_check_name_camel).upper(), |
| args.old_check_name.replace("-", "_").upper(), |
| ] |
| header_guard_new = (new_module + "_" + new_check_name_camel).upper() |
| |
| old_module_path = os.path.join(clang_tidy_path, old_module) |
| new_module_path = os.path.join(clang_tidy_path, new_module) |
| |
| if old_module != new_module: |
| # Remove the check from the old module. |
| cmake_lists = os.path.join(old_module_path, "CMakeLists.txt") |
| check_found = deleteMatchingLines(cmake_lists, "\\b" + check_name_camel) |
| if not check_found: |
| print( |
| "Check name '%s' not found in %s. Exiting." |
| % (check_name_camel, cmake_lists) |
| ) |
| return 1 |
| |
| modulecpp = next( |
| iter( |
| filter( |
| lambda p: p.lower() == old_module.lower() + "tidymodule.cpp", |
| os.listdir(old_module_path), |
| ) |
| ) |
| ) |
| deleteMatchingLines( |
| os.path.join(old_module_path, modulecpp), |
| "\\b" + check_name_camel + "|\\b" + args.old_check_name, |
| ) |
| |
| for filename in getListOfFiles(clang_tidy_path): |
| originalName = filename |
| filename = fileRename( |
| filename, old_module + "/" + old_name, new_module + "/" + new_name |
| ) |
| filename = fileRename(filename, args.old_check_name, args.new_check_name) |
| filename = fileRename(filename, check_name_camel, new_check_name_camel) |
| replaceInFile( |
| filename, |
| generateCommentLineHeader(originalName), |
| generateCommentLineHeader(filename), |
| ) |
| replaceInFile( |
| filename, |
| generateCommentLineSource(originalName), |
| generateCommentLineSource(filename), |
| ) |
| for header_guard in header_guard_variants: |
| replaceInFile(filename, header_guard, header_guard_new) |
| |
| if new_module + "/" + new_name + ".rst" in filename: |
| replaceInFile( |
| filename, |
| args.old_check_name + "\n" + "=" * len(args.old_check_name) + "\n", |
| args.new_check_name + "\n" + "=" * len(args.new_check_name) + "\n", |
| ) |
| |
| replaceInFile(filename, args.old_check_name, args.new_check_name) |
| replaceInFile( |
| filename, |
| old_module + "::" + check_name_camel, |
| new_module + "::" + new_check_name_camel, |
| ) |
| replaceInFile( |
| filename, |
| old_module + "/" + check_name_camel, |
| new_module + "/" + new_check_name_camel, |
| ) |
| replaceInFile( |
| filename, old_module + "/" + old_name, new_module + "/" + new_name |
| ) |
| replaceInFile(filename, check_name_camel, new_check_name_camel) |
| |
| if old_module != new_module or new_module == "llvm": |
| if new_module == "llvm": |
| new_namespace = new_module + "_check" |
| else: |
| new_namespace = 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) |
| replaceInFileRegex( |
| filename, |
| "namespace clang::tidy::" + old_module + "[^ \n]*", |
| "namespace clang::tidy::" + new_namespace, |
| ) |
| |
| if old_module != 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() |