Foundry/tt/tools/check_comment_only_change.sh

91 lines
3.6 KiB
Bash
Executable File

#!/usr/bin/env bash
# SPDX-License-Identifier: AGPL-3.0-or-later
# Copyright (C) 2026 David (Corey) Herrera
#
# check_comment_only_change.sh
# Proves a change touched COMMENTS ONLY: every changed C/C++/asm source between a git ref
# and the working tree must have the same comment-stripped token stream on both sides.
#
# Usage: check_comment_only_change.sh <base-ref> [path ...]
# Paths default to the whole repository. Exit 0 = comment-only, 1 = a code change
# (each one named), 2 = usage or tool error.
#
# HOW. A tokenizer that knows string and character literals replaces every comment with a
# space; whitespace is then squeezed and blank lines dropped, so re-wrapping a comment
# cannot read as a change. Python files are compared on their AST with docstrings removed.
# Any other changed file is reported as unchecked, never passed silently.
#
# PROVEN RED 2026-10-08: the first version stripped with `clang -fpreprocessed`, which Apple
# clang does not have; the error was swallowed, both sides were empty, and it passed a real
# code change. The stripper now fails loudly on an error or on an empty result.
#
# Status: added 2026-10-08 for the clean-room Phase 0 (licence notices are comments only).
# Issues: None
# Todo: None
#
set -uo pipefail
BASE=${1:?usage: check_comment_only_change.sh <base-ref> [path ...]}
shift
ROOT=$(git rev-parse --show-toplevel) || exit 2
cd "$ROOT" || exit 2
WORK=$(mktemp -d "${TMPDIR:-/tmp}/comment_only.XXXXXX")
trap 'rm -rf "$WORK"' EXIT
cat > "$WORK/strip_c.py" <<'PYEOF'
import re, sys
text = open(sys.argv[1], encoding="utf-8", errors="surrogateescape").read()
token = re.compile(r'//[^\n]*|/\*.*?\*/|"(?:\\.|[^"\\\n])*"|\'(?:\\.|[^\'\\\n])*\'|\s+|.', re.S)
out = []
for match in token.finditer(text):
piece = match.group(0)
if piece.startswith("//") or piece.startswith("/*"):
out.append(" ")
elif piece.isspace():
out.append("\n" if "\n" in piece else " ")
else:
out.append(piece)
code = [" ".join(line.split()) for line in "".join(out).split("\n")]
code = [line for line in code if line]
if not code:
sys.exit("strip_c: %s has no code left after stripping" % sys.argv[1])
print("\n".join(code))
PYEOF
cat > "$WORK/strip_py.py" <<'PYEOF'
import ast, sys
tree = ast.parse(open(sys.argv[1], encoding="utf-8").read())
for node in ast.walk(tree):
body = getattr(node, "body", None)
if isinstance(body, list) and body and isinstance(body[0], ast.Expr) \
and isinstance(getattr(body[0], "value", None), ast.Constant) \
and isinstance(body[0].value.value, str):
node.body = body[1:]
print(ast.dump(tree))
PYEOF
code=0 checked=0
while IFS= read -r path; do
[ -f "$path" ] || { echo "DELETED $path"; code=1; continue; }
old="$WORK/old"
git show "$BASE:$path" > "$old" 2>/dev/null || { echo "NEW FILE $path"; code=1; continue; }
case "$path" in
*.c|*.h|*.cc|*.cpp|*.hpp|*.S|*.ld) stripper=$WORK/strip_c.py ;;
*.py) stripper=$WORK/strip_py.py ;;
*) echo "UNCHECKED $path (not a C or Python source)"; code=1; continue ;;
esac
python3 "$stripper" "$old" > "$WORK/a" && python3 "$stripper" "$path" > "$WORK/b" ||
{ echo "ERROR $path (stripper failed)"; exit 2; }
checked=$((checked + 1))
if cmp -s "$WORK/a" "$WORK/b"; then
echo "comments $path"
else
echo "CODE $path"; diff "$WORK/a" "$WORK/b" | head -10 | sed 's/^/ /'
code=1
fi
done < <(git diff --name-only "$BASE" -- "$@")
echo "checked $checked file(s): $([ $code -eq 0 ] && echo COMMENT-ONLY || echo NOT comment-only)"
exit $code