brintos

brintos / llvm-project-archived public Read only

0
0
Text · 12.6 KiB · b864bff Raw
373 lines · python
1#!/usr/bin/env python32#3# ===-----------------------------------------------------------------------===#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-exception8#9# ===-----------------------------------------------------------------------===#10 11import argparse12import glob13import io14import os15import re16import sys17from typing import List18 19 20def replaceInFileRegex(fileName: str, sFrom: str, sTo: str) -> None:21    if sFrom == sTo:22        return23 24    # The documentation files are encoded using UTF-8, however on Windows the25    # default encoding might be different (e.g. CP-1252). To make sure UTF-8 is26    # always used, use `io.open(filename, mode, encoding='utf8')` for reading and27    # writing files here and elsewhere.28    txt = None29    with io.open(fileName, "r", encoding="utf8") as f:30        txt = f.read()31 32    txt = re.sub(sFrom, sTo, txt)33    print("Replacing '%s' -> '%s' in '%s'..." % (sFrom, sTo, fileName))34    with io.open(fileName, "w", encoding="utf8") as f:35        f.write(txt)36 37 38def replaceInFile(fileName: str, sFrom: str, sTo: str) -> None:39    if sFrom == sTo:40        return41    txt = None42    with io.open(fileName, "r", encoding="utf8") as f:43        txt = f.read()44 45    if sFrom not in txt:46        return47 48    txt = txt.replace(sFrom, sTo)49    print("Replacing '%s' -> '%s' in '%s'..." % (sFrom, sTo, fileName))50    with io.open(fileName, "w", encoding="utf8") as f:51        f.write(txt)52 53 54def fileRename(fileName: str, sFrom: str, sTo: str) -> str:55    if sFrom not in fileName or sFrom == sTo:56        return fileName57    newFileName = fileName.replace(sFrom, sTo)58    print("Renaming '%s' -> '%s'..." % (fileName, newFileName))59    os.rename(fileName, newFileName)60    return newFileName61 62 63def deleteMatchingLines(fileName: str, pattern: str) -> bool:64    lines = None65    with io.open(fileName, "r", encoding="utf8") as f:66        lines = f.readlines()67 68    not_matching_lines = [l for l in lines if not re.search(pattern, l)]69    if len(not_matching_lines) == len(lines):70        return False71 72    print("Removing lines matching '%s' in '%s'..." % (pattern, fileName))73    print("  " + "  ".join([l for l in lines if re.search(pattern, l)]))74    with io.open(fileName, "w", encoding="utf8") as f:75        f.writelines(not_matching_lines)76 77    return True78 79 80def getListOfFiles(clang_tidy_path: str) -> List[str]:81    files = glob.glob(os.path.join(clang_tidy_path, "**"), recursive=True)82    files += [83        os.path.normpath(os.path.join(clang_tidy_path, "../docs/ReleaseNotes.rst"))84    ]85    files += glob.glob(86        os.path.join(clang_tidy_path, "..", "test", "clang-tidy", "checkers", "**"),87        recursive=True,88    )89    files += glob.glob(90        os.path.join(clang_tidy_path, "..", "docs", "clang-tidy", "checks", "*.rst")91    )92    files += glob.glob(93        os.path.join(94            clang_tidy_path, "..", "docs", "clang-tidy", "checks", "*", "*.rst"95        ),96        recursive=True,97    )98    return [filename for filename in files if os.path.isfile(filename)]99 100 101# Adapts the module's CMakelist file. Returns 'True' if it could add a new102# entry and 'False' if the entry already existed.103def adapt_cmake(module_path: str, check_name_camel: str) -> bool:104    filename = os.path.join(module_path, "CMakeLists.txt")105    with io.open(filename, "r", encoding="utf8") as f:106        lines = f.readlines()107 108    cpp_file = check_name_camel + ".cpp"109 110    # Figure out whether this check already exists.111    for line in lines:112        if line.strip() == cpp_file:113            return False114 115    print("Updating %s..." % filename)116    with io.open(filename, "w", encoding="utf8") as f:117        cpp_found = False118        file_added = False119        for line in lines:120            cpp_line = line.strip().endswith(".cpp")121            if (not file_added) and (cpp_line or cpp_found):122                cpp_found = True123                if (line.strip() > cpp_file) or (not cpp_line):124                    f.write("  " + cpp_file + "\n")125                    file_added = True126            f.write(line)127 128    return True129 130 131# Modifies the module to include the new check.132def adapt_module(133    module_path: str, module: str, check_name: str, check_name_camel: str134) -> None:135    modulecpp = next(136        iter(137            filter(138                lambda p: p.lower() == module.lower() + "tidymodule.cpp",139                os.listdir(module_path),140            )141        )142    )143    filename = os.path.join(module_path, modulecpp)144    with io.open(filename, "r", encoding="utf8") as f:145        lines = f.readlines()146 147    print("Updating %s..." % filename)148    with io.open(filename, "w", encoding="utf8") as f:149        header_added = False150        header_found = False151        check_added = False152        check_decl = (153            "    CheckFactories.registerCheck<"154            + check_name_camel155            + '>(\n        "'156            + check_name157            + '");\n'158        )159 160        for line in lines:161            if not header_added:162                match = re.search('#include "(.*)"', line)163                if match:164                    header_found = True165                    if match.group(1) > check_name_camel:166                        header_added = True167                        f.write('#include "' + check_name_camel + '.h"\n')168                elif header_found:169                    header_added = True170                    f.write('#include "' + check_name_camel + '.h"\n')171 172            if not check_added:173                if line.strip() == "}":174                    check_added = True175                    f.write(check_decl)176                else:177                    match = re.search("registerCheck<(.*)>", line)178                    if match and match.group(1) > check_name_camel:179                        check_added = True180                        f.write(check_decl)181            f.write(line)182 183 184# Adds a release notes entry.185def add_release_notes(186    clang_tidy_path: str, old_check_name: str, new_check_name: str187) -> None:188    filename = os.path.normpath(189        os.path.join(clang_tidy_path, "../docs/ReleaseNotes.rst")190    )191    with io.open(filename, "r", encoding="utf8") as f:192        lines = f.readlines()193 194    lineMatcher = re.compile("Renamed checks")195    nextSectionMatcher = re.compile("Improvements to include-fixer")196    checkMatcher = re.compile("- The '(.*)")197 198    print("Updating %s..." % filename)199    with io.open(filename, "w", encoding="utf8") as f:200        note_added = False201        header_found = False202        add_note_here = False203 204        for line in lines:205            if not note_added:206                match = lineMatcher.match(line)207                match_next = nextSectionMatcher.match(line)208                match_check = checkMatcher.match(line)209                if match_check:210                    last_check = match_check.group(1)211                    if last_check > old_check_name:212                        add_note_here = True213 214                if match_next:215                    add_note_here = True216 217                if match:218                    header_found = True219                    f.write(line)220                    continue221 222                if line.startswith("^^^^"):223                    f.write(line)224                    continue225 226                if header_found and add_note_here:227                    if not line.startswith("^^^^"):228                        f.write(229                            """- The '%s' check was renamed to :doc:`%s230  <clang-tidy/checks/%s/%s>`231 232                    """233                            % (234                                old_check_name,235                                new_check_name,236                                new_check_name.split("-", 1)[0],237                                "-".join(new_check_name.split("-")[1:]),238                            )239                        )240                        note_added = True241 242            f.write(line)243 244 245def main() -> None:246    parser = argparse.ArgumentParser(description="Rename clang-tidy check.")247    parser.add_argument("old_check_name", type=str, help="Old check name.")248    parser.add_argument("new_check_name", type=str, help="New check name.")249    parser.add_argument(250        "--check_class_name",251        type=str,252        help="Old name of the class implementing the check.",253    )254    args = parser.parse_args()255 256    old_module = args.old_check_name.split("-")[0]257    new_module = args.new_check_name.split("-")[0]258    old_name = "-".join(args.old_check_name.split("-")[1:])259    new_name = "-".join(args.new_check_name.split("-")[1:])260 261    if args.check_class_name:262        check_name_camel = args.check_class_name263    else:264        check_name_camel = (265            "".join(map(lambda elem: elem.capitalize(), old_name.split("-"))) + "Check"266        )267 268    new_check_name_camel = (269        "".join(map(lambda elem: elem.capitalize(), new_name.split("-"))) + "Check"270    )271 272    clang_tidy_path = os.path.dirname(__file__)273 274    header_guard_variants = [275        (args.old_check_name.replace("-", "_")).upper() + "_CHECK",276        (old_module + "_" + check_name_camel).upper(),277        (old_module + "_" + new_check_name_camel).upper(),278        args.old_check_name.replace("-", "_").upper(),279    ]280    header_guard_new = (new_module + "_" + new_check_name_camel).upper()281 282    old_module_path = os.path.join(clang_tidy_path, old_module)283    new_module_path = os.path.join(clang_tidy_path, new_module)284 285    if old_module != new_module:286        # Remove the check from the old module.287        cmake_lists = os.path.join(old_module_path, "CMakeLists.txt")288        check_found = deleteMatchingLines(cmake_lists, "\\b" + check_name_camel)289        if not check_found:290            print(291                "Check name '%s' not found in %s. Exiting."292                % (check_name_camel, cmake_lists)293            )294            sys.exit(1)295 296        modulecpp = next(297            iter(298                filter(299                    lambda p: p.lower() == old_module.lower() + "tidymodule.cpp",300                    os.listdir(old_module_path),301                )302            )303        )304        deleteMatchingLines(305            os.path.join(old_module_path, modulecpp),306            "\\b" + check_name_camel + "|\\b" + args.old_check_name,307        )308 309    for filename in getListOfFiles(clang_tidy_path):310        originalName = filename311        filename = fileRename(312            filename, old_module + "/" + old_name, new_module + "/" + new_name313        )314        filename = fileRename(filename, args.old_check_name, args.new_check_name)315        filename = fileRename(filename, check_name_camel, new_check_name_camel)316        for header_guard in header_guard_variants:317            replaceInFile(filename, header_guard, header_guard_new)318 319        if new_module + "/" + new_name + ".rst" in filename:320            replaceInFile(321                filename,322                args.old_check_name + "\n" + "=" * len(args.old_check_name) + "\n",323                args.new_check_name + "\n" + "=" * len(args.new_check_name) + "\n",324            )325 326        replaceInFile(filename, args.old_check_name, args.new_check_name)327        replaceInFile(328            filename,329            old_module + "::" + check_name_camel,330            new_module + "::" + new_check_name_camel,331        )332        replaceInFile(333            filename,334            old_module + "/" + check_name_camel,335            new_module + "/" + new_check_name_camel,336        )337        replaceInFile(338            filename, old_module + "/" + old_name, new_module + "/" + new_name339        )340        replaceInFile(filename, check_name_camel, new_check_name_camel)341 342    if old_module != new_module or new_module == "llvm":343        if new_module == "llvm":344            new_namespace = new_module + "_check"345        else:346            new_namespace = new_module347        check_implementation_files = glob.glob(348            os.path.join(old_module_path, new_check_name_camel + "*")349        )350        for filename in check_implementation_files:351            # Move check implementation to the directory of the new module.352            filename = fileRename(filename, old_module_path, new_module_path)353            replaceInFileRegex(354                filename,355                "namespace clang::tidy::" + old_module + "[^ \n]*",356                "namespace clang::tidy::" + new_namespace,357            )358 359    if old_module != new_module:360 361        # Add check to the new module.362        adapt_cmake(new_module_path, new_check_name_camel)363        adapt_module(364            new_module_path, new_module, args.new_check_name, new_check_name_camel365        )366 367    os.system(os.path.join(clang_tidy_path, "add_new_check.py") + " --update-docs")368    add_release_notes(clang_tidy_path, args.old_check_name, args.new_check_name)369 370 371if __name__ == "__main__":372    main()373