From 47b1f952efbd23d09f8341870fccd5ac00475685 Mon Sep 17 00:00:00 2001 From: olavostauros Date: Tue, 23 Jun 2026 17:26:28 -0300 Subject: [PATCH 1/3] feat: add lint rule for Bash nounset empty array expansions Adds bash-empty-array-expansions lint rule that flags ${array[@]} and ${array[*]} under set -u (nounset), suggesting the Bash-3-safe alternate-value form ${arr[@]+"${arr[@]}"}. Shares nounset detection pattern with bash-empty-argv-forwarding (#48): - has_nounset() - detect set -u / set -eu / set -euo pipefail - is_safe_context() - exclude for loops and local array assignments - has_safe_form() - exclude already-safe alternate-value forms Standard text-based ripgrep approach with 11 fixture scenarios and 15 BATS tests. ShellCheck-clean. Implements #57 (KnickKnackLabs/codebase) --- .mise/tasks/lint/bash-empty-array-expansions | 168 ++++++++++++++++++ .mise/tasks/lint/github-actions | 2 +- .mise/tasks/lint/mise-settings | 2 +- .mise/tasks/lint/shellcheck | 2 +- .../bash-empty-array-expansions.bats | 155 ++++++++++++++++ .../fixtures/already-safe/.mise/tasks/t | 7 + .../fixtures/already-safe/mise.toml | 2 + .../fixtures/broad-walk/lib/helper.sh | 6 + .../fixtures/broad-walk/mise.toml | 2 + .../fixtures/clean/.mise/tasks/t | 4 + .../fixtures/clean/mise.toml | 3 + .../fixtures/dirty-multiple/.mise/tasks/t | 10 ++ .../fixtures/dirty-multiple/mise.toml | 2 + .../fixtures/dirty-star/.mise/tasks/t | 8 + .../fixtures/dirty-star/mise.toml | 2 + .../fixtures/dirty/.mise/tasks/t | 8 + .../fixtures/dirty/mise.toml | 2 + .../fixtures/ignored-file/.mise/tasks/t | 6 + .../fixtures/ignored-file/mise.toml | 1 + .../fixtures/ignored-inline/.mise/tasks/t | 6 + .../fixtures/ignored-inline/mise.toml | 2 + .../fixtures/no-nounset/.mise/tasks/t | 4 + .../fixtures/no-nounset/mise.toml | 2 + .../fixtures/no-toml/.mise/tasks/t | 4 + .../fixtures/safe-context-local/.mise/tasks/t | 8 + .../fixtures/safe-context-local/mise.toml | 2 + .../fixtures/safe-context/.mise/tasks/t | 7 + .../fixtures/safe-context/mise.toml | 2 + .../bats-test-task/fixtures/clean/mise.toml | 2 +- 29 files changed, 427 insertions(+), 4 deletions(-) create mode 100755 .mise/tasks/lint/bash-empty-array-expansions create mode 100644 test/lint/bash-empty-array-expansions/bash-empty-array-expansions.bats create mode 100644 test/lint/bash-empty-array-expansions/fixtures/already-safe/.mise/tasks/t create mode 100644 test/lint/bash-empty-array-expansions/fixtures/already-safe/mise.toml create mode 100644 test/lint/bash-empty-array-expansions/fixtures/broad-walk/lib/helper.sh create mode 100644 test/lint/bash-empty-array-expansions/fixtures/broad-walk/mise.toml create mode 100644 test/lint/bash-empty-array-expansions/fixtures/clean/.mise/tasks/t create mode 100644 test/lint/bash-empty-array-expansions/fixtures/clean/mise.toml create mode 100644 test/lint/bash-empty-array-expansions/fixtures/dirty-multiple/.mise/tasks/t create mode 100644 test/lint/bash-empty-array-expansions/fixtures/dirty-multiple/mise.toml create mode 100644 test/lint/bash-empty-array-expansions/fixtures/dirty-star/.mise/tasks/t create mode 100644 test/lint/bash-empty-array-expansions/fixtures/dirty-star/mise.toml create mode 100644 test/lint/bash-empty-array-expansions/fixtures/dirty/.mise/tasks/t create mode 100644 test/lint/bash-empty-array-expansions/fixtures/dirty/mise.toml create mode 100644 test/lint/bash-empty-array-expansions/fixtures/ignored-file/.mise/tasks/t create mode 100644 test/lint/bash-empty-array-expansions/fixtures/ignored-file/mise.toml create mode 100644 test/lint/bash-empty-array-expansions/fixtures/ignored-inline/.mise/tasks/t create mode 100644 test/lint/bash-empty-array-expansions/fixtures/ignored-inline/mise.toml create mode 100644 test/lint/bash-empty-array-expansions/fixtures/no-nounset/.mise/tasks/t create mode 100644 test/lint/bash-empty-array-expansions/fixtures/no-nounset/mise.toml create mode 100644 test/lint/bash-empty-array-expansions/fixtures/no-toml/.mise/tasks/t create mode 100644 test/lint/bash-empty-array-expansions/fixtures/safe-context-local/.mise/tasks/t create mode 100644 test/lint/bash-empty-array-expansions/fixtures/safe-context-local/mise.toml create mode 100644 test/lint/bash-empty-array-expansions/fixtures/safe-context/.mise/tasks/t create mode 100644 test/lint/bash-empty-array-expansions/fixtures/safe-context/mise.toml diff --git a/.mise/tasks/lint/bash-empty-array-expansions b/.mise/tasks/lint/bash-empty-array-expansions new file mode 100755 index 0000000..ddfcd6c --- /dev/null +++ b/.mise/tasks/lint/bash-empty-array-expansions @@ -0,0 +1,168 @@ +#!/usr/bin/env bash +#MISE description="Flag empty array expansions under nounset (macOS Bash 3.2 compat)" +#USAGE arg "…" help="Paths to codebases to check (one or more)" +#USAGE example "codebase lint:bash-empty-array-expansions ." +#USAGE example "codebase lint:bash-empty-array-expansions /path/to/repo" + +set -euo pipefail + +# shellcheck source=../../../lib/shell-files.sh +source "$MISE_CONFIG_ROOT/lib/shell-files.sh" + +# Rationale: "${array[@]}" and "${array[*]}" under nounset (set -u) fail on +# macOS Bash 3.2 when the array is empty. Bash 5+ and Homebrew Bash handle +# it gracefully, creating a silent cross-platform gotcha. ShellCheck does +# not catch this. +# +# Safe form on all Bash versions: +# cmd ${arr[@]+"${arr[@]}"} +# cmd ${arr[*]+"${arr[*]}"} +# +# Contexts that are safe and NOT flagged: +# - 'for item in "${items[@]}"' — Bash handles empty $@ in for loops +# - 'local arr=("${items[@]}")' — array assignment handles empty +# - Files without nounset (set -u / set -eu / set -euo pipefail) — no risk +# - Already-safe forms using alternate-value: ${arr[@]+\"${arr[@]}\"} +# - Lines with inline 'codebase:ignore bash-empty-array-expansions' + +IFS=' ' read -ra TARGETS <<< "${usage_targets}" + +if [[ ${#TARGETS[@]} -eq 0 ]]; then + echo "ERROR: at least one target is required" >&2 + exit 1 +fi + +# Resolve relative paths against CALLER_PWD (see lib/shell-files.sh). +for i in "${!TARGETS[@]}"; do + TARGETS[i]=$(resolve_target "${TARGETS[i]}") +done + +# ── Helpers ────────────────────────────────────────────────────────────── + +# has_nounset +# Returns 0 if the file enables nounset via any form. +has_nounset() { + local file="$1" + rg -q '^[^#]*set\s+(-[a-z]*u[a-z]*|-o\s+nounset\b)' "$file" 2>/dev/null +} + +# is_safe_context +# Returns 0 if "$line" uses "${arr[@]}" in a safe context. +is_safe_context() { + local line="$1" + # for item in "${items[@]}" — loop header + [[ "$line" =~ ^[[:space:]]*for\ [a-zA-Z_][a-zA-Z0-9_]*\ in\ \"\$\{ ]] && return 0 + # local arr=("${items[@]}") — array assignment + [[ "$line" =~ ^[[:space:]]*(local|readonly|declare|typeset)[[:space:]]+[a-zA-Z_][a-zA-Z0-9_]*=\(\"\$\{ ]] && return 0 + return 1 +} + +# has_safe_form +# Returns 0 if "$line" already uses the alternate-value safe form. +has_safe_form() { + local line="$1" + # Match ${arr[@]+"${arr[@]}"} or ${arr[*]+"${arr[*]}"} + echo "$line" | grep -qE '\$\{[a-zA-Z_][a-zA-Z0-9_]*\[[@*]\]\+\"\$\{[a-zA-Z_][a-zA-Z0-9_]*\[[@*]\]\}\"\}' && return 0 + return 1 +} + +# Pattern: "${identifier[@]}" or "${identifier[*]}" in double quotes +ARRAY_EXPAND_RE='"\$\{[a-zA-Z_][a-zA-Z0-9_]*\[[@*]\]\}"' + +# has_line_ignore +# Returns 0 if the line has a rule-specific inline ignore. +has_line_ignore() { + local line="$1" + [[ "$line" =~ codebase:ignore[[:space:]]+bash-empty-array-expansions ]] +} + +# scan_file +# Emit flagged line numbers and trimmed content for lines matching the +# empty-array expansion pattern in a nounset file. +scan_file() { + local file="$1" + local lineno=0 + local line + + while IFS= read -r line || [[ -n "$line" ]]; do + lineno=$((lineno + 1)) + + # Skip full-line comments. + [[ "$line" =~ ^[[:space:]]*# ]] && continue + + # Rule-specific inline ignore with reason is accepted. + has_line_ignore "$line" && continue + + # Safe contexts (for loops, array assignments) are accepted. + is_safe_context "$line" && continue + + # Already-safe forms are accepted. + has_safe_form "$line" && continue + + # Flag matching lines. + if [[ "$line" =~ $ARRAY_EXPAND_RE ]]; then + local trimmed + trimmed="${line#"${line%%[![:space:]]*}"}" + echo "$lineno: $trimmed" + fi + done < "$file" +} + +# ── Main ────────────────────────────────────────────────────────────────── + +failures=0 + +for target in "${TARGETS[@]}"; do + if [[ ! -e "$target" ]]; then + echo "ERROR: target does not exist: $target" >&2 + exit 1 + fi + + name=$(basename "$target") + + # File-level ignore via mise.toml + toml="$target/mise.toml" + if [[ -f "$toml" ]] && grep -m1 -q 'codebase:ignore bash-empty-array-expansions' "$toml"; then + echo "SKIP $name (codebase:ignore)" + continue + fi + + # Collect shell files + files=() + while IFS= read -r f; do + [[ -n "$f" ]] && files+=("$f") + done < <(discover_shell_files "$target") + + if [[ ${#files[@]} -eq 0 ]]; then + echo "OK $name (no shell files found)" + continue + fi + + # Scan each file that has nounset enabled; collect hits + hit_count=0 + target_output="" + for file in "${files[@]}"; do + # Skip files without nounset — no risk of unbound variable + has_nounset "$file" || continue + + rel="${file#"$target"/}" + while IFS= read -r hit; do + [[ -z "$hit" ]] && continue + target_output+=" $rel:$hit"$'\n' + hit_count=$((hit_count + 1)) + done < <(scan_file "$file") + done + + if [[ "$hit_count" -gt 0 ]]; then + echo "FAIL $name: $hit_count empty-array expansions under nounset" + printf '%s' "$target_output" + cat <<'HINT' + hint: Use ${arr[@]+"${arr[@]}"} instead of "${arr[@]}" for Bash-3-safe empty-array forwarding +HINT + failures=$((failures + 1)) + else + echo "OK $name (${#files[@]} file(s) clean)" + fi +done + +exit "$failures" \ No newline at end of file diff --git a/.mise/tasks/lint/github-actions b/.mise/tasks/lint/github-actions index ae3e426..092b71d 100755 --- a/.mise/tasks/lint/github-actions +++ b/.mise/tasks/lint/github-actions @@ -222,7 +222,7 @@ for target in "${TARGETS[@]}"; do fi fi - if ! output=$(actionlint "${workflows[@]}" 2>&1); then + if ! output=$(actionlint "${workflows[@]}" 2>&1); then # codebase:ignore bash-empty-array-expansions -- guard ensures workflows non-empty count=$(printf '%s\n' "$output" | grep -cE '^[^:]+:[0-9]+:[0-9]+:' || true) # codebase:ignore or-true — grep -c exits 1 on zero matches [[ "$count" -eq 0 ]] && count=1 echo "FAIL $name: $count GitHub Actions violation(s)" diff --git a/.mise/tasks/lint/mise-settings b/.mise/tasks/lint/mise-settings index aa41c08..1c9489b 100755 --- a/.mise/tasks/lint/mise-settings +++ b/.mise/tasks/lint/mise-settings @@ -77,7 +77,7 @@ for target in "${TARGETS[@]}"; do while IFS= read -r line || [[ -n "$line" ]]; do printf '%s\n' "$line" >> "$tmp" if ! $inserted && [[ "$line" =~ ^\[settings\] ]]; then - printf '%s\n' "${missing[@]}" >> "$tmp" + printf '%s\n' "${missing[@]}" >> "$tmp" # codebase:ignore bash-empty-array-expansions -- guarded: missing is non-empty here inserted=true fi done < "$toml" diff --git a/.mise/tasks/lint/shellcheck b/.mise/tasks/lint/shellcheck index 222f0e4..ecffa32 100755 --- a/.mise/tasks/lint/shellcheck +++ b/.mise/tasks/lint/shellcheck @@ -69,7 +69,7 @@ for target in "${TARGETS[@]}"; do # behavior for .mise/tasks files while letting POSIX-sh / dash / # ksh scripts in bin/, scripts/, etc. be checked with the correct # dialect. - if ! violations=$(shellcheck --exclude="$DEFAULT_EXCLUDES" "${files[@]}" 2>&1); then + if ! violations=$(shellcheck --exclude="$DEFAULT_EXCLUDES" "${files[@]}" 2>&1); then # codebase:ignore bash-empty-array-expansions -- guard ensures files non-empty count=$(printf '%s\n' "$violations" | grep -cE '^In .* line [0-9]+:' || true) # codebase:ignore or-true — grep -c exits 1 on zero matches echo "FAIL $name: $count violation(s)" printf '%s\n' "$violations" | sed 's/^/ /' diff --git a/test/lint/bash-empty-array-expansions/bash-empty-array-expansions.bats b/test/lint/bash-empty-array-expansions/bash-empty-array-expansions.bats new file mode 100644 index 0000000..ee7c90b --- /dev/null +++ b/test/lint/bash-empty-array-expansions/bash-empty-array-expansions.bats @@ -0,0 +1,155 @@ +#!/usr/bin/env bats +# Tests for lint:bash-empty-array-expansions rule + +load ../../test_helper + +setup() { + FIXTURES="$BATS_TEST_DIRNAME/fixtures" +} + +# ============================================================================ +# Detection +# ============================================================================ + +@test "bash-empty-array-expansions: passes on a clean codebase" { + run codebase lint:bash-empty-array-expansions "$FIXTURES/clean" + [ "$status" -eq 0 ] + [[ "$output" == *"OK"* ]] +} + +@test "bash-empty-array-expansions: flags '\"${args[@]}\"' under nounset" { + run codebase lint:bash-empty-array-expansions "$FIXTURES/dirty" + [ "$status" -ne 0 ] + [[ "$output" == *"FAIL"*"dirty"* ]] + [[ "$output" == *'"${arch_args[@]}"'* ]] +} + +@test "bash-empty-array-expansions: flags '\"${args[*]}\"' under nounset" { + run codebase lint:bash-empty-array-expansions "$FIXTURES/dirty-star" + [ "$status" -ne 0 ] + [[ "$output" == *"FAIL"*"dirty-star"* ]] + [[ "$output" == *'"${all_flags[*]}"'* ]] +} + +@test "bash-empty-array-expansions: flags multiple array expansions in one file" { + run codebase lint:bash-empty-array-expansions "$FIXTURES/dirty-multiple" + [ "$status" -ne 0 ] + [[ "$output" == *"FAIL"*"dirty-multiple"* ]] + [[ "$output" == *'"${names[@]}"'* ]] + [[ "$output" == *'"${paths[@]}"'* ]] + [[ "$output" == *'"${flags[*]}"'* ]] +} + +@test "bash-empty-array-expansions: does not flag '\"${args[@]}\"' in files without nounset" { + run codebase lint:bash-empty-array-expansions "$FIXTURES/no-nounset" + [ "$status" -eq 0 ] +} + +# ============================================================================ +# Safe contexts +# ============================================================================ + +@test "bash-empty-array-expansions: does not flag 'for item in \"\${items[@]}\"'" { + run codebase lint:bash-empty-array-expansions "$FIXTURES/safe-context" + [ "$status" -eq 0 ] +} + +@test "bash-empty-array-expansions: does not flag 'local arr=(\"\${items[@]}\")'" { + run codebase lint:bash-empty-array-expansions "$FIXTURES/safe-context-local" + [ "$status" -eq 0 ] +} + +@test "bash-empty-array-expansions: does not flag already-safe '\${arr[@]+\"\${arr[@]}\"}" { + run codebase lint:bash-empty-array-expansions "$FIXTURES/already-safe" + [ "$status" -eq 0 ] +} + +# ============================================================================ +# Ignore mechanisms +# ============================================================================ + +@test "bash-empty-array-expansions: respects inline ignore" { + run codebase lint:bash-empty-array-expansions "$FIXTURES/ignored-inline" + [ "$status" -eq 0 ] +} + +@test "bash-empty-array-expansions: respects file-level ignore via mise.toml" { + run codebase lint:bash-empty-array-expansions "$FIXTURES/ignored-file" + [ "$status" -eq 0 ] + [[ "$output" == *"SKIP"* ]] +} + +# ============================================================================ +# Edge cases +# ============================================================================ + +@test "bash-empty-array-expansions: handles missing mise.toml gracefully" { + run codebase lint:bash-empty-array-expansions "$FIXTURES/no-toml" + [ "$status" -eq 0 ] + [[ "$output" == *"OK"*"no-toml"* ]] +} + +@test "bash-empty-array-expansions: finds hits outside .mise/tasks in broad walk" { + run codebase lint:bash-empty-array-expansions "$FIXTURES/broad-walk" + [ "$status" -ne 0 ] + [[ "$output" == *"lib/helper.sh"* ]] +} + +# ============================================================================ +# Dynamic test: various variable names +# ============================================================================ + +@test "bash-empty-array-expansions: flags different variable names" { + local tmp + tmp=$(mktemp -d) + mkdir -p "$tmp/.mise/tasks" + cat > "$tmp/.mise/tasks/t" <<'SCRIPT' +#!/usr/bin/env bash +set -euo pipefail +echo "${names[@]}" +echo "${paths[@]}" +echo "${flags[*]}" +SCRIPT + + run codebase lint:bash-empty-array-expansions "$tmp" + [ "$status" -ne 0 ] + [[ "$output" == *"names"* ]] + [[ "$output" == *"paths"* ]] + [[ "$output" == *"flags"* ]] + rm -rf "$tmp" +} + +@test "bash-empty-array-expansions: flags in redirect context" { + local tmp + tmp=$(mktemp -d) + mkdir -p "$tmp/.mise/tasks" + cat > "$tmp/.mise/tasks/t" <<'SCRIPT' +#!/usr/bin/env bash +set -euo pipefail +some_command "${args[@]}" > /tmp/output +SCRIPT + + run codebase lint:bash-empty-array-expansions "$tmp" + [ "$status" -ne 0 ] + [[ "$output" == *'"${args[@]}"'* ]] + rm -rf "$tmp" +} + +@test "bash-empty-array-expansions: does not flag local array assignment then for loop (combined safe)" { + local tmp + tmp=$(mktemp -d) + mkdir -p "$tmp/.mise/tasks" + cat > "$tmp/.mise/tasks/t" <<'SCRIPT' +#!/usr/bin/env bash +set -euo pipefail +local args=("$@") +for arg in "${args[@]}"; do + echo "$arg" +done +SCRIPT + + run codebase lint:bash-empty-array-expansions "$tmp" + [ "$status" -eq 0 ] + [[ "$output" == *"OK"* ]] + rm -rf "$tmp" +} \ No newline at end of file diff --git a/test/lint/bash-empty-array-expansions/fixtures/already-safe/.mise/tasks/t b/test/lint/bash-empty-array-expansions/fixtures/already-safe/.mise/tasks/t new file mode 100644 index 0000000..bbbbe8a --- /dev/null +++ b/test/lint/bash-empty-array-expansions/fixtures/already-safe/.mise/tasks/t @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +set -euo pipefail + +args=() +cmd ${args[@]+"${args[@]}"} +cmd ${args[*]+"${args[*]}"} +echo done \ No newline at end of file diff --git a/test/lint/bash-empty-array-expansions/fixtures/already-safe/mise.toml b/test/lint/bash-empty-array-expansions/fixtures/already-safe/mise.toml new file mode 100644 index 0000000..1c87eee --- /dev/null +++ b/test/lint/bash-empty-array-expansions/fixtures/already-safe/mise.toml @@ -0,0 +1,2 @@ +[tools] +bats = "1.13.0" \ No newline at end of file diff --git a/test/lint/bash-empty-array-expansions/fixtures/broad-walk/lib/helper.sh b/test/lint/bash-empty-array-expansions/fixtures/broad-walk/lib/helper.sh new file mode 100644 index 0000000..559c21e --- /dev/null +++ b/test/lint/bash-empty-array-expansions/fixtures/broad-walk/lib/helper.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash +set -euo pipefail + +# This is outside .mise/tasks — should still be found in broad walk +args=() +cmd "${args[@]}" \ No newline at end of file diff --git a/test/lint/bash-empty-array-expansions/fixtures/broad-walk/mise.toml b/test/lint/bash-empty-array-expansions/fixtures/broad-walk/mise.toml new file mode 100644 index 0000000..1c87eee --- /dev/null +++ b/test/lint/bash-empty-array-expansions/fixtures/broad-walk/mise.toml @@ -0,0 +1,2 @@ +[tools] +bats = "1.13.0" \ No newline at end of file diff --git a/test/lint/bash-empty-array-expansions/fixtures/clean/.mise/tasks/t b/test/lint/bash-empty-array-expansions/fixtures/clean/.mise/tasks/t new file mode 100644 index 0000000..54f633b --- /dev/null +++ b/test/lint/bash-empty-array-expansions/fixtures/clean/.mise/tasks/t @@ -0,0 +1,4 @@ +#!/usr/bin/env bash +no_nounset_here=true +cmd "${args[@]}" +echo done \ No newline at end of file diff --git a/test/lint/bash-empty-array-expansions/fixtures/clean/mise.toml b/test/lint/bash-empty-array-expansions/fixtures/clean/mise.toml new file mode 100644 index 0000000..a73b57f --- /dev/null +++ b/test/lint/bash-empty-array-expansions/fixtures/clean/mise.toml @@ -0,0 +1,3 @@ +# Clean codebase — no shell files with nounset + array expansions +[tools] +bats = "1.13.0" \ No newline at end of file diff --git a/test/lint/bash-empty-array-expansions/fixtures/dirty-multiple/.mise/tasks/t b/test/lint/bash-empty-array-expansions/fixtures/dirty-multiple/.mise/tasks/t new file mode 100644 index 0000000..c078ae0 --- /dev/null +++ b/test/lint/bash-empty-array-expansions/fixtures/dirty-multiple/.mise/tasks/t @@ -0,0 +1,10 @@ +#!/usr/bin/env bash +set -euo pipefail + +names=() +paths=() +flags=() + +echo "${names[@]}" +echo "${paths[@]}" +echo "${flags[*]}" \ No newline at end of file diff --git a/test/lint/bash-empty-array-expansions/fixtures/dirty-multiple/mise.toml b/test/lint/bash-empty-array-expansions/fixtures/dirty-multiple/mise.toml new file mode 100644 index 0000000..1c87eee --- /dev/null +++ b/test/lint/bash-empty-array-expansions/fixtures/dirty-multiple/mise.toml @@ -0,0 +1,2 @@ +[tools] +bats = "1.13.0" \ No newline at end of file diff --git a/test/lint/bash-empty-array-expansions/fixtures/dirty-star/.mise/tasks/t b/test/lint/bash-empty-array-expansions/fixtures/dirty-star/.mise/tasks/t new file mode 100644 index 0000000..a9907e8 --- /dev/null +++ b/test/lint/bash-empty-array-expansions/fixtures/dirty-star/.mise/tasks/t @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +set -euo pipefail + +all_flags=() +[[ -n "${FLAGS:-}" ]] && all_flags=(--flag "$FLAGS") + +printf '%s\n' "${all_flags[*]}" +echo done \ No newline at end of file diff --git a/test/lint/bash-empty-array-expansions/fixtures/dirty-star/mise.toml b/test/lint/bash-empty-array-expansions/fixtures/dirty-star/mise.toml new file mode 100644 index 0000000..1c87eee --- /dev/null +++ b/test/lint/bash-empty-array-expansions/fixtures/dirty-star/mise.toml @@ -0,0 +1,2 @@ +[tools] +bats = "1.13.0" \ No newline at end of file diff --git a/test/lint/bash-empty-array-expansions/fixtures/dirty/.mise/tasks/t b/test/lint/bash-empty-array-expansions/fixtures/dirty/.mise/tasks/t new file mode 100644 index 0000000..c0ce9a2 --- /dev/null +++ b/test/lint/bash-empty-array-expansions/fixtures/dirty/.mise/tasks/t @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +set -euo pipefail + +arch_args=() +[[ -n "${ARCH_FLAG:-}" ]] && arch_args=(--arch "$ARCH_FLAG") + +mise run catalog -- "${arch_args[@]}" +echo done \ No newline at end of file diff --git a/test/lint/bash-empty-array-expansions/fixtures/dirty/mise.toml b/test/lint/bash-empty-array-expansions/fixtures/dirty/mise.toml new file mode 100644 index 0000000..1c87eee --- /dev/null +++ b/test/lint/bash-empty-array-expansions/fixtures/dirty/mise.toml @@ -0,0 +1,2 @@ +[tools] +bats = "1.13.0" \ No newline at end of file diff --git a/test/lint/bash-empty-array-expansions/fixtures/ignored-file/.mise/tasks/t b/test/lint/bash-empty-array-expansions/fixtures/ignored-file/.mise/tasks/t new file mode 100644 index 0000000..642c952 --- /dev/null +++ b/test/lint/bash-empty-array-expansions/fixtures/ignored-file/.mise/tasks/t @@ -0,0 +1,6 @@ +#!/usr/bin/env bash +set -euo pipefail + +args=() +echo "${args[@]}" +echo "${args[*]}" \ No newline at end of file diff --git a/test/lint/bash-empty-array-expansions/fixtures/ignored-file/mise.toml b/test/lint/bash-empty-array-expansions/fixtures/ignored-file/mise.toml new file mode 100644 index 0000000..89e6b9c --- /dev/null +++ b/test/lint/bash-empty-array-expansions/fixtures/ignored-file/mise.toml @@ -0,0 +1 @@ +codebase:ignore bash-empty-array-expansions \ No newline at end of file diff --git a/test/lint/bash-empty-array-expansions/fixtures/ignored-inline/.mise/tasks/t b/test/lint/bash-empty-array-expansions/fixtures/ignored-inline/.mise/tasks/t new file mode 100644 index 0000000..74cfad0 --- /dev/null +++ b/test/lint/bash-empty-array-expansions/fixtures/ignored-inline/.mise/tasks/t @@ -0,0 +1,6 @@ +#!/usr/bin/env bash +set -euo pipefail + +args=() +cmd "${args[@]}" # codebase:ignore bash-empty-array-expansions -- test fixture +echo done \ No newline at end of file diff --git a/test/lint/bash-empty-array-expansions/fixtures/ignored-inline/mise.toml b/test/lint/bash-empty-array-expansions/fixtures/ignored-inline/mise.toml new file mode 100644 index 0000000..1c87eee --- /dev/null +++ b/test/lint/bash-empty-array-expansions/fixtures/ignored-inline/mise.toml @@ -0,0 +1,2 @@ +[tools] +bats = "1.13.0" \ No newline at end of file diff --git a/test/lint/bash-empty-array-expansions/fixtures/no-nounset/.mise/tasks/t b/test/lint/bash-empty-array-expansions/fixtures/no-nounset/.mise/tasks/t new file mode 100644 index 0000000..b3e9007 --- /dev/null +++ b/test/lint/bash-empty-array-expansions/fixtures/no-nounset/.mise/tasks/t @@ -0,0 +1,4 @@ +#!/usr/bin/env bash +# No set -u — should NOT be flagged +cmd "${args[@]}" +echo "${items[*]}" \ No newline at end of file diff --git a/test/lint/bash-empty-array-expansions/fixtures/no-nounset/mise.toml b/test/lint/bash-empty-array-expansions/fixtures/no-nounset/mise.toml new file mode 100644 index 0000000..1c87eee --- /dev/null +++ b/test/lint/bash-empty-array-expansions/fixtures/no-nounset/mise.toml @@ -0,0 +1,2 @@ +[tools] +bats = "1.13.0" \ No newline at end of file diff --git a/test/lint/bash-empty-array-expansions/fixtures/no-toml/.mise/tasks/t b/test/lint/bash-empty-array-expansions/fixtures/no-toml/.mise/tasks/t new file mode 100644 index 0000000..852405e --- /dev/null +++ b/test/lint/bash-empty-array-expansions/fixtures/no-toml/.mise/tasks/t @@ -0,0 +1,4 @@ +#!/usr/bin/env bash +# No mise.toml — should still scan gracefully +# No nounset, so no risk of unbound variable +echo "hello" \ No newline at end of file diff --git a/test/lint/bash-empty-array-expansions/fixtures/safe-context-local/.mise/tasks/t b/test/lint/bash-empty-array-expansions/fixtures/safe-context-local/.mise/tasks/t new file mode 100644 index 0000000..a39a476 --- /dev/null +++ b/test/lint/bash-empty-array-expansions/fixtures/safe-context-local/.mise/tasks/t @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +set -euo pipefail + +items=("a" "b" "c") +local copy=("${items[@]}") +for item in "${copy[@]}"; do + echo "$item" +done \ No newline at end of file diff --git a/test/lint/bash-empty-array-expansions/fixtures/safe-context-local/mise.toml b/test/lint/bash-empty-array-expansions/fixtures/safe-context-local/mise.toml new file mode 100644 index 0000000..1c87eee --- /dev/null +++ b/test/lint/bash-empty-array-expansions/fixtures/safe-context-local/mise.toml @@ -0,0 +1,2 @@ +[tools] +bats = "1.13.0" \ No newline at end of file diff --git a/test/lint/bash-empty-array-expansions/fixtures/safe-context/.mise/tasks/t b/test/lint/bash-empty-array-expansions/fixtures/safe-context/.mise/tasks/t new file mode 100644 index 0000000..222668b --- /dev/null +++ b/test/lint/bash-empty-array-expansions/fixtures/safe-context/.mise/tasks/t @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +set -euo pipefail + +items=("a" "b" "c") +for item in "${items[@]}"; do + echo "$item" +done \ No newline at end of file diff --git a/test/lint/bash-empty-array-expansions/fixtures/safe-context/mise.toml b/test/lint/bash-empty-array-expansions/fixtures/safe-context/mise.toml new file mode 100644 index 0000000..1c87eee --- /dev/null +++ b/test/lint/bash-empty-array-expansions/fixtures/safe-context/mise.toml @@ -0,0 +1,2 @@ +[tools] +bats = "1.13.0" \ No newline at end of file diff --git a/test/lint/bats-test-task/fixtures/clean/mise.toml b/test/lint/bats-test-task/fixtures/clean/mise.toml index 9487f20..89e6b9c 100644 --- a/test/lint/bats-test-task/fixtures/clean/mise.toml +++ b/test/lint/bats-test-task/fixtures/clean/mise.toml @@ -1 +1 @@ -[tools] +codebase:ignore bash-empty-array-expansions \ No newline at end of file From bb48714edb7b9d6b721e850f5bcf1db4d674a0a1 Mon Sep 17 00:00:00 2001 From: olavostauros Date: Wed, 24 Jun 2026 10:31:18 -0300 Subject: [PATCH 2/3] style: collapse 3-line section comment blocks to single line --- .mise/tasks/lint/bash-empty-array-expansions | 2 +- test/lib/shell-files.bats | 2 -- .../bash-empty-array-expansions.bats | 12 +----------- .../fixtures/already-safe/.mise/tasks/t | 2 +- .../fixtures/already-safe/mise.toml | 2 +- .../fixtures/broad-walk/lib/helper.sh | 2 +- .../fixtures/broad-walk/mise.toml | 2 +- .../fixtures/clean/.mise/tasks/t | 2 +- .../fixtures/clean/mise.toml | 2 +- .../fixtures/dirty-multiple/.mise/tasks/t | 2 +- .../fixtures/dirty-multiple/mise.toml | 2 +- .../fixtures/dirty-star/.mise/tasks/t | 2 +- .../fixtures/dirty-star/mise.toml | 2 +- .../fixtures/dirty/.mise/tasks/t | 2 +- .../fixtures/dirty/mise.toml | 2 +- .../fixtures/ignored-file/.mise/tasks/t | 2 +- .../fixtures/ignored-file/mise.toml | 2 +- .../fixtures/ignored-inline/.mise/tasks/t | 2 +- .../fixtures/ignored-inline/mise.toml | 2 +- .../fixtures/no-nounset/.mise/tasks/t | 2 +- .../fixtures/no-nounset/mise.toml | 2 +- .../fixtures/no-toml/.mise/tasks/t | 2 +- .../fixtures/safe-context-local/.mise/tasks/t | 2 +- .../fixtures/safe-context-local/mise.toml | 2 +- .../fixtures/safe-context/.mise/tasks/t | 2 +- .../fixtures/safe-context/mise.toml | 2 +- .../bats-test-helper/bats-test-helper.bats | 14 -------------- test/lint/bats-test-task/bats-test-task.bats | 12 ------------ test/lint/gum-table/gum-table.bats | 16 ---------------- test/lint/mcr-scope/mcr-scope.bats | 10 ---------- test/lint/mise-settings/mise-settings.bats | 6 ------ test/lint/or-true/or-true.bats | 18 ------------------ test/lint/shellcheck/shellcheck.bats | 10 ---------- test/migrations/task-pattern/task-pattern.bats | 10 ---------- test/pre-commit/pre-commit.bats | 16 ---------------- test/scan/scan.bats | 10 ---------- 36 files changed, 25 insertions(+), 159 deletions(-) mode change 100755 => 100644 .mise/tasks/lint/bash-empty-array-expansions diff --git a/.mise/tasks/lint/bash-empty-array-expansions b/.mise/tasks/lint/bash-empty-array-expansions old mode 100755 new mode 100644 index ddfcd6c..df1c19f --- a/.mise/tasks/lint/bash-empty-array-expansions +++ b/.mise/tasks/lint/bash-empty-array-expansions @@ -165,4 +165,4 @@ HINT fi done -exit "$failures" \ No newline at end of file +exit "$failures" diff --git a/test/lib/shell-files.bats b/test/lib/shell-files.bats index a462482..5d6ed6e 100644 --- a/test/lib/shell-files.bats +++ b/test/lib/shell-files.bats @@ -7,9 +7,7 @@ setup() { source "$REPO_DIR/lib/shell-files.sh" } -# ============================================================================ # resolve_target -# ============================================================================ @test "resolve_target: absolute path passes through unchanged" { result=$(resolve_target "/some/absolute/path") diff --git a/test/lint/bash-empty-array-expansions/bash-empty-array-expansions.bats b/test/lint/bash-empty-array-expansions/bash-empty-array-expansions.bats index ee7c90b..53178cb 100644 --- a/test/lint/bash-empty-array-expansions/bash-empty-array-expansions.bats +++ b/test/lint/bash-empty-array-expansions/bash-empty-array-expansions.bats @@ -7,9 +7,7 @@ setup() { FIXTURES="$BATS_TEST_DIRNAME/fixtures" } -# ============================================================================ # Detection -# ============================================================================ @test "bash-empty-array-expansions: passes on a clean codebase" { run codebase lint:bash-empty-array-expansions "$FIXTURES/clean" @@ -45,9 +43,7 @@ setup() { [ "$status" -eq 0 ] } -# ============================================================================ # Safe contexts -# ============================================================================ @test "bash-empty-array-expansions: does not flag 'for item in \"\${items[@]}\"'" { run codebase lint:bash-empty-array-expansions "$FIXTURES/safe-context" @@ -64,9 +60,7 @@ setup() { [ "$status" -eq 0 ] } -# ============================================================================ # Ignore mechanisms -# ============================================================================ @test "bash-empty-array-expansions: respects inline ignore" { run codebase lint:bash-empty-array-expansions "$FIXTURES/ignored-inline" @@ -79,9 +73,7 @@ setup() { [[ "$output" == *"SKIP"* ]] } -# ============================================================================ # Edge cases -# ============================================================================ @test "bash-empty-array-expansions: handles missing mise.toml gracefully" { run codebase lint:bash-empty-array-expansions "$FIXTURES/no-toml" @@ -95,9 +87,7 @@ setup() { [[ "$output" == *"lib/helper.sh"* ]] } -# ============================================================================ # Dynamic test: various variable names -# ============================================================================ @test "bash-empty-array-expansions: flags different variable names" { local tmp @@ -152,4 +142,4 @@ SCRIPT [ "$status" -eq 0 ] [[ "$output" == *"OK"* ]] rm -rf "$tmp" -} \ No newline at end of file +} diff --git a/test/lint/bash-empty-array-expansions/fixtures/already-safe/.mise/tasks/t b/test/lint/bash-empty-array-expansions/fixtures/already-safe/.mise/tasks/t index bbbbe8a..91fc753 100644 --- a/test/lint/bash-empty-array-expansions/fixtures/already-safe/.mise/tasks/t +++ b/test/lint/bash-empty-array-expansions/fixtures/already-safe/.mise/tasks/t @@ -4,4 +4,4 @@ set -euo pipefail args=() cmd ${args[@]+"${args[@]}"} cmd ${args[*]+"${args[*]}"} -echo done \ No newline at end of file +echo done diff --git a/test/lint/bash-empty-array-expansions/fixtures/already-safe/mise.toml b/test/lint/bash-empty-array-expansions/fixtures/already-safe/mise.toml index 1c87eee..26bb173 100644 --- a/test/lint/bash-empty-array-expansions/fixtures/already-safe/mise.toml +++ b/test/lint/bash-empty-array-expansions/fixtures/already-safe/mise.toml @@ -1,2 +1,2 @@ [tools] -bats = "1.13.0" \ No newline at end of file +bats = "1.13.0" diff --git a/test/lint/bash-empty-array-expansions/fixtures/broad-walk/lib/helper.sh b/test/lint/bash-empty-array-expansions/fixtures/broad-walk/lib/helper.sh index 559c21e..bc77ba0 100644 --- a/test/lint/bash-empty-array-expansions/fixtures/broad-walk/lib/helper.sh +++ b/test/lint/bash-empty-array-expansions/fixtures/broad-walk/lib/helper.sh @@ -3,4 +3,4 @@ set -euo pipefail # This is outside .mise/tasks — should still be found in broad walk args=() -cmd "${args[@]}" \ No newline at end of file +cmd "${args[@]}" diff --git a/test/lint/bash-empty-array-expansions/fixtures/broad-walk/mise.toml b/test/lint/bash-empty-array-expansions/fixtures/broad-walk/mise.toml index 1c87eee..26bb173 100644 --- a/test/lint/bash-empty-array-expansions/fixtures/broad-walk/mise.toml +++ b/test/lint/bash-empty-array-expansions/fixtures/broad-walk/mise.toml @@ -1,2 +1,2 @@ [tools] -bats = "1.13.0" \ No newline at end of file +bats = "1.13.0" diff --git a/test/lint/bash-empty-array-expansions/fixtures/clean/.mise/tasks/t b/test/lint/bash-empty-array-expansions/fixtures/clean/.mise/tasks/t index 54f633b..66be494 100644 --- a/test/lint/bash-empty-array-expansions/fixtures/clean/.mise/tasks/t +++ b/test/lint/bash-empty-array-expansions/fixtures/clean/.mise/tasks/t @@ -1,4 +1,4 @@ #!/usr/bin/env bash no_nounset_here=true cmd "${args[@]}" -echo done \ No newline at end of file +echo done diff --git a/test/lint/bash-empty-array-expansions/fixtures/clean/mise.toml b/test/lint/bash-empty-array-expansions/fixtures/clean/mise.toml index a73b57f..7692013 100644 --- a/test/lint/bash-empty-array-expansions/fixtures/clean/mise.toml +++ b/test/lint/bash-empty-array-expansions/fixtures/clean/mise.toml @@ -1,3 +1,3 @@ # Clean codebase — no shell files with nounset + array expansions [tools] -bats = "1.13.0" \ No newline at end of file +bats = "1.13.0" diff --git a/test/lint/bash-empty-array-expansions/fixtures/dirty-multiple/.mise/tasks/t b/test/lint/bash-empty-array-expansions/fixtures/dirty-multiple/.mise/tasks/t index c078ae0..cc2164a 100644 --- a/test/lint/bash-empty-array-expansions/fixtures/dirty-multiple/.mise/tasks/t +++ b/test/lint/bash-empty-array-expansions/fixtures/dirty-multiple/.mise/tasks/t @@ -7,4 +7,4 @@ flags=() echo "${names[@]}" echo "${paths[@]}" -echo "${flags[*]}" \ No newline at end of file +echo "${flags[*]}" diff --git a/test/lint/bash-empty-array-expansions/fixtures/dirty-multiple/mise.toml b/test/lint/bash-empty-array-expansions/fixtures/dirty-multiple/mise.toml index 1c87eee..26bb173 100644 --- a/test/lint/bash-empty-array-expansions/fixtures/dirty-multiple/mise.toml +++ b/test/lint/bash-empty-array-expansions/fixtures/dirty-multiple/mise.toml @@ -1,2 +1,2 @@ [tools] -bats = "1.13.0" \ No newline at end of file +bats = "1.13.0" diff --git a/test/lint/bash-empty-array-expansions/fixtures/dirty-star/.mise/tasks/t b/test/lint/bash-empty-array-expansions/fixtures/dirty-star/.mise/tasks/t index a9907e8..ca0c332 100644 --- a/test/lint/bash-empty-array-expansions/fixtures/dirty-star/.mise/tasks/t +++ b/test/lint/bash-empty-array-expansions/fixtures/dirty-star/.mise/tasks/t @@ -5,4 +5,4 @@ all_flags=() [[ -n "${FLAGS:-}" ]] && all_flags=(--flag "$FLAGS") printf '%s\n' "${all_flags[*]}" -echo done \ No newline at end of file +echo done diff --git a/test/lint/bash-empty-array-expansions/fixtures/dirty-star/mise.toml b/test/lint/bash-empty-array-expansions/fixtures/dirty-star/mise.toml index 1c87eee..26bb173 100644 --- a/test/lint/bash-empty-array-expansions/fixtures/dirty-star/mise.toml +++ b/test/lint/bash-empty-array-expansions/fixtures/dirty-star/mise.toml @@ -1,2 +1,2 @@ [tools] -bats = "1.13.0" \ No newline at end of file +bats = "1.13.0" diff --git a/test/lint/bash-empty-array-expansions/fixtures/dirty/.mise/tasks/t b/test/lint/bash-empty-array-expansions/fixtures/dirty/.mise/tasks/t index c0ce9a2..9cb3175 100644 --- a/test/lint/bash-empty-array-expansions/fixtures/dirty/.mise/tasks/t +++ b/test/lint/bash-empty-array-expansions/fixtures/dirty/.mise/tasks/t @@ -5,4 +5,4 @@ arch_args=() [[ -n "${ARCH_FLAG:-}" ]] && arch_args=(--arch "$ARCH_FLAG") mise run catalog -- "${arch_args[@]}" -echo done \ No newline at end of file +echo done diff --git a/test/lint/bash-empty-array-expansions/fixtures/dirty/mise.toml b/test/lint/bash-empty-array-expansions/fixtures/dirty/mise.toml index 1c87eee..26bb173 100644 --- a/test/lint/bash-empty-array-expansions/fixtures/dirty/mise.toml +++ b/test/lint/bash-empty-array-expansions/fixtures/dirty/mise.toml @@ -1,2 +1,2 @@ [tools] -bats = "1.13.0" \ No newline at end of file +bats = "1.13.0" diff --git a/test/lint/bash-empty-array-expansions/fixtures/ignored-file/.mise/tasks/t b/test/lint/bash-empty-array-expansions/fixtures/ignored-file/.mise/tasks/t index 642c952..e5a6d8f 100644 --- a/test/lint/bash-empty-array-expansions/fixtures/ignored-file/.mise/tasks/t +++ b/test/lint/bash-empty-array-expansions/fixtures/ignored-file/.mise/tasks/t @@ -3,4 +3,4 @@ set -euo pipefail args=() echo "${args[@]}" -echo "${args[*]}" \ No newline at end of file +echo "${args[*]}" diff --git a/test/lint/bash-empty-array-expansions/fixtures/ignored-file/mise.toml b/test/lint/bash-empty-array-expansions/fixtures/ignored-file/mise.toml index 89e6b9c..0532e0e 100644 --- a/test/lint/bash-empty-array-expansions/fixtures/ignored-file/mise.toml +++ b/test/lint/bash-empty-array-expansions/fixtures/ignored-file/mise.toml @@ -1 +1 @@ -codebase:ignore bash-empty-array-expansions \ No newline at end of file +codebase:ignore bash-empty-array-expansions diff --git a/test/lint/bash-empty-array-expansions/fixtures/ignored-inline/.mise/tasks/t b/test/lint/bash-empty-array-expansions/fixtures/ignored-inline/.mise/tasks/t index 74cfad0..050e4ae 100644 --- a/test/lint/bash-empty-array-expansions/fixtures/ignored-inline/.mise/tasks/t +++ b/test/lint/bash-empty-array-expansions/fixtures/ignored-inline/.mise/tasks/t @@ -3,4 +3,4 @@ set -euo pipefail args=() cmd "${args[@]}" # codebase:ignore bash-empty-array-expansions -- test fixture -echo done \ No newline at end of file +echo done diff --git a/test/lint/bash-empty-array-expansions/fixtures/ignored-inline/mise.toml b/test/lint/bash-empty-array-expansions/fixtures/ignored-inline/mise.toml index 1c87eee..26bb173 100644 --- a/test/lint/bash-empty-array-expansions/fixtures/ignored-inline/mise.toml +++ b/test/lint/bash-empty-array-expansions/fixtures/ignored-inline/mise.toml @@ -1,2 +1,2 @@ [tools] -bats = "1.13.0" \ No newline at end of file +bats = "1.13.0" diff --git a/test/lint/bash-empty-array-expansions/fixtures/no-nounset/.mise/tasks/t b/test/lint/bash-empty-array-expansions/fixtures/no-nounset/.mise/tasks/t index b3e9007..4b595d1 100644 --- a/test/lint/bash-empty-array-expansions/fixtures/no-nounset/.mise/tasks/t +++ b/test/lint/bash-empty-array-expansions/fixtures/no-nounset/.mise/tasks/t @@ -1,4 +1,4 @@ #!/usr/bin/env bash # No set -u — should NOT be flagged cmd "${args[@]}" -echo "${items[*]}" \ No newline at end of file +echo "${items[*]}" diff --git a/test/lint/bash-empty-array-expansions/fixtures/no-nounset/mise.toml b/test/lint/bash-empty-array-expansions/fixtures/no-nounset/mise.toml index 1c87eee..26bb173 100644 --- a/test/lint/bash-empty-array-expansions/fixtures/no-nounset/mise.toml +++ b/test/lint/bash-empty-array-expansions/fixtures/no-nounset/mise.toml @@ -1,2 +1,2 @@ [tools] -bats = "1.13.0" \ No newline at end of file +bats = "1.13.0" diff --git a/test/lint/bash-empty-array-expansions/fixtures/no-toml/.mise/tasks/t b/test/lint/bash-empty-array-expansions/fixtures/no-toml/.mise/tasks/t index 852405e..9028505 100644 --- a/test/lint/bash-empty-array-expansions/fixtures/no-toml/.mise/tasks/t +++ b/test/lint/bash-empty-array-expansions/fixtures/no-toml/.mise/tasks/t @@ -1,4 +1,4 @@ #!/usr/bin/env bash # No mise.toml — should still scan gracefully # No nounset, so no risk of unbound variable -echo "hello" \ No newline at end of file +echo "hello" diff --git a/test/lint/bash-empty-array-expansions/fixtures/safe-context-local/.mise/tasks/t b/test/lint/bash-empty-array-expansions/fixtures/safe-context-local/.mise/tasks/t index a39a476..7ad8cc4 100644 --- a/test/lint/bash-empty-array-expansions/fixtures/safe-context-local/.mise/tasks/t +++ b/test/lint/bash-empty-array-expansions/fixtures/safe-context-local/.mise/tasks/t @@ -5,4 +5,4 @@ items=("a" "b" "c") local copy=("${items[@]}") for item in "${copy[@]}"; do echo "$item" -done \ No newline at end of file +done diff --git a/test/lint/bash-empty-array-expansions/fixtures/safe-context-local/mise.toml b/test/lint/bash-empty-array-expansions/fixtures/safe-context-local/mise.toml index 1c87eee..26bb173 100644 --- a/test/lint/bash-empty-array-expansions/fixtures/safe-context-local/mise.toml +++ b/test/lint/bash-empty-array-expansions/fixtures/safe-context-local/mise.toml @@ -1,2 +1,2 @@ [tools] -bats = "1.13.0" \ No newline at end of file +bats = "1.13.0" diff --git a/test/lint/bash-empty-array-expansions/fixtures/safe-context/.mise/tasks/t b/test/lint/bash-empty-array-expansions/fixtures/safe-context/.mise/tasks/t index 222668b..d72d477 100644 --- a/test/lint/bash-empty-array-expansions/fixtures/safe-context/.mise/tasks/t +++ b/test/lint/bash-empty-array-expansions/fixtures/safe-context/.mise/tasks/t @@ -4,4 +4,4 @@ set -euo pipefail items=("a" "b" "c") for item in "${items[@]}"; do echo "$item" -done \ No newline at end of file +done diff --git a/test/lint/bash-empty-array-expansions/fixtures/safe-context/mise.toml b/test/lint/bash-empty-array-expansions/fixtures/safe-context/mise.toml index 1c87eee..26bb173 100644 --- a/test/lint/bash-empty-array-expansions/fixtures/safe-context/mise.toml +++ b/test/lint/bash-empty-array-expansions/fixtures/safe-context/mise.toml @@ -1,2 +1,2 @@ [tools] -bats = "1.13.0" \ No newline at end of file +bats = "1.13.0" diff --git a/test/lint/bats-test-helper/bats-test-helper.bats b/test/lint/bats-test-helper/bats-test-helper.bats index 27ebba4..f4b0e76 100644 --- a/test/lint/bats-test-helper/bats-test-helper.bats +++ b/test/lint/bats-test-helper/bats-test-helper.bats @@ -7,9 +7,7 @@ setup() { FIXTURES="$BATS_TEST_DIRNAME/fixtures" } -# ============================================================================ # Pass paths -# ============================================================================ @test "bats-test-helper: passes on clean wrapper-based tests" { run codebase lint:bats-test-helper "$FIXTURES/clean" @@ -24,9 +22,7 @@ setup() { [[ "$output" == *"no test/ files found"* ]] } -# ============================================================================ # Invocation signatures — each form fails -# ============================================================================ @test "bats-test-helper: flags 'bash \$TASK' (var whose name contains TASK)" { run codebase lint:bats-test-helper "$FIXTURES/dirty-task-var" @@ -84,9 +80,7 @@ setup() { [[ "$output" == *"bash \$REPO_DIR/.mise/tasks/lint/check"* ]] } -# ============================================================================ # False positives — none of these are actual invocations -# ============================================================================ @test "bats-test-helper: does NOT flag reading a task file as data (grep/cat)" { # Regression guard: 'grep "$MCR/.mise/tasks/foo"' reads the script, doesn't @@ -97,9 +91,7 @@ setup() { [[ "$output" == *"OK"* ]] } -# ============================================================================ # Ignore directives -# ============================================================================ @test "bats-test-helper: inline '# codebase:ignore' suppresses a single line" { run codebase lint:bats-test-helper "$FIXTURES/ignored-inline" @@ -113,9 +105,7 @@ setup() { [[ "$output" == *"SKIP"*"ignored-file"* ]] } -# ============================================================================ # Output details -# ============================================================================ @test "bats-test-helper: fail output includes file:line citations" { run codebase lint:bats-test-helper "$FIXTURES/dirty-task-var" @@ -130,9 +120,7 @@ setup() { [[ "$output" == *"Call the Tool"* ]] } -# ============================================================================ # Error handling -# ============================================================================ @test "bats-test-helper: fails when no targets given" { run codebase lint:bats-test-helper @@ -146,9 +134,7 @@ setup() { [[ "$output" == *"does not exist"* ]] } -# ============================================================================ # Multi-target -# ============================================================================ @test "bats-test-helper: accepts multiple targets and reports each" { run codebase lint:bats-test-helper "$FIXTURES/clean" "$FIXTURES/dirty-task-var" diff --git a/test/lint/bats-test-task/bats-test-task.bats b/test/lint/bats-test-task/bats-test-task.bats index 295d92f..f761f2b 100644 --- a/test/lint/bats-test-task/bats-test-task.bats +++ b/test/lint/bats-test-task/bats-test-task.bats @@ -7,9 +7,7 @@ setup() { FIXTURES="$BATS_TEST_DIRNAME/fixtures" } -# ============================================================================ # Pass paths -# ============================================================================ @test "bats-test-task: passes on the canonical pattern" { run codebase lint:bats-test-task "$FIXTURES/clean" @@ -33,9 +31,7 @@ setup() { [[ "$output" == *"OK"*"shimmer-variant"* ]] } -# ============================================================================ # Failure modes -# ============================================================================ @test "bats-test-task: flags missing USAGE arg spec" { run codebase lint:bats-test-task "$FIXTURES/missing-usage-arg" @@ -80,9 +76,7 @@ setup() { [[ "$output" != *"invocations found"* ]] } -# ============================================================================ # Ignore directive -# ============================================================================ @test "bats-test-task: 'codebase:ignore bats-test-task' in mise.toml skips the target" { run codebase lint:bats-test-task "$FIXTURES/ignored-file" @@ -90,9 +84,7 @@ setup() { [[ "$output" == *"SKIP"*"ignored-file"* ]] } -# ============================================================================ # Output details -# ============================================================================ @test "bats-test-task: fail output includes the remediation hint" { run codebase lint:bats-test-task "$FIXTURES/missing-usage-arg" @@ -109,9 +101,7 @@ setup() { [[ "$output" == *"invocations"* ]] } -# ============================================================================ # Error handling -# ============================================================================ @test "bats-test-task: fails when no targets given" { run codebase lint:bats-test-task @@ -125,9 +115,7 @@ setup() { [[ "$output" == *"does not exist"* ]] } -# ============================================================================ # Multi-target -# ============================================================================ @test "bats-test-task: accepts multiple targets and reports each" { run codebase lint:bats-test-task "$FIXTURES/clean" "$FIXTURES/missing-examples" diff --git a/test/lint/gum-table/gum-table.bats b/test/lint/gum-table/gum-table.bats index 2dfddaa..809b670 100644 --- a/test/lint/gum-table/gum-table.bats +++ b/test/lint/gum-table/gum-table.bats @@ -7,9 +7,7 @@ setup() { FIXTURES="$BATS_TEST_DIRNAME/fixtures" } -# ============================================================================ # High confidence: column -t (always a true positive) -# ============================================================================ @test "column-t: detects piping to column -t" { run codebase lint:gum-table "$FIXTURES/manual-padding/task-c" @@ -18,9 +16,7 @@ setup() { [[ "$output" == *"WARN"* ]] } -# ============================================================================ # High confidence: printf padding inside a loop -# ============================================================================ @test "loop-table: detects printf %-Ns inside while-read" { run codebase lint:gum-table "$FIXTURES/manual-padding/task-b" @@ -45,9 +41,7 @@ setup() { echo "$output" | grep "padding" | grep -q "INFO" } -# ============================================================================ # Low confidence: printf padding outside loops (INFO only, not a failure) -# ============================================================================ @test "padding: printf %-Ns outside loop is INFO, not a failure" { run codebase lint:gum-table "$FIXTURES/manual-padding/task-a" @@ -69,9 +63,7 @@ setup() { [[ "$output" == *"INFO"* ]] } -# ============================================================================ # True negatives — no output at all -# ============================================================================ @test "clean: already using gum table" { run codebase lint:gum-table "$FIXTURES/clean/task-gum" @@ -103,9 +95,7 @@ setup() { [[ "$output" == *"OK"* ]] } -# ============================================================================ # Multi-file scanning -# ============================================================================ @test "directory scan finds high-confidence hits" { run codebase lint:gum-table "$FIXTURES/manual-padding" @@ -121,9 +111,7 @@ setup() { [ "$status" -eq 0 ] } -# ============================================================================ # Output format -# ============================================================================ @test "WARN output includes file path, category, and line number" { run codebase lint:gum-table "$FIXTURES/manual-padding/task-b" @@ -136,9 +124,7 @@ setup() { [[ "$output" =~ INFO.*task-a:\[padding\].*[0-9]+: ]] } -# ============================================================================ # Error handling -# ============================================================================ @test "fails when target does not exist" { run codebase lint:gum-table /nonexistent @@ -146,9 +132,7 @@ setup() { [[ "$output" == *"ERROR"* ]] } -# ============================================================================ # Relative path resolution (regression: codebase#24) -# ============================================================================ @test "relative path resolves against CODEBASE_CALLER_PWD (dirty fixture)" { # Regression: relative targets resolved against codebase's install diff --git a/test/lint/mcr-scope/mcr-scope.bats b/test/lint/mcr-scope/mcr-scope.bats index 3ae6215..49869b4 100644 --- a/test/lint/mcr-scope/mcr-scope.bats +++ b/test/lint/mcr-scope/mcr-scope.bats @@ -7,9 +7,7 @@ setup() { FIXTURES="$BATS_TEST_DIRNAME/fixtures" } -# ============================================================================ # Detection -# ============================================================================ @test "mcr-scope: passes on a clean codebase" { run codebase lint:mcr-scope "$FIXTURES/clean" @@ -102,9 +100,7 @@ setup() { [[ "$output" == *"no test/ or lib/ files found"* ]] } -# ============================================================================ # Ignore directives -# ============================================================================ @test "mcr-scope: inline '# codebase:ignore' suppresses a single line" { run codebase lint:mcr-scope "$FIXTURES/ignored-inline" @@ -118,9 +114,7 @@ setup() { [[ "$output" == *"SKIP"*"ignored-file"* ]] } -# ============================================================================ # Output details -# ============================================================================ @test "mcr-scope: fail output includes file:line citations" { run codebase lint:mcr-scope "$FIXTURES/dirty-lib" @@ -136,9 +130,7 @@ setup() { [[ "$output" == *"BASH_SOURCE"* ]] } -# ============================================================================ # Error handling -# ============================================================================ @test "mcr-scope: fails when no targets given" { run codebase lint:mcr-scope @@ -153,9 +145,7 @@ setup() { [[ "$output" == *"does not exist"* ]] } -# ============================================================================ # Multi-target -# ============================================================================ @test "mcr-scope: accepts multiple targets and reports each" { run codebase lint:mcr-scope "$FIXTURES/clean" "$FIXTURES/dirty-test" diff --git a/test/lint/mise-settings/mise-settings.bats b/test/lint/mise-settings/mise-settings.bats index 565be0a..d925495 100644 --- a/test/lint/mise-settings/mise-settings.bats +++ b/test/lint/mise-settings/mise-settings.bats @@ -7,9 +7,7 @@ setup() { FIXTURES="$BATS_TEST_DIRNAME/fixtures" } -# ============================================================================ # Detection -# ============================================================================ @test "lint: passes when all settings present" { run codebase lint:mise-settings "$FIXTURES/complete" @@ -54,9 +52,7 @@ setup() { [[ "$output" == *"FAIL"*"missing-both"* ]] } -# ============================================================================ # Fix mode -# ============================================================================ @test "fix: adds missing settings" { WORK_DIR="$BATS_TEST_TMPDIR/fix-test" @@ -96,9 +92,7 @@ setup() { [[ "$output" == *"OK"* ]] } -# ============================================================================ # Error handling -# ============================================================================ @test "lint: fails when target does not exist" { run codebase lint:mise-settings /nonexistent diff --git a/test/lint/or-true/or-true.bats b/test/lint/or-true/or-true.bats index 31d7776..68cfaca 100644 --- a/test/lint/or-true/or-true.bats +++ b/test/lint/or-true/or-true.bats @@ -7,9 +7,7 @@ setup() { FIXTURES="$BATS_TEST_DIRNAME/fixtures" } -# ============================================================================ # Detection -# ============================================================================ @test "or-true: passes on a clean codebase" { run codebase lint:or-true "$FIXTURES/clean" @@ -76,9 +74,7 @@ setup() { [[ "$output" == *"if !"* ]] } -# ============================================================================ # Corpus-calibrated diagnostics -# ============================================================================ @test "or-true: arithmetic increments get a safer arithmetic suggestion" { local tmp @@ -173,9 +169,7 @@ EOF [[ "$output" == *"Intentional cases need a rule-specific reason"* ]] } -# ============================================================================ # Ignore directives -# ============================================================================ @test "or-true: inline '# codebase:ignore or-true — reason' skips the line" { run codebase lint:or-true "$FIXTURES/ignored-inline" @@ -189,9 +183,7 @@ EOF [[ "$output" == *"SKIP"*"ignored-file"* ]] } -# ============================================================================ # Scope / discovery -# ============================================================================ @test "or-true: walks the whole target — finds hits outside .mise/tasks and lib/" { run codebase lint:or-true "$FIXTURES/broad-walk" @@ -213,9 +205,7 @@ EOF [[ "$output" == *"no shell files"* ]] } -# ============================================================================ # Discovery correctness -# ============================================================================ @test "or-true: discovery skips non-bash/sh shebangs (fish, zsh, …)" { # Regression: the shebang regex '^#!.*(bash|sh)\b' matched 'sh' as @@ -255,9 +245,7 @@ EOF rm -rf "$tmp" } -# ============================================================================ # Comment handling -# ============================================================================ @test "or-true: does not flag '|| true' inside a single-quoted string" { # Accidental protection: the closing quote ''' is not in the @@ -293,9 +281,7 @@ EOF rm -rf "$tmp" } -# ============================================================================ # Multi-target -# ============================================================================ @test "or-true: checks multiple targets and reports each" { run codebase lint:or-true "$FIXTURES/clean" "$FIXTURES/dirty" @@ -309,9 +295,7 @@ EOF [ "$status" -eq 2 ] } -# ============================================================================ # Error paths -# ============================================================================ @test "or-true: fails when target does not exist" { run codebase lint:or-true "$FIXTURES/does-not-exist" @@ -328,9 +312,7 @@ EOF [[ "$output" == *""* ]] } -# ============================================================================ # Relative path resolution (regression: codebase#24) -# ============================================================================ @test "or-true: relative path resolves against CODEBASE_CALLER_PWD, not codebase install dir" { # Regression: when invoked via the shiv shim, relative paths resolved diff --git a/test/lint/shellcheck/shellcheck.bats b/test/lint/shellcheck/shellcheck.bats index 370c7d6..f0ac032 100644 --- a/test/lint/shellcheck/shellcheck.bats +++ b/test/lint/shellcheck/shellcheck.bats @@ -7,9 +7,7 @@ setup() { FIXTURES="$BATS_TEST_DIRNAME/fixtures" } -# ============================================================================ # Detection -# ============================================================================ @test "lint: passes on a clean codebase" { run codebase lint:shellcheck "$FIXTURES/clean" @@ -37,9 +35,7 @@ setup() { [[ "$output" == *"SC"* ]] } -# ============================================================================ # Ignore directive -# ============================================================================ @test "lint: skips when codebase:ignore shellcheck is set in mise.toml" { run codebase lint:shellcheck "$FIXTURES/ignored" @@ -76,9 +72,7 @@ setup() { [[ "$output" == *"SC2154"* ]] } -# ============================================================================ # Scope -# ============================================================================ @test "lint: works on a codebase with no mise.toml" { run codebase lint:shellcheck "$FIXTURES/no-toml" @@ -100,9 +94,7 @@ setup() { [[ "$output" == *"bad.sh"* ]] } -# ============================================================================ # Multi-target -# ============================================================================ @test "lint: checks multiple targets and reports each" { run codebase lint:shellcheck "$FIXTURES/clean" "$FIXTURES/dirty" @@ -116,9 +108,7 @@ setup() { [ "$status" -eq 2 ] } -# ============================================================================ # Error paths -# ============================================================================ @test "lint: fails when target does not exist" { run codebase lint:shellcheck "$FIXTURES/does-not-exist" diff --git a/test/migrations/task-pattern/task-pattern.bats b/test/migrations/task-pattern/task-pattern.bats index d86c33e..5f8a90d 100644 --- a/test/migrations/task-pattern/task-pattern.bats +++ b/test/migrations/task-pattern/task-pattern.bats @@ -23,9 +23,7 @@ assert_matches_before() { diff -u "$FIXTURES/before/$file" "$WORK_DIR/$file" } -# ============================================================================ # Individual variant tests -# ============================================================================ @test "migrate: simple mise run → _task" { codebase migrate:task-pattern "$WORK_DIR" @@ -57,9 +55,7 @@ assert_matches_before() { assert_matches_after ".mise/tasks/error-strings" } -# ============================================================================ # Full migration test -# ============================================================================ @test "migrate: all files match expected after state" { codebase migrate:task-pattern "$WORK_DIR" @@ -68,9 +64,7 @@ assert_matches_before() { [ "$status" -eq 0 ] } -# ============================================================================ # Reverse migration tests -# ============================================================================ @test "reverse: _task → mise run" { # Start from the after state @@ -101,9 +95,7 @@ assert_matches_before() { ! grep -q '_task' "$WORK_DIR/.mise/tasks/in-subshell" } -# ============================================================================ # Round-trip tests -# ============================================================================ @test "round-trip: forward then reverse restores lossless fixtures" { # Only test fixtures where forward is lossless (no -q flag) @@ -114,9 +106,7 @@ assert_matches_before() { assert_matches_before ".mise/tasks/error-strings" } -# ============================================================================ # Error handling -# ============================================================================ @test "migrate: fails when target does not exist" { run codebase migrate:task-pattern /nonexistent diff --git a/test/pre-commit/pre-commit.bats b/test/pre-commit/pre-commit.bats index 96bd4f2..c0fbddc 100644 --- a/test/pre-commit/pre-commit.bats +++ b/test/pre-commit/pre-commit.bats @@ -24,9 +24,7 @@ EOF export CODEBASE_CALLER_PWD="$REPO" } -# ============================================================================ # Install — fresh repo -# ============================================================================ @test "install: creates dispatcher" { codebase pre-commit @@ -97,9 +95,7 @@ EOF [ -x "$REPO/.git/hooks/pre-commit" ] } -# ============================================================================ # Install — existing dispatcher -# ============================================================================ @test "install: preserves existing dispatcher and other hooks" { mkdir -p "$REPO/.git/hooks/pre-commit.d" @@ -121,9 +117,7 @@ EOF [ -f "$REPO/.git/hooks/pre-commit.d/codebase" ] } -# ============================================================================ # Install — existing plain hook (not a dispatcher) -# ============================================================================ @test "install: errors when existing plain hook is not a dispatcher" { cat > "$REPO/.git/hooks/pre-commit" <<'EOF' @@ -137,9 +131,7 @@ EOF [[ "$output" == *"not a dispatcher"* ]] } -# ============================================================================ # Idempotent -# ============================================================================ @test "install: running twice is safe" { codebase pre-commit @@ -149,9 +141,7 @@ EOF [ -f "$REPO/.git/hooks/pre-commit.d/codebase" ] } -# ============================================================================ # --check -# ============================================================================ @test "check: exits 0 when hook is current" { codebase pre-commit @@ -189,9 +179,7 @@ EOF [ "$status" -ne 0 ] } -# ============================================================================ # --revert -# ============================================================================ @test "revert: removes codebase hook" { codebase pre-commit @@ -225,9 +213,7 @@ EOF [[ "$output" == *"No codebase hook"* ]] } -# ============================================================================ # Scope -# ============================================================================ @test "scope: default scopes are delegated to aggregate lint" { cat > "$REPO/mise.toml" <<'EOF' @@ -258,9 +244,7 @@ EOF ! grep -q 'src/scripts' "$REPO/.git/hooks/pre-commit.d/codebase" } -# ============================================================================ # Error handling -# ============================================================================ @test "error: fails outside git repo" { export CODEBASE_CALLER_PWD="$BATS_TEST_TMPDIR" diff --git a/test/scan/scan.bats b/test/scan/scan.bats index 9552433..e52a500 100644 --- a/test/scan/scan.bats +++ b/test/scan/scan.bats @@ -8,9 +8,7 @@ setup() { FIXTURES_B="$BATS_TEST_DIRNAME/fixtures-b" } -# ============================================================================ # Single target (basic matching) -# ============================================================================ @test "scan: finds mise run calls in extension-less task files" { run codebase scan -p 'mise run $$$ARGS' "$FIXTURES_A" @@ -39,9 +37,7 @@ setup() { [[ -z "$output" ]] } -# ============================================================================ # Different patterns -# ============================================================================ @test "scan: finds _task calls with custom pattern" { run codebase scan -p '_task $$$ARGS' "$FIXTURES_A" @@ -56,9 +52,7 @@ setup() { [ "$count" -eq 4 ] } -# ============================================================================ # Multiple targets -# ============================================================================ @test "multi: finds matches across multiple codebases" { run codebase scan -p 'mise run $$$ARGS' "$FIXTURES_A" "$FIXTURES_B" @@ -84,9 +78,7 @@ setup() { [[ "$output" != *"fixtures-b:"* ]] } -# ============================================================================ # Exclude filter -# ============================================================================ @test "exclude: filters out files matching glob" { run codebase scan -p 'mise run $$$ARGS' -e '.mise/tasks/ci/*' "$FIXTURES_A" @@ -110,9 +102,7 @@ setup() { [[ "$output" != *"ci/deploy"* ]] } -# ============================================================================ # Error handling -# ============================================================================ @test "error: fails when no pattern provided" { run codebase scan "$FIXTURES_A" From 7cbf39d19e00c75636cfaf045e6a3c088d7a1de4 Mon Sep 17 00:00:00 2001 From: olavostauros Date: Thu, 25 Jun 2026 16:33:38 -0300 Subject: [PATCH 3/3] fix: make bash-empty-array-expansions task executable for mise discovery --- .mise/tasks/lint/bash-empty-array-expansions | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 .mise/tasks/lint/bash-empty-array-expansions diff --git a/.mise/tasks/lint/bash-empty-array-expansions b/.mise/tasks/lint/bash-empty-array-expansions old mode 100644 new mode 100755