Compare commits

...

73 Commits

Author SHA1 Message Date
Alex Lam S.L
d105ab9722 v3.8.1 2020-03-28 01:04:40 +08:00
Alex Lam S.L
b39228892d fix line accounting in multi-line strings (#3752)
fixes #3748
2020-03-21 07:17:41 +08:00
Alex Lam S.L
ff72eaa3c3 improve --reduce-test (#3742)
- ignore difference in error messages
- improve readability on trailing whitespace differences
- improve performance & quality via `console.log()` insertions
2020-03-21 05:50:41 +08:00
Alex Lam S.L
0a1c9b34ce fix corner case in evaluate & ie8 (#3751)
fixes #3750
2020-03-21 00:55:24 +08:00
Alex Lam S.L
03e968be62 improve suspicious option detection (#3749) 2020-03-13 04:03:47 +08:00
Alex Lam S.L
421bb7083a fix corner case in unused (#3747)
fixes #3746
2020-03-06 18:27:42 +00:00
Alex Lam S.L
bdc8ef2218 fix corner case in collapse_vars (#3745)
fixes #3744
2020-03-06 18:27:06 +00:00
Alex Lam S.L
bca52fcba2 speed up CI (#3741) 2020-03-02 22:07:30 +08:00
Alex Lam S.L
d6d31cbb5a improve AST fuzzing (#3740) 2020-03-02 19:38:30 +08:00
Alex Lam S.L
a051846d22 fix corner case in evaluate (#3739)
fixes #3738
2020-03-01 20:34:31 +00:00
Alex Lam S.L
3485472866 avoid reducing setter argument (#3737) 2020-03-01 05:04:21 +00:00
Alex Lam S.L
c8d60d6983 detect toplevel option properly (#3735)
fixes #3730
2020-02-29 17:33:48 +00:00
Alex Lam S.L
6092bf23de fix corner case in evaluate (#3729) 2020-02-19 00:41:10 +00:00
Alex Lam S.L
7052ce5aef fix corner case in evaluate (#3728)
- augment `ufuzz` for further `RegExp` testing
2020-02-18 19:35:37 +00:00
Alex Lam S.L
d13b71297e v3.8.0 2020-02-18 20:32:37 +08:00
Alex Lam S.L
457f958af3 improve --reduce-test (#3727)
- print out Node.js and OS information
2020-02-17 20:56:22 +00:00
Alex Lam S.L
53517db3e4 speed up --reduce-test (#3726)
- avoid pathological test case branches via adaptive time-out
- use initial test case elapsed time to adjust maximum time-out
- index output cache using hash instead of raw source
2020-02-17 15:35:07 +00:00
Alex Lam S.L
c13caf4876 speed up --reduce-test via result caching (#3725) 2020-02-15 22:43:34 +00:00
kzc
fbfa6178a6 improve --reduce-test (#3722)
- hoist body of functions and IIFEs
- simplify var declarations
2020-02-15 20:22:33 +00:00
Alex Lam S.L
5315dd95b0 minor cleanup (#3723) 2020-02-15 17:55:26 +00:00
Marco Gonzalez
31a7bf2a22 Updated "Output options" > "comments" in README.md (#3717)
Expanded the current documentation to include:

- What the value of `"some"` means based on `lib/output.js`.
- Information about the `Function` overload parameters and expected output.
2020-02-15 15:10:58 +00:00
Alex Lam S.L
f0a29902ac enhance properties (#3721) 2020-02-15 13:04:44 +00:00
Alex Lam S.L
0d820e4c0a workaround RegExp formatting bugs (#3720) 2020-02-15 05:26:48 +00:00
Alex Lam S.L
f01f580d6c improve --reduce-test (#3719)
- cover missing cases when eliminating unreferenced labels
- format multi-line outputs correctly
2020-02-14 02:47:20 +00:00
Alex Lam S.L
c01ff76288 improve code reuse (#3718) 2020-02-13 05:16:10 +00:00
Alex Lam S.L
83a42716c3 fix corner case in unused (#3716) 2020-02-12 23:46:16 +00:00
Alex Lam S.L
2557148bba increase mocha --reduce-test timeout (#3715) 2020-02-12 02:25:04 +00:00
Alex Lam S.L
dd22eda888 enhance evaluate (#3714) 2020-02-12 01:01:17 +00:00
Alex Lam S.L
f4c77886e7 add test for --reduce-test (#3712) 2020-02-09 23:21:46 +00:00
Alex Lam S.L
df547ffd97 improve test reduction (#3711)
- scan `AST_SymbolFunarg`
- scan `console.log(...)`
2020-02-09 20:42:36 +00:00
Alex Lam S.L
70551febc8 improve test/reduce (#3710)
- suppress several instances of malformed AST generation
- improve resilience & reporting against malformed ASTs
2020-02-09 08:07:55 +00:00
Alex Lam S.L
44499a6643 fix corner cases in test/reduce (#3709) 2020-02-07 02:41:07 +00:00
Alex Lam S.L
470a7d4df1 improve reduction of AST_BlockStatement (#3708) 2020-02-06 21:20:05 +00:00
Alex Lam S.L
551420132c export missing API for AST manipulation (#3707) 2020-02-06 18:46:25 +00:00
kzc
b0040ba654 implement CLI --reduce-test and reduce tests in ufuzz (#3705) 2020-02-06 02:50:59 +00:00
Alex Lam S.L
c93ca6ee53 fix corner case in ie8 & reduce_vars (#3706)
fixes #3703
2020-02-05 20:03:22 +00:00
Alex Lam S.L
df506439b1 fix corner case in sequences (#3704)
fixes #3703
2020-02-04 04:57:32 +00:00
Alex Lam S.L
36b2d35bf3 v3.7.7 2020-02-02 00:24:50 +00:00
Alex Lam S.L
79c60032a5 fix corner case in collapse_vars (#3701)
fixes #3700
2020-01-30 09:04:44 +08:00
Alex Lam S.L
a3754068dd fix corner case in collapse_vars (#3699)
fixes #3698
2020-01-30 00:08:53 +08:00
Alex Lam S.L
2ba5f391e0 enhance collapse_vars (#3697) 2020-01-29 08:52:20 +08:00
Alex Lam S.L
87119e44a0 fix corner case in sign propagation (#3696)
- migrate de-facto functionality to `evaluate`

fixes #3695
2020-01-28 22:44:18 +08:00
Alex Lam S.L
b499e03f82 enhance conditionals (#3694) 2020-01-28 12:33:21 +08:00
Alex Lam S.L
a478f275e4 enhance sequences (#3693) 2020-01-28 09:58:01 +08:00
Alex Lam S.L
e9e76dcf04 fix corner case in string concatenations (#3692)
- migrate de-facto compression to `conditionals` & `strings`

fixes #3689
2020-01-28 07:33:11 +08:00
Alex Lam S.L
0dcedad2d5 fix corner case in booleans (#3691)
fixes #3690
2020-01-28 02:04:44 +08:00
Alex Lam S.L
36a430cd1e v3.7.6 2020-01-19 11:02:58 +00:00
Alex Lam S.L
41a6eb892a fix corner case in evaluate (#3685)
fixes #3684
2020-01-16 01:51:37 +08:00
Alex Lam S.L
91d87ae663 fix corner case in unsafe_math (#3683)
fixes #3682
2020-01-15 04:05:58 +08:00
Alex Lam S.L
5beb7e4797 v3.7.5 2020-01-12 11:12:11 +08:00
Alex Lam S.L
46caaa82ba enhance collapse_vars (#3680)
closes #3679
2020-01-10 04:28:43 +08:00
Alex Lam S.L
5d258259a4 introduce --output-opts CLI option (#3678)
closes #3675
2020-01-08 20:44:03 +08:00
Alex Lam S.L
14c35739dd fix corner case in unsafe_math (#3677)
fixes #3676
2020-01-08 10:28:10 +08:00
Alex Lam S.L
f5ceff6e4b fix corner case in unused (#3674)
fixes #3673
2020-01-07 20:06:25 +08:00
Alex Lam S.L
4d6771b9b1 fix corner case in collapse_vars (#3672)
fixes #3671
2020-01-07 19:34:16 +08:00
Alex Lam S.L
d17191111a v3.7.4 2020-01-07 07:59:54 +08:00
Alex Lam S.L
0ff607cb80 improve ufuzz false positive detection (#3670) 2020-01-06 11:26:15 +08:00
Alex Lam S.L
1988495d71 fix corner case in conditionals (#3669)
fixes #3668
2020-01-04 09:24:28 +08:00
Alex Lam S.L
fdc10086da fix corner case in reduce_vars (#3667)
fixes #3666
2020-01-03 19:28:47 +08:00
Alex Lam S.L
746f5f6c62 fix corner case in unused (#3665)
fixes #3664
2020-01-01 20:24:30 +08:00
Alex Lam S.L
d83d3d741a enhance unused (#3662) 2019-12-31 23:39:24 +08:00
Alex Lam S.L
99ac73a635 enhance booleans (#3661) 2019-12-31 13:10:05 +08:00
Alex Lam S.L
a2e4c2fd97 enhance evaluate (#3660) 2019-12-31 11:51:21 +08:00
Alex Lam S.L
94785e8e14 fix corner case in booleans (#3659)
fixes #3658
2019-12-31 09:57:35 +08:00
Alex Lam S.L
4dbdac9c31 enhance booleans (#3657) 2019-12-30 22:41:11 +08:00
Alex Lam S.L
78c8efd851 fix corner case in evaluate (#3656)
fixes #3655
2019-12-29 21:16:53 +08:00
Alex Lam S.L
af310ba2d0 fix corner case in evaluate (#3654)
fixes #3653
2019-12-29 02:50:57 +00:00
Alex Lam S.L
2f3930d1b9 fix corner case in collapse_vars (#3652)
fixes #3651
2019-12-29 00:57:59 +00:00
Alex Lam S.L
d1a78920d9 workaround firefox asm.js quirks (#3650)
fixes #3636
2019-12-28 23:14:53 +00:00
Alex Lam S.L
d9cd3d33c8 enhance evaluate (#3649) 2019-12-28 20:26:15 +00:00
Alex Lam S.L
22b47cdd63 improve unicode handling (#3648) 2019-12-28 18:06:51 +00:00
Alex Lam S.L
4cf612dc9f increase mocha default timeout (#3647)
closes #3640
2019-12-28 02:32:22 +00:00
Alex Lam S.L
a19d31dd33 fix corner case in unsafe (#3646) 2019-12-27 14:24:54 +00:00
57 changed files with 4105 additions and 862 deletions

View File

@@ -4,10 +4,10 @@ jobs:
test:
strategy:
matrix:
node: [ "0.10", "0.12", 4, 6, 8, 10, latest ]
os: [ ubuntu-latest, windows-latest ]
node: [ "0.10", 0.12, 4, 6, 8, 10, latest ]
script: [ compress, mocha, release/benchmark, release/jetstream ]
name: ${{ matrix.os }} ${{ matrix.node }} ${{ matrix.script }}
name: ${{ matrix.node }} ${{ matrix.os }} ${{ matrix.script }}
runs-on: ${{ matrix.os }}
env:
NODE: ${{ matrix.node }}

View File

@@ -87,6 +87,7 @@ a double dash to prevent input files being used as option arguments:
`wrap_iife` Wrap IIFEs in parenthesis. Note: you may
want to disable `negate_iife` under
compressor options.
-O, --output-opts [options] Specify output options (`beautify` disabled by default).
-o, --output <file> Output file path (default STDOUT). Specify `ast` or
`spidermonkey` to write UglifyJS or SpiderMonkey AST
as JSON to STDOUT respectively.
@@ -735,6 +736,8 @@ If you're using the `X-SourceMap` header instead, you can just omit `sourceMap.u
annotation `/*@__PURE__*/` or `/*#__PURE__*/` immediately precedes the call. For
example: `/*@__PURE__*/foo();`
- `strings` (default: `true`) -- compact string concatenations.
- `switches` (default: `true`) -- de-duplicate and remove unreachable `switch` branches
- `toplevel` (default: `false`) -- drop unreferenced functions (`"funcs"`) and/or
@@ -845,8 +848,14 @@ can pass additional arguments that control the code output:
statement.
- `comments` (default `false`) -- pass `true` or `"all"` to preserve all
comments, `"some"` to preserve some comments, a regular expression string
(e.g. `/^!/`) or a function.
comments, `"some"` to preserve multi-line comments that contain `@cc_on`,
`@license`, or `@preserve` (case-insensitive), a regular expression string
(e.g. `/^!/`), or a function which returns `boolean`, e.g.
```js
function(node, comment) {
return comment.value.indexOf("@type " + node.TYPE) >= 0;
}
```
- `indent_level` (default `4`)

View File

@@ -36,6 +36,7 @@ program.option("-c, --compress [options]", "Enable compressor/specify compressor
program.option("-m, --mangle [options]", "Mangle names/specify mangler options.", parse_js());
program.option("--mangle-props [options]", "Mangle properties/specify mangler options.", parse_js());
program.option("-b, --beautify [options]", "Beautify output/specify output options.", parse_js());
program.option("-O, --output-opts [options]", "Output options (beautify disabled).", parse_js());
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.");
@@ -53,13 +54,14 @@ program.option("--toplevel", "Compress and/or mangle variables in toplevel scope
program.option("--verbose", "Print diagnostic messages.");
program.option("--warn", "Print warning messages.");
program.option("--wrap <name>", "Embed everything as a function with “exports” corresponding to “name” globally.");
program.option("--reduce-test", "Reduce a standalone `console.log` based test case.");
program.arguments("[files...]").parseArgv(process.argv);
if (program.configFile) {
options = JSON.parse(read_file(program.configFile));
if (options.mangle && options.mangle.properties && options.mangle.properties.regex) {
options.mangle.properties.regex = UglifyJS.parse(options.mangle.properties.regex, {
expression: true
}).getValue();
}).value;
}
}
if (!program.output && program.sourceMap && program.sourceMap.url != "inline") {
@@ -93,6 +95,10 @@ if (program.beautify) {
options.output.beautify = true;
}
}
if (program.outputOpts) {
if (program.beautify) fatal("--beautify cannot be used with --output-opts");
options.output = typeof program.outputOpts == "object" ? program.outputOpts : {};
}
if (program.comments) {
if (typeof options.output != "object") options.output = {};
options.output.comments = typeof program.comments == "string" ? program.comments : "some";
@@ -210,7 +216,15 @@ function run() {
} catch (ex) {
fatal(ex);
}
var result = UglifyJS.minify(files, options);
if (program.reduceTest) {
// load on demand - assumes dev tree checked out
var reduce_test = require("../test/reduce");
var testcase = files[0] || files[Object.keys(files)[0]];
var result = reduce_test(testcase, options, {verbose: true});
}
else {
var result = UglifyJS.minify(files, options);
}
if (result.error) {
var ex = result.error;
if (ex.name == "SyntaxError") {
@@ -370,7 +384,7 @@ function parse_js(flag) {
if (!(node instanceof UglifyJS.AST_Sequence)) throw node;
function to_string(value) {
return value instanceof UglifyJS.AST_Constant ? value.getValue() : value.print_to_string({
return value instanceof UglifyJS.AST_Constant ? value.value : value.print_to_string({
quote_keys: true
});
}

View File

@@ -169,10 +169,7 @@ var AST_SimpleStatement = DEFNODE("SimpleStatement", "body", {
}, AST_Statement);
function walk_body(node, visitor) {
var body = node.body;
if (body instanceof AST_Statement) {
body._walk(visitor);
} else body.forEach(function(node) {
node.body.forEach(function(node) {
node._walk(visitor);
});
}
@@ -351,7 +348,7 @@ var AST_Toplevel = DEFNODE("Toplevel", "globals", {
filename: "wrap=" + JSON.stringify(name)
}).transform(new TreeTransformer(function(node) {
if (node instanceof AST_Directive && node.value == "$ORIG") {
return MAP.splice(body);
return List.splice(body);
}
}));
},
@@ -370,7 +367,7 @@ var AST_Toplevel = DEFNODE("Toplevel", "globals", {
filename: "enclose=" + JSON.stringify(args_values)
}).transform(new TreeTransformer(function(node) {
if (node instanceof AST_Directive && node.value == "$ORIG") {
return MAP.splice(body);
return List.splice(body);
}
}));
}
@@ -618,7 +615,7 @@ var AST_PropAccess = DEFNODE("PropAccess", "expression property", {
getProperty: function() {
var p = this.property;
if (p instanceof AST_Constant) {
return p.getValue();
return p.value;
}
if (p instanceof AST_UnaryPrefix
&& p.operator == "void"
@@ -824,9 +821,6 @@ var AST_This = DEFNODE("This", null, {
var AST_Constant = DEFNODE("Constant", null, {
$documentation: "Base class for all constants",
getValue: function() {
return this.value;
}
});
var AST_String = DEFNODE("String", "value quote", {
@@ -967,11 +961,13 @@ TreeWalker.prototype = {
in_boolean_context: function() {
var self = this.self();
for (var i = 0, p; p = this.parent(i); i++) {
if (p instanceof AST_SimpleStatement
|| p instanceof AST_Conditional && p.condition === self
if (p instanceof AST_Conditional && p.condition === self
|| p instanceof AST_DWLoop && p.condition === self
|| p instanceof AST_For && p.condition === self
|| p instanceof AST_If && p.condition === self
|| p instanceof AST_Return && p.in_bool
|| p instanceof AST_Sequence && p.tail_node() !== self
|| p instanceof AST_SimpleStatement
|| p instanceof AST_UnaryPrefix && p.operator == "!" && p.expression === self) {
return true;
}

File diff suppressed because it is too large Load Diff

View File

@@ -119,15 +119,20 @@ function OutputStream(options) {
});
} : function(str) {
var s = "";
for (var i = 0; i < str.length; i++) {
if (is_surrogate_pair_head(str[i]) && !is_surrogate_pair_tail(str[i + 1])
|| is_surrogate_pair_tail(str[i]) && !is_surrogate_pair_head(str[i - 1])) {
s += "\\u" + str.charCodeAt(i).toString(16);
} else {
s += str[i];
for (var i = 0, j = 0; i < str.length; i++) {
var code = str.charCodeAt(i);
if (is_surrogate_pair_head(code)) {
if (is_surrogate_pair_tail(str.charCodeAt(i + 1))) {
i++;
continue;
}
} else if (!is_surrogate_pair_tail(code)) {
continue;
}
s += str.slice(j, i) + "\\u" + code.toString(16);
j = i + 1;
}
return s;
return j == 0 ? str : s + str.slice(j);
};
function make_string(str, quote) {
@@ -777,7 +782,9 @@ function OutputStream(options) {
PARENS(AST_Number, function(output) {
var p = output.parent();
if (p instanceof AST_PropAccess && p.expression === this) {
var value = this.getValue();
var value = this.value;
// https://github.com/mishoo/UglifyJS2/issues/115
// https://github.com/mishoo/UglifyJS2/pull/1009
if (value < 0 || /^0/.test(make_num(value))) {
return true;
}
@@ -1207,7 +1214,7 @@ function OutputStream(options) {
output.print_string(prop);
output.print("]");
} else {
if (expr instanceof AST_Number && expr.getValue() >= 0) {
if (expr instanceof AST_Number && expr.value >= 0) {
if (!/[xa-f.)]/i.test(output.last())) {
output.print(".");
}
@@ -1331,27 +1338,34 @@ function OutputStream(options) {
output.print("this");
});
DEFPRINT(AST_Constant, function(self, output) {
output.print(self.getValue());
output.print(self.value);
});
DEFPRINT(AST_String, function(self, output) {
output.print_string(self.getValue(), self.quote);
output.print_string(self.value, self.quote);
});
DEFPRINT(AST_Number, function(self, output) {
if (use_asm && self.start && self.start.raw != null) {
output.print(self.start.raw);
} else {
output.print(make_num(self.getValue()));
output.print(make_num(self.value));
}
});
DEFPRINT(AST_RegExp, function(self, output) {
var regexp = self.getValue();
var regexp = self.value;
var str = regexp.toString();
var end = str.lastIndexOf("/");
if (regexp.raw_source) {
str = "/" + regexp.raw_source + str.slice(str.lastIndexOf("/"));
str = "/" + regexp.raw_source + str.slice(end);
} else if (end == 1) {
str = "/(?:)" + str.slice(end);
} else if (str.indexOf("/", 1) < end) {
str = "/" + str.slice(1, end).replace(/\\\\|[^/]?\//g, function(match) {
return match[0] == "\\" ? match : match.slice(0, -1) + "\\/";
}) + str.slice(end);
}
output.print(output.to_utf8(str).replace(/\\(?:\0(?![0-9])|[^\0])/g, function(seq) {
switch (seq[1]) {
output.print(output.to_utf8(str).replace(/\\(?:\0(?![0-9])|[^\0])/g, function(match) {
switch (match[1]) {
case "\n": return "\\n";
case "\r": return "\\r";
case "\t": return "\t";
@@ -1361,7 +1375,7 @@ function OutputStream(options) {
case "\x0B": return "\v";
case "\u2028": return "\\u2028";
case "\u2029": return "\\u2029";
default: return seq;
default: return match;
}
}).replace(/[\n\r\u2028\u2029]/g, function(c) {
switch (c) {

View File

@@ -133,14 +133,10 @@ function is_letter(code) {
}
function is_surrogate_pair_head(code) {
if (typeof code == "string")
code = code.charCodeAt(0);
return code >= 0xd800 && code <= 0xdbff;
}
function is_surrogate_pair_tail(code) {
if (typeof code == "string")
code = code.charCodeAt(0);
return code >= 0xdc00 && code <= 0xdfff;
}
@@ -245,16 +241,16 @@ function tokenizer($TEXT, filename, html5_comments, shebang) {
if (signal_eof && !ch)
throw EX_EOF;
if (NEWLINE_CHARS[ch]) {
S.newline_before = S.newline_before || !in_string;
++S.line;
S.col = 0;
if (!in_string && ch == "\r" && peek() == "\n") {
// treat a \r\n sequence as a single \n
++S.pos;
S.line++;
if (!in_string) S.newline_before = true;
if (ch == "\r" && peek() == "\n") {
// treat `\r\n` as `\n`
S.pos++;
ch = "\n";
}
} else {
++S.col;
S.col++;
}
return ch;
}

View File

@@ -219,7 +219,12 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options) {
var redef;
while (redef = new_def.redefined()) new_def = redef;
} else {
new_def = self.globals.get(name) || scope.def_variable(node);
new_def = self.globals.get(name);
}
if (new_def) {
new_def.orig.push(node);
} else {
new_def = scope.def_variable(node);
}
old_def.orig.concat(old_def.references).forEach(function(node) {
node.thedef = new_def;
@@ -428,6 +433,11 @@ AST_Toplevel.DEFMETHOD("mangle_names", function(options) {
if (options.cache && node instanceof AST_Toplevel) {
node.globals.each(mangle);
}
if (node instanceof AST_Defun && tw.has_directive("use asm")) {
var sym = new AST_SymbolRef(node.name);
sym.scope = node;
sym.reference(options);
}
node.variables.each(function(def) {
if (!defer_redef(def)) mangle(def);
});

View File

@@ -52,7 +52,7 @@ TreeTransformer.prototype = new TreeWalker;
(function(DEF) {
function do_list(list, tw) {
return MAP(list, function(node) {
return List(list, function(node) {
return node.transform(tw, true);
});
}

View File

@@ -113,8 +113,8 @@ function return_true() { return true; }
function return_this() { return this; }
function return_null() { return null; }
var MAP = (function() {
function MAP(a, f, backwards) {
var List = (function() {
function List(a, f, backwards) {
var ret = [], top = [], i;
function doit() {
var val = f(a[i], i);
@@ -149,14 +149,14 @@ var MAP = (function() {
}
return top.concat(ret);
}
MAP.at_top = function(val) { return new AtTop(val) };
MAP.splice = function(val) { return new Splice(val) };
MAP.last = function(val) { return new Last(val) };
var skip = MAP.skip = {};
function AtTop(val) { this.v = val }
function Splice(val) { this.v = val }
function Last(val) { this.v = val }
return MAP;
List.at_top = function(val) { return new AtTop(val); };
List.splice = function(val) { return new Splice(val); };
List.last = function(val) { return new Last(val); };
var skip = List.skip = {};
function AtTop(val) { this.v = val; }
function Splice(val) { this.v = val; }
function Last(val) { this.v = val; }
return List;
})();
function push_uniq(array, el) {
@@ -185,7 +185,7 @@ function makePredicate(words) {
function all(array, predicate) {
for (var i = array.length; --i >= 0;)
if (!predicate(array[i]))
if (!predicate(array[i], i))
return false;
return true;
}

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.7.3",
"version": "3.8.1",
"engines": {
"node": ">=0.8.0"
},

View File

@@ -207,8 +207,9 @@ function reminify(orig_options, input_code, input_formatted, stdout) {
});
return false;
} else {
var expected = stdout[options.toplevel ? 1 : 0];
var actual = run_code(result.code, options.toplevel);
var toplevel = sandbox.has_toplevel(options);
var expected = stdout[toplevel ? 1 : 0];
var actual = run_code(result.code, toplevel);
if (typeof expected != "string" && typeof actual != "string" && expected.name == actual.name) {
actual = expected;
}
@@ -378,7 +379,10 @@ function test_case(test) {
}
if (test.expect_stdout && (!test.node_version || semver.satisfies(process.version, test.node_version))) {
var stdout = [ run_code(input_code), run_code(input_code, true) ];
var toplevel = test.options.toplevel;
var toplevel = sandbox.has_toplevel({
compress: test.options,
mangle: test.mangle
});
var actual = stdout[toplevel ? 1 : 0];
if (test.expect_stdout === true) {
test.expect_stdout = actual;

View File

@@ -16,6 +16,7 @@ holes_and_undefined: {
constant_join: {
options = {
evaluate: true,
strings: true,
unsafe: true,
}
input: {
@@ -65,6 +66,7 @@ constant_join: {
constant_join_2: {
options = {
evaluate: true,
strings: true,
unsafe: true,
}
input: {
@@ -94,9 +96,11 @@ constant_join_2: {
constant_join_3: {
options = {
evaluate: true,
strings: true,
unsafe: true,
}
input: {
var foo, bar, baz;
var a = [ null ].join();
var b = [ , ].join();
var c = [ , 1, , 3 ].join();
@@ -111,6 +115,7 @@ constant_join_3: {
var l = [ foo, bar + "baz" ].join("");
}
expect: {
var foo, bar, baz;
var a = "";
var b = "";
var c = ",1,,3";

View File

@@ -166,3 +166,69 @@ asm_nested_functions: {
}
expect_exact: '0;function a(){"use asm";0.0}0;function b(){0;function c(){"use asm";0.0}0;function d(){0}0}0;'
}
issue_3636_1: {
mangle = {}
input: {
function n(stdlib, foreign, buffer) {
"use asm";
function add(x, y) {
x = x | 0;
y = y | 0;
return x + y | 0;
}
return {
add: add
};
}
console.log(new n().add("foo", 42));
}
expect: {
function n(o, e, u) {
"use asm";
function d(n, o) {
n = n | 0;
o = o | 0;
return n + o | 0;
}
return {
add: d
};
}
console.log(new n().add("foo", 42));
}
expect_stdout: "42"
}
issue_3636_2: {
mangle = {}
input: {
var n = function(stdlib, foreign, buffer) {
"use asm";
function add(x, y) {
x = x | 0;
y = y | 0;
return x + y | 0;
}
return {
add: add
};
};
console.log(new n().add("foo", 42));
}
expect: {
var n = function(n, o, e) {
"use asm";
function r(n, o) {
n = n | 0;
o = o | 0;
return n + o | 0;
}
return {
add: r
};
};
console.log(new n().add("foo", 42));
}
expect_stdout: "42"
}

View File

@@ -86,3 +86,70 @@ issue_3465_3: {
}
expect_stdout: "PASS"
}
issue_2737_2: {
options = {
booleans: true,
inline: true,
reduce_vars: true,
unused: true,
}
input: {
(function(bar) {
for (;bar();) break;
})(function qux() {
return console.log("PASS"), qux;
});
}
expect: {
(function(bar) {
for (;bar();) break;
})(function() {
return console.log("PASS"), 1;
});
}
expect_stdout: "PASS"
}
issue_3658: {
options = {
booleans: true,
evaluate: true,
reduce_vars: true,
}
input: {
console.log(function f() {
console || f();
return "PASS";
}());
}
expect: {
console.log(function f() {
console || f();
return "PASS";
}());
}
expect_stdout: "PASS"
}
issue_3690: {
options = {
booleans: true,
unused: true,
}
input: {
console.log(function(a) {
return function() {
return a = [ this ];
}() ? "PASS" : "FAIL";
}());
}
expect: {
console.log(function(a) {
return function() {
return 1;
}() ? "PASS" : "FAIL";
}());
}
expect_stdout: "PASS"
}

View File

@@ -5863,8 +5863,8 @@ issue_2974: {
var c = 0;
(function(b) {
var a = 2;
for (; b.null = -4, c++, b.null && --a > 0;);
})(!0),
for (;c++, (!0).null && --a > 0;);
})(),
console.log(c);
}
expect_stdout: "1"
@@ -7422,3 +7422,387 @@ issue_3641: {
}
expect_stdout: "foo undefined"
}
issue_3651: {
options = {
collapse_vars: true,
toplevel: true,
}
input: {
var a, b = "PASS";
try {
a = function() {
try {
var c = 1;
while (0 < --c);
} catch (e) {} finally {
throw 42;
}
}();
b = "FAIL";
a.p;
} catch (e) {
console.log(b);
}
}
expect: {
var a, b = "PASS";
try {
a = function() {
try {
var c = 1;
while (0 < --c);
} catch (e) {} finally {
throw 42;
}
}();
b = "FAIL";
a.p;
} catch (e) {
console.log(b);
}
}
expect_stdout: "PASS"
}
issue_3671: {
options = {
collapse_vars: true,
}
input: {
var a = 0;
try {
a++;
A += 0;
a = 1 + a;
} catch (e) {
console.log(a);
}
}
expect: {
var a = 0;
try {
a++;
A += 0;
a = 1 + a;
} catch (e) {
console.log(a);
}
}
expect_stdout: "1"
}
call_1: {
options = {
collapse_vars: true,
}
input: {
(function(a) {
a = console;
(function() {})();
a.log("PASS");
})();
}
expect: {
(function(a) {
(function() {})();
(a = console).log("PASS");
})();
}
expect_stdout: "PASS"
}
call_1_symbol: {
options = {
collapse_vars: true,
reduce_vars: true,
}
input: {
(function(a) {
function f() {}
a = console;
f();
a.log(typeof f);
})();
}
expect: {
(function(a) {
function f() {}
f();
(a = console).log(typeof f);
})();
}
expect_stdout: "function"
}
call_2: {
options = {
collapse_vars: true,
}
input: {
(function(a) {
a = console;
(function() {
return 42;
console.log("FAIL");
})();
a.log("PASS");
})();
}
expect: {
(function(a) {
(function() {
return 42;
console.log("FAIL");
})();
(a = console).log("PASS");
})();
}
expect_stdout: "PASS"
}
call_2_symbol: {
options = {
collapse_vars: true,
reduce_vars: true,
}
input: {
(function(a) {
function f() {
return 42;
console.log("FAIL");
}
a = console;
f();
a.log(typeof f);
})();
}
expect: {
(function(a) {
function f() {
return 42;
console.log("FAIL");
}
f();
(a = console).log(typeof f);
})();
}
expect_stdout: "function"
}
call_3: {
options = {
collapse_vars: true,
}
input: {
(function(a) {
a = console;
(function() {
a = {
log: function() {
console.log("PASS");
}
}
})();
a.log("FAIL");
})();
}
expect: {
(function(a) {
a = console;
(function() {
a = {
log: function() {
console.log("PASS");
}
}
})();
a.log("FAIL");
})();
}
expect_stdout: "PASS"
}
call_3_symbol: {
options = {
collapse_vars: true,
reduce_vars: true,
}
input: {
(function(a) {
function f() {
a = {
log: function() {
console.log(typeof f);
}
}
}
a = console;
f();
a.log("FAIL");
})();
}
expect: {
(function(a) {
function f() {
a = {
log: function() {
console.log(typeof f);
}
}
}
a = console;
f();
a.log("FAIL");
})();
}
expect_stdout: "function"
}
issue_3698_1: {
options = {
collapse_vars: true,
}
input: {
var log = console.log;
var a, b = 0, c = 0;
(function() {
a = b;
})(b++, (b++, c++));
log(a, b, c);
}
expect: {
var log = console.log;
var a, b = 0, c = 0;
(function() {
a = b;
})(b++, (b++, c++));
log(a, b, c);
}
expect_stdout: "2 2 1"
}
issue_3698_2: {
options = {
collapse_vars: true,
reduce_vars: true,
}
input: {
var log = console.log;
var a, b = 0, c = 0, d = 1;
(function f() {
a = b;
d-- && f();
})(b++, (b++, c++));
log(a, b, c, d);
}
expect: {
var log = console.log;
var a, b = 0, c = 0, d = 1;
(function f() {
a = b;
d-- && f();
})(b++, (b++, c++));
log(a, b, c, d);
}
expect_stdout: "2 2 1 -1"
}
issue_3698_3: {
options = {
collapse_vars: true,
reduce_vars: true,
}
input: {
var a = 0, b = 0;
(function f(c) {
{
b++;
var bar_1 = (b = 1 + b, c = 0);
a-- && f();
}
})();
console.log(b);
}
expect: {
var a = 0, b = 0;
(function f(c) {
var bar_1 = (b = 1 + ++b, c = 0);
a-- && f();
})();
console.log(b);
}
expect_stdout: "2"
}
issue_3700: {
options = {
collapse_vars: true,
}
input: {
var a = "FAIL";
try {
a = "PASS";
(function() {
throw 0;
})();
a = 1 + a;
} catch (e) {
}
console.log(a);
}
expect: {
var a = "FAIL";
try {
a = "PASS";
(function() {
throw 0;
})();
a = 1 + a;
} catch (e) {
}
console.log(a);
}
expect_stdout: "PASS"
}
issue_3744: {
options = {
collapse_vars: true,
inline: true,
reduce_vars: true,
unused: true,
}
input: {
(function f(a) {
({
get p() {
switch (1) {
case 0:
f((a = 2, 3));
case 1:
console.log(function g(b) {
return b || "PASS";
}());
}
}
}).p;
})();
}
expect: {
(function f(a) {
({
get p() {
switch (1) {
case 0:
f();
case 1:
console.log(b || "PASS");
}
var b;
}
}).p;
})();
}
expect_stdout: "PASS"
}

View File

@@ -26,7 +26,9 @@ concat_1: {
}
concat_2: {
options = {}
options = {
strings: true,
}
input: {
console.log(
1 + (2 + 3),
@@ -55,7 +57,9 @@ concat_2: {
}
concat_3: {
options = {}
options = {
strings: true,
}
input: {
console.log(
1 + 2 + (3 + 4 + 5),
@@ -84,7 +88,9 @@ concat_3: {
}
concat_4: {
options = {}
options = {
strings: true,
}
input: {
console.log(
1 + "2" + (3 + 4 + 5),
@@ -113,7 +119,9 @@ concat_4: {
}
concat_5: {
options = {}
options = {
strings: true,
}
input: {
console.log(
"1" + 2 + (3 + 4 + 5),
@@ -142,7 +150,9 @@ concat_5: {
}
concat_6: {
options = {}
options = {
strings: true,
}
input: {
console.log(
"1" + "2" + (3 + 4 + 5),
@@ -171,6 +181,9 @@ concat_6: {
}
concat_7: {
options = {
strings: true,
}
input: {
console.log(
"" + 1,
@@ -197,6 +210,9 @@ concat_7: {
}
concat_8: {
options = {
strings: true,
}
input: {
console.log(
1 + "",
@@ -221,3 +237,20 @@ concat_8: {
}
expect_stdout: true
}
issue_3689: {
options = {
strings: true,
}
input: {
console.log(function(a) {
return a + ("" + (a[0] = 0));
}([]));
}
expect: {
console.log(function(a) {
return a + ("" + (a[0] = 0));
}([]));
}
expect_stdout: "00"
}

View File

@@ -294,6 +294,45 @@ cond_5: {
}
}
cond_6: {
options = {
booleans: true,
conditionals: true,
}
input: {
x ? a : b;
x ? a : a;
x ? y ? a : b : c;
x ? y ? a : a : b;
x ? y ? a : b : b;
x ? y ? a : b : a;
x ? y ? a : a : a;
x ? a : y ? b : c;
x ? a : y ? a : b;
x ? a : y ? b : b;
x ? a : y ? b : a;
x ? a : y ? a : a;
}
expect: {
x ? a : b;
x, a;
x ? y ? a : b : c;
x ? (y, a) : b;
x && y ? a : b;
!x || y ? a : b;
x && y, a;
x ? a : y ? b : c;
x || y ? a : b;
x ? a : (y, b);
!x && y ? b : a;
!x && y, a;
}
}
cond_7: {
options = {
conditionals: true,
@@ -726,6 +765,24 @@ cond_11: {
expect_stdout: "foo bar"
}
cond_12: {
options = {
conditionals: true,
}
input: {
x ? y && a : a;
x ? y || a : a;
x ? a : y && a;
x ? a : y || a;
}
expect: {
(!x || y) && a;
x && y || a;
(x || y) && a;
!x && y || a;
}
}
ternary_boolean_consequent: {
options = {
booleans: true,
@@ -1183,11 +1240,11 @@ issue_2535_1: {
expect: {
y();
x() && y();
(x(), 1) && y();
x(), y();
x() && y();
x() && y();
x() && y();
(x(), 0) && y();
x();
}
}
@@ -1578,3 +1635,34 @@ issue_3576: {
}
expect_stdout: "PASS"
}
issue_3668: {
options = {
conditionals: true,
if_return: true,
}
input: {
function f() {
try {
var undefined = typeof f;
if (!f) return undefined;
return;
} catch (e) {
return "FAIL";
}
}
console.log(f());
}
expect: {
function f() {
try {
var undefined = typeof f;
return f ? void 0 : undefined;
} catch (e) {
return "FAIL";
}
}
console.log(f());
}
expect_stdout: "undefined"
}

View File

@@ -141,207 +141,6 @@ try_catch_finally: {
]
}
accessor: {
options = {
side_effects: true,
}
input: {
({
get a() {},
set a(v){
this.b = 2;
},
b: 1
});
}
expect: {}
}
issue_2233_1: {
options = {
pure_getters: "strict",
side_effects: true,
unsafe: true,
}
input: {
Array.isArray;
Boolean;
console.log;
Date;
decodeURI;
decodeURIComponent;
encodeURI;
encodeURIComponent;
Error.name;
escape;
eval;
EvalError;
Function.length;
isFinite;
isNaN;
JSON;
Math.random;
Number.isNaN;
parseFloat;
parseInt;
RegExp;
Object.defineProperty;
String.fromCharCode;
RangeError;
ReferenceError;
SyntaxError;
TypeError;
unescape;
URIError;
}
expect: {}
expect_stdout: true
}
global_timeout_and_interval_symbols: {
options = {
pure_getters: "strict",
side_effects: true,
unsafe: true,
}
input: {
// These global symbols do not exist in the test sandbox
// and must be tested separately.
clearInterval;
clearTimeout;
setInterval;
setTimeout;
}
expect: {}
}
issue_2233_2: {
options = {
pure_getters: "strict",
reduce_funcs: true,
reduce_vars: true,
side_effects: true,
unsafe: true,
unused: true,
}
input: {
var RegExp;
Array.isArray;
RegExp;
UndeclaredGlobal;
function foo() {
var Number;
AnotherUndeclaredGlobal;
Math.sin;
Number.isNaN;
}
}
expect: {
var RegExp;
UndeclaredGlobal;
function foo() {
var Number;
AnotherUndeclaredGlobal;
Number.isNaN;
}
}
}
issue_2233_3: {
options = {
pure_getters: "strict",
reduce_funcs: true,
reduce_vars: true,
side_effects: true,
toplevel: true,
unsafe: true,
unused: true,
}
input: {
var RegExp;
Array.isArray;
RegExp;
UndeclaredGlobal;
function foo() {
var Number;
AnotherUndeclaredGlobal;
Math.sin;
Number.isNaN;
}
}
expect: {
UndeclaredGlobal;
}
}
global_fns: {
options = {
side_effects: true,
unsafe: true,
}
input: {
Boolean(1, 2);
decodeURI(1, 2);
decodeURIComponent(1, 2);
Date(1, 2);
encodeURI(1, 2);
encodeURIComponent(1, 2);
Error(1, 2);
escape(1, 2);
EvalError(1, 2);
isFinite(1, 2);
isNaN(1, 2);
Number(1, 2);
Object(1, 2);
parseFloat(1, 2);
parseInt(1, 2);
RangeError(1, 2);
ReferenceError(1, 2);
String(1, 2);
SyntaxError(1, 2);
TypeError(1, 2);
unescape(1, 2);
URIError(1, 2);
try {
Function(1, 2);
} catch (e) {
console.log(e.name);
}
try {
RegExp(1, 2);
} catch (e) {
console.log(e.name);
}
try {
Array(NaN);
} catch (e) {
console.log(e.name);
}
}
expect: {
try {
Function(1, 2);
} catch (e) {
console.log(e.name);
}
try {
RegExp(1, 2);
} catch (e) {
console.log(e.name);
}
try {
Array(NaN);
} catch (e) {
console.log(e.name);
}
}
expect_stdout: [
"SyntaxError",
"SyntaxError",
"RangeError",
]
}
collapse_vars_assignment: {
options = {
collapse_vars: true,
@@ -863,23 +662,6 @@ issue_2749: {
expect_stdout: "PASS"
}
unsafe_builtin: {
options = {
side_effects: true,
unsafe: true,
}
input: {
(!w).constructor(x);
Math.abs(y);
[ 1, 2, z ].valueOf();
}
expect: {
w, x;
y;
z;
}
}
issue_2860_1: {
options = {
dead_code: true,
@@ -941,24 +723,6 @@ issue_2929: {
expect_stdout: "PASS"
}
unsafe_string_replace: {
options = {
side_effects: true,
unsafe: true,
}
input: {
"foo".replace("f", function() {
console.log("PASS");
});
}
expect: {
"foo".replace("f", function() {
console.log("PASS");
});
}
expect_stdout: "PASS"
}
issue_3402: {
options = {
dead_code: true,
@@ -1066,6 +830,7 @@ issue_3552: {
unreachable_assign: {
options = {
dead_code: true,
strings: true,
}
input: {
console.log(A = "P" + (A = "A" + (B = "S" + (A = B = "S"))), A, B);

View File

@@ -699,18 +699,6 @@ iife: {
}
}
drop_value: {
options = {
side_effects: true,
}
input: {
(1, [2, foo()], 3, {a:1, b:bar()});
}
expect: {
foo(), bar();
}
}
issue_1539: {
options = {
collapse_vars: true,
@@ -1203,10 +1191,10 @@ issue_2105_1: {
input: {
!function(factory) {
factory();
}( function() {
}(function() {
return function(fn) {
fn()().prop();
}( function() {
}(function() {
function bar() {
var quux = function() {
console.log("PASS");
@@ -1217,7 +1205,7 @@ issue_2105_1: {
return { prop: foo };
}
return bar;
} );
});
});
}
expect: {
@@ -1247,10 +1235,10 @@ issue_2105_2: {
input: {
!function(factory) {
factory();
}( function() {
}(function() {
return function(fn) {
fn()().prop();
}( function() {
}(function() {
function bar() {
var quux = function() {
console.log("PASS");
@@ -1261,7 +1249,7 @@ issue_2105_2: {
return { prop: foo };
}
return bar;
} );
});
});
}
expect: {
@@ -1270,6 +1258,44 @@ issue_2105_2: {
expect_stdout: "PASS"
}
issue_2105_3: {
options = {
inline: true,
passes: 2,
reduce_vars: true,
unused: true,
}
input: {
!function(factory) {
factory();
}(function() {
return function(fn) {
fn()().prop();
}(function() {
function bar() {
var quux = function() {
console.log("PASS");
}, foo = function() {
console.log;
quux();
};
return { prop: foo };
}
return bar;
});
});
}
expect: {
!void void {
prop: function() {
console.log;
void console.log("PASS");
}
}.prop();
}
expect_stdout: "PASS"
}
issue_2226_1: {
options = {
side_effects: true,
@@ -2266,3 +2292,155 @@ issue_3598: {
}
expect_stdout: "PASS"
}
self_assign: {
options = {
passes: 2,
side_effects: true,
unused: true,
}
input: {
function d(a) {
a = a;
}
function e(a, b) {
a = b;
b = a;
}
function f(a, b, c) {
a = b;
b = c;
c = a;
}
function g(a, b, c) {
a = a * b + c;
}
}
expect: {
function d(a) {}
function e(a, b) {}
function f(a, b, c) {}
function g(a, b, c) {}
}
}
function_argument_reference: {
options = {
keep_fargs: false,
side_effects: true,
unused: true,
}
input: {
var a = 1, b = 42;
function f(a) {
b <<= a;
}
f();
console.log(a, b);
}
expect: {
var a = 1, b = 42;
function f(a) {
b <<= a;
}
f();
console.log(a, b);
}
expect_stdout: "1 42"
}
function_parameter_ie8: {
options = {
ie8: true,
reduce_vars: true,
unused: true,
}
input: {
(function() {
var a;
function f() {
console.log("PASS");
}
f(a = 1 + a);
})();
}
expect: {
(function() {
(function f() {
console.log("PASS");
})();
})();
}
expect_stdout: "PASS"
}
issue_3664: {
options = {
pure_getters: "strict",
unused: true,
}
input: {
console.log(function() {
var a, b = (a = (a = [ b && console.log("FAIL") ]).p = 0, 0);
return "PASS";
}());
}
expect: {
console.log(function() {
var b = ([ b && console.log("FAIL") ].p = 0, 0);
return "PASS";
}());
}
expect_stdout: "PASS"
}
issue_3673: {
options = {
pure_getters: "strict",
side_effects: true,
toplevel: true,
unused: true,
}
input: {
var a;
(a = [ a ]).p = 42;
console.log("PASS");
}
expect: {
var a;
(a = [ a ]).p = 42;
console.log("PASS");
}
expect_stdout: "PASS"
}
issue_3746: {
options = {
keep_fargs: "strict",
side_effects: true,
unused: true,
}
input: {
try {
A;
} catch (e) {
var e;
}
(function f(a) {
e = a;
})();
console.log("PASS");
}
expect: {
try {
A;
} catch (e) {
var e;
}
(function(a) {
e = a;
})();
console.log("PASS");
}
expect_stdout: "PASS"
}

View File

@@ -2161,3 +2161,16 @@ collapse_vars_regexp: {
"abbb",
]
}
issue_3738: {
options = {
evaluate: true,
}
input: {
console.log(1 / (0 + ([] - 1) % 1));
}
expect: {
console.log(1 / (0 + ([] - 1) % 1));
}
expect_stdout: "Infinity"
}

View File

@@ -266,12 +266,7 @@ issue_2084: {
}
expect: {
var c = 0;
!function() {
var c;
c = 1 + (c = -1),
c = 1 + (c = 0),
0 !== 23..toString() && (c = 1 + c);
}(),
23..toString(),
console.log(c);
}
expect_stdout: "0"
@@ -1281,7 +1276,7 @@ issue_2630_3: {
(function() {
(function f1(a) {
f2();
--x >= 0 && f1({});
--x >= 0 && f1();
})(a++);
function f2() {
a++;
@@ -1911,14 +1906,14 @@ issue_2737_2: {
}
input: {
(function(bar) {
for (;bar(); ) break;
for (;bar();) break;
})(function qux() {
return console.log("PASS"), qux;
});
}
expect: {
(function(bar) {
for (;bar(); ) break;
for (;bar();) break;
})(function qux() {
return console.log("PASS"), qux;
});
@@ -2249,7 +2244,7 @@ issue_3076: {
var c = "PASS";
(function(b) {
var n = 2;
while (--b + (e = void 0, e && (c = "FAIL"), e = 5, 1).toString() && --n > 0);
while (--b + (e = void 0, e && (c = "FAIL"), e = 5, 1..toString()) && --n > 0);
var e;
})(2),
console.log(c);
@@ -3705,3 +3700,88 @@ pr_3595_4: {
}
expect_stdout: "PASS"
}
issue_3679_1: {
options = {
collapse_vars: true,
inline: true,
pure_getters: "strict",
reduce_vars: true,
side_effects: true,
unused: true,
}
input: {
(function() {
var f = function() {};
f.g = function() {
console.log("PASS");
};
f.g();
})();
}
expect: {
console.log("PASS");
}
expect_stdout: "PASS"
}
issue_3679_2: {
options = {
collapse_vars: true,
inline: true,
passes: 2,
pure_getters: "strict",
reduce_vars: true,
side_effects: true,
unused: true,
}
input: {
(function() {
"use strict";
var f = function() {};
f.g = function() {
console.log("PASS");
};
f.g();
})();
}
expect: {
(function() {
"use strict";
console.log("PASS");
})();
}
expect_stdout: "PASS"
}
issue_3679_3: {
options = {
collapse_vars: true,
inline: true,
functions: true,
pure_getters: "strict",
reduce_vars: true,
side_effects: true,
unused: true,
}
input: {
(function() {
var f = function() {};
f.p = "PASS";
f.g = function() {
console.log(f.p);
};
f.g();
})();
}
expect: {
(function() {
function f() {};
f.p = "PASS";
(f.g = function() {
console.log(f.p);
})();
})();
}
expect_stdout: "PASS"
}

View File

@@ -664,7 +664,7 @@ issue_2519: {
}
expect: {
function testFunc() {
return 1 * ((6 + 5) / 2);
return +((6 + 5) / 2);
}
console.log(testFunc());
}

View File

@@ -2361,3 +2361,62 @@ issue_3542: {
}
expect_stdout: "1"
}
issue_3703: {
options = {
ie8: true,
reduce_vars: true,
toplevel: true,
unused: true,
}
input: {
var a = "PASS";
function f() {
var b;
function g() {
a = "FAIL";
}
var c = g;
function h() {
f;
}
a ? b |= c : b.p;
}
f();
console.log(a);
}
expect: {
var a = "PASS";
(function() {
var b;
var c = function g() {
a = "FAIL";
};
a ? b |= c : b.p;
})();
console.log(a);
}
expect_stdout: "PASS"
}
issue_3750: {
options = {
evaluate: true,
ie8: true,
}
input: {
(function(a) {
return function a() {
return a && console.log("PASS");
}();
})();
}
expect: {
(function(a) {
return function a() {
return a && console.log("PASS");
}();
})();
}
expect_stdout: "PASS"
}

View File

@@ -46,7 +46,7 @@ mangle_props: {
obj[1/0],
obj["Infinity"],
obj[-1/0],
obj[-1/0],
obj[-(1/0)],
obj["-Infinity"],
obj[null],
obj["null"]

View File

@@ -1,98 +1,111 @@
issue_269_1: {
options = {
options = {
unsafe: true,
}
input: {
f(
String(x),
Number(x),
Boolean(x),
input: {
var x = {};
console.log(
String(x),
Number(x),
Boolean(x),
String(),
Number(),
Boolean()
);
}
expect: {
f(
x + '', +x, !!x,
'', 0, false
);
}
String(),
Number(),
Boolean()
);
}
expect: {
var x = {};
console.log(
x + "", +x, !!x,
"", 0, false
);
}
expect_stdout: true
}
issue_269_dangers: {
options = {
options = {
unsafe: true,
}
input: {
f(
String(x, x),
Number(x, x),
Boolean(x, x)
);
}
expect: {
f(String(x, x), Number(x, x), Boolean(x, x));
}
input: {
var x = {};
console.log(
String(x, x),
Number(x, x),
Boolean(x, x)
);
}
expect: {
var x = {};
console.log(String(x, x), Number(x, x), Boolean(x, x));
}
expect_stdout: true
}
issue_269_in_scope: {
options = {
options = {
unsafe: true,
}
input: {
var String, Number, Boolean;
f(
String(x),
Number(x, x),
Boolean(x)
);
}
expect: {
var String, Number, Boolean;
f(String(x), Number(x, x), Boolean(x));
}
input: {
var String, Number, Boolean;
var x = {};
console.log(
String(x),
Number(x, x),
Boolean(x)
);
}
expect: {
var String, Number, Boolean;
var x = {};
console.log(String(x), Number(x, x), Boolean(x));
}
expect_stdout: true
}
strings_concat: {
options = {
options = {
strings: true,
unsafe: true,
}
input: {
f(
String(x + 'str'),
String('str' + x)
);
}
expect: {
f(
x + 'str',
'str' + x
);
}
input: {
var x = {};
console.log(
String(x + "str"),
String("str" + x)
);
}
expect: {
var x = {};
console.log(
x + "str",
"str" + x
);
}
expect_stdout: true
}
regexp: {
options = {
options = {
evaluate: true,
unsafe: true,
}
input: {
RegExp("foo");
RegExp("bar", "ig");
RegExp(foo);
RegExp("bar", ig);
RegExp("should", "fail");
}
expect: {
/foo/;
/bar/ig;
RegExp(foo);
RegExp("bar", ig);
RegExp("should", "fail");
}
expect_warnings: [
'WARN: Error converting RegExp("should","fail") [test/compress/issue-269.js:5,2]',
]
input: {
RegExp("foo");
RegExp("bar", "ig");
RegExp(foo);
RegExp("bar", ig);
RegExp("should", "fail");
}
expect: {
/foo/;
/bar/ig;
RegExp(foo);
RegExp("bar", ig);
RegExp("should", "fail");
}
expect_warnings: [
'WARN: Error converting RegExp("should","fail") [test/compress/issue-269.js:5,8]',
]
}

View File

@@ -16,7 +16,6 @@ wrongly_optimized: {
function func() {
foo();
}
// TODO: optimize to `func(), bar()`
(func(), 1) && bar();
func(), 1, bar();
}
}

View File

@@ -84,6 +84,7 @@ wrongly_optimized: {
options = {
booleans: true,
conditionals: true,
dead_code: true,
evaluate: true,
expression: true,
}
@@ -99,8 +100,8 @@ wrongly_optimized: {
function func() {
foo();
}
// TODO: optimize to `func(), bar()`
if (func(), 1) bar();
func(), 1;
bar();
}
}

View File

@@ -728,7 +728,7 @@ issue_2630_3: {
(function() {
(function f1() {
f2();
--x >= 0 && f1({});
--x >= 0 && f1();
})(a++);
function f2() {
a++;
@@ -1369,7 +1369,7 @@ recursive_iife_1: {
}
expect: {
console.log(function f(a, b) {
return b || f("FAIL", "PASS");
return b || f(0, "PASS");
}());
}
expect_stdout: "PASS"
@@ -1388,7 +1388,7 @@ recursive_iife_2: {
}
expect: {
console.log(function f(a, b) {
return b || f("FAIL", "PASS");
return b || f(0, "PASS");
}(0, 0));
}
expect_stdout: "PASS"
@@ -1416,7 +1416,7 @@ recursive_iife_3: {
var a = 1, c = "PASS";
(function() {
(function f(b, d, e) {
a-- && f(null, 42, 0);
a-- && f(0, 42, 0);
e && (c = "FAIL");
d && d.p;
})();

View File

@@ -6,7 +6,7 @@ while_becomes_for: {
while (foo()) bar();
}
expect: {
for (; foo(); ) bar();
for (;foo();) bar();
}
}
@@ -19,7 +19,7 @@ drop_if_break_1: {
if (foo()) break;
}
expect: {
for (; !foo(););
for (;!foo(););
}
}
@@ -32,7 +32,7 @@ drop_if_break_2: {
if (foo()) break;
}
expect: {
for (; bar() && !foo(););
for (;bar() && !foo(););
}
}
@@ -70,7 +70,7 @@ drop_if_break_4: {
}
}
expect: {
for (; bar() && (x(), y(), !foo());) z(), k();
for (;bar() && (x(), y(), !foo());) z(), k();
}
}
@@ -82,7 +82,7 @@ drop_if_else_break_1: {
for (;;) if (foo()) bar(); else break;
}
expect: {
for (; foo(); ) bar();
for (;foo();) bar();
}
}
@@ -97,7 +97,7 @@ drop_if_else_break_2: {
}
}
expect: {
for (; bar() && foo();) baz();
for (;bar() && foo();) baz();
}
}
@@ -114,7 +114,7 @@ drop_if_else_break_3: {
}
}
expect: {
for (; bar() && foo();) {
for (;bar() && foo();) {
baz();
stuff1();
stuff2();
@@ -138,7 +138,7 @@ drop_if_else_break_4: {
}
}
expect: {
for (; bar() && (x(), y(), foo());) baz(), z(), k();
for (;bar() && (x(), y(), foo());) baz(), z(), k();
}
}
@@ -523,13 +523,13 @@ issue_2740_1: {
loops: true,
}
input: {
for (; ; ) break;
for (a(); ; ) break;
for (; b(); ) break;
for (c(); d(); ) break;
for (; ; e()) break;
for (f(); ; g()) break;
for (; h(); i()) break;
for (;;) break;
for (a();;) break;
for (;b();) break;
for (c(); d();) break;
for (;;e()) break;
for (f();; g()) break;
for (;h(); i()) break;
for (j(); k(); l()) break;
}
expect: {
@@ -670,7 +670,7 @@ issue_3371: {
function a() {
console.log("PASS");
}
for (; a(); );
for (;a(););
})();
}
expect_stdout: "PASS"

View File

@@ -91,7 +91,7 @@ evaluate_1: {
expect: {
console.log(
x + 1 + 2,
1 * x * 2,
2 * x,
+x + 1 + 2,
1 + x + 2 + 3,
3 | x,
@@ -173,7 +173,7 @@ evaluate_2: {
var x = "42", y = null;
[
x + 1 + 2,
1 * x * 2,
2 * x,
+x + 1 + 2,
1 + x + 2 + 3,
3 | x,
@@ -669,6 +669,9 @@ issue_1710: {
}
unary_binary_parenthesis: {
options = {
evaluate: true,
}
input: {
var v = [ 0, 1, NaN, Infinity, null, undefined, true, false, "", "foo", /foo/ ];
v.forEach(function(x) {
@@ -979,3 +982,272 @@ unsafe_math_swap_constant: {
}
expect_stdout: "6 6 7 6 6 8 9 10"
}
identity_1: {
options = {
evaluate: true,
}
input: {
0 + a;
a + 0;
0 - a;
a - 0;
1 * a;
a * 1;
1 / a;
a / 1;
}
expect: {
0 + a;
a + 0;
0 - a;
+a;
+a;
+a;
1 / a;
+a;
}
}
identity_2: {
options = {
evaluate: true,
}
input: {
0 + !a;
!a + 0;
0 - !a;
!a - 0;
1 * !a;
!a * 1;
1 / !a;
!a / 1;
}
expect: {
+!a;
+!a;
0 - !a;
+!a;
+!a;
+!a;
1 / !a;
+!a;
}
}
identity_3: {
options = {
evaluate: true,
}
input: {
0 + --a;
--a + 0;
0 - --a;
--a - 0;
1 * --a;
--a * 1;
1 / --a;
--a / 1;
}
expect: {
--a;
--a;
0 - --a;
--a;
--a;
--a;
1 / --a;
--a;
}
}
issue_3653: {
options = {
evaluate: true,
}
input: {
console.log(0 - (console && 0));
console.log(0 + (0 - (console && 0)));
console.log(0 - (0 - (console && 0)));
console.log(1 * (0 - (console && 0)));
console.log(1 / (0 - (console && 0)));
console.log((0 - (console && 0)) + 0);
console.log((0 - (console && 0)) - 0);
console.log((0 - (console && 0)) * 1);
console.log((0 - (console && 0)) / 1);
}
expect: {
console.log(0 - (console && 0));
console.log(0 - (console && 0));
console.log(0 - (0 - (console && 0)));
console.log(0 - (console && 0));
console.log(1 / (0 - (console && 0)));
console.log(0 - (console && 0));
console.log(0 - (console && 0));
console.log(0 - (console && 0));
console.log(0 - (console && 0));
}
expect_stdout: [
"0",
"0",
"0",
"0",
"Infinity",
"0",
"0",
"0",
"0",
]
}
issue_3655: {
options = {
evaluate: true,
}
input: {
console.log(0 + 0 * -[].length);
console.log(0 + (0 + 0 * -[].length));
console.log(0 - (0 + 0 * -[].length));
console.log(1 * (0 + 0 * -[].length));
console.log(1 / (0 + 0 * -[].length));
console.log((0 + 0 * -[].length) + 0);
console.log((0 + 0 * -[].length) - 0);
console.log((0 + 0 * -[].length) * 1);
console.log((0 + 0 * -[].length) / 1);
}
expect: {
console.log(0 + 0 * -[].length);
console.log(0 + 0 * -[].length);
console.log(0 - (0 + 0 * -[].length));
console.log(0 + 0 * -[].length);
console.log(1 / (0 + 0 * -[].length));
console.log(0 + 0 * -[].length);
console.log(0 + 0 * -[].length);
console.log(0 + 0 * -[].length);
console.log(0 + 0 * -[].length);
}
expect_stdout: [
"0",
"0",
"0",
"0",
"Infinity",
"0",
"0",
"0",
"0",
]
}
issue_3676_1: {
options = {
evaluate: true,
unsafe_math: true,
}
input: {
var a = [];
console.log(false - (a - (a[1] = 42)));
}
expect: {
var a = [];
console.log(false - (a - (a[1] = 42)));
}
expect_stdout: "NaN"
}
issue_3676_2: {
options = {
evaluate: true,
unsafe_math: true,
}
input: {
var a;
console.log(false - ((a = []) - (a[1] = 42)));
}
expect: {
var a;
console.log(false - ((a = []) - (a[1] = 42)));
}
expect_stdout: "NaN"
}
issue_3682_1: {
options = {
evaluate: true,
unsafe_math: true,
}
input: {
var a = -0;
console.log(1 / (a - 1 + 1));
}
expect: {
var a = -0;
console.log(1 / (a - 1 + 1));
}
expect_stdout: "Infinity"
}
issue_3682_2: {
options = {
evaluate: true,
unsafe_math: true,
}
input: {
var a = -0, b = 1;
console.log(1 / (a - (b - b)));
}
expect: {
var a = -0, b = 1;
console.log(1 / (a - (b - b)));
}
expect_stdout: "-Infinity"
}
issue_3682_3: {
options = {
evaluate: true,
unsafe_math: true,
}
input: {
var a = -0, b = 1, c = -1;
console.log(1 / (a - (+b + +c)));
}
expect: {
var a = -0, b = 1, c = -1;
console.log(1 / (a - (+b + +c)));
}
expect_stdout: "-Infinity"
}
issue_3684: {
options = {
evaluate: true,
}
input: {
console.log(1 / (-1 * (0 & console) + 0));
console.log(1 / ((0 & console) / -1 + 0));
}
expect: {
console.log(1 / (-1 * (0 & console) + 0));
console.log(1 / ((0 & console) / -1 + 0));
}
expect_stdout: [
"Infinity",
"Infinity",
]
}
issue_3695: {
options = {
evaluate: true,
}
input: {
var a = [];
console.log(+(a * (a[0] = false)));
}
expect: {
var a = [];
console.log(+(a * (a[0] = false)));
}
expect_stdout: "NaN"
}

View File

@@ -817,6 +817,29 @@ issue_2208_5: {
expect_stdout: "42"
}
issue_2208_6: {
options = {
inline: true,
properties: true,
side_effects: true,
}
input: {
a = 42;
console.log(("FAIL", {
p: function() {
return this.a;
}
}.p)());
}
expect: {
a = 42;
console.log(function() {
return this.a;
}());
}
expect_stdout: "42"
}
issue_2256: {
options = {
side_effects: true,

View File

@@ -2071,13 +2071,8 @@ issue_1670_6: {
}
expect: {
(function(a) {
switch (1) {
case a = 1:
console.log(a);
break;
default:
console.log(2);
}
a = 1;
console.log(a);
})(1);
}
expect_stdout: "1"
@@ -6847,3 +6842,34 @@ issue_3631_2: {
}
expect_stdout: "undefined"
}
issue_3666: {
options = {
collapse_vars: true,
passes: 2,
reduce_vars: true,
toplevel: true,
unused: true,
}
input: {
try {
var a = "FAIL";
} finally {
for (;!a;)
var c = a++;
var a = "PASS", b = c = "PASS";
}
console.log(a, b);
}
expect: {
try {
var a = "FAIL";
} finally {
for (;!a;)
a++;
var b = a = "PASS";
}
console.log(a, b);
}
expect_stdout: "PASS PASS"
}

View File

@@ -186,3 +186,290 @@ issue_3434_3: {
/\nfo\n[\n]o\bbb/;
}
}
issue_3434_4: {
options = {
evaluate: true,
unsafe: true,
}
input: {
[
[ "", RegExp("") ],
[ "/", RegExp("/") ],
[ "//", RegExp("//") ],
[ "\/", RegExp("\\/") ],
[ "///", RegExp("///") ],
[ "/\/", RegExp("/\\/") ],
[ "\//", RegExp("\\//") ],
[ "\\/", RegExp("\\\\/") ],
[ "////", RegExp("////") ],
[ "//\/", RegExp("//\\/") ],
[ "/\//", RegExp("/\\//") ],
[ "/\\/", RegExp("/\\\\/") ],
[ "\///", RegExp("\\///") ],
[ "\/\/", RegExp("\\/\\/") ],
[ "\\//", RegExp("\\\\//") ],
[ "\\\/", RegExp("\\\\\\/") ],
].forEach(function(test) {
console.log(test[1].test("\\"), test[1].test(test[0]));
});
}
expect: {
[
[ "", /(?:)/ ],
[ "/", /\// ],
[ "//", /\/\// ],
[ "/", /\// ],
[ "///", /\/\/\// ],
[ "//", /\/\// ],
[ "//", /\/\// ],
[ "\\/", /\\\// ],
[ "////", /\/\/\/\// ],
[ "///", /\/\/\// ],
[ "///", /\/\/\// ],
[ "/\\/", /\/\\\// ],
[ "///", /\/\/\// ],
[ "//", /\/\// ],
[ "\\//", /\\\/\// ],
[ "\\/", /\\\// ],
].forEach(function(test) {
console.log(test[1].test("\\"), test[1].test(test[0]));
});
}
expect_stdout: [
"true true",
"false true",
"false true",
"false true",
"false true",
"false true",
"false true",
"false true",
"false true",
"false true",
"false true",
"false true",
"false true",
"false true",
"false true",
"false true",
]
}
exec: {
options = {
evaluate: true,
loops: true,
unsafe: true,
}
input: {
while (/a/.exec("AAA"))
console.log("FAIL");
console.log("PASS");
}
expect: {
for (;null;)
console.log("FAIL");
console.log("PASS");
}
expect_stdout: "PASS"
}
exec_global: {
options = {
evaluate: true,
loops: true,
unsafe: true,
}
input: {
while (/a/g.exec("AAA"))
console.log("FAIL");
console.log("PASS");
}
expect: {
for (;null;)
console.log("FAIL");
console.log("PASS");
}
expect_stdout: "PASS"
}
test: {
options = {
evaluate: true,
unsafe: true,
}
input: {
while (/a/.test("AAA"))
console.log("FAIL");
console.log("PASS");
}
expect: {
while (false)
console.log("FAIL");
console.log("PASS");
}
expect_stdout: "PASS"
}
test_global: {
options = {
evaluate: true,
unsafe: true,
}
input: {
while (/a/g.test("AAA"))
console.log("FAIL");
console.log("PASS");
}
expect: {
while (false)
console.log("FAIL");
console.log("PASS");
}
expect_stdout: "PASS"
}
var_exec: {
options = {
evaluate: true,
loops: true,
reduce_vars: true,
toplevel: true,
unsafe: true,
}
input: {
var r = /a/;
while (r.exec("AAA"))
console.log("FAIL");
console.log("PASS");
}
expect: {
var r = /a/;
for (;null;)
console.log("FAIL");
console.log("PASS");
}
expect_stdout: "PASS"
}
var_exec_global: {
options = {
evaluate: true,
loops: true,
reduce_vars: true,
toplevel: true,
unsafe: true,
}
input: {
var r = /a/g;
while (r.exec("aaa"))
console.log("PASS");
}
expect: {
var r = /a/g;
for (;r.exec("aaa");)
console.log("PASS");
}
expect_stdout: [
"PASS",
"PASS",
"PASS",
]
}
var_test: {
options = {
evaluate: true,
reduce_vars: true,
toplevel: true,
unsafe: true,
}
input: {
var r = /a/;
while (r.test("AAA"))
console.log("FAIL");
console.log("PASS");
}
expect: {
var r = /a/;
while (false)
console.log("FAIL");
console.log("PASS");
}
expect_stdout: "PASS"
}
var_test_global: {
options = {
evaluate: true,
reduce_vars: true,
toplevel: true,
unsafe: true,
}
input: {
var r = /a/g;
while (r.test("aaa"))
console.log("PASS");
}
expect: {
var r = /a/g;
while (r.test("aaa"))
console.log("PASS");
}
expect_stdout: [
"PASS",
"PASS",
"PASS",
]
}
lazy_boolean: {
options = {
evaluate: true,
passes: 2,
side_effects: true,
unsafe: true,
}
input: {
/b/.exec({}) && console.log("PASS");
/b/.test({}) && console.log("PASS");
/b/g.exec({}) && console.log("PASS");
/b/g.test({}) && console.log("PASS");
}
expect: {
console.log("PASS");
console.log("PASS");
console.log("PASS");
console.log("PASS");
}
expect_stdout: [
"PASS",
"PASS",
"PASS",
"PASS",
]
}
reset_state_between_evaluate: {
options = {
evaluate: true,
passes: 2,
unsafe: true,
}
input: {
console.log(function() {
for (var a in /[abc4]/g.exec("a"))
return "PASS";
return "FAIL";
}());
}
expect: {
console.log(function() {
for (var a in /[abc4]/g.exec("a"))
return "PASS";
return "FAIL";
}());
}
expect_stdout: "PASS"
}

View File

@@ -910,15 +910,23 @@ call: {
console.log(this === b ? "bar" : "baz");
};
(a, b)();
(a, b).c();
(a, b.c)();
(a, b)["c"]();
(a, b["c"])();
(a, function() {
console.log(this === a);
})();
new (a, b)();
new (a, b).c();
new (a, b.c)();
new (a, b)["c"]();
new (a, b["c"])();
new (a, function() {
console.log(this === a);
})();
console.log(typeof (a, b).c);
console.log(typeof (a, b)["c"]);
}
expect: {
var a = function() {
@@ -931,23 +939,39 @@ call: {
console.log(this === b ? "bar" : "baz");
},
b(),
b.c(),
(a, b.c)(),
b["c"](),
(a, b["c"])(),
function() {
console.log(this === a);
}(),
new b(),
new b.c(),
new b.c(),
new b["c"](),
new b["c"](),
new function() {
console.log(this === a);
}();
}(),
console.log((a, typeof b.c)),
console.log((a, typeof b["c"]));
}
expect_stdout: [
"foo",
"bar",
"baz",
"bar",
"baz",
"true",
"foo",
"baz",
"baz",
"baz",
"baz",
"false",
"function",
"function",
]
}
@@ -1069,3 +1093,22 @@ issue_3490_2: {
}
expect_stdout: "PASS 42"
}
issue_3703: {
options = {
evaluate: true,
sequences: true,
unsafe: true,
}
input: {
var a = "FAIL";
while ((a = "PASS", 0).foo = 0);
console.log(a);
}
expect: {
var a = "FAIL";
while (a = "PASS", (0).foo = 0);
console.log(a);
}
expect_stdout: "PASS"
}

View File

@@ -0,0 +1,277 @@
accessor: {
options = {
side_effects: true,
}
input: {
({
get a() {},
set a(v){
this.b = 2;
},
b: 1
});
}
expect: {}
}
issue_2233_1: {
options = {
pure_getters: "strict",
side_effects: true,
unsafe: true,
}
input: {
Array.isArray;
Boolean;
console.log;
Date;
decodeURI;
decodeURIComponent;
encodeURI;
encodeURIComponent;
Error.name;
escape;
eval;
EvalError;
Function.length;
isFinite;
isNaN;
JSON;
Math.random;
Number.isNaN;
parseFloat;
parseInt;
RegExp;
Object.defineProperty;
String.fromCharCode;
RangeError;
ReferenceError;
SyntaxError;
TypeError;
unescape;
URIError;
}
expect: {}
expect_stdout: true
}
global_timeout_and_interval_symbols: {
options = {
pure_getters: "strict",
side_effects: true,
unsafe: true,
}
input: {
// These global symbols do not exist in the test sandbox
// and must be tested separately.
clearInterval;
clearTimeout;
setInterval;
setTimeout;
}
expect: {}
}
issue_2233_2: {
options = {
pure_getters: "strict",
reduce_funcs: true,
reduce_vars: true,
side_effects: true,
unsafe: true,
unused: true,
}
input: {
var RegExp;
Array.isArray;
RegExp;
UndeclaredGlobal;
function foo() {
var Number;
AnotherUndeclaredGlobal;
Math.sin;
Number.isNaN;
}
}
expect: {
var RegExp;
UndeclaredGlobal;
function foo() {
var Number;
AnotherUndeclaredGlobal;
Number.isNaN;
}
}
}
issue_2233_3: {
options = {
pure_getters: "strict",
reduce_funcs: true,
reduce_vars: true,
side_effects: true,
toplevel: true,
unsafe: true,
unused: true,
}
input: {
var RegExp;
Array.isArray;
RegExp;
UndeclaredGlobal;
function foo() {
var Number;
AnotherUndeclaredGlobal;
Math.sin;
Number.isNaN;
}
}
expect: {
UndeclaredGlobal;
}
}
global_fns: {
options = {
side_effects: true,
unsafe: true,
}
input: {
Boolean(1, 2);
decodeURI(1, 2);
decodeURIComponent(1, 2);
Date(1, 2);
encodeURI(1, 2);
encodeURIComponent(1, 2);
Error(1, 2);
escape(1, 2);
EvalError(1, 2);
isFinite(1, 2);
isNaN(1, 2);
Number(1, 2);
Object(1, 2);
parseFloat(1, 2);
parseInt(1, 2);
RangeError(1, 2);
ReferenceError(1, 2);
String(1, 2);
SyntaxError(1, 2);
TypeError(1, 2);
unescape(1, 2);
URIError(1, 2);
try {
Function(1, 2);
} catch (e) {
console.log(e.name);
}
try {
RegExp(1, 2);
} catch (e) {
console.log(e.name);
}
try {
Array(NaN);
} catch (e) {
console.log(e.name);
}
}
expect: {
try {
Function(1, 2);
} catch (e) {
console.log(e.name);
}
try {
RegExp(1, 2);
} catch (e) {
console.log(e.name);
}
try {
Array(NaN);
} catch (e) {
console.log(e.name);
}
}
expect_stdout: [
"SyntaxError",
"SyntaxError",
"RangeError",
]
}
unsafe_builtin_1: {
options = {
side_effects: true,
unsafe: true,
}
input: {
(!w).constructor(x);
Math.abs(y);
[ 1, 2, z ].valueOf();
}
expect: {
w, x;
y;
z;
}
}
unsafe_builtin_2: {
options = {
side_effects: true,
unsafe: true,
}
input: {
var o = {};
constructor.call(o, 42);
__defineGetter__.call(o, "foo", function() {
return o.p;
});
__defineSetter__.call(o, void 0, function(a) {
o.p = a;
});
console.log(typeof o, o.undefined = "PASS", o.foo);
}
expect: {
var o = {};
constructor.call(o, 42);
__defineGetter__.call(o, "foo", function() {
return o.p;
});
__defineSetter__.call(o, void 0, function(a) {
o.p = a;
});
console.log(typeof o, o.undefined = "PASS", o.foo);
}
expect_stdout: "object PASS PASS"
}
unsafe_string_replace: {
options = {
side_effects: true,
unsafe: true,
}
input: {
"foo".replace("f", function() {
console.log("PASS");
});
}
expect: {
"foo".replace("f", function() {
console.log("PASS");
});
}
expect_stdout: "PASS"
}
drop_value: {
options = {
side_effects: true,
}
input: {
(1, [2, foo()], 3, {a:1, b:bar()});
}
expect: {
foo(), bar();
}
}

View File

@@ -103,6 +103,7 @@ if_return: {
booleans: true,
conditionals: true,
if_return: true,
passes: 2,
sequences: true,
side_effects: true,
}

View File

@@ -16,6 +16,81 @@ unicode_parse_variables: {
}
}
unicode_escaped_identifier: {
input: {
var \u0061 = "\ud800\udc00";
console.log(a);
}
expect_exact: 'var a="\ud800\udc00";console.log(a);'
expect_stdout: "\ud800\udc00"
}
unicode_identifier_ascii_only: {
beautify = {
ascii_only: true,
}
input: {
var \u0061 = "testing \udbc4\udd11";
var bar = "h\u0065llo";
console.log(a, \u0062\u0061r);
}
expect_exact: 'var a="testing \\udbc4\\udd11";var bar="hello";console.log(a,bar);'
expect_stdout: "testing \udbc4\udd11 hello"
}
unicode_string_literals: {
beautify = {
ascii_only: true,
}
input: {
var a = "6 length unicode character: \udbc4\udd11";
console.log(\u0061);
}
expect_exact: 'var a="6 length unicode character: \\udbc4\\udd11";console.log(a);'
expect_stdout: "6 length unicode character: \udbc4\udd11"
}
check_escape_style: {
beautify = {
ascii_only: true,
}
input: {
var a = "\x01";
var \ua0081 = "\x10"; // \u0081 only in ID_Continue
var \u0100 = "\u0100";
var \u1000 = "\u1000";
var \u1000 = "\ud800\udc00";
var \u3f80 = "\udbc0\udc00";
console.log(\u0061, \ua0081, \u0100, \u1000, \u3f80);
}
expect_exact: 'var a="\\x01";var \\ua0081="\\x10";var \\u0100="\\u0100";var \\u1000="\\u1000";var \\u1000="\\ud800\\udc00";var \\u3f80="\\udbc0\\udc00";console.log(a,\\ua0081,\\u0100,\\u1000,\\u3f80);'
expect_stdout: "\u0001 \u0010 \u0100 \ud800\udc00 \udbc0\udc00"
}
escape_non_escaped_identifier: {
beautify = {
ascii_only: true,
}
input: {
var µþ = "µþ";
console.log(\u00b5þ);
}
expect_exact: 'var \\u00b5\\u00fe="\\xb5\\xfe";console.log(\\u00b5\\u00fe);'
expect_stdout: "µþ"
}
non_escape_2_non_escape: {
beautify = {
ascii_only: false,
}
input: {
var µþ = "µþ";
console.log(\u00b5þ);
}
expect_exact: 'var µþ="µþ";console.log(µþ);'
expect_stdout: "µþ"
}
issue_2242_1: {
beautify = {
ascii_only: false,
@@ -24,6 +99,7 @@ issue_2242_1: {
console.log("\ud83d", "\ude00", "\ud83d\ude00", "\ud83d@\ude00");
}
expect_exact: 'console.log("\\ud83d","\\ude00","\ud83d\ude00","\\ud83d@\\ude00");'
expect_stdout: "\ud83d \ude00 \ud83d\ude00 \ud83d@\ude00"
}
issue_2242_2: {
@@ -34,6 +110,7 @@ issue_2242_2: {
console.log("\ud83d", "\ude00", "\ud83d\ude00", "\ud83d@\ude00");
}
expect_exact: 'console.log("\\ud83d","\\ude00","\\ud83d\\ude00","\\ud83d@\\ude00");'
expect_stdout: "\ud83d \ude00 \ud83d\ude00 \ud83d@\ude00"
}
issue_2242_3: {
@@ -44,6 +121,7 @@ issue_2242_3: {
console.log("\ud83d" + "\ude00", "\ud83d" + "@" + "\ude00");
}
expect_exact: 'console.log("\\ud83d"+"\\ude00","\\ud83d"+"@"+"\\ude00");'
expect_stdout: "\ud83d\ude00 \ud83d@\ude00"
}
issue_2242_4: {
@@ -54,6 +132,7 @@ issue_2242_4: {
console.log("\ud83d" + "\ude00", "\ud83d" + "@" + "\ude00");
}
expect_exact: 'console.log("\ud83d\ude00","\\ud83d@\\ude00");'
expect_stdout: "\ud83d\ude00 \ud83d@\ude00"
}
issue_2569: {

View File

@@ -1,6 +1,7 @@
exports["Compressor"] = Compressor;
exports["defaults"] = defaults;
exports["JS_Parse_Error"] = JS_Parse_Error;
exports["List"] = List;
exports["mangle_properties"] = mangle_properties;
exports["minify"] = minify;
exports["OutputStream"] = OutputStream;

View File

@@ -0,0 +1,17 @@
if (x) foo();
if (x) foo(); else baz();
if (x) foo(); else if (y) bar(); else baz();
if (x) if (y) foo(); else bar(); else baz();
if (x) foo(); else if (y) bar(); else if (z) baz(); else moo();
function f() {
if (x) foo();
if (x) foo(); else baz();
if (x) foo(); else if (y) bar(); else baz();
if (x) if (y) foo(); else bar(); else baz();
if (x) foo(); else if (y) bar(); else if (z) baz(); else moo();
}

View File

@@ -1,17 +1 @@
if (x) foo();
if (x) foo(); else baz();
if (x) foo(); else if (y) bar(); else baz();
if (x) if (y) foo(); else bar(); else baz();
if (x) foo(); else if (y) bar(); else if (z) baz(); else moo();
function f() {
if (x) foo();
if (x) foo(); else baz();
if (x) foo(); else if (y) bar(); else baz();
if (x) if (y) foo(); else bar(); else baz();
if (x) foo(); else if (y) bar(); else if (z) baz(); else moo();
}
if(x)foo();if(x)foo();else baz();if(x)foo();else if(y)bar();else baz();if(x)if(y)foo();else bar();else baz();if(x)foo();else if(y)bar();else if(z)baz();else moo();function f(){if(x)foo();if(x)foo();else baz();if(x)foo();else if(y)bar();else baz();if(x)if(y)foo();else bar();else baz();if(x)foo();else if(y)bar();else if(z)baz();else moo()}

View File

@@ -0,0 +1,9 @@
var o = this;
for (var k in o) L17060: {
a++;
}
var a;
console.log(k);

View File

@@ -0,0 +1,15 @@
// (beautified)
var o = this;
for (var k in o) {}
var a;
console.log(k);
// output: a
//
// minify: k
//
// options: {
// "mangle": false
// }

View File

@@ -0,0 +1,8 @@
console.log(function f(a) {
({
set p(v) {
f++;
}
});
return f.length;
}());

View File

@@ -0,0 +1,20 @@
// (beautified)
console.log(function f(a) {
({
set p(v) {
f++;
}
});
return f.length;
}());
// output: 1
//
// minify: 0
//
// options: {
// "compress": {
// "keep_fargs": false,
// "unsafe": true
// },
// "mangle": false
// }

View File

@@ -0,0 +1,18 @@
var _calls_ = 10, a = 100, b = 10, c = 0;
function f0(b_1, a, undefined_2) {
a++ + ++b;
{
var expr2 = (b + 1 - .1 - .1 - .1 || a || 3).toString();
L20778: for (var key2 in expr2) {
(c = c + 1) + [ --b + b_1, typeof f0 == "function" && --_calls_ >= 0 && f0(--b + typeof (undefined_2 = 1 === 1 ? a : b), --b + {
c: (c = c + 1) + null
}, a++ + (typeof f0 == "function" && --_calls_ >= 0 && f0(typeof (c = 1 + c, 3 / "a" * ("c" >>> 23..toString()) >= (b_1 && (b_1[(c = c + 1) + a--] = (- -0,
true + {})))), 3, 25))), 1 === 1 ? a : b ];
}
}
}
var a_1 = f0([ , 0 ].length === 2);
console.log(null, a, b, c, Infinity, NaN, undefined);

View File

@@ -0,0 +1,20 @@
// (beautified)
var b = 0;
var expr2 = (0 - 1 - .1 - .1).toString();
for (var key2 in expr2) {
--b;
}
console.log(b);
// output: -19
//
// minify: -4
//
// options: {
// "compress": {
// "unsafe_math": true
// },
// "mangle": false
// }

View File

@@ -1,7 +1,7 @@
var fs = require("fs");
var config = {
limit: 5000,
limit: 10000,
timeout: function(limit) {
this.limit = limit;
}
@@ -55,11 +55,11 @@ process.nextTick(function run() {
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");
}
reset();
log_titles(console.log, task.titles, green('\u221A '));
process.nextTick(run);
};

View File

@@ -176,7 +176,7 @@ describe("bin/uglifyjs", function() {
var command = uglifyjscmd + ' test/input/issue-1482/input.js -b';
exec(command, function(err, stdout) {
if (err) throw err;
assert.strictEqual(stdout, read("test/input/issue-1482/default.js"));
assert.strictEqual(stdout, read("test/input/issue-1482/beautify.js"));
done();
});
});
@@ -188,6 +188,22 @@ describe("bin/uglifyjs", function() {
done();
});
});
it("Should work with `--output-opts`", function(done) {
var command = uglifyjscmd + ' test/input/issue-1482/input.js -O';
exec(command, function(err, stdout) {
if (err) throw err;
assert.strictEqual(stdout, read("test/input/issue-1482/default.js"));
done();
});
});
it("Should fail when both --beautify & --output-opts are specified", function(done) {
var command = uglifyjscmd + " test/input/issue-520/input.js -bO";
exec(command, function(err, stdout, stderr) {
assert.ok(err);
assert.strictEqual(stderr, "ERROR: --beautify cannot be used with --output-opts\n");
done();
});
});
it("Should process inline source map", function(done) {
var command = [
uglifyjscmd,

View File

@@ -51,7 +51,7 @@ describe("minify", function() {
"var a=n(3),b=r(12);",
'c("qux",a,b),o(11);',
].join(""));
assert.strictEqual(run_code(compressed), run_code(original));
assert.strictEqual(run_code(compressed, true), run_code(original, true));
});
it("Should work with nameCache", function() {
@@ -84,7 +84,7 @@ describe("minify", function() {
"var a=n(3),b=r(12);",
'c("qux",a,b),o(11);',
].join(""));
assert.strictEqual(run_code(compressed), run_code(original));
assert.strictEqual(run_code(compressed, true), run_code(original, true));
});
it("Should avoid cached names when mangling top-level variables", function() {
@@ -113,7 +113,7 @@ describe("minify", function() {
'"xxyyy";var y={y:2,a:3},a=4;',
'console.log(x.x,y.y,y.a,a);',
].join(""));
assert.strictEqual(run_code(compressed), run_code(original));
assert.strictEqual(run_code(compressed, true), run_code(original, true));
});
it("Should avoid cached names when mangling inner-scoped variables", function() {
@@ -137,7 +137,7 @@ describe("minify", function() {
'var o=function(o,n){console.log("extend");o();n()};function n(){console.log("A")}',
'var e=function(n){function e(){console.log("B")}o(e,n);return e}(n);',
].join(""));
assert.strictEqual(run_code(compressed), run_code(original));
assert.strictEqual(run_code(compressed, true), run_code(original, true));
});
it("Should not parse invalid use of reserved words", function() {

249
test/mocha/reduce.js Normal file
View File

@@ -0,0 +1,249 @@
var assert = require("assert");
var exec = require("child_process").exec;
var fs = require("fs");
var reduce_test = require("../reduce");
var semver = require("semver");
function read(path) {
return fs.readFileSync(path, "utf8");
}
describe("test/reduce.js", function() {
this.timeout(60000);
it("Should reduce test case", function() {
var result = reduce_test(read("test/input/reduce/unsafe_math.js"), {
compress: {
unsafe_math: true,
},
mangle: false,
}, {
verbose: false,
});
if (result.error) throw result.error;
assert.strictEqual(result.code, read("test/input/reduce/unsafe_math.reduced.js"));
});
it("Should eliminate unreferenced labels", function() {
var result = reduce_test(read("test/input/reduce/label.js"), {
mangle: false,
}, {
verbose: false,
});
if (result.error) throw result.error;
assert.strictEqual(result.code, read("test/input/reduce/label.reduced.js"));
});
it("Should retain setter arguments", function() {
var result = reduce_test(read("test/input/reduce/setter.js"), {
compress: {
keep_fargs: false,
unsafe: true,
},
mangle: false,
}, {
verbose: false,
});
if (result.error) throw result.error;
assert.strictEqual(result.code, read("test/input/reduce/setter.reduced.js"));
});
it("Should handle test cases with --toplevel", function() {
var result = reduce_test([
"var Infinity = 42;",
"console.log(Infinity);",
].join("\n"), {
toplevel: true,
});
if (result.error) throw result.error;
assert.strictEqual(result.code, [
"// Can't reproduce test failure",
"// minify options: {",
'// "toplevel": true',
"// }",
].join("\n"));
});
it("Should handle test cases with --compress toplevel", function() {
var result = reduce_test([
"var NaN = 42;",
"console.log(NaN);",
].join("\n"), {
compress: {
toplevel: true,
},
});
if (result.error) throw result.error;
assert.strictEqual(result.code, [
"// Can't reproduce test failure",
"// minify options: {",
'// "compress": {',
'// "toplevel": true',
"// }",
"// }",
].join("\n"));
});
it("Should handle test cases with --mangle toplevel", function() {
var result = reduce_test([
"var undefined = 42;",
"console.log(undefined);",
].join("\n"), {
mangle: {
toplevel: true,
},
});
if (result.error) throw result.error;
assert.strictEqual(result.code, [
"// Can't reproduce test failure",
"// minify options: {",
'// "mangle": {',
'// "toplevel": true',
"// }",
"// }",
].join("\n"));
});
it("Should handle test result of NaN", function() {
var result = reduce_test("throw 0 / 0;");
if (result.error) throw result.error;
assert.strictEqual(result.code, [
"// Can't reproduce test failure",
"// minify options: {}",
].join("\n"));
});
it("Should print correct output for irreducible test case", function() {
var result = reduce_test([
"console.log(function f(a) {",
" return f.length;",
"}());",
].join("\n"), {
compress: {
keep_fargs: false,
},
mangle: false,
});
if (result.error) throw result.error;
assert.strictEqual(result.code, [
"// (beautified)",
"console.log(function f(a) {",
" return f.length;",
"}());",
"// output: 1",
"// ",
"// minify: 0",
"// ",
"// options: {",
'// "compress": {',
'// "keep_fargs": false',
"// },",
'// "mangle": false',
"// }",
].join("\n"));
});
it("Should fail when invalid option is supplied", function() {
var result = reduce_test("", {
compress: {
unsafe_regex: true,
},
});
var err = result.error;
assert.ok(err instanceof Error);
assert.strictEqual(err.stack.split(/\n/)[0], "DefaultsError: `unsafe_regex` is not a supported option");
});
it("Should report on test case with invalid syntax", function() {
var result = reduce_test("var 0 = 1;");
var err = result.error;
assert.ok(err instanceof Error);
assert.strictEqual(err.stack.split(/\n/)[0], "SyntaxError: Name expected");
});
it("Should format multi-line output correctly", function() {
var code = [
"var a = 0;",
"",
"for (var b in [ 1, 2, 3 ]) {",
" a = +a + 1 - .2;",
" console.log(a);",
"}",
].join("\n");
var result = reduce_test(code, {
compress: {
unsafe_math: true,
},
mangle: false,
});
if (result.error) throw result.error;
assert.strictEqual(result.code, [
"// (beautified)",
code,
"// output: 0.8",
"// 1.6",
"// 2.4",
"// ",
"// minify: 0.8",
"// 1.6",
"// 2.4000000000000004",
"// ",
"// options: {",
'// "compress": {',
'// "unsafe_math": true',
"// },",
'// "mangle": false',
"// }",
].join("\n"));
});
it("Should reduce infinite loops with reasonable performance", function() {
if (semver.satisfies(process.version, "0.10")) return;
this.timeout(120000);
var result = reduce_test("while (/9/.test(1 - .8));", {
compress: {
unsafe_math: true,
},
mangle: false,
});
if (result.error) throw result.error;
assert.strictEqual(result.code.replace(/ timed out after [0-9]+ms/, " timed out."), [
"// (beautified)",
"while (/9/.test(1 - .8)) {}",
"// output: Error: Script execution timed out.",
"// minify: ",
"// options: {",
'// "compress": {',
'// "unsafe_math": true',
"// },",
'// "mangle": false',
"// }",
].join("\n"));
});
it("Should ignore difference in Error.message", function() {
var result = reduce_test("null[function() {\n}];");
if (result.error) throw result.error;
assert.strictEqual(result.code, (semver.satisfies(process.version, "0.10") ? [
"// Can't reproduce test failure",
"// minify options: {}",
] : [
"// No differences except in error message",
"// minify options: {}",
]).join("\n"));
});
it("Should report trailing whitespace difference in stringified format", function() {
var code = [
"for (var a in (1 - .8).toString()) {",
" console.log();",
"}",
].join("\n");
var result = reduce_test(code, {
compress: {
unsafe_math: true,
},
mangle: false,
});
if (result.error) throw result.error;
assert.strictEqual(result.code, [
"// (beautified)",
code,
"// (stringified)",
'// output: "\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n"',
'// minify: "\\n\\n\\n"',
"// options: {",
'// "compress": {',
'// "unsafe_math": true',
'// },',
'// "mangle": false',
"// }",
].join("\n"));
});
});

View File

@@ -89,6 +89,22 @@ describe("sourcemaps", function() {
assert.strictEqual(result.code, code);
assert.strictEqual(result.map, '{"version":3,"sources":["0"],"names":["console","log"],"mappings":"AAAAA,QAAQC,IAAI","sourceRoot":"//foo.bar/"}');
});
it("Should produce same source map with DOS or UNIX line endings", function() {
var code = [
'console.log("\\',
'hello",',
'"world");',
];
var dos = UglifyJS.minify(code.join("\r\n"), {
sourceMap: true,
});
if (dos.error) throw dos.error;
var unix = UglifyJS.minify(code.join("\n"), {
sourceMap: true,
});
if (unix.error) throw unix.error;
assert.strictEqual(dos.map, unix.map);
});
describe("inSourceMap", function() {
it("Should read the given string filename correctly when sourceMapIncludeSources is enabled", function() {

View File

@@ -44,30 +44,37 @@ function test(original, estree, description) {
try_beautify(transformed.code);
}
console.log("!!!!!! Failed... round", round);
process.exit(1);
return false;
}
return true;
}
var num_iterations = ufuzz.num_iterations;
var minify_options = require("./ufuzz/options.json").map(JSON.stringify);
minify_options.unshift(null);
for (var round = 1; round <= num_iterations; round++) {
process.stdout.write(round + " of " + num_iterations + "\r");
var code = ufuzz.createTopLevelCode();
var uglified = UglifyJS.minify(code, {
compress: false,
mangle: false,
output: {
ast: true
minify_options.forEach(function(options) {
var input = options ? UglifyJS.minify(code, JSON.parse(options)).code : code;
var uglified = UglifyJS.minify(input, {
compress: false,
mangle: false,
output: {
ast: true
}
});
var ok = test(uglified.code, uglified.ast.to_mozilla_ast(), "AST_Node.to_mozilla_ast()");
try {
ok = test(uglified.code, acorn.parse(input), "acorn.parse()") && ok;
} catch (e) {
console.log("//=============================================================");
console.log("// acorn parser failed... round", round);
console.log(e);
console.log("// original code");
console.log(input);
}
if (!ok) process.exit(1);
});
test(uglified.code, uglified.ast.to_mozilla_ast(), "AST_Node.to_mozilla_ast()");
try {
test(uglified.code, acorn.parse(code), "acorn.parse()");
} catch (e) {
console.log("//=============================================================");
console.log("// acorn parser failed... round", round);
console.log(e);
console.log("// original code");
console.log(code);
}
}
console.log();

622
test/reduce.js Normal file
View File

@@ -0,0 +1,622 @@
var crypto = require("crypto");
var U = require("./node");
var List = U.List;
var os = require("os");
var sandbox = require("./sandbox");
// Reduce a ufuzz-style `console.log` based test case by iteratively replacing
// AST nodes with various permutations. Each AST_Statement in the tree is also
// speculatively dropped to determine whether it is needed. If the altered
// tree and the last known good tree produce the same non-nil error-free output
// after being run, then the permutation survives to the next generation and
// is the basis for subsequent iterations. The test case is reduced as a
// consequence of complex expressions being replaced with simpler ones.
// This function assumes that the testcase will not result in a parse or
// runtime Error. Note that a reduced test case will have different runtime
// output - it is not functionally equivalent to the original. The only criteria
// is that once the generated reduced test case is run without minification, it
// will produce different output from the code minified with `minify_options`.
// Returns a `minify` result object with an additonal boolean property `reduced`.
module.exports = function reduce_test(testcase, minify_options, reduce_options) {
if (testcase instanceof U.AST_Node) testcase = testcase.print_to_string();
minify_options = minify_options || {};
reduce_options = reduce_options || {};
var max_iterations = reduce_options.max_iterations || 1000;
var max_timeout = reduce_options.max_timeout || 10000;
var verbose = reduce_options.verbose;
var minify_options_json = JSON.stringify(minify_options, null, 2);
var result_cache = Object.create(null);
// the initial timeout to assess the viability of the test case must be large
var differs = producesDifferentResultWhenMinified(result_cache, testcase, minify_options, max_timeout);
if (verbose) {
console.error("// Node.js " + process.version + " on " + os.platform() + " " + os.arch());
}
if (!differs) {
// same stdout result produced when minified
return {
code: [
"// Can't reproduce test failure",
"// minify options: " + to_comment(minify_options_json)
].join("\n")
};
} else if (differs.timed_out) {
return {
code: [
"// Can't reproduce test failure within " + max_timeout + "ms",
"// minify options: " + to_comment(minify_options_json)
].join("\n")
};
} else if (differs.error) {
return differs;
} else if (is_error(differs.unminified_result)
&& is_error(differs.minified_result)
&& differs.unminified_result.name == differs.minified_result.name) {
return {
code: [
"// No differences except in error message",
"// minify options: " + to_comment(minify_options_json)
].join("\n")
};
} else {
max_timeout = Math.min(100 * differs.elapsed, max_timeout);
// Replace expressions with constants that will be parsed into
// AST_Nodes as required. Each AST_Node has its own permutation count,
// so these replacements can't be shared.
// Although simpler replacements are generally faster and better,
// feel free to experiment with a different replacement set.
var REPLACEMENTS = [
// "null", "''", "false", "'foo'", "undefined", "9",
"1", "0",
];
// There's a relationship between each node's _permute counter and
// REPLACEMENTS.length which is why fractional _permutes were needed.
// One could scale all _permute operations by a factor of `steps`
// to only deal with integer operations, but this works well enough.
var steps = 4; // must be a power of 2
var step = 1 / steps; // 0.25 is exactly representable in floating point
var tt = new U.TreeTransformer(function(node, descend, in_list) {
if (CHANGED) return;
// quick ignores
if (node instanceof U.AST_Accessor) return;
if (node instanceof U.AST_Directive) return;
if (!in_list && node instanceof U.AST_EmptyStatement) return;
if (node instanceof U.AST_Label) return;
if (node instanceof U.AST_LabelRef) return;
if (!in_list && node instanceof U.AST_SymbolDeclaration) return;
if (node instanceof U.AST_Toplevel) return;
var parent = tt.parent();
if (node instanceof U.AST_SymbolFunarg && parent instanceof U.AST_Accessor) return;
// ensure that the _permute prop is a number.
// can not use `node.start._permute |= 0;` as it will erase fractional part.
if (typeof node.start._permute === "undefined") node.start._permute = 0;
// if node reached permutation limit - skip over it.
// no structural AST changes before this point.
if (node.start._permute >= REPLACEMENTS.length) return;
if (parent instanceof U.AST_Assign
&& parent.left === node
|| parent instanceof U.AST_Unary
&& parent.expression === node
&& ["++", "--", "delete"].indexOf(parent.operator) >= 0) {
// ignore lvalues
return;
}
if ((parent instanceof U.AST_For || parent instanceof U.AST_ForIn)
&& parent.init === node && node instanceof U.AST_Var) {
// preserve for (var ...)
return node;
}
// node specific permutations with no parent logic
if (node instanceof U.AST_Array) {
var expr = node.elements[0];
if (expr && !(expr instanceof U.AST_Hole)) {
node.start._permute++;
CHANGED = true;
return expr;
}
}
else if (node instanceof U.AST_Binary) {
CHANGED = true;
var permute = ((node.start._permute += step) * steps | 0) % 4;
var expr = [
node.left,
node.right,
][ permute & 1 ];
if (permute < 2) return expr;
// wrap with console.log()
return new U.AST_Call({
expression: new U.AST_Dot({
expression: new U.AST_SymbolRef({
name: "console",
start: {},
}),
property: "log",
start: {},
}),
args: [ expr ],
start: {},
});
}
else if (node instanceof U.AST_Catch || node instanceof U.AST_Finally) {
// drop catch or finally block
node.start._permute++;
CHANGED = true;
return null;
}
else if (node instanceof U.AST_Conditional) {
CHANGED = true;
return [
node.condition,
node.consequent,
node.alternative,
][ ((node.start._permute += step) * steps | 0) % 3 ];
}
else if (node instanceof U.AST_BlockStatement) {
if (in_list) {
node.start._permute++;
CHANGED = true;
return List.splice(node.body);
}
}
else if (node instanceof U.AST_Call) {
var expr = [
node.expression,
node.args[0],
null, // intentional
][ ((node.start._permute += step) * steps | 0) % 3 ];
if (expr) {
CHANGED = true;
return expr;
}
if (node.expression instanceof U.AST_Function) {
// hoist and return expressions from the IIFE function expression
var body = node.expression.body;
node.expression.body = [];
var seq = [];
body.forEach(function(node) {
var expr = expr instanceof U.AST_Exit ? node.value : node.body;
if (expr instanceof U.AST_Node && !is_statement(expr)) {
// collect expressions from each statements' body
seq.push(expr);
}
});
CHANGED = true;
return to_sequence(seq);
}
}
else if (node instanceof U.AST_Defun) {
switch (((node.start._permute += step) * steps | 0) % 2) {
case 0:
CHANGED = true;
return List.skip;
case 1:
if (!has_exit(node)) {
// hoist function declaration body
var body = node.body;
node.body = [];
body.push(node); // retain function with empty body to be dropped later
CHANGED = true;
return List.splice(body);
}
}
}
else if (node instanceof U.AST_DWLoop) {
var expr = [
node.condition,
node.body,
null, // intentional
][ (node.start._permute * steps | 0) % 3 ];
node.start._permute += step;
if (!expr) {
if (node.body[0] instanceof U.AST_Break) {
if (node instanceof U.AST_Do) {
CHANGED = true;
return List.skip;
}
expr = node.condition; // AST_While - fall through
}
}
if (expr && (expr !== node.body || !has_loopcontrol(expr, node, parent))) {
CHANGED = true;
return to_statement(expr);
}
}
else if (node instanceof U.AST_PropAccess) {
var expr = [
node.expression,
node.property instanceof U.AST_Node && node.property,
][ node.start._permute++ % 2 ];
if (expr) {
CHANGED = true;
return expr;
}
}
else if (node instanceof U.AST_For) {
var expr = [
node.init,
node.condition,
node.step,
node.body,
][ (node.start._permute * steps | 0) % 4 ];
node.start._permute += step;
if (expr && (expr !== node.body || !has_loopcontrol(expr, node, parent))) {
CHANGED = true;
return to_statement(expr);
}
}
else if (node instanceof U.AST_ForIn) {
var expr = [
node.init,
node.object,
node.body,
][ (node.start._permute * steps | 0) % 3 ];
node.start._permute += step;
if (expr && (expr !== node.body || !has_loopcontrol(expr, node, parent))) {
CHANGED = true;
return to_statement(expr);
}
}
else if (node instanceof U.AST_If) {
var expr = [
node.condition,
node.body,
node.alternative,
][ (node.start._permute * steps | 0) % 3 ];
node.start._permute += step;
if (expr) {
// replace if statement with its condition, then block or else block
CHANGED = true;
return to_statement(expr);
}
}
else if (node instanceof U.AST_Object) {
// first property's value
var expr = node.properties[0] instanceof U.AST_ObjectKeyVal && node.properties[0].value;
if (expr) {
node.start._permute++;
CHANGED = true;
return expr;
}
}
else if (node instanceof U.AST_SimpleStatement) {
if (node.body instanceof U.AST_Call && node.body.expression instanceof U.AST_Function) {
// hoist simple statement IIFE function expression body
node.start._permute++;
if (!has_exit(node.body.expression)) {
var body = node.body.expression.body;
node.body.expression.body = [];
CHANGED = true;
return List.splice(body);
}
}
}
else if (node instanceof U.AST_Switch) {
var expr = [
node.expression, // switch expression
node.body[0] && node.body[0].expression, // first case expression or undefined
node.body[0] && node.body[0], // first case body or undefined
][ (node.start._permute * steps | 0) % 4 ];
node.start._permute += step;
if (expr && (!(expr instanceof U.AST_Statement) || !has_loopcontrol(expr, node, parent))) {
CHANGED = true;
return expr instanceof U.AST_SwitchBranch ? new U.AST_BlockStatement({
body: expr.body.slice(),
start: {},
}) : to_statement(expr);
}
}
else if (node instanceof U.AST_Try) {
var body = [
node.body,
node.bcatch && node.bcatch.body,
node.bfinally && node.bfinally.body,
null, // intentional
][ (node.start._permute * steps | 0) % 4 ];
node.start._permute += step;
if (body) {
// replace try statement with try block, catch block, or finally block
CHANGED = true;
return new U.AST_BlockStatement({
body: body,
start: {},
});
} else {
// replace try with a break or return if first in try statement
if (node.body[0] instanceof U.AST_Break
|| node.body[0] instanceof U.AST_Return) {
CHANGED = true;
return node.body[0];
}
}
}
else if (node instanceof U.AST_Unary) {
node.start._permute++;
CHANGED = true;
return node.expression;
}
else if (node instanceof U.AST_Var) {
if (node.definitions.length == 1 && node.definitions[0].value) {
// first declaration value
node.start._permute++;
CHANGED = true;
return to_statement(node.definitions[0].value);
}
}
else if (node instanceof U.AST_LabeledStatement) {
if (node.body instanceof U.AST_Statement
&& !has_loopcontrol(node.body, node.body, node)) {
// replace labelled statement with its non-labelled body
node.start._permute = REPLACEMENTS.length;
CHANGED = true;
return node.body;
}
}
if (in_list) {
// special case to drop object properties and switch branches
if (parent instanceof U.AST_Object
|| parent instanceof U.AST_Switch && parent.expression != node) {
node.start._permute++;
CHANGED = true;
return List.skip;
}
// replace or skip statement
if (node instanceof U.AST_Statement) {
node.start._permute++;
CHANGED = true;
return List.skip;
}
// remove this node unless its the sole element of a (transient) sequence
if (!(parent instanceof U.AST_Sequence) || parent.expressions.length > 1) {
node.start._permute++;
CHANGED = true;
return List.skip;
}
}
// replace this node
var newNode = is_statement(node) ? new U.AST_EmptyStatement({
start: {},
}) : U.parse(REPLACEMENTS[node.start._permute % REPLACEMENTS.length | 0], {
expression: true,
});
newNode.start._permute = ++node.start._permute;
CHANGED = true;
return newNode;
}, function(node, in_list) {
if (node instanceof U.AST_Sequence) {
// expand single-element sequence
if (node.expressions.length == 1) return node.expressions[0];
}
else if (node instanceof U.AST_Try) {
// expand orphaned try block
if (!node.bcatch && !node.bfinally) return new U.AST_BlockStatement({
body: node.body,
start: {},
});
}
else if (node instanceof U.AST_Var) {
// remove empty var statement
if (node.definitions.length == 0) return in_list ? List.skip : new U.AST_EmptyStatement({
start: {},
});
}
});
for (var pass = 1; pass <= 3; ++pass) {
var testcase_ast = U.parse(testcase);
testcase_ast.walk(new U.TreeWalker(function(node) {
// unshare start props to retain visit data between iterations
node.start = JSON.parse(JSON.stringify(node.start));
node.start._permute = 0;
}));
for (var c = 0; c < max_iterations; ++c) {
if (verbose) {
if (pass == 1 && c % 25 == 0) {
console.error("// reduce test pass "
+ pass + ", iteration " + c + ": " + testcase.length + " bytes");
}
}
var CHANGED = false;
var code_ast = testcase_ast.clone(true).transform(tt);
if (!CHANGED) break;
try {
var code = code_ast.print_to_string();
} catch (ex) {
// AST is not well formed.
// no harm done - just log the error, ignore latest change and continue iterating.
console.error("*** Error generating code from AST.");
console.error(ex);
console.error("*** Discarding permutation and continuing.");
continue;
}
var diff = producesDifferentResultWhenMinified(result_cache, code, minify_options, max_timeout);
if (diff) {
if (diff.timed_out) {
// can't trust the validity of `code_ast` and `code` when timed out.
// no harm done - just ignore latest change and continue iterating.
} else if (diff.error) {
// something went wrong during minify() - could be malformed AST or genuine bug.
// no harm done - just log code & error, ignore latest change and continue iterating.
console.error("*** Error during minification.");
console.error(code);
console.error(diff.error);
console.error("*** Discarding permutation and continuing.");
} else if (is_error(diff.unminified_result)
&& is_error(diff.minified_result)
&& diff.unminified_result.name == diff.minified_result.name) {
// ignore difference in error messages caused by minification
} else {
// latest permutation is valid, so use it as the basis of new changes
testcase_ast = code_ast;
testcase = code;
differs = diff;
}
}
}
if (c == 0) break;
if (verbose) {
console.error("// reduce test pass " + pass + ": " + testcase.length + " bytes");
}
}
testcase = try_beautify(result_cache, testcase, minify_options, differs.unminified_result, max_timeout);
var lines = [ "" ];
var unminified_result = strip_color_codes(differs.unminified_result);
var minified_result = strip_color_codes(differs.minified_result);
if (trim_trailing_whitespace(unminified_result) == trim_trailing_whitespace(minified_result)) {
lines.push(
"// (stringified)",
"// output: " + JSON.stringify(unminified_result),
"// minify: " + JSON.stringify(minified_result)
);
} else {
lines.push(
"// output: " + to_comment(unminified_result),
"// minify: " + to_comment(minified_result)
);
}
lines.push("// options: " + to_comment(minify_options_json));
testcase.code += lines.join("\n");
return testcase;
}
};
function strip_color_codes(value) {
return ("" + value).replace(/\u001b\[\d+m/g, "");
}
function to_comment(value) {
return ("" + value).replace(/\n/g, "\n// ");
}
function trim_trailing_whitespace(value) {
return ("" + value).replace(/\s+$/, "");
}
function try_beautify(result_cache, testcase, minify_options, expected, timeout) {
var result = U.minify(testcase, {
compress: false,
mangle: false,
output: {
beautify: true,
braces: true,
comments: true,
},
});
if (result.error) return {
code: testcase,
};
var toplevel = sandbox.has_toplevel(minify_options);
var actual = run_code(result_cache, result.code, toplevel, timeout);
if (!sandbox.same_stdout(expected, actual)) return {
code: testcase,
};
result.code = "// (beautified)\n" + result.code;
return result;
}
function has_exit(fn) {
var found = false;
var tw = new U.TreeWalker(function(node) {
if (found) return found;
if (node instanceof U.AST_Exit) {
return found = true;
}
if (node instanceof U.AST_Scope && node !== fn) {
return true; // don't descend into nested functions
}
});
fn.walk(tw);
return found;
}
function has_loopcontrol(body, loop, label) {
var found = false;
var tw = new U.TreeWalker(function(node) {
if (found) return true;
if (node instanceof U.AST_LoopControl && this.loopcontrol_target(node) === loop) {
return found = true;
}
});
if (label instanceof U.AST_LabeledStatement) tw.push(label);
tw.push(loop);
body.walk(tw);
return found;
}
function is_error(result) {
return typeof result == "object" && typeof result.name == "string" && typeof result.message == "string";
}
function is_timed_out(result) {
return is_error(result) && /timed out/.test(result);
}
function is_statement(node) {
return node instanceof U.AST_Statement && !(node instanceof U.AST_Function);
}
function merge_sequence(array, node) {
if (node instanceof U.AST_Sequence) {
array.push.apply(array, node.expressions);
} else {
array.push(node);
}
return array;
}
function to_sequence(expressions) {
if (expressions.length == 0) return new U.AST_Number({value: 0, start: {}});
if (expressions.length == 1) return expressions[0];
return new U.AST_Sequence({
expressions: expressions.reduce(merge_sequence, []),
start: {},
});
}
function to_statement(node) {
return is_statement(node) ? node : new U.AST_SimpleStatement({
body: node,
start: {},
});
}
function run_code(result_cache, code, toplevel, timeout) {
var key = crypto.createHash("sha1").update(code).digest("base64");
return result_cache[key] || (result_cache[key] = sandbox.run_code(code, toplevel, timeout));
}
function producesDifferentResultWhenMinified(result_cache, code, minify_options, max_timeout) {
var minified = U.minify(code, minify_options);
if (minified.error) return minified;
var toplevel = sandbox.has_toplevel(minify_options);
var elapsed = Date.now();
var unminified_result = run_code(result_cache, code, toplevel, max_timeout);
elapsed = Date.now() - elapsed;
var timeout = Math.min(100 * elapsed, max_timeout);
var minified_result = run_code(result_cache, minified.code, toplevel, timeout);
if (sandbox.same_stdout(unminified_result, minified_result)) {
return is_timed_out(unminified_result) && is_timed_out(minified_result) && {
timed_out: true,
};
}
return {
unminified_result: unminified_result,
minified_result: minified_result,
elapsed: elapsed,
};
}
Error.stackTraceLimit = Infinity;

View File

@@ -54,14 +54,15 @@ function createContext() {
}
}
exports.run_code = function(code, toplevel) {
exports.run_code = function(code, toplevel, timeout) {
timeout = timeout || 5000;
var stdout = "";
var original_write = process.stdout.write;
process.stdout.write = function(chunk) {
stdout += chunk;
};
try {
vm.runInContext(toplevel ? "(function(){" + code + "})()" : code, createContext(), { timeout: 5000 });
vm.runInContext(toplevel ? "(function(){" + code + "})()" : code, createContext(), { timeout: timeout });
return stdout;
} catch (ex) {
return ex;
@@ -76,8 +77,9 @@ function strip_func_ids(text) {
exports.same_stdout = semver.satisfies(process.version, "0.12") ? function(expected, actual) {
if (typeof expected != typeof actual) return false;
if (typeof expected != "string") {
if (expected.name != actual.name) return false;
if (typeof expected == "object" && typeof expected.name == "string" && typeof expected.message == "string") {
if (expected.name !== actual.name) return false;
if (typeof actual.message != "string") return false;
expected = expected.message.slice(expected.message.lastIndexOf("\n") + 1);
actual = actual.message.slice(actual.message.lastIndexOf("\n") + 1);
}
@@ -85,3 +87,8 @@ exports.same_stdout = semver.satisfies(process.version, "0.12") ? function(expec
} : function(expected, actual) {
return typeof expected == typeof actual && strip_func_ids(expected) == strip_func_ids(actual);
};
exports.has_toplevel = function(options) {
return options.toplevel
|| options.mangle && options.mangle.toplevel
|| options.compress && options.compress.toplevel;
};

View File

@@ -10,6 +10,7 @@ require("../../tools/exit");
var UglifyJS = require("../..");
var randomBytes = require("crypto").randomBytes;
var sandbox = require("../sandbox");
var reduce_test = require("../reduce");
var MAX_GENERATED_TOPLEVELS_PER_RUN = 1;
var MAX_GENERATION_RECURSION_DEPTH = 12;
@@ -741,6 +742,8 @@ function _createExpression(recurmax, noComma, stmtDepth, canThrow) {
return " ((" + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ") || a || 3).toString() ";
case p++:
return " /[abc4]/.test(((" + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ") || b || 5).toString()) ";
case p++:
return " /[abc4]/g.exec(((" + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ") || b || 5).toString()) ";
case p++:
return " ((" + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) +
") || " + rng(10) + ").toString()[" +
@@ -1009,7 +1012,7 @@ function log_suspects(minify_options, component) {
var defs = default_options[component];
var suspects = Object.keys(defs).filter(function(name) {
var flip = name == "keep_fargs";
if (flip ? name in options : (name in options ? options : defs)[name]) {
if (flip === !(name in options ? options : defs)[name]) {
var m = JSON.parse(JSON.stringify(minify_options));
var o = JSON.parse(JSON.stringify(options));
o[name] = flip;
@@ -1019,7 +1022,7 @@ function log_suspects(minify_options, component) {
errorln("Error testing options." + component + "." + name);
errorln(result.error);
} else {
var r = sandbox.run_code(result.code, m.toplevel);
var r = sandbox.run_code(result.code, sandbox.has_toplevel(m));
return sandbox.same_stdout(original_result, r);
}
}
@@ -1033,42 +1036,63 @@ function log_suspects(minify_options, component) {
}
}
function log_rename(options) {
var m = JSON.parse(JSON.stringify(options));
m.rename = false;
var result = UglifyJS.minify(original_code, m);
if (result.error) {
errorln("Error testing options.rename");
errorln(result.error);
} else {
var r = sandbox.run_code(result.code, m.toplevel);
if (sandbox.same_stdout(original_result, r)) {
errorln("Suspicious options:");
errorln(" rename");
errorln();
function log_suspects_global(options) {
var o = {};
UglifyJS.minify("", o);
var suspects = Object.keys(o).filter(function(component) {
return typeof o[component] != "object";
}).filter(function(component) {
var m = JSON.parse(options);
m[component] = false;
var result = UglifyJS.minify(original_code, m);
if (result.error) {
errorln("Error testing options." + component);
errorln(result.error);
} else {
var r = sandbox.run_code(result.code, sandbox.has_toplevel(m));
return sandbox.same_stdout(original_result, r);
}
});
if (suspects.length > 0) {
errorln("Suspicious options:");
suspects.forEach(function(name) {
errorln(" " + name);
});
errorln();
}
}
function log(options) {
options = JSON.parse(options);
var toplevel = sandbox.has_toplevel(JSON.parse(options));
if (!ok) errorln("\n\n\n\n\n\n!!!!!!!!!!\n\n\n");
errorln("//=============================================================");
if (!ok) errorln("// !!!!!! Failed... round " + round);
errorln("// original code");
try_beautify(original_code, options.toplevel, original_result, errorln);
try_beautify(original_code, toplevel, original_result, errorln);
errorln();
errorln();
errorln("//-------------------------------------------------------------");
if (typeof uglify_code == "string") {
errorln("// uglified code");
try_beautify(uglify_code, options.toplevel, uglify_result, errorln);
try_beautify(uglify_code, toplevel, uglify_result, errorln);
errorln();
errorln();
errorln("original result:");
errorln(original_result);
errorln("uglified result:");
errorln(uglify_result);
errorln("//-------------------------------------------------------------");
var reduced = reduce_test(original_code, JSON.parse(options), {
verbose: false,
}).code;
if (reduced) {
errorln();
errorln("// reduced test case (output will differ)");
errorln();
errorln(reduced);
errorln();
errorln("//-------------------------------------------------------------");
}
} else {
errorln("// !!! uglify failed !!!");
errorln(uglify_code);
@@ -1080,19 +1104,20 @@ function log(options) {
}
}
errorln("minify(options):");
errorln(JSON.stringify(options, null, 2));
errorln(JSON.stringify(JSON.parse(options), null, 2));
errorln();
if (!ok && typeof uglify_code == "string") {
Object.keys(default_options).forEach(log_suspects.bind(null, options));
log_rename(options);
Object.keys(default_options).forEach(log_suspects.bind(null, JSON.parse(options)));
log_suspects_global(options);
errorln("!!!!!! Failed... round " + round);
}
}
function fuzzy_match(original, uglified) {
original = original.split(" ", 5);
uglified = uglified.split(" ", 5);
for (var i = 0; i < 5; i++) {
uglified = uglified.split(" ");
var i = uglified.length;
original = original.split(" ", i);
while (--i >= 0) {
if (original[i] === uglified[i]) continue;
var a = +original[i];
var b = +uglified[i];
@@ -1118,17 +1143,18 @@ for (var round = 1; round <= num_iterations; round++) {
if (!errored) orig_result.push(sandbox.run_code(original_code, true));
(errored ? fallback_options : minify_options).forEach(function(options) {
var o = JSON.parse(options);
var toplevel = sandbox.has_toplevel(o);
uglify_code = UglifyJS.minify(original_code, o);
original_result = orig_result[o.toplevel ? 1 : 0];
original_result = orig_result[toplevel ? 1 : 0];
if (!uglify_code.error) {
uglify_code = uglify_code.code;
uglify_result = sandbox.run_code(uglify_code, o.toplevel);
uglify_result = sandbox.run_code(uglify_code, toplevel);
ok = sandbox.same_stdout(original_result, uglify_result);
if (!ok && typeof uglify_result == "string" && o.compress.unsafe_math) {
ok = fuzzy_match(original_result, uglify_result);
if (!ok) {
var fuzzy_result = sandbox.run_code(original_code.replace(/( - 0\.1){3}/g, " - 0.3"));
ok = sandbox.same_stdout(fuzzy_result, uglify_result, o.toplevel);
var fuzzy_result = sandbox.run_code(original_code.replace(/( - 0\.1){3}/g, " - 0.3"), toplevel);
ok = sandbox.same_stdout(fuzzy_result, uglify_result);
}
}
} else {
@@ -1141,7 +1167,7 @@ for (var round = 1; round <= num_iterations; round++) {
else if (errored) {
println("//=============================================================");
println("// original code");
try_beautify(original_code, o.toplevel, original_result, println);
try_beautify(original_code, toplevel, original_result, println);
println();
println();
println("original result:");

View File

@@ -1,4 +1,5 @@
exports["Dictionary"] = Dictionary;
exports["List"] = List;
exports["minify"] = minify;
exports["parse"] = parse;
exports["push_uniq"] = push_uniq;