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:
strategy:
matrix:
node: [ "0.10", "0.12", "4", "6", "8", "10", latest ]
os: [ ubuntu-latest, windows-latest ]
node: [ "0.10", 0.12, 4, 6, 8, 10, latest ]
script: [ compress, mocha, release/benchmark, release/jetstream ]
name: ${{ matrix.os }} ${{ matrix.node }} ${{ matrix.script }}
name: ${{ matrix.node }} ${{ matrix.os }} ${{ matrix.script }}
runs-on: ${{ matrix.os }}
env:
NODE: ${{ matrix.node }}
@@ -21,11 +21,19 @@ jobs:
- name: Perform tests
shell: bash
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 add $NODE
nvs use $NODE
node --version
npm --version --no-update-notifier
npm install --no-audit --no-optional --no-save --no-update-notifier
npm config set audit false
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

View File

@@ -15,11 +15,19 @@ jobs:
- name: Perform fuzzing
shell: bash
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 add 10
nvs use 10
node --version
npm --version --no-update-notifier
npm install --no-audit --no-optional --no-save --no-update-notifier
npm config set audit false
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

View File

@@ -848,8 +848,14 @@ can pass additional arguments that control the code output:
statement.
- `comments` (default `false`) -- pass `true` or `"all"` to preserve all
comments, `"some"` to preserve some comments, a regular expression string
(e.g. `/^!/`) or a function.
comments, `"some"` to preserve multi-line comments that contain `@cc_on`,
`@license`, or `@preserve` (case-insensitive), a regular expression string
(e.g. `/^!/`), or a function which returns `boolean`, e.g.
```js
function(node, comment) {
return comment.value.indexOf("@type " + node.TYPE) >= 0;
}
```
- `indent_level` (default `4`)

View File

@@ -54,6 +54,7 @@ program.option("--toplevel", "Compress and/or mangle variables in toplevel scope
program.option("--verbose", "Print diagnostic messages.");
program.option("--warn", "Print warning messages.");
program.option("--wrap <name>", "Embed everything as a function with “exports” corresponding to “name” globally.");
program.option("--reduce-test", "Reduce a standalone `console.log` based test case.");
program.arguments("[files...]").parseArgv(process.argv);
if (program.configFile) {
options = JSON.parse(read_file(program.configFile));
@@ -215,7 +216,15 @@ function run() {
} catch (ex) {
fatal(ex);
}
var result = UglifyJS.minify(files, options);
if (program.reduceTest) {
// load on demand - assumes dev tree checked out
var reduce_test = require("../test/reduce");
var testcase = files[0] || files[Object.keys(files)[0]];
var result = reduce_test(testcase, options, {verbose: true});
}
else {
var result = UglifyJS.minify(files, options);
}
if (result.error) {
var ex = result.error;
if (ex.name == "SyntaxError") {

View File

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

View File

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

View File

@@ -203,6 +203,7 @@ function minify(files, options) {
if (!HOP(options.output, "code") || options.output.code) {
if (options.sourceMap) {
options.output.source_map = SourceMap({
content: options.sourceMap.includeSources,
file: options.sourceMap.filename,
orig: source_maps,
root: options.sourceMap.root
@@ -211,10 +212,8 @@ function minify(files, options) {
if (files instanceof AST_Toplevel) {
throw new Error("original source content unavailable");
} 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;

View File

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

View File

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

View File

@@ -162,17 +162,22 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options) {
}
if (node instanceof AST_SymbolRef) {
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);
if (!sym) {
sym = self.def_global(node);
} else if (sym.scope instanceof AST_Lambda && name == "arguments") {
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.reference(options);
return true;
@@ -219,7 +224,12 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options) {
var redef;
while (redef = new_def.redefined()) new_def = redef;
} else {
new_def = self.globals.get(name) || scope.def_variable(node);
new_def = self.globals.get(name);
}
if (new_def) {
new_def.orig.push(node);
} else {
new_def = scope.def_variable(node);
}
old_def.orig.concat(old_def.references).forEach(function(node) {
node.thedef = new_def;

View File

@@ -43,62 +43,144 @@
"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) {
options = defaults(options, {
content: false,
file: null,
root: null,
orig: null,
orig_line_diff: 0,
dest_line_diff: 0,
}, true);
var generator = new MOZ_SourceMap.SourceMapGenerator({
file: options.file,
sourceRoot: options.root
});
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);
var sources = create_array_map();
var sources_content = options.content && Object.create(null);
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));
});
});
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 {
add: function(source, gen_line, gen_col, orig_line, orig_col, name) {
var map = maps && maps[source];
add: options.orig ? function(source, gen_line, gen_col, orig_line, orig_col, name) {
var map = options.orig[source];
if (map) {
var info = map.originalPositionFor({
line: orig_line,
column: orig_col
});
if (info.source === null) return;
source = info.source;
orig_line = info.line;
orig_col = info.column;
name = info.name || name;
}
generator.addMapping({
name: name,
source: source,
generated: {
line: gen_line + options.dest_line_diff,
column: gen_col
},
original: {
line: orig_line + options.orig_line_diff,
column: orig_col
var segments = map.mappings[orig_line - 1];
if (!segments) return;
var indices;
for (var i = 0; i < segments.length; i++) {
var col = segments[i][0];
if (orig_col >= col) indices = segments[i];
if (orig_col <= col) break;
}
});
},
get: function() {
return generator;
},
if (!indices || indices.length < 4) return;
source = map.sources[indices[1]];
orig_line = indices[2];
orig_col = indices[3];
if (indices.length > 4) name = map.names[indices[4]];
}
add(source, gen_line, gen_col, orig_line, orig_col, name);
} : add,
setSourceContent: sources_content ? function(source, content) {
sources_content[source] = content;
} : noop,
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 do_list(list, tw) {
return MAP(list, function(node) {
return List(list, function(node) {
return node.transform(tw, true);
});
}

View File

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

View File

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

View File

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

View File

@@ -7765,3 +7765,44 @@ issue_3700: {
}
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: {
y();
x() && y();
(x(), 1) && y();
x(), y();
x() && y();
x() && y();
x() && y();
(x(), 0) && y();
x();
}
}

View File

@@ -1191,10 +1191,10 @@ issue_2105_1: {
input: {
!function(factory) {
factory();
}( function() {
}(function() {
return function(fn) {
fn()().prop();
}( function() {
}(function() {
function bar() {
var quux = function() {
console.log("PASS");
@@ -1205,7 +1205,7 @@ issue_2105_1: {
return { prop: foo };
}
return bar;
} );
});
});
}
expect: {
@@ -1235,10 +1235,10 @@ issue_2105_2: {
input: {
!function(factory) {
factory();
}( function() {
}(function() {
return function(fn) {
fn()().prop();
}( function() {
}(function() {
function bar() {
var quux = function() {
console.log("PASS");
@@ -1249,7 +1249,7 @@ issue_2105_2: {
return { prop: foo };
}
return bar;
} );
});
});
}
expect: {
@@ -1258,6 +1258,44 @@ issue_2105_2: {
expect_stdout: "PASS"
}
issue_2105_3: {
options = {
inline: true,
passes: 2,
reduce_vars: true,
unused: true,
}
input: {
!function(factory) {
factory();
}(function() {
return function(fn) {
fn()().prop();
}(function() {
function bar() {
var quux = function() {
console.log("PASS");
}, foo = function() {
console.log;
quux();
};
return { prop: foo };
}
return bar;
});
});
}
expect: {
!void void {
prop: function() {
console.log;
void console.log("PASS");
}
}.prop();
}
expect_stdout: "PASS"
}
issue_2226_1: {
options = {
side_effects: true,
@@ -2330,7 +2368,7 @@ function_parameter_ie8: {
(function() {
(function f() {
console.log("PASS");
})(0);
})();
})();
}
expect_stdout: "PASS"
@@ -2375,3 +2413,34 @@ issue_3673: {
}
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",
]
}
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));
}
expect: {
console.log(function(a) {
return a;
}(1), function(a) {
return a;
}());
console.log(1, void 0);
}
expect_stdout: "1 undefined"
}
@@ -1024,9 +1020,7 @@ issue_2616: {
}
expect: {
var c = "FAIL";
!function(NaN) {
(true << NaN) - 0/0 || (c = "PASS");
}([]);
(true << []) - NaN || (c = "PASS");
console.log(c);
}
expect_stdout: "PASS"
@@ -1276,7 +1270,7 @@ issue_2630_3: {
(function() {
(function f1(a) {
f2();
--x >= 0 && f1({});
--x >= 0 && f1();
})(a++);
function f2() {
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 = {
inline: true,
reduce_vars: true,
@@ -1595,7 +1605,31 @@ duplicate_argnames: {
}
expect: {
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;
console.log(a);
}
@@ -1754,8 +1788,7 @@ inline_2: {
}
expect: {
console.log(1);
a = 2, console.log(a);
var a;
console.log(2);
(function(b) {
var c = b;
console.log(c);
@@ -1788,8 +1821,7 @@ inline_3: {
}
expect: {
console.log(1);
a = 2, console.log(a);
var a;
console.log(2);
b = 3, c = b, console.log(c);
var b, c;
}
@@ -1820,8 +1852,7 @@ inline_true: {
}
expect: {
console.log(1);
a = 2, console.log(a);
var a;
console.log(2);
b = 3, c = b, console.log(c);
var b, c;
}
@@ -1857,10 +1888,9 @@ use_before_init_in_loop: {
expect_stdout: "PASS"
}
duplicate_arg_var: {
duplicate_arg_var_1: {
options = {
inline: true,
toplevel: true,
}
input: {
console.log(function(b) {
@@ -1869,7 +1899,41 @@ duplicate_arg_var: {
}("PASS"));
}
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;
}
expect_stdout: "PASS"
@@ -2017,10 +2081,8 @@ issue_3016_1: {
expect: {
var b = 1;
do {
a = 3,
a[b];
3[b];
} while(0);
var a;
console.log(b);
}
expect_stdout: "1"
@@ -2528,10 +2590,9 @@ cross_references_2: {
options = {
collapse_vars: true,
evaluate: true,
hoist_props: true,
inline: true,
passes: 4,
pure_getters: true,
passes: 6,
properties: true,
reduce_vars: true,
sequences: true,
side_effects: true,
@@ -3657,9 +3718,7 @@ pr_3595_3: {
var g = [ "PASS" ];
console.log(function(problem) {
return g[problem];
}(function(arg) {
return g.indexOf(arg);
}("PASS")));
}(g.indexOf("PASS")));
}
expect_stdout: "PASS"
}
@@ -3785,3 +3844,221 @@ issue_3679_3: {
}
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"
}
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);
}
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.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() {
foo();
}
// TODO: optimize to `func(), bar()`
(func(), 1) && bar();
func(), 1, bar();
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -2071,13 +2071,8 @@ issue_1670_6: {
}
expect: {
(function(a) {
switch (1) {
case a = 1:
console.log(a);
break;
default:
console.log(2);
}
a = 1;
console.log(a);
})(1);
}
expect_stdout: "1"
@@ -2294,11 +2289,10 @@ redefine_farg_2: {
console.log(f([]), g([]), h([]));
}
expect: {
console.log((a = [], typeof a), "number",function(a, b) {
console.log(typeof [], "number",function(a, b) {
a = b;
return typeof a;
}([]));
var a;
}
expect_stdout: "object number undefined"
}
@@ -6516,17 +6510,17 @@ issue_3240_3: {
}
expect: {
(function() {
(function f(b) {
f();
function f(b) {
if (!f.a) f.a = 0;
console.log(f.a.toString());
var g = function() {
(function() {
(b ? function() {} : function() {
f.a++;
f(1);
})();
};
g();
})();
})();
}
})();
}
expect_stdout: [
@@ -6560,7 +6554,8 @@ issue_3240_4: {
}
expect: {
(function() {
(function f(b) {
f();
function f(b) {
if (!f.a) f.a = 0;
console.log(f.a.toString());
(function() {
@@ -6569,7 +6564,7 @@ issue_3240_4: {
f(1);
})();
})();
})();
}
})();
}
expect_stdout: [
@@ -6878,3 +6873,42 @@ issue_3666: {
}
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/;
}
}
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"
}
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["defaults"] = defaults;
exports["JS_Parse_Error"] = JS_Parse_Error;
exports["List"] = List;
exports["mangle_properties"] = mangle_properties;
exports["minify"] = minify;
exports["OutputStream"] = OutputStream;
@@ -12,3 +13,4 @@ exports["to_ascii"] = to_ascii;
exports["tokenizer"] = tokenizer;
exports["TreeTransformer"] = TreeTransformer;
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)));
//# 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)};
//# 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)};
//# 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) {
return g(x);
function g(x) {
return x;
return x + x;
}
}

View File

@@ -245,7 +245,7 @@ describe("bin/uglifyjs", function() {
if (err) throw err;
assert.strictEqual(stdout, [
"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"));
var stderrLines = stderr.split("\n");
@@ -587,7 +587,7 @@ describe("bin/uglifyjs", function() {
});
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;
return JSON.stringify(map).replace(/"/g, '\\"');
}
@@ -674,7 +674,7 @@ describe("bin/uglifyjs", function() {
var command = uglifyjscmd + " test/input/rename/input.js --rename";
exec(command, function(err, stdout, stderr) {
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();
});
});
@@ -682,7 +682,7 @@ describe("bin/uglifyjs", function() {
var command = uglifyjscmd + " test/input/rename/input.js -mc passes=2 --no-rename";
exec(command, function(err, stdout, stderr) {
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();
});
});
@@ -690,7 +690,7 @@ describe("bin/uglifyjs", function() {
var command = uglifyjscmd + " test/input/rename/input.js -mc passes=2";
exec(command, function(err, stdout, stderr) {
if (err) throw err;
assert.strictEqual(stdout, "function f(n){return n}\n");
assert.strictEqual(stdout, "function f(n){return n+n}\n");
done();
});
});
@@ -698,7 +698,7 @@ describe("bin/uglifyjs", function() {
var command = uglifyjscmd + " test/input/rename/input.js -c passes=2";
exec(command, function(err, stdout, stderr) {
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();
});
});

View File

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

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

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

View File

@@ -1,10 +1,9 @@
var assert = require("assert");
var readFileSync = require("fs").readFileSync;
var SourceMapConsumer = require("source-map").SourceMapConsumer;
var fs = require("fs");
var UglifyJS = require("../node");
function read(path) {
return readFileSync(path, "utf8");
return fs.readFileSync(path, "utf8");
}
function source_map(code) {
@@ -44,7 +43,7 @@ function prepare_map(sourceMap) {
}
});
if (result.error) throw result.error;
return new SourceMapConsumer(result.map);
return JSON.parse(result.map);
}
describe("sourcemaps", function() {
@@ -87,14 +86,30 @@ describe("sourcemaps", function() {
});
if (result.error) throw result.error;
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() {
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: {
content: read("./test/input/issue-1236/simple.js.map"),
content: read("test/input/issue-1236/simple.js.map"),
filename: "simple.min.js",
includeSources: true
}
@@ -106,7 +121,7 @@ describe("sourcemaps", function() {
assert.equal(map.sourcesContent[0], 'let foo = x => "foo " + x;\nconsole.log(foo("bar"));');
});
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 },
sourceMap: {
content: "inline",
@@ -115,10 +130,10 @@ describe("sourcemaps", function() {
}
});
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() {
var result = UglifyJS.minify(read("./test/input/issue-1323/sample.js"), {
var result = UglifyJS.minify(read("test/input/issue-1323/sample.js"), {
mangle: false,
sourceMap: {
content: "inline"
@@ -130,8 +145,8 @@ describe("sourcemaps", function() {
});
it("Should handle multiple input and inline source map", function() {
var result = UglifyJS.minify([
read("./test/input/issue-520/input.js"),
read("./test/input/issue-1323/sample.js"),
read("test/input/issue-520/input.js"),
read("test/input/issue-1323/sample.js"),
], {
sourceMap: {
content: "inline",
@@ -147,7 +162,7 @@ describe("sourcemaps", function() {
assert.deepEqual(result.warnings, [ "WARN: inline source map not found: 1" ]);
});
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,
mangle: false,
sourceMap: {
@@ -170,7 +185,7 @@ describe("sourcemaps", function() {
assert.ok(!("sourcesContent" in map));
});
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 },
sourceMap: {
content: "inline",
@@ -179,10 +194,10 @@ describe("sourcemaps", function() {
}
});
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() {
var result = UglifyJS.minify(read("./test/input/issue-3441/input.js"), {
var result = UglifyJS.minify(read("test/input/issue-3441/input.js"), {
compress: false,
mangle: false,
sourceMap: {
@@ -214,7 +229,7 @@ describe("sourcemaps", function() {
assert.strictEqual(code, "var a=function(n){return n};");
});
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: {
directives: false,
},
@@ -226,7 +241,7 @@ describe("sourcemaps", function() {
}
});
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() {
var code = [
@@ -265,29 +280,33 @@ describe("sourcemaps", function() {
it("Should copy over original sourcesContent", function() {
var orig = get_map();
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() {
var relativeMap = get_map();
relativeMap.sources = ['./index.js'];
var map = prepare_map(relativeMap);
assert.notEqual(map.sourcesContent, null);
assert.equal(map.sourcesContent.length, 1);
assert.equal(map.sourceContentFor("index.js"), relativeMap.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], relativeMap.sourcesContent[0]);
});
it("Should not have invalid mappings from inputSourceMap", function() {
var map = prepare_map(get_map());
// 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";
map.eachMapping(function(mapping) {
assert.ok(mapping.originalLine <= 2, msg);
});
map.allGeneratedPositionsFor({
source: "index.js",
line: 1,
column: 1
}).forEach(function(pos) {
assert.ok(pos.line <= 2, msg);
var lines = map.mappings.split(/;/);
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);
});
});
});
});

View File

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

View File

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

View File

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

View File

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

View File

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