Skip to content

Commit 8e86bf1

Browse files
committed
Fix linter warning
1 parent d64f054 commit 8e86bf1

File tree

4 files changed

+166
-161
lines changed

4 files changed

+166
-161
lines changed

evals/.tool-versions

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
python 3.13.2
22
golang 1.24.2
3-
rust 1.86.0
3+
rust 1.85.1
44
nodejs 20.18.1

scripts/generate-types.mts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,13 @@ async function main() {
2121
fs.writeFileSync("src/exports/types.ts", types.join("\n\n"))
2222

2323
await $`npx tsup src/exports/interface.ts --dts-only -d out`
24-
fs.writeFileSync("out/interface.d.ts", "src/exports/roo-code.d.ts")
24+
fs.copyFileSync("out/interface.d.ts", "src/exports/roo-code.d.ts")
2525

2626
await $`npx prettier --write src/exports/types.ts src/exports/roo-code.d.ts`
2727

2828
if (fs.existsSync(path.join("..", "Roo-Code-Types"))) {
29-
fs.copyFileSync("src/exports/roo-code.d.ts", path.join("..", "Roo-Code-Types", "index.d.ts"))
29+
fs.copyFileSync("out/interface.js", path.join("..", "Roo-Code-Types", "src", "index.js"))
30+
fs.copyFileSync("out/interface.d.ts", path.join("..", "Roo-Code-Types", "src", "index.d.ts"))
3031
}
3132
}
3233

webview-ui/src/__tests__/utils/command-validation.test.ts

Lines changed: 0 additions & 158 deletions
This file was deleted.

webview-ui/src/utils/__tests__/command-validation.test.ts

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
/* eslint-disable no-useless-escape */
2+
/* eslint-disable no-template-curly-in-string */
3+
4+
// npx jest src/utils/__tests__/command-validation.test.ts
5+
16
import { parseCommand, isAllowedSingleCommand, validateCommand } from "../command-validation"
27

38
describe("Command Validation", () => {
@@ -119,3 +124,160 @@ describe("Command Validation", () => {
119124
})
120125
})
121126
})
127+
128+
/**
129+
* Tests for the command-validation module
130+
*
131+
* These tests include a reproduction of a bug where the shell-quote library
132+
* used in parseCommand throws an error when parsing commands that contain
133+
* the Bash $RANDOM variable in array indexing expressions.
134+
*
135+
* Error: "Bad substitution: levels[$RANDOM"
136+
*
137+
* The issue occurs specifically with complex expressions like:
138+
* ${levels[$RANDOM % ${#levels[@]}]}
139+
*/
140+
describe("command-validation", () => {
141+
describe("parseCommand", () => {
142+
it("should correctly parse simple commands", () => {
143+
const result = parseCommand("echo hello")
144+
expect(result).toEqual(["echo hello"])
145+
})
146+
147+
it("should correctly parse commands with chaining operators", () => {
148+
const result = parseCommand("echo hello && echo world")
149+
expect(result).toEqual(["echo hello", "echo world"])
150+
})
151+
152+
it("should correctly parse commands with quotes", () => {
153+
const result = parseCommand('echo "hello world"')
154+
expect(result).toEqual(['echo "hello world"'])
155+
})
156+
157+
it("should correctly parse commands with redirections", () => {
158+
const result = parseCommand("echo hello 2>&1")
159+
expect(result).toEqual(["echo hello 2>&1"])
160+
})
161+
162+
it("should correctly parse commands with subshells", () => {
163+
const result = parseCommand("echo $(date)")
164+
expect(result).toEqual(["echo", "date"])
165+
})
166+
167+
it("should not throw an error when parsing commands with simple array indexing", () => {
168+
// Simple array indexing works fine
169+
const commandWithArrayIndex = "value=${array[$index]}"
170+
171+
expect(() => {
172+
parseCommand(commandWithArrayIndex)
173+
}).not.toThrow()
174+
})
175+
176+
it("should not throw an error when parsing commands with $RANDOM in array index", () => {
177+
// This test reproduces the specific bug reported in the error
178+
const commandWithRandom = "level=${levels[$RANDOM % ${#levels[@]}]}"
179+
180+
expect(() => {
181+
parseCommand(commandWithRandom)
182+
}).not.toThrow()
183+
})
184+
185+
it("should not throw an error with simple $RANDOM variable", () => {
186+
// Simple $RANDOM usage works fine
187+
const commandWithRandom = "echo $RANDOM"
188+
189+
expect(() => {
190+
parseCommand(commandWithRandom)
191+
}).not.toThrow()
192+
})
193+
194+
it("should not throw an error with simple array indexing using $RANDOM", () => {
195+
// Simple array indexing with $RANDOM works fine
196+
const commandWithRandomIndex = "echo ${array[$RANDOM]}"
197+
198+
expect(() => {
199+
parseCommand(commandWithRandomIndex)
200+
}).not.toThrow()
201+
})
202+
203+
it("should not throw an error with complex array indexing using $RANDOM and arithmetic", () => {
204+
// This test reproduces the exact expression from the original error
205+
const commandWithComplexRandom = "echo ${levels[$RANDOM % ${#levels[@]}]}"
206+
207+
expect(() => {
208+
parseCommand(commandWithComplexRandom)
209+
}).not.toThrow("Bad substitution")
210+
})
211+
212+
it("should not throw an error when parsing the full log generator command", () => {
213+
// This is the exact command from the original error message
214+
const logGeneratorCommand = `while true; do \\
215+
levels=(INFO WARN ERROR DEBUG); \\
216+
msgs=("User logged in" "Connection timeout" "Processing request" "Cache miss" "Database query"); \\
217+
level=\${levels[$RANDOM % \${#levels[@]}]}; \\
218+
msg=\${msgs[$RANDOM % \${#msgs[@]}]}; \\
219+
echo "\$(date '+%Y-%m-%d %H:%M:%S') [$level] $msg"; \\
220+
sleep 1; \\
221+
done`
222+
223+
// This reproduces the original error
224+
expect(() => {
225+
parseCommand(logGeneratorCommand)
226+
}).not.toThrow("Bad substitution: levels[$RANDOM")
227+
})
228+
229+
it("should not throw an error when parsing just the problematic part", () => {
230+
// This isolates just the part mentioned in the error message
231+
const problematicPart = "level=${levels[$RANDOM"
232+
233+
expect(() => {
234+
parseCommand(problematicPart)
235+
}).not.toThrow("Bad substitution")
236+
})
237+
})
238+
239+
describe("validateCommand", () => {
240+
it("should validate allowed commands", () => {
241+
const result = validateCommand("echo hello", ["echo"])
242+
expect(result).toBe(true)
243+
})
244+
245+
it("should reject disallowed commands", () => {
246+
const result = validateCommand("rm -rf /", ["echo", "ls"])
247+
expect(result).toBe(false)
248+
})
249+
250+
it("should not fail validation for commands with simple $RANDOM variable", () => {
251+
const commandWithRandom = "echo $RANDOM"
252+
253+
expect(() => {
254+
validateCommand(commandWithRandom, ["echo"])
255+
}).not.toThrow()
256+
})
257+
258+
it("should not fail validation for commands with simple array indexing using $RANDOM", () => {
259+
const commandWithRandomIndex = "echo ${array[$RANDOM]}"
260+
261+
expect(() => {
262+
validateCommand(commandWithRandomIndex, ["echo"])
263+
}).not.toThrow()
264+
})
265+
266+
it("should return false for the full log generator command due to subshell detection", () => {
267+
// This is the exact command from the original error message
268+
const logGeneratorCommand = `while true; do \\
269+
levels=(INFO WARN ERROR DEBUG); \\
270+
msgs=("User logged in" "Connection timeout" "Processing request" "Cache miss" "Database query"); \\
271+
level=\${levels[$RANDOM % \${#levels[@]}]}; \\
272+
msg=\${msgs[$RANDOM % \${#msgs[@]}]}; \\
273+
echo "\$(date '+%Y-%m-%d %H:%M:%S') [$level] $msg"; \\
274+
sleep 1; \\
275+
done`
276+
277+
// validateCommand should return false due to subshell detection
278+
// without throwing an error
279+
const result = validateCommand(logGeneratorCommand, ["while"])
280+
expect(result).toBe(false)
281+
})
282+
})
283+
})

0 commit comments

Comments
 (0)