scripts/run-clang-tidy.py (85 lines of code) (raw):

#!/usr/bin/env python3 # Copyright (c) Facebook, Inc. and its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import argparse from itertools import groupby import json import regex import sys import util CODE_CHECKS = """* -abseil-* -android-* -cert-err58-cpp -clang-analyzer-osx-* -cppcoreguidelines-avoid-c-arrays -cppcoreguidelines-avoid-magic-numbers -cppcoreguidelines-pro-bounds-array-to-pointer-decay -cppcoreguidelines-pro-bounds-pointer-arithmetic -cppcoreguidelines-pro-type-reinterpret-cast -cppcoreguidelines-pro-type-vararg -fuchsia-* -google-* -hicpp-avoid-c-arrays -hicpp-deprecated-headers -hicpp-no-array-decay -hicpp-use-equals-default -hicpp-vararg -llvmlibc-* -llvm-header-guard -llvm-include-order -mpi-* -misc-non-private-member-variables-in-classes -misc-no-recursion -misc-unused-parameters -modernize-avoid-c-arrays -modernize-deprecated-headers -modernize-use-nodiscard -modernize-use-trailing-return-type -objc-* -openmp-* -readability-avoid-const-params-in-decls -readability-convert-member-functions-to-static -readability-magic-numbers -zircon-* """ # Additional opt-outs because googletest macros trip too many things. # TEST_CHECKS = ( CODE_CHECKS + """ -cert-err58-cpp -cppcoreguidelines-avoid-goto -cppcoreguidelines-avoid-non-const-global-variables -cppcoreguidelines-owning-memory -cppcoreguidelines-pro-type-vararg -cppcoreguidelines-special-member-functions -hicpp-avoid-goto -hicpp-special-member-functions -hicpp-vararg -misc-no-recursion -readability-implicit-bool-conversion """ ) def check_list(check_string): return ",".join([check.strip() for check in check_string.strip().splitlines()]) CODE_CHECKS = check_list(CODE_CHECKS) TEST_CHECKS = check_list(TEST_CHECKS) class Multimap(dict): def __setitem__(self, key, value): if key not in self: dict.__setitem__(self, key, [value]) # call super method to avoid recursion else: self[key].append(value) def git_changed_lines(commit): file = "" changed_lines = Multimap() for line in util.run(f"git diff --text --unified=0 {commit}")[1].splitlines(): line = line.rstrip("\n") fields = line.split() match = regex.match(r"^\+\+\+ b/.*", line) if match: file = "" match = regex.match(r"^\+\+\+ b/(.*(\.cpp|\.h))$", line) if match: file = match.group(1) match = regex.match(r"^@@", line) if match and file != "": lspan = fields[2].split(",") if len(lspan) <= 1: lspan.append(0) changed_lines[file] = [int(lspan[0]), int(lspan[0]) + int(lspan[1])] return json.dumps( [{"name": key, "lines": value} for key, value in changed_lines.items()] ) def checks(args): status, stdout, stderr = util.run( f"clang-tidy -checks='{CODE_CHECKS}' --list-checks" ) print(stdout) def check_output(output): return regex.match(r"^/.* warning: ", output) def tidy(args): files = util.input_files(args.files) groups = Multimap() for file in files: groups["test" if "/tests/" in file else "main"] = file fix = "--fix" if args.fix == "fix" else "" lines = ( ("'--line-filter=" + git_changed_lines(args.commit)) + "'" if args.commit is not None else "" ) ok = True if groups.get("main", None): status, stdout, stderr = util.run( f"xargs clang-tidy -p=build/release/ --format-style=file -header-filter='.*' --checks='{CODE_CHECKS}' --quiet {fix} {lines}", input=groups["main"], ) ok = check_output(stdout) and ok if groups.get("test", None): status, stdout, stderr = util.run( f"xargs clang-tidy -p=build/release/ --format-style=file -header-filter='.*' --checks='{TEST_CHECKS}' --quiet {fix} {lines}", input=groups["test"], ) ok = check_output(stdout) and ok return 0 if ok else 1 def parse_args(): global parser parser = argparse.ArgumentParser(description="CircliCi Utility") parser.add_argument("--commit") parser.add_argument("--fix") parser.add_argument("files", metavar="FILES", nargs="+", help="files to process") return parser.parse_args() def main(): return tidy(parse_args()) if __name__ == "__main__": sys.exit(main())