Skip to content

Commit

Permalink
syntax: backquotes affect backslashes in single quotes too
Browse files Browse the repository at this point in the history
I incorrectly understood that backquote command substitutions
did not affect backslashes in single quotes. For example,

    $ echo `echo "1.2" | sed -e 's/\./\\\\./g'`
    1\.2

would be formatted by shfmt as the following, with different output:

    $ echo $(echo "1.2" | sed -e 's/\./\\\\./g')
    1\\.2

when in fact we do want to deduplicate the backslashes:

    $ echo $(echo "1.2" | sed -e 's/\./\\./g')
    1\.2

Tweak the existing test which expected the wrong result.
Thankfully, we simplify the code now with the fix.

Fixes #1041.
  • Loading branch information
mvdan committed Dec 26, 2023
1 parent 28117db commit 3384f3d
Show file tree
Hide file tree
Showing 3 changed files with 10 additions and 17 deletions.
13 changes: 9 additions & 4 deletions syntax/filetests_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1797,7 +1797,7 @@ var fileTests = []testCase{
{
Strs: []string{
`$(echo '\' 'a\b' "\\" "a\a")`,
"`" + `echo '\' 'a\b' "\\\\" "a\a"` + "`",
"`" + `echo '\' 'a\\b' "\\\\" "a\a"` + "`",
},
common: cmdSubst(stmt(call(
litWord("echo"),
Expand Down Expand Up @@ -4589,7 +4589,7 @@ func recursiveSanityCheck(tb testing.TB, src string, v any) {
return
}
}
tb.Errorf("Expected one of %q at %d in %q, found %q",
tb.Errorf("Expected one of %q at %s in %q, found %q",
strs, pos, src, gotErr)
}
checkNodePosEnd := func(n Node) {
Expand Down Expand Up @@ -4701,7 +4701,7 @@ func recursiveSanityCheck(tb testing.TB, src string, v any) {
tb.Errorf("Lit without newlines has Pos/End lines %d and %d",
posLine, endLine)
case strings.Contains(src, "`") && strings.Contains(src, "\\"):
// removed quotes inside backquote cmd substs
// removed backslashes inside backquote cmd substs
val = ""
case end < len(src) && (src[end] == '\n' || src[end] == '`'):
// heredoc literals that end with the
Expand Down Expand Up @@ -4794,7 +4794,12 @@ func recursiveSanityCheck(tb testing.TB, src string, v any) {
if x.Dollar {
valuePos = posAddCol(valuePos, 1)
}
checkPos(valuePos, x.Value)
val := x.Value
if strings.Contains(src, "`") && strings.Contains(src, "\\") {
// removed backslashes inside backquote cmd substs
val = ""
}
checkPos(valuePos, val)
if x.Dollar {
checkPos(x.Left, "$'")
} else {
Expand Down
5 changes: 0 additions & 5 deletions syntax/lexer.go
Original file line number Diff line number Diff line change
Expand Up @@ -412,11 +412,6 @@ func (p *Parser) peekByte(b byte) bool {
func (p *Parser) regToken(r rune) token {
switch r {
case '\'':
if p.openBquotes > 0 {
// bury openBquotes
p.buriedBquotes = p.openBquotes
p.openBquotes = 0
}
p.rune()
return sglQuote
case '"':
Expand Down
9 changes: 1 addition & 8 deletions syntax/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -389,9 +389,6 @@ type Parser struct {

// lastBquoteEsc is how many times the last backquote token was escaped
lastBquoteEsc int
// buriedBquotes is like openBquotes, but saved for when the parser
// comes out of single quotes
buriedBquotes int

rxOpenParens int
rxFirstPart bool
Expand Down Expand Up @@ -435,7 +432,7 @@ func (p *Parser) reset() {
p.openStmts = 0
p.heredocs, p.buriedHdocs = p.heredocs[:0], 0
p.parsingDoc = false
p.openBquotes, p.buriedBquotes = 0, 0
p.openBquotes = 0
p.accComs, p.curComs = nil, &p.accComs
p.litBatch = nil
p.wordBatch = nil
Expand Down Expand Up @@ -1124,10 +1121,6 @@ func (p *Parser) wordPart() WordPart {
sq.Right = p.nextPos()
sq.Value = p.endLit()

// restore openBquotes
p.openBquotes = p.buriedBquotes
p.buriedBquotes = 0

p.rune()
p.next()
return sq
Expand Down

0 comments on commit 3384f3d

Please sign in to comment.