From 7d5befa9c00bd214a933223816d50792d3ede475 Mon Sep 17 00:00:00 2001 From: greymoth-jp <246701683+greymoth-jp@users.noreply.github.com> Date: Mon, 29 Jun 2026 17:16:56 +0900 Subject: [PATCH] fix: support RegExp flags added after es2017 in clone and patch cloneRegExp and the trivial patch filter both re-parse the output of RegExp.prototype.toString() with a `[gimyu]` flag character class. That class predates the `s` (es2018), `d` (es2022) and `v` (es2024) flags, so clone() throws "Invalid RegExp" on any RegExp that uses one of them, and diffing then patching such a RegExp silently turns it back into a string. Widen both classes to `[dgimsuvy]` so every currently valid flag is kept. --- packages/jsondiffpatch/src/clone.ts | 2 +- packages/jsondiffpatch/src/filters/trivial.ts | 2 +- packages/jsondiffpatch/test/examples/diffpatch.ts | 12 ++++++++++++ packages/jsondiffpatch/test/index.spec.ts | 10 ++++++++++ 4 files changed, 24 insertions(+), 2 deletions(-) diff --git a/packages/jsondiffpatch/src/clone.ts b/packages/jsondiffpatch/src/clone.ts index cf6d3310..004b1bc1 100644 --- a/packages/jsondiffpatch/src/clone.ts +++ b/packages/jsondiffpatch/src/clone.ts @@ -1,5 +1,5 @@ function cloneRegExp(re: RegExp) { - const regexMatch = /^\/(.*)\/([gimyu]*)$/.exec(re.toString()); + const regexMatch = /^\/(.*)\/([dgimsuvy]*)$/.exec(re.toString()); if (!regexMatch) { throw new Error("Invalid RegExp"); } diff --git a/packages/jsondiffpatch/src/filters/trivial.ts b/packages/jsondiffpatch/src/filters/trivial.ts index 47041c0e..34f576a6 100644 --- a/packages/jsondiffpatch/src/filters/trivial.ts +++ b/packages/jsondiffpatch/src/filters/trivial.ts @@ -88,7 +88,7 @@ export const patchFilter: Filter = } if (nonNestedDelta.length === 2) { if (context.left instanceof RegExp) { - const regexArgs = /^\/(.*)\/([gimyu]+)$/.exec( + const regexArgs = /^\/(.*)\/([dgimsuvy]+)$/.exec( nonNestedDelta[1] as string, ); if (regexArgs?.[1]) { diff --git a/packages/jsondiffpatch/test/examples/diffpatch.ts b/packages/jsondiffpatch/test/examples/diffpatch.ts index 1e5e4211..317ebe6f 100644 --- a/packages/jsondiffpatch/test/examples/diffpatch.ts +++ b/packages/jsondiffpatch/test/examples/diffpatch.ts @@ -17,6 +17,11 @@ type ExampleGroup = Example[]; const exampleDate = () => new Date(2020, 10, 30, 15, 10, 3); +// Built via the constructor rather than a `/…/s` literal so these compile under +// the repo's es6 target (the `s` flag postdates es6 and a literal would error). +const regExpWithFlags = (source: string, flags: string) => + new RegExp(source, flags); + const atomicValues: ExampleGroup = [ // undefined { @@ -464,6 +469,13 @@ const atomicValues: ExampleGroup = [ delta: ["/regex/g", "/another regex/gi"], reverse: ["/another regex/gi", "/regex/g"], }, + { + name: "RegExp -> RegExp (dotAll flag)", + left: regExpWithFlags("regex", "s"), + right: regExpWithFlags("another regex", "s"), + delta: ["/regex/s", "/another regex/s"], + reverse: ["/another regex/s", "/regex/s"], + }, // object { diff --git a/packages/jsondiffpatch/test/index.spec.ts b/packages/jsondiffpatch/test/index.spec.ts index 48392288..aca5b8f1 100644 --- a/packages/jsondiffpatch/test/index.spec.ts +++ b/packages/jsondiffpatch/test/index.spec.ts @@ -137,6 +137,16 @@ describe("DiffPatcher", () => { pattern: /expr/gim, }); }); + it("clones RegExp with flags added after es2017 (s, d, v)", () => { + // `s` (es2018), `d` (es2022) and `v` (es2024) all postdate the repo's + // es6 target, so build via the constructor with a non-literal flag + // (a `/…/s` literal would not compile). + for (const flag of ["s", "d", "v"]) { + const pattern = new RegExp("expr", flag); + const cloned = jsondiffpatch.clone({ pattern }); + expect(cloned).toEqual({ pattern }); + } + }); }); describe("using cloneDiffValues", () => {