diff --git a/.mise/tasks/lint/_default b/.mise/tasks/lint/_default index 297abc7..f7c1dfb 100755 --- a/.mise/tasks/lint/_default +++ b/.mise/tasks/lint/_default @@ -36,24 +36,31 @@ failures=0 FAILED=() for rule in "${RULES[@]}"; do - rule_target=$(codebase_target_for_rule "$REPO_ROOT" "$rule") - echo "codebase: lint:$rule $rule_target" + # Collect all targets for this rule (supports multi-path scopes) + TARGETS=() + while IFS= read -r t; do + [[ -n "$t" ]] && TARGETS+=("$t") + done < <(codebase_targets_for_rule "$REPO_ROOT" "$rule") - output="" - if output=$(mise run -q "lint:$rule" "$rule_target" 2>&1); then - status=0 - else - status=$? - fi + for rule_target in "${TARGETS[@]}"; do + echo "codebase: lint:$rule $rule_target" - if [[ -n "$output" ]]; then - printf '%s\n' "$output" - fi + output="" + if output=$(mise run -q "lint:$rule" "$rule_target" 2>&1); then + status=0 + else + status=$? + fi - if [[ "$status" -ne 0 ]]; then - failures=$((failures + 1)) - FAILED+=("lint:$rule ($rule_target) exited $status") - fi + if [[ -n "$output" ]]; then + printf '%s\n' "$output" + fi + + if [[ "$status" -ne 0 ]]; then + failures=$((failures + 1)) + FAILED+=("lint:$rule ($rule_target) exited $status") + fi + done done if [[ "$failures" -gt 0 ]]; then diff --git a/lib/codebase-config.sh b/lib/codebase-config.sh index 2d43820..7b917a1 100644 --- a/lib/codebase-config.sh +++ b/lib/codebase-config.sh @@ -109,19 +109,29 @@ codebase_scope_for_rule() { codebase_default_scope_for_rule "$rule" } -# codebase_target_for_rule +# codebase_targets_for_rule # -# Emit the concrete target path for a rule after scope resolution. -codebase_target_for_rule() { +# Emit the concrete target path(s) for a rule after scope resolution. +# When the scope value is a space-separated list of paths (from TOML string +# values like or-true = ".mise/tasks lib"), emits one line per target. +codebase_targets_for_rule() { local repo_root="$1" local rule="$2" - local scope + local scope token scope=$(codebase_scope_for_rule "$repo_root" "$rule") + # Handle empty/dot scope — single target: the repo root itself. case "$scope" in - ""|".") printf '%s\n' "$repo_root" ;; - /*) printf '%s\n' "$scope" ;; - *) printf '%s/%s\n' "$repo_root" "$scope" ;; + ""|".") printf '%s\n' "$repo_root"; return 0 ;; esac + + # Split scope on whitespace into individual path tokens and resolve each. + # This supports multi-path scopes like ".mise/tasks lib" from TOML. + for token in $scope; do + case "$token" in + /*) printf '%s\n' "$token" ;; + *) printf '%s/%s\n' "$repo_root" "$token" ;; + esac + done } diff --git a/test/lint/default.bats b/test/lint/default.bats index 9a6eac1..1f99003 100644 --- a/test/lint/default.bats +++ b/test/lint/default.bats @@ -182,6 +182,44 @@ EOF [[ "$output" == *"no lint rules configured"* ]] } +@test "lint: honors multi-path scope (space-separated targets)" { + write_config <<'EOF' +[settings] +quiet = true +task_output = "interleave" + +[_.codebase] +lint = ["gum-table"] + +[_.codebase.scope] +gum-table = ".mise/tasks scripts" +EOF + + # Create a dirty file in .mise/tasks/ (printf inside loop = WARN) + write_clean_task + cat > "$REPO/.mise/tasks/build" <<'SCRIPT' +#!/usr/bin/env bash +while read -r item; do + printf "%-10s %s\n" "$item" ok +done < input +SCRIPT + + # Create a dirty file in scripts/ (printf padding = INFO, not a WARN) + mkdir -p "$REPO/scripts" + cat > "$REPO/scripts/deploy" <<'SCRIPT' +#!/usr/bin/env bash +printf "%-20s %s\n" "$1" "$2" +SCRIPT + + run codebase lint "$REPO" + # gum-table fails on .mise/tasks/build (loop-table WARN) + [ "$status" -ne 0 ] + [[ "$output" == *"codebase: lint:gum-table $REPO_ROOT/.mise/tasks"* ]] + [[ "$output" == *"codebase: lint:gum-table $REPO_ROOT/scripts"* ]] + [[ "$output" == *"WARN"*"tasks:build"* ]] + [[ "$output" == *"scripts:deploy"* ]] +} + @test "lint: parses multiline lint arrays" { write_config <<'EOF' [settings]