aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authoralexlamsl <alexlamsl@gmail.com>2017-02-18 19:27:31 +0800
committeralexlamsl <alexlamsl@gmail.com>2017-02-21 13:29:58 +0800
commite275148998638bdcf795257ed03941ca34e33018 (patch)
treec9019dec5f4a26a2ecfa47161cada4deff1c1bf7
parent974247c8c0e57901ef776e86784c8c9a1b87b5de (diff)
downloadtracifyjs-e275148998638bdcf795257ed03941ca34e33018.tar.gz
tracifyjs-e275148998638bdcf795257ed03941ca34e33018.zip
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
-rw-r--r--README.md2
-rw-r--r--lib/compress.js114
-rw-r--r--lib/scope.js22
-rw-r--r--test/compress/global_defs.js147
-rw-r--r--test/compress/issue-208.js41
-rw-r--r--test/input/global_defs/nested.js1
-rw-r--r--test/input/global_defs/simple.js1
-rw-r--r--test/mocha/cli.js30
8 files changed, 324 insertions, 34 deletions
diff --git a/README.md b/README.md
index a2eaeae4..1d1f2fcb 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 a60ba1a1..cb99a173 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 6ad12616..b0d92d7d 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 00000000..a69d031e
--- /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 2f103786..fb9861f6 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 00000000..dbf57909
--- /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 00000000..44d515e3
--- /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 c5b571bd..64599c51 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();
+ });
+ });
});