fix: properly handle multiline strings in command converter

- Added detection for multiline strings to avoid incorrect conversion
- Multiline strings now have newlines escaped as \n instead of being joined with semicolons
- Line continuations (backslash) are properly distinguished from multiline strings
- Added comprehensive test cases for multiline string handling
- Fixes issue where multiline strings were incorrectly converted with semicolons
This commit is contained in:
Roo Code 2025-08-15 23:07:00 +00:00
parent 4b5ede5278
commit 114091fbdf
2 changed files with 202 additions and 0 deletions

View file

@ -267,6 +267,54 @@ echo "three"`
expect(result.command).toContain('echo "Line 99"')
expect(result.command.split(";").length).toBeGreaterThan(50)
})
it("should handle multiline strings correctly", () => {
const input = `echo "This is a
multiline
string"`
const result = convertMultilineToSingleLine(input)
expect(result.success).toBe(true)
expect(result.command).toBe('echo "This is a\\nmultiline\\nstring"')
})
it("should handle multiline strings with single quotes", () => {
const input = `echo 'First line
Second line
Third line'`
const result = convertMultilineToSingleLine(input)
expect(result.success).toBe(true)
expect(result.command).toBe("echo 'First line\\nSecond line\\nThird line'")
})
it("should handle mixed commands with multiline strings", () => {
const input = `echo "Start"
MESSAGE="This is
a multiline
message"
echo "$MESSAGE"`
const result = convertMultilineToSingleLine(input)
expect(result.success).toBe(true)
expect(result.command).toBe(
'echo "Start" ; MESSAGE="This is\\na multiline\\nmessage" ; echo "$MESSAGE"',
)
})
it("should handle escaped quotes in strings", () => {
const input = `echo "Line with \\"escaped\\" quotes
and a new line"`
const result = convertMultilineToSingleLine(input)
expect(result.success).toBe(true)
expect(result.command).toBe('echo "Line with \\"escaped\\" quotes\\nand a new line"')
})
it("should handle commands after multiline strings", () => {
const input = `TEXT="Line 1
Line 2"
echo "Done"`
const result = convertMultilineToSingleLine(input)
expect(result.success).toBe(true)
expect(result.command).toBe('TEXT="Line 1\\nLine 2" ; echo "Done"')
})
})
describe("Real-world examples", () => {

View file

@ -20,6 +20,142 @@ function hasHereDocument(command: string): boolean {
return hereDocPattern.test(command)
}
/**
* Detects if a command contains a multiline string literal
*/
function hasMultilineString(command: string): boolean {
// First check if the command has line continuations - these are NOT multiline strings
// Line continuations end with backslash
if (/\\\s*\n/.test(command)) {
return false
}
// Check for multiline strings in quotes that span multiple lines
// This is a simplified check - looks for quotes with newlines between them
const lines = command.split("\n")
let inString = false
let stringDelimiter = ""
let escapeNext = false
for (const line of lines) {
for (let i = 0; i < line.length; i++) {
const char = line[i]
if (escapeNext) {
escapeNext = false
continue
}
if (char === "\\") {
escapeNext = true
continue
}
if ((char === '"' || char === "'") && !inString) {
inString = true
stringDelimiter = char
} else if (char === stringDelimiter && inString) {
inString = false
stringDelimiter = ""
}
}
// If we're still in a string after processing a line, it's multiline
if (inString && lines.indexOf(line) < lines.length - 1) {
return true
}
}
return false
}
/**
* Converts multiline strings to single-line with escaped newlines
*/
function convertMultilineStrings(command: string): string {
// Handle multiline strings by replacing newlines with \n
const lines = command.split("\n")
const result: string[] = []
let inString = false
let stringDelimiter = ""
let currentString = ""
let beforeString = "" // Track text before the string starts
let escapeNext = false
for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) {
const line = lines[lineIndex]
let processedLine = ""
for (let i = 0; i < line.length; i++) {
const char = line[i]
if (escapeNext) {
// We're escaping this character
if (inString) {
currentString += "\\" + char
} else {
processedLine += "\\" + char
}
escapeNext = false
continue
}
if (char === "\\") {
// This might be an escape character
const nextChar = i < line.length - 1 ? line[i + 1] : ""
if (nextChar === '"' || nextChar === "'") {
// It's escaping a quote
escapeNext = true
continue
} else {
// It's just a backslash
if (inString) {
currentString += char
} else {
processedLine += char
}
}
} else if ((char === '"' || char === "'") && !inString) {
// Starting a string
inString = true
stringDelimiter = char
beforeString = processedLine // Save text before string
currentString = char
processedLine = "" // Clear processed line as we're now in a string
} else if (char === stringDelimiter && inString) {
// Ending a string
currentString += char
processedLine = beforeString + currentString + processedLine
inString = false
stringDelimiter = ""
currentString = ""
beforeString = ""
} else if (inString) {
// Inside a string
currentString += char
} else {
// Outside a string
processedLine += char
}
}
if (inString && lineIndex < lines.length - 1) {
// We're in a multiline string, add \n for the newline
currentString += "\\n"
} else if (!inString && processedLine) {
// Not in a string, add the processed line
result.push(processedLine)
}
}
// If we're still in a string at the end, complete it
if (inString && currentString) {
// Close the unclosed string and add it with the text before it
result.push(beforeString + currentString)
}
return result.join("\n")
}
/**
* Main function to convert multiline commands to single line
* Uses a simple approach: join lines with semicolons for most cases
@ -39,6 +175,24 @@ export function convertMultilineToSingleLine(command: string): ConversionResult
}
}
// Check if command contains multiline strings
if (hasMultilineString(command)) {
// Convert multiline strings to single-line with \n
try {
const converted = convertMultilineStrings(command)
// After converting strings, check if there are still multiple lines
if (!converted.includes("\n")) {
return { success: true, command: converted }
}
// If there are still multiple lines after string conversion,
// continue with normal processing
command = converted
} catch (error) {
// If string conversion fails, continue with normal processing
console.log(`[multilineCommandConverter] String conversion failed: ${error.message}`)
}
}
try {
// Simple approach: handle common patterns
let result = command