#!/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 <