#!/usr/bin/env bash
set -euo pipefail
# Git exports these to its hooks, and they outrank the `-C
` the probes
# below rely on: every file then resolves its own directory as the repository,
# so a main checkout skips the file and a linked worktree measures all of it.
unset GIT_DIR GIT_WORK_TREE GIT_INDEX_FILE
max_density="${COMMENT_DENSITY_MAX:-5}"
min_lines="${COMMENT_DENSITY_MIN_LINES:-20}"
is_checkable() {
local file="$1"
case "$file" in
*.dart | *.kt | *.kts | *.swift | *.go | *.rs | *.java | *.cpp | *.cc | *.h | *.hpp | *.gradle | *.yaml | *.yml) ;;
*) return 1 ;;
esac
case "$file" in
*/build/* | */generated/* | */l10n/intl/* | */l10n/l10n.dart | */Clash.Meta/* | */.dart_tool/*) return 1 ;;
*.g.dart | *.freezed.dart | */frb_generated*.dart | */frb_generated.rs | */open_container.dart) return 1 ;;
esac
return 0
}
added_for() {
local file="$1"
[ -f "$file" ] || return 0
is_checkable "$file" || return 0
local repo relative
repo="$(git -C "$(dirname "$file")" rev-parse --show-toplevel 2>/dev/null || true)"
[ -n "$repo" ] || return 0
relative="$file"
case "$file" in
"$repo"/*) relative="${file#"$repo"/}" ;;
esac
if git -C "$repo" ls-files --error-unmatch "$relative" >/dev/null 2>&1; then
git -C "$repo" diff HEAD -U0 -- "$relative" | grep -E '^\+' | grep -vE '^\+\+\+' |
sed "s|^+|$relative\t|" || true
else
sed "s|^|$relative\t|" "$file" || true
fi
}
comment_lines() {
local body="$1"
printf '%s\n' "$body" |
grep -E "$(printf '\t')[[:space:]]*(//|/\*|#|\*([[:space:]]|/|$))" |
grep -vE 'ignore:|ignore_for_file:|coverage:ignore|Copyright|SPDX-|Licensed under|GENERATED|dart format off|yaml-language-server|[[:space:]]#!$|[[:space:]]#!/|#!?\[|#(include|define|undef|ifdef|ifndef|if|elif|else|endif|pragma|error)\b' || true
}
changed_files() {
local repo="$1"
{
git -C "$repo" diff HEAD --name-only -z 2>/dev/null || true
git -C "$repo" ls-files --others --exclude-standard -z 2>/dev/null || true
}
}
evaluate() {
local file="$1" added total hits count density
added="$(added_for "$file")"
[ -n "$added" ] || return 0
total="$(printf '%s\n' "$added" | grep -c . || true)"
[ "$total" -ge "$min_lines" ] || return 0
hits="$(comment_lines "$added")"
count="$(printf '%s' "$hits" | grep -c . || true)"
[ "$count" -gt 0 ] || return 0
density="$(awk -v c="$count" -v t="$total" 'BEGIN { printf "%.1f", c * 100 / t }')"
[ "$(awk -v d="$density" -v m="$max_density" 'BEGIN { print (d > m) ? 1 : 0 }')" -eq 1 ] || return 0
printf '%s: %s of %s added lines are standalone comments (%s%%)\n\n' \
"$(printf '%s\n' "$added" | head -1 | cut -f1)" "$count" "$total" "$density"
printf '%s\n\n' "$(printf '%s\n' "$hits" | sed 's/\t/: /')"
}
collect() {
local file
for file in "$@"; do
case "$file" in
/*) ;;
*) file="$PWD/$file" ;;
esac
evaluate "$file"
done
}
collect_changed() {
local repo file
repo="$(git rev-parse --show-toplevel 2>/dev/null || true)"
[ -n "$repo" ] || return 0
while IFS= read -r -d '' file; do
evaluate "$repo/$file"
done < <(changed_files "$repo")
}
if [ "$#" -gt 0 ]; then
mode='argv'
findings="$(collect "$@")"
else
mode='hook'
payload="$(cat)"
file="$(printf '%s' "$payload" | jq -r '.tool_input.file_path // .tool_response.filePath // empty' 2>/dev/null || true)"
if [ -n "$file" ]; then
findings="$(collect "$file")"
else
findings="$(collect_changed)"
fi
fi
[ -n "$findings" ] || exit 0
cat >&2 <