From 3746c9b9380b8c1e86bdcff984bc2506f9a94297 Mon Sep 17 00:00:00 2001 From: Dan Ilan <15796788+jmpfar@users.noreply.github.com> Date: Sun, 29 Dec 2024 00:12:54 +0200 Subject: [PATCH] Adding clangd-tidy-diff script for lintings of source control deltas Inspired by clang-tidy-diff script from the llvm project (https://clang.llvm.org/extra/doxygen/clang-tidy-diff_8py_source.html). This enables a CI check of only the changes lines --- clangd_tidy/__init__.py | 3 +- clangd_tidy/clangd_tidy_diff_cli.py | 91 +++++++++++++++++++++++++++++ pyproject.toml | 1 + 3 files changed, 94 insertions(+), 1 deletion(-) create mode 100644 clangd_tidy/clangd_tidy_diff_cli.py diff --git a/clangd_tidy/__init__.py b/clangd_tidy/__init__.py index eb89611..7f401a4 100644 --- a/clangd_tidy/__init__.py +++ b/clangd_tidy/__init__.py @@ -1,4 +1,5 @@ +from .clangd_tidy_diff_cli import clang_tidy_diff from .main_cli import main_cli from .version import __version__ -__all__ = ["main_cli", "__version__"] +__all__ = ["main_cli", "clang_tidy_diff", "__version__"] diff --git a/clangd_tidy/clangd_tidy_diff_cli.py b/clangd_tidy/clangd_tidy_diff_cli.py new file mode 100644 index 0000000..e18b01d --- /dev/null +++ b/clangd_tidy/clangd_tidy_diff_cli.py @@ -0,0 +1,91 @@ +""" +Receives a diff on stdin and runs clangd-tidy only on the changed lines. +This is useful to slowly onboard a codebase to linting or to find regressions. +Inspired by clang-tidy-diff.py from the LLVM project. + +Example usage with git: + git diff -U0 HEAD^^..HEAD | clangd-tidy-diff -p my/build +""" + +import argparse +import json +import re +import subprocess +import sys + +from .version import __version__ + +ADDED_FILE_NAME_REGEX = re.compile(r'^\+\+\+ "?(?P.*?/)(?P[^\s"]*)') +ADDED_LINES_REGEX = re.compile(r"^@@.*\+(?P\d+)(,(?P\d+))?") + + +def clang_tidy_diff(): + parser = argparse.ArgumentParser( + description="Runs clangd-tidy against modified files," + " and returns diagnostics only on changed lines." + ) + parser.add_argument( + "-V", "--version", action="version", version=f"%(prog)s {__version__}" + ) + parser.add_argument( + "-p", + "--compile-commands-dir", + help="Specify a path to look for compile_commands.json. If the path is invalid, clangd-tidy will look in the current directory and parent paths of each source file.", + ) + parser.add_argument( + "--pass-arg", + action="append", + help="Pass this argument to clangd-tidy (can be used multiple times)", + ) + args = parser.parse_args() + + last_file = None + lines_per_file = {} + for line in sys.stdin: + m = re.search(ADDED_FILE_NAME_REGEX, line) + if m: + last_file = m.group("file") + + if last_file is None: + continue + + m = re.search(ADDED_LINES_REGEX, line) + if m is None: + continue + + start_line = int(m.group("line")) + line_count = 1 + if m.group("count") is not None: + line_count = int(m.group("count")) + if line_count == 0: + continue + + end_line = start_line + line_count - 1 + lines_per_file.setdefault(last_file, []).append([start_line, end_line]) + + if len(lines_per_file) == 0: + print("No relevant changes found.") + sys.exit(0) + + filters = [] + for file, lines in lines_per_file.items(): + filters.append({"name": file, "lines": lines}) + + filters_json = json.dumps(filters) + command = ["clangd-tidy", "--line-filter", filters_json] + + if args.compile_commands_dir: + command.extend(["--compile-commands-dir", args.compile_commands_dir]) + + if args.pass_arg: + command.extend(args.pass_arg) + + files = list(lines_per_file.keys()) + command.append("--") + command.extend(files) + + sys.exit(subprocess.run(command).returncode) + + +if __name__ == "__main__": + clang_tidy_diff() diff --git a/pyproject.toml b/pyproject.toml index 200fedd..c684e8d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,6 +23,7 @@ classifiers = [ [project.scripts] clangd-tidy = "clangd_tidy:main_cli" +clangd-tidy-diff = "clangd_tidy:clang_tidy_diff" [project.urls] "Homepage" = "https://github.com/lljbash/clangd-tidy"