fix: preserve ]] in SC2292 suggestions#3455
Conversation
There was a problem hiding this comment.
Pull request overview
This PR fixes ShellCheck’s SC2292 autofix suggestion when require-double-brackets is enabled and a malformed single-bracket test is closed with ]], ensuring the suggested fix preserves the intended closing ]] (and whitespace) instead of producing an extra bracket.
Changes:
- Add
asSourceTexttoAnalysisSpecand thread the original source text intoParametersso checkers can make source-sensitive fixes. - Update the
require-double-bracketsfixer logic to detect and preserve an existing trailing]]while keeping the normal[ ... ] -> [[ ... ]]behavior unchanged. - Add property tests that validate the applied fix output across single-line, multiline, and tabbed inputs (including the malformed
]]case).
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
src/ShellCheck/Interface.hs |
Extends AnalysisSpec with asSourceText to carry original source text through analysis. |
src/ShellCheck/Checker.hs |
Populates asSourceText from the parsed script contents so fixers can inspect original text. |
src/ShellCheck/AnalyzerLib.hs |
Plumbs asSourceText into Parameters and ensures test helpers set it when analyzing strings. |
src/ShellCheck/Analytics.hs |
Implements source-span-based closing bracket handling for SC2292 under require-double-brackets and adds fix-application tests. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| sourceSpan start end | ||
| | otherwise = | ||
| let sourceLines = lines (sourceText params) | ||
| startLine = fromIntegral (posLine start) | ||
| endLine = fromIntegral (posLine end) | ||
| lineAt n | ||
| | n >= 1 && n <= length sourceLines = Just (sourceLines !! (n - 1)) | ||
| | otherwise = Nothing |
There was a problem hiding this comment.
sourceSpan recomputes lines (sourceText params) on every call and then indexes it with !!. Since replaceConditionEnd can run for many conditions in a script, this makes the fix generation potentially O(nodes * source_length). Consider computing a line array once (e.g., Array Int String) and reusing it for span extraction, or caching the split lines in Parameters/local where bindings so each condition doesn't rescan the entire source.
| columnToIndex line target = go line 0 1 | ||
| where | ||
| go rest raw visual | ||
| | target <= visual = raw | ||
| go [] raw _ = raw | ||
| go ('\t':rest) raw visual = go rest (raw + 1) (visual + 8 - ((visual - 1) `mod` 8)) | ||
| go (_:rest) raw visual = go rest (raw + 1) (visual + 1) |
There was a problem hiding this comment.
columnToIndex duplicates the tab-stop/column realignment logic that already exists in ShellCheck.Fixer.removeTabStops. To avoid future inconsistencies (especially around tab width and 1-based columns), consider extracting/reusing a shared helper rather than maintaining a second implementation here.
| columnToIndex line target = go line 0 1 | |
| where | |
| go rest raw visual | |
| | target <= visual = raw | |
| go [] raw _ = raw | |
| go ('\t':rest) raw visual = go rest (raw + 1) (visual + 8 - ((visual - 1) `mod` 8)) | |
| go (_:rest) raw visual = go rest (raw + 1) (visual + 1) | |
| tabWidth = 8 | |
| advanceVisualColumn visual '\t' = visual + tabWidth - ((visual - 1) `mod` tabWidth) | |
| advanceVisualColumn visual _ = visual + 1 | |
| columnToIndex line target = go line 0 1 | |
| where | |
| go rest raw visual | |
| | target <= visual = raw | |
| go [] raw _ = raw | |
| go (c:rest) raw visual = go rest (raw + 1) (advanceVisualColumn visual c) |
Fixes #3447
Before this change,
SC2292could suggest an extra closing bracket in TTY output for malformed single-bracket tests underrequire-double-brackets, e.g.[[ -n "$witness_me" ]]]for[ -n "$witness_me" ]].This change uses the original source span for the closing bracket segment so the fixer can preserve the existing trailing whitespace while replacing a real trailing
]]. That keeps the normal[ ... ]autofix behavior unchanged while correcting the malformed]]case in single-line, multiline, and tabbed inputs.Validation:
cabal buildcabal exec -- shellcheck --enable=require-double-brackets -sbash -on[ -n "$witness_me" ]]cabal exec -- shellcheck --enable=require-double-brackets -sbash -on[ -n "$witness_me" ]cabal exec -- shellcheck --enable=require-double-brackets -sbash -on multiline[/-n "$witness_me"/]]cabal exec -- shellcheck --enable=require-double-brackets -sbash -on[ -n "$witness_me" ]]cabal exec -- runghc ...harness foranalyzeScript (defaultSpec pr)with emptyasSourceTextcabal testAI-assisted