Skip to content

Commit 4b257fc

Browse files
roomotehannesrudolph
authored andcommitted
refactor: simplify command pattern parser using shell-quote library
- Replace custom parsing logic with shell-quote library - Implement simplified extractPatterns and processCommand functions - Maintain all existing test compatibility - Remove dependency on parseCommand from command-validation
1 parent 0a25464 commit 4b257fc

File tree

1 file changed

+44
-41
lines changed

1 file changed

+44
-41
lines changed

webview-ui/src/utils/commandPatterns.ts

Lines changed: 44 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { parseCommand } from "./command-validation"
1+
import { parse } from "shell-quote"
22

33
export interface CommandPattern {
44
pattern: string
@@ -10,60 +10,63 @@ export interface SecurityWarning {
1010
message: string
1111
}
1212

13-
export function extractCommandPatterns(command: string): string[] {
14-
if (!command?.trim()) return []
13+
function processCommand(cmd: string[], patterns: Set<string>): void {
14+
if (!cmd.length || typeof cmd[0] !== "string") return
1515

16-
const patterns = new Set<string>()
16+
const mainCmd = cmd[0]
1717

18-
// First, check if the command contains subshells and remove them
19-
// This is important for security - we don't want to extract patterns from subshell contents
20-
const cleanedCommand = command
21-
.replace(/\$\([^)]*\)/g, "") // Remove $() subshells
22-
.replace(/`[^`]*`/g, "") // Remove backtick subshells
18+
// Skip if it's just a number (like "0" from "0 total")
19+
if (/^\d+$/.test(mainCmd)) return
20+
21+
// Skip common output patterns that aren't commands
22+
const skipWords = ["total", "error", "warning", "failed", "success", "done"]
23+
if (skipWords.includes(mainCmd.toLowerCase())) return
24+
25+
patterns.add(mainCmd)
2326

24-
// Use parseCommand to split the cleaned command into sub-commands
25-
// This ensures consistent parsing behavior with command-validation
26-
const subCommands = parseCommand(cleanedCommand)
27+
const breakingExps = [/^-/, /[\\/.~]/]
2728

28-
// Process each sub-command to extract patterns
29-
for (const subCommand of subCommands) {
30-
// Skip empty commands
31-
if (!subCommand.trim()) continue
29+
for (let i = 1; i < cmd.length; i++) {
30+
const arg = cmd[i]
3231

33-
// Split the command into tokens
34-
const tokens = subCommand.trim().split(/\s+/)
32+
if (typeof arg !== "string" || breakingExps.some((re) => re.test(arg))) break
3533

36-
if (tokens.length === 0) continue
34+
const pattern = cmd.slice(0, i + 1).join(" ")
35+
patterns.add(pattern)
36+
}
37+
}
3738

38-
const mainCmd = tokens[0]
39+
function extractPatterns(cmdStr: string): Set<string> {
40+
const patterns = new Set<string>()
3941

40-
// Skip if it's just a number (like "0" from "0 total")
41-
if (/^\d+$/.test(mainCmd)) continue
42+
const parsed = parse(cmdStr)
4243

43-
// Skip common output patterns that aren't commands
44-
const skipWords = ["total", "error", "warning", "failed", "success", "done"]
45-
if (skipWords.includes(mainCmd.toLowerCase())) continue
44+
const commandSeparators = new Set(["|", "&&", "||", ";"])
45+
let current: string[] = []
46+
for (const token of parsed) {
47+
if (typeof token === "object" && "op" in token && commandSeparators.has(token.op)) {
48+
if (current.length) processCommand(current, patterns)
49+
current = []
50+
} else {
51+
current.push(String(token))
52+
}
53+
}
4654

47-
// Only add if it contains at least one letter or is a valid path
48-
if (/[a-zA-Z]/.test(mainCmd) || mainCmd.includes("/")) {
49-
patterns.add(mainCmd)
55+
if (current.length) processCommand(current, patterns)
5056

51-
// Build up patterns progressively (e.g., "npm", "npm install", "npm install express")
52-
// Stop at flags or special characters
53-
const stopPatterns = [/^-/, /[\\/.~]/]
57+
return patterns
58+
}
5459

55-
for (let i = 1; i < tokens.length; i++) {
56-
const token = tokens[i]
60+
export function extractCommandPatterns(command: string): string[] {
61+
if (!command?.trim()) return []
5762

58-
// Stop if we hit a flag or special character
59-
if (stopPatterns.some((re) => re.test(token))) break
63+
// First, check if the command contains subshells and remove them
64+
// This is important for security - we don't want to extract patterns from subshell contents
65+
const cleanedCommand = command
66+
.replace(/\$\([^)]*\)/g, "") // Remove $() subshells
67+
.replace(/`[^`]*`/g, "") // Remove backtick subshells
6068

61-
// Build the pattern up to this point
62-
const pattern = tokens.slice(0, i + 1).join(" ")
63-
patterns.add(pattern)
64-
}
65-
}
66-
}
69+
const patterns = extractPatterns(cleanedCommand)
6770

6871
return Array.from(patterns).sort()
6972
}

0 commit comments

Comments
 (0)