d899bf44f7
make -j3 -f Makefile.cbm lint runs the full lint-tidy sweep, which currently surfaces ~5,100 pre-existing clang-tidy findings across 113 files. Since the pre-commit hook (scripts/hooks/pre-commit) runs that target unconditionally, it blocks every commit for every contributor who has clang-tidy on PATH, regardless of what the commit touches. Add lint-tidy-diff (scripts/lint-tidy-diff.sh), which runs the same clang-tidy binary/config but passes clang-tidy's own -line-filter so only lines the commit actually added or modified can produce a diagnostic. Pre-existing findings on untouched lines are not reported, whether they are in an untouched file or on an untouched line of a file the commit does touch. The hook now runs lint-ci (unchanged: cppcheck + clang-format + no-suppress) plus lint-tidy-diff instead of the full lint target; make lint / make lint-tidy / scripts/lint.sh are unchanged and remain the full-tree audit. Signed-off-by: Yyunozor <yyunozor@icloud.com>
109 lines
4.0 KiB
Bash
Executable File
109 lines
4.0 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# lint-tidy-diff.sh — clang-tidy scoped to the lines this commit changes.
|
|
#
|
|
# `make lint-tidy` (the full sweep) checks the entire LINT_SRCS tree, which
|
|
# currently surfaces ~5,100 pre-existing findings unrelated to any given
|
|
# commit (see Makefile.cbm's `lint-tidy` target). That is fine for a manual
|
|
# deep audit but makes the pre-commit hook unusable: it blocks every commit,
|
|
# including ones that touch files with zero findings on the touched lines.
|
|
#
|
|
# This script runs the same clang-tidy binary/config against the SAME staged
|
|
# .c files, but passes clang-tidy's own `-line-filter` so only lines the
|
|
# commit actually added/modified can produce a diagnostic. Pre-existing
|
|
# findings on untouched lines — in the same file or elsewhere — are not
|
|
# reported.
|
|
#
|
|
# Invoked by `make lint-tidy-diff` (scripts/hooks/pre-commit), which supplies
|
|
# the resolved clang-tidy binary, then LINT_SRCS, then `--`, then the
|
|
# compiler flags — the same argv shape as the `lint-tidy` target itself.
|
|
#
|
|
# Known limitation: only staged *.c files that are members of LINT_SRCS are
|
|
# checked (matching the full sweep's own scope: vendored code and tests/ are
|
|
# never clang-tidy'd). clang-tidy reports a header's diagnostics under
|
|
# whatever path it was #include-d with, which is not reliably the same
|
|
# string as its path in the working tree, so a commit that touches only a
|
|
# header is not covered here even if a .c file including it also changed;
|
|
# run `make lint-tidy` for a full pass in that case.
|
|
|
|
set -euo pipefail
|
|
|
|
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
|
cd "$ROOT"
|
|
|
|
if [ "$#" -lt 1 ]; then
|
|
echo "usage: lint-tidy-diff.sh <clang-tidy-bin> [LINT_SRCS...] -- [CFLAGS...]" >&2
|
|
exit 2
|
|
fi
|
|
|
|
CLANG_TIDY="$1"
|
|
shift
|
|
|
|
LINT_SRCS=()
|
|
while [ "$#" -gt 0 ] && [ "$1" != "--" ]; do
|
|
LINT_SRCS+=("$1")
|
|
shift
|
|
done
|
|
[ "$#" -gt 0 ] && shift # drop the `--` separator
|
|
CFLAGS=("$@")
|
|
|
|
# bash 3.2 (macOS default, see scripts/check-lsp-originality.sh) has neither
|
|
# `mapfile` nor associative arrays — read loop + linear membership check.
|
|
is_lint_src() {
|
|
local needle="$1" f
|
|
[ "${#LINT_SRCS[@]}" -eq 0 ] && return 1
|
|
for f in "${LINT_SRCS[@]}"; do
|
|
[ "$f" = "$needle" ] && return 0
|
|
done
|
|
return 1
|
|
}
|
|
|
|
# Staged .c files this commit actually changed (added/copied/modified/
|
|
# renamed — a delete has no new lines to check) that are members of
|
|
# LINT_SRCS.
|
|
CHANGED=()
|
|
while IFS= read -r f; do
|
|
[ -n "$f" ] && is_lint_src "$f" && CHANGED+=("$f")
|
|
done < <(git diff --cached --name-only --diff-filter=ACMR -- '*.c')
|
|
|
|
if [ "${#CHANGED[@]}" -eq 0 ]; then
|
|
echo "lint-tidy-diff: no staged changes to a linted .c file, skipping clang-tidy"
|
|
exit 0
|
|
fi
|
|
|
|
# For each changed file, turn its staged hunks into a clang-tidy line-filter
|
|
# range: `@@ -a,b +c,d @@` -> lines [c, c+d-1]. Zero-context (-U0) so a range
|
|
# never includes untouched, pre-existing lines. Pure-deletion hunks (d == 0)
|
|
# add no new lines and are skipped.
|
|
LINE_FILTER='['
|
|
FILTER_SEP=''
|
|
TARGETS=()
|
|
for f in "${CHANGED[@]}"; do
|
|
[ -f "$f" ] || continue # renamed away / not present in the working tree
|
|
ranges=''
|
|
range_sep=''
|
|
while IFS= read -r hunk; do
|
|
if [[ "$hunk" =~ ^@@\ -[0-9]+(,[0-9]+)?\ \+([0-9]+)(,([0-9]+))?\ @@ ]]; then
|
|
new_start="${BASH_REMATCH[2]}"
|
|
new_count="${BASH_REMATCH[4]:-1}"
|
|
[ "$new_count" -eq 0 ] && continue
|
|
new_end=$((new_start + new_count - 1))
|
|
ranges+="${range_sep}[${new_start},${new_end}]"
|
|
range_sep=','
|
|
fi
|
|
done < <(git diff --cached -U0 --diff-filter=ACMR -- "$f" | grep -E '^@@ ')
|
|
[ -z "$ranges" ] && continue
|
|
|
|
LINE_FILTER+="${FILTER_SEP}{\"name\":\"${f}\",\"lines\":[${ranges}]}"
|
|
FILTER_SEP=','
|
|
TARGETS+=("$f")
|
|
done
|
|
LINE_FILTER+=']'
|
|
|
|
if [ "${#TARGETS[@]}" -eq 0 ]; then
|
|
echo "lint-tidy-diff: staged .c changes are deletion-only, skipping clang-tidy"
|
|
exit 0
|
|
fi
|
|
|
|
echo "lint-tidy-diff: ${#TARGETS[@]} changed file(s), diff-scoped clang-tidy"
|
|
"$CLANG_TIDY" --quiet "-line-filter=${LINE_FILTER}" "${TARGETS[@]}" -- "${CFLAGS[@]}"
|