Skip to content

SC2075: a backslash inside single quotes is literal — no false positive on '\\' followed by another quoted string (#439) - #441

Merged
noahgift merged 5 commits into
mainfrom
fix/439-sc2075-backslash
Oct 10, 2026
Merged

noahgift merged 5 commits into
mainfrom
fix/439-sc2075-backslash

Conversation

@noahgift

Copy link
Copy Markdown
Contributor

Fixes #439.

bashrs lint reported SC2075 [error] ("Escaping a single quote in single quotes won't work") on a valid single-quoted backslash when another single-quoted string followed on the same line:

echo 'a\b' | tr -d '\\' | grep -x 'ab'   # bash prints "ab"; bashrs said 1:20-36 SC2075

Inside single quotes a backslash is literal, so '\\' and '\' close normally. The rule now reports an attempt to escape a single quote only when the string the author meant actually closes later on the line, and treats a backslash before a closing single quote as literal. The true positive echo 'it\'s' still fires.

Commits:

  • RED: failing tests for the reproducer, the lone-backslash form, the same line inside "$( ... )", and the true positives.
  • The fix in rash/src/linter/rules/sc2075.rs, plus pins for awk -F'\' and the open quote an attempt leaves (SC1078 control).
  • Receipt (docs/audits/impl-PMAT-439-receipt.md) and review record (docs/audits/quorum-PMAT-439-review.json).

Evidence (in the receipt): targeted tests 18 passed / 0 failed; with the rule body reverted and the tests kept, 7 fail; cargo nextest run --workspace 17347 passed / 0 failed; bashrs corpus run 18814/18814; pv lint contracts PASS. Two blind review rounds, each 3/3 PASS on the real diff with a planted control caught 3/3.

Pmat-Ticket: PMAT-439

🤖 Generated with Claude Code

noahgift and others added 5 commits October 10, 2026 09:29
Inside '...' a backslash is literal and the next quote closes the
string. SC2075 matches '[^']*\\'[^']*' on the raw line, so in

    echo 'a\b' | tr -d '\\' | grep -x 'ab'

it reads the quote after '\\' as escaped and pairs the string with the
next quoted string on the line: an Error (2:20-36) on a script bash
runs and prints "ab".

Tests, all failing on this commit:
- the reproducer, the lone-backslash form tr -d '\' | grep -x 'a', and
  the same pipeline inside "$( ... )" report no SC2075 and no error
  (sc2075 unit tests, and end to end through lint_shell in
  lexer_context_tests);
- a backslash string followed by a separate word: a space, ';', ']',
  '"', '$', another quote, a trailing comment;
- \' outside a plain single-quoted string: "it\'s", $'it\'s', it\'s;
- echo 'it\'s' still reports SC2075 as an Error, also after an earlier
  valid '\\' on the same line, and echo 'a\'1 (no later quote) too.

The issue's controls (tr -d '\\' alone, x='\\', x='\') and the ten
existing SC2075 tests pass. F-LCX-021 in linter-lexer-context-v1 names
the end-to-end test.

Pmat-Ticket: PMAT-439
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The rule matched '[^']*\\'[^']*' on the raw line. That reads every \'
as an escaped quote, so tr -d '\\' | grep -x 'ab' became one string
running from '\\' to the next opening quote on the line, an Error on a
valid script.

It now scans each line with its quoting context. Inside '...' a
backslash is literal and the next quote closes the string; the attempt
it reports is a \' followed straight away by a letter, digit or
underscore, as in 'it\'s', where the closing quote runs into the same
word. \' inside "...", $'...', a comment or unquoted code is not read
as one. After an attempt the scan reads the string the way its author
meant it, so 'don\'t' 'won\'t' still reports twice; linter::quoting
reads the line as the shell does and closes at the \', which puts it
out of step with the author for the rest of the line, so the rule does
not use it.

The ten existing SC2075 tests keep their counts and spans. Putting the
regex back turns all seven PMAT439 tests red; with this change all 18
SC2075 tests pass. The highest cyclomatic complexity in the file is 7.

Pmat-Ticket: PMAT-439
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
awk -F'\' '{print $2}' file splits on a backslash. The old regex read
'\' '{print $2}' as one string with an escaped quote in it; the rule
now reports nothing there, and a row holds it.

An attempt followed by a space, echo 'can\' t', glues no word, so
SC2075 stays silent on it. The quote it leaves open is still an Error
from SC1078, and lexer_context_tests now asserts that, so narrowing
SC2075 to the word-gluing case does not let that line go clean.

Pmat-Ticket: PMAT-439
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
echo 'a\'b has no later quote on the line, so the shell's reading is
the only one: the string a\ closes and the word runs on into b, printing
a\b. The old regex needed a later quote before it reported, but the
scanner reported this valid line as SC2075. It now records an attempt
only when a later quote closes the string its author meant, which keeps
echo 'it\'s' and the digit form echo 'a\'1' reported.

$$ is the pid, so in echo $$'a\'b' the quote after it opens a plain
single-quoted string. The scanner read $' there as ANSI-C quoting and
went silent where the old regex reported. $$ is now stepped over as one
token.

Pmat-Ticket: PMAT-439
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The receipt maps each claim to its test, gives the RED and revert counts,
the gate counts and the corpus score, and lists what the fix does not
cover. The review record holds both blind rounds: each lane's verdict
on the real diff and on a control, and the cargo result for each control.

Pmat-Ticket: PMAT-439
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@noahgift
noahgift merged commit e25dbee into main Oct 10, 2026
8 of 9 checks passed
@noahgift
noahgift deleted the fix/439-sc2075-backslash branch October 10, 2026 09:25
noahgift added a commit that referenced this pull request Oct 10, 2026
…g from main carries, including the SC2075 fix forjar's I8 gate is waiting on (#442)

The second cut (#416) was not tagged, and two fixes merged after it:
#430 (PMAT-428, SEC012 follows an eval payload through a variable) and
#441 (PMAT-439, SC2075 false positive on '\\' followed by another quoted
string). A tag cut from main carries both, so CHANGELOG, release-notes,
releases.md and the book name fifteen fixes, both entries are completed
with release:v7.4.2, and PMAT-354 gains the third-cut criterion.

Docs-only. Measured on e25dbee (debug build): the completion check of
fw16-wired-10g-nm-owner from infra origin/main lints 0 errors (7.4.1:
1 error, SC2075 at 6:99-116), and `echo 'don\'t'` still fires SC2075.

Refs PMAT-354

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
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.

lint: SC2075 false positive on a valid single-quoted backslash ('\\' or '\') followed by another single-quoted string

1 participant