Compare commits

..

25 Commits

Author SHA1 Message Date
Alex Lam S.L
5843494ee2 v3.3.28 2018-05-29 12:25:42 +00:00
Alex Lam S.L
efa21ae3e6 fix corner case in reduce_vars (#3151) 2018-05-26 05:45:44 +08:00
Alex Lam S.L
24d9633a35 fix corner cases with eval() (#3147)
fixes #3146
2018-05-24 14:29:30 +08:00
Alex Lam S.L
7963b96681 augment tests for inline source maps (#3145) 2018-05-24 02:37:51 +08:00
Alex Lam S.L
8c62d854ce augment tests for RegExp (#3144) 2018-05-23 17:24:13 +08:00
Alex Lam S.L
69931574e1 v3.3.27 2018-05-22 17:09:12 +00:00
Alex Lam S.L
b5af8a1914 fix corner case in reduce_vars (#3141)
fixes #3140
2018-05-21 15:53:51 +08:00
Alex Lam S.L
c14d09ba84 v3.3.26 2018-05-20 17:32:32 +00:00
Alex Lam S.L
4fc39d8dad fix corner case in collapse_vars (#3139) 2018-05-19 05:45:14 +08:00
exvisory
0b7c70f726 Update README.md to clarify --source-map filename option (#3137)
Clarify that the --source-map filename option does NOT change the source map output filename but does set the 'file' attribute within the output source map. This was already documented in the API section of the README so I just copied that to the CLI options section, and a fragment to the CLI summary.
2018-05-18 15:50:36 +08:00
Alex Lam S.L
f72d3029dd v3.3.25 2018-05-12 23:50:40 +00:00
Alex Lam S.L
1a0d6edc81 remove colors dependency (#3133) 2018-05-13 07:50:02 +08:00
Alex Lam S.L
7b59b2f5b2 replace mocha dependency (#3131) 2018-05-11 20:15:34 +08:00
Alex Lam S.L
7bc7704edf fix corner case in reduce_vars (#3129) 2018-05-10 18:45:20 +08:00
Alex Lam S.L
14e712ee80 fix corner case in call binding (#3128)
fixes #3127
2018-05-10 06:16:35 +08:00
Alex Lam S.L
f83adcc995 v3.3.24 2018-05-07 20:17:34 +00:00
Alex Lam S.L
df8a99439a fix various corner cases (#3126)
- augment ufuzz/reminify test options

fixes #3125
2018-05-07 07:36:25 +08:00
Alex Lam S.L
6b91d12ec3 fix corner case in reduce_vars (#3124) 2018-05-06 16:42:35 +08:00
Alex Lam S.L
f37b91879f fix various corner cases (#3123) 2018-05-05 13:17:50 +08:00
Alex Lam S.L
d835c72c80 speed up collapse_vars (#3119) 2018-05-04 18:38:13 +08:00
Alex Lam S.L
c4cebb4b01 fix reduce_vars on nested invocations (#3118) 2018-05-04 06:05:38 +08:00
Alex Lam S.L
d51a00a450 compress AST_Sequence within AST_Call (#3117) 2018-05-03 19:14:56 +08:00
Alex Lam S.L
fc0f168a0c better fix for #3113 (#3115) 2018-05-03 15:51:51 +08:00
Alex Lam S.L
a0ca595c2c fix TreeWalker scan order (#3114)
fixes #3113
2018-05-03 00:27:45 +08:00
Alex Lam S.L
1a314e9f60 improve reduce_vars (#3112)
fixes #3110
2018-05-02 15:11:45 +08:00
19 changed files with 1469 additions and 166 deletions

View File

@@ -118,7 +118,8 @@ a double dash to prevent input files being used as option arguments:
JS that was generated from some other original
code. Specify "inline" if the source map is
included within the sources.
`filename` Name and/or location of the output source.
`filename` Filename and/or location of the output source
(sets `file` attribute in source map).
`includeSources` Pass this flag if you want to include
the content of source files in the
source map as sourcesContent property.
@@ -149,7 +150,9 @@ debugging your compressed JavaScript. To get a source map, pass
Additional options:
- `--source-map "filename='<NAME>'"` to specify the name of the source map.
- `--source-map "filename='<NAME>'"` to specify the name of the source map. The value of
`filename` is only used to set `file` attribute (see [the spec][sm-spec])
in source map file.
- `--source-map "root='<URL>'"` to pass the URL where the original files can be found.

View File

@@ -314,6 +314,9 @@ var AST_Scope = DEFNODE("Scope", "variables functions uses_with uses_eval parent
if (this.functions) node.functions = this.functions.clone();
if (this.enclosed) node.enclosed = this.enclosed.slice();
return node;
},
pinned: function() {
return this.uses_eval || this.uses_with;
}
}, AST_Block);
@@ -543,12 +546,11 @@ var AST_Call = DEFNODE("Call", "expression args", {
args: "[AST_Node*] array of arguments"
},
_walk: function(visitor) {
return visitor._visit(this, function(){
var args = this.args;
for (var i = 0, len = args.length; i < len; i++) {
args[i]._walk(visitor);
}
return visitor._visit(this, function() {
this.expression._walk(visitor);
this.args.forEach(function(node) {
node._walk(visitor);
});
});
}
});

View File

@@ -340,30 +340,38 @@ merge(Compressor.prototype, {
}
}
(function(def){
(function(def) {
def(AST_Node, noop);
function reset_def(compressor, def) {
function reset_def(tw, compressor, def) {
def.assignments = 0;
def.chained = false;
def.direct_access = false;
def.escaped = false;
if (def.scope.uses_eval || def.scope.uses_with) {
if (def.scope.pinned()) {
def.fixed = false;
} else if (!compressor.exposed(def)) {
def.fixed = def.init;
} else {
def.fixed = false;
}
if (def.fixed instanceof AST_Defun && !all(def.references, function(ref) {
var scope = ref.scope;
do {
if (def.scope === scope) return true;
} while (scope instanceof AST_Function && (scope = scope.parent_scope));
})) {
tw.defun_ids[def.id] = false;
}
def.recursive_refs = 0;
def.references = [];
def.should_replace = undefined;
def.single_use = undefined;
}
function reset_variables(tw, compressor, node) {
node.variables.each(function(def) {
reset_def(compressor, def);
function reset_variables(tw, compressor, scope) {
scope.variables.each(function(def) {
reset_def(tw, compressor, def);
if (def.fixed === null) {
def.safe_ids = tw.safe_ids;
mark(tw, def, true);
@@ -372,6 +380,48 @@ merge(Compressor.prototype, {
mark(tw, def, true);
}
});
scope.may_call_this = function() {
scope.may_call_this = noop;
if (!scope.contains_this()) return;
scope.functions.each(function(def) {
if (def.init instanceof AST_Defun && !(def.id in tw.defun_ids)) {
tw.defun_ids[def.id] = false;
}
});
};
}
function mark_defun(tw, def) {
if (def.id in tw.defun_ids) {
var marker = tw.defun_ids[def.id];
if (!marker) return;
var visited = tw.defun_visited[def.id];
if (marker === tw.safe_ids) {
if (!visited) return def.fixed;
} else if (visited) {
def.init.enclosed.forEach(function(d) {
if (def.init.variables.get(d.name) === d) return;
if (!safe_to_read(tw, d)) d.fixed = false;
});
} else {
tw.defun_ids[def.id] = false;
}
} else {
if (!tw.in_loop) {
tw.defun_ids[def.id] = tw.safe_ids;
return def.fixed;
}
tw.defun_ids[def.id] = false;
}
}
function walk_defuns(tw, scope) {
scope.functions.each(function(def) {
if (def.init instanceof AST_Defun && !tw.defun_visited[def.id]) {
tw.defun_ids[def.id] = tw.safe_ids;
def.init.walk(tw);
}
});
}
function push(tw) {
@@ -398,7 +448,7 @@ merge(Compressor.prototype, {
return def.fixed instanceof AST_Defun;
}
function safe_to_assign(tw, def, value) {
function safe_to_assign(tw, def, scope, value) {
if (def.fixed === undefined) return true;
if (def.fixed === null && def.safe_ids) {
def.safe_ids[def.id] = false;
@@ -409,6 +459,9 @@ merge(Compressor.prototype, {
if (!safe_to_read(tw, def)) return false;
if (def.fixed === false) return false;
if (def.fixed != null && (!value || def.references.length > def.assignments)) return false;
if (def.fixed instanceof AST_Defun) {
return value instanceof AST_Node && def.fixed.parent_scope === scope;
}
return all(def.orig, function(sym) {
return !(sym instanceof AST_SymbolDefun
|| sym instanceof AST_SymbolLambda);
@@ -417,8 +470,7 @@ merge(Compressor.prototype, {
function ref_once(tw, compressor, def) {
return compressor.option("unused")
&& !def.scope.uses_eval
&& !def.scope.uses_with
&& !def.scope.pinned()
&& def.references.length - def.recursive_refs == 1
&& tw.loop_ids[def.id] === tw.in_loop;
}
@@ -453,7 +505,10 @@ merge(Compressor.prototype, {
mark_escaped(tw, d, scope, parent, value, level + 1, depth + 1);
if (value) return;
}
if (level == 0) d.direct_access = true;
if (level > 0) return;
if (parent instanceof AST_Sequence && node !== parent.tail_node()) return;
if (parent instanceof AST_SimpleStatement) return;
d.direct_access = true;
}
var suppressor = new TreeWalker(function(node) {
@@ -468,19 +523,24 @@ merge(Compressor.prototype, {
reset_variables(tw, compressor, this);
descend();
pop(tw);
walk_defuns(tw, this);
return true;
});
def(AST_Assign, function(tw) {
def(AST_Assign, function(tw, descend, compressor) {
var node = this;
if (!(node.left instanceof AST_SymbolRef)) return;
var d = node.left.definition();
var sym = node.left;
if (!(sym instanceof AST_SymbolRef)) return;
var d = sym.definition();
var fixed = d.fixed;
if (!fixed && node.operator != "=") return;
if (!safe_to_assign(tw, d, node.right)) return;
d.references.push(node.left);
if (!safe_to_assign(tw, d, sym.scope, node.right)) return;
var eq = node.operator == "=";
var value = eq ? node.right : node;
if (is_modified(compressor, tw, node, value, 0)) return;
d.references.push(sym);
d.assignments++;
if (node.operator != "=") d.chained = true;
d.fixed = node.operator == "=" ? function() {
if (!eq) d.chained = true;
d.fixed = eq ? function() {
return node.right;
} : function() {
return make_node(AST_Binary, node, {
@@ -492,6 +552,7 @@ merge(Compressor.prototype, {
mark(tw, d, false);
node.right.walk(tw);
mark(tw, d, true);
mark_escaped(tw, d, sym.scope, node, value, 0, 1);
return true;
});
def(AST_Binary, function(tw) {
@@ -502,6 +563,18 @@ merge(Compressor.prototype, {
pop(tw);
return true;
});
def(AST_Call, function(tw, descend) {
tw.find_parent(AST_Scope).may_call_this();
var exp = this.expression;
if (!(exp instanceof AST_SymbolRef)) return;
var def = exp.definition();
if (!(def.fixed instanceof AST_Defun)) return;
var defun = mark_defun(tw, def);
if (!defun) return;
descend();
defun.walk(tw);
return true;
});
def(AST_Case, function(tw) {
push(tw);
this.expression.walk(tw);
@@ -528,12 +601,16 @@ merge(Compressor.prototype, {
return true;
});
def(AST_Defun, function(tw, descend, compressor) {
var id = this.name.definition().id;
if (tw.defun_visited[id]) return true;
if (tw.defun_ids[id] !== tw.safe_ids) return true;
tw.defun_visited[id] = true;
this.inlined = false;
var save_ids = tw.safe_ids;
tw.safe_ids = Object.create(null);
push(tw);
reset_variables(tw, compressor, this);
descend();
tw.safe_ids = save_ids;
pop(tw);
walk_defuns(tw, this);
return true;
});
def(AST_Do, function(tw) {
@@ -606,6 +683,7 @@ merge(Compressor.prototype, {
}
descend();
pop(tw);
walk_defuns(tw, node);
return true;
});
def(AST_If, function(tw) {
@@ -645,7 +723,7 @@ merge(Compressor.prototype, {
if (value instanceof AST_Lambda && recursive_ref(tw, d)) {
d.recursive_refs++;
} else if (value && ref_once(tw, compressor, d)) {
d.single_use = value instanceof AST_Lambda
d.single_use = value instanceof AST_Lambda && !value.pinned()
|| d.scope === this.scope && value.is_constant_expression();
} else {
d.single_use = false;
@@ -659,12 +737,23 @@ merge(Compressor.prototype, {
}
}
mark_escaped(tw, d, this.scope, this, value, 0, 1);
var parent;
if (d.fixed instanceof AST_Defun
&& !((parent = tw.parent()) instanceof AST_Call && parent.expression === this)) {
var defun = mark_defun(tw, d);
if (defun) defun.walk(tw);
}
});
def(AST_Toplevel, function(tw, descend, compressor) {
this.globals.each(function(def) {
reset_def(compressor, def);
reset_def(tw, compressor, def);
});
push(tw);
reset_variables(tw, compressor, this);
descend();
pop(tw);
walk_defuns(tw, this);
return true;
});
def(AST_Try, function(tw) {
push(tw);
@@ -681,12 +770,13 @@ merge(Compressor.prototype, {
def(AST_Unary, function(tw, descend) {
var node = this;
if (node.operator != "++" && node.operator != "--") return;
if (!(node.expression instanceof AST_SymbolRef)) return;
var d = node.expression.definition();
var exp = node.expression;
if (!(exp instanceof AST_SymbolRef)) return;
var d = exp.definition();
var fixed = d.fixed;
if (!fixed) return;
if (!safe_to_assign(tw, d, true)) return;
d.references.push(node.expression);
if (!safe_to_assign(tw, d, exp.scope, true)) return;
d.references.push(exp);
d.assignments++;
d.chained = true;
d.fixed = function() {
@@ -708,7 +798,7 @@ merge(Compressor.prototype, {
var node = this;
var d = node.name.definition();
if (node.value) {
if (safe_to_assign(tw, d, node.value)) {
if (safe_to_assign(tw, d, node.name.scope, node.value)) {
d.fixed = function() {
return node.value;
};
@@ -736,19 +826,25 @@ merge(Compressor.prototype, {
});
AST_Toplevel.DEFMETHOD("reset_opt_flags", function(compressor) {
var reduce_vars = compressor.option("reduce_vars");
var tw = new TreeWalker(function(node, descend) {
var tw = new TreeWalker(compressor.option("reduce_vars") ? function(node, descend) {
node._squeezed = false;
node._optimized = false;
return node.reduce_vars(tw, descend, compressor);
} : function(node) {
node._squeezed = false;
node._optimized = false;
if (reduce_vars) return node.reduce_vars(tw, descend, compressor);
});
// Flow control for visiting `AST_Defun`s
tw.defun_ids = Object.create(null);
tw.defun_visited = Object.create(null);
// Record the loop body in which `AST_SymbolDeclaration` is first encountered
tw.in_loop = null;
tw.loop_ids = Object.create(null);
// Stack of look-up tables to keep track of whether a `SymbolDef` has been
// properly assigned before use:
// - `push()` & `pop()` when visiting conditional branches
// - backup & restore via `save_ids` when visiting out-of-order sections
tw.safe_ids = Object.create(null);
tw.in_loop = null;
tw.loop_ids = Object.create(null);
this.walk(tw);
});
@@ -840,15 +936,21 @@ merge(Compressor.prototype, {
type: typeof val
}));
}
};
}
function needs_unbinding(compressor, val) {
return val instanceof AST_PropAccess
|| compressor.has_directive("use strict")
&& is_undeclared_ref(val)
&& val.name == "eval";
}
// we shouldn't compress (1,func)(something) to
// func(something) because that changes the meaning of
// the func (becomes lexical instead of global).
function maintain_this_binding(parent, orig, val) {
function maintain_this_binding(compressor, parent, orig, val) {
if (parent instanceof AST_UnaryPrefix && parent.operator == "delete"
|| parent instanceof AST_Call && parent.expression === orig
&& (val instanceof AST_PropAccess || val instanceof AST_SymbolRef && val.name == "eval")) {
|| parent.TYPE == "Call" && parent.expression === orig && needs_unbinding(compressor, val)) {
return make_sequence(orig, [ make_node(AST_Number, orig, { value: 0 }), val ]);
}
return val;
@@ -962,7 +1064,7 @@ merge(Compressor.prototype, {
// Will not attempt to collapse assignments into or past code blocks
// which are not sequentially executed, e.g. loops and conditionals.
function collapse(statements, compressor) {
if (scope.uses_eval || scope.uses_with) return statements;
if (scope.pinned()) return statements;
var args;
var candidates = [];
var stat_index = statements.length;
@@ -1001,9 +1103,11 @@ merge(Compressor.prototype, {
if (is_lhs(node, parent)) {
if (value_def) replaced++;
return node;
} else {
replaced++;
if (value_def && candidate instanceof AST_VarDef) return node;
}
CHANGED = abort = true;
replaced++;
compressor.info("Collapsing {name} [{file}:{line},{col}]", {
name: node.print_to_string(),
file: node.start.file,
@@ -1014,14 +1118,10 @@ merge(Compressor.prototype, {
return make_node(AST_UnaryPrefix, candidate, candidate);
}
if (candidate instanceof AST_VarDef) {
if (value_def) {
abort = false;
return node;
}
var def = candidate.name.definition();
if (def.references.length - def.replaced == 1 && !compressor.exposed(def)) {
def.replaced++;
return maintain_this_binding(parent, node, candidate.value);
return maintain_this_binding(compressor, parent, node, candidate.value);
}
return make_node(AST_Assign, candidate, {
operator: "=",
@@ -1062,7 +1162,7 @@ merge(Compressor.prototype, {
if (is_lhs(node, multi_replacer.parent())) return node;
def.replaced++;
value_def.replaced--;
return candidate.value;
return candidate.value.clone();
}
// Skip (non-executed) functions and (leading) default case in switch statements
if (node instanceof AST_Default || node instanceof AST_Scope) return node;
@@ -1149,7 +1249,10 @@ merge(Compressor.prototype, {
}
function should_stop(node, parent) {
if (node instanceof AST_Assign) return node.operator != "=" && lhs.equivalent_to(node.left);
if (parent instanceof AST_For) return node !== parent.init;
if (node instanceof AST_Assign) {
return node.operator != "=" && lhs.equivalent_to(node.left);
}
if (node instanceof AST_Call) {
return lhs instanceof AST_PropAccess && lhs.equivalent_to(node.expression);
}
@@ -1158,7 +1261,6 @@ merge(Compressor.prototype, {
if (node instanceof AST_LoopControl) return true;
if (node instanceof AST_Try) return true;
if (node instanceof AST_With) return true;
if (parent instanceof AST_For) return node !== parent.init;
if (replace_all) return false;
return node instanceof AST_SymbolRef && !node.is_declared(compressor);
}
@@ -1198,7 +1300,7 @@ merge(Compressor.prototype, {
if (fn instanceof AST_Function
&& !fn.name
&& !fn.uses_arguments
&& !fn.uses_eval
&& !fn.pinned()
&& (iife = compressor.parent()) instanceof AST_Call
&& iife.expression === fn) {
var fn_strict = compressor.has_directive("use strict");
@@ -1215,9 +1317,12 @@ merge(Compressor.prototype, {
}));
if (sym.name in names) continue;
names[sym.name] = true;
if (!arg) arg = make_node(AST_Undefined, sym).transform(compressor);
else {
var tw = new TreeWalker(function(node) {
if (!arg) {
arg = make_node(AST_Undefined, sym).transform(compressor);
} else if (arg instanceof AST_Lambda && arg.pinned()) {
arg = null;
} else {
arg.walk(new TreeWalker(function(node) {
if (!arg) return true;
if (node instanceof AST_SymbolRef && fn.variables.has(node.name)) {
var s = node.definition().scope;
@@ -1226,12 +1331,11 @@ merge(Compressor.prototype, {
}
arg = null;
}
if (node instanceof AST_This && (fn_strict || !tw.find_parent(AST_Scope))) {
if (node instanceof AST_This && (fn_strict || !this.find_parent(AST_Scope))) {
arg = null;
return true;
}
});
arg.walk(tw);
}));
}
if (arg) candidates.unshift([ make_node(AST_VarDef, sym, {
name: sym,
@@ -3159,7 +3263,7 @@ merge(Compressor.prototype, {
if (!compressor.option("unused")) return;
if (compressor.has_directive("use asm")) return;
var self = this;
if (self.uses_eval || self.uses_with) return;
if (self.pinned()) return;
var drop_funcs = !(self instanceof AST_Toplevel) || compressor.toplevel.funcs;
var drop_vars = !(self instanceof AST_Toplevel) || compressor.toplevel.vars;
var assign_as_unused = /keep_assign/.test(compressor.option("unused")) ? return_false : function(node, props) {
@@ -3269,7 +3373,7 @@ merge(Compressor.prototype, {
}
if (value) {
props.push(value);
return maintain_this_binding(parent, node, make_sequence(node, props.map(function(prop) {
return maintain_this_binding(compressor, parent, node, make_sequence(node, props.map(function(prop) {
return prop.transform(tt);
})));
}
@@ -4395,7 +4499,24 @@ merge(Compressor.prototype, {
return self;
});
OPT(AST_Call, function(self, compressor){
AST_Call.DEFMETHOD("lift_sequences", function(compressor) {
if (!compressor.option("sequences")) return this;
var exp = this.expression;
if (!(exp instanceof AST_Sequence)) return this;
var tail = exp.tail_node();
if (needs_unbinding(compressor, tail) && !(this instanceof AST_New)) return this;
var expressions = exp.expressions.slice(0, -1);
var node = this.clone();
node.expression = tail;
expressions.push(node);
return make_sequence(this, expressions).optimize(compressor);
});
OPT(AST_Call, function(self, compressor) {
var seq = self.lift_sequences(compressor);
if (seq !== self) {
return seq;
}
var exp = self.expression;
var fn = exp;
if (compressor.option("reduce_vars") && fn instanceof AST_SymbolRef) {
@@ -4405,7 +4526,7 @@ merge(Compressor.prototype, {
if (compressor.option("unused")
&& is_func
&& !fn.uses_arguments
&& !fn.uses_eval) {
&& !fn.pinned()) {
var pos = 0, last = 0;
for (var i = 0, len = self.args.length; i < len; i++) {
var trim = i >= fn.argnames.length;
@@ -4596,13 +4717,16 @@ merge(Compressor.prototype, {
func = func.fixed_value();
}
if (func instanceof AST_Lambda && !func.contains_this()) {
return make_sequence(this, [
return (self.args.length ? make_sequence(this, [
self.args[0],
make_node(AST_Call, self, {
expression: exp.expression,
args: self.args.slice(1)
})
]).optimize(compressor);
]) : make_node(AST_Call, self, {
expression: exp.expression,
args: []
})).optimize(compressor);
}
break;
}
@@ -4682,7 +4806,7 @@ merge(Compressor.prototype, {
var def, value, scope, in_loop, level = -1;
if (can_inline
&& !fn.uses_arguments
&& !fn.uses_eval
&& !fn.pinned()
&& !(fn.name && fn instanceof AST_Function)
&& (value = can_flatten_body(stat))
&& (exp === fn
@@ -4903,7 +5027,11 @@ merge(Compressor.prototype, {
}
});
OPT(AST_New, function(self, compressor){
OPT(AST_New, function(self, compressor) {
var seq = self.lift_sequences(compressor);
if (seq !== self) {
return seq;
}
if (compressor.option("unsafe")) {
var exp = self.expression;
if (is_undeclared_ref(exp)) {
@@ -4927,7 +5055,7 @@ merge(Compressor.prototype, {
var end = expressions.length - 1;
trim_right_for_undefined();
if (end == 0) {
self = maintain_this_binding(compressor.parent(), compressor.self(), expressions[0]);
self = maintain_this_binding(compressor, compressor.parent(), compressor.self(), expressions[0]);
if (!(self instanceof AST_Sequence)) self = self.optimize(compressor);
return self;
}
@@ -4959,14 +5087,12 @@ merge(Compressor.prototype, {
});
AST_Unary.DEFMETHOD("lift_sequences", function(compressor){
if (compressor.option("sequences")) {
if (this.expression instanceof AST_Sequence) {
var x = this.expression.expressions.slice();
var e = this.clone();
e.expression = x.pop();
x.push(e);
return make_sequence(this, x).optimize(compressor);
}
if (compressor.option("sequences") && this.expression instanceof AST_Sequence) {
var x = this.expression.expressions.slice();
var e = this.clone();
e.expression = x.pop();
x.push(e);
return make_sequence(this, x).optimize(compressor);
}
return this;
});
@@ -5248,7 +5374,7 @@ merge(Compressor.prototype, {
var ll = fuzzy_eval(self.left);
if (!ll) {
compressor.warn("Condition left of && always false [{file}:{line},{col}]", self.start);
return maintain_this_binding(compressor.parent(), compressor.self(), self.left).optimize(compressor);
return maintain_this_binding(compressor, compressor.parent(), compressor.self(), self.left).optimize(compressor);
} else if (!(ll instanceof AST_Node)) {
compressor.warn("Condition left of && always true [{file}:{line},{col}]", self.start);
return make_sequence(self, [ self.left, self.right ]).optimize(compressor);
@@ -5287,7 +5413,7 @@ merge(Compressor.prototype, {
return make_sequence(self, [ self.left, self.right ]).optimize(compressor);
} else if (!(ll instanceof AST_Node)) {
compressor.warn("Condition left of || always true [{file}:{line},{col}]", self.start);
return maintain_this_binding(compressor.parent(), compressor.self(), self.left).optimize(compressor);
return maintain_this_binding(compressor, compressor.parent(), compressor.self(), self.left).optimize(compressor);
}
var rr = self.right.evaluate(compressor);
if (!rr) {
@@ -6163,7 +6289,7 @@ merge(Compressor.prototype, {
return self;
});
AST_Lambda.DEFMETHOD("contains_this", function() {
AST_Scope.DEFMETHOD("contains_this", function() {
var result;
var self = this;
self.walk(new TreeWalker(function(node) {

View File

@@ -63,12 +63,12 @@ SymbolDef.prototype = {
unmangleable: function(options) {
if (!options) options = {};
return (this.global && !options.toplevel)
return this.global && !options.toplevel
|| this.undeclared
|| (!options.eval && (this.scope.uses_eval || this.scope.uses_with))
|| (options.keep_fnames
|| !options.eval && this.scope.pinned()
|| options.keep_fnames
&& (this.orig[0] instanceof AST_SymbolLambda
|| this.orig[0] instanceof AST_SymbolDefun));
|| this.orig[0] instanceof AST_SymbolDefun);
},
mangle: function(options) {
var cache = options.cache && options.cache.props;
@@ -355,7 +355,7 @@ function next_mangled_name(scope, options, def) {
return name;
}
AST_Symbol.DEFMETHOD("unmangleable", function(options){
AST_Symbol.DEFMETHOD("unmangleable", function(options) {
var def = this.definition();
return !def || def.unmangleable(options);
});
@@ -363,16 +363,15 @@ AST_Symbol.DEFMETHOD("unmangleable", function(options){
// labels are always mangleable
AST_Label.DEFMETHOD("unmangleable", return_false);
AST_Symbol.DEFMETHOD("unreferenced", function(){
return this.definition().references.length == 0
&& !(this.scope.uses_eval || this.scope.uses_with);
AST_Symbol.DEFMETHOD("unreferenced", function() {
return !this.definition().references.length && !this.scope.pinned();
});
AST_Symbol.DEFMETHOD("definition", function(){
AST_Symbol.DEFMETHOD("definition", function() {
return this.thedef;
});
AST_Symbol.DEFMETHOD("global", function(){
AST_Symbol.DEFMETHOD("global", function() {
return this.definition().global;
});

View File

@@ -3,7 +3,7 @@
"description": "JavaScript parser, mangler/compressor and beautifier toolkit",
"author": "Mihai Bazon <mihai.bazon@gmail.com> (http://lisperator.net/)",
"license": "BSD-2-Clause",
"version": "3.3.23",
"version": "3.3.28",
"engines": {
"node": ">=0.8.0"
},
@@ -28,7 +28,6 @@
},
"devDependencies": {
"acorn": "~5.5.3",
"mocha": "~3.5.1",
"semver": "~5.5.0"
},
"scripts": {

View File

@@ -4056,6 +4056,36 @@ replace_all_var: {
expect_stdout: "PASS"
}
replace_all_var_scope: {
rename = true;
options = {
collapse_vars: true,
unused: true,
}
mangle = {}
input: {
var a = 100, b = 10;
(function(r, a) {
switch (~a) {
case (b += a):
case a++:
}
})(--b, a);
console.log(a, b);
}
expect: {
var a = 100, b = 10;
(function(c, o) {
switch (~a) {
case (b += a):
case o++:
}
})(--b, a);
console.log(a, b);
}
expect_stdout: "100 109"
}
cascade_statement: {
options = {
collapse_vars: true,
@@ -4901,6 +4931,27 @@ collapse_rhs_lhs_2: {
expect_stdout: "PASS"
}
collapse_rhs_loop: {
options = {
collapse_vars: true,
}
input: {
var s;
s = "<tpl>PASS</tpl>";
for (var m, r = /<tpl>(.*)<\/tpl>/; m = s.match(r);)
s = s.replace(m[0], m[1]);
console.log(s);
}
expect: {
var s;
s = "<tpl>PASS</tpl>";
for (var m, r = /<tpl>(.*)<\/tpl>/; m = s.match(r);)
s = s.replace(m[0], m[1]);
console.log(s);
}
expect_stdout: "PASS"
}
collapse_rhs_side_effects: {
options = {
collapse_vars: true,

View File

@@ -1814,3 +1814,115 @@ issue_2995: {
}
expect_stdout: "PASS"
}
issue_3146_1: {
options = {
collapse_vars: true,
unused: true,
}
input: {
(function(f) {
f("g()");
})(function(a) {
eval(a);
function g(b) {
if (!b) b = "PASS";
console.log(b);
}
});
}
expect: {
(function(f) {
f("g()");
})(function(a) {
eval(a);
function g(b) {
if (!b) b = "PASS";
console.log(b);
}
});
}
expect_stdout: "PASS"
}
issue_3146_2: {
options = {
reduce_vars: true,
unused: true,
}
input: {
(function(f) {
f("g()");
})(function(a) {
eval(a);
function g(b) {
if (!b) b = "PASS";
console.log(b);
}
});
}
expect: {
(function(f) {
f("g()");
})(function(a) {
eval(a);
function g(b) {
if (!b) b = "PASS";
console.log(b);
}
});
}
expect_stdout: "PASS"
}
issue_3146_3: {
options = {
collapse_vars: true,
unused: true,
}
input: {
var g = "PASS";
(function(f) {
var g = "FAIL";
f("console.log(g)", g[g]);
})(function(a) {
eval(a);
});
}
expect: {
var g = "PASS";
(function(f) {
var g = "FAIL";
f("console.log(g)", g[g]);
})(function(a) {
eval(a);
});
}
expect_stdout: "PASS"
}
issue_3146_4: {
options = {
reduce_vars: true,
unused: true,
}
input: {
var g = "PASS";
(function(f) {
var g = "FAIL";
f("console.log(g)", g[g]);
})(function(a) {
eval(a);
});
}
expect: {
var g = "PASS";
(function(f) {
var g = "FAIL";
f("console.log(g)", g[g]);
})(function(a) {
eval(a);
});
}
expect_stdout: "PASS"
}

View File

@@ -2303,3 +2303,19 @@ issue_3076: {
}
expect_stdout: "PASS"
}
issue_3125: {
options = {
inline: true,
unsafe: true,
}
input: {
console.log(function() {
return "PASS";
}.call());
}
expect: {
console.log("PASS");
}
expect_stdout: "PASS"
}

View File

@@ -1,11 +1,31 @@
remove_redundant_sequence_items: {
options = { side_effects: true };
remove_sequence: {
options = {
side_effects: true,
}
input: {
(0, 1, eval)();
(0, 1, logThis)();
(0, 1, _decorators.logThis)();
}
expect: {
eval();
logThis();
(0, _decorators.logThis)();
}
}
remove_redundant_sequence_items: {
options = {
side_effects: true,
}
input: {
"use strict";
(0, 1, eval)();
(0, 1, logThis)();
(0, 1, _decorators.logThis)();
}
expect: {
"use strict";
(0, eval)();
logThis();
(0, _decorators.logThis)();
@@ -13,13 +33,17 @@ remove_redundant_sequence_items: {
}
dont_remove_this_binding_sequence: {
options = { side_effects: true };
options = {
side_effects: true,
}
input: {
"use strict";
(0, eval)();
(0, logThis)();
(0, _decorators.logThis)();
}
expect: {
"use strict";
(0, eval)();
logThis();
(0, _decorators.logThis)();

View File

@@ -3,8 +3,9 @@ this_binding_conditionals: {
conditionals: true,
evaluate: true,
side_effects: true,
};
}
input: {
"use strict";
(1 && a)();
(0 || a)();
(0 || 1 && a)();
@@ -26,6 +27,7 @@ this_binding_conditionals: {
(1 ? eval : 0)();
}
expect: {
"use strict";
a();
a();
a();
@@ -53,13 +55,15 @@ this_binding_collapse_vars: {
collapse_vars: true,
toplevel: true,
unused: true,
};
}
input: {
"use strict";
var c = a; c();
var d = a.b; d();
var e = eval; e();
}
expect: {
"use strict";
a();
(0, a.b)();
(0, eval)();
@@ -69,31 +73,88 @@ this_binding_collapse_vars: {
this_binding_side_effects: {
options = {
side_effects : true
};
}
input: {
(function (foo) {
(function(foo) {
(0, foo)();
(0, foo.bar)();
(0, eval)('console.log(foo);');
(0, eval)("console.log(foo);");
}());
(function (foo) {
(function(foo) {
"use strict";
(0, foo)();
(0, foo.bar)();
(0, eval)("console.log(foo);");
}());
(function(foo) {
var eval = console;
(0, foo)();
(0, foo.bar)();
(0, eval)('console.log(foo);');
(0, eval)("console.log(foo);");
}());
}
expect: {
(function (foo) {
(function(foo) {
foo();
(0, foo.bar)();
(0, eval)('console.log(foo);');
eval("console.log(foo);");
}());
(function (foo) {
(function(foo) {
"use strict";
foo();
(0, foo.bar)();
(0, eval)("console.log(foo);");
}());
(function(foo) {
var eval = console;
foo();
(0, foo.bar)();
(0, eval)('console.log(foo);');
eval("console.log(foo);");
}());
}
}
}
this_binding_sequences: {
options = {
sequences: true,
side_effects: true,
}
input: {
console.log(typeof function() {
return eval("this");
}());
console.log(typeof function() {
"use strict";
return eval("this");
}());
console.log(typeof function() {
return (0, eval)("this");
}());
console.log(typeof function() {
"use strict";
return (0, eval)("this");
}());
}
expect: {
console.log(typeof function() {
return eval("this");
}()),
console.log(typeof function() {
"use strict";
return eval("this");
}()),
console.log(typeof function() {
return eval("this");
}()),
console.log(typeof function() {
"use strict";
return (0, eval)("this");
}());
}
expect_stdout: [
"object",
"undefined",
"object",
"object",
]
}

View File

@@ -1476,18 +1476,18 @@ defun_redefine: {
};
return g() + h();
}
console.log(f());
}
expect: {
function f() {
function g() {
return 1;
}
g = function() {
(function() {
return 3;
};
return g() + 2;
});
return 3 + 2;
}
console.log(f());
}
expect_stdout: "5"
}
func_inline: {
@@ -1527,23 +1527,37 @@ func_modified: {
}
input: {
function f(a) {
function a() { return 1; }
function b() { return 2; }
function c() { return 3; }
function a() {
return 1;
}
function b() {
return 2;
}
function c() {
return 3;
}
b.inject = [];
c = function() { return 4; };
c = function() {
return 4;
};
return a() + b() + c();
}
console.log(f());
}
expect: {
function f(a) {
function b() { return 2; }
function c() { return 3; }
function b() {
return 2;
}
b.inject = [];
c = function() { return 4; };
return 1 + 2 + c();
(function() {
return 4;
});
return 1 + 2 + 4;
}
console.log(f());
}
expect_stdout: "7"
}
defun_label: {
@@ -5054,9 +5068,7 @@ defun_var_1: {
console.log(typeof a, typeof b);
}
expect: {
var a = 42;
function a() {}
console.log(typeof a, "function");
console.log("number", "function");
}
expect_stdout: "number function"
}
@@ -5076,9 +5088,7 @@ defun_var_2: {
console.log(typeof a, typeof b);
}
expect: {
function a() {}
var a = 42;
console.log(typeof a, "function");
console.log("number", "function");
}
expect_stdout: "number function"
}
@@ -5710,3 +5720,686 @@ issue_3068_2: {
}
expect_stdout: true
}
issue_3110_1: {
options = {
conditionals: true,
evaluate: true,
inline: true,
passes: 3,
properties: true,
reduce_vars: true,
sequences: true,
side_effects: true,
unused: true,
}
input: {
(function() {
function foo() {
return isDev ? "foo" : "bar";
}
var isDev = true;
var obj = {
foo: foo
};
console.log(foo());
console.log(obj.foo());
})();
}
expect: {
console.log("foo"),
console.log("foo");
}
expect_stdout: [
"foo",
"foo",
]
}
issue_3110_2: {
options = {
conditionals: true,
evaluate: true,
inline: true,
passes: 4,
properties: true,
reduce_vars: true,
sequences: true,
side_effects: true,
unused: true,
}
input: {
(function() {
function foo() {
return isDev ? "foo" : "bar";
}
var isDev = true;
console.log(foo());
var obj = {
foo: foo
};
console.log(obj.foo());
})();
}
expect: {
console.log("foo"),
console.log("foo");
}
expect_stdout: [
"foo",
"foo",
]
}
issue_3110_3: {
options = {
conditionals: true,
evaluate: true,
inline: true,
properties: true,
reduce_vars: true,
sequences: true,
side_effects: true,
unused: true,
}
input: {
(function() {
function foo() {
return isDev ? "foo" : "bar";
}
console.log(foo());
var isDev = true;
var obj = {
foo: foo
};
console.log(obj.foo());
})();
}
expect: {
(function() {
function foo() {
return isDev ? "foo" : "bar";
}
console.log(foo());
var isDev = true;
var obj = {
foo: foo
};
console.log(obj.foo());
})();
}
expect_stdout: [
"bar",
"foo",
]
}
issue_3113_1: {
options = {
evaluate: true,
reduce_vars: true,
}
input: {
var c = 0;
(function() {
function f() {
while (g());
}
var a = f();
function g() {
a && a[c++];
}
g(a = 1);
})();
console.log(c);
}
expect: {
var c = 0;
(function() {
function f() {
while (g());
}
var a = f();
function g() {
a && a[c++];
}
g(a = 1);
})();
console.log(c);
}
expect_stdout: "1"
}
issue_3113_2: {
options = {
evaluate: true,
reduce_vars: true,
}
input: {
var c = 0;
(function() {
function f() {
while (g());
}
var a = f();
function g() {
a && a[c++];
}
a = 1;
g();
})();
console.log(c);
}
expect: {
var c = 0;
(function() {
function f() {
while (g());
}
var a = f();
function g() {
a && a[c++];
}
a = 1;
g();
})();
console.log(c);
}
expect_stdout: "1"
}
issue_3113_3: {
options = {
evaluate: true,
inline: true,
passes: 2,
pure_getters: "strict",
reduce_vars: true,
side_effects: true,
unused: true,
}
input: {
var c = 0;
(function() {
function f() {
while (g());
}
var a;
function g() {
a && a[c++];
}
g(a = 1);
})();
console.log(c);
}
expect: {
var c = 0;
c++;
console.log(c);
}
expect_stdout: "1"
}
issue_3113_4: {
options = {
evaluate: true,
reduce_vars: true,
toplevel: true,
}
input: {
var a = 0, b = 0;
function f() {
b += a;
}
f(f(), ++a);
console.log(a, b);
}
expect: {
var a = 0, b = 0;
function f() {
b += a;
}
f(f(), ++a);
console.log(a, b);
}
expect_stdout: "1 1"
}
issue_3113_5: {
options = {
evaluate: true,
reduce_vars: true,
toplevel: true,
}
input: {
function f() {
console.log(a);
}
function g() {
f();
}
while (g());
var a = 1;
f();
}
expect: {
function f() {
console.log(a);
}
function g() {
f();
}
while (g());
var a = 1;
f();
}
expect_stdout: [
"undefined",
"1",
]
}
conditional_nested_1: {
options = {
evaluate: true,
reduce_vars: true,
}
input: {
var a = 1, b = 0;
(function f(c) {
function g() {
c && (c.a = 0);
c && (c.a = 0);
c && (c[b++] *= 0);
}
g(a-- && f(g(c = 42)));
})();
console.log(b);
}
expect: {
var a = 1, b = 0;
(function f(c) {
function g() {
c && (c.a = 0);
c && (c.a = 0);
c && (c[b++] *= 0);
}
g(a-- && f(g(c = 42)));
})();
console.log(b);
}
expect_stdout: "2"
}
conditional_nested_2: {
options = {
evaluate: true,
reduce_vars: true,
}
input: {
var c = 0;
(function(a) {
function f() {
a && c++;
}
f(!c && f(), a = 1);
})();
console.log(c);
}
expect: {
var c = 0;
(function(a) {
function f() {
a && c++;
}
f(!c && f(), a = 1);
})();
console.log(c);
}
expect_stdout: "1"
}
conditional_nested_3: {
options = {
evaluate: true,
reduce_vars: true,
}
input: {
var n = 2, c = 0;
(function f(a) {
0 < n-- && g(a = 1);
function g() {
a && c++;
}
g();
0 < n-- && f();
})();
console.log(c);
}
expect: {
var n = 2, c = 0;
(function f(a) {
0 < n-- && g(a = 1);
function g() {
a && c++;
}
g();
0 < n-- && f();
})();
console.log(c);
}
expect_stdout: "2"
}
issue_2436: {
options = {
evaluate: true,
reduce_vars: true,
toplevel: true,
unsafe: true,
}
input: {
var c;
console.log(((c = {
a: 1,
b: 2
}).a = 3, {
x: c.a,
y: c.b
}));
}
expect: {
var c;
console.log(((c = {
a: 1,
b: 2
}).a = 3, {
x: c.a,
y: c.b
}));
}
expect_stdout: true
}
issue_2916: {
options = {
collapse_vars: true,
evaluate: true,
inline: true,
passes: 2,
reduce_vars: true,
side_effects: true,
unsafe: true,
unused: true,
}
input: {
var c = "FAIL";
(function(b) {
(function(d) {
d[0] = 1;
})(b);
+b && (c = "PASS");
})([]);
console.log(c);
}
expect: {
var c = "FAIL";
(function(b) {
b[0] = 1;
+b && (c = "PASS");
})([]);
console.log(c);
}
expect_stdout: "PASS"
}
issue_3125: {
options = {
evaluate: true,
reduce_vars: true,
toplevel: true,
unsafe: true,
}
input: {
var o;
console.log((function() {
this.p++;
}.call(o = {
p: 6
}), o.p));
}
expect: {
var o;
console.log((function() {
this.p++;
}.call(o = {
p: 6
}), o.p));
}
expect_stdout: "7"
}
issue_3140_1: {
options = {
reduce_vars: true,
unused: true,
}
input: {
(function() {
var a;
function f() {
}
f.g = function g() {
function h() {
console.log(a ? "PASS" : "FAIL");
}
a = true;
this();
a = false;
h.g = g;
return h;
};
return f;
})().g().g();
}
expect: {
(function() {
var a;
function f() {
}
f.g = function g() {
function h() {
console.log(a ? "PASS" : "FAIL");
}
a = true;
this();
a = false;
h.g = g;
return h;
};
return f;
})().g().g();
}
expect_stdout: "PASS"
}
issue_3140_2: {
options = {
reduce_vars: true,
unused: true,
}
input: {
(function() {
var a;
function f() {
}
f.g = function g() {
var self = this;
function h() {
console.log(a ? "PASS" : "FAIL");
}
a = true;
self();
a = false;
h.g = g;
return h;
};
return f;
})().g().g();
}
expect: {
(function() {
var a;
function f() {
}
f.g = function g() {
function h() {
console.log(a ? "PASS" : "FAIL");
}
a = true;
this();
a = false;
h.g = g;
return h;
};
return f;
})().g().g();
}
expect_stdout: "PASS"
}
issue_3140_3: {
options = {
reduce_vars: true,
unused: true,
}
input: {
(function() {
var a;
function f() {
}
f.g = function g() {
var self = this;
function h() {
console.log(a ? "PASS" : "FAIL");
}
a = true;
(function() {
return self;
})()();
a = false;
h.g = g;
return h;
};
return f;
})().g().g();
}
expect: {
(function() {
var a;
function f() {
}
f.g = function g() {
var self = this;
function h() {
console.log(a ? "PASS" : "FAIL");
}
a = true;
(function() {
return self;
})()();
a = false;
h.g = g;
return h;
};
return f;
})().g().g();
}
expect_stdout: "PASS"
}
issue_3140_4: {
options = {
reduce_vars: true,
unused: true,
}
input: {
(function() {
var a;
function f() {
}
f.g = function g() {
var o = {
p: this
};
function h() {
console.log(a ? "PASS" : "FAIL");
}
a = true;
o.p();
a = false;
h.g = g;
return h;
};
return f;
})().g().g();
}
expect: {
(function() {
var a;
function f() {
}
f.g = function g() {
var o = {
p: this
};
function h() {
console.log(a ? "PASS" : "FAIL");
}
a = true;
o.p();
a = false;
h.g = g;
return h;
};
return f;
})().g().g();
}
expect_stdout: "PASS"
}
issue_3140_5: {
options = {
evaluate: true,
reduce_vars: true,
}
input: {
var n = 1, c = 0;
(function(a) {
var b = function() {
this;
n-- && h();
}();
function h() {
b && c++;
}
h(b = 1);
})();
console.log(c);
}
expect: {
var n = 1, c = 0;
(function(a) {
var b = function() {
this;
n-- && h();
}();
function h() {
b && c++;
}
h(b = 1);
})();
console.log(c);
}
expect_stdout: "1"
}

37
test/compress/regexp.js Normal file
View File

@@ -0,0 +1,37 @@
regexp_simple: {
input: {
/rx/ig
}
expect_exact: "/rx/gi;"
}
regexp_slashes: {
input: {
/\\\/rx\/\\/ig
}
expect_exact: "/\\\\\\/rx\\/\\\\/gi;"
}
regexp_1: {
input: {
console.log(JSON.stringify("COMPASS? Overpass.".match(/([Sap]+)/ig)));
}
expect: {
console.log(JSON.stringify("COMPASS? Overpass.".match(/([Sap]+)/gi)));
}
expect_stdout: '["PASS","pass"]'
}
regexp_2: {
options = {
evaluate: true,
unsafe: true,
}
input: {
console.log(JSON.stringify("COMPASS? Overpass.".match(new RegExp("([Sap]+)", "ig"))));
}
expect: {
console.log(JSON.stringify("COMPASS? Overpass.".match(/([Sap]+)/gi)));
}
expect_stdout: '["PASS","pass"]'
}

View File

@@ -876,3 +876,59 @@ forin: {
}
expect_stdout: "PASS"
}
call: {
options = {
sequences: true,
}
input: {
var a = function() {
return this;
}();
function b() {
console.log("foo");
}
b.c = function() {
console.log(this === b ? "bar" : "baz");
};
(a, b)();
(a, b.c)();
(a, function() {
console.log(this === a);
})();
new (a, b)();
new (a, b.c)();
new (a, function() {
console.log(this === a);
})();
}
expect: {
var a = function() {
return this;
}();
function b() {
console.log("foo");
}
b.c = function() {
console.log(this === b ? "bar" : "baz");
},
a, b(),
(a, b.c)(),
a, function() {
console.log(this === a);
}(),
a, new b(),
a, new b.c(),
a, new function() {
console.log(this === a);
}();
}
expect_stdout: [
"foo",
"baz",
"true",
"foo",
"baz",
"false",
]
}

View File

@@ -14,6 +14,13 @@ issue_1929: {
function f(s) {
return s.split(/[\\/]/);
}
console.log(JSON.stringify(f("A/B\\C\\D/E\\F")));
}
expect_exact: "function f(s){return s.split(/[\\\\/]/)}"
expect: {
function f(s) {
return s.split(/[\\/]/);
}
console.log(JSON.stringify(f("A/B\\C\\D/E\\F")));
}
expect_stdout: '["A","B","C","D","E","F"]'
}

View File

@@ -90,17 +90,11 @@ typeof_defun_1: {
"function" == typeof h && h();
}
expect: {
function g() {
h = 42;
console.log("NOPE");
}
function h() {
console.log("YUP");
}
g = 42;
console.log("YES");
"function" == typeof g && g();
"function" == typeof h && h();
h();
}
expect_stdout: [
"YES",

View File

@@ -1,24 +1,109 @@
var fs = require("fs");
var Mocha = require("mocha");
var path = require("path");
// Instantiate a Mocha instance
var mocha = new Mocha({
timeout: 5000
});
var testDir = __dirname + "/mocha/";
var config = {
limit: 5000,
timeout: function(limit) {
this.limit = limit;
}
};
var tasks = [];
var titles = [];
describe = function(title, fn) {
config = Object.create(config);
titles.push(title);
fn.call(config);
titles.pop();
config = Object.getPrototypeOf(config);
};
it = function(title, fn) {
fn.limit = config.limit;
fn.titles = titles.slice();
fn.titles.push(title);
tasks.push(fn);
};
// Add each .js file to the Mocha instance
fs.readdirSync(testDir).filter(function(file) {
fs.readdirSync("test/mocha").filter(function(file) {
return /\.js$/.test(file);
}).forEach(function(file) {
mocha.addFile(path.join(testDir, file));
require("./mocha/" + file);
});
module.exports = function() {
mocha.run(function(failures) {
if (failures) process.on("exit", function() {
process.exit(failures);
function log_titles(log, current, marker) {
var indent = "";
var writing = false;
for (var i = 0; i < current.length; i++, indent += " ") {
if (titles[i] != current[i]) writing = true;
if (writing) log(indent + (i == current.length - 1 && marker || "") + current[i]);
}
titles = current;
}
function red(text) {
return "\u001B[31m" + text + "\u001B[39m";
}
function green(text) {
return "\u001B[32m" + text + "\u001B[39m";
}
var errors = [];
var total = tasks.length;
titles = [];
process.nextTick(function run() {
var task = tasks.shift();
if (task) try {
var elapsed = Date.now();
var timer;
var done = function() {
clearTimeout(timer);
done = function() {};
elapsed = Date.now() - elapsed;
if (elapsed > task.limit) {
throw new Error("Timed out: " + elapsed + "ms > " + task.limit + "ms");
}
log_titles(console.log, task.titles, green('\u221A '));
process.nextTick(run);
};
if (task.length) {
task.timeout = function(limit) {
clearTimeout(timer);
task.limit = limit;
timer = setTimeout(function() {
raise(new Error("Timed out: exceeds " + limit + "ms"));
}, limit);
};
task.timeout(task.limit);
task.call(task, done);
} else {
task.timeout = config.timeout;
task.call(task);
done();
}
} catch (err) {
raise(err);
} else if (errors.length) {
console.error();
console.log(red(errors.length + " test(s) failed!"));
titles = [];
errors.forEach(function(titles, index) {
console.error();
log_titles(console.error, titles, (index + 1) + ") ");
var lines = titles.error.stack.split('\n');
console.error(red(lines[0]));
console.error(lines.slice(1).join("\n"));
});
});
};
process.exit(1);
} else {
console.log();
console.log(green(total + " test(s) passed."));
}
function raise(err) {
clearTimeout(timer);
done = function() {};
task.titles.error = err;
errors.push(task.titles);
log_titles(console.log, task.titles, red('\u00D7 '));
process.nextTick(run);
}
});

View File

@@ -57,24 +57,23 @@ describe("sourcemaps", function() {
includeSources: true
}
});
if (result.error) throw result.error;
var map = JSON.parse(result.map);
assert.equal(map.file, 'simple.min.js');
assert.equal(map.file, "simple.min.js");
assert.equal(map.sourcesContent.length, 1);
assert.equal(map.sourcesContent[0],
'let foo = x => "foo " + x;\nconsole.log(foo("bar"));');
assert.equal(map.sourcesContent[0], 'let foo = x => "foo " + x;\nconsole.log(foo("bar"));');
});
it("Should process inline source map", function() {
var code = Uglify.minify(read("./test/input/issue-520/input.js"), {
var result = Uglify.minify(read("./test/input/issue-520/input.js"), {
compress: { toplevel: true },
sourceMap: {
content: "inline",
includeSources: true,
url: "inline"
}
}).code + "\n";
assert.strictEqual(code, readFileSync("test/input/issue-520/output.js", "utf8"));
});
if (result.error) throw result.error;
assert.strictEqual(result.code + "\n", readFileSync("test/input/issue-520/output.js", "utf8"));
});
it("Should warn for missing inline source map", function() {
var warn_function = Uglify.AST_Node.warn_function;
@@ -149,22 +148,24 @@ describe("sourcemaps", function() {
});
describe("sourceMapInline", function() {
it("should append source map to output js when sourceMapInline is enabled", function() {
it("Should append source map to output js when sourceMapInline is enabled", function() {
var result = Uglify.minify('var a = function(foo) { return foo; };', {
sourceMap: {
url: "inline"
}
});
if (result.error) throw result.error;
var code = result.code;
assert.strictEqual(code, "var a=function(n){return n};\n" +
"//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIjAiXSwibmFtZXMiOlsiYSIsImZvbyJdLCJtYXBwaW5ncyI6IkFBQUEsSUFBSUEsRUFBSSxTQUFTQyxHQUFPLE9BQU9BIn0=");
});
it("should not append source map to output js when sourceMapInline is not enabled", function() {
it("Should not append source map to output js when sourceMapInline is not enabled", function() {
var result = Uglify.minify('var a = function(foo) { return foo; };');
if (result.error) throw result.error;
var code = result.code;
assert.strictEqual(code, "var a=function(n){return n};");
});
it("should work with max_line_len", function() {
it("Should work with max_line_len", function() {
var result = Uglify.minify(read("./test/input/issue-505/input.js"), {
output: {
max_line_len: 20
@@ -173,8 +174,39 @@ describe("sourcemaps", function() {
url: "inline"
}
});
assert.strictEqual(result.error, undefined);
if (result.error) throw result.error;
assert.strictEqual(result.code, read("./test/input/issue-505/output.js"));
});
it("Should work with unicode characters", function() {
var code = [
"var tëst = '→unicøde←';",
"alert(tëst);",
].join("\n");
var result = Uglify.minify(code, {
sourceMap: {
includeSources: true,
url: "inline",
}
});
if (result.error) throw result.error;
var map = JSON.parse(result.map);
assert.strictEqual(map.sourcesContent.length, 1);
assert.strictEqual(map.sourcesContent[0], code);
var encoded = result.code.slice(result.code.lastIndexOf(",") + 1);
map = JSON.parse(new Buffer(encoded, "base64").toString());
assert.strictEqual(map.sourcesContent.length, 1);
assert.strictEqual(map.sourcesContent[0], code);
result = Uglify.minify(result.code, {
sourceMap: {
content: "inline",
includeSources: true,
}
});
if (result.error) throw result.error;
map = JSON.parse(result.map);
assert.strictEqual(map.names.length, 2);
assert.strictEqual(map.names[0], "tëst");
assert.strictEqual(map.names[1], "alert");
});
});
});

View File

@@ -18,9 +18,8 @@ if (failures) {
console.error("!!! " + Object.keys(failed_files).join(", "));
process.exit(1);
}
var mocha_tests = require("./mocha.js");
mocha_tests();
console.log();
require("./mocha.js");
/* -----[ utils ]----- */

View File

@@ -18,6 +18,13 @@
{
"toplevel": true
},
{
"compress": {
"passes": 1e6,
"unsafe": true
},
"toplevel": true
},
{
"compress": {
"keep_fargs": false,