From 07785f02ef9d614d395afea4657268b9d2d0ed63 Mon Sep 17 00:00:00 2001 From: Shine Wang Date: Fri, 11 Nov 2016 15:02:22 -0800 Subject: [PATCH 1/3] Improved escaping and prettification of regexp replacements. --- .../transform-regexp-constructors-test.js | 24 ++++++++++++++++--- .../src/index.js | 18 ++++++++------ 2 files changed, 32 insertions(+), 10 deletions(-) diff --git a/packages/babel-plugin-transform-regexp-constructors/__tests__/transform-regexp-constructors-test.js b/packages/babel-plugin-transform-regexp-constructors/__tests__/transform-regexp-constructors-test.js index eb0744723..f2d331e0b 100644 --- a/packages/babel-plugin-transform-regexp-constructors/__tests__/transform-regexp-constructors-test.js +++ b/packages/babel-plugin-transform-regexp-constructors/__tests__/transform-regexp-constructors-test.js @@ -10,6 +10,18 @@ function transform(code) { } describe("transform-regexp-constructors-plugin", () => { + it("should work", () => { + const source = String.raw`var x = new RegExp('\\/');`; + const expected = String.raw`var x = /\//;`; + expect(transform(source)).toBe(expected); + }); + + it("should work 2", () => { + const source = String.raw`var x = new RegExp('\\n');`; + const expected = String.raw`var x = /\n/;`; + expect(transform(source)).toBe(expected); + }); + it("should transform RegExp constructors with string literals", () => { const source = "var x = new RegExp('ab+c');"; const expected = "var x = /ab+c/;"; @@ -60,9 +72,15 @@ const ret = /ab+c\\wd/g;`; expect(transform(source)).toBe(expected); }); - it("should escape invalid chars", () => { - const source = "var x = new RegExp('\\r\\n\\n/x/')"; - const expected = "var x = /\\r\\n\\n\\/x\\//;"; + it("should prettify special whitespaces", () => { + const source = String.raw`var x = new RegExp('\b\f\v\t\r\n\n');`; + const expected = String.raw`var x = /[\b]\f\v\t\r\n\n/;`; + expect(transform(source)).toBe(expected); + }); + + it("should escape forward slashes", () => { + const source = String.raw`var x = new RegExp('/x/');`; + const expected = String.raw`var x = /\/x\//;`; expect(transform(source)).toBe(expected); }); }); diff --git a/packages/babel-plugin-transform-regexp-constructors/src/index.js b/packages/babel-plugin-transform-regexp-constructors/src/index.js index 9c7453bc2..f20124932 100644 --- a/packages/babel-plugin-transform-regexp-constructors/src/index.js +++ b/packages/babel-plugin-transform-regexp-constructors/src/index.js @@ -8,8 +8,7 @@ module.exports = function({ types: t }) { if (!t.isIdentifier(path.node.callee, {name: "RegExp"})) { return; } - const evaluatedArgs = path.get("arguments") - .map((a) => a.evaluate()); + const evaluatedArgs = path.get("arguments").map((a) => a.evaluate()); if (!evaluatedArgs.every((a) => a.confident === true && typeof a.value === "string")) { return; @@ -22,11 +21,16 @@ module.exports = function({ types: t }) { evaluatedArgs[1].value : ""; - pattern = pattern - .replace(/\n/g, "\\n") - .replace(/\r/g, "\\r") - .replace(/\//g, "\\/"); - + pattern = new RegExp(pattern).source; + // This step is for prettification -- technically we can just match + // literal Unicode fine. e.g. '\t'.replace(/ /, '') === ''. + // This makes the output unecessarily bigger. + pattern = pattern.replace(/\n/g, "\\n") + .replace(/\t/g, "\\t") + .replace(/[\b]/g, "[\\b]") + .replace(/\v/g, "\\v") + .replace(/\f/g, "\\f") + .replace(/\r/g, "\\r"); path.replaceWith(t.regExpLiteral(pattern, flags)); } }, From 6f417e60be0ddba76be9e1105184163daeecffaf Mon Sep 17 00:00:00 2001 From: Shine Wang Date: Fri, 11 Nov 2016 15:11:55 -0800 Subject: [PATCH 2/3] Extended to RegExp calls. --- .../transform-regexp-constructors-test.js | 10 ++- .../src/index.js | 71 +++++++++++-------- 2 files changed, 51 insertions(+), 30 deletions(-) diff --git a/packages/babel-plugin-transform-regexp-constructors/__tests__/transform-regexp-constructors-test.js b/packages/babel-plugin-transform-regexp-constructors/__tests__/transform-regexp-constructors-test.js index f2d331e0b..8a23b65eb 100644 --- a/packages/babel-plugin-transform-regexp-constructors/__tests__/transform-regexp-constructors-test.js +++ b/packages/babel-plugin-transform-regexp-constructors/__tests__/transform-regexp-constructors-test.js @@ -10,13 +10,13 @@ function transform(code) { } describe("transform-regexp-constructors-plugin", () => { - it("should work", () => { + it("should not duplicate forward-slash escapes", () => { const source = String.raw`var x = new RegExp('\\/');`; const expected = String.raw`var x = /\//;`; expect(transform(source)).toBe(expected); }); - it("should work 2", () => { + it("should transform newlines fine", () => { const source = String.raw`var x = new RegExp('\\n');`; const expected = String.raw`var x = /\n/;`; expect(transform(source)).toBe(expected); @@ -28,6 +28,12 @@ describe("transform-regexp-constructors-plugin", () => { expect(transform(source)).toBe(expected); }); + it("should transform RegExp calls with string literals", () => { + const source = "var x = RegExp('ab+c');"; + const expected = "var x = /ab+c/;"; + expect(transform(source)).toBe(expected); + }); + it("should transform RegExp constructors with flags", () => { const source = "var x = new RegExp('ab+c', 'gimuy');"; const expected = "var x = /ab+c/gimuy;"; diff --git a/packages/babel-plugin-transform-regexp-constructors/src/index.js b/packages/babel-plugin-transform-regexp-constructors/src/index.js index f20124932..31f96cf3e 100644 --- a/packages/babel-plugin-transform-regexp-constructors/src/index.js +++ b/packages/babel-plugin-transform-regexp-constructors/src/index.js @@ -1,38 +1,53 @@ "use strict"; +function createRegExpLiteral(args, t) { + const evaluatedArgs = args.map((a) => a.evaluate()); + if (!evaluatedArgs.every((a) => a.confident === true && + typeof a.value === "string")) { + return; + } + let pattern = (evaluatedArgs.length >= 1 && + evaluatedArgs[0].value !== "") ? + evaluatedArgs[0].value : + "(?:)"; + const flags = evaluatedArgs.length >= 2 ? + evaluatedArgs[1].value : + ""; + + pattern = new RegExp(pattern).source; + // This step is for prettification -- technically we can insert whitespace + // literals into a regExpLiteral just fine. + // e.g. '\t'.replace(/ /, '') === '\t'.replace(/\t/, '') === ''. + pattern = pattern.replace(/\n/g, "\\n") + .replace(/\t/g, "\\t") + .replace(/[\b]/g, "[\\b]") + .replace(/\v/g, "\\v") + .replace(/\f/g, "\\f") + .replace(/\r/g, "\\r"); + return t.regExpLiteral(pattern, flags); +} + +function maybeReplaceWithRegExpLiteral(path, t) { + if (!t.isIdentifier(path.node.callee, {name: "RegExp"})) { + return; + } + const regExpLiteral = createRegExpLiteral(path.get("arguments"), t); + if (regExpLiteral) { + path.replaceWith(regExpLiteral); + } +} + module.exports = function({ types: t }) { return { name: "transform-regexp-constructors", visitor: { NewExpression(path) { - if (!t.isIdentifier(path.node.callee, {name: "RegExp"})) { - return; - } - const evaluatedArgs = path.get("arguments").map((a) => a.evaluate()); - if (!evaluatedArgs.every((a) => a.confident === true && - typeof a.value === "string")) { - return; - } - let pattern = (evaluatedArgs.length >= 1 && - evaluatedArgs[0].value !== "") ? - evaluatedArgs[0].value : - "(?:)"; - const flags = evaluatedArgs.length >= 2 ? - evaluatedArgs[1].value : - ""; - - pattern = new RegExp(pattern).source; - // This step is for prettification -- technically we can just match - // literal Unicode fine. e.g. '\t'.replace(/ /, '') === ''. - // This makes the output unecessarily bigger. - pattern = pattern.replace(/\n/g, "\\n") - .replace(/\t/g, "\\t") - .replace(/[\b]/g, "[\\b]") - .replace(/\v/g, "\\v") - .replace(/\f/g, "\\f") - .replace(/\r/g, "\\r"); - path.replaceWith(t.regExpLiteral(pattern, flags)); - } + maybeReplaceWithRegExpLiteral(path, t); + }, + CallExpression(path) { + // equivalent to `new RegExp()` according to ยง21.2.3 + maybeReplaceWithRegExpLiteral(path, t); + }, }, }; }; From 40e471119795d23eb675e9f553321e5524a7b7b1 Mon Sep 17 00:00:00 2001 From: Shine Wang Date: Fri, 11 Nov 2016 16:06:10 -0800 Subject: [PATCH 3/3] Removed tab --- .../src/index.js | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/packages/babel-plugin-transform-regexp-constructors/src/index.js b/packages/babel-plugin-transform-regexp-constructors/src/index.js index 31f96cf3e..bb0d2f743 100644 --- a/packages/babel-plugin-transform-regexp-constructors/src/index.js +++ b/packages/babel-plugin-transform-regexp-constructors/src/index.js @@ -1,6 +1,6 @@ "use strict"; -function createRegExpLiteral(args, t) { +function createRegExpLiteral(args, prettify, t) { const evaluatedArgs = args.map((a) => a.evaluate()); if (!evaluatedArgs.every((a) => a.confident === true && typeof a.value === "string")) { @@ -15,15 +15,13 @@ function createRegExpLiteral(args, t) { ""; pattern = new RegExp(pattern).source; - // This step is for prettification -- technically we can insert whitespace - // literals into a regExpLiteral just fine. - // e.g. '\t'.replace(/ /, '') === '\t'.replace(/\t/, '') === ''. - pattern = pattern.replace(/\n/g, "\\n") - .replace(/\t/g, "\\t") - .replace(/[\b]/g, "[\\b]") - .replace(/\v/g, "\\v") - .replace(/\f/g, "\\f") - .replace(/\r/g, "\\r"); + if (prettyify) { + pattern = pattern.replace(/\n/g, "\\n") + .replace(/[\b]/g, "[\\b]") + .replace(/\v/g, "\\v") + .replace(/\f/g, "\\f") + .replace(/\r/g, "\\r"); + } return t.regExpLiteral(pattern, flags); } @@ -31,7 +29,7 @@ function maybeReplaceWithRegExpLiteral(path, t) { if (!t.isIdentifier(path.node.callee, {name: "RegExp"})) { return; } - const regExpLiteral = createRegExpLiteral(path.get("arguments"), t); + const regExpLiteral = createRegExpLiteral(path.get("arguments"), true, t); if (regExpLiteral) { path.replaceWith(regExpLiteral); }