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
This commit is contained in:
@@ -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
|
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.
|
||||||
|
|||||||
@@ -219,17 +219,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, {
|
||||||
@@ -991,6 +980,68 @@ merge(Compressor.prototype, {
|
|||||||
|| parent instanceof AST_Assign && parent.left === node;
|
|| 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) {
|
function best_of(ast1, ast2) {
|
||||||
return ast1.print_to_string().length >
|
return ast1.print_to_string().length >
|
||||||
ast2.print_to_string().length
|
ast2.print_to_string().length
|
||||||
@@ -2793,13 +2844,13 @@ merge(Compressor.prototype, {
|
|||||||
});
|
});
|
||||||
|
|
||||||
OPT(AST_SymbolRef, function(self, compressor){
|
OPT(AST_SymbolRef, function(self, compressor){
|
||||||
if (self.undeclared() && !isLHS(self, compressor.parent())) {
|
var def = self.resolve_defines(compressor);
|
||||||
var defines = compressor.option("global_defs");
|
if (def) {
|
||||||
if (defines && HOP(defines, self.name)) {
|
return def;
|
||||||
return make_node_from_constant(compressor, defines[self.name], self);
|
|
||||||
}
|
}
|
||||||
// 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 (self.undeclared() && !isLHS(self, compressor.parent())
|
||||||
|
&& (!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);
|
||||||
@@ -2809,7 +2860,6 @@ merge(Compressor.prototype, {
|
|||||||
return make_node(AST_Infinity, self).transform(compressor);
|
return make_node(AST_Infinity, self).transform(compressor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if (compressor.option("evaluate") && !isLHS(self, compressor.parent())) {
|
if (compressor.option("evaluate") && !isLHS(self, compressor.parent())) {
|
||||||
var d = self.definition();
|
var d = self.definition();
|
||||||
if (d && d.constant && d.init && d.init.is_constant(compressor)) {
|
if (d && d.constant && d.init && d.init.is_constant(compressor)) {
|
||||||
@@ -3085,6 +3135,10 @@ merge(Compressor.prototype, {
|
|||||||
});
|
});
|
||||||
|
|
||||||
OPT(AST_Dot, function(self, compressor){
|
OPT(AST_Dot, function(self, compressor){
|
||||||
|
var def = self.resolve_defines(compressor);
|
||||||
|
if (def) {
|
||||||
|
return def;
|
||||||
|
}
|
||||||
var prop = self.property;
|
var prop = self.property;
|
||||||
if (RESERVED_WORDS(prop) && !compressor.option("screw_ie8")) {
|
if (RESERVED_WORDS(prop) && !compressor.option("screw_ie8")) {
|
||||||
return make_node(AST_Sub, self, {
|
return make_node(AST_Sub, self, {
|
||||||
@@ -3114,4 +3168,12 @@ merge(Compressor.prototype, {
|
|||||||
return self;
|
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;
|
||||||
|
});
|
||||||
|
|
||||||
})();
|
})();
|
||||||
|
|||||||
22
lib/scope.js
22
lib/scope.js
@@ -206,14 +206,7 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){
|
|||||||
node.scope.uses_arguments = true;
|
node.scope.uses_arguments = true;
|
||||||
}
|
}
|
||||||
if (!sym) {
|
if (!sym) {
|
||||||
if (globals.has(name)) {
|
sym = self.def_global(node);
|
||||||
sym = globals.get(name);
|
|
||||||
} else {
|
|
||||||
sym = new SymbolDef(self, globals.size(), node);
|
|
||||||
sym.undeclared = true;
|
|
||||||
sym.global = true;
|
|
||||||
globals.set(name, sym);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
node.thedef = sym;
|
node.thedef = sym;
|
||||||
node.reference(options);
|
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(){
|
AST_Scope.DEFMETHOD("init_scope_vars", function(){
|
||||||
this.variables = new Dictionary(); // map name to AST_SymbolVar (variables defined in this scope; includes functions)
|
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)
|
this.functions = new Dictionary(); // map name to AST_SymbolDefun (functions defined in this scope)
|
||||||
|
|||||||
147
test/compress/global_defs.js
Normal file
147
test/compress/global_defs.js
Normal file
@@ -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]',
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -27,3 +27,44 @@ do_update_rhs: {
|
|||||||
MY_DEBUG += 0;
|
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]',
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|||||||
1
test/input/global_defs/nested.js
Normal file
1
test/input/global_defs/nested.js
Normal file
@@ -0,0 +1 @@
|
|||||||
|
console.log(C.V, C.D);
|
||||||
1
test/input/global_defs/simple.js
Normal file
1
test/input/global_defs/simple.js
Normal file
@@ -0,0 +1 @@
|
|||||||
|
console.log(D);
|
||||||
@@ -100,4 +100,34 @@ describe("bin/uglifyjs", function () {
|
|||||||
done();
|
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();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user