mirror of
https://github.com/llvm/llvm-project.git
synced 2025-04-16 17:26:35 +00:00

``` > python3 -m mypy --strict clang-tools-extra/clang-tidy/rename_check.py Success: no issues found in 1 source file ```
407 lines
14 KiB
Python
Executable File
407 lines
14 KiB
Python
Executable File
#!/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
|
|
#
|
|
# ===-----------------------------------------------------------------------===#
|
|
|
|
import argparse
|
|
import glob
|
|
import io
|
|
import os
|
|
import re
|
|
import sys
|
|
from typing import List
|
|
|
|
|
|
def replaceInFileRegex(fileName: str, sFrom: str, sTo: str) -> None:
|
|
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: str, sFrom: str, sTo: str) -> None:
|
|
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: str) -> str:
|
|
return "".join(
|
|
[
|
|
"//===--- ",
|
|
os.path.basename(filename),
|
|
" - clang-tidy ",
|
|
"-" * max(0, 42 - len(os.path.basename(filename))),
|
|
"*- C++ -*-===//",
|
|
]
|
|
)
|
|
|
|
|
|
def generateCommentLineSource(filename: str) -> str:
|
|
return "".join(
|
|
[
|
|
"//===--- ",
|
|
os.path.basename(filename),
|
|
" - clang-tidy",
|
|
"-" * max(0, 52 - len(os.path.basename(filename))),
|
|
"-===//",
|
|
]
|
|
)
|
|
|
|
|
|
def fileRename(fileName: str, sFrom: str, sTo: str) -> str:
|
|
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: str, pattern: str) -> bool:
|
|
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: str) -> List[str]:
|
|
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: str, check_name_camel: str) -> bool:
|
|
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: str, module: str, check_name: str, check_name_camel: str
|
|
) -> None:
|
|
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: str, old_check_name: str, new_check_name: str
|
|
) -> None:
|
|
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() -> None:
|
|
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)
|
|
)
|
|
sys.exit(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()
|