diff --git a/src/check-standard.sh b/src/check-standard.sh index dff1249..0f9dc55 100755 --- a/src/check-standard.sh +++ b/src/check-standard.sh @@ -18,6 +18,7 @@ export LC_ALL=C MAX_BYTES=$((5 * 1024 * 1024)) # Besides letters, digits and . _ - this allows the route symbols frameworks put in file names: # [slug] (group) @slot +page $param {-$optional}. +# shellcheck disable=SC2016 # the $ is a literal character here, not an expansion NAME_RE='^[][A-Za-z0-9._@+()${}-]+$' KEBAB_RE='^[a-z0-9]+(-[a-z0-9]+)*(\.[a-z0-9]+)*$' TITLE_RE='^(feat|fix|docs|chore|refactor|test|perf|ci|build|style|revert)(\([a-z0-9._/-]+\))?!?: [^ ]' @@ -74,6 +75,7 @@ kebab_of() { # suggested kebab-case spelling of a name # --- exceptions --------------------------------------------------------------------------- +# shellcheck disable=SC2094 # report only names the file in a message; it never writes to it load_exceptions() { [ -f "$EXCEPTIONS_FILE" ] || return 0 local line pattern reason n=0 @@ -141,7 +143,7 @@ set_kind() { # NAME — sets $kind for file types with their own rule, or emptie } check_path() { # PATH — name and file-type rules for one repository-relative path - local path=$1 rest=$1 comp first= depth=0 + local path=$1 rest=$1 comp first='' depth=0 is_excepted "$path" && return 0 while :; do comp=${rest%%/*} @@ -304,7 +306,7 @@ run_staged() { } run_hook() { # blocks Claude Code from creating a file whose name breaks the standard - local input file_path script_dir root_l root_p rel cur rest comp match + local input file_path script_dir root_l root_p rel cur rest comp match entry input=$(cat) if ! command -v jq >/dev/null 2>&1; then echo "check-standard: jq is not installed, so the file-name check was skipped." >&2 @@ -332,7 +334,14 @@ run_hook() { # blocks Claude Code from creating a file whose name breaks the sta rest=$rel while [ "$errors" -eq 0 ]; do # a new name must not clash by letter case with an existing one comp=${rest%%/*} - match=$(ls -A "$cur" 2>/dev/null | grep -ixF -- "$comp" | grep -vxF -- "$comp" | head -n 1) + match= + shopt -s nocasematch + for entry in "$cur"/* "$cur"/.[!.]* "$cur"/..?*; do + [ -e "$entry" ] || continue + entry=${entry##*/} + if [ "$entry" != "$comp" ] && [[ $entry == "$comp" ]]; then match=$entry; break; fi + done + shopt -u nocasematch if [ -n "$match" ]; then report error "$rel" "\"$comp\" differs only by letter case from the existing \"$match\". Use \"$match\"." break diff --git a/tests/check-standard.test.sh b/tests/check-standard.test.sh index 4bf4109..396fc66 100755 --- a/tests/check-standard.test.sh +++ b/tests/check-standard.test.sh @@ -75,7 +75,7 @@ expect_clean() { echo "== whole repository ==" new_repo; run; expect_clean "clean repository passes" -cd "$TMP"; run; expect "outside a git repository fails" 1 "git repository" +cd "$TMP" || exit 1; run; expect "outside a git repository fails" 1 "git repository" new_repo; rm README.md; run; expect "missing README.md fails" 1 "README.md" new_repo; printf '# no import\n' > CLAUDE.md; run; expect "CLAUDE.md without @AGENTS.md fails" 1 "@AGENTS.md" @@ -98,6 +98,7 @@ touch "src/app/[slug]/page.tsx" "src/app/(marketing)/page.tsx" "src/app/@modal/d src/adlete3d/body/__init__.py src/components/Button.tsx run; expect_clean "framework names inside src pass" new_repo; mkdir -p "src/routes/{-\$locale}" +# shellcheck disable=SC2016 # literal $ in TanStack Router file names touch 'src/routes/$username.tsx' 'src/routes/{-$locale}/index.tsx' src/routes/__root.tsx run; expect_clean "TanStack Router names inside src pass" new_repo; blob=$(git hash-object -w /dev/null) @@ -160,7 +161,7 @@ new_repo; touch "docs/old notes.md"; git add -A; git_q commit -qm legacy echo change >> "docs/old notes.md"; git add -A; run --staged; expect_clean "editing a legacy file does not block the commit" new_repo; head -c 6000000 /dev/zero > src/model.bin; git add -A; run --staged; expect "file over 5 MB blocks the commit" 1 "src/model.bin" new_repo; mkdir tasks; touch tasks/todo.md; git add -A; run --staged; expect "new unknown top-level folder warns but passes" 0 "tasks/" -dir=$(mktemp -d "$TMP/empty.XXXXXX"); cd "$dir"; git init -q -b main; touch "Bad Name.md"; git add -A +dir=$(mktemp -d "$TMP/empty.XXXXXX"); cd "$dir" || exit 1; git init -q -b main; touch "Bad Name.md"; git add -A run --staged; expect "first commit of an empty repository is checked" 1 "Bad Name.md" echo "== Claude Code hook =="