From 2509f7c320adeed44cd5ab0f849c604b18b85d25 Mon Sep 17 00:00:00 2001 From: Shine Wang Date: Mon, 17 Oct 2016 17:26:48 -0700 Subject: [PATCH 01/10] Implemented transform-void-to-nothing plugin. Removes the 'void 0' rval for let-/var-assignments. --- .../.npmignore | 4 + .../README.md | 59 ++++++++++++ ...lugin-remove-undefined-if-possible-test.js | 78 +++++++++++++++ .../src/index.js | 96 +++++++++++++++++++ 4 files changed, 237 insertions(+) create mode 100644 packages/babel-plugin-remove-undefined-if-possible/.npmignore create mode 100644 packages/babel-plugin-remove-undefined-if-possible/README.md create mode 100644 packages/babel-plugin-remove-undefined-if-possible/__tests__/babel-plugin-remove-undefined-if-possible-test.js create mode 100644 packages/babel-plugin-remove-undefined-if-possible/src/index.js diff --git a/packages/babel-plugin-remove-undefined-if-possible/.npmignore b/packages/babel-plugin-remove-undefined-if-possible/.npmignore new file mode 100644 index 000000000..22250660e --- /dev/null +++ b/packages/babel-plugin-remove-undefined-if-possible/.npmignore @@ -0,0 +1,4 @@ +src +__tests__ +node_modules +*.log diff --git a/packages/babel-plugin-remove-undefined-if-possible/README.md b/packages/babel-plugin-remove-undefined-if-possible/README.md new file mode 100644 index 000000000..16a79ab9c --- /dev/null +++ b/packages/babel-plugin-remove-undefined-if-possible/README.md @@ -0,0 +1,59 @@ +# babel-plugin-remove-undefined-if-possible + +For variable assignments, this removes rvals that evaluate to `undefined` +(`var`s in functions only). +For functions, this removes return arguments that evaluate to `undefined`. + +## Example + +**In** + +```javascript +let a = void 0; +function foo() { + var b = undefined; + return undefined; +} +``` + +**Out** + +```javascript +let a; +function foo() { + var b; + return; +} +``` + +## Installation + +```sh +$ npm install babel-plugin-remove-undefined-if-possible +``` + +## Usage + +### Via `.babelrc` (Recommended) + +**.babelrc** + +```json +{ + "plugins": ["babel-plugin-remove-undefined-if-possible"] +} +``` + +### Via CLI + +```sh +$ babel --plugins babel-plugin-remove-undefined-if-possible script.js +``` + +### Via Node API + +```javascript +require("babel-core").transform("code", { + plugins: ["babel-plugin-remove-undefined-if-possible"] +}); +``` diff --git a/packages/babel-plugin-remove-undefined-if-possible/__tests__/babel-plugin-remove-undefined-if-possible-test.js b/packages/babel-plugin-remove-undefined-if-possible/__tests__/babel-plugin-remove-undefined-if-possible-test.js new file mode 100644 index 000000000..2e0edfe18 --- /dev/null +++ b/packages/babel-plugin-remove-undefined-if-possible/__tests__/babel-plugin-remove-undefined-if-possible-test.js @@ -0,0 +1,78 @@ +jest.autoMockOff(); + +const babel = require("babel-core"); +const plugin = require("../src/index"); + +function transform(code) { + return babel.transform(code, { + plugins: [plugin], + }).code; +} + +describe("remove-undefined-if-possible-plugin", () => { + it("should remove multiple undefined assignments", () => { + const source = "let a = undefined, b = 3, c = undefined, d;"; + const expected = "let a,\n b = 3,\n c,\n d;"; + expect(transform(source)).toBe(expected); + }); + + it("should remove let-assignments to undefined", () => { + const source = "let a = undefined;"; + const expected = "let a;"; + expect(transform(source)).toBe(expected); + }); + + it("should remove let-assignments to void 0", () => { + const source = "let a = void 0;"; + const expected = "let a;"; + expect(transform(source)).toBe(expected); + }); + + it("should remove undefined return value", () => { + const source = ` +function foo() { + const a = undefined; + return undefined; +}`; + const expected = ` +function foo() { + const a = undefined; + return; +}`; + expect(transform(source)).toBe(expected); + }); + + it("should remove var declarations in functions", () => { + const source = ` +function foo() { + var a = undefined; +}`; + const expected = ` +function foo() { + var a; +}`; + expect(transform(source)).toBe(expected); + }); + + it("should not remove const-assignments to undefined", () => { + const source = "const a = undefined;"; + expect(transform(source)).toBe(source); + }); + + it("should not remove var-assignments in loops", () => { + const source = ` +for (var a = undefined;;) { + var b = undefined; +}`; + expect(transform(source)).toBe(source); + }); + + it("should not remove var-assignments if referenced before", () => { + const source = ` +function foo() { + a = 3; + var a = undefined; +}`; + expect(transform(source)).toBe(source); + }); +}); diff --git a/packages/babel-plugin-remove-undefined-if-possible/src/index.js b/packages/babel-plugin-remove-undefined-if-possible/src/index.js new file mode 100644 index 000000000..52e9d57ad --- /dev/null +++ b/packages/babel-plugin-remove-undefined-if-possible/src/index.js @@ -0,0 +1,96 @@ +"use strict"; + +function isInLocalLoop(path, t) { + if (path === null) { + return false; + } else if (t.isLoop(path)) { + return true; + } else if (t.isFunction(path)) { + return false; + } else { + return isInLocalLoop(path.parentPath, t); + } +} + +function removeRvalIfUndefined(declaratorPath) { + const rval = declaratorPath.get("init") + .evaluate(); + if (rval.confident === true && rval.value === undefined) { + declaratorPath.node.init = null; + } +} + +function isAnyLvalReferencedBefore(declaratorPath, seenIds, t) { + const id = declaratorPath.get("id"); + if (t.isIdentifier(id)) { + return seenIds.has(id.node.name); + } + let hasReference = false; + id.traverse({ + Identifier(path) { + if (seenIds.has(path.node.name)) { + hasReference = true; + } + } + }); + return hasReference; +} + +function removeUndefinedAssignments(functionPath, t) { + const seenIds = new Set(); + functionPath.traverse({ + Function(path) { + path.skip(); + }, + Identifier(path) { + // potential optimization: only store ids that are lvals of assignments. + seenIds.add(path.node.name); + }, + VariableDeclaration(path) { + switch (path.node.kind) { + case "const": + break; + case "let": + path.node.declarations.forEach((declarator, index) => { + const declaratorPath = path.get("declarations")[index]; + removeRvalIfUndefined(declaratorPath); + }); + break; + case "var": + if (!t.isFunction(functionPath)) { + break; + } + if (isInLocalLoop(path.parentPath, t)) { + break; + } + path.node.declarations.forEach((declarator, index) => { + const declaratorPath = path.get("declarations")[index]; + if (!isAnyLvalReferencedBefore(declaratorPath, seenIds, t)) { + removeRvalIfUndefined(declaratorPath); + } + }); + break; + } + }, + }); +} + +module.exports = function({ types: t }) { + return { + name: "remove-undefined-if-possible", + visitor: { + FunctionParent(path) { + removeUndefinedAssignments(path, t); + }, + ReturnStatement(path) { + if (path.node.argument !== null) { + const rval = path.get("argument") + .evaluate(); + if (rval.confident === true && rval.value === undefined) { + path.node.argument = null; + } + } + }, + }, + }; +}; From 9202a2956593bc86f9e3287e67d032abaa839064 Mon Sep 17 00:00:00 2001 From: Shine Wang Date: Thu, 20 Oct 2016 12:09:59 -0700 Subject: [PATCH 02/10] addressed PR comments - used t.getBindingIdentifiers - changed from forEach to for-loop - added tests --- ...lugin-remove-undefined-if-possible-test.js | 92 +++++++++++++------ .../src/index.js | 40 +++----- 2 files changed, 80 insertions(+), 52 deletions(-) diff --git a/packages/babel-plugin-remove-undefined-if-possible/__tests__/babel-plugin-remove-undefined-if-possible-test.js b/packages/babel-plugin-remove-undefined-if-possible/__tests__/babel-plugin-remove-undefined-if-possible-test.js index 2e0edfe18..16b5da46d 100644 --- a/packages/babel-plugin-remove-undefined-if-possible/__tests__/babel-plugin-remove-undefined-if-possible-test.js +++ b/packages/babel-plugin-remove-undefined-if-possible/__tests__/babel-plugin-remove-undefined-if-possible-test.js @@ -2,6 +2,7 @@ jest.autoMockOff(); const babel = require("babel-core"); const plugin = require("../src/index"); +const unpad = require("../../../utils/unpad"); function transform(code) { return babel.transform(code, { @@ -29,28 +30,56 @@ describe("remove-undefined-if-possible-plugin", () => { }); it("should remove undefined return value", () => { - const source = ` -function foo() { - const a = undefined; - return undefined; -}`; - const expected = ` -function foo() { - const a = undefined; - return; -}`; + const source = unpad(` + function foo() { + const a = undefined; + return undefined; + }`); + const expected = unpad(` + function foo() { + const a = undefined; + return; + }`); expect(transform(source)).toBe(expected); }); it("should remove var declarations in functions", () => { - const source = ` -function foo() { - var a = undefined; -}`; - const expected = ` -function foo() { - var a; -}`; + const source = unpad(` + function foo() { + var a = undefined; + }`); + const expected = unpad(` + function foo() { + var a; + }`); + expect(transform(source)).toBe(expected); + }); + + it("should remove nested var-assignments if not referenced before", () => { + const source = unpad(` + function foo() { + a = 3; + var { a: aa, b: bb } = undefined; + }`); + const expected = unpad(` + function foo() { + a = 3; + var { a: aa, b: bb }; + }`); + expect(transform(source)).toBe(expected); + }); + + it("should remove let-assignments in inner blocks", () => { + const source = unpad(` + let a = 1; + { + let a = undefined; + }`); + const expected = unpad(` + let a = 1; + { + let a; + }`); expect(transform(source)).toBe(expected); }); @@ -60,19 +89,28 @@ function foo() { }); it("should not remove var-assignments in loops", () => { - const source = ` -for (var a = undefined;;) { - var b = undefined; -}`; + const source = unpad(` + for (var a = undefined;;) { + var b = undefined; + }`); expect(transform(source)).toBe(source); }); it("should not remove var-assignments if referenced before", () => { - const source = ` -function foo() { - a = 3; - var a = undefined; -}`; + const source = unpad(` + function foo() { + a = 3; + var a = undefined; + }`); + expect(transform(source)).toBe(source); + }); + + it("should not remove nested var-assignments if referenced before", () => { + const source = unpad(` + function foo() { + aa = 3; + var { a: aa, b: bb } = undefined; + }`); expect(transform(source)).toBe(source); }); }); diff --git a/packages/babel-plugin-remove-undefined-if-possible/src/index.js b/packages/babel-plugin-remove-undefined-if-possible/src/index.js index 52e9d57ad..1a054f3c3 100644 --- a/packages/babel-plugin-remove-undefined-if-possible/src/index.js +++ b/packages/babel-plugin-remove-undefined-if-possible/src/index.js @@ -21,19 +21,14 @@ function removeRvalIfUndefined(declaratorPath) { } function isAnyLvalReferencedBefore(declaratorPath, seenIds, t) { - const id = declaratorPath.get("id"); - if (t.isIdentifier(id)) { - return seenIds.has(id.node.name); - } - let hasReference = false; - id.traverse({ - Identifier(path) { - if (seenIds.has(path.node.name)) { - hasReference = true; - } + const id = declaratorPath.node.id; + const ids = t.getBindingIdentifiers(id); + for (const name in ids) { + if (seenIds.has(name)) { + return true; } - }); - return hasReference; + } + return false; } function removeUndefinedAssignments(functionPath, t) { @@ -51,24 +46,19 @@ function removeUndefinedAssignments(functionPath, t) { case "const": break; case "let": - path.node.declarations.forEach((declarator, index) => { - const declaratorPath = path.get("declarations")[index]; - removeRvalIfUndefined(declaratorPath); - }); + for (const declarator of path.get("declarations")) { + removeRvalIfUndefined(declarator); + } break; case "var": - if (!t.isFunction(functionPath)) { - break; - } - if (isInLocalLoop(path.parentPath, t)) { + if (!t.isFunction(functionPath) || isInLocalLoop(path.parentPath, t)) { break; } - path.node.declarations.forEach((declarator, index) => { - const declaratorPath = path.get("declarations")[index]; - if (!isAnyLvalReferencedBefore(declaratorPath, seenIds, t)) { - removeRvalIfUndefined(declaratorPath); + for (const declarator of path.get("declarations")) { + if (!isAnyLvalReferencedBefore(declarator, seenIds, t)) { + removeRvalIfUndefined(declarator); } - }); + } break; } }, From 0f57a441975c0c2c697c1abc1246b8e6043f3530 Mon Sep 17 00:00:00 2001 From: Shine Wang Date: Tue, 25 Oct 2016 14:51:32 -0700 Subject: [PATCH 03/10] Optimized visitor to be O(N) in the number of AST nodes instead of O(N^2). --- .../src/index.js | 125 ++++++++++-------- 1 file changed, 73 insertions(+), 52 deletions(-) diff --git a/packages/babel-plugin-remove-undefined-if-possible/src/index.js b/packages/babel-plugin-remove-undefined-if-possible/src/index.js index 1a054f3c3..a445204b2 100644 --- a/packages/babel-plugin-remove-undefined-if-possible/src/index.js +++ b/packages/babel-plugin-remove-undefined-if-possible/src/index.js @@ -1,17 +1,5 @@ "use strict"; -function isInLocalLoop(path, t) { - if (path === null) { - return false; - } else if (t.isLoop(path)) { - return true; - } else if (t.isFunction(path)) { - return false; - } else { - return isInLocalLoop(path.parentPath, t); - } -} - function removeRvalIfUndefined(declaratorPath) { const rval = declaratorPath.get("init") .evaluate(); @@ -20,57 +8,55 @@ function removeRvalIfUndefined(declaratorPath) { } } -function isAnyLvalReferencedBefore(declaratorPath, seenIds, t) { +function isAnyLvalReferencedBefore(declaratorPath, seenNames, t) { const id = declaratorPath.node.id; - const ids = t.getBindingIdentifiers(id); - for (const name in ids) { - if (seenIds.has(name)) { + const names = t.getBindingIdentifiers(id); + for (const name in names) { + if (seenNames.has(name)) { return true; } } return false; } -function removeUndefinedAssignments(functionPath, t) { - const seenIds = new Set(); - functionPath.traverse({ - Function(path) { - path.skip(); - }, - Identifier(path) { - // potential optimization: only store ids that are lvals of assignments. - seenIds.add(path.node.name); - }, - VariableDeclaration(path) { - switch (path.node.kind) { - case "const": - break; - case "let": - for (const declarator of path.get("declarations")) { - removeRvalIfUndefined(declarator); - } - break; - case "var": - if (!t.isFunction(functionPath) || isInLocalLoop(path.parentPath, t)) { - break; - } - for (const declarator of path.get("declarations")) { - if (!isAnyLvalReferencedBefore(declarator, seenIds, t)) { - removeRvalIfUndefined(declarator); - } - } - break; - } - }, - }); -} - module.exports = function({ types: t }) { + let functionStack = null; + let functionLoopStack = null; + let functionToNamesMap = null; return { name: "remove-undefined-if-possible", visitor: { - FunctionParent(path) { - removeUndefinedAssignments(path, t); + Program: { + enter(path) { + functionStack = []; + functionLoopStack = []; + functionToNamesMap = new Map(); + }, + exit(path) { + functionStack = null; + functionLoopStack = null; + functionToNamesMap = null; + }, + }, + Function: { + enter(path) { + functionStack.push(path); + functionLoopStack.push(path); + functionToNamesMap.set(path, new Set()); + }, + exit(path) { + functionStack.pop(); + functionLoopStack.pop(); + functionToNamesMap.delete(path); + }, + }, + Loop: { + enter(path) { + functionLoopStack.push(path); + }, + exit(path) { + functionLoopStack.pop(); + }, }, ReturnStatement(path) { if (path.node.argument !== null) { @@ -81,6 +67,41 @@ module.exports = function({ types: t }) { } } }, + Identifier(path) { + // potential optimization: only store ids that are lvals, instead of all + // ids + if (functionStack.length > 0) { + functionToNamesMap.get(functionStack[functionStack.length - 1]) + .add(path.node.name); + } + }, + VariableDeclaration(path) { + switch (path.node.kind) { + case "const": + break; + case "let": + for (const declarator of path.get("declarations")) { + removeRvalIfUndefined(declarator); + } + break; + case "var": + if (functionLoopStack.length === 0) { + // ignore global variables + break; + } + const fnOrLoop = functionLoopStack[functionLoopStack.length - 1]; + if (t.isLoop(fnOrLoop)) { + break; + } + for (const declarator of path.get("declarations")) { + if (!isAnyLvalReferencedBefore(declarator, + functionToNamesMap.get(fnOrLoop), t)) { + removeRvalIfUndefined(declarator); + } + } + break; + } + }, }, }; }; From 735e9f076db9a199c31a9a1922fa8479545fe41e Mon Sep 17 00:00:00 2001 From: Shine Wang Date: Tue, 25 Oct 2016 15:21:50 -0700 Subject: [PATCH 04/10] Lint and added package.json. --- .../package.json | 16 ++++++++++++++++ .../src/index.js | 6 +++--- packages/babel-preset-babili/package.json | 1 + packages/babel-preset-babili/src/index.js | 1 + 4 files changed, 21 insertions(+), 3 deletions(-) create mode 100644 packages/babel-plugin-remove-undefined-if-possible/package.json diff --git a/packages/babel-plugin-remove-undefined-if-possible/package.json b/packages/babel-plugin-remove-undefined-if-possible/package.json new file mode 100644 index 000000000..616edb51d --- /dev/null +++ b/packages/babel-plugin-remove-undefined-if-possible/package.json @@ -0,0 +1,16 @@ +{ + "name": "babel-plugin-remove-undefined-if-possible", + "version": "0.0.1", + "description": "This removes rvals that are equivalent to undefined wherever possible", + "homepage": "https://github.com/babel/babili#readme", + "repository": "https://github.com/babel/babili/tree/master/packages/babel-plugin-remove-undefined-if-possible", + "bugs": "https://github.com/babel/babili/issues", + "author": "shinew", + "license": "MIT", + "main": "lib/index.js", + "keywords": [ + "babel-plugin" + ], + "dependencies": {}, + "devDependencies": {} +} diff --git a/packages/babel-plugin-remove-undefined-if-possible/src/index.js b/packages/babel-plugin-remove-undefined-if-possible/src/index.js index a445204b2..e1c7666b4 100644 --- a/packages/babel-plugin-remove-undefined-if-possible/src/index.js +++ b/packages/babel-plugin-remove-undefined-if-possible/src/index.js @@ -27,12 +27,12 @@ module.exports = function({ types: t }) { name: "remove-undefined-if-possible", visitor: { Program: { - enter(path) { + enter() { functionStack = []; functionLoopStack = []; functionToNamesMap = new Map(); }, - exit(path) { + exit() { functionStack = null; functionLoopStack = null; functionToNamesMap = null; @@ -54,7 +54,7 @@ module.exports = function({ types: t }) { enter(path) { functionLoopStack.push(path); }, - exit(path) { + exit() { functionLoopStack.pop(); }, }, diff --git a/packages/babel-preset-babili/package.json b/packages/babel-preset-babili/package.json index 7259642c9..0bd4798a9 100644 --- a/packages/babel-preset-babili/package.json +++ b/packages/babel-preset-babili/package.json @@ -21,6 +21,7 @@ "babel-plugin-minify-replace": "^0.0.1", "babel-plugin-minify-simplify": "^0.0.3", "babel-plugin-minify-type-constructors": "^0.0.1", + "babel-plugin-remove-undefined-if-possible": "^0.0.1", "babel-plugin-transform-member-expression-literals": "^6.8.0", "babel-plugin-transform-merge-sibling-variables": "^6.8.0", "babel-plugin-transform-minify-booleans": "^6.8.0", diff --git a/packages/babel-preset-babili/src/index.js b/packages/babel-preset-babili/src/index.js index a07007230..918d55558 100644 --- a/packages/babel-preset-babili/src/index.js +++ b/packages/babel-preset-babili/src/index.js @@ -10,6 +10,7 @@ module.exports = { require("babel-plugin-minify-replace"), require("babel-plugin-minify-simplify"), require("babel-plugin-minify-type-constructors"), + require("babel-plugin-remove-undefined-if-possible"), require("babel-plugin-transform-member-expression-literals"), require("babel-plugin-transform-merge-sibling-variables"), require("babel-plugin-transform-minify-booleans"), From 93931265a84edcd1aa3b503d9dccd427df4650e4 Mon Sep 17 00:00:00 2001 From: Shine Wang Date: Tue, 25 Oct 2016 16:43:16 -0700 Subject: [PATCH 05/10] Bug fix: should capture inner functions' usages of var-declared identifiers too. --- .../src/index.js | 61 ++++++------------- 1 file changed, 18 insertions(+), 43 deletions(-) diff --git a/packages/babel-plugin-remove-undefined-if-possible/src/index.js b/packages/babel-plugin-remove-undefined-if-possible/src/index.js index e1c7666b4..5940508a0 100644 --- a/packages/babel-plugin-remove-undefined-if-possible/src/index.js +++ b/packages/babel-plugin-remove-undefined-if-possible/src/index.js @@ -8,56 +8,44 @@ function removeRvalIfUndefined(declaratorPath) { } } -function isAnyLvalReferencedBefore(declaratorPath, seenNames, t) { +function areAllBindingsNotSeen(declaratorPath, seenNames, t) { const id = declaratorPath.node.id; const names = t.getBindingIdentifiers(id); for (const name in names) { if (seenNames.has(name)) { - return true; + return false; } } - return false; + return true; } module.exports = function({ types: t }) { - let functionStack = null; - let functionLoopStack = null; - let functionToNamesMap = null; + let names = null; + let functionNesting = 0; return { name: "remove-undefined-if-possible", visitor: { Program: { enter() { - functionStack = []; - functionLoopStack = []; - functionToNamesMap = new Map(); + names = new Set(); + functionNesting = 0; }, exit() { - functionStack = null; - functionLoopStack = null; - functionToNamesMap = null; + names = null; + functionNesting = 0; }, }, Function: { - enter(path) { - functionStack.push(path); - functionLoopStack.push(path); - functionToNamesMap.set(path, new Set()); - }, - exit(path) { - functionStack.pop(); - functionLoopStack.pop(); - functionToNamesMap.delete(path); - }, - }, - Loop: { - enter(path) { - functionLoopStack.push(path); + enter() { + functionNesting++; }, exit() { - functionLoopStack.pop(); + functionNesting--; }, }, + Identifier(path) { + names.add(path.node.name); + }, ReturnStatement(path) { if (path.node.argument !== null) { const rval = path.get("argument") @@ -67,14 +55,6 @@ module.exports = function({ types: t }) { } } }, - Identifier(path) { - // potential optimization: only store ids that are lvals, instead of all - // ids - if (functionStack.length > 0) { - functionToNamesMap.get(functionStack[functionStack.length - 1]) - .add(path.node.name); - } - }, VariableDeclaration(path) { switch (path.node.kind) { case "const": @@ -85,17 +65,12 @@ module.exports = function({ types: t }) { } break; case "var": - if (functionLoopStack.length === 0) { - // ignore global variables - break; - } - const fnOrLoop = functionLoopStack[functionLoopStack.length - 1]; - if (t.isLoop(fnOrLoop)) { + if (functionNesting === 0) { + // ignore global vars break; } for (const declarator of path.get("declarations")) { - if (!isAnyLvalReferencedBefore(declarator, - functionToNamesMap.get(fnOrLoop), t)) { + if (areAllBindingsNotSeen(declarator, names, t)) { removeRvalIfUndefined(declarator); } } From 5e2d216eeedd06d035ec7e79b31b964605fa11e1 Mon Sep 17 00:00:00 2001 From: Shine Wang Date: Wed, 26 Oct 2016 11:08:58 -0700 Subject: [PATCH 06/10] Renamed babel-plugin-remove-undefined-if-possible -> babel-plugin-transform-remove-undefined. --- .../.npmignore | 0 .../README.md | 0 .../__tests__/babel-plugin-remove-undefined-if-possible-test.js | 0 .../package.json | 0 .../src/index.js | 0 packages/babel-preset-babili/package.json | 2 +- packages/babel-preset-babili/src/index.js | 2 +- 7 files changed, 2 insertions(+), 2 deletions(-) rename packages/{babel-plugin-remove-undefined-if-possible => babel-plugin-transform-remove-undefined}/.npmignore (100%) rename packages/{babel-plugin-remove-undefined-if-possible => babel-plugin-transform-remove-undefined}/README.md (100%) rename packages/{babel-plugin-remove-undefined-if-possible => babel-plugin-transform-remove-undefined}/__tests__/babel-plugin-remove-undefined-if-possible-test.js (100%) rename packages/{babel-plugin-remove-undefined-if-possible => babel-plugin-transform-remove-undefined}/package.json (100%) rename packages/{babel-plugin-remove-undefined-if-possible => babel-plugin-transform-remove-undefined}/src/index.js (100%) diff --git a/packages/babel-plugin-remove-undefined-if-possible/.npmignore b/packages/babel-plugin-transform-remove-undefined/.npmignore similarity index 100% rename from packages/babel-plugin-remove-undefined-if-possible/.npmignore rename to packages/babel-plugin-transform-remove-undefined/.npmignore diff --git a/packages/babel-plugin-remove-undefined-if-possible/README.md b/packages/babel-plugin-transform-remove-undefined/README.md similarity index 100% rename from packages/babel-plugin-remove-undefined-if-possible/README.md rename to packages/babel-plugin-transform-remove-undefined/README.md diff --git a/packages/babel-plugin-remove-undefined-if-possible/__tests__/babel-plugin-remove-undefined-if-possible-test.js b/packages/babel-plugin-transform-remove-undefined/__tests__/babel-plugin-remove-undefined-if-possible-test.js similarity index 100% rename from packages/babel-plugin-remove-undefined-if-possible/__tests__/babel-plugin-remove-undefined-if-possible-test.js rename to packages/babel-plugin-transform-remove-undefined/__tests__/babel-plugin-remove-undefined-if-possible-test.js diff --git a/packages/babel-plugin-remove-undefined-if-possible/package.json b/packages/babel-plugin-transform-remove-undefined/package.json similarity index 100% rename from packages/babel-plugin-remove-undefined-if-possible/package.json rename to packages/babel-plugin-transform-remove-undefined/package.json diff --git a/packages/babel-plugin-remove-undefined-if-possible/src/index.js b/packages/babel-plugin-transform-remove-undefined/src/index.js similarity index 100% rename from packages/babel-plugin-remove-undefined-if-possible/src/index.js rename to packages/babel-plugin-transform-remove-undefined/src/index.js diff --git a/packages/babel-preset-babili/package.json b/packages/babel-preset-babili/package.json index 0bd4798a9..f3fc9565e 100644 --- a/packages/babel-preset-babili/package.json +++ b/packages/babel-preset-babili/package.json @@ -21,12 +21,12 @@ "babel-plugin-minify-replace": "^0.0.1", "babel-plugin-minify-simplify": "^0.0.3", "babel-plugin-minify-type-constructors": "^0.0.1", - "babel-plugin-remove-undefined-if-possible": "^0.0.1", "babel-plugin-transform-member-expression-literals": "^6.8.0", "babel-plugin-transform-merge-sibling-variables": "^6.8.0", "babel-plugin-transform-minify-booleans": "^6.8.0", "babel-plugin-transform-property-literals": "^6.8.0", "babel-plugin-transform-regexp-constructors": "^0.0.1", + "babel-plugin-transform-remove-undefined": "^0.0.1", "babel-plugin-transform-simplify-comparison-operators": "^6.8.0", "babel-plugin-transform-undefined-to-void": "^6.8.0" }, diff --git a/packages/babel-preset-babili/src/index.js b/packages/babel-preset-babili/src/index.js index 918d55558..dc72470ed 100644 --- a/packages/babel-preset-babili/src/index.js +++ b/packages/babel-preset-babili/src/index.js @@ -10,12 +10,12 @@ module.exports = { require("babel-plugin-minify-replace"), require("babel-plugin-minify-simplify"), require("babel-plugin-minify-type-constructors"), - require("babel-plugin-remove-undefined-if-possible"), require("babel-plugin-transform-member-expression-literals"), require("babel-plugin-transform-merge-sibling-variables"), require("babel-plugin-transform-minify-booleans"), require("babel-plugin-transform-property-literals"), require("babel-plugin-transform-regexp-constructors"), + require("babel-plugin-transform-remove-undefined"), require("babel-plugin-transform-simplify-comparison-operators"), require("babel-plugin-transform-undefined-to-void"), ], From 06c4195d6c5e96c5412cda4a77c7db07ea29402f Mon Sep 17 00:00:00 2001 From: Justin Ridgewell Date: Thu, 27 Oct 2016 01:37:53 -0400 Subject: [PATCH 07/10] Track using violations There's no way we can visit every identifier. Instead, figure out which constant violations hamper the minification. Oh, and some test cases. Those should probably be cleaned up. :grin: --- ...lugin-remove-undefined-if-possible-test.js | 84 ++++++++++++++++-- .../src/index.js | 85 +++++++++++-------- 2 files changed, 126 insertions(+), 43 deletions(-) diff --git a/packages/babel-plugin-transform-remove-undefined/__tests__/babel-plugin-remove-undefined-if-possible-test.js b/packages/babel-plugin-transform-remove-undefined/__tests__/babel-plugin-remove-undefined-if-possible-test.js index 16b5da46d..2ea73afbf 100644 --- a/packages/babel-plugin-transform-remove-undefined/__tests__/babel-plugin-remove-undefined-if-possible-test.js +++ b/packages/babel-plugin-transform-remove-undefined/__tests__/babel-plugin-remove-undefined-if-possible-test.js @@ -55,18 +55,13 @@ describe("remove-undefined-if-possible-plugin", () => { expect(transform(source)).toBe(expected); }); - it("should remove nested var-assignments if not referenced before", () => { + it("idk what this is", () => { const source = unpad(` function foo() { a = 3; var { a: aa, b: bb } = undefined; }`); - const expected = unpad(` - function foo() { - a = 3; - var { a: aa, b: bb }; - }`); - expect(transform(source)).toBe(expected); + expect(transform(source)).toBe(source); }); it("should remove let-assignments in inner blocks", () => { @@ -88,11 +83,25 @@ describe("remove-undefined-if-possible-plugin", () => { expect(transform(source)).toBe(source); }); - it("should not remove var-assignments in loops", () => { + it("should remove var-assignments in loops", () => { const source = unpad(` for (var a = undefined;;) { var b = undefined; }`); + const expected = unpad(` + for (var a;;) { + var b; + }`); + expect(transform(source)).toBe(expected); + }); + + it("should not remove var-assignments in loops 2", () => { + const source = unpad(` + for (var a;;) { + var b = undefined; + console.log(b); + b = 3; + }`); expect(transform(source)).toBe(source); }); @@ -113,4 +122,63 @@ describe("remove-undefined-if-possible-plugin", () => { }`); expect(transform(source)).toBe(source); }); + + it("should not remove ...", () => { + const source = unpad(` + function foo() { + bar(); + var x = undefined; + console.log(x); + function bar() { + x = 3; + } + }`); + expect(transform(source)).toBe(source); + }); + + it("should remove ...", () => { + const source = unpad(` + function foo() { + var x = undefined; + bar(); + console.log(x); + function bar() { + x = 3; + } + }`); + const expected = unpad(` + function foo() { + var x; + bar(); + console.log(x); + function bar() { + x = 3; + } + }`); + expect(transform(source)).toBe(expected); + }); + + it("should remove ...", () => { + const source = unpad(` + foo(); + function foo() { + var x = undefined; + bar(); + console.log(x); + function bar() { + x = 3; + } + }`); + const expected = unpad(` + foo(); + function foo() { + var x; + bar(); + console.log(x); + function bar() { + x = 3; + } + }`); + expect(transform(source)).toBe(expected); + }); }); diff --git a/packages/babel-plugin-transform-remove-undefined/src/index.js b/packages/babel-plugin-transform-remove-undefined/src/index.js index 5940508a0..bf0761f31 100644 --- a/packages/babel-plugin-transform-remove-undefined/src/index.js +++ b/packages/babel-plugin-transform-remove-undefined/src/index.js @@ -8,15 +8,16 @@ function removeRvalIfUndefined(declaratorPath) { } } -function areAllBindingsNotSeen(declaratorPath, seenNames, t) { - const id = declaratorPath.node.id; - const names = t.getBindingIdentifiers(id); - for (const name in names) { - if (seenNames.has(name)) { - return false; - } - } - return true; +function getLoopParent(path, scopeParent) { + const parent = path.findParent((p) => p.isLoop() || p === scopeParent); + // don't traverse higher than the function the var is defined in. + return parent === scopeParent ? null : parent; +} + +function getFunctionParent(path, scopeParent) { + const parent = path.findParent((p) => p.isFunction()); + // don't traverse higher than the function the var is defined in. + return parent === scopeParent ? null : parent; } module.exports = function({ types: t }) { @@ -25,27 +26,6 @@ module.exports = function({ types: t }) { return { name: "remove-undefined-if-possible", visitor: { - Program: { - enter() { - names = new Set(); - functionNesting = 0; - }, - exit() { - names = null; - functionNesting = 0; - }, - }, - Function: { - enter() { - functionNesting++; - }, - exit() { - functionNesting--; - }, - }, - Identifier(path) { - names.add(path.node.name); - }, ReturnStatement(path) { if (path.node.argument !== null) { const rval = path.get("argument") @@ -55,6 +35,7 @@ module.exports = function({ types: t }) { } } }, + VariableDeclaration(path) { switch (path.node.kind) { case "const": @@ -65,12 +46,46 @@ module.exports = function({ types: t }) { } break; case "var": - if (functionNesting === 0) { - // ignore global vars - break; - } + const { node, scope } = path; for (const declarator of path.get("declarations")) { - if (areAllBindingsNotSeen(declarator, names, t)) { + const binding = scope.getBinding(declarator.node.id.name); + if (!binding) { + continue; + } + + const { start } = node; + const scopeParent = declarator.getFunctionParent(); + + const violation = binding.constantViolations.some(v => { + const violationStart = v.node.start; + if (violationStart < start) { + return true; + } + + for (let func = getFunctionParent(v, scopeParent); func; func = getFunctionParent(func, scopeParent)) { + const id = func.node.id; + const binding = id && scope.getBinding(id.name); + + if (!binding) { + continue; + } + + const funcViolation = binding.referencePaths.some((p) => { + return p.node.start < start; + }); + if (funcViolation) { + return true; + } + } + + for (let loop = getLoopParent(declarator, scopeParent); loop; loop = getLoopParent(loop, scopeParent)) { + if (loop.node.end > violationStart) { + return true; + } + } + }); + + if (!violation) { removeRvalIfUndefined(declarator); } } From 712922aa5e86a3338fb98f57aa3b4cf4c30a83e6 Mon Sep 17 00:00:00 2001 From: Justin Ridgewell Date: Fri, 28 Oct 2016 20:55:45 -0400 Subject: [PATCH 08/10] Find the function's function's references --- .../src/index.js | 34 ++++++++++++------- 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/packages/babel-plugin-transform-remove-undefined/src/index.js b/packages/babel-plugin-transform-remove-undefined/src/index.js index bf0761f31..5b8de2097 100644 --- a/packages/babel-plugin-transform-remove-undefined/src/index.js +++ b/packages/babel-plugin-transform-remove-undefined/src/index.js @@ -20,6 +20,25 @@ function getFunctionParent(path, scopeParent) { return parent === scopeParent ? null : parent; } +function getFunctionReferences(path, scopeParent, references = []) { + for (let func = getFunctionParent(path, scopeParent); func; func = getFunctionParent(func, scopeParent)) { + const id = func.node.id; + const binding = id && func.scope.getBinding(id.name); + + if (!binding) { + continue; + } + + binding.referencePaths.forEach((path) => { + if (references.indexOf(path) == -1) { + references.push(path); + getFunctionReferences(path, scopeParent, references); + } + }); + } + return references; +} + module.exports = function({ types: t }) { let names = null; let functionNesting = 0; @@ -62,18 +81,9 @@ module.exports = function({ types: t }) { return true; } - for (let func = getFunctionParent(v, scopeParent); func; func = getFunctionParent(func, scopeParent)) { - const id = func.node.id; - const binding = id && scope.getBinding(id.name); - - if (!binding) { - continue; - } - - const funcViolation = binding.referencePaths.some((p) => { - return p.node.start < start; - }); - if (funcViolation) { + var references = getFunctionReferences(v, scopeParent); + for (let ref of references) { + if (ref.node.start < start) { return true; } } From 580c6777c36fbc7f785bf050f90bc4416b9850c0 Mon Sep 17 00:00:00 2001 From: Shine Wang Date: Mon, 31 Oct 2016 11:56:33 -0700 Subject: [PATCH 09/10] Fixed many things: - purity-check of rvals - added unit tests - refactoring --- .../README.md | 10 +- ...plugin-transform-remove-undefined-test.js} | 40 +++++-- .../package.json | 4 +- .../src/index.js | 107 ++++++++++-------- 4 files changed, 95 insertions(+), 66 deletions(-) rename packages/babel-plugin-transform-remove-undefined/__tests__/{babel-plugin-remove-undefined-if-possible-test.js => babel-plugin-transform-remove-undefined-test.js} (86%) diff --git a/packages/babel-plugin-transform-remove-undefined/README.md b/packages/babel-plugin-transform-remove-undefined/README.md index 16a79ab9c..781812ce9 100644 --- a/packages/babel-plugin-transform-remove-undefined/README.md +++ b/packages/babel-plugin-transform-remove-undefined/README.md @@ -1,4 +1,4 @@ -# babel-plugin-remove-undefined-if-possible +# babel-plugin-transform-remove-undefined For variable assignments, this removes rvals that evaluate to `undefined` (`var`s in functions only). @@ -29,7 +29,7 @@ function foo() { ## Installation ```sh -$ npm install babel-plugin-remove-undefined-if-possible +$ npm install babel-plugin-transform-remove-undefined ``` ## Usage @@ -40,20 +40,20 @@ $ npm install babel-plugin-remove-undefined-if-possible ```json { - "plugins": ["babel-plugin-remove-undefined-if-possible"] + "plugins": ["babel-plugin-transform-remove-undefined"] } ``` ### Via CLI ```sh -$ babel --plugins babel-plugin-remove-undefined-if-possible script.js +$ babel --plugins babel-plugin-transform-remove-undefined script.js ``` ### Via Node API ```javascript require("babel-core").transform("code", { - plugins: ["babel-plugin-remove-undefined-if-possible"] + plugins: ["babel-plugin-transform-remove-undefined"] }); ``` diff --git a/packages/babel-plugin-transform-remove-undefined/__tests__/babel-plugin-remove-undefined-if-possible-test.js b/packages/babel-plugin-transform-remove-undefined/__tests__/babel-plugin-transform-remove-undefined-test.js similarity index 86% rename from packages/babel-plugin-transform-remove-undefined/__tests__/babel-plugin-remove-undefined-if-possible-test.js rename to packages/babel-plugin-transform-remove-undefined/__tests__/babel-plugin-transform-remove-undefined-test.js index 2ea73afbf..14ccc12a7 100644 --- a/packages/babel-plugin-transform-remove-undefined/__tests__/babel-plugin-remove-undefined-if-possible-test.js +++ b/packages/babel-plugin-transform-remove-undefined/__tests__/babel-plugin-transform-remove-undefined-test.js @@ -10,7 +10,7 @@ function transform(code) { }).code; } -describe("remove-undefined-if-possible-plugin", () => { +describe("transform-remove-undefined-plugin", () => { it("should remove multiple undefined assignments", () => { const source = "let a = undefined, b = 3, c = undefined, d;"; const expected = "let a,\n b = 3,\n c,\n d;"; @@ -55,15 +55,6 @@ describe("remove-undefined-if-possible-plugin", () => { expect(transform(source)).toBe(expected); }); - it("idk what this is", () => { - const source = unpad(` - function foo() { - a = 3; - var { a: aa, b: bb } = undefined; - }`); - expect(transform(source)).toBe(source); - }); - it("should remove let-assignments in inner blocks", () => { const source = unpad(` let a = 1; @@ -123,7 +114,7 @@ describe("remove-undefined-if-possible-plugin", () => { expect(transform(source)).toBe(source); }); - it("should not remove ...", () => { + it("should not remove if call to constant violation function", () => { const source = unpad(` function foo() { bar(); @@ -136,7 +127,7 @@ describe("remove-undefined-if-possible-plugin", () => { expect(transform(source)).toBe(source); }); - it("should remove ...", () => { + it("should remove if not referenced in any way before", () => { const source = unpad(` function foo() { var x = undefined; @@ -181,4 +172,29 @@ describe("remove-undefined-if-possible-plugin", () => { }`); expect(transform(source)).toBe(expected); }); + + it("should not remove nor break on mutually recursive function", () => { + const source = unpad(` + function foo() { + a(); + var c = undefined; + function a() { + b(); + } + function b() { + a(); + c = 3; + } + }`); + expect(transform(source)).toBe(source); + }); + + it("should not remove if rval has side effects", () => { + const source = unpad(` + function foo() { + var a = void b(); + return void bar(); + }`); + expect(transform(source)).toBe(source); + }); }); diff --git a/packages/babel-plugin-transform-remove-undefined/package.json b/packages/babel-plugin-transform-remove-undefined/package.json index 616edb51d..310aeeb0e 100644 --- a/packages/babel-plugin-transform-remove-undefined/package.json +++ b/packages/babel-plugin-transform-remove-undefined/package.json @@ -1,9 +1,9 @@ { - "name": "babel-plugin-remove-undefined-if-possible", + "name": "babel-plugin-transform-remove-undefined", "version": "0.0.1", "description": "This removes rvals that are equivalent to undefined wherever possible", "homepage": "https://github.com/babel/babili#readme", - "repository": "https://github.com/babel/babili/tree/master/packages/babel-plugin-remove-undefined-if-possible", + "repository": "https://github.com/babel/babili/tree/master/packages/babel-plugin-transform-remove-undefined", "bugs": "https://github.com/babel/babili/issues", "author": "shinew", "license": "MIT", diff --git a/packages/babel-plugin-transform-remove-undefined/src/index.js b/packages/babel-plugin-transform-remove-undefined/src/index.js index 5b8de2097..262dad84d 100644 --- a/packages/babel-plugin-transform-remove-undefined/src/index.js +++ b/packages/babel-plugin-transform-remove-undefined/src/index.js @@ -1,11 +1,15 @@ "use strict"; -function removeRvalIfUndefined(declaratorPath) { - const rval = declaratorPath.get("init") - .evaluate(); - if (rval.confident === true && rval.value === undefined) { - declaratorPath.node.init = null; +function isPureAndUndefined(rval) { + if (rval.isIdentifier() && + rval.node.name === "undefined") { + return true; } + if (!rval.isPure()) { + return false; + } + const evaluation = rval.evaluate(); + return evaluation.confident === true && evaluation.value === undefined; } function getLoopParent(path, scopeParent) { @@ -20,7 +24,7 @@ function getFunctionParent(path, scopeParent) { return parent === scopeParent ? null : parent; } -function getFunctionReferences(path, scopeParent, references = []) { +function getFunctionReferences(path, scopeParent, references = new Set()) { for (let func = getFunctionParent(path, scopeParent); func; func = getFunctionParent(func, scopeParent)) { const id = func.node.id; const binding = id && func.scope.getBinding(id.name); @@ -30,8 +34,8 @@ function getFunctionReferences(path, scopeParent, references = []) { } binding.referencePaths.forEach((path) => { - if (references.indexOf(path) == -1) { - references.push(path); + if (!references.has(path)) { + references.add(path); getFunctionReferences(path, scopeParent, references); } }); @@ -39,17 +43,46 @@ function getFunctionReferences(path, scopeParent, references = []) { return references; } -module.exports = function({ types: t }) { - let names = null; - let functionNesting = 0; +function hasViolation(declarator, scope, start) { + const binding = scope.getBinding(declarator.node.id.name); + if (!binding) { + return true; + } + + const scopeParent = declarator.getFunctionParent(); + + const violation = binding.constantViolations.some((v) => { + // return 'true' if we cannot guarantee the violation references + // the initialized identifier after + const violationStart = v.node.start; + if (violationStart === undefined || violationStart < start) { + return true; + } + + const references = getFunctionReferences(v, scopeParent); + for (const ref of references) { + if (ref.node.start === undefined || ref.node.start < start) { + return true; + } + } + + for (let loop = getLoopParent(declarator, scopeParent); loop; loop = getLoopParent(loop, scopeParent)) { + if (loop.node.end === undefined || loop.node.end > violationStart) { + return true; + } + } + }); + + return violation; +} + +module.exports = function() { return { - name: "remove-undefined-if-possible", + name: "transform-remove-undefined", visitor: { ReturnStatement(path) { if (path.node.argument !== null) { - const rval = path.get("argument") - .evaluate(); - if (rval.confident === true && rval.value === undefined) { + if (isPureAndUndefined(path.get("argument"))) { path.node.argument = null; } } @@ -61,42 +94,22 @@ module.exports = function({ types: t }) { break; case "let": for (const declarator of path.get("declarations")) { - removeRvalIfUndefined(declarator); + if (isPureAndUndefined(declarator.get("init"))) { + declarator.node.init = null; + } } break; case "var": - const { node, scope } = path; + const start = path.node.start; + if (start === undefined) { + // This is common for plugin-generated nodes + break; + } + const scope = path.scope; for (const declarator of path.get("declarations")) { - const binding = scope.getBinding(declarator.node.id.name); - if (!binding) { - continue; - } - - const { start } = node; - const scopeParent = declarator.getFunctionParent(); - - const violation = binding.constantViolations.some(v => { - const violationStart = v.node.start; - if (violationStart < start) { - return true; - } - - var references = getFunctionReferences(v, scopeParent); - for (let ref of references) { - if (ref.node.start < start) { - return true; - } - } - - for (let loop = getLoopParent(declarator, scopeParent); loop; loop = getLoopParent(loop, scopeParent)) { - if (loop.node.end > violationStart) { - return true; - } - } - }); - - if (!violation) { - removeRvalIfUndefined(declarator); + if (isPureAndUndefined(declarator.get("init")) && + !hasViolation(declarator, scope, start)) { + declarator.node.init = null; } } break; From 42a941424cce6f49cffe4130dd7588fabff7d4f3 Mon Sep 17 00:00:00 2001 From: Shine Wang Date: Thu, 3 Nov 2016 20:27:13 -0700 Subject: [PATCH 10/10] renamed tests --- ...-plugin-transform-remove-undefined-test.js | 46 ++++--------------- 1 file changed, 10 insertions(+), 36 deletions(-) diff --git a/packages/babel-plugin-transform-remove-undefined/__tests__/babel-plugin-transform-remove-undefined-test.js b/packages/babel-plugin-transform-remove-undefined/__tests__/babel-plugin-transform-remove-undefined-test.js index 14ccc12a7..6320a17cb 100644 --- a/packages/babel-plugin-transform-remove-undefined/__tests__/babel-plugin-transform-remove-undefined-test.js +++ b/packages/babel-plugin-transform-remove-undefined/__tests__/babel-plugin-transform-remove-undefined-test.js @@ -11,7 +11,7 @@ function transform(code) { } describe("transform-remove-undefined-plugin", () => { - it("should remove multiple undefined assignments", () => { + it("should remove multiple undefined assignments in 1 statement", () => { const source = "let a = undefined, b = 3, c = undefined, d;"; const expected = "let a,\n b = 3,\n c,\n d;"; expect(transform(source)).toBe(expected); @@ -29,15 +29,18 @@ describe("transform-remove-undefined-plugin", () => { expect(transform(source)).toBe(expected); }); + it("should not remove const-assignments to undefined", () => { + const source = "const a = undefined;"; + expect(transform(source)).toBe(source); + }); + it("should remove undefined return value", () => { const source = unpad(` function foo() { - const a = undefined; return undefined; }`); const expected = unpad(` function foo() { - const a = undefined; return; }`); expect(transform(source)).toBe(expected); @@ -69,12 +72,7 @@ describe("transform-remove-undefined-plugin", () => { expect(transform(source)).toBe(expected); }); - it("should not remove const-assignments to undefined", () => { - const source = "const a = undefined;"; - expect(transform(source)).toBe(source); - }); - - it("should remove var-assignments in loops", () => { + it("should remove var-assignments in loops if no modify", () => { const source = unpad(` for (var a = undefined;;) { var b = undefined; @@ -86,7 +84,7 @@ describe("transform-remove-undefined-plugin", () => { expect(transform(source)).toBe(expected); }); - it("should not remove var-assignments in loops 2", () => { + it("should not remove var-assignments in loops if modify", () => { const source = unpad(` for (var a;;) { var b = undefined; @@ -114,7 +112,7 @@ describe("transform-remove-undefined-plugin", () => { expect(transform(source)).toBe(source); }); - it("should not remove if call to constant violation function", () => { + it("should not remove if lval is reference before via a function", () => { const source = unpad(` function foo() { bar(); @@ -149,31 +147,7 @@ describe("transform-remove-undefined-plugin", () => { expect(transform(source)).toBe(expected); }); - it("should remove ...", () => { - const source = unpad(` - foo(); - function foo() { - var x = undefined; - bar(); - console.log(x); - function bar() { - x = 3; - } - }`); - const expected = unpad(` - foo(); - function foo() { - var x; - bar(); - console.log(x); - function bar() { - x = 3; - } - }`); - expect(transform(source)).toBe(expected); - }); - - it("should not remove nor break on mutually recursive function", () => { + it("should not remove on mutually recursive function", () => { const source = unpad(` function foo() { a();