export type SearchMatcherOptions = { matchCase?: boolean matchWholeWord?: boolean useRegularExpression?: boolean } export type CompileStringMatcherResult = { matcher: (content: string) => boolean isValid: boolean } export const escapeRegex = (value: string) => { return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') } export const buildRegex = (pattern: string, flags = '') => { try { return new RegExp(pattern, flags) } catch { return null } } export const compileStringMatcher = ( query: string, options: SearchMatcherOptions = {}, ): CompileStringMatcherResult => { if (!query) return { matcher: () => true, isValid: true } const matchCase = options.matchCase ?? false const matchWholeWord = options.matchWholeWord ?? false const useRegularExpression = options.useRegularExpression ?? false const flags = matchCase ? '' : 'i' if (useRegularExpression) { const regex = buildRegex(query, flags) if (!regex) return { matcher: () => false, isValid: false } return { matcher: (content: string) => regex.test(content), isValid: true } } if (matchWholeWord) { const regex = buildRegex(`\\b${escapeRegex(query)}\\b`, flags) if (!regex) return { matcher: () => false, isValid: false } return { matcher: (content: string) => regex.test(content), isValid: true } } if (matchCase) { return { matcher: (content: string) => content.includes(query), isValid: true, } } const target = query.toLowerCase() return { matcher: (content: string) => content.toLowerCase().includes(target), isValid: true, } }