From 2c8345fd58df210dcecb7980ea978ae9826e1532 Mon Sep 17 00:00:00 2001 From: Boopathi Rajaa Date: Sat, 5 Nov 2016 10:51:14 +0100 Subject: [PATCH 01/10] Reuse removed vars in mangler ResetNext identifier only when reuse is true Fix tests - add keepClassName Reuse vars as default I dont know why it works - #326 Extract tracker to a separate file, Add topLevel Option --- .../__tests__/mangle-names-reuse-test.js | 1342 +++++++++++++++++ .../__tests__/mangle-names-test.js | 74 +- .../src/charset.js | 51 + .../src/counted-set.js | 28 + .../src/index.js | 330 ++-- .../src/is-label-identifier.js | 8 + .../src/scope-tracker.js | 157 ++ .../__tests__/preset-tests.js | 54 +- 8 files changed, 1887 insertions(+), 157 deletions(-) create mode 100644 packages/babel-plugin-minify-mangle-names/__tests__/mangle-names-reuse-test.js create mode 100644 packages/babel-plugin-minify-mangle-names/src/charset.js create mode 100644 packages/babel-plugin-minify-mangle-names/src/counted-set.js create mode 100644 packages/babel-plugin-minify-mangle-names/src/is-label-identifier.js create mode 100644 packages/babel-plugin-minify-mangle-names/src/scope-tracker.js diff --git a/packages/babel-plugin-minify-mangle-names/__tests__/mangle-names-reuse-test.js b/packages/babel-plugin-minify-mangle-names/__tests__/mangle-names-reuse-test.js new file mode 100644 index 000000000..58ccd9741 --- /dev/null +++ b/packages/babel-plugin-minify-mangle-names/__tests__/mangle-names-reuse-test.js @@ -0,0 +1,1342 @@ +jest.autoMockOff(); + +const traverse = require("babel-traverse").default; +const babel = require("babel-core"); +const unpad = require("../../../utils/unpad"); + +function transform(code, options = {}, sourceType = "script") { + options.reuse = true; + return babel.transform(code, { + sourceType, + plugins: [ + [require("../src/index"), options], + ], + }).code; +} + +function transformWithSimplify(code, options = {}, sourceType = "script") { + options.reuse = true; + return babel.transform(code, { + sourceType, + plugins: [ + require("../../babel-plugin-minify-simplify/src/index"), + [require("../src/index"), options] + ] + }).code; +} + +describe("mangle-names", () => { + it("should not mangle names in the global namespace", () => { + const source = unpad(` + var Foo = 1; + `); + const expected = unpad(` + var Foo = 1; + `); + + expect(transform(source)).toBe(expected); + }); + + it("should mangle names", () => { + const source = unpad(` + function foo() { + var xxx = 1; + if (xxx) { + console.log(xxx); + } + } + `); + const expected = unpad(` + function foo() { + var a = 1; + if (a) { + console.log(a); + } + } + `); + + expect(transform(source)).toBe(expected); + }); + + it("should handle name collisions", () => { + const source = unpad(` + function foo() { + var x = 2; + var xxx = 1; + if (xxx) { + console.log(xxx + x); + } + } + `); + const expected = unpad(` + function foo() { + var a = 2; + var b = 1; + if (b) { + console.log(b + a); + } + } + `); + + expect(transform(source)).toBe(expected); + }); + + it("should be fine with shadowing", () => { + const source = unpad(` + var a = 1; + function foo() { + var xxx = 1; + if (xxx) { + console.log(xxx); + } + } + `); + const expected = unpad(` + var a = 1; + function foo() { + var a = 1; + if (a) { + console.log(a); + } + } + `); + + expect(transform(source)).toBe(expected); + }); + + it("should not shadow outer references", () => { + const source = unpad(` + function bar() { + function foo(a, b, c) { + lol(a,b,c); + } + + function lol() {} + } + `); + const expected = unpad(` + function bar() { + function a(d, a, e) { + b(d, a, e); + } + + function b() {} + } + `); + + expect(transform(source)).toBe(expected); + }); + + it("should mangle args", () => { + const source = unpad(` + function foo(xxx) { + if (xxx) { + console.log(xxx); + } + } + `); + const expected = unpad(` + function foo(a) { + if (a) { + console.log(a); + } + } + `); + + expect(transform(source)).toBe(expected); + }); + + it("should ignore labels", () => { + const source = unpad(` + function foo() { + meh: for (;;) { + continue meh; + } + } + `); + + const expected = unpad(` + function foo() { + meh: for (;;) { + continue meh; + } + } + `); + + expect(transform(source)).toBe(expected); + }); + + it("should not have labels conflicting with bindings", () => { + const source = unpad(` + function foo() { + meh: for (;;) { + var meh; + break meh; + } + } + `); + + const expected = unpad(` + function foo() { + meh: for (;;) { + var a; + break meh; + } + } + `); + + expect(transform(source)).toBe(expected); + }); + + // https://phabricator.babeljs.io/T6957 + it("labels should not shadow bindings", () => { + const source = unpad(` + function foo() { + var meh; + meh: for (;;) { + break meh; + } + return meh; + } + `); + + const expected = unpad(` + function foo() { + var a; + meh: for (;;) { + break meh; + } + return a; + } + `); + + expect(transform(source)).toBe(expected); + }); + + it("labels should not shadow bindings 2", () => { + const source = unpad(` + function f(a) { + try { + a: { + console.log(a); + } + } catch ($a) { } + } + `); + const expected = unpad(` + function f(b) { + try { + a: { + console.log(b); + } + } catch (a) {} + } + `); + expect(transform(source)).toBe(expected); + }); + + it("should be order independent", () => { + const source = unpad(` + function foo() { + function bar(aaa, bbb, ccc) { + baz(aaa, bbb, ccc); + } + function baz() { + var baz = who(); + baz.bam(); + } + bar(); + } + `); + + const expected = unpad(` + function foo() { + function a(a, c, d) { + b(a, c, d); + } + function b() { + var a = who(); + a.bam(); + } + a(); + } + `); + + expect(transform(source)).toBe(expected); + }); + + it("should be order independent 2", () => { + const source = unpad(` + function foo() { + (function bar() { + bar(); + return function() { + var bar = wow(); + bar.woo(); + }; + })(); + } + `); + + const expected = unpad(` + function foo() { + (function a() { + a(); + return function () { + var a = wow(); + a.woo(); + }; + })(); + } + `); + + expect(transform(source)).toBe(expected); + }); + + it("should handle only think in function scopes", () => { + const source = unpad(` + function foo() { + function xx(bar, baz) { + if (1) { + yy(bar, baz); + } + } + function yy(){} + } + `); + const expected = unpad(` + function foo() { + function a(a, c) { + if (1) { + b(a, c); + } + } + function b() {} + } + `); + + expect(transform(source)).toBe(expected); + }); + + it("should be fine with shadowing 2", () => { + const source = unpad(` + function foo() { + function xx(bar, baz) { + return function(boo, foo) { + bar(boo, foo); + }; + } + function yy(){} + } + `); + const expected = unpad(` + function foo() { + function a(a, b) { + return function (b, c) { + a(b, c); + }; + } + function b() {} + } + `); + + expect(transform(source)).toBe(expected); + }); + + it("should not be confused by scopes", () => { + const source = unpad(` + function foo() { + function bar() { + var baz; + if (baz) { + bam(); + } + } + function bam() {} + } + `); + const expected = unpad(` + function foo() { + function a() { + var a; + if (a) { + b(); + } + } + function b() {} + } + `); + + expect(transform(source)).toBe(expected); + }); + + it("should not be confused by scopes (closures)", () => { + const source = unpad(` + function foo() { + function bar(baz) { + return function() { + bam(); + }; + } + function bam() {} + } + `); + const expected = unpad(` + function foo() { + function a(a) { + return function () { + b(); + }; + } + function b() {} + } + `); + + expect(transform(source)).toBe(expected); + }); + + it("should handle recursion", () => { + const source = unpad(` + function bar() { + function foo(a, b, c) { + foo(a,b,c); + } + } + `); + const expected = unpad(` + function bar() { + function a(d, e, b) { + a(d, e, b); + } + } + `); + + expect(transform(source)).toBe(expected); + }); + + it("should handle global name conflict", () => { + const source = unpad(` + function e() { + function foo() { + b = bar(); + } + function bar() {} + } + `); + const expected = unpad(` + function e() { + function a() { + b = c(); + } + function c() {} + } + `); + + expect(transform(source)).toBe(expected); + }); + + it("should handle global name", () => { + const source = unpad(` + function foo() { + var bar = 1; + var baz = 2; + } + `); + + const expected = unpad(` + function foo() { + var bar = 1; + var a = 2; + } + `); + expect(transform(source, { blacklist: {foo: true, bar: true }})).toBe(expected); + }); + + it("should handle deeply nested paths with no bindings", () => { + const source = unpad(` + function xoo() { + function foo(zz, xx, yy) { + function bar(zip, zap, zop) { + return function(bar) { + zap(); + return function() { + zip(); + } + } + } + } + } + `); + const expected = unpad(` + function xoo() { + function a(a, b, c) { + function d(a, b, c) { + return function (c) { + b(); + return function () { + a(); + }; + }; + } + } + } + `); + expect(transform(source)).toBe(expected); + }); + + it("should handle try/catch", () => { + const source = unpad(` + function xoo() { + var e; + try {} catch (e) { + + } + } + `); + const expected = unpad(` + function xoo() { + var a; + try {} catch (a) {} + } + `); + expect(transform(source)).toBe(expected); + }); + + it("should not mangle vars in scope with eval", () => { + const source = unpad(` + function foo() { + var inScopeOuter = 1; + (function () { + var inScopeInner = 2; + eval("inScopeInner + inScopeOuter"); + (function () { + var outOfScope = 1; + })(); + })(); + } + `); + const expected = unpad(` + function foo() { + var inScopeOuter = 1; + (function () { + var inScopeInner = 2; + eval("inScopeInner + inScopeOuter"); + (function () { + var a = 1; + })(); + })(); + } + `); + expect(transform(source)).toBe(expected); + }); + + it("should mangle names with local eval bindings", () => { + const source = unpad(` + function eval() {} + function foo() { + var bar = 1; + eval('...'); + } + `); + const expected = unpad(` + function eval() {} + function foo() { + var a = 1; + eval('...'); + } + `); + expect(transform(source)).toBe(expected); + }); + + it("should mangle names with option eval = true", () => { + const source = unpad(` + function foo() { + var inScopeOuter = 1; + (function () { + var inScopeInner = 2; + eval("..."); + (function () { + var outOfScope = 1; + })(); + })(); + } + `); + const expected = unpad(` + function foo() { + var a = 1; + (function () { + var a = 2; + eval("..."); + (function () { + var a = 1; + })(); + })(); + } + `); + expect(transform(source, { eval: true })).toBe(expected); + }); + + it("should integrate with block scoping plugin", () => { + const srcTxt = unpad(` + function f(x) { + for (let i = 0; i; i++) { + let n; + if (n) { + return; + } + g(() => n); + } + } + `); + + const first = babel.transform(srcTxt, { + plugins: ["transform-es2015-block-scoping"], + code: false, + }); + + traverse.clearCache(); + + const actual = babel.transformFromAst(first.ast, null, { + plugins: [[require("../src/index"), { reuse: true }]], + }).code; + + const expected = unpad(` + function f(a) { + var b = function (a) { + var b = void 0; + if (b) { + return { + v: void 0 + }; + } + g(() => b); + }; + + for (var d = 0; d; d++) { + var c = b(d); + if (typeof c === "object") return c.v; + } + } + `); + + expect(actual).toBe(expected); + }); + + it("should integrate with block scoping plugin 2", () => { + const srcTxt = unpad(` + (function () { + function bar() { + if (smth) { + let entries = blah(); + entries(); + } + foo(); + } + function foo() { } + module.exports = { bar }; + })(); + `); + + const first = babel.transform(srcTxt, { + plugins: ["transform-es2015-block-scoping"], + code: false, + }); + + traverse.clearCache(); + + const actual = babel.transformFromAst(first.ast, null, { + plugins: [[require("../src/index"), { reuse: true }]], + }).code; + + const expected = unpad(` + (function () { + function a() { + if (smth) { + var a = blah(); + a(); + } + b(); + } + function b() {} + module.exports = { bar: a }; + })(); + `); + + expect(actual).toBe(expected); + }); + + it("should keep mangled named consistent across scopes when defined later on", () => { + const source = unpad(` + (function() { + function foo() { + { + var baz = true; + + { + bar(); + } + } + } + + function bar() {} + }()); + `); + + const expected = unpad(` + (function () { + function a() { + { + var a = true; + + { + b(); + } + } + } + + function b() {} + })(); + `); + + expect(transform(source)).toBe(expected); + }); + + it("should correctly mangle in nested loops", () => { + const source = unpad(` + (function () { + for (let x in foo) { + for (let y in foo[x]) { + alert(foo[x][y]); + } + } + })(); + `); + + const expected = unpad(` + (function () { + for (let a in foo) { + for (let b in foo[a]) { + alert(foo[a][b]); + } + } + })(); + `); + + expect(transform(source)).toBe(expected); + }); + + // #issue55, #issue57 + it("should correctly mangle function declarations in different order", () => { + const source = unpad(` + (function(){ + (function() { + for (let x in y) y[x]; + f(() => { g() }); + })(); + function g() {} + })(); + `); + + const ast = babel.transform(source, { + presets: ["env"], + sourceType: "script", + code: false + }).ast; + + traverse.clearCache(); + + const actual = babel.transformFromAst(ast, null, { + sourceType: "script", + plugins: [require("../src/index")] + }).code; + + const expected = unpad(` + "use strict"; + + (function () { + (function () { + for (var b in y) { + y[b]; + }f(function () { + a(); + }); + })(); + function a() {} + })(); + `); + + expect(actual).toBe(expected); + }); + + it("should NOT mangle functions when keepFnName is true", () => { + const source = unpad(` + (function() { + var foo = function foo() { + foo(); + } + function bar() { + foo(); + } + bar(); + var baz = foo; + baz(); + })(); + `); + const expected = unpad(` + (function () { + var a = function foo() { + foo(); + }; + function bar() { + a(); + } + bar(); + var b = a; + b(); + })(); + `); + expect(transform(source, {keepFnName: true})).toBe(expected); + }); + + it("should NOT mangle classes when keepClassName is true", () => { + const source = unpad(` + (function() { + class Foo {} + const Bar = class Bar extends Foo {} + var foo = class Baz {} + function bar() { + new foo(); + } + bar(); + })(); + `); + const expected = unpad(` + (function () { + class Foo {} + const b = class Bar extends Foo {}; + var c = class Baz {}; + function a() { + new c(); + } + a(); + })(); + `); + expect(transform(source, {keepClassName: true})).toBe(expected); + }); + + it("should mangle variable re-declaration / K violations", () => { + const source = unpad(` + !function () { + var foo = 1; + foo++; + var foo = 2; + foo++; + } + `); + const expected = unpad(` + !function () { + var a = 1; + a++; + var a = 2; + a++; + }; + `); + expect(transform(source)).toBe(expected); + }); + + it("should handle K violations - 2", () => { + const source = unpad(` + !function () { + var bar = 1; + bar--; + var bar = 10; + foo(bar) + function foo() { + var foo = 10; + foo++; + var foo = 20; + foo(foo); + } + } + `); + const expected = unpad(` + !function () { + var b = 1; + b--; + var b = 10; + a(b); + function a() { + var a = 10; + a++; + var a = 20; + a(a); + } + }; + `); + expect(transform(source)).toBe(expected); + }); + + it("should work with redeclarations", () => { + const source = unpad(` + (function () { + var x = y; + x = z; + x; + })(); + `); + const expected = unpad(` + (function () { + var a = y; + a = z; + a; + })(); + `); + expect(transform(source)).toBe(expected); + }); + + it("should reuse removed vars", () => { + const source = unpad(` + function Foo() { + var a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z; + var A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z; + var $, _; + a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z; + A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z; + $, _; + function Foo() { + var a, b, c, d, e, f, g, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z; + var A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z; + var $, _; + a, b, c, d, e, f, g, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z; + A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z; + $, _; + function Foo() { + var a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z; + var A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z; + var $, _; + a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z; + A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z; + $, _; + } + Foo(); + } + Foo(); + } + `); + const expected = unpad(` + function Foo() { + var ba, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y; + var z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y; + var Z, $; + ba, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y; + z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y; + Z, $; + function aa() { + var aa, a, b, c, d, e, f, g, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y; + var z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y; + var Z, $; + aa, a, b, c, d, e, f, g, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y; + z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y; + Z, $; + function h() { + var aa, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y; + var z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y; + var Z, $; + aa, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y; + z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y; + Z, $; + } + h(); + } + aa(); + } + `); + expect(transform(source)).toBe(expected); + }); + + it("should mangle both referenced and binding identifiers with K violations", () => { + const source = unpad(` + (function () { + var foo = bar, + foo = baz; + foo; + })(); + `); + const expected = unpad(` + (function () { + var a = bar, + a = baz; + a; + })(); + `); + expect(transform(source)).toBe(expected); + }); + + it("should handle export declarations", () => { + const source = unpad(` + const foo = 1; + export { foo }; + export const bar = 2; + export function baz(bar, foo) { + bar(); + foo(); + }; + export default function (bar, baz) { + bar(); + baz(); + } + `); + const expected = unpad(` + const foo = 1; + export { foo }; + export const bar = 2; + export function baz(a, b) { + a(); + b(); + }; + export default function (a, b) { + a(); + b(); + } + `); + expect(transform(source, {}, "module")).toBe(expected); + }); + + it("should find global scope properly", () => { + const source = unpad(` + class A {} + class B extends A {} + (function () { + class C { + constructor() { + new A(); + new B(); + C; + } + } + })(); + `); + const expected = unpad(` + class A {} + class B extends A {} + (function () { + class a { + constructor() { + new A(); + new B(); + a; + } + } + })(); + `); + expect(transform(source)).toBe(expected); + }); + + it("should mangle classes properly", () => { + const source = unpad(` + class A {} + class B {} + new A(); + new B(); + function a() { + class A {} + class B {} + new A(); + new B(); + } + `); + const expected = unpad(` + class A {} + class B {} + new A(); + new B(); + function a() { + class a {} + class b {} + new a(); + new b(); + } + `); + expect(transform(source)).toBe(expected); + }); + + // https://github.com/babel/babili/issues/138 + it("should handle class exports in modules - issue#138", () => { + const source = unpad(` + export class App extends Object {}; + `); + const expected = source; + expect(transform(source, {}, "module")).toBe(expected); + }); + + it("should not mangle the name arguments", () => { + const source = unpad(` + (function () { + var arguments = void 0; + (function () { + console.log(arguments); + })("argument"); + })(); + `); + const expected = source; + expect(transform(source)).toBe(expected); + }); + + it("should handle constant violations across multiple blocks", () => { + const source = unpad(` + function foo() { + var x;x;x; + { + var x;x;x; + function y() { + var x;x;x; + { + var x;x;x; + } + } + } + } + `); + const expected = unpad(` + function foo() { + var a;a;a; + { + var a;a;a; + function b() { + var a;a;a; + { + var a;a;a; + } + } + } + } + `); + expect(transform(source)).toBe(expected); + }); + + it("should work with if_return optimization changing fn scope", () => { + const source = unpad(` + function foo() { + if (x) + return; + function bar() {} + bar(a); + } + `); + const expected = unpad(` + function foo() { + function b() {} + x || b(a); + } + `); + expect(transformWithSimplify(source)).toBe(expected); + }); + + it("should fix #326, #369 - destructuring", () => { + const source = unpad(` + // issue#326 + function a() { + let foo, bar, baz; + ({foo, bar, baz} = {}); + return {foo, bar, baz}; + } + // issue#369 + function decodeMessage(message){ + let namespace; + let name; + let value = null; + + [, namespace, name, value] = message.split(',') || []; + console.log(name); + } + `); + const expected = unpad(` + // issue#326 + function a() { + let a, b, c; + ({ foo: a, bar: b, baz: c } = {}); + return { foo: a, bar: b, baz: c }; + } + // issue#369 + function decodeMessage(a) { + let b; + let c; + let d = null; + + [, b, c, d] = a.split(',') || []; + console.log(c); + } + `); + expect(transform(source)).toBe(expected); + }); + + it("should mangle topLevel when topLevel option is true", () => { + const source = unpad(` + function foo() { + if (FOO_ENV === "production") { + HELLO_WORLD.call(); + } + } + const FOO_ENV = "production"; + var HELLO_WORLD = function bar() { + new AbstractClass({ + [FOO_ENV]: "foo", + a: foo(HELLO_WORLD) + }); + }; + class AbstractClass {} + foo(); + `); + + const expected = unpad(` + function a() { + if (b === "production") { + c.call(); + } + } + const b = "production"; + var c = function e() { + new d({ + [b]: "foo", + a: a(c) + }); + }; + class d {} + a(); + `); + + expect(transform(source, { topLevel: true })).toBe(expected); + }); + + it("should fix #326, #369 - destructuring", () => { + const source = unpad(` + // issue#326 + function a() { + let foo, bar, baz; + ({foo, bar, baz} = {}); + return {foo, bar, baz}; + } + // issue#369 + function decodeMessage(message){ + let namespace; + let name; + let value = null; + + [, namespace, name, value] = message.split(',') || []; + console.log(name); + } + `); + const expected = unpad(` + // issue#326 + function a() { + let a, b, c; + ({ foo: a, bar: b, baz: c } = {}); + return { foo: a, bar: b, baz: c }; + } + // issue#369 + function decodeMessage(a) { + let b; + let c; + let d = null; + + [, b, c, d] = a.split(',') || []; + console.log(c); + } + `); + expect(transform(source)).toBe(expected); + }); + + it("should rename binding.identifier - issue#411", () => { + const source = unpad(` + !function () { + function e(e) { + foo(e); + } + return function () { + return e(); + }; + }(); + `); + const expected = unpad(` + !function () { + function a(a) { + foo(a); + } + return function () { + return a(); + }; + }(); + `); + expect(transform(source)).toBe(expected); + }); + + it("should fix issue#365 - classDeclaration with unsafe parent scope", () => { + const source = unpad(` + function foo() { + eval(""); + class A {} + class B {} + } + `); + expect(transform(source)).toBe(source); + }); + + it("should fix classDeclaration with unsafe program scope", () => { + const source = unpad(` + class A {} + class B {} + eval(""); + `); + expect(transform(source, { topLevel: true })).toBe(source); + }); + + it("should handle constant violations across multiple blocks", () => { + const source = unpad(` + function foo() { + var x;x;x; + { + var x;x;x; + function y() { + var x;x;x; + { + var x;x;x; + } + } + } + } + `); + const expected = unpad(` + function foo() { + var a;a;a; + { + var a;a;a; + function b() { + var a;a;a; + { + var a;a;a; + } + } + } + } + `); + expect(transform(source)).toBe(expected); + }); + + it("should work with if_return optimization changing fn scope", () => { + const source = unpad(` + function foo() { + if (x) + return; + function bar() {} + bar(a); + } + `); + const expected = unpad(` + function foo() { + function b() {} + x || b(a); + } + `); + expect(transformWithSimplify(source)).toBe(expected); + }); +}); diff --git a/packages/babel-plugin-minify-mangle-names/__tests__/mangle-names-test.js b/packages/babel-plugin-minify-mangle-names/__tests__/mangle-names-test.js index 2299ee5b3..91d66f825 100644 --- a/packages/babel-plugin-minify-mangle-names/__tests__/mangle-names-test.js +++ b/packages/babel-plugin-minify-mangle-names/__tests__/mangle-names-test.js @@ -5,12 +5,24 @@ const babel = require("babel-core"); const unpad = require("../../../utils/unpad"); function transform(code, options = {}, sourceType = "script") { + options.reuse = false; return babel.transform(code, { sourceType, plugins: [[require("../src/index"), options]] }).code; } +function transformWithSimplify(code, options = {}, sourceType = "script") { + options.reuse = false; + return babel.transform(code, { + sourceType, + plugins: [ + require("../../babel-plugin-minify-simplify/src/index"), + [require("../src/index"), options] + ] + }).code; +} + describe("mangle-names", () => { it("should not mangle names in the global namespace", () => { const source = unpad( @@ -685,20 +697,20 @@ describe("mangle-names", () => { traverse.clearCache(); const actual = babel.transformFromAst(first.ast, null, { - plugins: [require("../src/index")] + plugins: [[require("../src/index"), { reuse: false }]] }).code; const expected = unpad( ` function f(a) { - var b = function (d) { - var e = void 0; - if (e) { + var b = function (e) { + var h = void 0; + if (h) { return { v: void 0 }; } - g(() => e); + g(() => h); }; for (var d = 0; d; d++) { @@ -737,7 +749,7 @@ describe("mangle-names", () => { traverse.clearCache(); const actual = babel.transformFromAst(first.ast, null, { - plugins: [require("../src/index")] + plugins: [[require("../src/index"), { reuse: false }]] }).code; const expected = unpad( @@ -1366,4 +1378,54 @@ describe("mangle-names", () => { ); expect(transform(source, { topLevel: true }, "module")).toBe(expected); }); + + it("should handle constant violations across multiple blocks", () => { + const source = unpad(` + function foo() { + var x;x;x; + { + var x;x;x; + function y() { + var x;x;x; + { + var x;x;x; + } + } + } + } + `); + const expected = unpad(` + function foo() { + var a;a;a; + { + var a;a;a; + function b() { + var c;c;c; + { + var c;c;c; + } + } + } + } + `); + expect(transform(source)).toBe(expected); + }); + + it("should work with if_return optimization changing fn scope", () => { + const source = unpad(` + function foo() { + if (x) + return; + function bar() {} + bar(a); + } + `); + const expected = unpad(` + function foo() { + function b() {} + x || b(a); + } + `); + expect(transformWithSimplify(source)).toBe(expected); + }); }); diff --git a/packages/babel-plugin-minify-mangle-names/src/charset.js b/packages/babel-plugin-minify-mangle-names/src/charset.js new file mode 100644 index 000000000..161d9129d --- /dev/null +++ b/packages/babel-plugin-minify-mangle-names/src/charset.js @@ -0,0 +1,51 @@ +"use strict"; + +const CHARSET = ("abcdefghijklmnopqrstuvwxyz" + + "ABCDEFGHIJKLMNOPQRSTUVWXYZ$_").split(""); + +module.exports = class Charset { + constructor(shouldConsider) { + this.shouldConsider = shouldConsider; + this.chars = CHARSET.slice(); + this.frequency = {}; + this.chars.forEach((c) => { this.frequency[c] = 0; }); + this.finalized = false; + } + + consider(str) { + if (!this.shouldConsider) { + return; + } + + str.split("").forEach((c) => { + if (this.frequency[c] != null) { + this.frequency[c]++; + } + }); + } + + sort() { + if (this.shouldConsider) { + this.chars = this.chars.sort( + (a, b) => this.frequency[b] - this.frequency[a] + ); + } + + this.finalized = true; + } + + getIdentifier(num) { + if (!this.finalized) { + throw new Error("Should sort first"); + } + + let ret = ""; + num++; + do { + num--; + ret += this.chars[num % this.chars.length]; + num = Math.floor(num / this.chars.length); + } while (num > 0); + return ret; + } +}; diff --git a/packages/babel-plugin-minify-mangle-names/src/counted-set.js b/packages/babel-plugin-minify-mangle-names/src/counted-set.js new file mode 100644 index 000000000..684cb80bf --- /dev/null +++ b/packages/babel-plugin-minify-mangle-names/src/counted-set.js @@ -0,0 +1,28 @@ +// Set that counts +module.exports = class CountedSet { + constructor() { + // because you can't simply extend Builtins yet + this.map = new Map; + } + keys() { + return [...this.map.keys()]; + } + has(value) { + return this.map.has(value); + } + add(value) { + if (!this.has(value)) { + this.map.set(value, 0); + } + this.map.set(value, this.map.get(value) + 1); + } + delete(value) { + if (!this.has(value)) return; + const count = this.map.get(value); + if (count <= 1) { + this.map.delete(value); + } else { + this.map.set(value, count - 1); + } + } +}; diff --git a/packages/babel-plugin-minify-mangle-names/src/index.js b/packages/babel-plugin-minify-mangle-names/src/index.js index 77e108248..ae318d684 100644 --- a/packages/babel-plugin-minify-mangle-names/src/index.js +++ b/packages/babel-plugin-minify-mangle-names/src/index.js @@ -1,4 +1,6 @@ -"use strict"; +const Charset = require("./charset"); +const ScopeTracker = require("./scope-tracker"); +const isLabelIdentifier = require("./is-label-identifier"); const { markEvalScopes, @@ -6,8 +8,6 @@ const { hasEval } = require("babel-helper-mark-eval-scopes"); -const PATH_RENAME_MARKER = Symbol("PATH_RENAME_MARKER"); - module.exports = ({ types: t, traverse }) => { const hop = Object.prototype.hasOwnProperty; @@ -18,9 +18,10 @@ module.exports = ({ types: t, traverse }) => { { blacklist = {}, keepFnName = false, + keepClassName = false, eval: _eval = false, topLevel = false, - keepClassName = false + reuse = true } = {} ) { this.charset = charset; @@ -28,64 +29,130 @@ module.exports = ({ types: t, traverse }) => { this.blacklist = toObject(blacklist); this.keepFnName = keepFnName; this.keepClassName = keepClassName; - this.eval = _eval; this.topLevel = topLevel; + this.eval = _eval; + this.reuse = reuse; - this.unsafeScopes = new Set(); this.visitedScopes = new Set(); - - this.referencesToUpdate = new Map(); + this.scopeTracker = new ScopeTracker({ reuse }); + this.renamedNodes = new Set(); } run() { - this.cleanup(); + this.crawlScope(); this.collect(); this.charset.sort(); this.mangle(); } - cleanup() { - traverse.clearCache(); - this.program.scope.crawl(); - } - isBlacklist(name) { return hop.call(this.blacklist, name) && this.blacklist[name]; } - markUnsafeScopes(scope) { - let evalScope = scope; - do { - this.unsafeScopes.add(evalScope); - } while ((evalScope = evalScope.parent)); + crawlScope() { + traverse.clearCache(); + this.program.scope.crawl(); } collect() { const mangler = this; + const { scopeTracker } = mangler; + + scopeTracker.addScope(this.program.scope); if (!isEvalScopesMarked(mangler.program.scope)) { markEvalScopes(mangler.program); } - if (this.charset.shouldConsider) { - const collectVisitor = { - Identifier(path) { - const { node } = path; - - if ( - path.parentPath.isMemberExpression({ property: node }) || - path.parentPath.isObjectProperty({ key: node }) - ) { - mangler.charset.consider(node.name); + const collectVisitor = { + Scopable({ scope }) { + scopeTracker.addScope(scope); + Object.keys(scope.bindings).forEach(name => { + scopeTracker.addBinding(scope.bindings[name]); + }); + }, + ReferencedIdentifier(path) { + if (isLabelIdentifier(path)) return; + const { scope, node: { name } } = path; + const binding = scope.getBinding(name); + scopeTracker.addReference(scope, binding, name); + }, + // this fixes a bug where converting let to var + // doesn't change the binding's scope to function scope + VariableDeclaration: { + enter(path) { + if (path.node.kind !== "var") { + return; } - }, - Literal({ node }) { - mangler.charset.consider(String(node.value)); + const ids = path.getOuterBindingIdentifiers(); + const fnScope = path.scope.getFunctionParent(); + Object.keys(ids).forEach(id => { + const binding = path.scope.getBinding(id); + + if (binding.scope !== fnScope) { + const existingBinding = fnScope.bindings[id]; + if (!existingBinding) { + // move binding to the function scope + fnScope.bindings[id] = binding; + binding.scope = fnScope; + delete binding.scope.bindings[id]; + } else { + // we need a new binding that's valid in both the scopes + // binding.scope and fnScope + const newName = fnScope.generateUid( + binding.scope.generateUid(id) + ); + + // rename binding in the original scope + mangler.rename(binding.scope, binding, id, newName); + + // move binding to fnScope as newName + fnScope.bindings[newName] = binding; + binding.scope = fnScope; + delete binding.scope.bindings[newName]; + } + } + }); } - }; + }, + BindingIdentifier: { + exit(path) { + if (isLabelIdentifier(path)) return; + const { scope, node: { name } } = path; + const binding = scope.getBinding(name); + if (!binding) { + if (scope.hasGlobal(name)) return; + if ( + path.parentPath.isExportSpecifier() && + path.parentKey === "exported" + ) { + return; + } + console.log(scope.globals); + throw new Error("binding not found " + name); + } + scopeTracker.addBinding(binding); + } + } + }; - mangler.program.traverse(collectVisitor); + if (this.charset.shouldConsider) { + collectVisitor.Identifier = function Identifer(path) { + const { node } = path; + + if ( + path.parentPath.isMemberExpression({ property: node }) || + path.parentPath.isObjectProperty({ key: node }) + ) { + mangler.charset.consider(node.name); + } + }; + collectVisitor.Literal = function Literal({ node }) { + mangler.charset.consider(String(node.value)); + }; } + + mangler.program.traverse(collectVisitor); } isExportedWithName(binding) { @@ -108,6 +175,7 @@ module.exports = ({ types: t, traverse }) => { mangleScope(scope) { const mangler = this; + const { scopeTracker } = mangler; if (!mangler.eval && hasEval(scope)) return; @@ -125,26 +193,20 @@ module.exports = ({ types: t, traverse }) => { // => var aa, a, b ,c; // instead of // => var aa, ab, ...; - // TODO: - // Re-enable after enabling this feature - // This doesn't work right now as we are concentrating - // on performance improvements - // function resetNext() { - // i = 0; - // } + function resetNext() { + i = 0; + } - const bindings = scope.getAllBindings(); - const names = Object.keys(bindings); + const bindings = scopeTracker.bindings.get(scope); + const names = [...bindings.keys()]; for (let i = 0; i < names.length; i++) { const oldName = names[i]; - const binding = bindings[oldName]; + const binding = bindings.get(oldName); if ( // arguments oldName === "arguments" || - // other scope bindings - !scope.hasOwnBinding(oldName) || // labels binding.path.isLabeledStatement() || // ClassDeclaration has binding in two scopes @@ -169,15 +231,16 @@ module.exports = ({ types: t, traverse }) => { next = getNext(); } while ( !t.isValidIdentifier(next) || - hop.call(bindings, next) || + scopeTracker.hasBinding(scope, next) || scope.hasGlobal(next) || - scope.hasReference(next) + scopeTracker.hasReference(scope, next) || + !scopeTracker.canUseInReferencedScopes(binding, next) ); - // TODO: - // re-enable this - check above - // resetNext(); - mangler.rename(scope, oldName, next); + if (mangler.reuse) { + resetNext(); + } + mangler.rename(scope, binding, oldName, next); } } @@ -185,7 +248,7 @@ module.exports = ({ types: t, traverse }) => { const mangler = this; if (mangler.topLevel) { - mangler.mangleScope(mangler.program.scope); + mangler.mangleScope(this.program.scope); } this.program.traverse({ @@ -195,50 +258,58 @@ module.exports = ({ types: t, traverse }) => { }); } - rename(scope, oldName, newName) { - const binding = scope.getBinding(oldName); - - // rename at the declaration level - const bindingPaths = binding.path.getBindingIdentifierPaths(true, false); - - // we traverse through all bindingPaths because, - // there is no binding.identifierPath in babel - for (const name in bindingPaths) { + renameBindingIds(path, oldName, newName, predicate = () => true) { + const bindingIds = path.getBindingIdentifierPaths(true, false); + for (const name in bindingIds) { if (name !== oldName) continue; - for (const idPath of bindingPaths[name]) { - if (binding.identifier === idPath.node) { + for (const idPath of bindingIds[name]) { + if (predicate(idPath)) { + this.renamedNodes.add(idPath.node); idPath.replaceWith(t.identifier(newName)); - binding.identifier = idPath.node; - idPath[PATH_RENAME_MARKER] = true; + this.renamedNodes.add(idPath.node); } } } + } - const { bindings } = scope; - bindings[newName] = binding; - delete bindings[oldName]; + rename(scope, binding, oldName, newName) { + const mangler = this; + const { scopeTracker } = mangler; + + // rename at the declaration level + this.renameBindingIds( + binding.path, + oldName, + newName, + idPath => idPath.node === binding.identifier + ); + + // update Tracking + scopeTracker.renameBinding(scope, oldName, newName); // update all constant violations & redeclarations const violations = binding.constantViolations; for (let i = 0; i < violations.length; i++) { if (violations[i].isLabeledStatement()) continue; - const bindings = violations[i].getBindingIdentifierPaths(); - Object.keys(bindings).map(b => { - if (b === oldName && !bindings[b][PATH_RENAME_MARKER]) { - bindings[b].replaceWith(t.identifier(newName)); - bindings[b][PATH_RENAME_MARKER] = true; - } - }); + // const bindings = violations[i].getBindingIdentifierPaths(); + // Object.keys(bindings).map(b => { + // if (b === oldName && !bindings[b][PATH_RENAME_MARKER]) { + // bindings[b].replaceWith(t.identifier(newName)); + // bindings[b][PATH_RENAME_MARKER] = true; + // } + // }); + + this.renameBindingIds(violations[i], oldName, newName); } // update all referenced places const refs = binding.referencePaths; for (let i = 0; i < refs.length; i++) { const path = refs[i]; - if (path[PATH_RENAME_MARKER]) continue; const { node } = path; + if (!path.isIdentifier()) { // Ideally, this should not happen // it happens in these places now - @@ -250,19 +321,49 @@ module.exports = ({ types: t, traverse }) => { // replacement in dce from `x` to `!x` gives referencePath as `!x` path.traverse({ ReferencedIdentifier(refPath) { - if ( - refPath.node.name === oldName && - refPath.scope === scope && - !refPath[PATH_RENAME_MARKER] - ) { - refPath.node.name = newName; + if (refPath.node.name !== oldName) { + return; + } + const actualBinding = refPath.scope.getBinding(oldName); + if (actualBinding !== binding) { + return; } + mangler.renamedNodes.add(refPath.node); + refPath.replaceWith(t.identifier(newName)); + mangler.renamedNodes.add(refPath.node); + + scopeTracker.updateReference( + refPath.scope, + binding, + oldName, + newName + ); } }); } else if (!isLabelIdentifier(path)) { - node.name = newName; + if (path.node.name === oldName) { + mangler.renamedNodes.add(path.node); + path.replaceWith(t.identifier(newName)); + mangler.renamedNodes.add(path.node); + + scopeTracker.updateReference(path.scope, binding, oldName, newName); + } else if (mangler.renamedNodes.has(path.node)) { + // already renamed, + // just update the references + scopeTracker.updateReference(path.scope, binding, oldName, newName); + } else { + throw new Error( + `Unexpected Error - Trying to replace ${node.name}: from ${oldName} to ${newName}` + ); + } } + // else label } + + // update babel's scope tracking + const { bindings } = scope; + bindings[newName] = binding; + delete bindings[oldName]; } } @@ -287,58 +388,6 @@ module.exports = ({ types: t, traverse }) => { }; }; -const CHARSET = ("abcdefghijklmnopqrstuvwxyz" + - "ABCDEFGHIJKLMNOPQRSTUVWXYZ$_").split(""); - -class Charset { - constructor(shouldConsider) { - this.shouldConsider = shouldConsider; - this.chars = CHARSET.slice(); - this.frequency = {}; - this.chars.forEach(c => { - this.frequency[c] = 0; - }); - this.finalized = false; - } - - consider(str) { - if (!this.shouldConsider) { - return; - } - - str.split("").forEach(c => { - if (this.frequency[c] != null) { - this.frequency[c]++; - } - }); - } - - sort() { - if (this.shouldConsider) { - this.chars = this.chars.sort( - (a, b) => this.frequency[b] - this.frequency[a] - ); - } - - this.finalized = true; - } - - getIdentifier(num) { - if (!this.finalized) { - throw new Error("Should sort first"); - } - - let ret = ""; - num++; - do { - num--; - ret += this.chars[num % this.chars.length]; - num = Math.floor(num / this.chars.length); - } while (num > 0); - return ret; - } -} - // convert value to object function toObject(value) { if (!Array.isArray(value)) { @@ -361,12 +410,3 @@ function isFunction(path) { function isClass(path) { return path.isClassExpression() || path.isClassDeclaration(); } - -function isLabelIdentifier(path) { - const { node } = path; - return ( - path.parentPath.isLabeledStatement({ label: node }) || - path.parentPath.isBreakStatement({ label: node }) || - path.parentPath.isContinueStatement({ label: node }) - ); -} diff --git a/packages/babel-plugin-minify-mangle-names/src/is-label-identifier.js b/packages/babel-plugin-minify-mangle-names/src/is-label-identifier.js new file mode 100644 index 000000000..28d6ffe2c --- /dev/null +++ b/packages/babel-plugin-minify-mangle-names/src/is-label-identifier.js @@ -0,0 +1,8 @@ +module.exports = isLabelIdentifier; + +function isLabelIdentifier(path) { + const {node} = path; + return path.parentPath.isLabeledStatement({ label: node }) + || path.parentPath.isBreakStatement({ label: node }) + || path.parentPath.isContinueStatement({ label: node }); +} diff --git a/packages/babel-plugin-minify-mangle-names/src/scope-tracker.js b/packages/babel-plugin-minify-mangle-names/src/scope-tracker.js new file mode 100644 index 000000000..54675282d --- /dev/null +++ b/packages/babel-plugin-minify-mangle-names/src/scope-tracker.js @@ -0,0 +1,157 @@ +const CountedSet = require("./counted-set"); +const isLabelIdentifier = require("./is-label-identifier"); + +/** + * Scope - References, Bindings + */ +module.exports = class ScopeTracker { + constructor({ reuse }) { + this.references = new Map; + this.bindings = new Map; + + this.reuse = reuse; + } + + // Register a new Scope and initiliaze it with empty sets + addScope(scope) { + if (!this.references.has(scope)) { + this.references.set(scope, new CountedSet); + } + if (!this.bindings.has(scope)) { + this.bindings.set(scope, new Map); + } + } + + addReference(scope, binding, name) { + let parent = scope; + do { + if (!this.references.has(parent)) { + this.addScope(parent); + this.updateScope(parent); + } + this.references.get(parent).add(name); + + // here binding is undefined for globals, + // so we just add to all scopes up + if (binding && binding.scope === parent) { + break; + } + } while (parent = parent.parent); + } + + hasReference(scope, name) { + if (!this.reuse) { + return scope.hasReference(name); + } + if (!this.references.has(scope)) { + this.addScope(scope); + this.updateScope(scope); + } + return this.references.get(scope).has(name); + } + + canUseInReferencedScopes(binding, next) { + const tracker = this; + + if (tracker.hasReference(binding.scope, next)) { + return false; + } + + for (let i = 0; i < binding.constantViolations.length; i++) { + const violation = binding.constantViolations[i]; + if (tracker.hasReference(violation.scope, next)) { + return false; + } + } + + for (let i = 0; i < binding.referencePaths; i++) { + const ref = binding.referencePaths[i]; + if (!ref.isIdentifier()) { + let canUse = true; + ref.traverse({ + ReferencedIdentifier(path) { + if (path.node.name !== next) return; + if (tracker.hasReference(path.scope, next)) { + canUse = false; + } + } + }); + if (!canUse) { + return canUse; + } + } else if (!isLabelIdentifier(ref)) { + if (tracker.hasReference(ref.scope, next)) { + return false; + } + } + } + + return true; + } + + updateReference(scope, binding, oldName, newName) { + let parent = scope; + do { + if (!this.references.has(parent)) { + this.addScope(parent); + this.updateScope(parent); + } + + // update + const ref = this.references.get(parent); + if (ref.has(oldName)) { + ref.delete(oldName); + ref.add(newName); + } + // else already renamed + + if (binding.scope === parent) { + break; + } + } while (parent = parent.parent); + } + + addBinding(binding) { + if (!binding) { + return; + } + const bindings = this.bindings.get(binding.scope); + if (!bindings.has(binding.identifier.name)) { + bindings.set(binding.identifier.name, binding); + } + } + + hasBinding(scope, name) { + if (!this.reuse) { + return scope.hasBinding(name); + } + return this.bindings.get(scope).has(name); + } + + renameBinding(scope, oldName, newName) { + const bindings = this.bindings.get(scope); + bindings.set(newName, bindings.get(oldName)); + bindings.delete(oldName); + } + + // This is a fallback option and is used when something happens - + // during traversal and checks we find that a scope doesn't + // exist in the tracker + // + // This should NOT happen ultimately. Just used as a fallback + // with a throw statement. This helps in understanding where it + // happens to debug it. + updateScope(scope) { + throw new Error("Tracker received a scope it doesn't know about yet. Please report this - https://github.com/babel/babili/issues/new"); + + const tracker = this; + scope.path.traverse({ + ReferencedIdentifier(path) { + if (path.scope === scope) { + const binding = scope.getBinding(path.node.name); + tracker.addReference(scope, binding, path.node.name); + } + } + }); + } +}; diff --git a/packages/babel-preset-babili/__tests__/preset-tests.js b/packages/babel-preset-babili/__tests__/preset-tests.js index cb90a9eab..b3f41e988 100644 --- a/packages/babel-preset-babili/__tests__/preset-tests.js +++ b/packages/babel-preset-babili/__tests__/preset-tests.js @@ -7,7 +7,7 @@ function transform(code, options = {}, sourceType = "script") { return babel.transform(code, { sourceType, minified: false, - presets: [require("../src/index")] + presets: [[require("../src/index"), options]] }).code; } @@ -34,8 +34,8 @@ describe("preset", () => { const expected = unpad( ` function foo() { - var d, e, f; - d ? e && f : e || f; + var d, a, b; + d ? a && b : a || b; } ` ); @@ -129,12 +129,54 @@ describe("preset", () => { const expected = unpad( ` function a() { - var b = Math.floor, - c = Math.max; - c(foo, bar); + var a = Math.floor, + b = Math.max; + b(foo, bar); } ` ); expect(transform(source)).toBe(expected); }); + + it("should fix bug#326 - object destructuring", () => { + const source = unpad(` + function a() { + let foo, bar, baz; + ({foo, bar, baz} = {}); + return {foo, bar, baz}; + } + `); + const expected = unpad(` + function a() { + let a, b, c; + + return ({ foo: a, bar: b, baz: c } = {}), { foo: a, bar: b, baz: c }; + } + `); + expect(transform(source)).toBe(expected); + }); + + it("should fix bug#326 - object destructuring", () => { + const source = unpad(` + function a() { + let foo, bar, baz; + ({foo, bar, baz} = {}); + return {foo, bar, baz}; + } + `); + const expected = unpad(` + function a() { + let b, c, d; + + return ({ foo: b, bar: c, baz: d } = {}), { foo: b, bar: c, baz: d }; + } + `); + expect( + transform(source, { + mangle: { + reuse: false + } + }) + ).toBe(expected); + }); }); From 52b887ee3fdc8cf2eaf21a5197898c77221b0b88 Mon Sep 17 00:00:00 2001 From: Boopathi Rajaa Date: Fri, 19 May 2017 00:31:15 +0200 Subject: [PATCH 02/10] BFS traverse --- .../src/bfs-traverse.js | 48 ++++++ .../src/index.js | 138 +++++++++--------- .../src/scope-tracker.js | 28 ++-- 3 files changed, 130 insertions(+), 84 deletions(-) create mode 100644 packages/babel-plugin-minify-mangle-names/src/bfs-traverse.js diff --git a/packages/babel-plugin-minify-mangle-names/src/bfs-traverse.js b/packages/babel-plugin-minify-mangle-names/src/bfs-traverse.js new file mode 100644 index 000000000..439f8a1b2 --- /dev/null +++ b/packages/babel-plugin-minify-mangle-names/src/bfs-traverse.js @@ -0,0 +1,48 @@ +"use strict"; + +module.exports = function bfsTraverseCreator({ types: t, traverse }) { + function getFields(path) { + return t.VISITOR_KEYS[path.type]; + } + + return function bfsTraverse(path, _visitor) { + if (!path.node) { + throw new Error("Not a valid path"); + } + const visitor = traverse.explode(_visitor); + + const queue = [path]; + let current; // current depth + + while (queue.length > 0) { + current = queue.shift(); + + // call + if ( + visitor && + visitor[current.type] && + Array.isArray(visitor[current.type].enter) + ) { + const fns = visitor[current.type].enter; + for (const fn of fns) { + if (typeof fn === "function") fn(current); + } + } + + const fields = getFields(current); + + for (const field of fields) { + const child = current.get(field); + + if (Array.isArray(child)) { + // visit container left to right + for (const c of child) { + if (c.node) queue.push(c); + } + } else { + if (child.node) queue.push(child); + } + } + } + }; +}; diff --git a/packages/babel-plugin-minify-mangle-names/src/index.js b/packages/babel-plugin-minify-mangle-names/src/index.js index ae318d684..2eb71e822 100644 --- a/packages/babel-plugin-minify-mangle-names/src/index.js +++ b/packages/babel-plugin-minify-mangle-names/src/index.js @@ -1,6 +1,7 @@ const Charset = require("./charset"); const ScopeTracker = require("./scope-tracker"); const isLabelIdentifier = require("./is-label-identifier"); +const bfsTraverseCreator = require("./bfs-traverse"); const { markEvalScopes, @@ -8,7 +9,9 @@ const { hasEval } = require("babel-helper-mark-eval-scopes"); -module.exports = ({ types: t, traverse }) => { +module.exports = babel => { + const { types: t, traverse } = babel; + const bfsTraverse = bfsTraverseCreator(babel); const hop = Object.prototype.hasOwnProperty; class Mangler { @@ -20,8 +23,7 @@ module.exports = ({ types: t, traverse }) => { keepFnName = false, keepClassName = false, eval: _eval = false, - topLevel = false, - reuse = true + topLevel = false } = {} ) { this.charset = charset; @@ -31,15 +33,15 @@ module.exports = ({ types: t, traverse }) => { this.keepClassName = keepClassName; this.topLevel = topLevel; this.eval = _eval; - this.reuse = reuse; this.visitedScopes = new Set(); - this.scopeTracker = new ScopeTracker({ reuse }); + this.scopeTracker = new ScopeTracker(); this.renamedNodes = new Set(); } run() { this.crawlScope(); + this.fixup(); this.collect(); this.charset.sort(); this.mangle(); @@ -54,6 +56,49 @@ module.exports = ({ types: t, traverse }) => { this.program.scope.crawl(); } + fixup() { + const mangler = this; + this.program.traverse({ + // this fixes a bug where converting let to var + // doesn't change the binding's scope to function scope + // https://github.com/babel/babel/issues/4818 + VariableDeclaration(path) { + if (path.node.kind !== "var") { + return; + } + const ids = path.getOuterBindingIdentifiers(); + const fnScope = path.scope.getFunctionParent(); + Object.keys(ids).forEach(id => { + const binding = path.scope.getBinding(id); + + if (binding.scope !== fnScope) { + const existingBinding = fnScope.bindings[id]; + if (!existingBinding) { + // move binding to the function scope + fnScope.bindings[id] = binding; + binding.scope = fnScope; + delete binding.scope.bindings[id]; + } else { + // we need a new binding that's valid in both the scopes + // binding.scope and fnScope + const newName = fnScope.generateUid( + binding.scope.generateUid(id) + ); + + // rename binding in the original scope + mangler.rename(binding.scope, binding, id, newName); + + // move binding to fnScope as newName + fnScope.bindings[newName] = binding; + binding.scope = fnScope; + delete binding.scope.bindings[newName]; + } + } + }); + } + }); + } + collect() { const mangler = this; const { scopeTracker } = mangler; @@ -77,62 +122,23 @@ module.exports = ({ types: t, traverse }) => { const binding = scope.getBinding(name); scopeTracker.addReference(scope, binding, name); }, - // this fixes a bug where converting let to var - // doesn't change the binding's scope to function scope - VariableDeclaration: { - enter(path) { - if (path.node.kind !== "var") { + + BindingIdentifier(path) { + if (isLabelIdentifier(path)) return; + + const { scope, node: { name } } = path; + const binding = scope.getBinding(name); + if (!binding) { + if (scope.hasGlobal(name)) return; + if ( + path.parentPath.isExportSpecifier() && + path.parentKey === "exported" + ) { return; } - const ids = path.getOuterBindingIdentifiers(); - const fnScope = path.scope.getFunctionParent(); - Object.keys(ids).forEach(id => { - const binding = path.scope.getBinding(id); - - if (binding.scope !== fnScope) { - const existingBinding = fnScope.bindings[id]; - if (!existingBinding) { - // move binding to the function scope - fnScope.bindings[id] = binding; - binding.scope = fnScope; - delete binding.scope.bindings[id]; - } else { - // we need a new binding that's valid in both the scopes - // binding.scope and fnScope - const newName = fnScope.generateUid( - binding.scope.generateUid(id) - ); - - // rename binding in the original scope - mangler.rename(binding.scope, binding, id, newName); - - // move binding to fnScope as newName - fnScope.bindings[newName] = binding; - binding.scope = fnScope; - delete binding.scope.bindings[newName]; - } - } - }); - } - }, - BindingIdentifier: { - exit(path) { - if (isLabelIdentifier(path)) return; - const { scope, node: { name } } = path; - const binding = scope.getBinding(name); - if (!binding) { - if (scope.hasGlobal(name)) return; - if ( - path.parentPath.isExportSpecifier() && - path.parentKey === "exported" - ) { - return; - } - console.log(scope.globals); - throw new Error("binding not found " + name); - } - scopeTracker.addBinding(binding); + throw new Error("binding not found " + name); } + scopeTracker.addBinding(binding); } }; @@ -152,7 +158,7 @@ module.exports = ({ types: t, traverse }) => { }; } - mangler.program.traverse(collectVisitor); + bfsTraverse(mangler.program, collectVisitor); } isExportedWithName(binding) { @@ -237,9 +243,8 @@ module.exports = ({ types: t, traverse }) => { !scopeTracker.canUseInReferencedScopes(binding, next) ); - if (mangler.reuse) { - resetNext(); - } + resetNext(); + mangler.rename(scope, binding, oldName, next); } } @@ -247,13 +252,10 @@ module.exports = ({ types: t, traverse }) => { mangle() { const mangler = this; - if (mangler.topLevel) { - mangler.mangleScope(this.program.scope); - } - - this.program.traverse({ + bfsTraverse(this.program, { Scopable(path) { - mangler.mangleScope(path.scope); + if (!path.isProgram() || mangler.topLevel) + mangler.mangleScope(path.scope); } }); } diff --git a/packages/babel-plugin-minify-mangle-names/src/scope-tracker.js b/packages/babel-plugin-minify-mangle-names/src/scope-tracker.js index 54675282d..72601f8a7 100644 --- a/packages/babel-plugin-minify-mangle-names/src/scope-tracker.js +++ b/packages/babel-plugin-minify-mangle-names/src/scope-tracker.js @@ -5,20 +5,18 @@ const isLabelIdentifier = require("./is-label-identifier"); * Scope - References, Bindings */ module.exports = class ScopeTracker { - constructor({ reuse }) { - this.references = new Map; - this.bindings = new Map; - - this.reuse = reuse; + constructor() { + this.references = new Map(); + this.bindings = new Map(); } // Register a new Scope and initiliaze it with empty sets addScope(scope) { if (!this.references.has(scope)) { - this.references.set(scope, new CountedSet); + this.references.set(scope, new CountedSet()); } if (!this.bindings.has(scope)) { - this.bindings.set(scope, new Map); + this.bindings.set(scope, new Map()); } } @@ -36,13 +34,10 @@ module.exports = class ScopeTracker { if (binding && binding.scope === parent) { break; } - } while (parent = parent.parent); + } while ((parent = parent.parent)); } hasReference(scope, name) { - if (!this.reuse) { - return scope.hasReference(name); - } if (!this.references.has(scope)) { this.addScope(scope); this.updateScope(scope); @@ -108,7 +103,7 @@ module.exports = class ScopeTracker { if (binding.scope === parent) { break; } - } while (parent = parent.parent); + } while ((parent = parent.parent)); } addBinding(binding) { @@ -122,9 +117,6 @@ module.exports = class ScopeTracker { } hasBinding(scope, name) { - if (!this.reuse) { - return scope.hasBinding(name); - } return this.bindings.get(scope).has(name); } @@ -142,7 +134,10 @@ module.exports = class ScopeTracker { // with a throw statement. This helps in understanding where it // happens to debug it. updateScope(scope) { - throw new Error("Tracker received a scope it doesn't know about yet. Please report this - https://github.com/babel/babili/issues/new"); + /* eslint-disable no-unreachable */ + throw new Error( + "Tracker received a scope it doesn't know about yet. Please report this - https://github.com/babel/babili/issues/new" + ); const tracker = this; scope.path.traverse({ @@ -153,5 +148,6 @@ module.exports = class ScopeTracker { } } }); + /* eslint-enable */ } }; From 6ba27de510a0143c7818c23cfce3c1ce93fb83e2 Mon Sep 17 00:00:00 2001 From: Boopathi Rajaa Date: Fri, 19 May 2017 15:07:01 +0200 Subject: [PATCH 03/10] Fix scope tracking --- .../__tests__/mangle-names-reuse-test.js | 28 ++-- .../src/fixup-var-scoping.js | 55 ++++++++ .../src/index.js | 90 ++++++------- .../src/scope-tracker.js | 127 ++++++++---------- 4 files changed, 166 insertions(+), 134 deletions(-) create mode 100644 packages/babel-plugin-minify-mangle-names/src/fixup-var-scoping.js diff --git a/packages/babel-plugin-minify-mangle-names/__tests__/mangle-names-reuse-test.js b/packages/babel-plugin-minify-mangle-names/__tests__/mangle-names-reuse-test.js index 58ccd9741..325b16ed5 100644 --- a/packages/babel-plugin-minify-mangle-names/__tests__/mangle-names-reuse-test.js +++ b/packages/babel-plugin-minify-mangle-names/__tests__/mangle-names-reuse-test.js @@ -1,16 +1,14 @@ jest.autoMockOff(); const traverse = require("babel-traverse").default; -const babel = require("babel-core"); -const unpad = require("../../../utils/unpad"); +const babel = require("babel-core"); +const unpad = require("../../../utils/unpad"); function transform(code, options = {}, sourceType = "script") { options.reuse = true; - return babel.transform(code, { + return babel.transform(code, { sourceType, - plugins: [ - [require("../src/index"), options], - ], + plugins: [[require("../src/index"), options]] }).code; } @@ -293,7 +291,7 @@ describe("mangle-names", () => { expect(transform(source)).toBe(expected); }); - it("should handle only think in function scopes", () => { + it("should handle only thunk in function scopes", () => { const source = unpad(` function foo() { function xx(bar, baz) { @@ -449,7 +447,9 @@ describe("mangle-names", () => { var a = 2; } `); - expect(transform(source, { blacklist: {foo: true, bar: true }})).toBe(expected); + expect(transform(source, { blacklist: { foo: true, bar: true } })).toBe( + expected + ); }); it("should handle deeply nested paths with no bindings", () => { @@ -591,13 +591,13 @@ describe("mangle-names", () => { const first = babel.transform(srcTxt, { plugins: ["transform-es2015-block-scoping"], - code: false, + code: false }); traverse.clearCache(); const actual = babel.transformFromAst(first.ast, null, { - plugins: [[require("../src/index"), { reuse: true }]], + plugins: [[require("../src/index"), { reuse: true }]] }).code; const expected = unpad(` @@ -639,13 +639,13 @@ describe("mangle-names", () => { const first = babel.transform(srcTxt, { plugins: ["transform-es2015-block-scoping"], - code: false, + code: false }); traverse.clearCache(); const actual = babel.transformFromAst(first.ast, null, { - plugins: [[require("../src/index"), { reuse: true }]], + plugins: [[require("../src/index"), { reuse: true }]] }).code; const expected = unpad(` @@ -795,7 +795,7 @@ describe("mangle-names", () => { b(); })(); `); - expect(transform(source, {keepFnName: true})).toBe(expected); + expect(transform(source, { keepFnName: true })).toBe(expected); }); it("should NOT mangle classes when keepClassName is true", () => { @@ -821,7 +821,7 @@ describe("mangle-names", () => { a(); })(); `); - expect(transform(source, {keepClassName: true})).toBe(expected); + expect(transform(source, { keepClassName: true })).toBe(expected); }); it("should mangle variable re-declaration / K violations", () => { diff --git a/packages/babel-plugin-minify-mangle-names/src/fixup-var-scoping.js b/packages/babel-plugin-minify-mangle-names/src/fixup-var-scoping.js new file mode 100644 index 000000000..61adfdfb9 --- /dev/null +++ b/packages/babel-plugin-minify-mangle-names/src/fixup-var-scoping.js @@ -0,0 +1,55 @@ +// this fixes a bug where converting let to var +// doesn't change the binding's scope to function scope +// https://github.com/babel/babel/issues/4818 +module.exports = function(mangler) { + mangler.program.traverse({ + VariableDeclaration(path) { + if (path.node.kind !== "var") { + return; + } + const fnScope = path.scope.getFunctionParent(); + const bindingIds = path.getOuterBindingIdentifierPaths(); + + for (const name in bindingIds) { + const binding = path.scope.getBinding(name); + + // var isn't hoisted to fnScope + if (binding.scope !== fnScope) { + const existingBinding = fnScope.bindings[name]; + // make sure we are clear that the fnScope doesn't already have + // an existing binding + if (!existingBinding) { + // move binding to the function scope + + // update our scopeTracker first before + // we mutate the scope + mangler.scopeTracker.moveBinding(binding, fnScope); + + fnScope.bindings[name] = binding; + binding.scope = fnScope; + delete binding.scope.bindings[name]; + } else { + // we need a new binding that's valid in both the scopes + // binding.scope and fnScope + const newName = fnScope.generateUid( + binding.scope.generateUid(name) + ); + + // rename binding in the original scope + mangler.rename(binding.scope, binding, name, newName); + + // move binding to fnScope as newName + + // update our scopeTracker first before + // we mutate the scope + mangler.scopeTracker.moveBinding(binding, fnScope); + + fnScope.bindings[newName] = binding; + binding.scope = fnScope; + delete binding.scope.bindings[newName]; + } + } + } + } + }); +}; diff --git a/packages/babel-plugin-minify-mangle-names/src/index.js b/packages/babel-plugin-minify-mangle-names/src/index.js index 2eb71e822..92dd20f04 100644 --- a/packages/babel-plugin-minify-mangle-names/src/index.js +++ b/packages/babel-plugin-minify-mangle-names/src/index.js @@ -2,6 +2,7 @@ const Charset = require("./charset"); const ScopeTracker = require("./scope-tracker"); const isLabelIdentifier = require("./is-label-identifier"); const bfsTraverseCreator = require("./bfs-traverse"); +const fixupVarScoping = require("./fixup-var-scoping"); const { markEvalScopes, @@ -41,8 +42,8 @@ module.exports = babel => { run() { this.crawlScope(); - this.fixup(); this.collect(); + this.fixup(); this.charset.sort(); this.mangle(); } @@ -57,46 +58,7 @@ module.exports = babel => { } fixup() { - const mangler = this; - this.program.traverse({ - // this fixes a bug where converting let to var - // doesn't change the binding's scope to function scope - // https://github.com/babel/babel/issues/4818 - VariableDeclaration(path) { - if (path.node.kind !== "var") { - return; - } - const ids = path.getOuterBindingIdentifiers(); - const fnScope = path.scope.getFunctionParent(); - Object.keys(ids).forEach(id => { - const binding = path.scope.getBinding(id); - - if (binding.scope !== fnScope) { - const existingBinding = fnScope.bindings[id]; - if (!existingBinding) { - // move binding to the function scope - fnScope.bindings[id] = binding; - binding.scope = fnScope; - delete binding.scope.bindings[id]; - } else { - // we need a new binding that's valid in both the scopes - // binding.scope and fnScope - const newName = fnScope.generateUid( - binding.scope.generateUid(id) - ); - - // rename binding in the original scope - mangler.rename(binding.scope, binding, id, newName); - - // move binding to fnScope as newName - fnScope.bindings[newName] = binding; - binding.scope = fnScope; - delete binding.scope.bindings[newName]; - } - } - }); - } - }); + fixupVarScoping(this); } collect() { @@ -120,7 +82,12 @@ module.exports = babel => { if (isLabelIdentifier(path)) return; const { scope, node: { name } } = path; const binding = scope.getBinding(name); - scopeTracker.addReference(scope, binding, name); + if (!binding) { + if (scope.hasGlobal(name)) return; + throw new Error("Something went wrong"); + } else { + scopeTracker.addReference(scope, binding, name); + } }, BindingIdentifier(path) { @@ -138,7 +105,13 @@ module.exports = babel => { } throw new Error("binding not found " + name); } - scopeTracker.addBinding(binding); + if (binding.identifier === path.node) { + scopeTracker.addBinding(binding); + } else { + //constant violation + // console.log("adding constant violation for ", name); + scopeTracker.addReference(scope, binding, name); + } } }; @@ -235,6 +208,14 @@ module.exports = babel => { let next; do { next = getNext(); + // console.log( + // next, + // !t.isValidIdentifier(next), + // scopeTracker.hasBinding(scope, next), + // scope.hasGlobal(next), + // scopeTracker.hasReference(scope, next), + // !scopeTracker.canUseInReferencedScopes(binding, next) + // ); } while ( !t.isValidIdentifier(next) || scopeTracker.hasBinding(scope, next) || @@ -245,6 +226,8 @@ module.exports = babel => { resetNext(); + // console.log("-----------------"); + // console.log("mangling", oldName, next); mangler.rename(scope, binding, oldName, next); } } @@ -294,15 +277,13 @@ module.exports = babel => { for (let i = 0; i < violations.length; i++) { if (violations[i].isLabeledStatement()) continue; - // const bindings = violations[i].getBindingIdentifierPaths(); - // Object.keys(bindings).map(b => { - // if (b === oldName && !bindings[b][PATH_RENAME_MARKER]) { - // bindings[b].replaceWith(t.identifier(newName)); - // bindings[b][PATH_RENAME_MARKER] = true; - // } - // }); - this.renameBindingIds(violations[i], oldName, newName); + scopeTracker.updateReference( + violations[i].scope, + binding, + oldName, + newName + ); } // update all referenced places @@ -347,11 +328,18 @@ module.exports = babel => { mangler.renamedNodes.add(path.node); path.replaceWith(t.identifier(newName)); mangler.renamedNodes.add(path.node); + // console.log( + // "renaming ref in ", + // path.scope.path.type, + // oldName, + // newName + // ); scopeTracker.updateReference(path.scope, binding, oldName, newName); } else if (mangler.renamedNodes.has(path.node)) { // already renamed, // just update the references + // throw new Error("Who is replacing again"); scopeTracker.updateReference(path.scope, binding, oldName, newName); } else { throw new Error( diff --git a/packages/babel-plugin-minify-mangle-names/src/scope-tracker.js b/packages/babel-plugin-minify-mangle-names/src/scope-tracker.js index 72601f8a7..1a9aa6b1b 100644 --- a/packages/babel-plugin-minify-mangle-names/src/scope-tracker.js +++ b/packages/babel-plugin-minify-mangle-names/src/scope-tracker.js @@ -2,8 +2,12 @@ const CountedSet = require("./counted-set"); const isLabelIdentifier = require("./is-label-identifier"); /** - * Scope - References, Bindings + * Scope Tracker + * references: Map > + * bindings: Map > */ + +let counter = 0; module.exports = class ScopeTracker { constructor() { this.references = new Map(); @@ -23,50 +27,57 @@ module.exports = class ScopeTracker { addReference(scope, binding, name) { let parent = scope; do { - if (!this.references.has(parent)) { - this.addScope(parent); - this.updateScope(parent); - } this.references.get(parent).add(name); - - // here binding is undefined for globals, - // so we just add to all scopes up - if (binding && binding.scope === parent) { - break; - } + if (!binding) throw new Error("How did global come here"); + if (binding.scope === parent) break; } while ((parent = parent.parent)); } hasReference(scope, name) { - if (!this.references.has(scope)) { - this.addScope(scope); - this.updateScope(scope); - } return this.references.get(scope).has(name); } + updateReference(scope, binding, oldName, newName) { + let parent = scope; + do { + const ref = this.references.get(parent); + // if (!ref.has(oldName)) { + // throw new Error("ref " + oldName + " not found"); + // } + ref.delete(oldName); + ref.add(newName); + // console.log("adding", newName, "to", parent.path.type); + if (!binding) throw new Error("How did global get here"); + if (binding.scope === parent) break; + } while ((parent = parent.parent)); + } + + hasBindingOrReference(scope, binding, name) { + return this.hasReference(scope, name) || this.hasBinding(scope, name); + } + canUseInReferencedScopes(binding, next) { const tracker = this; - if (tracker.hasReference(binding.scope, next)) { + if (tracker.hasBindingOrReference(binding.scope, binding, next)) { return false; } for (let i = 0; i < binding.constantViolations.length; i++) { const violation = binding.constantViolations[i]; - if (tracker.hasReference(violation.scope, next)) { + if (tracker.hasBindingOrReference(violation.scope, binding, next)) { return false; } } - for (let i = 0; i < binding.referencePaths; i++) { + for (let i = 0; i < binding.referencePaths.length; i++) { const ref = binding.referencePaths[i]; if (!ref.isIdentifier()) { let canUse = true; ref.traverse({ ReferencedIdentifier(path) { if (path.node.name !== next) return; - if (tracker.hasReference(path.scope, next)) { + if (tracker.hasBindingOrReference(path.scope, binding, next)) { canUse = false; } } @@ -75,7 +86,7 @@ module.exports = class ScopeTracker { return canUse; } } else if (!isLabelIdentifier(ref)) { - if (tracker.hasReference(ref.scope, next)) { + if (tracker.hasBindingOrReference(ref.scope, binding, next)) { return false; } } @@ -84,36 +95,39 @@ module.exports = class ScopeTracker { return true; } - updateReference(scope, binding, oldName, newName) { - let parent = scope; - do { - if (!this.references.has(parent)) { - this.addScope(parent); - this.updateScope(parent); - } - - // update - const ref = this.references.get(parent); - if (ref.has(oldName)) { - ref.delete(oldName); - ref.add(newName); - } - // else already renamed - - if (binding.scope === parent) { - break; - } - } while ((parent = parent.parent)); - } - addBinding(binding) { if (!binding) { return; } + const bindings = this.bindings.get(binding.scope); - if (!bindings.has(binding.identifier.name)) { - bindings.set(binding.identifier.name, binding); + const existingBinding = bindings.get(binding.identifier.name); + + if (existingBinding && existingBinding !== binding) { + throw new Error( + "Binding " + + existingBinding.identifier.name + + "already exists. " + + "Trying to add " + + binding.identifier.name + ); } + + bindings.set(binding.identifier.name, binding); + } + + // required for fixup-var-scope + moveBinding(binding, toScope) { + // console.log( + // "moving binding", + // binding.identifier.name, + // "to", + // toScope.path.type, + // "from", + // binding.scope.path.type + // ); + this.bindings.get(binding.scope).delete(binding.identifier.name); + this.bindings.get(toScope).set(binding.identifier.name, binding); } hasBinding(scope, name) { @@ -125,29 +139,4 @@ module.exports = class ScopeTracker { bindings.set(newName, bindings.get(oldName)); bindings.delete(oldName); } - - // This is a fallback option and is used when something happens - - // during traversal and checks we find that a scope doesn't - // exist in the tracker - // - // This should NOT happen ultimately. Just used as a fallback - // with a throw statement. This helps in understanding where it - // happens to debug it. - updateScope(scope) { - /* eslint-disable no-unreachable */ - throw new Error( - "Tracker received a scope it doesn't know about yet. Please report this - https://github.com/babel/babili/issues/new" - ); - - const tracker = this; - scope.path.traverse({ - ReferencedIdentifier(path) { - if (path.scope === scope) { - const binding = scope.getBinding(path.node.name); - tracker.addReference(scope, binding, path.node.name); - } - } - }); - /* eslint-enable */ - } }; From 769c11aef4bc5b609746e6ea5e6064d7352b7603 Mon Sep 17 00:00:00 2001 From: Boopathi Rajaa Date: Sun, 21 May 2017 22:23:43 +0200 Subject: [PATCH 04/10] Fix tests --- .../__tests__/mangle-names-reuse-test.js | 1342 ----------------- .../__tests__/mangle-names-test.js | 868 +++++------ .../src/index.js | 2 +- 3 files changed, 404 insertions(+), 1808 deletions(-) delete mode 100644 packages/babel-plugin-minify-mangle-names/__tests__/mangle-names-reuse-test.js diff --git a/packages/babel-plugin-minify-mangle-names/__tests__/mangle-names-reuse-test.js b/packages/babel-plugin-minify-mangle-names/__tests__/mangle-names-reuse-test.js deleted file mode 100644 index 325b16ed5..000000000 --- a/packages/babel-plugin-minify-mangle-names/__tests__/mangle-names-reuse-test.js +++ /dev/null @@ -1,1342 +0,0 @@ -jest.autoMockOff(); - -const traverse = require("babel-traverse").default; -const babel = require("babel-core"); -const unpad = require("../../../utils/unpad"); - -function transform(code, options = {}, sourceType = "script") { - options.reuse = true; - return babel.transform(code, { - sourceType, - plugins: [[require("../src/index"), options]] - }).code; -} - -function transformWithSimplify(code, options = {}, sourceType = "script") { - options.reuse = true; - return babel.transform(code, { - sourceType, - plugins: [ - require("../../babel-plugin-minify-simplify/src/index"), - [require("../src/index"), options] - ] - }).code; -} - -describe("mangle-names", () => { - it("should not mangle names in the global namespace", () => { - const source = unpad(` - var Foo = 1; - `); - const expected = unpad(` - var Foo = 1; - `); - - expect(transform(source)).toBe(expected); - }); - - it("should mangle names", () => { - const source = unpad(` - function foo() { - var xxx = 1; - if (xxx) { - console.log(xxx); - } - } - `); - const expected = unpad(` - function foo() { - var a = 1; - if (a) { - console.log(a); - } - } - `); - - expect(transform(source)).toBe(expected); - }); - - it("should handle name collisions", () => { - const source = unpad(` - function foo() { - var x = 2; - var xxx = 1; - if (xxx) { - console.log(xxx + x); - } - } - `); - const expected = unpad(` - function foo() { - var a = 2; - var b = 1; - if (b) { - console.log(b + a); - } - } - `); - - expect(transform(source)).toBe(expected); - }); - - it("should be fine with shadowing", () => { - const source = unpad(` - var a = 1; - function foo() { - var xxx = 1; - if (xxx) { - console.log(xxx); - } - } - `); - const expected = unpad(` - var a = 1; - function foo() { - var a = 1; - if (a) { - console.log(a); - } - } - `); - - expect(transform(source)).toBe(expected); - }); - - it("should not shadow outer references", () => { - const source = unpad(` - function bar() { - function foo(a, b, c) { - lol(a,b,c); - } - - function lol() {} - } - `); - const expected = unpad(` - function bar() { - function a(d, a, e) { - b(d, a, e); - } - - function b() {} - } - `); - - expect(transform(source)).toBe(expected); - }); - - it("should mangle args", () => { - const source = unpad(` - function foo(xxx) { - if (xxx) { - console.log(xxx); - } - } - `); - const expected = unpad(` - function foo(a) { - if (a) { - console.log(a); - } - } - `); - - expect(transform(source)).toBe(expected); - }); - - it("should ignore labels", () => { - const source = unpad(` - function foo() { - meh: for (;;) { - continue meh; - } - } - `); - - const expected = unpad(` - function foo() { - meh: for (;;) { - continue meh; - } - } - `); - - expect(transform(source)).toBe(expected); - }); - - it("should not have labels conflicting with bindings", () => { - const source = unpad(` - function foo() { - meh: for (;;) { - var meh; - break meh; - } - } - `); - - const expected = unpad(` - function foo() { - meh: for (;;) { - var a; - break meh; - } - } - `); - - expect(transform(source)).toBe(expected); - }); - - // https://phabricator.babeljs.io/T6957 - it("labels should not shadow bindings", () => { - const source = unpad(` - function foo() { - var meh; - meh: for (;;) { - break meh; - } - return meh; - } - `); - - const expected = unpad(` - function foo() { - var a; - meh: for (;;) { - break meh; - } - return a; - } - `); - - expect(transform(source)).toBe(expected); - }); - - it("labels should not shadow bindings 2", () => { - const source = unpad(` - function f(a) { - try { - a: { - console.log(a); - } - } catch ($a) { } - } - `); - const expected = unpad(` - function f(b) { - try { - a: { - console.log(b); - } - } catch (a) {} - } - `); - expect(transform(source)).toBe(expected); - }); - - it("should be order independent", () => { - const source = unpad(` - function foo() { - function bar(aaa, bbb, ccc) { - baz(aaa, bbb, ccc); - } - function baz() { - var baz = who(); - baz.bam(); - } - bar(); - } - `); - - const expected = unpad(` - function foo() { - function a(a, c, d) { - b(a, c, d); - } - function b() { - var a = who(); - a.bam(); - } - a(); - } - `); - - expect(transform(source)).toBe(expected); - }); - - it("should be order independent 2", () => { - const source = unpad(` - function foo() { - (function bar() { - bar(); - return function() { - var bar = wow(); - bar.woo(); - }; - })(); - } - `); - - const expected = unpad(` - function foo() { - (function a() { - a(); - return function () { - var a = wow(); - a.woo(); - }; - })(); - } - `); - - expect(transform(source)).toBe(expected); - }); - - it("should handle only thunk in function scopes", () => { - const source = unpad(` - function foo() { - function xx(bar, baz) { - if (1) { - yy(bar, baz); - } - } - function yy(){} - } - `); - const expected = unpad(` - function foo() { - function a(a, c) { - if (1) { - b(a, c); - } - } - function b() {} - } - `); - - expect(transform(source)).toBe(expected); - }); - - it("should be fine with shadowing 2", () => { - const source = unpad(` - function foo() { - function xx(bar, baz) { - return function(boo, foo) { - bar(boo, foo); - }; - } - function yy(){} - } - `); - const expected = unpad(` - function foo() { - function a(a, b) { - return function (b, c) { - a(b, c); - }; - } - function b() {} - } - `); - - expect(transform(source)).toBe(expected); - }); - - it("should not be confused by scopes", () => { - const source = unpad(` - function foo() { - function bar() { - var baz; - if (baz) { - bam(); - } - } - function bam() {} - } - `); - const expected = unpad(` - function foo() { - function a() { - var a; - if (a) { - b(); - } - } - function b() {} - } - `); - - expect(transform(source)).toBe(expected); - }); - - it("should not be confused by scopes (closures)", () => { - const source = unpad(` - function foo() { - function bar(baz) { - return function() { - bam(); - }; - } - function bam() {} - } - `); - const expected = unpad(` - function foo() { - function a(a) { - return function () { - b(); - }; - } - function b() {} - } - `); - - expect(transform(source)).toBe(expected); - }); - - it("should handle recursion", () => { - const source = unpad(` - function bar() { - function foo(a, b, c) { - foo(a,b,c); - } - } - `); - const expected = unpad(` - function bar() { - function a(d, e, b) { - a(d, e, b); - } - } - `); - - expect(transform(source)).toBe(expected); - }); - - it("should handle global name conflict", () => { - const source = unpad(` - function e() { - function foo() { - b = bar(); - } - function bar() {} - } - `); - const expected = unpad(` - function e() { - function a() { - b = c(); - } - function c() {} - } - `); - - expect(transform(source)).toBe(expected); - }); - - it("should handle global name", () => { - const source = unpad(` - function foo() { - var bar = 1; - var baz = 2; - } - `); - - const expected = unpad(` - function foo() { - var bar = 1; - var a = 2; - } - `); - expect(transform(source, { blacklist: { foo: true, bar: true } })).toBe( - expected - ); - }); - - it("should handle deeply nested paths with no bindings", () => { - const source = unpad(` - function xoo() { - function foo(zz, xx, yy) { - function bar(zip, zap, zop) { - return function(bar) { - zap(); - return function() { - zip(); - } - } - } - } - } - `); - const expected = unpad(` - function xoo() { - function a(a, b, c) { - function d(a, b, c) { - return function (c) { - b(); - return function () { - a(); - }; - }; - } - } - } - `); - expect(transform(source)).toBe(expected); - }); - - it("should handle try/catch", () => { - const source = unpad(` - function xoo() { - var e; - try {} catch (e) { - - } - } - `); - const expected = unpad(` - function xoo() { - var a; - try {} catch (a) {} - } - `); - expect(transform(source)).toBe(expected); - }); - - it("should not mangle vars in scope with eval", () => { - const source = unpad(` - function foo() { - var inScopeOuter = 1; - (function () { - var inScopeInner = 2; - eval("inScopeInner + inScopeOuter"); - (function () { - var outOfScope = 1; - })(); - })(); - } - `); - const expected = unpad(` - function foo() { - var inScopeOuter = 1; - (function () { - var inScopeInner = 2; - eval("inScopeInner + inScopeOuter"); - (function () { - var a = 1; - })(); - })(); - } - `); - expect(transform(source)).toBe(expected); - }); - - it("should mangle names with local eval bindings", () => { - const source = unpad(` - function eval() {} - function foo() { - var bar = 1; - eval('...'); - } - `); - const expected = unpad(` - function eval() {} - function foo() { - var a = 1; - eval('...'); - } - `); - expect(transform(source)).toBe(expected); - }); - - it("should mangle names with option eval = true", () => { - const source = unpad(` - function foo() { - var inScopeOuter = 1; - (function () { - var inScopeInner = 2; - eval("..."); - (function () { - var outOfScope = 1; - })(); - })(); - } - `); - const expected = unpad(` - function foo() { - var a = 1; - (function () { - var a = 2; - eval("..."); - (function () { - var a = 1; - })(); - })(); - } - `); - expect(transform(source, { eval: true })).toBe(expected); - }); - - it("should integrate with block scoping plugin", () => { - const srcTxt = unpad(` - function f(x) { - for (let i = 0; i; i++) { - let n; - if (n) { - return; - } - g(() => n); - } - } - `); - - const first = babel.transform(srcTxt, { - plugins: ["transform-es2015-block-scoping"], - code: false - }); - - traverse.clearCache(); - - const actual = babel.transformFromAst(first.ast, null, { - plugins: [[require("../src/index"), { reuse: true }]] - }).code; - - const expected = unpad(` - function f(a) { - var b = function (a) { - var b = void 0; - if (b) { - return { - v: void 0 - }; - } - g(() => b); - }; - - for (var d = 0; d; d++) { - var c = b(d); - if (typeof c === "object") return c.v; - } - } - `); - - expect(actual).toBe(expected); - }); - - it("should integrate with block scoping plugin 2", () => { - const srcTxt = unpad(` - (function () { - function bar() { - if (smth) { - let entries = blah(); - entries(); - } - foo(); - } - function foo() { } - module.exports = { bar }; - })(); - `); - - const first = babel.transform(srcTxt, { - plugins: ["transform-es2015-block-scoping"], - code: false - }); - - traverse.clearCache(); - - const actual = babel.transformFromAst(first.ast, null, { - plugins: [[require("../src/index"), { reuse: true }]] - }).code; - - const expected = unpad(` - (function () { - function a() { - if (smth) { - var a = blah(); - a(); - } - b(); - } - function b() {} - module.exports = { bar: a }; - })(); - `); - - expect(actual).toBe(expected); - }); - - it("should keep mangled named consistent across scopes when defined later on", () => { - const source = unpad(` - (function() { - function foo() { - { - var baz = true; - - { - bar(); - } - } - } - - function bar() {} - }()); - `); - - const expected = unpad(` - (function () { - function a() { - { - var a = true; - - { - b(); - } - } - } - - function b() {} - })(); - `); - - expect(transform(source)).toBe(expected); - }); - - it("should correctly mangle in nested loops", () => { - const source = unpad(` - (function () { - for (let x in foo) { - for (let y in foo[x]) { - alert(foo[x][y]); - } - } - })(); - `); - - const expected = unpad(` - (function () { - for (let a in foo) { - for (let b in foo[a]) { - alert(foo[a][b]); - } - } - })(); - `); - - expect(transform(source)).toBe(expected); - }); - - // #issue55, #issue57 - it("should correctly mangle function declarations in different order", () => { - const source = unpad(` - (function(){ - (function() { - for (let x in y) y[x]; - f(() => { g() }); - })(); - function g() {} - })(); - `); - - const ast = babel.transform(source, { - presets: ["env"], - sourceType: "script", - code: false - }).ast; - - traverse.clearCache(); - - const actual = babel.transformFromAst(ast, null, { - sourceType: "script", - plugins: [require("../src/index")] - }).code; - - const expected = unpad(` - "use strict"; - - (function () { - (function () { - for (var b in y) { - y[b]; - }f(function () { - a(); - }); - })(); - function a() {} - })(); - `); - - expect(actual).toBe(expected); - }); - - it("should NOT mangle functions when keepFnName is true", () => { - const source = unpad(` - (function() { - var foo = function foo() { - foo(); - } - function bar() { - foo(); - } - bar(); - var baz = foo; - baz(); - })(); - `); - const expected = unpad(` - (function () { - var a = function foo() { - foo(); - }; - function bar() { - a(); - } - bar(); - var b = a; - b(); - })(); - `); - expect(transform(source, { keepFnName: true })).toBe(expected); - }); - - it("should NOT mangle classes when keepClassName is true", () => { - const source = unpad(` - (function() { - class Foo {} - const Bar = class Bar extends Foo {} - var foo = class Baz {} - function bar() { - new foo(); - } - bar(); - })(); - `); - const expected = unpad(` - (function () { - class Foo {} - const b = class Bar extends Foo {}; - var c = class Baz {}; - function a() { - new c(); - } - a(); - })(); - `); - expect(transform(source, { keepClassName: true })).toBe(expected); - }); - - it("should mangle variable re-declaration / K violations", () => { - const source = unpad(` - !function () { - var foo = 1; - foo++; - var foo = 2; - foo++; - } - `); - const expected = unpad(` - !function () { - var a = 1; - a++; - var a = 2; - a++; - }; - `); - expect(transform(source)).toBe(expected); - }); - - it("should handle K violations - 2", () => { - const source = unpad(` - !function () { - var bar = 1; - bar--; - var bar = 10; - foo(bar) - function foo() { - var foo = 10; - foo++; - var foo = 20; - foo(foo); - } - } - `); - const expected = unpad(` - !function () { - var b = 1; - b--; - var b = 10; - a(b); - function a() { - var a = 10; - a++; - var a = 20; - a(a); - } - }; - `); - expect(transform(source)).toBe(expected); - }); - - it("should work with redeclarations", () => { - const source = unpad(` - (function () { - var x = y; - x = z; - x; - })(); - `); - const expected = unpad(` - (function () { - var a = y; - a = z; - a; - })(); - `); - expect(transform(source)).toBe(expected); - }); - - it("should reuse removed vars", () => { - const source = unpad(` - function Foo() { - var a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z; - var A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z; - var $, _; - a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z; - A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z; - $, _; - function Foo() { - var a, b, c, d, e, f, g, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z; - var A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z; - var $, _; - a, b, c, d, e, f, g, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z; - A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z; - $, _; - function Foo() { - var a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z; - var A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z; - var $, _; - a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z; - A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z; - $, _; - } - Foo(); - } - Foo(); - } - `); - const expected = unpad(` - function Foo() { - var ba, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y; - var z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y; - var Z, $; - ba, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y; - z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y; - Z, $; - function aa() { - var aa, a, b, c, d, e, f, g, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y; - var z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y; - var Z, $; - aa, a, b, c, d, e, f, g, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y; - z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y; - Z, $; - function h() { - var aa, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y; - var z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y; - var Z, $; - aa, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y; - z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y; - Z, $; - } - h(); - } - aa(); - } - `); - expect(transform(source)).toBe(expected); - }); - - it("should mangle both referenced and binding identifiers with K violations", () => { - const source = unpad(` - (function () { - var foo = bar, - foo = baz; - foo; - })(); - `); - const expected = unpad(` - (function () { - var a = bar, - a = baz; - a; - })(); - `); - expect(transform(source)).toBe(expected); - }); - - it("should handle export declarations", () => { - const source = unpad(` - const foo = 1; - export { foo }; - export const bar = 2; - export function baz(bar, foo) { - bar(); - foo(); - }; - export default function (bar, baz) { - bar(); - baz(); - } - `); - const expected = unpad(` - const foo = 1; - export { foo }; - export const bar = 2; - export function baz(a, b) { - a(); - b(); - }; - export default function (a, b) { - a(); - b(); - } - `); - expect(transform(source, {}, "module")).toBe(expected); - }); - - it("should find global scope properly", () => { - const source = unpad(` - class A {} - class B extends A {} - (function () { - class C { - constructor() { - new A(); - new B(); - C; - } - } - })(); - `); - const expected = unpad(` - class A {} - class B extends A {} - (function () { - class a { - constructor() { - new A(); - new B(); - a; - } - } - })(); - `); - expect(transform(source)).toBe(expected); - }); - - it("should mangle classes properly", () => { - const source = unpad(` - class A {} - class B {} - new A(); - new B(); - function a() { - class A {} - class B {} - new A(); - new B(); - } - `); - const expected = unpad(` - class A {} - class B {} - new A(); - new B(); - function a() { - class a {} - class b {} - new a(); - new b(); - } - `); - expect(transform(source)).toBe(expected); - }); - - // https://github.com/babel/babili/issues/138 - it("should handle class exports in modules - issue#138", () => { - const source = unpad(` - export class App extends Object {}; - `); - const expected = source; - expect(transform(source, {}, "module")).toBe(expected); - }); - - it("should not mangle the name arguments", () => { - const source = unpad(` - (function () { - var arguments = void 0; - (function () { - console.log(arguments); - })("argument"); - })(); - `); - const expected = source; - expect(transform(source)).toBe(expected); - }); - - it("should handle constant violations across multiple blocks", () => { - const source = unpad(` - function foo() { - var x;x;x; - { - var x;x;x; - function y() { - var x;x;x; - { - var x;x;x; - } - } - } - } - `); - const expected = unpad(` - function foo() { - var a;a;a; - { - var a;a;a; - function b() { - var a;a;a; - { - var a;a;a; - } - } - } - } - `); - expect(transform(source)).toBe(expected); - }); - - it("should work with if_return optimization changing fn scope", () => { - const source = unpad(` - function foo() { - if (x) - return; - function bar() {} - bar(a); - } - `); - const expected = unpad(` - function foo() { - function b() {} - x || b(a); - } - `); - expect(transformWithSimplify(source)).toBe(expected); - }); - - it("should fix #326, #369 - destructuring", () => { - const source = unpad(` - // issue#326 - function a() { - let foo, bar, baz; - ({foo, bar, baz} = {}); - return {foo, bar, baz}; - } - // issue#369 - function decodeMessage(message){ - let namespace; - let name; - let value = null; - - [, namespace, name, value] = message.split(',') || []; - console.log(name); - } - `); - const expected = unpad(` - // issue#326 - function a() { - let a, b, c; - ({ foo: a, bar: b, baz: c } = {}); - return { foo: a, bar: b, baz: c }; - } - // issue#369 - function decodeMessage(a) { - let b; - let c; - let d = null; - - [, b, c, d] = a.split(',') || []; - console.log(c); - } - `); - expect(transform(source)).toBe(expected); - }); - - it("should mangle topLevel when topLevel option is true", () => { - const source = unpad(` - function foo() { - if (FOO_ENV === "production") { - HELLO_WORLD.call(); - } - } - const FOO_ENV = "production"; - var HELLO_WORLD = function bar() { - new AbstractClass({ - [FOO_ENV]: "foo", - a: foo(HELLO_WORLD) - }); - }; - class AbstractClass {} - foo(); - `); - - const expected = unpad(` - function a() { - if (b === "production") { - c.call(); - } - } - const b = "production"; - var c = function e() { - new d({ - [b]: "foo", - a: a(c) - }); - }; - class d {} - a(); - `); - - expect(transform(source, { topLevel: true })).toBe(expected); - }); - - it("should fix #326, #369 - destructuring", () => { - const source = unpad(` - // issue#326 - function a() { - let foo, bar, baz; - ({foo, bar, baz} = {}); - return {foo, bar, baz}; - } - // issue#369 - function decodeMessage(message){ - let namespace; - let name; - let value = null; - - [, namespace, name, value] = message.split(',') || []; - console.log(name); - } - `); - const expected = unpad(` - // issue#326 - function a() { - let a, b, c; - ({ foo: a, bar: b, baz: c } = {}); - return { foo: a, bar: b, baz: c }; - } - // issue#369 - function decodeMessage(a) { - let b; - let c; - let d = null; - - [, b, c, d] = a.split(',') || []; - console.log(c); - } - `); - expect(transform(source)).toBe(expected); - }); - - it("should rename binding.identifier - issue#411", () => { - const source = unpad(` - !function () { - function e(e) { - foo(e); - } - return function () { - return e(); - }; - }(); - `); - const expected = unpad(` - !function () { - function a(a) { - foo(a); - } - return function () { - return a(); - }; - }(); - `); - expect(transform(source)).toBe(expected); - }); - - it("should fix issue#365 - classDeclaration with unsafe parent scope", () => { - const source = unpad(` - function foo() { - eval(""); - class A {} - class B {} - } - `); - expect(transform(source)).toBe(source); - }); - - it("should fix classDeclaration with unsafe program scope", () => { - const source = unpad(` - class A {} - class B {} - eval(""); - `); - expect(transform(source, { topLevel: true })).toBe(source); - }); - - it("should handle constant violations across multiple blocks", () => { - const source = unpad(` - function foo() { - var x;x;x; - { - var x;x;x; - function y() { - var x;x;x; - { - var x;x;x; - } - } - } - } - `); - const expected = unpad(` - function foo() { - var a;a;a; - { - var a;a;a; - function b() { - var a;a;a; - { - var a;a;a; - } - } - } - } - `); - expect(transform(source)).toBe(expected); - }); - - it("should work with if_return optimization changing fn scope", () => { - const source = unpad(` - function foo() { - if (x) - return; - function bar() {} - bar(a); - } - `); - const expected = unpad(` - function foo() { - function b() {} - x || b(a); - } - `); - expect(transformWithSimplify(source)).toBe(expected); - }); -}); diff --git a/packages/babel-plugin-minify-mangle-names/__tests__/mangle-names-test.js b/packages/babel-plugin-minify-mangle-names/__tests__/mangle-names-test.js index 91d66f825..40bf0acd2 100644 --- a/packages/babel-plugin-minify-mangle-names/__tests__/mangle-names-test.js +++ b/packages/babel-plugin-minify-mangle-names/__tests__/mangle-names-test.js @@ -5,7 +5,6 @@ const babel = require("babel-core"); const unpad = require("../../../utils/unpad"); function transform(code, options = {}, sourceType = "script") { - options.reuse = false; return babel.transform(code, { sourceType, plugins: [[require("../src/index"), options]] @@ -13,7 +12,6 @@ function transform(code, options = {}, sourceType = "script") { } function transformWithSimplify(code, options = {}, sourceType = "script") { - options.reuse = false; return babel.transform(code, { sourceType, plugins: [ @@ -25,48 +23,39 @@ function transformWithSimplify(code, options = {}, sourceType = "script") { describe("mangle-names", () => { it("should not mangle names in the global namespace", () => { - const source = unpad( - ` + const source = unpad(` var Foo = 1; - ` - ); - const expected = unpad( - ` + `); + const expected = unpad(` var Foo = 1; - ` - ); + `); expect(transform(source)).toBe(expected); }); it("should mangle names", () => { - const source = unpad( - ` + const source = unpad(` function foo() { var xxx = 1; if (xxx) { console.log(xxx); } } - ` - ); - const expected = unpad( - ` + `); + const expected = unpad(` function foo() { var a = 1; if (a) { console.log(a); } } - ` - ); + `); expect(transform(source)).toBe(expected); }); it("should handle name collisions", () => { - const source = unpad( - ` + const source = unpad(` function foo() { var x = 2; var xxx = 1; @@ -74,10 +63,8 @@ describe("mangle-names", () => { console.log(xxx + x); } } - ` - ); - const expected = unpad( - ` + `); + const expected = unpad(` function foo() { var a = 2; var b = 1; @@ -85,15 +72,13 @@ describe("mangle-names", () => { console.log(b + a); } } - ` - ); + `); expect(transform(source)).toBe(expected); }); it("should be fine with shadowing", () => { - const source = unpad( - ` + const source = unpad(` var a = 1; function foo() { var xxx = 1; @@ -101,127 +86,105 @@ describe("mangle-names", () => { console.log(xxx); } } - ` - ); - const expected = unpad( - ` + `); + const expected = unpad(` var a = 1; function foo() { - var b = 1; - if (b) { - console.log(b); + var a = 1; + if (a) { + console.log(a); } } - ` - ); + `); expect(transform(source)).toBe(expected); }); it("should not shadow outer references", () => { - const source = unpad( - ` + const source = unpad(` function bar() { function foo(a, b, c) { lol(a,b,c); } - function lol() {} } - ` - ); - const expected = unpad( - ` + `); + const expected = unpad(` function bar() { - function d(f, g, h) { - e(f, g, h); + function a(e, a, b) { + d(e, a, b); } - - function e() {} + function d() {} } - ` - ); + `); expect(transform(source)).toBe(expected); }); it("should mangle args", () => { - const source = unpad( - ` + const source = unpad(` function foo(xxx) { if (xxx) { console.log(xxx); } } - ` - ); - const expected = unpad( - ` + `); + const expected = unpad(` function foo(a) { if (a) { console.log(a); } } - ` - ); + `); expect(transform(source)).toBe(expected); }); it("should ignore labels", () => { - const source = unpad( - ` + const source = unpad(` function foo() { meh: for (;;) { continue meh; } } - ` - ); + `); - const expected = unpad( - ` + const expected = unpad(` function foo() { meh: for (;;) { continue meh; } } - ` - ); + `); expect(transform(source)).toBe(expected); }); it("should not have labels conflicting with bindings", () => { - const source = unpad( - ` + const source = unpad(` function foo() { meh: for (;;) { var meh; break meh; } } - ` - ); + `); - const expected = unpad( - ` + const expected = unpad(` function foo() { meh: for (;;) { var a; break meh; } } - ` - ); + `); expect(transform(source)).toBe(expected); }); // https://phabricator.babeljs.io/T6957 it("labels should not shadow bindings", () => { - const source = unpad( - ` + const source = unpad(` function foo() { var meh; meh: for (;;) { @@ -229,11 +192,9 @@ describe("mangle-names", () => { } return meh; } - ` - ); + `); - const expected = unpad( - ` + const expected = unpad(` function foo() { var a; meh: for (;;) { @@ -241,15 +202,13 @@ describe("mangle-names", () => { } return a; } - ` - ); + `); expect(transform(source)).toBe(expected); }); it("labels should not shadow bindings 2", () => { - const source = unpad( - ` + const source = unpad(` function f(a) { try { a: { @@ -257,25 +216,21 @@ describe("mangle-names", () => { } } catch ($a) { } } - ` - ); - const expected = unpad( - ` + `); + const expected = unpad(` function f(b) { try { a: { console.log(b); } - } catch (c) {} + } catch (a) {} } - ` - ); + `); expect(transform(source)).toBe(expected); }); it("should be order independent", () => { - const source = unpad( - ` + const source = unpad(` function foo() { function bar(aaa, bbb, ccc) { baz(aaa, bbb, ccc); @@ -286,30 +241,26 @@ describe("mangle-names", () => { } bar(); } - ` - ); + `); - const expected = unpad( - ` + const expected = unpad(` function foo() { - function a(c, d, e) { - b(c, d, e); + function a(a, c, d) { + b(a, c, d); } function b() { - var c = who(); - c.bam(); + var a = who(); + a.bam(); } a(); } - ` - ); + `); expect(transform(source)).toBe(expected); }); it("should be order independent 2", () => { - const source = unpad( - ` + const source = unpad(` function foo() { (function bar() { bar(); @@ -319,29 +270,25 @@ describe("mangle-names", () => { }; })(); } - ` - ); + `); - const expected = unpad( - ` + const expected = unpad(` function foo() { (function a() { a(); return function () { - var b = wow(); - b.woo(); + var a = wow(); + a.woo(); }; })(); } - ` - ); + `); expect(transform(source)).toBe(expected); }); - it("should handle only think in function scopes", () => { - const source = unpad( - ` + it("should handle only thunk in function scopes", () => { + const source = unpad(` function foo() { function xx(bar, baz) { if (1) { @@ -350,27 +297,23 @@ describe("mangle-names", () => { } function yy(){} } - ` - ); - const expected = unpad( - ` + `); + const expected = unpad(` function foo() { - function a(c, d) { + function a(a, c) { if (1) { - b(c, d); + b(a, c); } } function b() {} } - ` - ); + `); expect(transform(source)).toBe(expected); }); it("should be fine with shadowing 2", () => { - const source = unpad( - ` + const source = unpad(` function foo() { function xx(bar, baz) { return function(boo, foo) { @@ -379,27 +322,23 @@ describe("mangle-names", () => { } function yy(){} } - ` - ); - const expected = unpad( - ` + `); + const expected = unpad(` function foo() { - function a(c, d) { - return function (e, f) { - c(e, f); + function a(a, b) { + return function (b, c) { + a(b, c); }; } function b() {} } - ` - ); + `); expect(transform(source)).toBe(expected); }); it("should not be confused by scopes", () => { - const source = unpad( - ` + const source = unpad(` function foo() { function bar() { var baz; @@ -409,28 +348,24 @@ describe("mangle-names", () => { } function bam() {} } - ` - ); - const expected = unpad( - ` + `); + const expected = unpad(` function foo() { function a() { - var c; - if (c) { + var a; + if (a) { b(); } } function b() {} } - ` - ); + `); expect(transform(source)).toBe(expected); }); it("should not be confused by scopes (closures)", () => { - const source = unpad( - ` + const source = unpad(` function foo() { function bar(baz) { return function() { @@ -439,100 +374,82 @@ describe("mangle-names", () => { } function bam() {} } - ` - ); - const expected = unpad( - ` + `); + const expected = unpad(` function foo() { - function a(c) { + function a(a) { return function () { b(); }; } function b() {} } - ` - ); + `); expect(transform(source)).toBe(expected); }); it("should handle recursion", () => { - const source = unpad( - ` + const source = unpad(` function bar() { function foo(a, b, c) { foo(a,b,c); } } - ` - ); - const expected = unpad( - ` + `); + const expected = unpad(` function bar() { - function d(e, f, g) { - d(e, f, g); + function d(e, a, b) { + d(e, a, b); } } - ` - ); + `); expect(transform(source)).toBe(expected); }); it("should handle global name conflict", () => { - const source = unpad( - ` + const source = unpad(` function e() { function foo() { b = bar(); } function bar() {} } - ` - ); - const expected = unpad( - ` + `); + const expected = unpad(` function e() { function a() { b = c(); } function c() {} } - ` - ); + `); expect(transform(source)).toBe(expected); }); it("should handle global name", () => { - const source = unpad( - ` - function Foo() { - var foo = 3; + const source = unpad(` + function foo() { var bar = 1; var baz = 2; } - ` - ); + `); - const expected = unpad( - ` - function Foo() { - var foo = 3; - var a = 1; - var b = 2; + const expected = unpad(` + function foo() { + var bar = 1; + var a = 2; } - ` - ); + `); expect(transform(source, { blacklist: { foo: true, bar: false } })).toBe( expected ); }); it("should handle deeply nested paths with no bindings", () => { - const source = unpad( - ` + const source = unpad(` function xoo() { function foo(zz, xx, yy) { function bar(zip, zap, zop) { @@ -545,52 +462,44 @@ describe("mangle-names", () => { } } } - ` - ); - const expected = unpad( - ` + `); + const expected = unpad(` function xoo() { - function a(b, c, d) { - function e(f, g, h) { - return function (i) { - g(); + function a(a, b, c) { + function d(a, b, c) { + return function (c) { + b(); return function () { - f(); + a(); }; }; } } } - ` - ); + `); expect(transform(source)).toBe(expected); }); it("should handle try/catch", () => { - const source = unpad( - ` + const source = unpad(` function xoo() { var e; try {} catch (e) { } } - ` - ); - const expected = unpad( - ` + `); + const expected = unpad(` function xoo() { var a; - try {} catch (b) {} + try {} catch (a) {} } - ` - ); + `); expect(transform(source)).toBe(expected); }); it("should not mangle vars in scope with eval", () => { - const source = unpad( - ` + const source = unpad(` function foo() { var inScopeOuter = 1; (function () { @@ -601,10 +510,8 @@ describe("mangle-names", () => { })(); })(); } - ` - ); - const expected = unpad( - ` + `); + const expected = unpad(` function foo() { var inScopeOuter = 1; (function () { @@ -615,36 +522,30 @@ describe("mangle-names", () => { })(); })(); } - ` - ); + `); expect(transform(source)).toBe(expected); }); it("should mangle names with local eval bindings", () => { - const source = unpad( - ` + const source = unpad(` function eval() {} function foo() { var bar = 1; eval('...'); } - ` - ); - const expected = unpad( - ` + `); + const expected = unpad(` function eval() {} function foo() { var a = 1; eval('...'); } - ` - ); + `); expect(transform(source)).toBe(expected); }); it("should mangle names with option eval = true", () => { - const source = unpad( - ` + const source = unpad(` function foo() { var inScopeOuter = 1; (function () { @@ -655,28 +556,24 @@ describe("mangle-names", () => { })(); })(); } - ` - ); - const expected = unpad( - ` + `); + const expected = unpad(` function foo() { var a = 1; (function () { - var b = 2; + var a = 2; eval("..."); (function () { - var c = 1; + var a = 1; })(); })(); } - ` - ); + `); expect(transform(source, { eval: true })).toBe(expected); }); it("should integrate with block scoping plugin", () => { - const srcTxt = unpad( - ` + const srcTxt = unpad(` function f(x) { for (let i = 0; i; i++) { let n; @@ -686,8 +583,7 @@ describe("mangle-names", () => { g(() => n); } } - ` - ); + `); const first = babel.transform(srcTxt, { plugins: ["transform-es2015-block-scoping"], @@ -697,20 +593,19 @@ describe("mangle-names", () => { traverse.clearCache(); const actual = babel.transformFromAst(first.ast, null, { - plugins: [[require("../src/index"), { reuse: false }]] + plugins: [require("../src/index")] }).code; - const expected = unpad( - ` + const expected = unpad(` function f(a) { - var b = function (e) { - var h = void 0; - if (h) { + var b = function (a) { + var b = void 0; + if (b) { return { v: void 0 }; } - g(() => h); + g(() => b); }; for (var d = 0; d; d++) { @@ -718,15 +613,13 @@ describe("mangle-names", () => { if (typeof c === "object") return c.v; } } - ` - ); + `); expect(actual).toBe(expected); }); it("should integrate with block scoping plugin 2", () => { - const srcTxt = unpad( - ` + const srcTxt = unpad(` (function () { function bar() { if (smth) { @@ -738,8 +631,7 @@ describe("mangle-names", () => { function foo() { } module.exports = { bar }; })(); - ` - ); + `); const first = babel.transform(srcTxt, { plugins: ["transform-es2015-block-scoping"], @@ -749,31 +641,28 @@ describe("mangle-names", () => { traverse.clearCache(); const actual = babel.transformFromAst(first.ast, null, { - plugins: [[require("../src/index"), { reuse: false }]] + plugins: [require("../src/index")] }).code; - const expected = unpad( - ` + const expected = unpad(` (function () { function a() { if (smth) { - var c = blah(); - c(); + var a = blah(); + a(); } b(); } function b() {} module.exports = { bar: a }; })(); - ` - ); + `); expect(actual).toBe(expected); }); it("should keep mangled named consistent across scopes when defined later on", () => { - const source = unpad( - ` + const source = unpad(` (function() { function foo() { { @@ -787,15 +676,13 @@ describe("mangle-names", () => { function bar() {} }()); - ` - ); + `); - const expected = unpad( - ` + const expected = unpad(` (function () { function a() { { - var c = true; + var a = true; { b(); @@ -805,15 +692,13 @@ describe("mangle-names", () => { function b() {} })(); - ` - ); + `); expect(transform(source)).toBe(expected); }); it("should correctly mangle in nested loops", () => { - const source = unpad( - ` + const source = unpad(` (function () { for (let x in foo) { for (let y in foo[x]) { @@ -821,11 +706,9 @@ describe("mangle-names", () => { } } })(); - ` - ); + `); - const expected = unpad( - ` + const expected = unpad(` (function () { for (let a in foo) { for (let b in foo[a]) { @@ -833,16 +716,14 @@ describe("mangle-names", () => { } } })(); - ` - ); + `); expect(transform(source)).toBe(expected); }); // #issue55, #issue57 it("should correctly mangle function declarations in different order", () => { - const source = unpad( - ` + const source = unpad(` (function(){ (function() { for (let x in y) y[x]; @@ -850,8 +731,7 @@ describe("mangle-names", () => { })(); function g() {} })(); - ` - ); + `); const ast = babel.transform(source, { presets: ["env"], @@ -866,8 +746,7 @@ describe("mangle-names", () => { plugins: [require("../src/index")] }).code; - const expected = unpad( - ` + const expected = unpad(` "use strict"; (function () { @@ -880,15 +759,13 @@ describe("mangle-names", () => { })(); function a() {} })(); - ` - ); + `); expect(actual).toBe(expected); }); it("should NOT mangle functions when keepFnName is true", () => { - const source = unpad( - ` + const source = unpad(` (function() { var foo = function foo() { foo(); @@ -900,10 +777,8 @@ describe("mangle-names", () => { var baz = foo; baz(); })(); - ` - ); - const expected = unpad( - ` + `); + const expected = unpad(` (function () { var a = function foo() { foo(); @@ -915,14 +790,12 @@ describe("mangle-names", () => { var b = a; b(); })(); - ` - ); + `); expect(transform(source, { keepFnName: true })).toBe(expected); }); it("should NOT mangle classes when keepClassName is true", () => { - const source = unpad( - ` + const source = unpad(` (function() { class Foo {} const Bar = class Bar extends Foo {} @@ -932,10 +805,8 @@ describe("mangle-names", () => { } bar(); })(); - ` - ); - const expected = unpad( - ` + `); + const expected = unpad(` (function () { class Foo {} const b = class Bar extends Foo {}; @@ -945,38 +816,32 @@ describe("mangle-names", () => { } a(); })(); - ` - ); + `); expect(transform(source, { keepClassName: true })).toBe(expected); }); it("should mangle variable re-declaration / K violations", () => { - const source = unpad( - ` + const source = unpad(` !function () { var foo = 1; foo++; var foo = 2; foo++; } - ` - ); - const expected = unpad( - ` + `); + const expected = unpad(` !function () { var a = 1; a++; var a = 2; a++; }; - ` - ); + `); expect(transform(source)).toBe(expected); }); it("should handle K violations - 2", () => { - const source = unpad( - ` + const source = unpad(` !function () { var bar = 1; bar--; @@ -989,96 +854,122 @@ describe("mangle-names", () => { foo(foo); } } - ` - ); - const expected = unpad( - ` + `); + const expected = unpad(` !function () { var b = 1; b--; var b = 10; a(b); function a() { - var c = 10; - c++; - var c = 20; - c(c); + var a = 10; + a++; + var a = 20; + a(a); } }; - ` - ); + `); expect(transform(source)).toBe(expected); }); it("should work with redeclarations", () => { - const source = unpad( - ` + const source = unpad(` (function () { var x = y; x = z; x; })(); - ` - ); - const expected = unpad( - ` + `); + const expected = unpad(` (function () { var a = y; a = z; a; })(); - ` - ); + `); expect(transform(source)).toBe(expected); }); it("should reuse removed vars", () => { - const source = unpad( - ` + const source = unpad(` function Foo() { var a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z; var A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z; var $, _; + a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z; + A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z; + $, _; + function Foo() { + var a, b, c, d, e, f, g, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z; + var A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z; + var $, _; + a, b, c, d, e, f, g, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z; + A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z; + $, _; + function Foo() { + var a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z; + var A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z; + var $, _; + a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z; + A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z; + $, _; + } + Foo(); + } + Foo(); } - ` - ); - const expected = unpad( - ` + `); + const expected = unpad(` function Foo() { - var aa, ba, ca, da, ea, fa, ga, ha, ia, ja, ka, la, ma, na, oa, pa, qa, ra, sa, ta, ua, va, wa, xa, ya, za; - var Aa, Ba, Ca, Da, Ea, Fa, Ga, Ha, Ia, Ja, Ka, La, Ma, Na, Oa, Pa, Qa, Ra, Sa, Ta, Ua, Va, Wa, Xa, Ya, Za; - var $a, _a; + var ba, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y; + var z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y; + var Z, $; + ba, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y; + z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y; + Z, $; + function aa() { + var aa, a, b, c, d, e, f, g, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y; + var z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y; + var Z, $; + aa, a, b, c, d, e, f, g, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y; + z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y; + Z, $; + function h() { + var aa, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y; + var z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y; + var Z, $; + aa, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y; + z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y; + Z, $; + } + h(); + } + aa(); } - ` - ); + `); expect(transform(source)).toBe(expected); }); it("should mangle both referenced and binding identifiers with K violations", () => { - const source = unpad( - ` + const source = unpad(` (function () { var foo = bar, foo = baz; foo; })(); - ` - ); - const expected = unpad( - ` + `); + const expected = unpad(` (function () { var a = bar, a = baz; a; })(); - ` - ); + `); expect(transform(source)).toBe(expected); }); it("should handle export declarations", () => { - const source = unpad( - ` + const source = unpad(` const foo = 1; export { foo }; export const bar = 2; @@ -1090,10 +981,8 @@ describe("mangle-names", () => { bar(); baz(); } - ` - ); - const expected = unpad( - ` + `); + const expected = unpad(` const foo = 1; export { foo }; export const bar = 2; @@ -1105,14 +994,12 @@ describe("mangle-names", () => { a(); b(); } - ` - ); + `); expect(transform(source, {}, "module")).toBe(expected); }); it("should find global scope properly", () => { - const source = unpad( - ` + const source = unpad(` class A {} class B extends A {} (function () { @@ -1124,10 +1011,8 @@ describe("mangle-names", () => { } } })(); - ` - ); - const expected = unpad( - ` + `); + const expected = unpad(` class A {} class B extends A {} (function () { @@ -1139,14 +1024,12 @@ describe("mangle-names", () => { } } })(); - ` - ); + `); expect(transform(source)).toBe(expected); }); it("should mangle classes properly", () => { - const source = unpad( - ` + const source = unpad(` class A {} class B {} new A(); @@ -1157,54 +1040,134 @@ describe("mangle-names", () => { new A(); new B(); } - ` - ); - const expected = unpad( - ` + `); + const expected = unpad(` class A {} class B {} new A(); new B(); function a() { + class a {} class b {} - class c {} + new a(); new b(); - new c(); } - ` - ); + `); expect(transform(source)).toBe(expected); }); // https://github.com/babel/babili/issues/138 it("should handle class exports in modules - issue#138", () => { - const source = unpad( - ` + const source = unpad(` export class App extends Object {}; - ` - ); + `); const expected = source; expect(transform(source, {}, "module")).toBe(expected); }); it("should not mangle the name arguments", () => { - const source = unpad( - ` + const source = unpad(` (function () { var arguments = void 0; (function () { console.log(arguments); })("argument"); })(); - ` - ); + `); const expected = source; expect(transform(source)).toBe(expected); }); + it("should handle constant violations across multiple blocks", () => { + const source = unpad(` + function foo() { + var x;x;x; + { + var x;x;x; + function y() { + var x;x;x; + { + var x;x;x; + } + } + } + } + `); + const expected = unpad(` + function foo() { + var a;a;a; + { + var a;a;a; + function b() { + var a;a;a; + { + var a;a;a; + } + } + } + } + `); + expect(transform(source)).toBe(expected); + }); + + it("should work with if_return optimization changing fn scope", () => { + const source = unpad(` + function foo() { + if (x) + return; + function bar() {} + bar(a); + } + `); + const expected = unpad(` + function foo() { + function b() {} + x || b(a); + } + `); + expect(transformWithSimplify(source)).toBe(expected); + }); + + it("should fix #326, #369 - destructuring", () => { + const source = unpad(` + // issue#326 + function a() { + let foo, bar, baz; + ({foo, bar, baz} = {}); + return {foo, bar, baz}; + } + // issue#369 + function decodeMessage(message){ + let namespace; + let name; + let value = null; + + [, namespace, name, value] = message.split(',') || []; + console.log(name); + } + `); + const expected = unpad(` + // issue#326 + function a() { + let a, b, c; + ({ foo: a, bar: b, baz: c } = {}); + return { foo: a, bar: b, baz: c }; + } + // issue#369 + function decodeMessage(a) { + let b; + let c; + let d = null; + + [, b, c, d] = a.split(',') || []; + console.log(c); + } + `); + expect(transform(source)).toBe(expected); + }); + it("should mangle topLevel when topLevel option is true", () => { - const source = unpad( - ` + const source = unpad(` function foo() { if (FOO_ENV === "production") { HELLO_WORLD.call(); @@ -1219,11 +1182,9 @@ describe("mangle-names", () => { }; class AbstractClass {} foo(); - ` - ); + `); - const expected = unpad( - ` + const expected = unpad(` function a() { if (b === "production") { c.call(); @@ -1238,15 +1199,13 @@ describe("mangle-names", () => { }; class d {} a(); - ` - ); + `); expect(transform(source, { topLevel: true })).toBe(expected); }); it("should fix #326, #369 - destructuring", () => { - const source = unpad( - ` + const source = unpad(` // issue#326 function a() { let foo, bar, baz; @@ -1262,33 +1221,29 @@ describe("mangle-names", () => { [, namespace, name, value] = message.split(',') || []; console.log(name); } - ` - ); - const expected = unpad( - ` + `); + const expected = unpad(` // issue#326 function a() { - let b, c, d; - ({ foo: b, bar: c, baz: d } = {}); - return { foo: b, bar: c, baz: d }; + let a, b, c; + ({ foo: a, bar: b, baz: c } = {}); + return { foo: a, bar: b, baz: c }; } // issue#369 - function decodeMessage(b) { + function decodeMessage(a) { + let b; let c; - let d; - let e = null; + let d = null; - [, c, d, e] = b.split(\',\') || []; - console.log(d); + [, b, c, d] = a.split(',') || []; + console.log(c); } - ` - ); + `); expect(transform(source)).toBe(expected); }); it("should rename binding.identifier - issue#411", () => { - const source = unpad( - ` + const source = unpad(` !function () { function e(e) { foo(e); @@ -1297,88 +1252,40 @@ describe("mangle-names", () => { return e(); }; }(); - ` - ); - const expected = unpad( - ` + `); + const expected = unpad(` !function () { - function a(b) { - foo(b); + function a(a) { + foo(a); } return function () { return a(); }; }(); - ` - ); + `); expect(transform(source)).toBe(expected); }); it("should fix issue#365 - classDeclaration with unsafe parent scope", () => { - const source = unpad( - ` + const source = unpad(` function foo() { eval(""); class A {} class B {} } - ` - ); + `); expect(transform(source)).toBe(source); }); it("should fix classDeclaration with unsafe program scope", () => { - const source = unpad( - ` + const source = unpad(` class A {} class B {} eval(""); - ` - ); + `); expect(transform(source, { topLevel: true })).toBe(source); }); - it("should not mangle named exports - 1", () => { - const source = unpad( - ` - export const Foo = foo; - ` - ); - expect(transform(source, { topLevel: true }, "module")).toBe(source); - }); - - it("should not mangle named exports - 2", () => { - const source = unpad( - ` - const Foo = a; - export {Foo}; - ` - ); - const expected = unpad( - ` - const b = a; - export { b as Foo }; - ` - ); - expect(transform(source, { topLevel: true }, "module")).toBe(expected); - }); - - it("should not mangle named exports - 3", () => { - const source = unpad( - ` - const Foo = a; - export {Foo as Bar}; - ` - ); - const expected = unpad( - ` - const b = a; - export { b as Bar }; - ` - ); - expect(transform(source, { topLevel: true }, "module")).toBe(expected); - }); - it("should handle constant violations across multiple blocks", () => { const source = unpad(` function foo() { @@ -1400,9 +1307,9 @@ describe("mangle-names", () => { { var a;a;a; function b() { - var c;c;c; + var a;a;a; { - var c;c;c; + var a;a;a; } } } @@ -1428,4 +1335,35 @@ describe("mangle-names", () => { `); expect(transformWithSimplify(source)).toBe(expected); }); + + it("should not mangle named exports - 1", () => { + const source = unpad(` + export const Foo = foo; + `); + expect(transform(source, { topLevel: true }, "module")).toBe(source); + }); + + it("should not mangle named exports - 2", () => { + const source = unpad(` + const Foo = a; + export {Foo}; + `); + const expected = unpad(` + const b = a; + export { b as Foo }; + `); + expect(transform(source, { topLevel: true }, "module")).toBe(expected); + }); + + it("should not mangle named exports - 3", () => { + const source = unpad(` + const Foo = a; + export {Foo as Bar}; + `); + const expected = unpad(` + const b = a; + export { b as Bar }; + `); + expect(transform(source, { topLevel: true }, "module")).toBe(expected); + }); }); diff --git a/packages/babel-plugin-minify-mangle-names/src/index.js b/packages/babel-plugin-minify-mangle-names/src/index.js index 92dd20f04..34f32ad1c 100644 --- a/packages/babel-plugin-minify-mangle-names/src/index.js +++ b/packages/babel-plugin-minify-mangle-names/src/index.js @@ -109,7 +109,7 @@ module.exports = babel => { scopeTracker.addBinding(binding); } else { //constant violation - // console.log("adding constant violation for ", name); + // track constant violations as references in ScopeTracking scopeTracker.addReference(scope, binding, name); } } From 7c6a63b17b9781a5caeb440533964037d82c8b43 Mon Sep 17 00:00:00 2001 From: Boopathi Rajaa Date: Sun, 21 May 2017 22:30:00 +0200 Subject: [PATCH 05/10] Fix mangle blacklist tests --- .../__tests__/mangle-names-test.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/babel-plugin-minify-mangle-names/__tests__/mangle-names-test.js b/packages/babel-plugin-minify-mangle-names/__tests__/mangle-names-test.js index 40bf0acd2..65404df85 100644 --- a/packages/babel-plugin-minify-mangle-names/__tests__/mangle-names-test.js +++ b/packages/babel-plugin-minify-mangle-names/__tests__/mangle-names-test.js @@ -438,14 +438,17 @@ describe("mangle-names", () => { `); const expected = unpad(` - function foo() { + function a() { var bar = 1; var a = 2; } `); - expect(transform(source, { blacklist: { foo: true, bar: false } })).toBe( - expected - ); + expect( + transform(source, { + blacklist: { foo: false, bar: true }, + topLevel: true + }) + ).toBe(expected); }); it("should handle deeply nested paths with no bindings", () => { From 3e025cc6013e7a94b8427a1ecd411c0dcddd02a0 Mon Sep 17 00:00:00 2001 From: Boopathi Rajaa Date: Sun, 21 May 2017 22:30:57 +0200 Subject: [PATCH 06/10] Fix formatting --- .../babel-plugin-minify-mangle-names/src/charset.js | 8 +++++--- .../src/counted-set.js | 2 +- .../src/is-label-identifier.js | 10 ++++++---- .../src/scope-tracker.js | 1 - 4 files changed, 12 insertions(+), 9 deletions(-) diff --git a/packages/babel-plugin-minify-mangle-names/src/charset.js b/packages/babel-plugin-minify-mangle-names/src/charset.js index 161d9129d..43646c667 100644 --- a/packages/babel-plugin-minify-mangle-names/src/charset.js +++ b/packages/babel-plugin-minify-mangle-names/src/charset.js @@ -1,14 +1,16 @@ "use strict"; const CHARSET = ("abcdefghijklmnopqrstuvwxyz" + - "ABCDEFGHIJKLMNOPQRSTUVWXYZ$_").split(""); + "ABCDEFGHIJKLMNOPQRSTUVWXYZ$_").split(""); module.exports = class Charset { constructor(shouldConsider) { this.shouldConsider = shouldConsider; this.chars = CHARSET.slice(); this.frequency = {}; - this.chars.forEach((c) => { this.frequency[c] = 0; }); + this.chars.forEach(c => { + this.frequency[c] = 0; + }); this.finalized = false; } @@ -17,7 +19,7 @@ module.exports = class Charset { return; } - str.split("").forEach((c) => { + str.split("").forEach(c => { if (this.frequency[c] != null) { this.frequency[c]++; } diff --git a/packages/babel-plugin-minify-mangle-names/src/counted-set.js b/packages/babel-plugin-minify-mangle-names/src/counted-set.js index 684cb80bf..034912c23 100644 --- a/packages/babel-plugin-minify-mangle-names/src/counted-set.js +++ b/packages/babel-plugin-minify-mangle-names/src/counted-set.js @@ -2,7 +2,7 @@ module.exports = class CountedSet { constructor() { // because you can't simply extend Builtins yet - this.map = new Map; + this.map = new Map(); } keys() { return [...this.map.keys()]; diff --git a/packages/babel-plugin-minify-mangle-names/src/is-label-identifier.js b/packages/babel-plugin-minify-mangle-names/src/is-label-identifier.js index 28d6ffe2c..4cc15cfc3 100644 --- a/packages/babel-plugin-minify-mangle-names/src/is-label-identifier.js +++ b/packages/babel-plugin-minify-mangle-names/src/is-label-identifier.js @@ -1,8 +1,10 @@ module.exports = isLabelIdentifier; function isLabelIdentifier(path) { - const {node} = path; - return path.parentPath.isLabeledStatement({ label: node }) - || path.parentPath.isBreakStatement({ label: node }) - || path.parentPath.isContinueStatement({ label: node }); + const { node } = path; + return ( + path.parentPath.isLabeledStatement({ label: node }) || + path.parentPath.isBreakStatement({ label: node }) || + path.parentPath.isContinueStatement({ label: node }) + ); } diff --git a/packages/babel-plugin-minify-mangle-names/src/scope-tracker.js b/packages/babel-plugin-minify-mangle-names/src/scope-tracker.js index 1a9aa6b1b..4bea446bb 100644 --- a/packages/babel-plugin-minify-mangle-names/src/scope-tracker.js +++ b/packages/babel-plugin-minify-mangle-names/src/scope-tracker.js @@ -7,7 +7,6 @@ const isLabelIdentifier = require("./is-label-identifier"); * bindings: Map > */ -let counter = 0; module.exports = class ScopeTracker { constructor() { this.references = new Map(); From 9bc2c0acbe288bb0ebe48367e8461582eb8b569d Mon Sep 17 00:00:00 2001 From: Boopathi Rajaa Date: Sun, 21 May 2017 22:34:03 +0200 Subject: [PATCH 07/10] Remove unnecessary preset tests --- .../__tests__/preset-tests.js | 24 ------------------- 1 file changed, 24 deletions(-) diff --git a/packages/babel-preset-babili/__tests__/preset-tests.js b/packages/babel-preset-babili/__tests__/preset-tests.js index b3f41e988..9694a4951 100644 --- a/packages/babel-preset-babili/__tests__/preset-tests.js +++ b/packages/babel-preset-babili/__tests__/preset-tests.js @@ -155,28 +155,4 @@ describe("preset", () => { `); expect(transform(source)).toBe(expected); }); - - it("should fix bug#326 - object destructuring", () => { - const source = unpad(` - function a() { - let foo, bar, baz; - ({foo, bar, baz} = {}); - return {foo, bar, baz}; - } - `); - const expected = unpad(` - function a() { - let b, c, d; - - return ({ foo: b, bar: c, baz: d } = {}), { foo: b, bar: c, baz: d }; - } - `); - expect( - transform(source, { - mangle: { - reuse: false - } - }) - ).toBe(expected); - }); }); From a9f26be4116c76867365ed29da1057e85cead4db Mon Sep 17 00:00:00 2001 From: Boopathi Rajaa Date: Mon, 22 May 2017 01:37:34 +0200 Subject: [PATCH 08/10] Add JSDoc Comments and update Error messages --- .../src/bfs-traverse.js | 2 +- .../src/index.js | 211 +++++++++++++++--- .../src/scope-tracker.js | 102 +++++++-- 3 files changed, 262 insertions(+), 53 deletions(-) diff --git a/packages/babel-plugin-minify-mangle-names/src/bfs-traverse.js b/packages/babel-plugin-minify-mangle-names/src/bfs-traverse.js index 439f8a1b2..0bff09d35 100644 --- a/packages/babel-plugin-minify-mangle-names/src/bfs-traverse.js +++ b/packages/babel-plugin-minify-mangle-names/src/bfs-traverse.js @@ -12,7 +12,7 @@ module.exports = function bfsTraverseCreator({ types: t, traverse }) { const visitor = traverse.explode(_visitor); const queue = [path]; - let current; // current depth + let current; while (queue.length > 0) { current = queue.shift(); diff --git a/packages/babel-plugin-minify-mangle-names/src/index.js b/packages/babel-plugin-minify-mangle-names/src/index.js index 34f32ad1c..66da547d8 100644 --- a/packages/babel-plugin-minify-mangle-names/src/index.js +++ b/packages/babel-plugin-minify-mangle-names/src/index.js @@ -1,3 +1,5 @@ +"use strict"; + const Charset = require("./charset"); const ScopeTracker = require("./scope-tracker"); const isLabelIdentifier = require("./is-label-identifier"); @@ -10,6 +12,8 @@ const { hasEval } = require("babel-helper-mark-eval-scopes"); +const newIssueUrl = "https://github.com/babel/babili/issues/new"; + module.exports = babel => { const { types: t, traverse } = babel; const bfsTraverse = bfsTraverseCreator(babel); @@ -29,17 +33,23 @@ module.exports = babel => { ) { this.charset = charset; this.program = program; + + // user passed options this.blacklist = toObject(blacklist); this.keepFnName = keepFnName; this.keepClassName = keepClassName; this.topLevel = topLevel; this.eval = _eval; + // tracking this.visitedScopes = new Set(); this.scopeTracker = new ScopeTracker(); this.renamedNodes = new Set(); } + /** + * Run the mangler + */ run() { this.crawlScope(); this.collect(); @@ -48,77 +58,171 @@ module.exports = babel => { this.mangle(); } + /** + * Tells if a variable name is blacklisted + * @param {String} name + */ isBlacklist(name) { return hop.call(this.blacklist, name) && this.blacklist[name]; } + /** + * Clears traverse cache and recrawls the AST + * + * to recompute the bindings, references, other scope information + * and paths because the other transformations in the same pipeline + * (other plugins and presets) changes the AST and does NOT update + * the scope objects + */ crawlScope() { traverse.clearCache(); this.program.scope.crawl(); } + /** + * Re-crawling comes with a side-effect that let->var conversion + * reverts the update of the binding information (block to fn scope). + * This function takes care of it by updating it again. + * + * TODO: This is unnecessary work and needs to be fixed in babel. + * https://github.com/babel/babel/issues/4818 + * + * When this is removed, remember to remove fixup's dependency in + * ScopeTracker + */ fixup() { fixupVarScoping(this); } + /** + * A single pass through the AST to collect info for + * + * 1. Scope Tracker + * 2. Unsafe Scopes (direct eval scopes) + * 3. Charset considerations for better gzip compression + * + * Traversed in the same fashion(BFS) the mangling is done + */ collect() { const mangler = this; const { scopeTracker } = mangler; scopeTracker.addScope(this.program.scope); + /** + * Same usage as in DCE, whichever runs first + */ if (!isEvalScopesMarked(mangler.program.scope)) { markEvalScopes(mangler.program); } + /** + * The visitors to be used in traversal. + * + * Note: BFS traversal supports only the `enter` handlers, `exit` + * handlers are simply dropped without Errors + * + * Collects items defined in the ScopeTracker + */ const collectVisitor = { Scopable({ scope }) { scopeTracker.addScope(scope); + + // Collect bindings defined in the scope Object.keys(scope.bindings).forEach(name => { scopeTracker.addBinding(scope.bindings[name]); }); }, + /** + * This is necessary because, in Babel, the scope.references + * does NOT contain the references in that scope. Only the program + * scope (top most level) contains all the references. + * + * We collect the references in a fashion where all the scopes between + * and including the referenced scope and scope where it is declared + * is considered as scope referencing that identifier + */ ReferencedIdentifier(path) { if (isLabelIdentifier(path)) return; const { scope, node: { name } } = path; const binding = scope.getBinding(name); if (!binding) { + // Do not collect globals as they are already available via + // babel's API if (scope.hasGlobal(name)) return; - throw new Error("Something went wrong"); + // This should NOT happen ultimately. Panic if this code block is + // reached + throw new Error( + "Binding not found for ReferencedIdentifier. " + + name + + "Please report this at " + + newIssueUrl + ); } else { + // Add it to our scope tracker if everything is fine scopeTracker.addReference(scope, binding, name); } }, + /** + * This is useful to detect binding ids and add them to the + * scopeTracker's bindings + */ BindingIdentifier(path) { if (isLabelIdentifier(path)) return; const { scope, node: { name } } = path; const binding = scope.getBinding(name); + if (!binding) { + // ignore the globals as it's available via Babel's API if (scope.hasGlobal(name)) return; + + // Ignore the NamedExports as they should NOT be mangled if ( path.parentPath.isExportSpecifier() && path.parentKey === "exported" ) { return; } - throw new Error("binding not found " + name); + + // This should NOT happen ultimately. Panic if this code is reached + throw new Error( + "Binding not found for BindingIdentifier. " + + name + + "Please report this at " + + newIssueUrl + ); } + + /** + * Detect constant violations + * + * If it's a constant violation, then add the Identifier Path as + * a Reference instead of Binding - This is because the ScopeTracker + * tracks these Re-declaration and mutation of variables as References + * as it is simple to rename them + */ if (binding.identifier === path.node) { scopeTracker.addBinding(binding); } else { - //constant violation - // track constant violations as references in ScopeTracking + // constant violation scopeTracker.addReference(scope, binding, name); } } }; + /** + * These visitors are for collecting the Characters used in the program + * to measure the frequency and generate variable names for mangling so + * as to improve the gzip compression - as gzip likes repetition + */ if (this.charset.shouldConsider) { collectVisitor.Identifier = function Identifer(path) { const { node } = path; + // We don't mangle properties, so we collect them as they contribute + // to the frequency of characters if ( path.parentPath.isMemberExpression({ property: node }) || path.parentPath.isObjectProperty({ key: node }) @@ -131,9 +235,16 @@ module.exports = babel => { }; } + // Traverse the AST bfsTraverse(mangler.program, collectVisitor); } + /** + * Tells if a binding is exported as a NamedExport - so as to NOT mangle + * + * Babel treats NamedExports as a binding referenced by this NamedExport decl + * @param {Binding} binding + */ isExportedWithName(binding) { // short circuit if (!this.topLevel) { @@ -152,26 +263,30 @@ module.exports = babel => { return false; } + /** + * Mangle the scope + * @param {Scope} scope + */ mangleScope(scope) { const mangler = this; const { scopeTracker } = mangler; + // Unsafe Scope if (!mangler.eval && hasEval(scope)) return; + // Already visited + // This is because for a function, in Babel, the function and + // the function body's BlockStatement has the same scope, and will + // be visited twice by the Scopable handler, and we want to mangle + // it only once if (mangler.visitedScopes.has(scope)) return; mangler.visitedScopes.add(scope); + // Helpers to generate names let i = 0; function getNext() { return mangler.charset.getIdentifier(i++); } - - // This is useful when we have vars of single character - // => var a, ...z, A, ...Z, $, _; - // to - // => var aa, a, b ,c; - // instead of - // => var aa, ab, ...; function resetNext() { i = 0; } @@ -179,19 +294,24 @@ module.exports = babel => { const bindings = scopeTracker.bindings.get(scope); const names = [...bindings.keys()]; + /** + * 1. Iterate through the list of BindingIdentifiers + * 2. Rename each of them in-place + * 3. Update the scope tree. + */ for (let i = 0; i < names.length; i++) { const oldName = names[i]; const binding = bindings.get(oldName); + // Names which should NOT be mangled if ( - // arguments + // arguments - for non-strict mode oldName === "arguments" || // labels binding.path.isLabeledStatement() || // ClassDeclaration has binding in two scopes // 1. The scope in which it is declared // 2. The class's own scope - // - https://github.com/babel/babel/issues/5156 (binding.path.isClassDeclaration() && binding.path === scope.path) || // blacklisted mangler.isBlacklist(oldName) || @@ -208,14 +328,6 @@ module.exports = babel => { let next; do { next = getNext(); - // console.log( - // next, - // !t.isValidIdentifier(next), - // scopeTracker.hasBinding(scope, next), - // scope.hasGlobal(next), - // scopeTracker.hasReference(scope, next), - // !scopeTracker.canUseInReferencedScopes(binding, next) - // ); } while ( !t.isValidIdentifier(next) || scopeTracker.hasBinding(scope, next) || @@ -224,14 +336,19 @@ module.exports = babel => { !scopeTracker.canUseInReferencedScopes(binding, next) ); + // Reset so variables which are removed can be reused resetNext(); - // console.log("-----------------"); - // console.log("mangling", oldName, next); + // Once we detected a valid `next` Identifier which could be used, + // call the renamer mangler.rename(scope, binding, oldName, next); } } + /** + * The mangle function that traverses through all the Scopes in a BFS + * fashion - calls mangleScope + */ mangle() { const mangler = this; @@ -243,6 +360,18 @@ module.exports = babel => { }); } + /** + * Given a NodePath, collects all the Identifiers which are BindingIdentifiers + * and replaces them with the new name + * + * For example, + * var a = 1, { b } = c; // a and b are BindingIdentifiers + * + * @param {NodePath} path + * @param {String} oldName + * @param {String} newName + * @param {Function} predicate + */ renameBindingIds(path, oldName, newName, predicate = () => true) { const bindingIds = path.getBindingIdentifierPaths(true, false); for (const name in bindingIds) { @@ -257,6 +386,21 @@ module.exports = babel => { } } + /** + * The Renamer: + * Renames the following for one Binding in a Scope + * + * 1. Binding in that Scope + * 2. All the Binding's constant violations + * 3. All its References + * 4. Updates mangler.scopeTracker + * 5. Updates Babel's Scope tracking + * + * @param {Scope} scope + * @param {Binding} binding + * @param {String} oldName + * @param {String} newName + */ rename(scope, binding, oldName, newName) { const mangler = this; const { scopeTracker } = mangler; @@ -269,10 +413,10 @@ module.exports = babel => { idPath => idPath.node === binding.identifier ); - // update Tracking + // update mangler's ScopeTracker scopeTracker.renameBinding(scope, oldName, newName); - // update all constant violations & redeclarations + // update all constant violations const violations = binding.constantViolations; for (let i = 0; i < violations.length; i++) { if (violations[i].isLabeledStatement()) continue; @@ -328,26 +472,20 @@ module.exports = babel => { mangler.renamedNodes.add(path.node); path.replaceWith(t.identifier(newName)); mangler.renamedNodes.add(path.node); - // console.log( - // "renaming ref in ", - // path.scope.path.type, - // oldName, - // newName - // ); - scopeTracker.updateReference(path.scope, binding, oldName, newName); } else if (mangler.renamedNodes.has(path.node)) { // already renamed, // just update the references - // throw new Error("Who is replacing again"); scopeTracker.updateReference(path.scope, binding, oldName, newName); } else { throw new Error( - `Unexpected Error - Trying to replace ${node.name}: from ${oldName} to ${newName}` + `Unexpected Rename Error: ` + + `Trying to replace ${node.name}: from ${oldName} to ${newName}` + + `Please report it at ${newIssueUrl}` ); } } - // else label + // else label identifier - silently ignore } // update babel's scope tracking @@ -360,6 +498,9 @@ module.exports = babel => { return { name: "minify-mangle-names", visitor: { + /** + * Mangler is run as a single pass. It's the same pattern as used in DCE + */ Program: { exit(path) { // If the source code is small then we're going to assume that the user diff --git a/packages/babel-plugin-minify-mangle-names/src/scope-tracker.js b/packages/babel-plugin-minify-mangle-names/src/scope-tracker.js index 4bea446bb..711c45a90 100644 --- a/packages/babel-plugin-minify-mangle-names/src/scope-tracker.js +++ b/packages/babel-plugin-minify-mangle-names/src/scope-tracker.js @@ -1,19 +1,25 @@ +"use strict"; + const CountedSet = require("./counted-set"); const isLabelIdentifier = require("./is-label-identifier"); +const newIssueUrl = "https://github.com/babel/babili/issues/new"; + /** - * Scope Tracker + * ScopeTracker * references: Map > * bindings: Map > */ - module.exports = class ScopeTracker { constructor() { this.references = new Map(); this.bindings = new Map(); } - // Register a new Scope and initiliaze it with empty sets + /** + * Register a new Scope and initiliaze it with empty sets + * @param {Scope} scope + */ addScope(scope) { if (!this.references.has(scope)) { this.references.set(scope, new CountedSet()); @@ -23,6 +29,13 @@ module.exports = class ScopeTracker { } } + /** + * Add reference to all Scopes between and including the ReferencedScope + * and Binding's Scope + * @param {Scope} scope + * @param {Binding} binding + * @param {String} name + */ addReference(scope, binding, name) { let parent = scope; do { @@ -32,29 +45,70 @@ module.exports = class ScopeTracker { } while ((parent = parent.parent)); } + /** + * has a Reference in the given {Scope} or a child Scope + * + * Refer {addReference} to know why the following call will be valid + * for detecting references in child Scopes + * + * @param {Scope} scope + * @param {String} name + */ hasReference(scope, name) { return this.references.get(scope).has(name); } + /** + * Update reference count in all scopes between and including the + * Referenced Scope and the Binding's Scope + * + * @param {Scope} scope + * @param {Binding} binding + * @param {String} oldName + * @param {String} newName + */ updateReference(scope, binding, oldName, newName) { let parent = scope; do { const ref = this.references.get(parent); - // if (!ref.has(oldName)) { - // throw new Error("ref " + oldName + " not found"); - // } + ref.delete(oldName); ref.add(newName); - // console.log("adding", newName, "to", parent.path.type); - if (!binding) throw new Error("How did global get here"); + + if (!binding) { + // Something went wrong - panic + throw new Error( + "Binding Not Found during scopeTracker.updateRefernce: " + + `Updating "${oldName}" to "${newName}"` + + `Please report at ${newIssueUrl}` + ); + } + if (binding.scope === parent) break; } while ((parent = parent.parent)); } + /** + * has either a Binding or a Reference + * @param {Scope} scope + * @param {Binding} binding + * @param {String} name + */ hasBindingOrReference(scope, binding, name) { return this.hasReference(scope, name) || this.hasBinding(scope, name); } + /** + * For a Binding visit all places where the Binding is used and detect + * if the newName {next} can be used in all these places + * + * 1. binding's own scope + * 2. constant violations' scopes + * 3. referencePaths' scopes + * + * @param {Binding} binding + * @param {String} next + */ canUseInReferencedScopes(binding, next) { const tracker = this; @@ -94,6 +148,10 @@ module.exports = class ScopeTracker { return true; } + /** + * Add a binding to Tracker in binding's own Scope + * @param {Binding} binding + */ addBinding(binding) { if (!binding) { return; @@ -115,24 +173,34 @@ module.exports = class ScopeTracker { bindings.set(binding.identifier.name, binding); } - // required for fixup-var-scope + /** + * Moves Binding from it's own Scope to {toScope} + * + * required for fixup-var-scope + * + * @param {Binding} binding + * @param {Scope} toScope + */ moveBinding(binding, toScope) { - // console.log( - // "moving binding", - // binding.identifier.name, - // "to", - // toScope.path.type, - // "from", - // binding.scope.path.type - // ); this.bindings.get(binding.scope).delete(binding.identifier.name); this.bindings.get(toScope).set(binding.identifier.name, binding); } + /** + * has a Binding in the current {Scope} + * @param {Scope} scope + * @param {String} name + */ hasBinding(scope, name) { return this.bindings.get(scope).has(name); } + /** + * Update the ScopeTracker on rename + * @param {Scope} scope + * @param {String} oldName + * @param {String} newName + */ renameBinding(scope, oldName, newName) { const bindings = this.bindings.get(scope); bindings.set(newName, bindings.get(oldName)); From 921f5ddc2c21cf05298a7c3020655d51ca73ac6e Mon Sep 17 00:00:00 2001 From: Boopathi Rajaa Date: Mon, 22 May 2017 10:37:40 +0200 Subject: [PATCH 09/10] Remove duplicate tests --- .../__tests__/mangle-names-test.js | 88 ------------------- 1 file changed, 88 deletions(-) diff --git a/packages/babel-plugin-minify-mangle-names/__tests__/mangle-names-test.js b/packages/babel-plugin-minify-mangle-names/__tests__/mangle-names-test.js index 65404df85..c1d8ab57d 100644 --- a/packages/babel-plugin-minify-mangle-names/__tests__/mangle-names-test.js +++ b/packages/babel-plugin-minify-mangle-names/__tests__/mangle-names-test.js @@ -1081,94 +1081,6 @@ describe("mangle-names", () => { expect(transform(source)).toBe(expected); }); - it("should handle constant violations across multiple blocks", () => { - const source = unpad(` - function foo() { - var x;x;x; - { - var x;x;x; - function y() { - var x;x;x; - { - var x;x;x; - } - } - } - } - `); - const expected = unpad(` - function foo() { - var a;a;a; - { - var a;a;a; - function b() { - var a;a;a; - { - var a;a;a; - } - } - } - } - `); - expect(transform(source)).toBe(expected); - }); - - it("should work with if_return optimization changing fn scope", () => { - const source = unpad(` - function foo() { - if (x) - return; - function bar() {} - bar(a); - } - `); - const expected = unpad(` - function foo() { - function b() {} - x || b(a); - } - `); - expect(transformWithSimplify(source)).toBe(expected); - }); - - it("should fix #326, #369 - destructuring", () => { - const source = unpad(` - // issue#326 - function a() { - let foo, bar, baz; - ({foo, bar, baz} = {}); - return {foo, bar, baz}; - } - // issue#369 - function decodeMessage(message){ - let namespace; - let name; - let value = null; - - [, namespace, name, value] = message.split(',') || []; - console.log(name); - } - `); - const expected = unpad(` - // issue#326 - function a() { - let a, b, c; - ({ foo: a, bar: b, baz: c } = {}); - return { foo: a, bar: b, baz: c }; - } - // issue#369 - function decodeMessage(a) { - let b; - let c; - let d = null; - - [, b, c, d] = a.split(',') || []; - console.log(c); - } - `); - expect(transform(source)).toBe(expected); - }); - it("should mangle topLevel when topLevel option is true", () => { const source = unpad(` function foo() { From 310b40edc43358691e3d2b8ee83b7aa4b307ab42 Mon Sep 17 00:00:00 2001 From: Boopathi Rajaa Date: Mon, 22 May 2017 12:03:41 +0200 Subject: [PATCH 10/10] Fix Error message --- .../babel-plugin-minify-mangle-names/src/scope-tracker.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/babel-plugin-minify-mangle-names/src/scope-tracker.js b/packages/babel-plugin-minify-mangle-names/src/scope-tracker.js index 711c45a90..1e0c8becb 100644 --- a/packages/babel-plugin-minify-mangle-names/src/scope-tracker.js +++ b/packages/babel-plugin-minify-mangle-names/src/scope-tracker.js @@ -40,7 +40,12 @@ module.exports = class ScopeTracker { let parent = scope; do { this.references.get(parent).add(name); - if (!binding) throw new Error("How did global come here"); + if (!binding) { + throw new Error( + `Binding Not Found for ${name} during scopeTracker.addRefernce: ` + + `Please report at ${newIssueUrl}` + ); + } if (binding.scope === parent) break; } while ((parent = parent.parent)); }