Skip to content

Commit ee67de3

Browse files
committed
Fix CRLF RawValue consistency in line continuation and stop-parsing state
Three fixes found during edge-case review: - Line continuation RawValue: for CRLF (backtick + \r\n), the previous code added \r and \n to RawValue then only removed two runes, leaving the backtick in RawValue. Now the peeked newline runes are consumed from the stream without being added to RawValue, so only one removeLastRaw (for the escape char) is needed. This makes LF and CRLF produce identical RawValue (no backtick or newline residue). - Stop-parsing token State: the Next() handler was overriding token.State with t.state (START_STATE) after scanStopParsing had already correctly set it to IN_WORD_STATE. Removed the override so the state set by scanStopParsing is preserved. - Added CRLF RawValue consistency regression test. Assisted-by: Crush:glm-5.2
1 parent 1c42226 commit ee67de3

2 files changed

Lines changed: 21 additions & 18 deletions

File tree

format_powershell_test.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -129,9 +129,13 @@ func TestPowershellFormat_BacktickLineContinuationCRLF(t *testing.T) {
129129
if err != nil {
130130
t.Fatal(err)
131131
}
132-
words := tokens.Words().Strings()
133-
if len(words) != 2 || words[1] != "foobar" {
134-
t.Errorf("powershell line continuation CRLF: Words = %v, want [echo foobar]", words)
132+
words := tokens.Words()
133+
if words.Strings()[1] != "foobar" {
134+
t.Errorf("powershell line continuation CRLF: Words = %v, want [echo foobar]", words.Strings())
135+
}
136+
// RawValue should NOT contain the backtick or CRLF (line continuation is consumed)
137+
if words[1].RawValue != "foobar" {
138+
t.Errorf("powershell line continuation CRLF: RawValue = %q, want foobar", words[1].RawValue)
135139
}
136140
}
137141

shlex.go

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -489,19 +489,18 @@ func (t *tokenizer) scanStream() (*Token, error) {
489489
continue
490490
}
491491
if lc.IsLineContinuation(peekRune) {
492-
token.RawValue += string(peekRune)
492+
// Consume optional \n after \r
493493
if peekRune == '\r' {
494-
// Consume optional \n after \r
495494
peek2, _, peek2Err := t.ReadRune()
496495
if peek2Err == nil && peek2 == '\n' {
497-
token.RawValue += string(peek2)
496+
// CRLF consumed — don't add to RawValue
498497
} else if peek2Err == nil {
499498
t.UnreadRune()
500499
}
501500
}
502-
// Line continuation: skip escape+newline, stay in START_STATE
503-
token.removeLastRaw() // remove the peeked newline
501+
// Line continuation: remove escape char from RawValue and Value
504502
token.removeLastRaw() // remove the escape char
503+
// Stay in START_STATE (no word content yet)
505504
continue
506505
}
507506
// Not a line continuation — unread and enter ESCAPING_STATE
@@ -643,18 +642,18 @@ func (t *tokenizer) scanStream() (*Token, error) {
643642
continue
644643
}
645644
if lc.IsLineContinuation(peekRune) {
646-
token.RawValue += string(peekRune)
645+
// Consume optional \n after \r
647646
if peekRune == '\r' {
648647
peek2, _, peek2Err := t.ReadRune()
649648
if peek2Err == nil && peek2 == '\n' {
650-
token.RawValue += string(peek2)
649+
// CRLF consumed — don't add to RawValue
651650
} else if peek2Err == nil {
652651
t.UnreadRune()
653652
}
654653
}
655-
// Line continuation: skip escape+newline, stay in IN_WORD_STATE
656-
token.removeLastRaw() // remove peeked newline
657-
token.removeLastRaw() // remove escape char
654+
// Line continuation: remove escape char from RawValue
655+
token.removeLastRaw() // remove the escape char
656+
// Stay in IN_WORD_STATE (word continues on next line)
658657
continue
659658
}
660659
// Not a line continuation — unread and enter ESCAPING_STATE
@@ -675,17 +674,17 @@ func (t *tokenizer) scanStream() (*Token, error) {
675674
default:
676675
// Check for line continuation (e.g. PowerShell backtick + newline)
677676
if lc, ok := t.format.(LineContinuationEscaper); ok && lc.IsLineContinuation(nextRune) {
678-
// Consume optional \n after \r
677+
// Consume optional \n after \r (without adding to RawValue)
679678
if nextRune == '\r' {
680679
peek2, _, peek2Err := t.ReadRune()
681680
if peek2Err == nil && peek2 == '\n' {
682-
token.RawValue += string(peek2)
681+
// CRLF consumed
683682
} else if peek2Err == nil {
684683
t.UnreadRune()
685684
}
686685
}
687-
// Line continuation: skip escape+newline
688-
token.removeLastRaw() // remove newline
686+
// Line continuation: remove newline and escape char from RawValue
687+
token.removeLastRaw() // remove newline (\n or \r)
689688
token.removeLastRaw() // remove escape char
690689
// If we have word content, continue in IN_WORD_STATE;
691690
// otherwise go back to START_STATE
@@ -1023,7 +1022,7 @@ func (t *tokenizer) Next() (*Token, error) {
10231022
if t.state == STOP_PARSING_STATE {
10241023
token, err := t.scanStopParsing()
10251024
if err == nil {
1026-
token.State = t.state
1025+
// scanStopParsing already sets token.State and t.state
10271026
if token.Span.End == 0 && token.Span.Start >= 0 {
10281027
token.Span.End = token.Span.Start + len([]rune(token.RawValue))
10291028
}

0 commit comments

Comments
 (0)