Compare commits

..

54 Commits

Author SHA1 Message Date
Alex Lam S.L
77261e1ee0 v3.9.0 2020-04-13 13:45:02 +01:00
Alex Lam S.L
903a5df9a5 fix corner case in inline (#3778)
fixes #3777
2020-04-11 19:54:26 +08:00
Alex Lam S.L
c810ecd081 improve handling of eval (#3776)
closes #3768
2020-04-11 06:36:17 +08:00
Alex Lam S.L
dce9dfce0e fix corner case in reduce_vars (#3775)
fixes #3774
2020-04-11 02:19:38 +08:00
Alex Lam S.L
3d72663689 add tests for eval() (#3769)
closes #3768
2020-04-11 00:36:53 +08:00
Alex Lam S.L
a2b16e89a4 fix corner cases in inline (#3773)
fixes #3770
fixes #3771
fixes #3772
2020-04-11 00:34:45 +08:00
Alex Lam S.L
b35f4c5a83 enhance inline (#3767) 2020-04-10 10:48:24 +08:00
Alex Lam S.L
41eb4f1725 workaround intermittent nodejs.org corruptions (#3766) 2020-04-07 08:40:38 +08:00
Alex Lam S.L
94bc221669 fix export of PATH to Node.js (#3765) 2020-04-07 01:14:16 +08:00
Alex Lam S.L
822d298a55 fix Github Actions retry logic (#3763) 2020-04-06 22:16:48 +08:00
Alex Lam S.L
273c6020ba expand ufuzz patterns (#3761) 2020-04-05 22:12:46 +08:00
Alex Lam S.L
1b07f64057 enhance inline (#3760) 2020-04-05 10:42:23 +08:00
Alex Lam S.L
80d9c44b22 improve resilience against nodejs.org failures (#3759) 2020-04-03 02:49:38 +08:00
Alex Lam S.L
dc0cd088cf fix corner case in evaluate & unsafe_math (#3756)
fixes #3755
2020-03-30 19:13:14 +08:00
Alex Lam S.L
c69c026728 improve resilience against nodejs.org failures (#3758) 2020-03-30 10:20:13 +08:00
Alex Lam S.L
b5f4e1187f handle single-field segments (#3757) 2020-03-30 06:39:32 +08:00
Alex Lam S.L
827bcec186 handle source-map operations internally (#3754) 2020-03-28 22:18:56 +08:00
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
54 changed files with 2731 additions and 467 deletions

View File

@@ -4,10 +4,10 @@ jobs:
test: test:
strategy: strategy:
matrix: matrix:
node: [ "0.10", "0.12", "4", "6", "8", "10", latest ]
os: [ ubuntu-latest, windows-latest ] os: [ ubuntu-latest, windows-latest ]
node: [ "0.10", 0.12, 4, 6, 8, 10, latest ]
script: [ compress, mocha, release/benchmark, release/jetstream ] 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 }} runs-on: ${{ matrix.os }}
env: env:
NODE: ${{ matrix.node }} NODE: ${{ matrix.node }}
@@ -21,11 +21,19 @@ jobs:
- name: Perform tests - name: Perform tests
shell: bash shell: bash
run: | run: |
git clone --branch v1.5.3 --depth 1 https://github.com/jasongin/nvs.git ~/.nvs git clone --branch v1.5.4 --depth 1 https://github.com/jasongin/nvs.git ~/.nvs
while ! timeout 60 bash -c '. ~/.nvs/nvs.sh add $NODE && nvs use $NODE'; do
cd ~/.nvs
git clean -xdf
cd -
done
. ~/.nvs/nvs.sh --version . ~/.nvs/nvs.sh --version
nvs add $NODE
nvs use $NODE nvs use $NODE
node --version node --version
npm --version --no-update-notifier npm config set audit false
npm install --no-audit --no-optional --no-save --no-update-notifier npm config set optional false
npm config set save false
npm config set update-notifier false
npm --version
while !(npm install); do echo "'npm install' failed - retrying..."; done
node test/$TYPE node test/$TYPE

View File

@@ -15,11 +15,19 @@ jobs:
- name: Perform fuzzing - name: Perform fuzzing
shell: bash shell: bash
run: | run: |
git clone --branch v1.5.3 --depth 1 https://github.com/jasongin/nvs.git ~/.nvs git clone --branch v1.5.4 --depth 1 https://github.com/jasongin/nvs.git ~/.nvs
while ! timeout 60 bash -c '. ~/.nvs/nvs.sh add 10 && nvs use 10'; do
cd ~/.nvs
git clean -xdf
cd -
done
. ~/.nvs/nvs.sh --version . ~/.nvs/nvs.sh --version
nvs add 10
nvs use 10 nvs use 10
node --version node --version
npm --version --no-update-notifier npm config set audit false
npm install --no-audit --no-optional --no-save --no-update-notifier npm config set optional false
npm config set save false
npm config set update-notifier false
npm --version
while !(npm install); do echo "'npm install' failed - retrying..."; done
node test/ufuzz/job 3600000 node test/ufuzz/job 3600000

View File

@@ -848,8 +848,14 @@ can pass additional arguments that control the code output:
statement. statement.
- `comments` (default `false`) -- pass `true` or `"all"` to preserve all - `comments` (default `false`) -- pass `true` or `"all"` to preserve all
comments, `"some"` to preserve some comments, a regular expression string comments, `"some"` to preserve multi-line comments that contain `@cc_on`,
(e.g. `/^!/`) or a function. `@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`) - `indent_level` (default `4`)

View File

@@ -54,6 +54,7 @@ program.option("--toplevel", "Compress and/or mangle variables in toplevel scope
program.option("--verbose", "Print diagnostic messages."); program.option("--verbose", "Print diagnostic messages.");
program.option("--warn", "Print warning messages."); program.option("--warn", "Print warning messages.");
program.option("--wrap <name>", "Embed everything as a function with “exports” corresponding to “name” globally."); 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); program.arguments("[files...]").parseArgv(process.argv);
if (program.configFile) { if (program.configFile) {
options = JSON.parse(read_file(program.configFile)); options = JSON.parse(read_file(program.configFile));
@@ -215,7 +216,15 @@ function run() {
} catch (ex) { } catch (ex) {
fatal(ex); fatal(ex);
} }
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); var result = UglifyJS.minify(files, options);
}
if (result.error) { if (result.error) {
var ex = result.error; var ex = result.error;
if (ex.name == "SyntaxError") { if (ex.name == "SyntaxError") {

View File

@@ -169,10 +169,7 @@ var AST_SimpleStatement = DEFNODE("SimpleStatement", "body", {
}, AST_Statement); }, AST_Statement);
function walk_body(node, visitor) { function walk_body(node, visitor) {
var body = node.body; node.body.forEach(function(node) {
if (body instanceof AST_Statement) {
body._walk(visitor);
} else body.forEach(function(node) {
node._walk(visitor); node._walk(visitor);
}); });
} }
@@ -351,7 +348,7 @@ var AST_Toplevel = DEFNODE("Toplevel", "globals", {
filename: "wrap=" + JSON.stringify(name) filename: "wrap=" + JSON.stringify(name)
}).transform(new TreeTransformer(function(node) { }).transform(new TreeTransformer(function(node) {
if (node instanceof AST_Directive && node.value == "$ORIG") { 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) filename: "enclose=" + JSON.stringify(args_values)
}).transform(new TreeTransformer(function(node) { }).transform(new TreeTransformer(function(node) {
if (node instanceof AST_Directive && node.value == "$ORIG") { if (node instanceof AST_Directive && node.value == "$ORIG") {
return MAP.splice(body); return List.splice(body);
} }
})); }));
} }

View File

@@ -315,10 +315,10 @@ merge(Compressor.prototype, {
if (value instanceof AST_Array) return native_fns.Array[name]; if (value instanceof AST_Array) return native_fns.Array[name];
if (value instanceof AST_Function) return native_fns.Function[name]; if (value instanceof AST_Function) return native_fns.Function[name];
if (value instanceof AST_Object) return native_fns.Object[name]; if (value instanceof AST_Object) return native_fns.Object[name];
if (value instanceof AST_RegExp) return native_fns.RegExp[name]; if (value instanceof AST_RegExp) return native_fns.RegExp[name] && !value.value.global;
} }
function is_modified(compressor, tw, node, value, level, immutable) { function is_modified(compressor, tw, node, value, level, immutable, recursive) {
var parent = tw.parent(level); var parent = tw.parent(level);
if (compressor.option("unsafe") && parent instanceof AST_Dot && is_read_only_fn(value, parent.property)) { if (compressor.option("unsafe") && parent instanceof AST_Dot && is_read_only_fn(value, parent.property)) {
return; return;
@@ -342,7 +342,7 @@ merge(Compressor.prototype, {
} }
if (parent instanceof AST_PropAccess && parent.expression === node) { if (parent instanceof AST_PropAccess && parent.expression === node) {
var prop = read_property(value, parent); var prop = read_property(value, parent);
return !immutable && is_modified(compressor, tw, parent, prop, level + 1); return (!immutable || recursive) && is_modified(compressor, tw, parent, prop, level + 1);
} }
} }
@@ -759,7 +759,8 @@ merge(Compressor.prototype, {
d.fixed = false; d.fixed = false;
} else if (d.fixed) { } else if (d.fixed) {
value = this.fixed_value(); value = this.fixed_value();
if (recursive_ref(tw, d)) { var recursive = recursive_ref(tw, d);
if (recursive) {
d.recursive_refs++; d.recursive_refs++;
} else if (value && ref_once(tw, compressor, d)) { } else if (value && ref_once(tw, compressor, d)) {
d.single_use = value instanceof AST_Lambda && !value.pinned() d.single_use = value instanceof AST_Lambda && !value.pinned()
@@ -767,7 +768,7 @@ merge(Compressor.prototype, {
} else { } else {
d.single_use = false; d.single_use = false;
} }
if (is_modified(compressor, tw, this, value, 0, is_immutable(value))) { if (is_modified(compressor, tw, this, value, 0, is_immutable(value), recursive)) {
if (d.single_use) { if (d.single_use) {
d.single_use = "m"; d.single_use = "m";
} else { } else {
@@ -995,9 +996,7 @@ merge(Compressor.prototype, {
function needs_unbinding(compressor, val) { function needs_unbinding(compressor, val) {
return val instanceof AST_PropAccess return val instanceof AST_PropAccess
|| compressor.has_directive("use strict") || is_undeclared_ref(val) && val.name == "eval";
&& is_undeclared_ref(val)
&& val.name == "eval";
} }
// we shouldn't compress (1,func)(something) to // we shouldn't compress (1,func)(something) to
@@ -1230,7 +1229,7 @@ merge(Compressor.prototype, {
var parent = multi_replacer.parent(); var parent = multi_replacer.parent();
if (parent instanceof AST_Sequence && parent.tail_node() !== node) { if (parent instanceof AST_Sequence && parent.tail_node() !== node) {
value_def.replaced++; value_def.replaced++;
return MAP.skip; return List.skip;
} }
return get_rvalue(candidate); return get_rvalue(candidate);
case 1: case 1:
@@ -1384,7 +1383,10 @@ merge(Compressor.prototype, {
function is_last_node(node, parent) { function is_last_node(node, parent) {
if (node instanceof AST_Call) { if (node instanceof AST_Call) {
var fn = node.expression; var fn = node.expression;
if (fn instanceof AST_SymbolRef) fn = fn.fixed_value(); if (fn instanceof AST_SymbolRef) {
if (fn.definition().recursive_refs > 0) return true;
fn = fn.fixed_value();
}
if (!(fn instanceof AST_Lambda)) return true; if (!(fn instanceof AST_Lambda)) return true;
if (fn.collapse_scanning) return false; if (fn.collapse_scanning) return false;
fn.collapse_scanning = true; fn.collapse_scanning = true;
@@ -1863,7 +1865,7 @@ merge(Compressor.prototype, {
node.value = null; node.value = null;
return node; return node;
} }
return in_list ? MAP.skip : null; return in_list ? List.skip : null;
}, patch_sequence)); }, patch_sequence));
} }
@@ -2528,9 +2530,8 @@ merge(Compressor.prototype, {
return left.is_negative_zero() || right.is_negative_zero(); return left.is_negative_zero() || right.is_negative_zero();
case "*": case "*":
case "/": case "/":
return true;
case "%": case "%":
return left.is_negative_zero(); return true;
default: default:
return false; return false;
} }
@@ -3003,6 +3004,7 @@ merge(Compressor.prototype, {
].concat(object_fns), ].concat(object_fns),
Object: object_fns, Object: object_fns,
RegExp: [ RegExp: [
"exec",
"test", "test",
].concat(object_fns), ].concat(object_fns),
String: [ String: [
@@ -3071,15 +3073,21 @@ merge(Compressor.prototype, {
// If the node has been successfully reduced to a constant, // If the node has been successfully reduced to a constant,
// then its value is returned; otherwise the element itself // then its value is returned; otherwise the element itself
// is returned. // is returned.
//
// They can be distinguished as constant value is never a // They can be distinguished as constant value is never a
// descendant of AST_Node. // descendant of AST_Node.
AST_Node.DEFMETHOD("evaluate", function(compressor) { //
// When `ignore_side_effects` is `true`, inspect the constant value
// produced without worrying about any side effects caused by said
// expression.
AST_Node.DEFMETHOD("evaluate", function(compressor, ignore_side_effects) {
if (!compressor.option("evaluate")) return this; if (!compressor.option("evaluate")) return this;
var cached = []; var cached = [];
var val = this._eval(compressor, cached, 1); var val = this._eval(compressor, ignore_side_effects, cached, 1);
cached.forEach(function(node) { cached.forEach(function(node) {
delete node._eval; delete node._eval;
}); });
if (ignore_side_effects) return val;
if (!val || val instanceof RegExp) return val; if (!val || val instanceof RegExp) return val;
if (typeof val == "function" || typeof val == "object") return this; if (typeof val == "function" || typeof val == "object") return this;
return val; return val;
@@ -3104,6 +3112,19 @@ merge(Compressor.prototype, {
def(AST_Constant, function() { def(AST_Constant, function() {
return this.value; return this.value;
}); });
def(AST_Assign, function(compressor, ignore_side_effects, cached, depth) {
if (!ignore_side_effects) return this;
if (this.operator != "=") return this;
var node = this.right;
var value = node._eval(compressor, ignore_side_effects, cached, depth);
return value === node ? this : value;
});
def(AST_Sequence, function(compressor, ignore_side_effects, cached, depth) {
if (!ignore_side_effects) return this;
var node = this.tail_node();
var value = node._eval(compressor, ignore_side_effects, cached, depth);
return value === node ? this : value;
});
def(AST_Function, function(compressor) { def(AST_Function, function(compressor) {
if (compressor.option("unsafe")) { if (compressor.option("unsafe")) {
var fn = function() {}; var fn = function() {};
@@ -3115,12 +3136,12 @@ merge(Compressor.prototype, {
} }
return this; return this;
}); });
def(AST_Array, function(compressor, cached, depth) { def(AST_Array, function(compressor, ignore_side_effects, cached, depth) {
if (compressor.option("unsafe")) { if (compressor.option("unsafe")) {
var elements = []; var elements = [];
for (var i = 0; i < this.elements.length; i++) { for (var i = 0; i < this.elements.length; i++) {
var element = this.elements[i]; var element = this.elements[i];
var value = element._eval(compressor, cached, depth); var value = element._eval(compressor, ignore_side_effects, cached, depth);
if (element === value) return this; if (element === value) return this;
elements.push(value); elements.push(value);
} }
@@ -3128,7 +3149,7 @@ merge(Compressor.prototype, {
} }
return this; return this;
}); });
def(AST_Object, function(compressor, cached, depth) { def(AST_Object, function(compressor, ignore_side_effects, cached, depth) {
if (compressor.option("unsafe")) { if (compressor.option("unsafe")) {
var val = {}; var val = {};
for (var i = 0; i < this.properties.length; i++) { for (var i = 0; i < this.properties.length; i++) {
@@ -3137,14 +3158,14 @@ merge(Compressor.prototype, {
if (key instanceof AST_Symbol) { if (key instanceof AST_Symbol) {
key = key.name; key = key.name;
} else if (key instanceof AST_Node) { } else if (key instanceof AST_Node) {
key = key._eval(compressor, cached, depth); key = key._eval(compressor, ignore_side_effects, cached, depth);
if (key === prop.key) return this; if (key === prop.key) return this;
} }
if (typeof Object.prototype[key] === 'function') { if (typeof Object.prototype[key] === 'function') {
return this; return this;
} }
if (prop.value instanceof AST_Function) continue; if (prop.value instanceof AST_Function) continue;
val[key] = prop.value._eval(compressor, cached, depth); val[key] = prop.value._eval(compressor, ignore_side_effects, cached, depth);
if (val[key] === prop.value) return this; if (val[key] === prop.value) return this;
} }
return val; return val;
@@ -3152,7 +3173,7 @@ merge(Compressor.prototype, {
return this; return this;
}); });
var non_converting_unary = makePredicate("! typeof void"); var non_converting_unary = makePredicate("! typeof void");
def(AST_UnaryPrefix, function(compressor, cached, depth) { def(AST_UnaryPrefix, function(compressor, ignore_side_effects, cached, depth) {
var e = this.expression; var e = this.expression;
// Function would be evaluated to an array and so typeof would // Function would be evaluated to an array and so typeof would
// incorrectly return 'object'. Hence making is a special case. // incorrectly return 'object'. Hence making is a special case.
@@ -3164,7 +3185,7 @@ merge(Compressor.prototype, {
return typeof function(){}; return typeof function(){};
} }
if (!non_converting_unary[this.operator]) depth++; if (!non_converting_unary[this.operator]) depth++;
var v = e._eval(compressor, cached, depth); var v = e._eval(compressor, ignore_side_effects, cached, depth);
if (v === this.expression) return this; if (v === this.expression) return this;
switch (this.operator) { switch (this.operator) {
case "!": return !v; case "!": return !v;
@@ -3187,12 +3208,12 @@ merge(Compressor.prototype, {
return this; return this;
}); });
var non_converting_binary = makePredicate("&& || === !=="); var non_converting_binary = makePredicate("&& || === !==");
def(AST_Binary, function(compressor, cached, depth) { def(AST_Binary, function(compressor, ignore_side_effects, cached, depth) {
if (!non_converting_binary[this.operator]) depth++; if (!non_converting_binary[this.operator]) depth++;
var left = this.left._eval(compressor, cached, depth); var left = this.left._eval(compressor, ignore_side_effects, cached, depth);
if (left === this.left) return this; if (left === this.left) return this;
if (this.operator == (left ? "||" : "&&")) return left; if (this.operator == (left ? "||" : "&&")) return left;
var right = this.right._eval(compressor, cached, depth); var right = this.right._eval(compressor, ignore_side_effects, cached, depth);
if (right === this.right) return this; if (right === this.right) return this;
var result; var result;
switch (this.operator) { switch (this.operator) {
@@ -3221,6 +3242,7 @@ merge(Compressor.prototype, {
} }
if (isNaN(result)) return compressor.find_parent(AST_With) ? this : result; if (isNaN(result)) return compressor.find_parent(AST_With) ? this : result;
if (compressor.option("unsafe_math") if (compressor.option("unsafe_math")
&& !ignore_side_effects
&& result && result
&& typeof result == "number" && typeof result == "number"
&& (this.operator == "+" || this.operator == "-")) { && (this.operator == "+" || this.operator == "-")) {
@@ -3235,14 +3257,14 @@ merge(Compressor.prototype, {
return (match[1] || ".").length - 1 - (match[2] || "").slice(1); return (match[1] || ".").length - 1 - (match[2] || "").slice(1);
} }
}); });
def(AST_Conditional, function(compressor, cached, depth) { def(AST_Conditional, function(compressor, ignore_side_effects, cached, depth) {
var condition = this.condition._eval(compressor, cached, depth); var condition = this.condition._eval(compressor, ignore_side_effects, cached, depth);
if (condition === this.condition) return this; if (condition === this.condition) return this;
var node = condition ? this.consequent : this.alternative; var node = condition ? this.consequent : this.alternative;
var value = node._eval(compressor, cached, depth); var value = node._eval(compressor, ignore_side_effects, cached, depth);
return value === node ? this : value; return value === node ? this : value;
}); });
def(AST_SymbolRef, function(compressor, cached, depth) { def(AST_SymbolRef, function(compressor, ignore_side_effects, cached, depth) {
var fixed = this.fixed_value(); var fixed = this.fixed_value();
if (!fixed) return this; if (!fixed) return this;
var value; var value;
@@ -3250,7 +3272,7 @@ merge(Compressor.prototype, {
value = fixed._eval(); value = fixed._eval();
} else { } else {
this._eval = return_this; this._eval = return_this;
value = fixed._eval(compressor, cached, depth); value = fixed._eval(compressor, ignore_side_effects, cached, depth);
delete this._eval; delete this._eval;
if (value === fixed) return this; if (value === fixed) return this;
fixed._eval = function() { fixed._eval = function() {
@@ -3307,11 +3329,11 @@ merge(Compressor.prototype, {
], ],
}); });
var regexp_props = makePredicate("global ignoreCase multiline source"); var regexp_props = makePredicate("global ignoreCase multiline source");
def(AST_PropAccess, function(compressor, cached, depth) { def(AST_PropAccess, function(compressor, ignore_side_effects, cached, depth) {
if (compressor.option("unsafe")) { if (compressor.option("unsafe")) {
var key = this.property; var key = this.property;
if (key instanceof AST_Node) { if (key instanceof AST_Node) {
key = key._eval(compressor, cached, depth); key = key._eval(compressor, ignore_side_effects, cached, depth);
if (key === this.property) return this; if (key === this.property) return this;
} }
var exp = this.expression; var exp = this.expression;
@@ -3321,7 +3343,7 @@ merge(Compressor.prototype, {
if (!static_value || !static_value[key]) return this; if (!static_value || !static_value[key]) return this;
val = global_objs[exp.name]; val = global_objs[exp.name];
} else { } else {
val = exp._eval(compressor, cached, depth + 1); val = exp._eval(compressor, ignore_side_effects, cached, depth + 1);
if (val == null || val === exp) return this; if (val == null || val === exp) return this;
if (val instanceof RegExp) { if (val instanceof RegExp) {
if (!regexp_props[key]) return this; if (!regexp_props[key]) return this;
@@ -3340,7 +3362,7 @@ merge(Compressor.prototype, {
} }
return this; return this;
}); });
def(AST_Call, function(compressor, cached, depth) { def(AST_Call, function(compressor, ignore_side_effects, cached, depth) {
var exp = this.expression; var exp = this.expression;
var fn = exp instanceof AST_SymbolRef ? exp.fixed_value() : exp; var fn = exp instanceof AST_SymbolRef ? exp.fixed_value() : exp;
if (fn instanceof AST_Lambda) { if (fn instanceof AST_Lambda) {
@@ -3351,24 +3373,27 @@ merge(Compressor.prototype, {
var args = eval_args(this.args); var args = eval_args(this.args);
if (!args) return this; if (!args) return this;
if (!stat.value) return undefined; if (!stat.value) return undefined;
fn.argnames.forEach(function(sym, i) { if (!all(fn.argnames, function(sym, i) {
var value = args[i]; var value = args[i];
sym.definition().references.forEach(function(node) { var def = sym.definition();
if (def.orig[def.orig.length - 1] !== sym) return false;
def.references.forEach(function(node) {
node._eval = function() { node._eval = function() {
return value; return value;
}; };
cached.push(node); cached.push(node);
}); });
}); return true;
})) return this;
fn.evaluating = true; fn.evaluating = true;
var val = stat.value._eval(compressor, cached, depth); var val = stat.value._eval(compressor, ignore_side_effects, cached, depth);
delete fn.evaluating; delete fn.evaluating;
if (val === stat.value) return this; if (val === stat.value) return this;
return val; return val;
} else if (compressor.option("unsafe") && exp instanceof AST_PropAccess) { } else if (compressor.option("unsafe") && exp instanceof AST_PropAccess) {
var key = exp.property; var key = exp.property;
if (key instanceof AST_Node) { if (key instanceof AST_Node) {
key = key._eval(compressor, cached, depth); key = key._eval(compressor, ignore_side_effects, cached, depth);
if (key === exp.property) return this; if (key === exp.property) return this;
} }
var val; var val;
@@ -3378,10 +3403,11 @@ merge(Compressor.prototype, {
if (!static_fn || !static_fn[key]) return this; if (!static_fn || !static_fn[key]) return this;
val = global_objs[e.name]; val = global_objs[e.name];
} else { } else {
val = e._eval(compressor, cached, depth + 1); val = e._eval(compressor, ignore_side_effects, cached, depth + 1);
if (val == null || val === e) return this; if (val == null || val === e) return this;
var native_fn = native_fns[val.constructor.name]; var native_fn = native_fns[val.constructor.name];
if (!native_fn || !native_fn[key]) return this; if (!native_fn || !native_fn[key]) return this;
if (val instanceof RegExp && val.global && !(e instanceof AST_RegExp)) return this;
} }
var args = eval_args(this.args); var args = eval_args(this.args);
if (!args) return this; if (!args) return this;
@@ -3395,6 +3421,8 @@ merge(Compressor.prototype, {
line: this.start.line, line: this.start.line,
col: this.start.col col: this.start.col
}); });
} finally {
if (val instanceof RegExp) val.lastIndex = 0;
} }
} }
return this; return this;
@@ -3403,7 +3431,7 @@ merge(Compressor.prototype, {
var values = []; var values = [];
for (var i = 0; i < args.length; i++) { for (var i = 0; i < args.length; i++) {
var arg = args[i]; var arg = args[i];
var value = arg._eval(compressor, cached, depth); var value = arg._eval(compressor, ignore_side_effects, cached, depth);
if (arg === value) return; if (arg === value) return;
values.push(value); values.push(value);
} }
@@ -3891,14 +3919,18 @@ merge(Compressor.prototype, {
} else if (node instanceof AST_Unary) { } else if (node instanceof AST_Unary) {
if (node.write_only) sym = node.expression; if (node.write_only) sym = node.expression;
} }
if (!/strict/.test(compressor.option("pure_getters"))) return sym instanceof AST_SymbolRef && sym; if (/strict/.test(compressor.option("pure_getters"))) {
while (sym instanceof AST_PropAccess && !sym.expression.may_throw_on_access(compressor)) { while (sym instanceof AST_PropAccess && !sym.expression.may_throw_on_access(compressor)) {
if (sym instanceof AST_Sub) props.unshift(sym.property); if (sym instanceof AST_Sub) props.unshift(sym.property);
sym = sym.expression; sym = sym.expression;
} }
return sym instanceof AST_SymbolRef && all(sym.definition().orig, function(sym) { }
if (!(sym instanceof AST_SymbolRef)) return;
if (compressor.exposed(sym.definition())) return;
if (!all(sym.definition().orig, function(sym) {
return !(sym instanceof AST_SymbolLambda); return !(sym instanceof AST_SymbolLambda);
}) && sym; })) return;
return sym;
}; };
var in_use = []; var in_use = [];
var in_use_ids = Object.create(null); // avoid expensive linear scans of in_use var in_use_ids = Object.create(null); // avoid expensive linear scans of in_use
@@ -3988,6 +4020,8 @@ merge(Compressor.prototype, {
}; };
// pass 3: we should drop declarations not in_use // pass 3: we should drop declarations not in_use
var unused_fn_names = []; var unused_fn_names = [];
var calls_to_drop_args = [];
var fns_with_marked_args = [];
var tt = new TreeTransformer(function(node, descend, in_list) { var tt = new TreeTransformer(function(node, descend, in_list) {
var parent = tt.parent(); var parent = tt.parent();
if (drop_vars) { if (drop_vars) {
@@ -4017,7 +4051,7 @@ merge(Compressor.prototype, {
if (value) props.push(value); if (value) props.push(value);
switch (props.length) { switch (props.length) {
case 0: case 0:
return MAP.skip; return List.skip;
case 1: case 1:
return maintain_this_binding(compressor, parent, node, props[0].transform(tt)); return maintain_this_binding(compressor, parent, node, props[0].transform(tt));
default: default:
@@ -4028,6 +4062,7 @@ merge(Compressor.prototype, {
} }
} }
} }
if (node instanceof AST_Call) calls_to_drop_args.push(node);
if (scope !== self) return; if (scope !== self) return;
if (node instanceof AST_Function && node.name && drop_fn_name(node.name.definition())) { if (node instanceof AST_Function && node.name && drop_fn_name(node.name.definition())) {
unused_fn_names.push(node); unused_fn_names.push(node);
@@ -4046,6 +4081,7 @@ merge(Compressor.prototype, {
trim = false; trim = false;
} }
} }
fns_with_marked_args.push(node);
} }
if (drop_funcs && node instanceof AST_Defun && node !== self) { if (drop_funcs && node instanceof AST_Defun && node !== self) {
var def = node.name.definition(); var def = node.name.definition();
@@ -4159,11 +4195,11 @@ merge(Compressor.prototype, {
} }
switch (body.length) { switch (body.length) {
case 0: case 0:
return in_list ? MAP.skip : make_node(AST_EmptyStatement, node); return in_list ? List.skip : make_node(AST_EmptyStatement, node);
case 1: case 1:
return body[0]; return body[0];
default: default:
return in_list ? MAP.splice(body) : make_node(AST_BlockStatement, node, { return in_list ? List.splice(body) : make_node(AST_BlockStatement, node, {
body: body body: body
}); });
} }
@@ -4177,7 +4213,7 @@ merge(Compressor.prototype, {
var block = node.body; var block = node.body;
node.body = block.body.pop(); node.body = block.body.pop();
block.body.push(node); block.body.push(node);
return in_list ? MAP.splice(block.body) : block; return in_list ? List.splice(block.body) : block;
} }
return node; return node;
} }
@@ -4214,7 +4250,7 @@ merge(Compressor.prototype, {
} else if (is_empty(node.init)) { } else if (is_empty(node.init)) {
node.init = null; node.init = null;
} }
return !block ? node : in_list ? MAP.splice(block.body) : block; return !block ? node : in_list ? List.splice(block.body) : block;
} else if (node instanceof AST_ForIn) { } else if (node instanceof AST_ForIn) {
if (!drop_vars || !compressor.option("loops")) return; if (!drop_vars || !compressor.option("loops")) return;
if (!(node.init instanceof AST_Definitions)) return; if (!(node.init instanceof AST_Definitions)) return;
@@ -4229,7 +4265,7 @@ merge(Compressor.prototype, {
body: value body: value
}); });
} }
return in_list ? MAP.skip : make_node(AST_EmptyStatement, node); return in_list ? List.skip : make_node(AST_EmptyStatement, node);
} else if (node instanceof AST_Sequence) { } else if (node instanceof AST_Sequence) {
if (node.expressions.length == 1) return node.expressions[0]; if (node.expressions.length == 1) return node.expressions[0];
} }
@@ -4239,6 +4275,9 @@ merge(Compressor.prototype, {
unused_fn_names.forEach(function(fn) { unused_fn_names.forEach(function(fn) {
fn.name = null; fn.name = null;
}); });
calls_to_drop_args.forEach(function(call) {
drop_unused_call_args(call, compressor, fns_with_marked_args);
});
function log(sym, text, props) { function log(sym, text, props) {
AST_Node[sym.unreferenced() ? "warn" : "info"](text, props); AST_Node[sym.unreferenced() ? "warn" : "info"](text, props);
@@ -4478,7 +4517,7 @@ merge(Compressor.prototype, {
node.in_bool = true; node.in_bool = true;
var value = node.value; var value = node.value;
if (value) { if (value) {
var ev = value.is_truthy() || value.tail_node().evaluate(compressor); var ev = value.is_truthy() || value.evaluate(compressor, true);
if (!ev) { if (!ev) {
value = value.drop_side_effect_free(compressor); value = value.drop_side_effect_free(compressor);
node.value = value ? make_sequence(node.value, [ node.value = value ? make_sequence(node.value, [
@@ -4610,7 +4649,7 @@ merge(Compressor.prototype, {
})); }));
}); });
defs_by_id[node.name.definition().id] = defs; defs_by_id[node.name.definition().id] = defs;
return MAP.splice(var_defs); return List.splice(var_defs);
} }
function make_sym(sym, key) { function make_sym(sym, key) {
@@ -4746,21 +4785,17 @@ merge(Compressor.prototype, {
return exprs && make_sequence(this, exprs); return exprs && make_sequence(this, exprs);
} }
if (exp instanceof AST_Function && (!exp.name || !exp.name.definition().references.length)) { if (exp instanceof AST_Function && (!exp.name || !exp.name.definition().references.length)) {
var node = this.clone();
exp.process_expression(false, function(node) { exp.process_expression(false, function(node) {
var value = node.value && node.value.drop_side_effect_free(compressor, true); var value = node.value && node.value.drop_side_effect_free(compressor, true);
return value ? make_node(AST_SimpleStatement, node, { return value ? make_node(AST_SimpleStatement, node, {
body: value body: value
}) : make_node(AST_EmptyStatement, node); }) : make_node(AST_EmptyStatement, node);
}); });
exp.walk(new TreeWalker(function(node) { scan_local_returns(exp, function(node) {
if (node instanceof AST_Return && node.value) { if (node.value) node.value = node.value.drop_side_effect_free(compressor);
node.value = node.value.drop_side_effect_free(compressor); });
return true; // always shallow clone to ensure stripping of negated IIFEs
} return this.clone();
if (node instanceof AST_Scope && node !== exp) return true;
}));
return node;
} }
return this; return this;
} }
@@ -4878,7 +4913,7 @@ merge(Compressor.prototype, {
OPT(AST_Do, function(self, compressor) { OPT(AST_Do, function(self, compressor) {
if (!compressor.option("loops")) return self; if (!compressor.option("loops")) return self;
var cond = self.condition.is_truthy() || self.condition.tail_node().evaluate(compressor); var cond = self.condition.is_truthy() || self.condition.evaluate(compressor, true);
if (!(cond instanceof AST_Node)) { if (!(cond instanceof AST_Node)) {
if (cond) return make_node(AST_For, self, { if (cond) return make_node(AST_For, self, {
body: make_node(AST_BlockStatement, self.body, { body: make_node(AST_BlockStatement, self.body, {
@@ -5008,17 +5043,15 @@ merge(Compressor.prototype, {
} }
if (self.condition) { if (self.condition) {
var cond = self.condition.evaluate(compressor); var cond = self.condition.evaluate(compressor);
if (!(cond instanceof AST_Node)) { if (cond instanceof AST_Node) {
if (cond) self.condition = null; cond = self.condition.is_truthy() || self.condition.evaluate(compressor, true);
else if (!compressor.option("dead_code")) { } else if (cond) {
self.condition = null;
} else if (!compressor.option("dead_code")) {
var orig = self.condition; var orig = self.condition;
self.condition = make_node_from_constant(cond, self.condition); self.condition = make_node_from_constant(cond, self.condition);
self.condition = best_of_expression(self.condition.transform(compressor), orig); self.condition = best_of_expression(self.condition.transform(compressor), orig);
} }
}
if (cond instanceof AST_Node) {
cond = self.condition.is_truthy() || self.condition.tail_node().evaluate(compressor);
}
if (!cond) { if (!cond) {
if (compressor.option("dead_code")) { if (compressor.option("dead_code")) {
var body = []; var body = [];
@@ -5109,7 +5142,7 @@ merge(Compressor.prototype, {
} }
if (compressor.option("dead_code")) { if (compressor.option("dead_code")) {
if (cond instanceof AST_Node) { if (cond instanceof AST_Node) {
cond = self.condition.is_truthy() || self.condition.tail_node().evaluate(compressor); cond = self.condition.is_truthy() || self.condition.evaluate(compressor, true);
} }
if (!cond) { if (!cond) {
AST_Node.warn("Condition always false [{file}:{line},{col}]", self.condition.start); AST_Node.warn("Condition always false [{file}:{line},{col}]", self.condition.start);
@@ -5255,7 +5288,7 @@ merge(Compressor.prototype, {
} }
if (!compressor.option("dead_code")) return self; if (!compressor.option("dead_code")) return self;
if (value instanceof AST_Node) { if (value instanceof AST_Node) {
value = self.expression.tail_node().evaluate(compressor); value = self.expression.evaluate(compressor, true);
} }
var decl = []; var decl = [];
var body = []; var body = [];
@@ -5277,7 +5310,7 @@ merge(Compressor.prototype, {
eliminate_branch(branch, body[body.length - 1]); eliminate_branch(branch, body[body.length - 1]);
continue; continue;
} }
if (exp instanceof AST_Node) exp = branch.expression.tail_node().evaluate(compressor); if (exp instanceof AST_Node) exp = branch.expression.evaluate(compressor, true);
if (exp === value) { if (exp === value) {
exact_match = branch; exact_match = branch;
if (default_branch) { if (default_branch) {
@@ -5437,6 +5470,62 @@ merge(Compressor.prototype, {
return make_sequence(node, x); return make_sequence(node, x);
} }
function drop_unused_call_args(call, compressor, fns_with_marked_args) {
var exp = call.expression;
var fn = exp instanceof AST_SymbolRef ? exp.fixed_value() : exp;
if (!(fn instanceof AST_Lambda)) return;
if (fn.uses_arguments) return;
if (fn.pinned()) return;
if (fns_with_marked_args && fns_with_marked_args.indexOf(fn) < 0) return;
var args = call.args;
var pos = 0, last = 0;
var drop_fargs = fn === exp && !fn.name && compressor.drop_fargs(fn, call);
var side_effects = [];
for (var i = 0; i < args.length; i++) {
var trim = i >= fn.argnames.length;
if (trim || fn.argnames[i].__unused) {
var node = args[i].drop_side_effect_free(compressor);
if (drop_fargs) {
fn.argnames.splice(i, 1);
args.splice(i, 1);
if (node) side_effects.push(node);
i--;
continue;
} else if (node) {
side_effects.push(node);
args[pos++] = make_sequence(call, side_effects);
side_effects = [];
} else if (!trim) {
if (side_effects.length) {
node = make_sequence(call, side_effects);
side_effects = [];
} else {
node = make_node(AST_Number, args[i], {
value: 0
});
}
args[pos++] = node;
continue;
}
} else {
side_effects.push(args[i]);
args[pos++] = make_sequence(call, side_effects);
side_effects = [];
}
last = pos;
}
if (drop_fargs) for (; i < fn.argnames.length; i++) {
if (fn.argnames[i].__unused) fn.argnames.splice(i--, 1);
}
args.length = last;
if (!side_effects.length) return;
var arg = make_sequence(call, side_effects);
args.push(args.length < fn.argnames.length ? make_node(AST_UnaryPrefix, call, {
operator: "void",
expression: arg
}) : arg);
}
OPT(AST_Call, function(self, compressor) { OPT(AST_Call, function(self, compressor) {
var exp = self.expression; var exp = self.expression;
if (compressor.option("sequences")) { if (compressor.option("sequences")) {
@@ -5453,63 +5542,7 @@ merge(Compressor.prototype, {
if (seq !== self) return seq.optimize(compressor); if (seq !== self) return seq.optimize(compressor);
} }
} }
var fn = exp; if (compressor.option("unused")) drop_unused_call_args(self, compressor);
if (compressor.option("reduce_vars") && fn instanceof AST_SymbolRef) {
fn = fn.fixed_value();
}
var is_func = fn instanceof AST_Lambda;
if (compressor.option("unused")
&& is_func
&& !fn.uses_arguments
&& !fn.pinned()) {
var pos = 0, last = 0;
var drop_fargs = exp === fn && !fn.name && compressor.drop_fargs(fn, self);
var side_effects = [];
for (var i = 0; i < self.args.length; i++) {
var trim = i >= fn.argnames.length;
if (trim || fn.argnames[i].__unused) {
var node = self.args[i].drop_side_effect_free(compressor);
if (drop_fargs) {
fn.argnames.splice(i, 1);
self.args.splice(i, 1);
if (node) side_effects.push(node);
i--;
continue;
} else if (node) {
side_effects.push(node);
self.args[pos++] = make_sequence(self, side_effects);
side_effects = [];
} else if (!trim) {
if (side_effects.length) {
node = make_sequence(self, side_effects);
side_effects = [];
} else {
node = make_node(AST_Number, self.args[i], {
value: 0
});
}
self.args[pos++] = node;
continue;
}
} else {
side_effects.push(self.args[i]);
self.args[pos++] = make_sequence(self, side_effects);
side_effects = [];
}
last = pos;
}
if (drop_fargs) for (; i < fn.argnames.length; i++) {
if (fn.argnames[i].__unused) fn.argnames.splice(i--, 1);
}
self.args.length = last;
if (side_effects.length) {
var arg = make_sequence(self, side_effects);
self.args.push(self.args.length < fn.argnames.length ? make_node(AST_UnaryPrefix, self, {
operator: "void",
expression: arg
}) : arg);
}
}
if (compressor.option("unsafe")) { if (compressor.option("unsafe")) {
if (is_undeclared_ref(exp)) switch (exp.name) { if (is_undeclared_ref(exp)) switch (exp.name) {
case "Array": case "Array":
@@ -5775,33 +5808,58 @@ merge(Compressor.prototype, {
} }
} }
} }
var fn = exp instanceof AST_SymbolRef ? exp.fixed_value() : exp;
var is_func = fn instanceof AST_Lambda;
var stat = is_func && fn.first_statement(); var stat = is_func && fn.first_statement();
var can_inline = compressor.option("inline") && !self.is_expr_pure(compressor); var can_inline = compressor.option("inline") && !self.is_expr_pure(compressor);
if (exp === fn && can_inline && stat instanceof AST_Return) { if (can_inline && stat instanceof AST_Return) {
var value = stat.value; var value = stat.value;
if (!value || value.is_constant_expression()) { if (exp === fn && (!value || value.is_constant_expression())) {
var args = self.args.concat(value || make_node(AST_Undefined, self)); var args = self.args.concat(value || make_node(AST_Undefined, self));
return make_sequence(self, args).optimize(compressor); return make_sequence(self, args).optimize(compressor);
} }
} }
if (is_func) { if (is_func) {
var def, value, scope, in_loop, level = -1; var def, value, var_assigned = false;
if (can_inline if (can_inline
&& !fn.uses_arguments && !fn.uses_arguments
&& !fn.pinned() && !fn.pinned()
&& !(fn.name && fn instanceof AST_Function) && !(fn.name && fn instanceof AST_Function)
&& (value = can_flatten_body(stat)) && (value = can_flatten_body(stat))
&& (exp === fn && (exp === fn
|| compressor.option("unused") || !recursive_ref(compressor, def = exp.definition()) && fn.is_constant_expression(exp.scope))
&& (def = exp.definition()).references.length == 1 && !fn.contains_this()) {
&& !recursive_ref(compressor, def) if (can_substitute_directly()) {
&& fn.is_constant_expression(exp.scope)) var args = self.args.slice();
&& !self.pure args.push(value.clone(true).transform(new TreeTransformer(function(node) {
&& !fn.contains_this() if (node instanceof AST_SymbolRef) {
var def = node.definition();
if (fn.variables.get(node.name) !== def) {
if (exp !== fn) def.references.push(node);
return node;
}
var index = resolve_index(def);
var arg = args[index];
if (!arg) return make_node(AST_Undefined, self);
args[index] = null;
var parent = this.parent();
return parent ? maintain_this_binding(compressor, parent, node, arg) : arg;
}
})));
var node = make_sequence(self, args.filter(function(arg) {
return arg;
})).optimize(compressor);
node = maintain_this_binding(compressor, compressor.parent(), compressor.self(), node);
if (best_of(compressor, self, node) === node) return node;
}
var scope, in_loop, level = -1;
if ((exp === fn || compressor.option("unused") && exp.definition().references.length == 1)
&& can_inject_symbols()) { && can_inject_symbols()) {
fn._squeezed = true; fn._squeezed = true;
if (exp !== fn) fn.parent_scope = exp.scope; if (exp !== fn) fn.parent_scope = exp.scope;
return make_sequence(self, flatten_fn()).optimize(compressor); var node = make_sequence(self, flatten_fn()).optimize(compressor);
return maintain_this_binding(compressor, compressor.parent(), compressor.self(), node);
}
} }
if (compressor.option("side_effects") if (compressor.option("side_effects")
&& all(fn.body, is_empty) && all(fn.body, is_empty)
@@ -5851,10 +5909,12 @@ merge(Compressor.prototype, {
for (var i = 0; i < len; i++) { for (var i = 0; i < len; i++) {
var line = fn.body[i]; var line = fn.body[i];
if (line instanceof AST_Var) { if (line instanceof AST_Var) {
if (stat && !all(line.definitions, function(var_def) { var assigned = var_assigned || !all(line.definitions, function(var_def) {
return !var_def.value; return !var_def.value;
})) { });
return false; if (assigned) {
var_assigned = true;
if (stat) return false;
} }
} else if (line instanceof AST_Defun || line instanceof AST_EmptyStatement) { } else if (line instanceof AST_Defun || line instanceof AST_EmptyStatement) {
continue; continue;
@@ -5867,6 +5927,61 @@ merge(Compressor.prototype, {
return return_value(stat); return return_value(stat);
} }
function resolve_index(def) {
for (var i = fn.argnames.length; --i >= 0;) {
if (fn.argnames[i].definition() === def) return i;
}
}
function can_substitute_directly() {
if (var_assigned) return;
if (compressor.option("inline") <= 1 && fn.argnames.length) return;
if (!fn.variables.all(function(def) {
return def.references.length < 2 && def.orig[0] instanceof AST_SymbolFunarg;
})) return;
var abort = false;
var begin;
var in_order = [];
var side_effects = false;
value.walk(new TreeWalker(function(node) {
if (abort) return true;
if (node instanceof AST_Binary && lazy_op[node.operator]
|| node instanceof AST_Conditional) {
in_order = null;
return;
}
if (node instanceof AST_Scope) return abort = true;
var def;
if (node instanceof AST_SymbolRef && fn.variables.get(node.name) === (def = node.definition())) {
if (def.init instanceof AST_Defun) return abort = true;
if (is_lhs(node, this.parent())) return abort = true;
var index = resolve_index(def);
if (!(begin < index)) begin = index;
if (!in_order) return;
if (side_effects) {
in_order = null;
} else {
in_order.push(fn.argnames[index]);
}
return;
}
if (node.has_side_effects(compressor)) side_effects = true;
}));
if (abort) return;
var end = self.args.length;
if (in_order && fn.argnames.length >= end) {
end = fn.argnames.length;
while (end-- > begin && fn.argnames[end] === in_order.pop());
end++;
}
var scope = side_effects && compressor.find_parent(AST_Scope);
return end <= begin || all(self.args.slice(begin, end), side_effects ? function(funarg) {
return funarg.is_constant_expression(scope);
} : function(funarg) {
return !funarg.has_side_effects(compressor);
});
}
function var_exists(defined, name) { function var_exists(defined, name) {
return defined[name] || identifier_atom[name] || scope.var_names()[name]; return defined[name] || identifier_atom[name] || scope.var_names()[name];
} }
@@ -6162,15 +6277,24 @@ merge(Compressor.prototype, {
}); });
AST_Binary.DEFMETHOD("lift_sequences", function(compressor) { AST_Binary.DEFMETHOD("lift_sequences", function(compressor) {
if (compressor.option("sequences")) { if (this.left instanceof AST_PropAccess) {
if (!(this.left.expression instanceof AST_Sequence)) return this;
var x = this.left.expression.expressions.slice();
var e = this.clone();
e.left = e.left.clone();
e.left.expression = x.pop();
x.push(e);
return make_sequence(this, x);
}
if (this.left instanceof AST_Sequence) { if (this.left instanceof AST_Sequence) {
var x = this.left.expressions.slice(); var x = this.left.expressions.slice();
var e = this.clone(); var e = this.clone();
e.left = x.pop(); e.left = x.pop();
x.push(e); x.push(e);
return make_sequence(this, x).optimize(compressor); return make_sequence(this, x);
} }
if (this.right instanceof AST_Sequence && !this.left.has_side_effects(compressor)) { if (this.right instanceof AST_Sequence) {
if (this.left.has_side_effects(compressor)) return this;
var assign = this.operator == "=" && this.left instanceof AST_SymbolRef; var assign = this.operator == "=" && this.left instanceof AST_SymbolRef;
var x = this.right.expressions; var x = this.right.expressions;
var last = x.length - 1; var last = x.length - 1;
@@ -6182,14 +6306,14 @@ merge(Compressor.prototype, {
var e = this.clone(); var e = this.clone();
e.right = x.pop(); e.right = x.pop();
x.push(e); x.push(e);
return make_sequence(this, x).optimize(compressor); return make_sequence(this, x);
} else if (i > 0) { }
if (i > 0) {
var e = this.clone(); var e = this.clone();
e.right = make_sequence(this.right, x.slice(i)); e.right = make_sequence(this.right, x.slice(i));
x = x.slice(0, i); x = x.slice(0, i);
x.push(e); x.push(e);
return make_sequence(this, x).optimize(compressor); return make_sequence(this, x);
}
} }
} }
return this; return this;
@@ -6240,8 +6364,10 @@ merge(Compressor.prototype, {
// result. hence, force switch. // result. hence, force switch.
reverse(); reverse();
} }
if (compressor.option("sequences")) {
var seq = self.lift_sequences(compressor); var seq = self.lift_sequences(compressor);
if (seq !== self) return seq; if (seq !== self) return seq.optimize(compressor);
}
if (compressor.option("assignments") && lazy_op[self.operator]) { if (compressor.option("assignments") && lazy_op[self.operator]) {
var assign = self.right; var assign = self.right;
// a || (a = x) => a = a || x // a || (a = x) => a = a || x
@@ -6450,7 +6576,7 @@ merge(Compressor.prototype, {
} }
// (x || false) && y => x ? y : false // (x || false) && y => x ? y : false
if (self.left.operator == "||") { if (self.left.operator == "||") {
var lr = self.left.right.tail_node().evaluate(compressor); var lr = self.left.right.evaluate(compressor, true);
if (!lr) return make_node(AST_Conditional, self, { if (!lr) return make_node(AST_Conditional, self, {
condition: self.left.left, condition: self.left.left,
consequent: self.right, consequent: self.right,
@@ -6484,7 +6610,7 @@ merge(Compressor.prototype, {
} }
// x && true || y => x ? true : y // x && true || y => x ? true : y
if (self.left.operator == "&&") { if (self.left.operator == "&&") {
var lr = self.left.right.is_truthy() || self.left.right.tail_node().evaluate(compressor); var lr = self.left.right.is_truthy() || self.left.right.evaluate(compressor, true);
if (lr && !(lr instanceof AST_Node)) return make_node(AST_Conditional, self, { if (lr && !(lr instanceof AST_Node)) return make_node(AST_Conditional, self, {
condition: self.left.left, condition: self.left.left,
consequent: self.left.right, consequent: self.left.right,
@@ -6831,7 +6957,7 @@ merge(Compressor.prototype, {
if (node.truthy) return true; if (node.truthy) return true;
if (node.falsy) return false; if (node.falsy) return false;
if (node.is_truthy()) return true; if (node.is_truthy()) return true;
return node.evaluate(compressor); return node.evaluate(compressor, true);
} }
function is_indexFn(node) { function is_indexFn(node) {
@@ -6953,9 +7079,9 @@ merge(Compressor.prototype, {
var fn = node.fixed_value(); var fn = node.fixed_value();
if (!(fn instanceof AST_Lambda)) return; if (!(fn instanceof AST_Lambda)) return;
if (!fn.name) return; if (!fn.name) return;
var fn_def = fn.name.definition(); if (fn.name.definition() !== def) return;
if (fn_def.scope !== fn.name.scope) return; if (def.scope !== fn.name.scope) return;
if (fixed.variables.get(fn.name.name) !== fn_def) return; if (fixed.variables.get(fn.name.name) !== def) return;
fn.name = fn.name.clone(); fn.name = fn.name.clone();
var value_def = value.variables.get(fn.name.name) || value.def_function(fn.name); var value_def = value.variables.get(fn.name.name) || value.def_function(fn.name);
node.thedef = value_def; node.thedef = value_def;
@@ -7158,8 +7284,10 @@ merge(Compressor.prototype, {
|| parent instanceof AST_UnaryPrefix); || parent instanceof AST_UnaryPrefix);
} }
} }
if (compressor.option("sequences")) {
var seq = self.lift_sequences(compressor); var seq = self.lift_sequences(compressor);
if (seq !== self) return seq; if (seq !== self) return seq.optimize(compressor);
}
if (!compressor.option("assignments")) return self; if (!compressor.option("assignments")) return self;
if (self.operator == "=" && self.left instanceof AST_SymbolRef && self.right instanceof AST_Binary) { if (self.operator == "=" && self.left instanceof AST_SymbolRef && self.right instanceof AST_Binary) {
// x = expr1 OP expr2 // x = expr1 OP expr2
@@ -7581,16 +7709,14 @@ merge(Compressor.prototype, {
value = value.fixed_value(); value = value.fixed_value();
} }
if (!value) return false; if (!value) return false;
return !(value instanceof AST_Lambda) if (!(value instanceof AST_Lambda)) return true;
|| compressor.parent() instanceof AST_New var parent = compressor.parent();
|| !value.contains_this(); if (parent.TYPE != "Call") return true;
if (parent.expression !== compressor.self()) return true;
return !value.contains_this();
} }
OPT(AST_Sub, function(self, compressor) { OPT(AST_Sub, function(self, compressor) {
if (compressor.option("sequences") && compressor.parent().TYPE != "Call") {
var seq = lift_sequence_in_expression(self, compressor);
if (seq !== self) return seq.optimize(compressor);
}
var expr = self.expression; var expr = self.expression;
var prop = self.property; var prop = self.property;
if (compressor.option("properties")) { if (compressor.option("properties")) {
@@ -7661,6 +7787,10 @@ merge(Compressor.prototype, {
} }
} }
if (is_lhs(compressor.self(), parent)) return self; if (is_lhs(compressor.self(), parent)) return self;
if (compressor.option("sequences") && compressor.parent().TYPE != "Call") {
var seq = lift_sequence_in_expression(self, compressor);
if (seq !== self) return seq.optimize(compressor);
}
if (key !== prop) { if (key !== prop) {
var sub = self.flatten_object(property, compressor); var sub = self.flatten_object(property, compressor);
if (sub) { if (sub) {
@@ -7755,10 +7885,6 @@ merge(Compressor.prototype, {
}); });
OPT(AST_Dot, function(self, compressor) { OPT(AST_Dot, function(self, compressor) {
if (compressor.option("sequences") && compressor.parent().TYPE != "Call") {
var seq = lift_sequence_in_expression(self, compressor);
if (seq !== self) return seq.optimize(compressor);
}
if (self.property == "arguments" || self.property == "caller") { if (self.property == "arguments" || self.property == "caller") {
AST_Node.warn("Function.prototype.{prop} not supported [{file}:{line},{col}]", { AST_Node.warn("Function.prototype.{prop} not supported [{file}:{line},{col}]", {
prop: self.property, prop: self.property,
@@ -7768,6 +7894,10 @@ merge(Compressor.prototype, {
}); });
} }
if (is_lhs(compressor.self(), compressor.parent())) return self; if (is_lhs(compressor.self(), compressor.parent())) return self;
if (compressor.option("sequences") && compressor.parent().TYPE != "Call") {
var seq = lift_sequence_in_expression(self, compressor);
if (seq !== self) return seq.optimize(compressor);
}
if (compressor.option("unsafe_proto") if (compressor.option("unsafe_proto")
&& self.expression instanceof AST_Dot && self.expression instanceof AST_Dot
&& self.expression.property == "prototype") { && self.expression.property == "prototype") {

View File

@@ -203,6 +203,7 @@ function minify(files, options) {
if (!HOP(options.output, "code") || options.output.code) { if (!HOP(options.output, "code") || options.output.code) {
if (options.sourceMap) { if (options.sourceMap) {
options.output.source_map = SourceMap({ options.output.source_map = SourceMap({
content: options.sourceMap.includeSources,
file: options.sourceMap.filename, file: options.sourceMap.filename,
orig: source_maps, orig: source_maps,
root: options.sourceMap.root root: options.sourceMap.root
@@ -211,10 +212,8 @@ function minify(files, options) {
if (files instanceof AST_Toplevel) { if (files instanceof AST_Toplevel) {
throw new Error("original source content unavailable"); throw new Error("original source content unavailable");
} else for (var name in files) if (HOP(files, name)) { } else for (var name in files) if (HOP(files, name)) {
options.output.source_map.get().setSourceContent(name, files[name]); options.output.source_map.setSourceContent(name, files[name]);
} }
} else {
options.output.source_map.get()._sourcesContents = null;
} }
} }
delete options.output.ast; delete options.output.ast;

View File

@@ -783,6 +783,8 @@ function OutputStream(options) {
var p = output.parent(); var p = output.parent();
if (p instanceof AST_PropAccess && p.expression === this) { if (p instanceof AST_PropAccess && p.expression === this) {
var value = this.value; 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))) { if (value < 0 || /^0/.test(make_num(value))) {
return true; return true;
} }
@@ -1352,11 +1354,18 @@ function OutputStream(options) {
DEFPRINT(AST_RegExp, function(self, output) { DEFPRINT(AST_RegExp, function(self, output) {
var regexp = self.value; var regexp = self.value;
var str = regexp.toString(); var str = regexp.toString();
var end = str.lastIndexOf("/");
if (regexp.raw_source) { 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) { output.print(output.to_utf8(str).replace(/\\(?:\0(?![0-9])|[^\0])/g, function(match) {
switch (seq[1]) { switch (match[1]) {
case "\n": return "\\n"; case "\n": return "\\n";
case "\r": return "\\r"; case "\r": return "\\r";
case "\t": return "\t"; case "\t": return "\t";
@@ -1366,7 +1375,7 @@ function OutputStream(options) {
case "\x0B": return "\v"; case "\x0B": return "\v";
case "\u2028": return "\\u2028"; case "\u2028": return "\\u2028";
case "\u2029": return "\\u2029"; case "\u2029": return "\\u2029";
default: return seq; default: return match;
} }
}).replace(/[\n\r\u2028\u2029]/g, function(c) { }).replace(/[\n\r\u2028\u2029]/g, function(c) {
switch (c) { switch (c) {

View File

@@ -241,16 +241,16 @@ function tokenizer($TEXT, filename, html5_comments, shebang) {
if (signal_eof && !ch) if (signal_eof && !ch)
throw EX_EOF; throw EX_EOF;
if (NEWLINE_CHARS[ch]) { if (NEWLINE_CHARS[ch]) {
S.newline_before = S.newline_before || !in_string;
++S.line;
S.col = 0; S.col = 0;
if (!in_string && ch == "\r" && peek() == "\n") { S.line++;
// treat a \r\n sequence as a single \n if (!in_string) S.newline_before = true;
++S.pos; if (ch == "\r" && peek() == "\n") {
// treat `\r\n` as `\n`
S.pos++;
ch = "\n"; ch = "\n";
} }
} else { } else {
++S.col; S.col++;
} }
return ch; return ch;
} }

View File

@@ -162,17 +162,22 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options) {
} }
if (node instanceof AST_SymbolRef) { if (node instanceof AST_SymbolRef) {
var name = node.name; var name = node.name;
if (name == "eval" && tw.parent() instanceof AST_Call) {
for (var s = node.scope; s && !s.uses_eval; s = s.parent_scope) {
s.uses_eval = true;
}
}
var sym = node.scope.find_variable(name); var sym = node.scope.find_variable(name);
if (!sym) { if (!sym) {
sym = self.def_global(node); sym = self.def_global(node);
} else if (sym.scope instanceof AST_Lambda && name == "arguments") { } else if (sym.scope instanceof AST_Lambda && name == "arguments") {
sym.scope.uses_arguments = true; sym.scope.uses_arguments = true;
} }
if (name == "eval") {
var parent = tw.parent();
if (parent.TYPE == "Call" && parent.expression === node) {
for (var s = node.scope; s && !s.uses_eval; s = s.parent_scope) {
s.uses_eval = true;
}
} else if (sym.undeclared) {
self.uses_eval = true;
}
}
node.thedef = sym; node.thedef = sym;
node.reference(options); node.reference(options);
return true; return true;
@@ -219,7 +224,12 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options) {
var redef; var redef;
while (redef = new_def.redefined()) new_def = redef; while (redef = new_def.redefined()) new_def = redef;
} else { } 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) { old_def.orig.concat(old_def.references).forEach(function(node) {
node.thedef = new_def; node.thedef = new_def;

View File

@@ -43,62 +43,144 @@
"use strict"; "use strict";
// a small wrapper around fitzgen's source-map library var vlq_char = characters("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/");
var vlq_bits = vlq_char.reduce(function(map, ch, bits) {
map[ch] = bits;
return map;
}, Object.create(null));
function vlq_decode(indices, str) {
var value = 0;
var shift = 0;
for (var i = 0, j = 0; i < str.length; i++) {
var bits = vlq_bits[str[i]];
value += (bits & 31) << shift;
if (bits & 32) {
shift += 5;
} else {
indices[j++] += value & 1 ? 0x80000000 | -(value >> 1) : value >> 1;
value = shift = 0;
}
}
return j;
}
function vlq_encode(num) {
var result = "";
num = Math.abs(num) << 1 | num >>> 31;
do {
var bits = num & 31;
if (num >>>= 5) bits |= 32;
result += vlq_char[bits];
} while (num);
return result;
}
function create_array_map() {
var map = Object.create(null);
var array = [];
array.index = function(name) {
if (!HOP(map, name)) {
map[name] = array.length;
array.push(name);
}
return map[name];
};
return array;
}
function SourceMap(options) { function SourceMap(options) {
options = defaults(options, { options = defaults(options, {
content: false,
file: null, file: null,
root: null, root: null,
orig: null, orig: null,
orig_line_diff: 0,
dest_line_diff: 0,
}, true); }, true);
var generator = new MOZ_SourceMap.SourceMapGenerator({ var sources = create_array_map();
file: options.file, var sources_content = options.content && Object.create(null);
sourceRoot: options.root var names = create_array_map();
var mappings = "";
if (options.orig) Object.keys(options.orig).forEach(function(name) {
var map = options.orig[name];
var indices = [ 0, 0, 1, 0, 0 ];
map.mappings = map.mappings.split(/;/).map(function(line) {
indices[0] = 0;
return line.split(/,/).map(function(segment) {
return indices.slice(0, vlq_decode(indices, segment));
}); });
var maps = options.orig && Object.create(null);
if (maps) for (var source in options.orig) {
var map = new MOZ_SourceMap.SourceMapConsumer(options.orig[source]);
if (Array.isArray(options.orig[source].sources)) {
map._sources.toArray().forEach(function(source) {
var sourceContent = map.sourceContentFor(source, true);
if (sourceContent) generator.setSourceContent(source, sourceContent);
}); });
if (!sources_content || !map.sourcesContent) return;
for (var i = 0; i < map.sources.length; i++) {
var content = map.sourcesContent[i];
if (content) sources_content[map.sources[i]] = content;
} }
maps[source] = map; });
} var generated_line = 1;
var generated_column = 0;
var source_index = 0;
var original_line = 1;
var original_column = 0;
var name_index = 0;
return { return {
add: function(source, gen_line, gen_col, orig_line, orig_col, name) { add: options.orig ? function(source, gen_line, gen_col, orig_line, orig_col, name) {
var map = maps && maps[source]; var map = options.orig[source];
if (map) { if (map) {
var info = map.originalPositionFor({ var segments = map.mappings[orig_line - 1];
line: orig_line, if (!segments) return;
column: orig_col var indices;
}); for (var i = 0; i < segments.length; i++) {
if (info.source === null) return; var col = segments[i][0];
source = info.source; if (orig_col >= col) indices = segments[i];
orig_line = info.line; if (orig_col <= col) break;
orig_col = info.column;
name = info.name || name;
} }
generator.addMapping({ if (!indices || indices.length < 4) return;
name: name, source = map.sources[indices[1]];
source: source, orig_line = indices[2];
generated: { orig_col = indices[3];
line: gen_line + options.dest_line_diff, if (indices.length > 4) name = map.names[indices[4]];
column: gen_col
},
original: {
line: orig_line + options.orig_line_diff,
column: orig_col
} }
}); add(source, gen_line, gen_col, orig_line, orig_col, name);
}, } : add,
get: function() { setSourceContent: sources_content ? function(source, content) {
return generator; sources_content[source] = content;
}, } : noop,
toString: function() { toString: function() {
return JSON.stringify(generator.toJSON()); return JSON.stringify({
version: 3,
file: options.file || undefined,
sourceRoot: options.root || undefined,
sources: sources,
sourcesContent: sources_content ? sources.map(function(source) {
return sources_content[source] || null;
}) : undefined,
names: names,
mappings: mappings,
});
} }
}; };
function add(source, gen_line, gen_col, orig_line, orig_col, name) {
if (generated_line < gen_line) {
generated_column = 0;
do {
mappings += ";";
} while (++generated_line < gen_line);
} else if (mappings) {
mappings += ",";
}
mappings += vlq_encode(gen_col - generated_column);
generated_column = gen_col;
var src_idx = sources.index(source);
mappings += vlq_encode(src_idx - source_index);
source_index = src_idx;
mappings += vlq_encode(orig_line - original_line);
original_line = orig_line;
mappings += vlq_encode(orig_col - original_column);
original_column = orig_col;
if (name != null) {
var name_idx = names.index(name);
mappings += vlq_encode(name_idx - name_index);
name_index = name_idx;
}
}
} }

View File

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

View File

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

View File

@@ -3,7 +3,7 @@
"description": "JavaScript parser, mangler/compressor and beautifier toolkit", "description": "JavaScript parser, mangler/compressor and beautifier toolkit",
"author": "Mihai Bazon <mihai.bazon@gmail.com> (http://lisperator.net/)", "author": "Mihai Bazon <mihai.bazon@gmail.com> (http://lisperator.net/)",
"license": "BSD-2-Clause", "license": "BSD-2-Clause",
"version": "3.7.7", "version": "3.9.0",
"engines": { "engines": {
"node": ">=0.8.0" "node": ">=0.8.0"
}, },
@@ -23,8 +23,7 @@
"LICENSE" "LICENSE"
], ],
"dependencies": { "dependencies": {
"commander": "~2.20.3", "commander": "~2.20.3"
"source-map": "~0.6.1"
}, },
"devDependencies": { "devDependencies": {
"acorn": "~7.1.0", "acorn": "~7.1.0",

View File

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

View File

@@ -7765,3 +7765,44 @@ issue_3700: {
} }
expect_stdout: "PASS" 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

@@ -1240,11 +1240,11 @@ issue_2535_1: {
expect: { expect: {
y(); y();
x() && y(); x() && y();
(x(), 1) && y(); x(), y();
x() && y(); x() && y();
x() && y(); x() && y();
x() && y(); x() && y();
(x(), 0) && y(); x();
} }
} }

View File

@@ -1191,10 +1191,10 @@ issue_2105_1: {
input: { input: {
!function(factory) { !function(factory) {
factory(); factory();
}( function() { }(function() {
return function(fn) { return function(fn) {
fn()().prop(); fn()().prop();
}( function() { }(function() {
function bar() { function bar() {
var quux = function() { var quux = function() {
console.log("PASS"); console.log("PASS");
@@ -1205,7 +1205,7 @@ issue_2105_1: {
return { prop: foo }; return { prop: foo };
} }
return bar; return bar;
} ); });
}); });
} }
expect: { expect: {
@@ -1235,10 +1235,10 @@ issue_2105_2: {
input: { input: {
!function(factory) { !function(factory) {
factory(); factory();
}( function() { }(function() {
return function(fn) { return function(fn) {
fn()().prop(); fn()().prop();
}( function() { }(function() {
function bar() { function bar() {
var quux = function() { var quux = function() {
console.log("PASS"); console.log("PASS");
@@ -1249,7 +1249,7 @@ issue_2105_2: {
return { prop: foo }; return { prop: foo };
} }
return bar; return bar;
} ); });
}); });
} }
expect: { expect: {
@@ -1258,6 +1258,44 @@ issue_2105_2: {
expect_stdout: "PASS" 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: { issue_2226_1: {
options = { options = {
side_effects: true, side_effects: true,
@@ -2330,7 +2368,7 @@ function_parameter_ie8: {
(function() { (function() {
(function f() { (function f() {
console.log("PASS"); console.log("PASS");
})(0); })();
})(); })();
} }
expect_stdout: "PASS" expect_stdout: "PASS"
@@ -2375,3 +2413,34 @@ issue_3673: {
} }
expect_stdout: "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,32 @@ collapse_vars_regexp: {
"abbb", "abbb",
] ]
} }
issue_3738: {
options = {
evaluate: true,
}
input: {
console.log(1 / (0 + ([] - 1) % 1));
}
expect: {
console.log(1 / (0 + ([] - 1) % 1));
}
expect_stdout: "Infinity"
}
issue_3755: {
options = {
booleans: true,
evaluate: true,
unsafe: true,
unsafe_math: true,
}
input: {
console.log((/4/.exec(1 + (!0 - 5 / "23")) || 0).p);
}
expect: {
console.log((/4/.exec(!0 - 5 / "23" + 1), 0).p);
}
expect_stdout: "undefined"
}

View File

@@ -342,11 +342,7 @@ inner_ref: {
}(2)); }(2));
} }
expect: { expect: {
console.log(function(a) { console.log(1, void 0);
return a;
}(1), function(a) {
return a;
}());
} }
expect_stdout: "1 undefined" expect_stdout: "1 undefined"
} }
@@ -1024,9 +1020,7 @@ issue_2616: {
} }
expect: { expect: {
var c = "FAIL"; var c = "FAIL";
!function(NaN) { (true << []) - NaN || (c = "PASS");
(true << NaN) - 0/0 || (c = "PASS");
}([]);
console.log(c); console.log(c);
} }
expect_stdout: "PASS" expect_stdout: "PASS"
@@ -1276,7 +1270,7 @@ issue_2630_3: {
(function() { (function() {
(function f1(a) { (function f1(a) {
f2(); f2();
--x >= 0 && f1({}); --x >= 0 && f1();
})(a++); })(a++);
function f2() { function f2() {
a++; a++;
@@ -1577,7 +1571,23 @@ issue_2663_3: {
] ]
} }
duplicate_argnames: { duplicate_argnames_1: {
options = {
inline: true,
side_effects: true,
}
input: {
console.log(function(a, a, a) {
return a;
}("FAIL", 42, "PASS"));
}
expect: {
console.log("PASS");
}
expect_stdout: "PASS"
}
duplicate_argnames_2: {
options = { options = {
inline: true, inline: true,
reduce_vars: true, reduce_vars: true,
@@ -1595,7 +1605,31 @@ duplicate_argnames: {
} }
expect: { expect: {
var a = "PASS"; var a = "PASS";
console, b && (a = "FAIL"); console, void 0 && (a = "FAIL");
console.log(a);
}
expect_stdout: "PASS"
}
duplicate_argnames_3: {
options = {
inline: true,
reduce_vars: true,
side_effects: true,
toplevel: true,
unused: true,
}
input: {
var a = "FAIL";
function f(b, b, b) {
b && (a = "PASS");
}
f(null, 0, console, "42".toString());
console.log(a);
}
expect: {
var a = "FAIL";
b = console, "42".toString(), b && (a = "PASS");
var b; var b;
console.log(a); console.log(a);
} }
@@ -1754,8 +1788,7 @@ inline_2: {
} }
expect: { expect: {
console.log(1); console.log(1);
a = 2, console.log(a); console.log(2);
var a;
(function(b) { (function(b) {
var c = b; var c = b;
console.log(c); console.log(c);
@@ -1788,8 +1821,7 @@ inline_3: {
} }
expect: { expect: {
console.log(1); console.log(1);
a = 2, console.log(a); console.log(2);
var a;
b = 3, c = b, console.log(c); b = 3, c = b, console.log(c);
var b, c; var b, c;
} }
@@ -1820,8 +1852,7 @@ inline_true: {
} }
expect: { expect: {
console.log(1); console.log(1);
a = 2, console.log(a); console.log(2);
var a;
b = 3, c = b, console.log(c); b = 3, c = b, console.log(c);
var b, c; var b, c;
} }
@@ -1857,10 +1888,9 @@ use_before_init_in_loop: {
expect_stdout: "PASS" expect_stdout: "PASS"
} }
duplicate_arg_var: { duplicate_arg_var_1: {
options = { options = {
inline: true, inline: true,
toplevel: true,
} }
input: { input: {
console.log(function(b) { console.log(function(b) {
@@ -1869,7 +1899,41 @@ duplicate_arg_var: {
}("PASS")); }("PASS"));
} }
expect: { expect: {
console.log((b = "PASS", b)); console.log("PASS");
}
expect_stdout: "PASS"
}
duplicate_arg_var_2: {
options = {
inline: true,
toplevel: true,
}
input: {
console.log(function(b) {
return b + "SS";
var b;
}("PA"));
}
expect: {
console.log("PA" + "SS");
}
expect_stdout: "PASS"
}
duplicate_arg_var_3: {
options = {
inline: true,
toplevel: true,
}
input: {
console.log(function(b) {
return b + "SS";
var b;
}("PA", "42".toString()));
}
expect: {
console.log((b = "PA", "42".toString(), b + "SS"));
var b; var b;
} }
expect_stdout: "PASS" expect_stdout: "PASS"
@@ -2017,10 +2081,8 @@ issue_3016_1: {
expect: { expect: {
var b = 1; var b = 1;
do { do {
a = 3, 3[b];
a[b];
} while(0); } while(0);
var a;
console.log(b); console.log(b);
} }
expect_stdout: "1" expect_stdout: "1"
@@ -2528,10 +2590,9 @@ cross_references_2: {
options = { options = {
collapse_vars: true, collapse_vars: true,
evaluate: true, evaluate: true,
hoist_props: true,
inline: true, inline: true,
passes: 4, passes: 6,
pure_getters: true, properties: true,
reduce_vars: true, reduce_vars: true,
sequences: true, sequences: true,
side_effects: true, side_effects: true,
@@ -3657,9 +3718,7 @@ pr_3595_3: {
var g = [ "PASS" ]; var g = [ "PASS" ];
console.log(function(problem) { console.log(function(problem) {
return g[problem]; return g[problem];
}(function(arg) { }(g.indexOf("PASS")));
return g.indexOf(arg);
}("PASS")));
} }
expect_stdout: "PASS" expect_stdout: "PASS"
} }
@@ -3785,3 +3844,221 @@ issue_3679_3: {
} }
expect_stdout: "PASS" expect_stdout: "PASS"
} }
preceding_side_effects: {
options = {
inline: true,
}
input: {
console.log(function(a, b, c) {
return b;
}(console, "PASS", 42));
}
expect: {
console.log((console, 42, "PASS"));
}
expect_stdout: "PASS"
}
trailing_side_effects: {
options = {
inline: true,
}
input: {
console.log(function(a, b, c) {
return b;
}(42, "PASS", console));
}
expect: {
console.log(function(a, b, c) {
return b;
}(42, "PASS", console));
}
expect_stdout: "PASS"
}
preserve_binding_1: {
options = {
inline: true,
}
input: {
var o = {
f: function() {
return this === o ? "FAIL" : "PASS";
},
};
console.log(function(a) {
return a;
}(o.f)());
}
expect: {
var o = {
f: function() {
return this === o ? "FAIL" : "PASS";
},
};
console.log((0, o.f)());
}
expect_stdout: "PASS"
}
preserve_binding_2: {
options = {
collapse_vars: true,
inline: true,
unused: true,
}
input: {
var o = {
f: function() {
return this === o ? "FAIL" : "PASS";
},
};
console.log(function(a) {
return a;
}(o.f)());
}
expect: {
var o = {
f: function() {
return this === o ? "FAIL" : "PASS";
},
};
console.log((0, o.f)());
}
expect_stdout: "PASS"
}
issue_3770: {
options = {
inline: true,
reduce_vars: true,
side_effects: true,
unused: true,
}
input: {
(function() {
function f(a, a) {
var b = function() {
return a || "PASS";
}();
console.log(b);
}
f("FAIL");
})();
}
expect: {
(function() {
b = a || "PASS",
console.log(b);
var a, b;
})();
}
expect_stdout: "PASS"
}
issue_3771: {
options = {
inline: true,
reduce_vars: true,
side_effects: true,
toplevel: true,
unused: true,
}
input: {
try {
function f(a) {
var a = f(1234);
}
f();
} catch (e) {
console.log("PASS");
}
}
expect: {
try {
(function f(a) {
f();
})();
} catch (e) {
console.log("PASS");
}
}
expect_stdout: "PASS"
}
issue_3772: {
options = {
collapse_vars: true,
dead_code: true,
inline: true,
reduce_vars: true,
side_effects: true,
toplevel: true,
unused: true,
}
input: {
var a = "PASS";
function f() {
return a;
}
var b = f();
function g() {
console.log(f());
}
g();
}
expect: {
var a = "PASS";
console.log(a);
}
expect_stdout: "PASS"
}
issue_3777_1: {
options = {
inline: true,
reduce_vars: true,
side_effects: true,
}
input: {
(function() {
ff && ff(NaN);
function ff(a) {
var a = console.log("PASS");
}
})();
}
expect: {
(function() {
ff && ff(NaN);
function ff(a) {
var a = console.log("PASS");
}
})();
}
expect_stdout: "PASS"
}
issue_3777_2: {
options = {
inline: true,
pure_getters: "strict",
reduce_vars: true,
side_effects: true,
toplevel: true,
}
input: {
ff(ff.p);
function ff(a) {
var a = console.log("PASS");
}
}
expect: {
ff(ff.p);
function ff(a) {
var a = console.log("PASS");
}
}
expect_stdout: "PASS"
}

View File

@@ -2361,3 +2361,62 @@ issue_3542: {
} }
expect_stdout: "1" 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

@@ -26,7 +26,7 @@ warn: {
}().length); }().length);
} }
expect_warnings: [ expect_warnings: [
"WARN: Function.prototype.caller not supported [test/compress/issue-2719.js:5,19]",
"WARN: Function.prototype.arguments not supported [test/compress/issue-2719.js:5,19]", "WARN: Function.prototype.arguments not supported [test/compress/issue-2719.js:5,19]",
"WARN: Function.prototype.caller not supported [test/compress/issue-2719.js:5,19]",
] ]
} }

128
test/compress/issue-3768.js Normal file
View File

@@ -0,0 +1,128 @@
mangle: {
mangle = {
toplevel: true,
}
input: {
var e = eval, x = 42;
(function() {
console.log(e("typeof x"));
})();
}
expect: {
var e = eval, x = 42;
(function() {
console.log(e("typeof x"));
})();
}
expect_stdout: true
}
compress: {
options = {
collapse_vars: true,
inline: true,
unused: true,
}
input: {
console.log(function() {
var a = 42;
return eval("typeof a");
}(), function(e) {
var a = null;
return e("typeof a");
}(eval), function(eval) {
var a = false;
return eval("typeof a");
}(eval), function(f) {
var a = "STRING";
var eval = f;
return eval("typeof a");
}(eval), function(g) {
var a = eval;
function eval() {
return g;
}
return eval()("typeof a");
}(eval));
}
expect: {
console.log(function() {
var a = 42;
return eval("typeof a");
}(), (0, eval)("typeof a"), function(eval) {
var a = false;
return eval("typeof a");
}(eval), function(f) {
var a = "STRING";
var eval = f;
return eval("typeof a");
}(eval), function(g) {
var a = eval;
function eval() {
return g;
}
return eval()("typeof a");
}(eval));
}
expect_stdout: "number undefined boolean string undefined"
}
call_arg_1: {
mangle = {
toplevel: true,
}
input: {
var z = "foo";
(function() {
var z = false;
(function(e) {
var z = 42;
e("console.log(typeof z)");
})(eval);
})();
}
expect: {
var z = "foo";
(function() {
var o = false;
(function(o) {
var a = 42;
o("console.log(typeof z)");
})(eval);
})();
}
expect_stdout: true
}
call_arg_2: {
mangle = {
toplevel: true,
}
input: {
function eval() {
console.log("PASS");
}
var z = "foo";
(function() {
var z = false;
(function(e) {
var z = 42;
e("console.log(typeof z)");
})(eval);
})();
}
expect: {
function n() {
console.log("PASS");
}
var o = "foo";
(function() {
var o = false;
(function(o) {
var n = 42;
o("console.log(typeof z)");
})(n);
})();
}
expect_stdout: "PASS"
}

View File

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

View File

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

View File

@@ -8,7 +8,7 @@ remove_sequence: {
(0, 1, _decorators.logThis)(); (0, 1, _decorators.logThis)();
} }
expect: { expect: {
eval(); (0, eval)();
logThis(); logThis();
(0, _decorators.logThis)(); (0, _decorators.logThis)();
} }

View File

@@ -53,21 +53,24 @@ this_binding_conditionals: {
this_binding_collapse_vars: { this_binding_collapse_vars: {
options = { options = {
collapse_vars: true, collapse_vars: true,
toplevel: true,
unused: true, unused: true,
} }
input: { input: {
function f() {
"use strict"; "use strict";
var c = a; c(); var c = a; c();
var d = a.b; d(); var d = a.b; d();
var e = eval; e(); var e = eval; e();
} }
}
expect: { expect: {
function f() {
"use strict"; "use strict";
a(); a();
(0, a.b)(); (0, a.b)();
(0, eval)(); (0, eval)();
} }
}
} }
this_binding_side_effects: { this_binding_side_effects: {
@@ -97,7 +100,7 @@ this_binding_side_effects: {
(function(foo) { (function(foo) {
foo(); foo();
(0, foo.bar)(); (0, foo.bar)();
eval("console.log(foo);"); (0, eval)("console.log(foo);");
}()); }());
(function(foo) { (function(foo) {
"use strict"; "use strict";
@@ -144,7 +147,7 @@ this_binding_sequences: {
return eval("this"); return eval("this");
}()), }()),
console.log(typeof function() { console.log(typeof function() {
return eval("this"); return (0, eval)("this");
}()), }()),
console.log(typeof function() { console.log(typeof function() {
"use strict"; "use strict";

View File

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

View File

@@ -817,6 +817,29 @@ issue_2208_5: {
expect_stdout: "42" 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: { issue_2256: {
options = { options = {
side_effects: true, side_effects: true,

View File

@@ -2071,13 +2071,8 @@ issue_1670_6: {
} }
expect: { expect: {
(function(a) { (function(a) {
switch (1) { a = 1;
case a = 1:
console.log(a); console.log(a);
break;
default:
console.log(2);
}
})(1); })(1);
} }
expect_stdout: "1" expect_stdout: "1"
@@ -2294,11 +2289,10 @@ redefine_farg_2: {
console.log(f([]), g([]), h([])); console.log(f([]), g([]), h([]));
} }
expect: { expect: {
console.log((a = [], typeof a), "number",function(a, b) { console.log(typeof [], "number",function(a, b) {
a = b; a = b;
return typeof a; return typeof a;
}([])); }([]));
var a;
} }
expect_stdout: "object number undefined" expect_stdout: "object number undefined"
} }
@@ -6516,17 +6510,17 @@ issue_3240_3: {
} }
expect: { expect: {
(function() { (function() {
(function f(b) { f();
function f(b) {
if (!f.a) f.a = 0; if (!f.a) f.a = 0;
console.log(f.a.toString()); console.log(f.a.toString());
var g = function() { (function() {
(b ? function() {} : function() { (b ? function() {} : function() {
f.a++; f.a++;
f(1); f(1);
})(); })();
};
g();
})(); })();
}
})(); })();
} }
expect_stdout: [ expect_stdout: [
@@ -6560,7 +6554,8 @@ issue_3240_4: {
} }
expect: { expect: {
(function() { (function() {
(function f(b) { f();
function f(b) {
if (!f.a) f.a = 0; if (!f.a) f.a = 0;
console.log(f.a.toString()); console.log(f.a.toString());
(function() { (function() {
@@ -6569,7 +6564,7 @@ issue_3240_4: {
f(1); f(1);
})(); })();
})(); })();
})(); }
})(); })();
} }
expect_stdout: [ expect_stdout: [
@@ -6878,3 +6873,42 @@ issue_3666: {
} }
expect_stdout: "PASS PASS" expect_stdout: "PASS PASS"
} }
issue_3774: {
options = {
reduce_funcs: true,
reduce_vars: true,
unused: true,
}
input: {
var f = function() {
function g() {
if (!g.p) {
g.p = 1;
console.log("PASS");
}
}
return function() {
g();
};
}();
f();
f();
}
expect: {
var f = function() {
function g() {
if (!g.p) {
g.p = 1;
console.log("PASS");
}
}
return function() {
g();
};
}();
f();
f();
}
expect_stdout: "PASS"
}

View File

@@ -186,3 +186,290 @@ issue_3434_3: {
/\nfo\n[\n]o\bbb/; /\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

@@ -1093,3 +1093,22 @@ issue_3490_2: {
} }
expect_stdout: "PASS 42" 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

@@ -1,6 +1,7 @@
exports["Compressor"] = Compressor; exports["Compressor"] = Compressor;
exports["defaults"] = defaults; exports["defaults"] = defaults;
exports["JS_Parse_Error"] = JS_Parse_Error; exports["JS_Parse_Error"] = JS_Parse_Error;
exports["List"] = List;
exports["mangle_properties"] = mangle_properties; exports["mangle_properties"] = mangle_properties;
exports["minify"] = minify; exports["minify"] = minify;
exports["OutputStream"] = OutputStream; exports["OutputStream"] = OutputStream;
@@ -12,3 +13,4 @@ exports["to_ascii"] = to_ascii;
exports["tokenizer"] = tokenizer; exports["tokenizer"] = tokenizer;
exports["TreeTransformer"] = TreeTransformer; exports["TreeTransformer"] = TreeTransformer;
exports["TreeWalker"] = TreeWalker; exports["TreeWalker"] = TreeWalker;
exports["vlq_decode"] = vlq_decode;

View File

@@ -1,2 +1,2 @@
function _toConsumableArray(arr){if(Array.isArray(arr)){for(var i=0,arr2=Array(arr.length);i<arr.length;i++){arr2[i]=arr[i]}return arr2}else{return Array.from(arr)}}var _require=require("bar"),foo=_require.foo;var _require2=require("world"),hello=_require2.hello;foo.x.apply(foo,_toConsumableArray(foo.y(hello.z))); function _toConsumableArray(arr){if(Array.isArray(arr)){for(var i=0,arr2=Array(arr.length);i<arr.length;i++){arr2[i]=arr[i]}return arr2}else{return Array.from(arr)}}var _require=require("bar"),foo=_require.foo;var _require2=require("world"),hello=_require2.hello;foo.x.apply(foo,_toConsumableArray(foo.y(hello.z)));
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImlucHV0Mi5qcyJdLCJuYW1lcyI6WyJyZXF1aXJlIiwiYXJyIl0sIm1hcHBpbmdzIjoiMEpBQWNBLEtBQVFDIiwic291cmNlc0NvbnRlbnQiOlsiY29uc3Qge2Zvb30gPSByZXF1aXJlKFwiYmFyXCIpO1xuY29uc3Qge2hlbGxvfSA9IHJlcXVpcmUoXCJ3b3JsZFwiKTtcblxuZm9vLngoLi4uZm9vLnkoaGVsbG8ueikpO1xuIl19 //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImlucHV0Mi5qcyJdLCJzb3VyY2VzQ29udGVudCI6WyJjb25zdCB7Zm9vfSA9IHJlcXVpcmUoXCJiYXJcIik7XG5jb25zdCB7aGVsbG99ID0gcmVxdWlyZShcIndvcmxkXCIpO1xuXG5mb28ueCguLi5mb28ueShoZWxsby56KSk7XG4iXSwibmFtZXMiOlsicmVxdWlyZSIsImFyciJdLCJtYXBwaW5ncyI6IjBKQUFjQSxLQUFRQyJ9

View File

@@ -1,2 +1,2 @@
new function(){console.log(3)}; new function(){console.log(3)};
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbInN0ZGluIl0sIm5hbWVzIjpbImNvbnNvbGUiLCJsb2ciXSwibWFwcGluZ3MiOiJBQUErQyxJQUFyQyxXQUFnQkEsUUFBUUMsSUFBSSIsInNvdXJjZXNDb250ZW50IjpbImNsYXNzIEZvbyB7IGNvbnN0cnVjdG9yKCl7Y29uc29sZS5sb2coMSsyKTt9IH0gbmV3IEZvbygpO1xuIl19 //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbInN0ZGluIl0sInNvdXJjZXNDb250ZW50IjpbImNsYXNzIEZvbyB7IGNvbnN0cnVjdG9yKCl7Y29uc29sZS5sb2coMSsyKTt9IH0gbmV3IEZvbygpO1xuIl0sIm5hbWVzIjpbImNvbnNvbGUiLCJsb2ciXSwibWFwcGluZ3MiOiJBQUErQyxJQUFyQyxXQUFnQkEsUUFBUUMsSUFBSSJ9

View File

@@ -1,2 +1,2 @@
new function(){console.log(3)}; new function(){console.log(3)};
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbInN0ZGluIl0sIm5hbWVzIjpbImNvbnNvbGUiLCJsb2ciXSwibWFwcGluZ3MiOiJBQUErQyxJQUFyQyxXQUFnQkEsUUFBUUMsSUFBSSIsInNvdXJjZXNDb250ZW50IjpbImNsYXNzIEZvbyB7IGNvbnN0cnVjdG9yKCl7Y29uc29sZS5sb2coMSsyKTt9IH0gbmV3IEZvbygpO1xuIl19 //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbInN0ZGluIl0sInNvdXJjZXNDb250ZW50IjpbImNsYXNzIEZvbyB7IGNvbnN0cnVjdG9yKCl7Y29uc29sZS5sb2coMSsyKTt9IH0gbmV3IEZvbygpO1xuIl0sIm5hbWVzIjpbImNvbnNvbGUiLCJsb2ciXSwibWFwcGluZ3MiOiJBQUErQyxJQUFyQyxXQUFnQkEsUUFBUUMsSUFBSSJ9

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,6 +1,6 @@
function f(x) { function f(x) {
return g(x); return g(x);
function g(x) { function g(x) {
return x; return x + x;
} }
} }

View File

@@ -245,7 +245,7 @@ describe("bin/uglifyjs", function() {
if (err) throw err; if (err) throw err;
assert.strictEqual(stdout, [ assert.strictEqual(stdout, [
"var Foo=function Foo(){console.log(1+2)};new Foo;var bar=function(){function foo(bar){return bar}return foo}();", "var Foo=function Foo(){console.log(1+2)};new Foo;var bar=function(){function foo(bar){return bar}return foo}();",
"//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbInN0ZGluIiwidGVzdC9pbnB1dC9pc3N1ZS0xMzIzL3NhbXBsZS5qcyJdLCJuYW1lcyI6WyJGb28iLCJjb25zb2xlIiwibG9nIiwiYmFyIiwiZm9vIl0sIm1hcHBpbmdzIjoiQUFBQSxJQUFNQSxJQUFJLFNBQUFBLE1BQWdCQyxRQUFRQyxJQUFJLEVBQUUsSUFBTyxJQUFJRixJQ0FuRCxJQUFJRyxJQUFNLFdBQ04sU0FBU0MsSUFBS0QsS0FDVixPQUFPQSxJQUdYLE9BQU9DLElBTEQifQ==", "//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbInN0ZGluIiwidGVzdC9pbnB1dC9pc3N1ZS0xMzIzL3NhbXBsZS5qcyJdLCJuYW1lcyI6WyJGb28iLCJjb25zb2xlIiwibG9nIiwiYmFyIiwiZm9vIl0sIm1hcHBpbmdzIjoiQUFBQSxJQUFNQSxJQUFJLFNBQUVBLE1BQWNDLFFBQVFDLElBQUksRUFBRSxJQUFPLElBQUlGLElDQW5ELElBQUlHLElBQU0sV0FDTixTQUFTQyxJQUFLRCxLQUNWLE9BQU9BLElBR1gsT0FBT0MsSUFMRCJ9",
"", "",
].join("\n")); ].join("\n"));
var stderrLines = stderr.split("\n"); var stderrLines = stderr.split("\n");
@@ -587,7 +587,7 @@ describe("bin/uglifyjs", function() {
}); });
function read_map() { function read_map() {
var map = JSON.parse(read("./test/input/issue-1236/simple.js.map")); var map = JSON.parse(read("test/input/issue-1236/simple.js.map"));
delete map.sourcesContent; delete map.sourcesContent;
return JSON.stringify(map).replace(/"/g, '\\"'); return JSON.stringify(map).replace(/"/g, '\\"');
} }
@@ -674,7 +674,7 @@ describe("bin/uglifyjs", function() {
var command = uglifyjscmd + " test/input/rename/input.js --rename"; var command = uglifyjscmd + " test/input/rename/input.js --rename";
exec(command, function(err, stdout, stderr) { exec(command, function(err, stdout, stderr) {
if (err) throw err; if (err) throw err;
assert.strictEqual(stdout, "function f(a){return b(a);function b(c){return c}}\n"); assert.strictEqual(stdout, "function f(a){return b(a);function b(c){return c+c}}\n");
done(); done();
}); });
}); });
@@ -682,7 +682,7 @@ describe("bin/uglifyjs", function() {
var command = uglifyjscmd + " test/input/rename/input.js -mc passes=2 --no-rename"; var command = uglifyjscmd + " test/input/rename/input.js -mc passes=2 --no-rename";
exec(command, function(err, stdout, stderr) { exec(command, function(err, stdout, stderr) {
if (err) throw err; if (err) throw err;
assert.strictEqual(stdout, "function f(n){return function(n){return n}(n)}\n"); assert.strictEqual(stdout, "function f(n){return function(n){return n+n}(n)}\n");
done(); done();
}); });
}); });
@@ -690,7 +690,7 @@ describe("bin/uglifyjs", function() {
var command = uglifyjscmd + " test/input/rename/input.js -mc passes=2"; var command = uglifyjscmd + " test/input/rename/input.js -mc passes=2";
exec(command, function(err, stdout, stderr) { exec(command, function(err, stdout, stderr) {
if (err) throw err; if (err) throw err;
assert.strictEqual(stdout, "function f(n){return n}\n"); assert.strictEqual(stdout, "function f(n){return n+n}\n");
done(); done();
}); });
}); });
@@ -698,7 +698,7 @@ describe("bin/uglifyjs", function() {
var command = uglifyjscmd + " test/input/rename/input.js -c passes=2"; var command = uglifyjscmd + " test/input/rename/input.js -c passes=2";
exec(command, function(err, stdout, stderr) { exec(command, function(err, stdout, stderr) {
if (err) throw err; if (err) throw err;
assert.strictEqual(stdout, "function f(x){return function(x){return x}(x)}\n"); assert.strictEqual(stdout, "function f(x){return function(x){return x+x}(x)}\n");
done(); done();
}); });
}); });

View File

@@ -51,7 +51,7 @@ describe("minify", function() {
"var a=n(3),b=r(12);", "var a=n(3),b=r(12);",
'c("qux",a,b),o(11);', 'c("qux",a,b),o(11);',
].join("")); ].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() { it("Should work with nameCache", function() {
@@ -84,7 +84,7 @@ describe("minify", function() {
"var a=n(3),b=r(12);", "var a=n(3),b=r(12);",
'c("qux",a,b),o(11);', 'c("qux",a,b),o(11);',
].join("")); ].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() { 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;', '"xxyyy";var y={y:2,a:3},a=4;',
'console.log(x.x,y.y,y.a,a);', 'console.log(x.x,y.y,y.a,a);',
].join("")); ].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() { 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 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);', 'var e=function(n){function e(){console.log("B")}o(e,n);return e}(n);',
].join("")); ].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() { 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

@@ -1,10 +1,9 @@
var assert = require("assert"); var assert = require("assert");
var readFileSync = require("fs").readFileSync; var fs = require("fs");
var SourceMapConsumer = require("source-map").SourceMapConsumer;
var UglifyJS = require("../node"); var UglifyJS = require("../node");
function read(path) { function read(path) {
return readFileSync(path, "utf8"); return fs.readFileSync(path, "utf8");
} }
function source_map(code) { function source_map(code) {
@@ -44,7 +43,7 @@ function prepare_map(sourceMap) {
} }
}); });
if (result.error) throw result.error; if (result.error) throw result.error;
return new SourceMapConsumer(result.map); return JSON.parse(result.map);
} }
describe("sourcemaps", function() { describe("sourcemaps", function() {
@@ -87,14 +86,30 @@ describe("sourcemaps", function() {
}); });
if (result.error) throw result.error; if (result.error) throw result.error;
assert.strictEqual(result.code, code); assert.strictEqual(result.code, code);
assert.strictEqual(result.map, '{"version":3,"sources":["0"],"names":["console","log"],"mappings":"AAAAA,QAAQC,IAAI","sourceRoot":"//foo.bar/"}'); assert.strictEqual(result.map, '{"version":3,"sourceRoot":"//foo.bar/","sources":["0"],"names":["console","log"],"mappings":"AAAAA,QAAQC,IAAI"}');
});
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() { describe("inSourceMap", function() {
it("Should read the given string filename correctly when sourceMapIncludeSources is enabled", function() { it("Should read the given string filename correctly when sourceMapIncludeSources is enabled", function() {
var result = UglifyJS.minify(read("./test/input/issue-1236/simple.js"), { var result = UglifyJS.minify(read("test/input/issue-1236/simple.js"), {
sourceMap: { sourceMap: {
content: read("./test/input/issue-1236/simple.js.map"), content: read("test/input/issue-1236/simple.js.map"),
filename: "simple.min.js", filename: "simple.min.js",
includeSources: true includeSources: true
} }
@@ -106,7 +121,7 @@ describe("sourcemaps", function() {
assert.equal(map.sourcesContent[0], 'let foo = x => "foo " + x;\nconsole.log(foo("bar"));'); assert.equal(map.sourcesContent[0], 'let foo = x => "foo " + x;\nconsole.log(foo("bar"));');
}); });
it("Should process inline source map", function() { it("Should process inline source map", function() {
var result = UglifyJS.minify(read("./test/input/issue-520/input.js"), { var result = UglifyJS.minify(read("test/input/issue-520/input.js"), {
compress: { toplevel: true }, compress: { toplevel: true },
sourceMap: { sourceMap: {
content: "inline", content: "inline",
@@ -115,10 +130,10 @@ describe("sourcemaps", function() {
} }
}); });
if (result.error) throw result.error; if (result.error) throw result.error;
assert.strictEqual(result.code + "\n", readFileSync("test/input/issue-520/output.js", "utf8")); assert.strictEqual(result.code + "\n", read("test/input/issue-520/output.js"));
}); });
it("Should warn for missing inline source map", function() { it("Should warn for missing inline source map", function() {
var result = UglifyJS.minify(read("./test/input/issue-1323/sample.js"), { var result = UglifyJS.minify(read("test/input/issue-1323/sample.js"), {
mangle: false, mangle: false,
sourceMap: { sourceMap: {
content: "inline" content: "inline"
@@ -130,8 +145,8 @@ describe("sourcemaps", function() {
}); });
it("Should handle multiple input and inline source map", function() { it("Should handle multiple input and inline source map", function() {
var result = UglifyJS.minify([ var result = UglifyJS.minify([
read("./test/input/issue-520/input.js"), read("test/input/issue-520/input.js"),
read("./test/input/issue-1323/sample.js"), read("test/input/issue-1323/sample.js"),
], { ], {
sourceMap: { sourceMap: {
content: "inline", content: "inline",
@@ -147,7 +162,7 @@ describe("sourcemaps", function() {
assert.deepEqual(result.warnings, [ "WARN: inline source map not found: 1" ]); assert.deepEqual(result.warnings, [ "WARN: inline source map not found: 1" ]);
}); });
it("Should drop source contents for includeSources=false", function() { it("Should drop source contents for includeSources=false", function() {
var result = UglifyJS.minify(read("./test/input/issue-520/input.js"), { var result = UglifyJS.minify(read("test/input/issue-520/input.js"), {
compress: false, compress: false,
mangle: false, mangle: false,
sourceMap: { sourceMap: {
@@ -170,7 +185,7 @@ describe("sourcemaps", function() {
assert.ok(!("sourcesContent" in map)); assert.ok(!("sourcesContent" in map));
}); });
it("Should parse the correct sourceMappingURL", function() { it("Should parse the correct sourceMappingURL", function() {
var result = UglifyJS.minify(read("./test/input/issue-3294/input.js"), { var result = UglifyJS.minify(read("test/input/issue-3294/input.js"), {
compress: { toplevel: true }, compress: { toplevel: true },
sourceMap: { sourceMap: {
content: "inline", content: "inline",
@@ -179,10 +194,10 @@ describe("sourcemaps", function() {
} }
}); });
if (result.error) throw result.error; if (result.error) throw result.error;
assert.strictEqual(result.code + "\n", readFileSync("test/input/issue-3294/output.js", "utf8")); assert.strictEqual(result.code + "\n", read("test/input/issue-3294/output.js"));
}); });
it("Should work in presence of unrecognised annotations", function() { it("Should work in presence of unrecognised annotations", function() {
var result = UglifyJS.minify(read("./test/input/issue-3441/input.js"), { var result = UglifyJS.minify(read("test/input/issue-3441/input.js"), {
compress: false, compress: false,
mangle: false, mangle: false,
sourceMap: { sourceMap: {
@@ -214,7 +229,7 @@ describe("sourcemaps", function() {
assert.strictEqual(code, "var a=function(n){return n};"); assert.strictEqual(code, "var a=function(n){return n};");
}); });
it("Should work with max_line_len", function() { it("Should work with max_line_len", function() {
var result = UglifyJS.minify(read("./test/input/issue-505/input.js"), { var result = UglifyJS.minify(read("test/input/issue-505/input.js"), {
compress: { compress: {
directives: false, directives: false,
}, },
@@ -226,7 +241,7 @@ describe("sourcemaps", function() {
} }
}); });
if (result.error) throw result.error; if (result.error) throw result.error;
assert.strictEqual(result.code, read("./test/input/issue-505/output.js")); assert.strictEqual(result.code, read("test/input/issue-505/output.js"));
}); });
it("Should work with unicode characters", function() { it("Should work with unicode characters", function() {
var code = [ var code = [
@@ -265,29 +280,33 @@ describe("sourcemaps", function() {
it("Should copy over original sourcesContent", function() { it("Should copy over original sourcesContent", function() {
var orig = get_map(); var orig = get_map();
var map = prepare_map(orig); var map = prepare_map(orig);
assert.equal(map.sourceContentFor("index.js"), orig.sourcesContent[0]); assert.strictEqual(map.sources.length, 1);
assert.strictEqual(map.sources[0], "index.js");
assert.strictEqual(map.sourcesContent.length, 1);
assert.equal(map.sourcesContent[0], orig.sourcesContent[0]);
}); });
it("Should copy sourcesContent if sources are relative", function() { it("Should copy sourcesContent if sources are relative", function() {
var relativeMap = get_map(); var relativeMap = get_map();
relativeMap.sources = ['./index.js']; relativeMap.sources = ['./index.js'];
var map = prepare_map(relativeMap); var map = prepare_map(relativeMap);
assert.notEqual(map.sourcesContent, null); assert.strictEqual(map.sources.length, 1);
assert.equal(map.sourcesContent.length, 1); assert.strictEqual(map.sources[0], "./index.js");
assert.equal(map.sourceContentFor("index.js"), relativeMap.sourcesContent[0]); assert.strictEqual(map.sourcesContent.length, 1);
assert.equal(map.sourcesContent[0], relativeMap.sourcesContent[0]);
}); });
it("Should not have invalid mappings from inputSourceMap", function() { it("Should not have invalid mappings from inputSourceMap", function() {
var map = prepare_map(get_map()); var map = prepare_map(get_map());
// The original source has only 2 lines, check that mappings don't have more lines // The original source has only 2 lines, check that mappings don't have more lines
var msg = "Mapping should not have higher line number than the original file had"; var msg = "Mapping should not have higher line number than the original file had";
map.eachMapping(function(mapping) { var lines = map.mappings.split(/;/);
assert.ok(mapping.originalLine <= 2, msg); assert.ok(lines.length <= 2, msg);
var indices = [ 0, 0, 1, 0, 0];
lines.forEach(function(segments) {
indices[0] = 0;
segments.split(/,/).forEach(function(segment) {
UglifyJS.vlq_decode(indices, segment);
assert.ok(indices[2] <= 2, msg);
}); });
map.allGeneratedPositionsFor({
source: "index.js",
line: 1,
column: 1
}).forEach(function(pos) {
assert.ok(pos.line <= 2, msg);
}); });
}); });
}); });

View File

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

View File

@@ -1,6 +1,6 @@
var fs = require("fs"); var fs = require("fs");
new Function("MOZ_SourceMap", "exports", require("../tools/node").FILES.map(function(file) { new Function("exports", require("../tools/node").FILES.map(function(file) {
if (/exports\.js$/.test(file)) file = require.resolve("./exports"); if (/exports\.js$/.test(file)) file = require.resolve("./exports");
return fs.readFileSync(file, "utf8"); return fs.readFileSync(file, "utf8");
}).join("\n\n"))(require("source-map"), exports); }).join("\n\n"))(exports);

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 stdout = "";
var original_write = process.stdout.write; var original_write = process.stdout.write;
process.stdout.write = function(chunk) { process.stdout.write = function(chunk) {
stdout += chunk; stdout += chunk;
}; };
try { try {
vm.runInContext(toplevel ? "(function(){" + code + "})()" : code, createContext(), { timeout: 5000 }); vm.runInContext(toplevel ? "(function(){" + code + "})()" : code, createContext(), { timeout: timeout });
return stdout; return stdout;
} catch (ex) { } catch (ex) {
return ex; return ex;
@@ -76,8 +77,9 @@ function strip_func_ids(text) {
exports.same_stdout = semver.satisfies(process.version, "0.12") ? function(expected, actual) { exports.same_stdout = semver.satisfies(process.version, "0.12") ? function(expected, actual) {
if (typeof expected != typeof actual) return false; if (typeof expected != typeof actual) return false;
if (typeof expected != "string") { if (typeof expected == "object" && typeof expected.name == "string" && typeof expected.message == "string") {
if (expected.name != actual.name) return false; if (expected.name !== actual.name) return false;
if (typeof actual.message != "string") return false;
expected = expected.message.slice(expected.message.lastIndexOf("\n") + 1); expected = expected.message.slice(expected.message.lastIndexOf("\n") + 1);
actual = actual.message.slice(actual.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) { } : function(expected, actual) {
return typeof expected == typeof actual && strip_func_ids(expected) == strip_func_ids(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 UglifyJS = require("../..");
var randomBytes = require("crypto").randomBytes; var randomBytes = require("crypto").randomBytes;
var sandbox = require("../sandbox"); var sandbox = require("../sandbox");
var reduce_test = require("../reduce");
var MAX_GENERATED_TOPLEVELS_PER_RUN = 1; var MAX_GENERATED_TOPLEVELS_PER_RUN = 1;
var MAX_GENERATION_RECURSION_DEPTH = 12; 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() "; return " ((" + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ") || a || 3).toString() ";
case p++: case p++:
return " /[abc4]/.test(((" + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ") || b || 5).toString()) "; 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++: case p++:
return " ((" + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + return " ((" + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) +
") || " + rng(10) + ").toString()[" + ") || " + rng(10) + ").toString()[" +
@@ -769,6 +772,12 @@ function _createExpression(recurmax, noComma, stmtDepth, canThrow) {
case p++: case p++:
case p++: case p++:
case p++: case p++:
if (rng(16) == 0) {
var name = getVarName();
var fn = name + "." + getDotKey();
called[name] = true;
return name + " && " + "typeof " + fn + ' == "function" && --_calls_ >= 0 && ' + fn + "(" + createArgs(recurmax, stmtDepth, canThrow) + ")";
}
var name = rng(3) == 0 ? getVarName() : "f" + rng(funcs + 2); var name = rng(3) == 0 ? getVarName() : "f" + rng(funcs + 2);
called[name] = true; called[name] = true;
return "typeof " + name + ' == "function" && --_calls_ >= 0 && ' + name + "(" + createArgs(recurmax, stmtDepth, canThrow) + ")"; return "typeof " + name + ' == "function" && --_calls_ >= 0 && ' + name + "(" + createArgs(recurmax, stmtDepth, canThrow) + ")";
@@ -1009,7 +1018,7 @@ function log_suspects(minify_options, component) {
var defs = default_options[component]; var defs = default_options[component];
var suspects = Object.keys(defs).filter(function(name) { var suspects = Object.keys(defs).filter(function(name) {
var flip = name == "keep_fargs"; 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 m = JSON.parse(JSON.stringify(minify_options));
var o = JSON.parse(JSON.stringify(options)); var o = JSON.parse(JSON.stringify(options));
o[name] = flip; o[name] = flip;
@@ -1019,7 +1028,7 @@ function log_suspects(minify_options, component) {
errorln("Error testing options." + component + "." + name); errorln("Error testing options." + component + "." + name);
errorln(result.error); errorln(result.error);
} else { } 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); return sandbox.same_stdout(original_result, r);
} }
} }
@@ -1033,42 +1042,63 @@ function log_suspects(minify_options, component) {
} }
} }
function log_rename(options) { function log_suspects_global(options) {
var m = JSON.parse(JSON.stringify(options)); var o = {};
m.rename = false; 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); var result = UglifyJS.minify(original_code, m);
if (result.error) { if (result.error) {
errorln("Error testing options.rename"); errorln("Error testing options." + component);
errorln(result.error); errorln(result.error);
} else { } else {
var r = sandbox.run_code(result.code, m.toplevel); var r = sandbox.run_code(result.code, sandbox.has_toplevel(m));
if (sandbox.same_stdout(original_result, r)) { return sandbox.same_stdout(original_result, r);
errorln("Suspicious options:");
errorln(" rename");
errorln();
} }
});
if (suspects.length > 0) {
errorln("Suspicious options:");
suspects.forEach(function(name) {
errorln(" " + name);
});
errorln();
} }
} }
function log(options) { 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"); if (!ok) errorln("\n\n\n\n\n\n!!!!!!!!!!\n\n\n");
errorln("//============================================================="); errorln("//=============================================================");
if (!ok) errorln("// !!!!!! Failed... round " + round); if (!ok) errorln("// !!!!!! Failed... round " + round);
errorln("// original code"); errorln("// original code");
try_beautify(original_code, options.toplevel, original_result, errorln); try_beautify(original_code, toplevel, original_result, errorln);
errorln(); errorln();
errorln(); errorln();
errorln("//-------------------------------------------------------------"); errorln("//-------------------------------------------------------------");
if (typeof uglify_code == "string") { if (typeof uglify_code == "string") {
errorln("// uglified code"); errorln("// uglified code");
try_beautify(uglify_code, options.toplevel, uglify_result, errorln); try_beautify(uglify_code, toplevel, uglify_result, errorln);
errorln(); errorln();
errorln(); errorln();
errorln("original result:"); errorln("original result:");
errorln(original_result); errorln(original_result);
errorln("uglified result:"); errorln("uglified result:");
errorln(uglify_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 { } else {
errorln("// !!! uglify failed !!!"); errorln("// !!! uglify failed !!!");
errorln(uglify_code); errorln(uglify_code);
@@ -1080,11 +1110,11 @@ function log(options) {
} }
} }
errorln("minify(options):"); errorln("minify(options):");
errorln(JSON.stringify(options, null, 2)); errorln(JSON.stringify(JSON.parse(options), null, 2));
errorln(); errorln();
if (!ok && typeof uglify_code == "string") { if (!ok && typeof uglify_code == "string") {
Object.keys(default_options).forEach(log_suspects.bind(null, options)); Object.keys(default_options).forEach(log_suspects.bind(null, JSON.parse(options)));
log_rename(options); log_suspects_global(options);
errorln("!!!!!! Failed... round " + round); errorln("!!!!!! Failed... round " + round);
} }
} }
@@ -1119,16 +1149,17 @@ for (var round = 1; round <= num_iterations; round++) {
if (!errored) orig_result.push(sandbox.run_code(original_code, true)); if (!errored) orig_result.push(sandbox.run_code(original_code, true));
(errored ? fallback_options : minify_options).forEach(function(options) { (errored ? fallback_options : minify_options).forEach(function(options) {
var o = JSON.parse(options); var o = JSON.parse(options);
var toplevel = sandbox.has_toplevel(o);
uglify_code = UglifyJS.minify(original_code, 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) { if (!uglify_code.error) {
uglify_code = uglify_code.code; 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); ok = sandbox.same_stdout(original_result, uglify_result);
if (!ok && typeof uglify_result == "string" && o.compress.unsafe_math) { if (!ok && typeof uglify_result == "string" && o.compress.unsafe_math) {
ok = fuzzy_match(original_result, uglify_result); ok = fuzzy_match(original_result, uglify_result);
if (!ok) { if (!ok) {
var fuzzy_result = sandbox.run_code(original_code.replace(/( - 0\.1){3}/g, " - 0.3")); var fuzzy_result = sandbox.run_code(original_code.replace(/( - 0\.1){3}/g, " - 0.3"), toplevel);
ok = sandbox.same_stdout(fuzzy_result, uglify_result); ok = sandbox.same_stdout(fuzzy_result, uglify_result);
} }
} }
@@ -1142,7 +1173,7 @@ for (var round = 1; round <= num_iterations; round++) {
else if (errored) { else if (errored) {
println("//============================================================="); println("//=============================================================");
println("// original code"); println("// original code");
try_beautify(original_code, o.toplevel, original_result, println); try_beautify(original_code, toplevel, original_result, println);
println(); println();
println(); println();
println("original result:"); println("original result:");

View File

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

View File

@@ -15,13 +15,13 @@ exports.FILES = [
require.resolve("./exports.js"), require.resolve("./exports.js"),
]; ];
new Function("MOZ_SourceMap", "exports", function() { new Function("exports", function() {
var code = exports.FILES.map(function(file) { var code = exports.FILES.map(function(file) {
return fs.readFileSync(file, "utf8"); return fs.readFileSync(file, "utf8");
}); });
code.push("exports.describe_ast = " + describe_ast.toString()); code.push("exports.describe_ast = " + describe_ast.toString());
return code.join("\n\n"); return code.join("\n\n");
}())(require("source-map"), exports); }())(exports);
function describe_ast() { function describe_ast() {
var out = OutputStream({ beautify: true }); var out = OutputStream({ beautify: true });