diff --git a/webview-ui/src/utils/__tests__/command-validation.spec.ts b/webview-ui/src/utils/__tests__/command-validation.spec.ts index e9265616822..ea776589b78 100644 --- a/webview-ui/src/utils/__tests__/command-validation.spec.ts +++ b/webview-ui/src/utils/__tests__/command-validation.spec.ts @@ -276,6 +276,41 @@ done` parseCommand(problematicPart) }).not.toThrow("Bad substitution") }) + + it("should not throw an error when parsing commands with BASH_REMATCH array syntax", () => { + // This test reproduces the bug reported in issue #5978 + const commandWithBashRematch = 'repo="${BASH_REMATCH[1]}"' + + expect(() => { + parseCommand(commandWithBashRematch) + }).not.toThrow("Bad substitution") + }) + + it("should not throw an error when parsing the full gh alias command from issue #5978", () => { + // This is the exact command from the issue that causes the crash + // Using a regular string to avoid template literal evaluation issues + const ghAliasCommand = + "gh alias set pr-reviews --clobber '!f() { \n" + + ' url="$1"\n' + + ' if [[ "$url" =~ github\\.com/([^/]+/[^/]+)/pull/([0-9]+) ]]; then\n' + + ' repo="${BASH_REMATCH[1]}"\n' + + ' pr="${BASH_REMATCH[2]}"\n' + + ' echo "=== Review Comments for $repo PR #$pr ==="\n' + + ' gh api "repos/$repo/pulls/$pr/reviews" --jq ".[] | {user: .user.login, state: .state, body: .body, submitted_at: .submitted_at}"\n' + + " echo\n" + + ' echo "=== Line Comments ==="\n' + + ' gh api "repos/$repo/pulls/$pr/comments" --jq ".[] | {user: .user.login, body: .body, path: .path, line: .line, created_at: .created_at}"\n' + + " else\n" + + ' echo "Error: Please provide a valid GitHub PR URL"\n' + + ' echo "Example: gh pr-reviews https://github.com/owner/repo/pull/123"\n' + + ' echo "Received: $url"\n' + + " fi\n" + + "}; f'" + + expect(() => { + parseCommand(ghAliasCommand) + }).not.toThrow() + }) }) describe("isAutoApprovedCommand (legacy behavior)", () => { diff --git a/webview-ui/src/utils/command-validation.ts b/webview-ui/src/utils/command-validation.ts index 1243cd1ee29..894dddd3b1a 100644 --- a/webview-ui/src/utils/command-validation.ts +++ b/webview-ui/src/utils/command-validation.ts @@ -83,8 +83,22 @@ export function parseCommand(command: string): string[] { return `__REDIR_${redirections.length - 1}__` }) - // Handle array indexing expressions: ${array[...]} pattern and partial expressions - processedCommand = processedCommand.replace(/\$\{[^}]*\[[^\]]*(\]([^}]*\})?)?/g, (match) => { + // Handle BASH_REMATCH specifically first to avoid shell-quote parsing issues + // Match ${BASH_REMATCH[n]} pattern + processedCommand = processedCommand.replace(/\$\{BASH_REMATCH\[[^\]]*\]\}/g, (match) => { + arrayIndexing.push(match) + return `__ARRAY_${arrayIndexing.length - 1}__` + }) + + // Also handle BASH_REMATCH without braces: $BASH_REMATCH[n] + processedCommand = processedCommand.replace(/\$BASH_REMATCH\[[^\]]*\]/g, (match) => { + arrayIndexing.push(match) + return `__ARRAY_${arrayIndexing.length - 1}__` + }) + + // Handle other array indexing expressions: ${array[...]} pattern and partial expressions + // This handles general array syntax but excludes BASH_REMATCH which was already handled + processedCommand = processedCommand.replace(/\$\{(?!BASH_REMATCH)[^}]*\[[^\]]*(\]([^}]*\})?)?/g, (match) => { arrayIndexing.push(match) return `__ARRAY_${arrayIndexing.length - 1}__` })