[Pkg-javascript-commits] [uglifyjs] 29/228: enhance `global_defs` - support arrays, objects & AST_Node - support `"a.b":1` on both cli & API - emit warning if variable is modified - override top-level variables

Jonas Smedegaard dr at jones.dk
Sat Apr 15 14:25:13 UTC 2017


This is an automated email from the git hooks/post-receive script.

js pushed a commit to branch master
in repository uglifyjs.

commit e275148998638bdcf795257ed03941ca34e33018
Author: alexlamsl <alexlamsl at gmail.com>
Date:   Sat Feb 18 19:27:31 2017 +0800

    enhance `global_defs`
    - support arrays, objects & AST_Node
    - support `"a.b":1` on both cli & API
    - emit warning if variable is modified
    - override top-level variables
    
    fixes #1416
    closes #1198
    closes #1469
---
 README.md                        |   2 +
 lib/compress.js                  | 114 +++++++++++++++++++++++-------
 lib/scope.js                     |  22 +++---
 test/compress/global_defs.js     | 147 +++++++++++++++++++++++++++++++++++++++
 test/compress/issue-208.js       |  41 +++++++++++
 test/input/global_defs/nested.js |   1 +
 test/input/global_defs/simple.js |   1 +
 test/mocha/cli.js                |  30 ++++++++
 8 files changed, 324 insertions(+), 34 deletions(-)

diff --git a/README.md b/README.md
index a2eaeae..1d1f2fc 100644
--- a/README.md
+++ b/README.md
@@ -454,6 +454,8 @@ if (DEBUG) {
 }
 ```
 
+You can specify nested constants in the form of `--define env.DEBUG=false`.
+
 UglifyJS will warn about the condition being always false and about dropping
 unreachable code; for now there is no option to turn off only this specific
 warning, you can pass `warnings=false` to turn off *all* warnings.
diff --git a/lib/compress.js b/lib/compress.js
index a60ba1a..cb99a17 100644
--- a/lib/compress.js
+++ b/lib/compress.js
@@ -219,17 +219,6 @@ merge(Compressor.prototype, {
     };
 
     function make_node_from_constant(compressor, val, orig) {
-        // XXX: WIP.
-        // if (val instanceof AST_Node) return val.transform(new TreeTransformer(null, function(node){
-        //     if (node instanceof AST_SymbolRef) {
-        //         var scope = compressor.find_parent(AST_Scope);
-        //         var def = scope.find_variable(node);
-        //         node.thedef = def;
-        //         return node;
-        //     }
-        // })).transform(compressor);
-
-        if (val instanceof AST_Node) return val.transform(compressor);
         switch (typeof val) {
           case "string":
             return make_node(AST_String, orig, {
@@ -991,6 +980,68 @@ merge(Compressor.prototype, {
             || parent instanceof AST_Assign && parent.left === node;
     }
 
+    (function (def){
+        AST_Node.DEFMETHOD("resolve_defines", function(compressor) {
+            if (!compressor.option("global_defs")) return;
+            var def = this._find_defs(compressor, "");
+            if (def) {
+                var node, parent = this, level = 0;
+                do {
+                    node = parent;
+                    parent = compressor.parent(level++);
+                } while (parent instanceof AST_PropAccess && parent.expression === node);
+                if (isLHS(node, parent)) {
+                    compressor.warn('global_defs ' + this.print_to_string() + ' redefined [{file}:{line},{col}]', this.start);
+                } else {
+                    return def;
+                }
+            }
+        });
+        function to_node(compressor, value, orig) {
+            if (value instanceof AST_Node) return make_node(value.CTOR, orig, value);
+            if (Array.isArray(value)) return make_node(AST_Array, orig, {
+                elements: value.map(function(value) {
+                    return to_node(compressor, value, orig);
+                })
+            });
+            if (value && typeof value == "object") {
+                var props = [];
+                for (var key in value) {
+                    props.push(make_node(AST_ObjectKeyVal, orig, {
+                        key: key,
+                        value: to_node(compressor, value[key], orig)
+                    }));
+                }
+                return make_node(AST_Object, orig, {
+                    properties: props
+                });
+            }
+            return make_node_from_constant(compressor, value, orig);
+        }
+        def(AST_Node, noop);
+        def(AST_Dot, function(compressor, suffix){
+            return this.expression._find_defs(compressor, suffix + "." + this.property);
+        });
+        def(AST_SymbolRef, function(compressor, suffix){
+            if (!this.global()) return;
+            var name;
+            var defines = compressor.option("global_defs");
+            if (defines && HOP(defines, (name = this.name + suffix))) {
+                var node = to_node(compressor, defines[name], this);
+                var top = compressor.find_parent(AST_Toplevel);
+                node.walk(new TreeWalker(function(node) {
+                    if (node instanceof AST_SymbolRef) {
+                        node.scope = top;
+                        node.thedef = top.def_global(node);
+                    }
+                }));
+                return node;
+            }
+        });
+    })(function(node, func){
+        node.DEFMETHOD("_find_defs", func);
+    });
+
     function best_of(ast1, ast2) {
         return ast1.print_to_string().length >
             ast2.print_to_string().length
@@ -2793,21 +2844,20 @@ merge(Compressor.prototype, {
     });
 
     OPT(AST_SymbolRef, function(self, compressor){
-        if (self.undeclared() && !isLHS(self, compressor.parent())) {
-            var defines = compressor.option("global_defs");
-            if (defines && HOP(defines, self.name)) {
-                return make_node_from_constant(compressor, defines[self.name], self);
-            }
-            // testing against !self.scope.uses_with first is an optimization
-            if (!self.scope.uses_with || !compressor.find_parent(AST_With)) {
-                switch (self.name) {
-                  case "undefined":
-                    return make_node(AST_Undefined, self);
-                  case "NaN":
-                    return make_node(AST_NaN, self).transform(compressor);
-                  case "Infinity":
-                    return make_node(AST_Infinity, self).transform(compressor);
-                }
+        var def = self.resolve_defines(compressor);
+        if (def) {
+            return def;
+        }
+        // testing against !self.scope.uses_with first is an optimization
+        if (self.undeclared() && !isLHS(self, compressor.parent())
+            && (!self.scope.uses_with || !compressor.find_parent(AST_With))) {
+            switch (self.name) {
+              case "undefined":
+                return make_node(AST_Undefined, self);
+              case "NaN":
+                return make_node(AST_NaN, self).transform(compressor);
+              case "Infinity":
+                return make_node(AST_Infinity, self).transform(compressor);
             }
         }
         if (compressor.option("evaluate") && !isLHS(self, compressor.parent())) {
@@ -3085,6 +3135,10 @@ merge(Compressor.prototype, {
     });
 
     OPT(AST_Dot, function(self, compressor){
+        var def = self.resolve_defines(compressor);
+        if (def) {
+            return def;
+        }
         var prop = self.property;
         if (RESERVED_WORDS(prop) && !compressor.option("screw_ie8")) {
             return make_node(AST_Sub, self, {
@@ -3114,4 +3168,12 @@ merge(Compressor.prototype, {
         return self;
     });
 
+    OPT(AST_VarDef, function(self, compressor){
+        var defines = compressor.option("global_defs");
+        if (defines && HOP(defines, self.name.name)) {
+            compressor.warn('global_defs ' + self.name.name + ' redefined [{file}:{line},{col}]', self.start);
+        }
+        return self;
+    });
+
 })();
diff --git a/lib/scope.js b/lib/scope.js
index 6ad1261..b0d92d7 100644
--- a/lib/scope.js
+++ b/lib/scope.js
@@ -206,14 +206,7 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){
                 node.scope.uses_arguments = true;
             }
             if (!sym) {
-                if (globals.has(name)) {
-                    sym = globals.get(name);
-                } else {
-                    sym = new SymbolDef(self, globals.size(), node);
-                    sym.undeclared = true;
-                    sym.global = true;
-                    globals.set(name, sym);
-                }
+                sym = self.def_global(node);
             }
             node.thedef = sym;
             node.reference(options);
@@ -227,6 +220,19 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){
     }
 });
 
+AST_Toplevel.DEFMETHOD("def_global", function(node){
+    var globals = this.globals, name = node.name;
+    if (globals.has(name)) {
+        return globals.get(name);
+    } else {
+        var g = new SymbolDef(this, globals.size(), node);
+        g.undeclared = true;
+        g.global = true;
+        globals.set(name, g);
+        return g;
+    }
+});
+
 AST_Scope.DEFMETHOD("init_scope_vars", function(){
     this.variables = new Dictionary(); // map name to AST_SymbolVar (variables defined in this scope; includes functions)
     this.functions = new Dictionary(); // map name to AST_SymbolDefun (functions defined in this scope)
diff --git a/test/compress/global_defs.js b/test/compress/global_defs.js
new file mode 100644
index 0000000..a69d031
--- /dev/null
+++ b/test/compress/global_defs.js
@@ -0,0 +1,147 @@
+must_replace: {
+    options = {
+        global_defs: {
+            D: "foo bar",
+        }
+    }
+    input: {
+        console.log(D);
+    }
+    expect: {
+        console.log("foo bar");
+    }
+}
+
+keyword: {
+    options = {
+        global_defs: {
+            undefined: 0,
+            NaN: 1,
+            Infinity: 2,
+        },
+    }
+    input: {
+        console.log(undefined, NaN, Infinity);
+    }
+    expect: {
+        console.log(0, 1, 2);
+    }
+}
+
+object: {
+    options = {
+        evaluate: true,
+        global_defs: {
+            CONFIG: {
+                DEBUG: [ 0 ],
+                VALUE: 42,
+            },
+        },
+        unsafe: true,
+    }
+    input: {
+        function f(CONFIG) {
+            // CONFIG not global - do not replace
+            return CONFIG.VALUE;
+        }
+        function g() {
+            var CONFIG = { VALUE: 1 };
+            // CONFIG not global - do not replace
+            return CONFIG.VALUE;
+        }
+        function h() {
+            return CONFIG.VALUE;
+        }
+        if (CONFIG.DEBUG[0])
+            console.debug("foo");
+    }
+    expect: {
+        function f(CONFIG) {
+            return CONFIG.VALUE;
+        }
+        function g() {
+            var CONFIG = { VALUE: 1 };
+            return CONFIG.VALUE;
+        }
+        function h() {
+            return 42;
+        }
+        if (0)
+            console.debug("foo");
+    }
+}
+
+expanded: {
+    options = {
+        global_defs: {
+            "CONFIG.DEBUG": [ 0 ],
+            "CONFIG.VALUE": 42,
+        },
+    }
+    input: {
+        function f(CONFIG) {
+            // CONFIG not global - do not replace
+            return CONFIG.VALUE;
+        }
+        function g() {
+            var CONFIG = { VALUE: 1 };
+            // CONFIG not global - do not replace
+            return CONFIG.VALUE;
+        }
+        function h() {
+            return CONFIG.VALUE;
+        }
+        if (CONFIG.DEBUG[0])
+            console.debug("foo");
+    }
+    expect: {
+        function f(CONFIG) {
+            return CONFIG.VALUE;
+        }
+        function g() {
+            var CONFIG = { VALUE: 1 };
+            return CONFIG.VALUE;
+        }
+        function h() {
+            return 42;
+        }
+        if ([0][0])
+            console.debug("foo");
+    }
+}
+
+mixed: {
+    options = {
+        evaluate: true,
+        global_defs: {
+            "CONFIG.VALUE": 42,
+            "FOO.BAR": "moo",
+        },
+        properties: true,
+    }
+    input: {
+        const FOO = { BAR: 0 };
+        console.log(FOO.BAR);
+        console.log(++CONFIG.DEBUG);
+        console.log(++CONFIG.VALUE);
+        console.log(++CONFIG["VAL" + "UE"]);
+        console.log(++DEBUG[CONFIG.VALUE]);
+        CONFIG.VALUE.FOO = "bar";
+        console.log(CONFIG);
+    }
+    expect: {
+        const FOO = { BAR: 0 };
+        console.log("moo");
+        console.log(++CONFIG.DEBUG);
+        console.log(++CONFIG.VALUE);
+        console.log(++CONFIG.VALUE);
+        console.log(++DEBUG[42]);
+        CONFIG.VALUE.FOO = "bar";
+        console.log(CONFIG);
+    }
+    expect_warnings: [
+        'WARN: global_defs CONFIG.VALUE redefined [test/compress/global_defs.js:126,22]',
+        'WARN: global_defs CONFIG.VALUE redefined [test/compress/global_defs.js:127,22]',
+        'WARN: global_defs CONFIG.VALUE redefined [test/compress/global_defs.js:129,8]',
+    ]
+}
diff --git a/test/compress/issue-208.js b/test/compress/issue-208.js
index 2f10378..fb9861f 100644
--- a/test/compress/issue-208.js
+++ b/test/compress/issue-208.js
@@ -27,3 +27,44 @@ do_update_rhs: {
         MY_DEBUG += 0;
     }
 }
+
+mixed: {
+    options = {
+        evaluate: true,
+        global_defs: {
+            DEBUG: 0,
+            ENV: 1,
+            FOO: 2,
+        }
+    }
+    input: {
+        const ENV = 3;
+        var FOO = 4;
+        f(ENV * 10);
+        --FOO;
+        DEBUG = 1;
+        DEBUG++;
+        DEBUG += 1;
+        f(DEBUG);
+        x = DEBUG;
+    }
+    expect: {
+        const ENV = 3;
+        var FOO = 4;
+        f(10);
+        --FOO;
+        DEBUG = 1;
+        DEBUG++;
+        DEBUG += 1;
+        f(0);
+        x = 0;
+    }
+    expect_warnings: [
+        'WARN: global_defs ENV redefined [test/compress/issue-208.js:41,14]',
+        'WARN: global_defs FOO redefined [test/compress/issue-208.js:42,12]',
+        'WARN: global_defs FOO redefined [test/compress/issue-208.js:44,10]',
+        'WARN: global_defs DEBUG redefined [test/compress/issue-208.js:45,8]',
+        'WARN: global_defs DEBUG redefined [test/compress/issue-208.js:46,8]',
+        'WARN: global_defs DEBUG redefined [test/compress/issue-208.js:47,8]',
+    ]
+}
diff --git a/test/input/global_defs/nested.js b/test/input/global_defs/nested.js
new file mode 100644
index 0000000..dbf5790
--- /dev/null
+++ b/test/input/global_defs/nested.js
@@ -0,0 +1 @@
+console.log(C.V, C.D);
diff --git a/test/input/global_defs/simple.js b/test/input/global_defs/simple.js
new file mode 100644
index 0000000..44d515e
--- /dev/null
+++ b/test/input/global_defs/simple.js
@@ -0,0 +1 @@
+console.log(D);
diff --git a/test/mocha/cli.js b/test/mocha/cli.js
index c5b571b..64599c5 100644
--- a/test/mocha/cli.js
+++ b/test/mocha/cli.js
@@ -100,4 +100,34 @@ describe("bin/uglifyjs", function () {
            done();
        });
     });
+    it("Should work with --define (simple)", function (done) {
+       var command = uglifyjscmd + ' test/input/global_defs/simple.js --define D=5 -c';
+
+       exec(command, function (err, stdout) {
+           if (err) throw err;
+
+           assert.strictEqual(stdout, "console.log(5);\n");
+           done();
+       });
+    });
+    it("Should work with --define (nested)", function (done) {
+       var command = uglifyjscmd + ' test/input/global_defs/nested.js --define C.D=5,C.V=3 -c';
+
+       exec(command, function (err, stdout) {
+           if (err) throw err;
+
+           assert.strictEqual(stdout, "console.log(3,5);\n");
+           done();
+       });
+    });
+    it("Should work with --define (AST_Node)", function (done) {
+       var command = uglifyjscmd + ' test/input/global_defs/simple.js --define console.log=stdout.println -c';
+
+       exec(command, function (err, stdout) {
+           if (err) throw err;
+
+           assert.strictEqual(stdout, "stdout.println(D);\n");
+           done();
+       });
+    });
 });

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-javascript/uglifyjs.git



More information about the Pkg-javascript-commits mailing list