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 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 ac0e048e7..bd859192c 100644 --- a/packages/babel-plugin-minify-mangle-names/src/index.js +++ b/packages/babel-plugin-minify-mangle-names/src/index.js @@ -1,4 +1,12 @@ -module.exports = ({ types: t }) => { +"use strict"; + +const { + markEvalScopes, + isMarked: isEvalScopesMarked, + hasEval, +} = require("babel-helper-mark-eval-scopes"); + +module.exports = ({ types: t, traverse }) => { const hop = Object.prototype.hasOwnProperty; class Mangler { @@ -24,11 +32,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); } @@ -43,39 +57,28 @@ module.exports = ({ types: t }) => { 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() { @@ -86,7 +89,7 @@ module.exports = ({ types: t }) => { const {scope} = path; if (!mangler.eval) { - if (mangler.unsafeScopes.has(scope)) return; + if (hasEval(scope)) return; // This check is to work around a bug in babel - // + https://github.com/babel/babili/issues/365 @@ -101,7 +104,7 @@ module.exports = ({ types: t }) => { // // TODO: // Remove for babel7 or whenever it's fixed in babel - if (path.isClassDeclaration() && mangler.unsafeScopes.has(path.parentPath.scope)) return; + if (path.isClassDeclaration() && hasEval(path.parentPath.scope)) return; } if (mangler.visitedScopes.has(scope)) return; diff --git a/packages/babel-plugin-minify-simplify/__tests__/simplify-test.js b/packages/babel-plugin-minify-simplify/__tests__/simplify-test.js index 85367426c..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) { @@ -2617,4 +2618,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, + comparisonPlugin + ], + }).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..aafec29f0 100644 --- a/packages/babel-plugin-minify-simplify/src/index.js +++ b/packages/babel-plugin-minify-simplify/src/index.js @@ -686,6 +686,16 @@ module.exports = ({ types: t }) => { return; } 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(); + } + }); }, BlockStatement: {