Compare commits

...

19 Commits

Author SHA1 Message Date
Alex Lam S.L
bce7ee5f6a v3.4.0 2018-06-02 05:57:10 +00:00
Jiavan
b39043f3ab re-introduce enclose (#3163)
fixes #2443
2018-06-01 16:47:11 +08:00
Alex Lam S.L
caf96acb08 handle asynchronous test failures (#3164) 2018-05-31 20:21:39 +08:00
Alex Lam S.L
c76749084b update JetStream URL (#3165) 2018-05-31 16:23:49 +08:00
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
22 changed files with 883 additions and 107 deletions

View File

@@ -104,6 +104,8 @@ a double dash to prevent input files being used as option arguments:
sequences.
--config-file <file> Read `minify()` options from JSON file.
-d, --define <expr>[=value] Global definitions.
-e, --enclose [arg[:value]] Embed everything in a big function, with configurable
argument(s) & value(s).
--ie8 Support non-standard Internet Explorer 8.
Equivalent to setting `ie8: true` in `minify()`
for `compress`, `mangle` and `output` options.
@@ -118,7 +120,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 +152,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

@@ -40,6 +40,7 @@ program.option("-o, --output <file>", "Output file (default STDOUT).");
program.option("--comments [filter]", "Preserve copyright comments in the output.");
program.option("--config-file <file>", "Read minify() options from JSON file.");
program.option("-d, --define <expr>[=value]", "Global definitions.", parse_js("define"));
program.option("-e, --enclose [arg[,...][:value[,...]]]", "Embed everything in a big function, with configurable argument(s) & value(s).");
program.option("--ie8", "Support non-standard Internet Explorer 8.");
program.option("--keep-fnames", "Do not mangle/drop function names. Useful for code relying on Function.prototype.name.");
program.option("--name-cache <file>", "File to hold mangled name mappings.");
@@ -47,7 +48,7 @@ program.option("--rename", "Force symbol expansion.");
program.option("--no-rename", "Disable symbol expansion.");
program.option("--self", "Build UglifyJS as a library (implies --wrap UglifyJS)");
program.option("--source-map [options]", "Enable source map/specify source map options.", parse_js());
program.option("--timings", "Display operations run time on STDERR.")
program.option("--timings", "Display operations run time on STDERR.");
program.option("--toplevel", "Compress and/or mangle variables in toplevel scope.");
program.option("--verbose", "Print diagnostic messages.");
program.option("--warn", "Print warning messages.");
@@ -61,6 +62,7 @@ if (!program.output && program.sourceMap && program.sourceMap.url != "inline") {
}
[
"compress",
"enclose",
"ie8",
"mangle",
"sourceMap",

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);
@@ -332,6 +335,23 @@ var AST_Toplevel = DEFNODE("Toplevel", "globals", {
}
}));
return wrapped_tl;
},
wrap_enclose: function(args_values) {
if (typeof args_values != "string") args_values = "";
var index = args_values.indexOf(":");
if (index < 0) index = args_values.length;
var body = this.body;
return parse([
"(function(",
args_values.slice(0, index),
'){"$ORIG"})(',
args_values.slice(index + 1),
")"
].join("")).transform(new TreeTransformer(function(node) {
if (node instanceof AST_Directive && node.value == "$ORIG") {
return MAP.splice(body);
}
}));
}
}, AST_Scope);

View File

@@ -348,20 +348,20 @@ merge(Compressor.prototype, {
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.init instanceof AST_Defun && !all(def.references, function(ref) {
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] = undefined;
tw.defun_ids[def.id] = false;
}
def.recursive_refs = 0;
def.references = [];
@@ -380,29 +380,44 @@ 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;
if (marker !== tw.safe_ids) {
tw.defun_ids[def.id] = undefined;
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;
}
return def.fixed;
}
if (!tw.in_loop) {
tw.defun_ids[def.id] = tw.safe_ids;
return def.fixed;
} else if (tw.defun_ids[def.id] !== false) {
tw.defun_ids[def.id] = undefined;
} 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_ids[def.id] === undefined) {
if (def.init instanceof AST_Defun && !tw.defun_visited[def.id]) {
tw.defun_ids[def.id] = tw.safe_ids;
def.init.walk(tw);
}
@@ -455,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;
}
@@ -550,6 +564,7 @@ merge(Compressor.prototype, {
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();
@@ -587,8 +602,9 @@ merge(Compressor.prototype, {
});
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_ids[id] = false;
tw.defun_visited[id] = true;
this.inlined = false;
push(tw);
reset_variables(tw, compressor, this);
@@ -707,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;
@@ -820,6 +836,7 @@ merge(Compressor.prototype, {
});
// 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);
@@ -919,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.TYPE == "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;
@@ -1041,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;
@@ -1098,7 +1121,7 @@ merge(Compressor.prototype, {
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: "=",
@@ -1226,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);
}
@@ -1235,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);
}
@@ -1275,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");
@@ -1292,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;
@@ -1303,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,
@@ -3236,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) {
@@ -3346,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);
})));
}
@@ -4477,7 +4504,7 @@ merge(Compressor.prototype, {
var exp = this.expression;
if (!(exp instanceof AST_Sequence)) return this;
var tail = exp.tail_node();
if (tail instanceof AST_PropAccess && !(this instanceof AST_New)) return this;
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;
@@ -4499,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;
@@ -4779,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
@@ -5028,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;
}
@@ -5347,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);
@@ -5386,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) {
@@ -6262,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

@@ -55,6 +55,7 @@ function minify(files, options) {
try {
options = defaults(options, {
compress: {},
enclose: false,
ie8: false,
keep_fnames: false,
mangle: {},
@@ -157,6 +158,9 @@ function minify(files, options) {
if (options.wrap) {
toplevel = toplevel.wrap_commonjs(options.wrap);
}
if (options.enclose) {
toplevel = toplevel.wrap_enclose(options.enclose);
}
if (timings) timings.rename = Date.now();
if (options.rename) {
toplevel.figure_out_scope(options.mangle);

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.24",
"version": "3.4.0",
"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

@@ -4931,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

@@ -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

@@ -6059,6 +6059,38 @@ conditional_nested_2: {
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,
@@ -6146,3 +6178,228 @@ issue_3125: {
}
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

@@ -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

@@ -1,4 +1,5 @@
var fs = require("fs");
var parse = require("url").parse;
var path = require("path");
try {
@@ -19,7 +20,9 @@ module.exports = function(url, callback) {
var result = read(url);
result.on("error", function(e) {
if (e.code != "ENOENT") return callback(e);
require(url.slice(0, url.indexOf(":"))).get(url, function(res) {
var options = parse(url);
options.rejectUnauthorized = false;
require(options.protocol.slice(0, -1)).get(options, function(res) {
if (res.statusCode !== 200) return callback(res);
res.pipe(fs.createWriteStream(local(url)).on("close", function() {
callback(null, read(url));

View File

@@ -0,0 +1,4 @@
function enclose() {
console.log("test enclose");
}
enclose();

View File

@@ -3,7 +3,7 @@
"use strict";
var site = "http://browserbench.org/JetStream";
var site = "https://browserbench.org/JetStream";
if (typeof phantom == "undefined") {
require("../tools/exit");
var args = process.argv.slice(2);

View File

@@ -1,24 +1,114 @@
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() {
reset();
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);
process.on("uncaughtException", raise);
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) {
reset();
task.titles.error = err;
errors.push(task.titles);
log_titles(console.log, task.titles, red('\u00D7 '));
process.nextTick(run);
}
function reset() {
clearTimeout(timer);
done = function() {};
process.removeListener("uncaughtException", raise);
}
});

View File

@@ -6,7 +6,7 @@ function read(path) {
return fs.readFileSync(path, "utf8");
}
describe("bin/uglifyjs", function () {
describe("bin/uglifyjs", function() {
var uglifyjscmd = '"' + process.argv[0] + '" bin/uglifyjs';
it("should produce a functional build when using --self", function (done) {
this.timeout(30000);
@@ -744,4 +744,36 @@ describe("bin/uglifyjs", function () {
done();
});
});
it("Should work with --enclose", function(done) {
var command = uglifyjscmd + " test/input/enclose/input.js --enclose";
exec(command, function(err, stdout, stderr) {
if (err) throw err;
assert.strictEqual(stdout, '(function(){function enclose(){console.log("test enclose")}enclose()})();\n');
done();
});
});
it("Should work with --enclose arg", function(done) {
var command = uglifyjscmd + " test/input/enclose/input.js --enclose undefined";
exec(command, function(err, stdout, stderr) {
if (err) throw err;
assert.strictEqual(stdout, '(function(undefined){function enclose(){console.log("test enclose")}enclose()})();\n');
done();
});
});
it("Should work with --enclose arg:value", function(done) {
var command = uglifyjscmd + " test/input/enclose/input.js --enclose window,undefined:window";
exec(command, function(err, stdout, stderr) {
if (err) throw err;
assert.strictEqual(stdout, '(function(window,undefined){function enclose(){console.log("test enclose")}enclose()})(window);\n');
done();
});
});
it("Should work with --enclose & --wrap", function(done) {
var command = uglifyjscmd + " test/input/enclose/input.js --enclose window,undefined:window --wrap exports";
exec(command, function(err, stdout, stderr) {
if (err) throw err;
assert.strictEqual(stdout, '(function(window,undefined){(function(exports){function enclose(){console.log("test enclose")}enclose()})(typeof exports=="undefined"?exports={}:exports)})(window);\n');
done();
});
});
});

View File

@@ -303,4 +303,45 @@ describe("minify", function() {
}
});
});
describe("enclose", function() {
var code = read("test/input/enclose/input.js");
it("Should work with true", function() {
var result = Uglify.minify(code, {
compress: false,
enclose: true,
mangle: false,
});
if (result.error) throw result.error;
assert.strictEqual(result.code, '(function(){function enclose(){console.log("test enclose")}enclose()})();');
});
it("Should work with arg", function() {
var result = Uglify.minify(code, {
compress: false,
enclose: 'undefined',
mangle: false,
});
if (result.error) throw result.error;
assert.strictEqual(result.code, '(function(undefined){function enclose(){console.log("test enclose")}enclose()})();');
});
it("Should work with arg:value", function() {
var result = Uglify.minify(code, {
compress: false,
enclose: 'window,undefined:window',
mangle: false,
});
if (result.error) throw result.error;
assert.strictEqual(result.code, '(function(window,undefined){function enclose(){console.log("test enclose")}enclose()})(window);');
});
it("Should work alongside wrap", function() {
var result = Uglify.minify(code, {
compress: false,
enclose: 'window,undefined:window',
mangle: false,
wrap: 'exports',
});
if (result.error) throw result.error;
assert.strictEqual(result.code, '(function(window,undefined){(function(exports){function enclose(){console.log("test enclose")}enclose()})(typeof exports=="undefined"?exports={}:exports)})(window);');
});
});
});

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 ]----- */