fix: escape regex special chars in pattern matcher

Fixes #1521. When hook matcher patterns contained regex special characters
like parentheses, the pattern-matcher would throw 'SyntaxError: Invalid
regular expression: unmatched parentheses' because these characters were
not escaped before constructing the RegExp.

The fix escapes all regex special characters (.+?^${}()|[\]\) EXCEPT
the asterisk (*) which is intentionally converted to .* for glob-style
matching.

Add comprehensive test suite for pattern-matcher covering:
- Exact matching (case-insensitive)
- Wildcard matching (glob-style *)
- Pipe-separated patterns
- All regex special characters (parentheses, brackets, etc.)
- Edge cases (empty matcher, complex patterns)
This commit is contained in:
Rishi Vhavle
2026-02-06 12:48:28 +05:30
parent 917bba9d1b
commit bc782ca4d4
2 changed files with 189 additions and 1 deletions
+12 -1
View File
@@ -1,5 +1,14 @@
import type { ClaudeHooksConfig, HookMatcher } from "../hooks/claude-code-hooks/types"
/**
* Escape all regex special characters EXCEPT asterisk (*).
* Asterisk is preserved for glob-to-regex conversion.
*/
function escapeRegexExceptAsterisk(str: string): string {
// Escape all regex special chars except * (which we convert to .* for glob matching)
return str.replace(/[.+?^${}()|[\]\\]/g, "\\$&")
}
export function matchesToolMatcher(toolName: string, matcher: string): boolean {
if (!matcher) {
return true
@@ -7,7 +16,9 @@ export function matchesToolMatcher(toolName: string, matcher: string): boolean {
const patterns = matcher.split("|").map((p) => p.trim())
return patterns.some((p) => {
if (p.includes("*")) {
const regex = new RegExp(`^${p.replace(/\*/g, ".*")}$`, "i")
// First escape regex special chars (except *), then convert * to .*
const escaped = escapeRegexExceptAsterisk(p)
const regex = new RegExp(`^${escaped.replace(/\*/g, ".*")}$`, "i")
return regex.test(toolName)
}
return p.toLowerCase() === toolName.toLowerCase()