This commit is contained in:
Alex Lam S.L 2017-02-12 09:37:13 +00:00 committed by GitHub
commit 520db07334
8 changed files with 249 additions and 22 deletions

View File

@ -450,6 +450,9 @@ 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 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. warning, you can pass `warnings=false` to turn off *all* warnings.
You can specify nested constants like `--define env.DEBUG=false` which requires
`-c unsafe` in order to work.
Another way of doing that is to declare your globals as constants in a Another way of doing that is to declare your globals as constants in a
separate file and include it into the build. For example you can have a separate file and include it into the build. For example you can have a
`build/defines.js` file with the following: `build/defines.js` file with the following:

View File

@ -184,7 +184,7 @@ if (ARGS.reserve_domprops) {
} }
if (ARGS.d) { if (ARGS.d) {
if (COMPRESS) COMPRESS.global_defs = getOptions("d"); if (COMPRESS) COMPRESS.global_defs = getOptions("d", true);
} }
if (ARGS.pure_funcs) { if (ARGS.pure_funcs) {

View File

@ -82,6 +82,22 @@ function Compressor(options, false_by_default) {
}, true); }, true);
var sequences = this.options["sequences"]; var sequences = this.options["sequences"];
this.sequences_limit = sequences == 1 ? 200 : sequences | 0; this.sequences_limit = sequences == 1 ? 200 : sequences | 0;
if ("global_defs" in this.options) {
var global_defs = this.options["global_defs"];
for (var key in global_defs) {
var tokens = key.split(/\./);
if (tokens.length > 1) {
var last = tokens.pop();
tokens.reduce(function(o, token) {
if (!(token in o)) {
o[token] = {};
}
return o[token];
}, global_defs)[last] = global_defs[key];
delete global_defs[key];
}
}
}
this.warnings_produced = {}; this.warnings_produced = {};
}; };
@ -154,7 +170,7 @@ merge(Compressor.prototype, {
this.walk(new TreeWalker(function(node){ this.walk(new TreeWalker(function(node){
if (node instanceof AST_SymbolRef) { if (node instanceof AST_SymbolRef) {
var d = node.definition(); var d = node.definition();
if (d && d.init) { if (d.init) {
delete d.init._evaluated; delete d.init._evaluated;
} }
} }
@ -175,17 +191,6 @@ merge(Compressor.prototype, {
}; };
function make_node_from_constant(compressor, val, orig) { 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) { switch (typeof val) {
case "string": case "string":
return make_node(AST_String, orig, { return make_node(AST_String, orig, {
@ -977,8 +982,11 @@ merge(Compressor.prototype, {
}); });
function isLHS(node, parent) { function isLHS(node, parent) {
return parent instanceof AST_Unary && (parent.operator === "++" || parent.operator === "--") return (parent instanceof AST_Unary
|| parent instanceof AST_Assign && parent.left === node; && parent.expression === node
&& (parent.operator == "++" || parent.operator == "--"))
|| (parent instanceof AST_Assign
&& parent.left === node);
} }
function best_of(ast1, ast2) { function best_of(ast1, ast2) {
@ -998,12 +1006,15 @@ merge(Compressor.prototype, {
// constant; otherwise it's the original or a replacement node. // constant; otherwise it's the original or a replacement node.
AST_Node.DEFMETHOD("evaluate", function(compressor){ AST_Node.DEFMETHOD("evaluate", function(compressor){
if (!compressor.option("evaluate")) return [ this ]; if (!compressor.option("evaluate")) return [ this ];
var val; var val, has_global_defs;
try { try {
val = this._eval(compressor); val = this._eval(compressor);
has_global_defs = compressor._ev_has_global_defs;
} catch(ex) { } catch(ex) {
if (ex !== def) throw ex; if (ex !== def) throw ex;
return [ this ]; return [ this ];
} finally {
delete compressor._ev_has_global_defs;
} }
var node; var node;
try { try {
@ -1011,6 +1022,17 @@ merge(Compressor.prototype, {
} catch(ex) { } catch(ex) {
return [ this ]; return [ this ];
} }
if (has_global_defs) {
var lhs, parent = this, level = 0;
do {
lhs = parent;
parent = compressor.parent(level++);
} while (parent instanceof AST_PropAccess && parent.expression === lhs);
if (isLHS(lhs, parent)) {
compressor.warn('global_defs ' + lhs.print_to_string() + ' redefined [{file}:{line},{col}]', lhs.start);
return [ this ];
}
}
return [ best_of(node, this), val ]; return [ best_of(node, this), val ];
}); });
var unaryPrefix = makePredicate("! ~ - +"); var unaryPrefix = makePredicate("! ~ - +");
@ -1167,7 +1189,14 @@ merge(Compressor.prototype, {
this._evaluating = true; this._evaluating = true;
try { try {
var d = this.definition(); var d = this.definition();
if (d && (d.constant || compressor.option("reduce_vars") && !d.modified) && d.init) { if (d.undeclared) {
var defines = compressor.option("global_defs");
if (defines && HOP(defines, d.name)) {
compressor._ev_has_global_defs = true;
return defines[d.name];
}
}
if ((d.constant || compressor.option("reduce_vars") && !d.modified) && d.init) {
if (compressor.option("unsafe")) { if (compressor.option("unsafe")) {
if (!HOP(d.init, '_evaluated')) { if (!HOP(d.init, '_evaluated')) {
d.init._evaluated = ev(d.init, compressor); d.init._evaluated = ev(d.init, compressor);
@ -2686,13 +2715,14 @@ merge(Compressor.prototype, {
}); });
OPT(AST_SymbolRef, function(self, compressor){ OPT(AST_SymbolRef, function(self, compressor){
if (self.undeclared() && !isLHS(self, compressor.parent())) { if (self.undeclared()) {
var defines = compressor.option("global_defs"); var e = self.evaluate(compressor);
if (defines && HOP(defines, self.name)) { if (e.length > 1) {
return make_node_from_constant(compressor, defines[self.name], self); return self === e[0] ? make_node_from_constant(compressor, e[1], self) : e[0];
} }
// testing against !self.scope.uses_with first is an optimization // testing against !self.scope.uses_with first is an optimization
if (!self.scope.uses_with || !compressor.find_parent(AST_With)) { if (!isLHS(self, compressor)
&& (!self.scope.uses_with || !compressor.find_parent(AST_With))) {
switch (self.name) { switch (self.name) {
case "undefined": case "undefined":
return make_node(AST_Undefined, self); return make_node(AST_Undefined, self);

View File

@ -0,0 +1,144 @@
must_replace: {
options = {
evaluate: true,
global_defs: {
D: "foo bar",
}
}
input: {
console.log(D);
}
expect: {
console.log("foo bar");
}
}
keyword: {
options = {
evaluate: true,
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) {
return CONFIG.VALUE;
}
function g() {
var CONFIG = { VALUE: 1 };
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 = {
evaluate: true,
global_defs: {
"CONFIG.DEBUG": [ 0 ],
"CONFIG.VALUE": 42,
},
unsafe: true,
}
input: {
function f(CONFIG) {
return CONFIG.VALUE;
}
function g() {
var CONFIG = { VALUE: 1 };
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");
}
}
mixed: {
options = {
evaluate: true,
global_defs: {
"CONFIG.VALUE": 42,
},
unsafe: true,
}
input: {
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, ++(1 + 1));
console.log(({ 42: "foo" })[CONFIG.VALUE] += "bar");
}
expect: {
console.log(++CONFIG.DEBUG);
console.log(++CONFIG.VALUE);
console.log(++CONFIG["VALUE"]);
console.log(++DEBUG[42]);
CONFIG.VALUE.FOO = "bar";
console.log(CONFIG, ++2);
console.log("foo" += "bar");
}
expect_warnings: [
'WARN: global_defs CONFIG.VALUE redefined [test/compress/global_defs.js:123,22]',
'WARN: global_defs CONFIG["VALUE"] redefined [test/compress/global_defs.js:124,22]',
'WARN: global_defs CONFIG.VALUE.FOO redefined [test/compress/global_defs.js:126,8]',
]
}

View File

@ -1,5 +1,6 @@
do_not_update_lhs: { do_not_update_lhs: {
options = { options = {
evaluate: true,
global_defs: { DEBUG: 0 } global_defs: { DEBUG: 0 }
} }
input: { input: {
@ -16,6 +17,7 @@ do_not_update_lhs: {
do_update_rhs: { do_update_rhs: {
options = { options = {
evaluate: true,
global_defs: { DEBUG: 0 } global_defs: { DEBUG: 0 }
} }
input: { input: {
@ -27,3 +29,29 @@ do_update_rhs: {
MY_DEBUG += 0; MY_DEBUG += 0;
} }
} }
mixed: {
options = {
evaluate: true,
global_defs: { DEBUG: 0 }
}
input: {
DEBUG = 1;
DEBUG++;
DEBUG += 1;
f(DEBUG);
x = DEBUG;
}
expect: {
DEBUG = 1;
DEBUG++;
DEBUG += 1;
f(0);
x = 0;
}
expect_warnings: [
'WARN: global_defs DEBUG redefined [test/compress/issue-208.js:39,8]',
'WARN: global_defs DEBUG redefined [test/compress/issue-208.js:40,8]',
'WARN: global_defs DEBUG redefined [test/compress/issue-208.js:41,8]',
]
}

View File

@ -0,0 +1 @@
console.log(D);

View File

@ -0,0 +1 @@
console.log(C.V, C.D);

View File

@ -100,4 +100,24 @@ describe("bin/uglifyjs", function () {
done(); done();
}); });
}); });
it("Should work with --define", function (done) {
var command = uglifyjscmd + ' test/input/issue-1467/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 & -c unsafe", function (done) {
var command = uglifyjscmd + ' test/input/issue-1467/unsafe.js --define C.D=5,C.V=3 -c unsafe';
exec(command, function (err, stdout) {
if (err) throw err;
assert.strictEqual(stdout, "console.log(3,5);\n");
done();
});
});
}); });