From 7e568bf1be2cb2ea4bbf8f15349f73ad8569ed03 Mon Sep 17 00:00:00 2001 From: Juriy Zaytsev Date: Wed, 18 Jan 2017 17:04:06 -0500 Subject: [PATCH 1/6] Add Identifier traversal + getTypeAnnotations as a way of fixing type annotations for identifiers. Closes #323 --- .../__tests__/simplify-test.js | 25 +++++++++++++++++++ .../babel-plugin-minify-simplify/src/index.js | 6 +++++ 2 files changed, 31 insertions(+) diff --git a/packages/babel-plugin-minify-simplify/__tests__/simplify-test.js b/packages/babel-plugin-minify-simplify/__tests__/simplify-test.js index 85367426c..1b4694418 100644 --- a/packages/babel-plugin-minify-simplify/__tests__/simplify-test.js +++ b/packages/babel-plugin-minify-simplify/__tests__/simplify-test.js @@ -2617,4 +2617,29 @@ describe("simplify-plugin", () => { `); expect(transform(source)).toBe(expected); }); + + it("should fix issue#323 with != and !==", () => { + const source = unpad(` + function foo() { + var x, y; + y = o[x]; + foo(y !== undefined); + } + `); + const expected = unpad(` + function foo() { + var x, y; + y = o[x], foo(y !== undefined); + } + `); + function transform(code) { + return babel.transform(code, { + plugins: [ + plugin, + 'transform-simplify-comparison-operators' + ], + }).code; + } + expect(transform(source)).toBe(expected); + }); }); diff --git a/packages/babel-plugin-minify-simplify/src/index.js b/packages/babel-plugin-minify-simplify/src/index.js index 1eba002aa..5c2d0eff0 100644 --- a/packages/babel-plugin-minify-simplify/src/index.js +++ b/packages/babel-plugin-minify-simplify/src/index.js @@ -686,6 +686,12 @@ module.exports = ({ types: t }) => { return; } node.body = statements; + + path.traverse({ + Identifier: function(path) { + path.getTypeAnnotation(); + } + }); }, BlockStatement: { From c45af4a6fde11aaff2ef5d6d0c0dbbeec3a334c4 Mon Sep 17 00:00:00 2001 From: Juriy Zaytsev Date: Wed, 18 Jan 2017 17:15:36 -0500 Subject: [PATCH 2/6] Add a note about a fix --- packages/babel-plugin-minify-simplify/src/index.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/babel-plugin-minify-simplify/src/index.js b/packages/babel-plugin-minify-simplify/src/index.js index 5c2d0eff0..aafec29f0 100644 --- a/packages/babel-plugin-minify-simplify/src/index.js +++ b/packages/babel-plugin-minify-simplify/src/index.js @@ -687,6 +687,10 @@ module.exports = ({ types: t }) => { } node.body = statements; + // this additional traversal is horrible but it's done to fix + // https://github.com/babel/babili/issues/323 + // in which type annotation somehow gets messed up + // during sequence expression transformation path.traverse({ Identifier: function(path) { path.getTypeAnnotation(); From 1d7b1132308a3c6f98d7a8915a4096da4f4319ea Mon Sep 17 00:00:00 2001 From: Juriy Zaytsev Date: Wed, 18 Jan 2017 17:59:21 -0500 Subject: [PATCH 3/6] Resolve plugin properly --- .../babel-plugin-minify-simplify/__tests__/simplify-test.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/babel-plugin-minify-simplify/__tests__/simplify-test.js b/packages/babel-plugin-minify-simplify/__tests__/simplify-test.js index 1b4694418..f22a4c20d 100644 --- a/packages/babel-plugin-minify-simplify/__tests__/simplify-test.js +++ b/packages/babel-plugin-minify-simplify/__tests__/simplify-test.js @@ -34,6 +34,7 @@ jest.autoMockOff(); const babel = require("babel-core"); const plugin = require("../src/index"); +const comparisonPlugin = require("../../babel-plugin-transform-simplify-comparison-operators/src"); const unpad = require("../../../utils/unpad"); function transform(code) { @@ -2636,7 +2637,7 @@ describe("simplify-plugin", () => { return babel.transform(code, { plugins: [ plugin, - 'transform-simplify-comparison-operators' + comparisonPlugin ], }).code; } From d01ac10fa752d37d703b770f14d7a8eb26c83c62 Mon Sep 17 00:00:00 2001 From: Henry Zhu Date: Thu, 19 Jan 2017 11:07:09 -0500 Subject: [PATCH 4/6] Babili v0.0.10 changelog [skip ci] (#379) * Babili v0.0.10 changelog [skip ci] * rm extraneous [skip ci] * Update CHANGELOG [skip ci] * Update CHANGELOG.md --- CHANGELOG.md | 71 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1ebc99733..acc8dd5d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,74 @@ +## Babili v0.0.10: Changelog for 2017-01-18 + + - babel-helper-flip-expressions@0.0.2 + - babel-helper-to-multiple-sequence-expressions@0.0.3 + - babel-plugin-minify-dead-code-elimination@0.1.2 + - babel-plugin-minify-flip-comparisons@0.0.2 + - babel-plugin-minify-guarded-expressions@0.0.4 + - babel-plugin-minify-mangle-names@0.0.6 + - babel-plugin-minify-simplify@0.0.6 + - babel-plugin-minify-type-constructors@0.0.3 + - babel-plugin-transform-inline-consecutive-adds@0.0.2 + - babel-plugin-transform-inline-environment-variables@0.0.2 + - babel-plugin-transform-member-expression-literals@6.8.1 + - babel-plugin-transform-merge-sibling-variables@6.8.1 + - babel-plugin-transform-node-env-inline@0.0.2 + - babel-plugin-transform-property-literals@6.8.1 + - babel-plugin-transform-regexp-constructors@0.0.5 + - babel-plugin-transform-remove-undefined@0.0.4 + - babel-plugin-transform-simplify-comparison-operators@6.8.1 + - babel-preset-babili@0.0.10 + - babili@0.0.10 + +#### :rocket: New Feature +* `babel-plugin-minify-dead-code-elimination`, `babel-plugin-minify-mangle-names`, `babel-preset-babili` + * [#311](https://github.com/babel/babili/pull/311) add keepClassName as separate options (Closes [#298](https://github.com/babel/babili/issues/298)). ([@vigneshshanmugam](https://github.com/vigneshshanmugam)) +* `babel-preset-babili` + * [#162](https://github.com/babel/babili/pull/162) Minify Options. ([@boopathi](https://github.com/boopathi)) + +#### :bug: Bug Fix +* `babel-preset-babili` + * [#325](https://github.com/babel/babili/pull/325) remove comments by default in babel-preset-babili. ([@hzoo](https://github.com/hzoo)) +* `babili` + * [#315](https://github.com/babel/babili/pull/315) resolve babili-preset relative to babili-cli. ([@boopathi](https://github.com/boopathi)) +* `babel-plugin-transform-merge-sibling-variables` + * [#314](https://github.com/babel/babili/pull/314) dont lift declarations when not intialized (Closes [#309](https://github.com/babel/babili/issues/309)). ([@vigneshshanmugam](https://github.com/vigneshshanmugam)) +* `babel-plugin-transform-regexp-constructors` + * [#304](https://github.com/babel/babili/pull/304) Escape unicode newline in regex optimization. ([@SimonSelg](https://github.com/SimonSelg)) +* `babel-plugin-minify-dead-code-elimination` + * [#303](https://github.com/babel/babili/pull/303) Fix dce - recompute path & scope before pass. ([@boopathi](https://github.com/boopathi)) +* `babel-plugin-minify-dead-code-elimination`, `babel-preset-babili` + * [#292](https://github.com/babel/babili/pull/292) run DCE on program exit (Closes [#289](https://github.com/babel/babili/issues/289)). ([@vigneshshanmugam](https://github.com/vigneshshanmugam)) + +#### :memo: Documentation +* Other + * [#348](https://github.com/babel/babili/pull/348) add install, removing #redundancy [skip ci]. ([@hzoo](https://github.com/hzoo)) +* `babel-plugin-minify-simplify` + * [#320](https://github.com/babel/babili/pull/320) Tweak simpify README. ([@existentialism](https://github.com/existentialism)) +* `babel-preset-babili` + * [#293](https://github.com/babel/babili/pull/293) [skip ci] Add preset options docs. ([@boopathi](https://github.com/boopathi)) + +#### :house: Internal +* [#335](https://github.com/babel/babili/pull/335) Add fb package and an option to bench local file. ([@kangax](https://github.com/kangax)) +* [#148](https://github.com/babel/babili/pull/148) Improve benchmarks accuracy. ([@kangax](https://github.com/kangax)) +* [#272](https://github.com/babel/babili/pull/272) Add plugin contribution. ([@boopathi](https://github.com/boopathi)) +* [#319](https://github.com/babel/babili/pull/319) Remove dollar from sh snippets. ([@xtuc](https://github.com/xtuc)) + +#### Chore +* [#376](https://github.com/babel/babili/pull/376) devDeps: eslint-config-babel v5.0.0. ([@kaicataldo](https://github.com/kaicataldo)) + +#### Committers: 10 +- Arnaud Marant ([amarant](https://github.com/amarant)) +- Boopathi Rajaa ([boopathi](https://github.com/boopathi)) +- Brian Ng ([existentialism](https://github.com/existentialism)) +- Chris Vaszauskas ([chrisvasz](https://github.com/chrisvasz)) +- Henry Zhu ([hzoo](https://github.com/hzoo)) +- Juriy Zaytsev ([kangax](https://github.com/kangax)) +- Kai Cataldo ([kaicataldo](https://github.com/kaicataldo)) +- Simon Selg ([SimonSelg](https://github.com/SimonSelg)) +- Sven SAULEAU ([xtuc](https://github.com/xtuc)) +- Vignesh Shanmugam ([vigneshshanmugam](https://github.com/vigneshshanmugam)) + ## Babili v0.0.9: Changelog for 2016-11-21 - babili: 0.0.8 => 0.0.9 From 6656c186f799121dc149a14b9031469257e19261 Mon Sep 17 00:00:00 2001 From: Boopathi Rajaa Date: Thu, 19 Jan 2017 17:13:37 +0100 Subject: [PATCH 5/6] Clear traverse cache and recrawl for mangler (#381) --- packages/babel-plugin-minify-mangle-names/src/index.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/babel-plugin-minify-mangle-names/src/index.js b/packages/babel-plugin-minify-mangle-names/src/index.js index 5be2bb2a4..a2d1cc999 100644 --- a/packages/babel-plugin-minify-mangle-names/src/index.js +++ b/packages/babel-plugin-minify-mangle-names/src/index.js @@ -1,4 +1,4 @@ -module.exports = ({ types: t }) => { +module.exports = ({ types: t, traverse }) => { const hop = Object.prototype.hasOwnProperty; class Mangler { @@ -24,11 +24,17 @@ module.exports = ({ types: t }) => { } run() { + this.cleanup(); this.collect(); this.charset.sort(); this.mangle(); } + cleanup() { + traverse.clearCache(); + this.program.scope.crawl(); + } + isBlacklist(name) { return hop.call(this.blacklist, name); } From 2b98b98073dd4087dc03302662c69e7c13d43d80 Mon Sep 17 00:00:00 2001 From: Boopathi Rajaa Date: Thu, 19 Jan 2017 22:02:28 +0100 Subject: [PATCH 6/6] Add mark eval scopes helper and deopt DCE fn unused params (#371) * Add mark eval scopes * Fix readme bug as a result of copy-pasting * Add more tests * Use Symbol for EVAL_SCOPE_MARKER * Fix lint --- .../babel-helper-mark-eval-scopes/README.md | 9 ++ .../__tests__/helper-mark-eval-scopes-test.js | 48 ++++++++ .../package.json | 17 +++ .../src/index.js | 52 ++++++++ .../__tests__/dead-code-elimination-test.js | 115 ++++++++++++++++++ .../package.json | 1 + .../src/index.js | 9 ++ .../package.json | 4 +- .../src/index.js | 53 ++++---- 9 files changed, 279 insertions(+), 29 deletions(-) create mode 100644 packages/babel-helper-mark-eval-scopes/README.md create mode 100644 packages/babel-helper-mark-eval-scopes/__tests__/helper-mark-eval-scopes-test.js create mode 100644 packages/babel-helper-mark-eval-scopes/package.json create mode 100644 packages/babel-helper-mark-eval-scopes/src/index.js diff --git a/packages/babel-helper-mark-eval-scopes/README.md b/packages/babel-helper-mark-eval-scopes/README.md new file mode 100644 index 000000000..1c30b0ffc --- /dev/null +++ b/packages/babel-helper-mark-eval-scopes/README.md @@ -0,0 +1,9 @@ +# babel-helper-mark-eval-scopes + +Traverse through input path and mark all scopes that contain Direct eval (`eval("")`) calls. + +## Installation + +```sh +npm install babel-helper-mark-eval-scopes +``` diff --git a/packages/babel-helper-mark-eval-scopes/__tests__/helper-mark-eval-scopes-test.js b/packages/babel-helper-mark-eval-scopes/__tests__/helper-mark-eval-scopes-test.js new file mode 100644 index 000000000..26c09861f --- /dev/null +++ b/packages/babel-helper-mark-eval-scopes/__tests__/helper-mark-eval-scopes-test.js @@ -0,0 +1,48 @@ +jest.autoMockOff(); + +const babel = require("babel-core"); +const helper = require("../src"); + +function getPath(source) { + let path; + + babel.transform(source, { + babelrc: false, + plugins: [ + function ({traverse}) { + traverse.clearCache(); + return { + visitor: { + Program(programPath) { + path = programPath; + } + } + }; + } + ] + }); + + return path; +} + +describe("babel-helper-mark-eval-scopes", () => { + it("getEvalScopes - should give a set of scopes which contains eval", () => { + const source = ` + function foo() { + function bar() { + eval(";"); + } + function baz() { + noeval(); + } + } + `; + + const program = getPath(source); + const evalScopes = [...helper.getEvalScopes(program)]; + + expect(evalScopes).toContain(program.scope); + expect(evalScopes).toContain(program.get("body.0.body.body.0").scope); + expect(evalScopes).not.toContain(program.get("body.0.body.body.1").scope); + }); +}); diff --git a/packages/babel-helper-mark-eval-scopes/package.json b/packages/babel-helper-mark-eval-scopes/package.json new file mode 100644 index 000000000..7f8f8d68f --- /dev/null +++ b/packages/babel-helper-mark-eval-scopes/package.json @@ -0,0 +1,17 @@ +{ + "name": "babel-helper-mark-eval-scopes", + "version": "0.0.1", + "description": "Mark scopes for deopt which contain a direct eval call", + "homepage": "https://github.com/babel/babili#readme", + "repository": "https://github.com/babel/babili/tree/master/packages/babel-helper-mark-eval-scopes", + "bugs": "https://github.com/babel/babili/issues", + "author": "boopathi", + "license": "MIT", + "main": "lib/index.js", + "keywords": [ + "babel-plugin", + "babili" + ], + "dependencies": {}, + "devDependencies": {} +} diff --git a/packages/babel-helper-mark-eval-scopes/src/index.js b/packages/babel-helper-mark-eval-scopes/src/index.js new file mode 100644 index 000000000..466a9fa69 --- /dev/null +++ b/packages/babel-helper-mark-eval-scopes/src/index.js @@ -0,0 +1,52 @@ +"use strict"; + +const EVAL_SCOPE_MARKER = Symbol("evalInScope"); + +module.exports = { + EVAL_SCOPE_MARKER, + getEvalScopes, + markEvalScopes, + isMarked, + hasEval, +}; + +function getEvalScopes(path) { + const evalScopes = new Set; + + function add(scope) { + let evalScope = scope; + do { + evalScopes.add(evalScope); + } while (evalScope = evalScope.parent); + } + + path.traverse({ + CallExpression(evalPath) { + const callee = evalPath.get("callee"); + + if (callee.isIdentifier() && callee.node.name === "eval" && !callee.scope.getBinding("eval")) { + add(callee.scope); + } + } + }); + + return evalScopes; +} + +function markEvalScopes(path, key = EVAL_SCOPE_MARKER) { + const evalScopes = getEvalScopes(path); + [...evalScopes].forEach((scope) => { + scope[key] = true; + }); +} + +function isMarked(scope, key = EVAL_SCOPE_MARKER) { + return Object.prototype.hasOwnProperty.call(scope, key); +} + +function hasEval(scope, key = EVAL_SCOPE_MARKER) { + if (!isMarked(scope, key)) { + markEvalScopes(scope, key); + } + return scope[key]; +} diff --git a/packages/babel-plugin-minify-dead-code-elimination/__tests__/dead-code-elimination-test.js b/packages/babel-plugin-minify-dead-code-elimination/__tests__/dead-code-elimination-test.js index cd3922821..372da8dcf 100644 --- a/packages/babel-plugin-minify-dead-code-elimination/__tests__/dead-code-elimination-test.js +++ b/packages/babel-plugin-minify-dead-code-elimination/__tests__/dead-code-elimination-test.js @@ -2306,4 +2306,119 @@ describe("dce-plugin", () => { `); expect(transformWithSimplify(source)).toBe(expected); }); + + it("should not remove params from functions containing direct eval", () => { + const source = unpad(` + function a(b, c, d) { + eval(";"); + return b; + } + function b(c, d, e) { + (1, eval)(";"); + return c; + } + `); + + const expected = unpad(` + function a(b, c, d) { + eval(";"); + return b; + } + function b(c) { + (1, eval)(";"); + return c; + } + `); + + expect(transform(source)).toBe(expected); + }); + + it("should not remove params/vars from functions containing direct eval", () => { + const source = unpad(` + function foo(bar, baz) { + function foox(a, b, c) { + x.then((data, unused) => { + let unused1; + eval(data); + foox1(); + { + var unused2; + } + }); + + function foox1(unused) { + console.log("foox1"); + } + } + function fooy(unused1, unused2) { + console.log("fooy"); + } + } + `); + + const expected = unpad(` + function foo(bar, baz) { + function foox(a, b, c) { + x.then((data, unused) => { + let unused1; + eval(data); + foox1(); + { + var unused2; + } + }); + + function foox1() { + console.log("foox1"); + } + } + function fooy() { + console.log("fooy"); + } + } + `); + + expect(transform(source)).toBe(expected); + }); + + it("should not optimize/remove vars from functions containing direct eval", () => { + const source = unpad(` + function foo() { + bar(); + + var x = 5; + return x; + + function bar() { + eval(";"); + return 5; + } + + function baz() { + let x = 10; + return x; + } + } + `); + + const expected = unpad(` + function foo() { + bar(); + + var x = 5; + return x; + + function bar() { + eval(";"); + return 5; + } + + function baz() { + return 10; + } + } + `); + + expect(transform(source)).toBe(expected); + }); }); diff --git a/packages/babel-plugin-minify-dead-code-elimination/package.json b/packages/babel-plugin-minify-dead-code-elimination/package.json index 01efde2ea..efca8b50d 100644 --- a/packages/babel-plugin-minify-dead-code-elimination/package.json +++ b/packages/babel-plugin-minify-dead-code-elimination/package.json @@ -12,6 +12,7 @@ "babel-plugin" ], "dependencies": { + "babel-helper-mark-eval-scopes": "^0.0.1", "babel-helper-remove-or-void": "^0.0.1", "lodash.some": "^4.6.0" }, diff --git a/packages/babel-plugin-minify-dead-code-elimination/src/index.js b/packages/babel-plugin-minify-dead-code-elimination/src/index.js index 47bebe420..8fcc304ab 100644 --- a/packages/babel-plugin-minify-dead-code-elimination/src/index.js +++ b/packages/babel-plugin-minify-dead-code-elimination/src/index.js @@ -1,6 +1,7 @@ "use strict"; const some = require("lodash.some"); +const { markEvalScopes, isMarked: isEvalScopesMarked, hasEval } = require("babel-helper-mark-eval-scopes"); module.exports = ({ types: t, traverse }) => { const removeOrVoid = require("babel-helper-remove-or-void")(t); @@ -104,6 +105,10 @@ module.exports = ({ types: t, traverse }) => { return; } + if (hasEval(path.scope)) { + return; + } + const { scope } = path; // if the scope is created by a function, we obtain its @@ -712,6 +717,10 @@ module.exports = ({ types: t, traverse }) => { traverse.clearCache(); path.scope.crawl(); + if (!isEvalScopesMarked(path.scope)) { + markEvalScopes(path); + } + // We need to run this plugin in isolation. path.traverse(main, { functionToBindings: new Map(), diff --git a/packages/babel-plugin-minify-mangle-names/package.json b/packages/babel-plugin-minify-mangle-names/package.json index bd82d9f45..2f4a5343d 100644 --- a/packages/babel-plugin-minify-mangle-names/package.json +++ b/packages/babel-plugin-minify-mangle-names/package.json @@ -11,6 +11,8 @@ "keywords": [ "babel-plugin" ], - "dependencies": {}, + "dependencies": { + "babel-helper-mark-eval-scopes": "^0.0.1" + }, "devDependencies": {} } diff --git a/packages/babel-plugin-minify-mangle-names/src/index.js b/packages/babel-plugin-minify-mangle-names/src/index.js index a2d1cc999..e55bcd474 100644 --- a/packages/babel-plugin-minify-mangle-names/src/index.js +++ b/packages/babel-plugin-minify-mangle-names/src/index.js @@ -1,3 +1,11 @@ +"use strict"; + +const { + markEvalScopes, + isMarked: isEvalScopesMarked, + hasEval, +} = require("babel-helper-mark-eval-scopes"); + module.exports = ({ types: t, traverse }) => { const hop = Object.prototype.hasOwnProperty; @@ -49,39 +57,28 @@ module.exports = ({ types: t, traverse }) => { collect() { const mangler = this; - const collectVisitor = { - // capture direct evals - CallExpression(path) { - const callee = path.get("callee"); - - if (callee.isIdentifier() - && callee.node.name === "eval" - && !callee.scope.getBinding("eval") - ) { - mangler.markUnsafeScopes(path.scope); - } - } - }; + if (!isEvalScopesMarked(mangler.program.scope)) { + markEvalScopes(mangler.program); + } if (this.charset.shouldConsider) { - // charset considerations - collectVisitor.Identifier = function Identifier(path) { - const { node } = path; - - if ((path.parentPath.isMemberExpression({ property: node })) || - (path.parentPath.isObjectProperty({ key: node })) - ) { - mangler.charset.consider(node.name); + const collectVisitor = { + Identifier(path) { + const { node } = path; + + if ((path.parentPath.isMemberExpression({ property: node })) || + (path.parentPath.isObjectProperty({ key: node })) + ) { + mangler.charset.consider(node.name); + } + }, + Literal({ node }) { + mangler.charset.consider(String(node.value)); } }; - // charset considerations - collectVisitor.Literal = function Literal({ node }) { - mangler.charset.consider(String(node.value)); - }; + mangler.program.traverse(collectVisitor); } - - this.program.traverse(collectVisitor); } mangle() { @@ -91,7 +88,7 @@ module.exports = ({ types: t, traverse }) => { Scopable(path) { const {scope} = path; - if (!mangler.eval && mangler.unsafeScopes.has(scope)) return; + if (!mangler.eval && hasEval(scope)) return; if (mangler.visitedScopes.has(scope)) return; mangler.visitedScopes.add(scope);