fix(linter): SC2242 counts case/loop nesting instead of flagging it (closes #331) - #332
Open
noahgift wants to merge 1 commit into
Open
fix(linter): SC2242 counts case/loop nesting instead of flagging it (closes #331)#332noahgift wants to merge 1 commit into
noahgift wants to merge 1 commit into
Conversation
…loses #331) `bashrs lint` reported SC2242 on two `continue`s that are plainly inside a `while` loop, in a script `bash -n` accepts and that runs. Two defects, one shape — a detector reporting a result it did not measure: 1. `in_case`/`in_loop`/`in_function` were BOOLEANS, not depth counters, so an inner `done` cleared `in_loop` while the outer `while` was still open. 2. `is_case_end` demanded the line BE `esac`, so a one-line `case … esac` never cleared `in_case` — it stayed true for the rest of the file. Either alone is survivable. Together they make `in_case && !in_loop` true INSIDE a loop, and every later break/continue is flagged: while IFS= read -r id; do case "$id" in '') continue ;; esac for a in 1 2; do for b in 3 4; do printf '%s%s\n' "$a" "$b"; done done if [ -z "$id" ]; then continue # <- reported as "outside a loop" fi done < <(printf 'x\n') Now: keyword-TOKEN depth counters (`;`, `(`, `)`, `&`, `|`, backtick are separators, so a `break ;; esac` clause yields `break` and `esac` as tokens of their own). Openers count BEFORE the check and closers AFTER, so a one-line case whose clause breaks is still judged inside its own case while a one-line `for … done` balances. Still a line/keyword heuristic and the code says so — a keyword inside a quoted string or a heredoc is still miscounted — but it is no longer wrong for code anyone writes. Six tests, including the reproducer above, `donefile`/`casework` (a word containing a keyword is not the keyword) and case-in-loop-in-case. PROVEN TO DISCRIMINATE: reverting `check()` to the boolean behaviour turns exactly the two new nesting tests red and leaves the other 14 green. The rule still fires on the real defect — a `break` in a case outside any loop is one error, whether the case is written on one line or three. Found while landing paiml/infra PMAT-534, where it was 2 of the 6 errors on a done_when script; the other 4 were real and were fixed there. cargo test -p bashrs --lib linter:: 6067 passed, 0 failed cargo clippy -p bashrs --lib -- -D warnings clean the fixed binary: 0 errors on the script that started this Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two defects in
rash/src/linter/rules/sc2242.rs, one shape — a detector reporting a result it did not measure.Reproducer (14 lines;
bash -naccepts it, and it runs)Root cause
check()tracks context within_case/in_loop/in_functionas booleans, not depth counters:donecloses the outer loop.for/forsetin_loop = true(already true), then eachof the two
donelines setsin_loop = false— so after the nested loops close, the still-openwhileis invisible.case … esacnever closes.is_case_endisline.trim_start() == "esac", which isfalse for a
case … ;; esacwritten on one line, soin_casestays true for the rest of the file.Either alone is survivable; together they make
in_case && !in_looptrue inside a loop, and every laterbreak/continueis flagged. Found while linting a done_when script inpaiml/infra(PMAT-534): therule reported 2 errors on a script
bash -naccepts and that runs correctly.Fix
fix/sc2242-nesting-depth: keyword-TOKEN depth counters (;,(,),&,|and backtick areseparators, so a
break ;; esacclause yieldsbreakandesacas their own tokens); openers countbefore the check and closers after, so a one-line case whose clause breaks is still judged inside its own
case while a one-line
for … donebalances. Still a line/keyword heuristic, not a parser — a keywordinside a quoted string or a heredoc is still miscounted, and the code says so — but it is no longer wrong
for code anyone writes.
Six tests added, including the reproducer above,
donefile/casework(a word containing a keyword isnot the keyword) and a case-in-loop-in-case nesting. Discrimination proven: reverting
check()tothe boolean behaviour turns exactly the two new nesting tests RED
(
a_continue_after_nested_loops_close_is_not_in_the_case,a_one_line_case_closes_on_its_own_line) andleaves the other 14 green. The rule still fires on the real defect: a
breakin a case outside any loopis still one error, whether the case is written on one line or three.
Verification
cargo test -p bashrs --lib linter::— 6067 passed, 0 failedcargo clippy -p bashrs --lib -- -D warnings— cleancargo fmt --check— cleancheck()to the boolean behaviour turns exactly the two new nesting tests red —a_continue_after_nested_loops_close_is_not_in_the_caseanda_one_line_case_closes_on_its_own_line— and leaves the other 14 green.Found while landing
paiml/infraPMAT-534, where these were 2 of the 6bashrs linterrors on a done_when script. The other 4 were real findings and were fixed there.🤖 Generated with Claude Code