From 33561b4e5b3e2caa4486e0dd0f966583a762c983 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timo=20K=C3=B6ssler?= Date: Thu, 27 Nov 2025 13:55:00 +0100 Subject: [PATCH 1/2] Use built-in RegExp.escape if available --- library/helpers/escapeStringRegexp.test.ts | 47 ++++++++++++++++++---- library/helpers/escapeStringRegexp.ts | 8 ++++ 2 files changed, 47 insertions(+), 8 deletions(-) diff --git a/library/helpers/escapeStringRegexp.test.ts b/library/helpers/escapeStringRegexp.test.ts index dd916fcf1..add41e180 100644 --- a/library/helpers/escapeStringRegexp.test.ts +++ b/library/helpers/escapeStringRegexp.test.ts @@ -1,17 +1,48 @@ import * as t from "tap"; import { escapeStringRegexp } from "./escapeStringRegexp"; -t.test("main", async (t) => { +const isUsingBuiltIn = typeof (globalThis as any).RegExp?.escape === "function"; + +if (!isUsingBuiltIn) { + t.test("main", async (t) => { + t.same( + escapeStringRegexp("\\ ^ $ * + ? . ( ) | { } [ ]"), + "\\\\ \\^ \\$ \\* \\+ \\? \\. \\( \\) \\| \\{ \\} \\[ \\]" + ); + t.same(escapeStringRegexp("hello world"), "hello world"); + }); + + t.test("escapes `-` in a way compatible with PCRE", async (t) => { + t.same(escapeStringRegexp("foo - bar"), "foo \\x2d bar"); + }); + + t.test("escapes `-` in a way compatible with the Unicode flag", async (t) => { + t.ok(new RegExp(escapeStringRegexp("-"), "u").test("-")); + }); +} else { t.same( escapeStringRegexp("\\ ^ $ * + ? . ( ) | { } [ ]"), - "\\\\ \\^ \\$ \\* \\+ \\? \\. \\( \\) \\| \\{ \\} \\[ \\]" + "\\\\\\x20\\^\\x20\\$\\x20\\*\\x20\\+\\x20\\?\\x20\\.\\x20\\(\\x20\\)\\x20\\|\\x20\\{\\x20\\}\\x20\\[\\x20\\]" ); -}); -t.test("escapes `-` in a way compatible with PCRE", async (t) => { - t.same(escapeStringRegexp("foo - bar"), "foo \\x2d bar"); -}); + t.test("escapes `-` in a way compatible with PCRE", async (t) => { + t.same(escapeStringRegexp("foo - bar"), "\\x66oo\\x20\\x2d\\x20bar"); + }); -t.test("escapes `-` in a way compatible with the Unicode flag", async (t) => { - t.ok(new RegExp(escapeStringRegexp("-"), "u").test("-")); + t.test("escapes `-` in a way compatible with the Unicode flag", async (t) => { + t.ok(new RegExp(escapeStringRegexp("-"), "u").test("-")); + }); +} + +t.test("escaped regex matches correctly", async (t) => { + t.ok(new RegExp(escapeStringRegexp("hello world")).test("hello world")); + t.ok( + new RegExp(escapeStringRegexp("hello world \\d")).test("hello world \\d") + ); + t.ok(new RegExp(escapeStringRegexp("hello\\sworld")).test("hello\\sworld")); + + t.notOk( + new RegExp(escapeStringRegexp("hello world \\d")).test("hello world 1") + ); + t.notOk(new RegExp(escapeStringRegexp("hello\\sworld")).test("hello sworld")); }); diff --git a/library/helpers/escapeStringRegexp.ts b/library/helpers/escapeStringRegexp.ts index 2e68ed0cb..430c4b1cf 100644 --- a/library/helpers/escapeStringRegexp.ts +++ b/library/helpers/escapeStringRegexp.ts @@ -6,5 +6,13 @@ * Taken from https://github.com/sindresorhus/escape-string-regexp/ */ export function escapeStringRegexp(string: string) { + // Use native implementation if available (Node.js 24+) + // A benchmark showed that it is up to 50% faster than the polyfill + // @ts-expect-error Outdated Node.js types + if (typeof RegExp?.escape === "function") { + // @ts-expect-error Outdated Node.js types + return RegExp.escape(string); + } + return string.replace(/[|\\{}()[\]^$+*?.]/g, "\\$&").replace(/-/g, "\\x2d"); } From 902cb2778b1f43f7ec47e0d91574b8a1d4be1bbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timo=20K=C3=B6ssler?= Date: Thu, 11 Dec 2025 09:27:31 +0100 Subject: [PATCH 2/2] Apply review suggestion --- library/helpers/escapeStringRegexp.ts | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/library/helpers/escapeStringRegexp.ts b/library/helpers/escapeStringRegexp.ts index 430c4b1cf..2c17da488 100644 --- a/library/helpers/escapeStringRegexp.ts +++ b/library/helpers/escapeStringRegexp.ts @@ -1,3 +1,5 @@ +let funcToExport: (string: string) => string; + /** * Escape characters with special meaning either inside or outside character sets. * @@ -5,14 +7,18 @@ * * Taken from https://github.com/sindresorhus/escape-string-regexp/ */ -export function escapeStringRegexp(string: string) { - // Use native implementation if available (Node.js 24+) - // A benchmark showed that it is up to 50% faster than the polyfill - // @ts-expect-error Outdated Node.js types - if (typeof RegExp?.escape === "function") { - // @ts-expect-error Outdated Node.js types - return RegExp.escape(string); - } - +function escapeStringRegexpFallback(string: string) { return string.replace(/[|\\{}()[\]^$+*?.]/g, "\\$&").replace(/-/g, "\\x2d"); } + +// Use native implementation if available (Node.js 24+) +// A benchmark showed that it is up to 50% faster than the polyfill +// @ts-expect-error Outdated Node.js types +if (typeof RegExp?.escape === "function") { + // @ts-expect-error Outdated Node.js types + funcToExport = RegExp.escape; +} else { + funcToExport = escapeStringRegexpFallback; +} + +export { funcToExport as escapeStringRegexp };