allow global_defs to pass in objects and arrays
- no longer throw errors against objects - now depends on `evaluate` - `unsafe` when using objects - support `"a.b":1` on both cli & API - emit warning if variable is modified fixes #1416 closes #1198
This commit is contained in:
parent
7f8d72d9d3
commit
263fb4c2d9
|
|
@ -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
|
||||
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
|
||||
separate file and include it into the build. For example you can have a
|
||||
`build/defines.js` file with the following:
|
||||
|
|
|
|||
|
|
@ -184,7 +184,7 @@ if (ARGS.reserve_domprops) {
|
|||
}
|
||||
|
||||
if (ARGS.d) {
|
||||
if (COMPRESS) COMPRESS.global_defs = getOptions("d");
|
||||
if (COMPRESS) COMPRESS.global_defs = getOptions("d", true);
|
||||
}
|
||||
|
||||
if (ARGS.pure_funcs) {
|
||||
|
|
|
|||
|
|
@ -82,6 +82,22 @@ function Compressor(options, false_by_default) {
|
|||
}, true);
|
||||
var sequences = this.options["sequences"];
|
||||
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 = {};
|
||||
};
|
||||
|
||||
|
|
@ -154,7 +170,7 @@ merge(Compressor.prototype, {
|
|||
this.walk(new TreeWalker(function(node){
|
||||
if (node instanceof AST_SymbolRef) {
|
||||
var d = node.definition();
|
||||
if (d && d.init) {
|
||||
if (d.init) {
|
||||
delete d.init._evaluated;
|
||||
}
|
||||
}
|
||||
|
|
@ -175,17 +191,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, {
|
||||
|
|
@ -977,8 +982,11 @@ merge(Compressor.prototype, {
|
|||
});
|
||||
|
||||
function isLHS(node, parent) {
|
||||
return parent instanceof AST_Unary && (parent.operator === "++" || parent.operator === "--")
|
||||
|| parent instanceof AST_Assign && parent.left === node;
|
||||
return (parent instanceof AST_Unary
|
||||
&& parent.expression === node
|
||||
&& (parent.operator == "++" || parent.operator == "--"))
|
||||
|| (parent instanceof AST_Assign
|
||||
&& parent.left === node);
|
||||
}
|
||||
|
||||
function best_of(ast1, ast2) {
|
||||
|
|
@ -998,12 +1006,15 @@ merge(Compressor.prototype, {
|
|||
// constant; otherwise it's the original or a replacement node.
|
||||
AST_Node.DEFMETHOD("evaluate", function(compressor){
|
||||
if (!compressor.option("evaluate")) return [ this ];
|
||||
var val;
|
||||
var val, has_global_defs;
|
||||
try {
|
||||
val = this._eval(compressor);
|
||||
has_global_defs = compressor._ev_has_global_defs;
|
||||
} catch(ex) {
|
||||
if (ex !== def) throw ex;
|
||||
return [ this ];
|
||||
} finally {
|
||||
delete compressor._ev_has_global_defs;
|
||||
}
|
||||
var node;
|
||||
try {
|
||||
|
|
@ -1011,6 +1022,17 @@ merge(Compressor.prototype, {
|
|||
} catch(ex) {
|
||||
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 ];
|
||||
});
|
||||
var unaryPrefix = makePredicate("! ~ - +");
|
||||
|
|
@ -1167,7 +1189,14 @@ merge(Compressor.prototype, {
|
|||
this._evaluating = true;
|
||||
try {
|
||||
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 (!HOP(d.init, '_evaluated')) {
|
||||
d.init._evaluated = ev(d.init, compressor);
|
||||
|
|
@ -2686,13 +2715,14 @@ 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);
|
||||
if (self.undeclared()) {
|
||||
var e = self.evaluate(compressor);
|
||||
if (e.length > 1) {
|
||||
return self === e[0] ? make_node_from_constant(compressor, e[1], self) : e[0];
|
||||
}
|
||||
// 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) {
|
||||
case "undefined":
|
||||
return make_node(AST_Undefined, self);
|
||||
|
|
|
|||
144
test/compress/global_defs.js
Normal file
144
test/compress/global_defs.js
Normal 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]',
|
||||
]
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
do_not_update_lhs: {
|
||||
options = {
|
||||
evaluate: true,
|
||||
global_defs: { DEBUG: 0 }
|
||||
}
|
||||
input: {
|
||||
|
|
@ -16,6 +17,7 @@ do_not_update_lhs: {
|
|||
|
||||
do_update_rhs: {
|
||||
options = {
|
||||
evaluate: true,
|
||||
global_defs: { DEBUG: 0 }
|
||||
}
|
||||
input: {
|
||||
|
|
@ -27,3 +29,29 @@ do_update_rhs: {
|
|||
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]',
|
||||
]
|
||||
}
|
||||
|
|
|
|||
1
test/input/issue-1467/simple.js
Normal file
1
test/input/issue-1467/simple.js
Normal file
|
|
@ -0,0 +1 @@
|
|||
console.log(D);
|
||||
1
test/input/issue-1467/unsafe.js
Normal file
1
test/input/issue-1467/unsafe.js
Normal file
|
|
@ -0,0 +1 @@
|
|||
console.log(C.V, C.D);
|
||||
|
|
@ -100,4 +100,24 @@ describe("bin/uglifyjs", function () {
|
|||
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();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user