From b9feb0b81db023ac14d224b9f9131cab4830e13b Mon Sep 17 00:00:00 2001 From: olavostauros Date: Mon, 22 Jun 2026 14:38:30 -0300 Subject: [PATCH] fix: support multi-path scope values in [_.codebase.scope] MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The codebase_target_for_rule() function treated the entire scope value as a single path, so a TOML string like or-true = ".mise/tasks lib" would produce a single target /repo/.mise/tasks lib (with space) instead of two separate targets /repo/.mise/tasks and /repo/lib. This meant multi-path scope overrides either errored (path doesn't exist) or silently scanned nothing — users thought they had multi-path coverage but got false passes. Changes: - lib/codebase-config.sh: Replace codebase_target_for_rule() with codebase_targets_for_rule() that splits the scope value on whitespace and emits one target line per token. - .mise/tasks/lint/_default: Iterate over the multi-line output so each target is linted independently. - test/lint/default.bats: Add test covering multi-path scope with space-separated path list. The fix uses shell word-splitting on the scope string (unquoted in a for loop), which naturally handles TOML string values with internal spaces. Each token is resolved as a relative path against the repo root, or passed through unchanged if absolute. Fixes #22 --- .mise/tasks/lint/_default | 37 ++++++++++++++++++++++--------------- lib/codebase-config.sh | 24 +++++++++++++++++------- test/lint/default.bats | 38 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 77 insertions(+), 22 deletions(-) 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]