mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-09-05 08:10:14 +00:00
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:
parent
4b5ede5278
commit
114091fbdf
2 changed files with 202 additions and 0 deletions
|
|
@ -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", () => {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue