Skip to content

fix: preserve ]] in SC2292 suggestions#3455

Closed
lawrence3699 wants to merge 1 commit into
koalaman:masterfrom
lawrence3699:fix/sc2292-autofix-brackets
Closed

fix: preserve ]] in SC2292 suggestions#3455
lawrence3699 wants to merge 1 commit into
koalaman:masterfrom
lawrence3699:fix/sc2292-autofix-brackets

Conversation

@lawrence3699
Copy link
Copy Markdown

Fixes #3447

Before this change, SC2292 could suggest an extra closing bracket in TTY output for malformed single-bracket tests under require-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 build
  • cabal 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 for analyzeScript (defaultSpec pr) with empty asSourceText
  • cabal test

AI-assisted

Copilot AI review requested due to automatic review settings April 19, 2026 12:51
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 asSourceText to AnalysisSpec and thread the original source text into Parameters so checkers can make source-sensitive fixes.
  • Update the require-double-brackets fixer 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.

Comment on lines +4749 to +4756
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
Copy link

Copilot AI Apr 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines +4773 to +4779
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)
Copy link

Copilot AI Apr 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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)

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Invalid fix suggestion for mishandled [[ when require-double-brackets is enabled

2 participants