Skip to content

Commit 8ce389e

Browse files
authored
refactor: extract regex escape pattern into shared utility (#15676)
### What Extracted duplicate regex escape logic into a shared `escapeRegExp` utility. ### Why The same regex escape pattern `.replace(/[\\^$*+?.()|[\]{}]/g, '\\$&')` was duplicated across multiple locations. ### How - Created `packages/payload/src/utilities/escapeRegExp.ts` - Exported from payload package - Replaced 5 inline occurrences with utility calls
1 parent 71be0ca commit 8ce389e

File tree

5 files changed

+18
-8
lines changed

5 files changed

+18
-8
lines changed

packages/db-mongodb/src/queries/buildSearchParams.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import type { FilterQuery } from 'mongoose'
22
import type { FlattenedField, Operator, PathToQuery, Payload } from 'payload'
33

44
import { Types } from 'mongoose'
5-
import { APIError, getFieldByPath, getLocalizedPaths } from 'payload'
5+
import { APIError, escapeRegExp, getFieldByPath, getLocalizedPaths } from 'payload'
66
import { validOperatorSet } from 'payload/shared'
77

88
import type { MongooseAdapter } from '../index.js'
@@ -316,7 +316,7 @@ export async function buildSearchParam({
316316
$and: words.map((word) => ({
317317
[path]: {
318318
$options: 'i',
319-
$regex: word.replace(/[\\^$*+?.()|[\]{}]/g, '\\$&'),
319+
$regex: escapeRegExp(word),
320320
},
321321
})),
322322
},
@@ -334,7 +334,7 @@ export async function buildSearchParam({
334334
[path]: {
335335
$not: {
336336
$options: 'i',
337-
$regex: word.replace(/[\\^$*+?.()|[\]{}]/g, '\\$&'),
337+
$regex: escapeRegExp(word),
338338
},
339339
},
340340
})),

packages/db-mongodb/src/queries/sanitizeQueryValue.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import type {
88
} from 'payload'
99

1010
import { Types } from 'mongoose'
11-
import { createArrayFromCommaDelineated } from 'payload'
11+
import { createArrayFromCommaDelineated, escapeRegExp } from 'payload'
1212
import { fieldShouldBeLocalized } from 'payload/shared'
1313

1414
type SanitizeQueryValueArgs = {
@@ -458,7 +458,7 @@ export const sanitizeQueryValue = ({
458458
// For array fields, we need to use $elemMatch to search within array elements
459459
if (typeof formattedValue === 'string') {
460460
// Search for documents where any array element contains this string
461-
const escapedValue = formattedValue.replace(/[\\^$*+?.()|[\]{}]/g, '\\$&')
461+
const escapedValue = escapeRegExp(formattedValue)
462462
return {
463463
rawQuery: {
464464
[path]: {
@@ -474,7 +474,7 @@ export const sanitizeQueryValue = ({
474474
return {
475475
rawQuery: {
476476
$or: formattedValue.map((val) => {
477-
const escapedValue = String(val).replace(/[\\^$*+?.()|[\]{}]/g, '\\$&')
477+
const escapedValue = escapeRegExp(String(val))
478478
return {
479479
[path]: {
480480
$elemMatch: {
@@ -491,7 +491,7 @@ export const sanitizeQueryValue = ({
491491
// Regular (non-hasMany) text field
492492
formattedValue = {
493493
$options: 'i',
494-
$regex: formattedValue.replace(/[\\^$*+?.()|[\]{}]/g, '\\$&'),
494+
$regex: escapeRegExp(formattedValue),
495495
}
496496
}
497497
}

packages/payload/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1786,6 +1786,7 @@ export {
17861786
} from './utilities/dependencies/dependencyChecker.js'
17871787
export { getDependencies } from './utilities/dependencies/getDependencies.js'
17881788
export { dynamicImport } from './utilities/dynamicImport.js'
1789+
export { escapeRegExp } from './utilities/escapeRegExp.js'
17891790
export {
17901791
findUp,
17911792
findUpSync,
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
/**
2+
* Escapes special characters in a string for use in a regular expression
3+
* @param string - The string to escape
4+
* @returns The escaped string safe for use in RegExp
5+
*/
6+
export const escapeRegExp = (string: string): string =>
7+
string.replace(/[\\^$*+?.()|[\]{}]/g, '\\$&')

packages/payload/src/utilities/wordBoundariesRegex.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
1+
import { escapeRegExp } from './escapeRegExp.js'
2+
13
export const wordBoundariesRegex = (input: string): RegExp => {
24
const words = input.split(' ')
35

46
// Regex word boundaries that work for cyrillic characters - https://stackoverflow.com/a/47062016/1717697
57
const wordBoundaryBefore = '(?:(?:[^\\p{L}\\p{N}])|^)' // Converted to a non-matching group instead of positive lookbehind for Safari
68
const wordBoundaryAfter = '(?=[^\\p{L}\\p{N}]|$)'
79
const regex = words.reduce((pattern, word, i) => {
8-
const escapedWord = word.replace(/[\\^$*+?.()|[\]{}]/g, '\\$&')
10+
const escapedWord = escapeRegExp(word)
911
return `${pattern}(?=.*${wordBoundaryBefore}.*${escapedWord}.*${wordBoundaryAfter})${
1012
i + 1 === words.length ? '.+' : ''
1113
}`

0 commit comments

Comments
 (0)