diff --git a/packages/csv-stringify/lib/api/index.js b/packages/csv-stringify/lib/api/index.js index 39de6053..75295232 100644 --- a/packages/csv-stringify/lib/api/index.js +++ b/packages/csv-stringify/lib/api/index.js @@ -253,15 +253,10 @@ const stringifier = function (options, state, info) { quotedString || quotedMatch; if (shouldQuote === true && containsEscape === true) { - const regexp = - escape === "\\" - ? new RegExp(escape + escape, "g") - : new RegExp(escape, "g"); - value = value.replace(regexp, escape + escape); + value = value.replaceAll(escape, () => escape + escape); } if (containsQuote === true) { - const regexp = new RegExp(quote, "g"); - value = value.replace(regexp, escape + quote); + value = value.replaceAll(quote, () => escape + quote); } if (shouldQuote === true) { value = quote + value + quote; diff --git a/packages/csv-stringify/test/option.escape.ts b/packages/csv-stringify/test/option.escape.ts index d84143b6..85398443 100644 --- a/packages/csv-stringify/test/option.escape.ts +++ b/packages/csv-stringify/test/option.escape.ts @@ -1,6 +1,7 @@ import "should"; import dedent from "dedent"; import { stringify } from "../lib/index.js"; +import { stringify as stringifySync } from "../lib/sync.js"; describe("Option `escape`", function () { it("default", function (next) { @@ -82,4 +83,29 @@ describe("Option `escape`", function () { }, ); }); + + it("regexp metacharacter escape is doubled literally (fix #494)", function () { + // The escape character was interpolated into `new RegExp(escape, "g")`. + // Metacharacters (| . * + ? ( [ { ^ $ ...) broke the doubling: "|" and + // "." matched everywhere, "*" threw "Nothing to repeat", "$" anchored. + // A field that must be quoted and contains the escape char must have every + // literal escape occurrence doubled, whatever the character. + // Before #494, the returned value was `"||a|||||b||,||c||"` + stringifySync([["a|b,c"]], { escape: "|", eof: false }).should.eql( + '"a||b,c"', + ); + // Before #494, the returned value was `..........` + stringifySync([["a.b,c"]], { escape: ".", eof: false }).should.eql( + '"a..b,c"', + ); + // Before #494, an error was thrown `Invalid regular expression: /*/g: Nothing to repeat` + stringifySync([["a*b,c"]], { escape: "*", eof: false }).should.eql( + '"a**b,c"', + ); + // "$" is also special in the replacement string, not only the pattern. + // Before #494, the returned value was `"a$b,c$"` + stringifySync([["a$b,c"]], { escape: "$", eof: false }).should.eql( + '"a$$b,c"', + ); + }); }); diff --git a/packages/csv-stringify/test/option.quote.ts b/packages/csv-stringify/test/option.quote.ts index 851b3150..ca5d52b4 100644 --- a/packages/csv-stringify/test/option.quote.ts +++ b/packages/csv-stringify/test/option.quote.ts @@ -1,6 +1,7 @@ import "should"; import dedent from "dedent"; import { stringify } from "../lib/index.js"; +import { stringify as stringifySync } from "../lib/sync.js"; describe("Option `quote`", function () { it("default", function (next) { @@ -225,4 +226,18 @@ describe("Option `quote`", function () { }, ); }); + + it("regex metacharacter quote is doubled literally (fix #494)", function () { + // See "Option `escape` - regexp metacharacter escape is doubled literally (fix #494)" + // Same class of bug on `new RegExp(quote, "g")` when the quote character is + // a regexp metacharacter and appears inside the field. + // Before #494, the returned value was `."."."..` + stringifySync([["a.b"]], { quote: ".", eof: false }).should.eql('.a".b.'); + // Before #494, the returned value was `|\|a\||\|b\||` + stringifySync([["a|b"]], { + quote: "|", + escape: "\\", + eof: false, + }).should.eql("|a\\|b|"); + }); });