Compare commits

...

49 Commits

Author SHA1 Message Date
Alex Lam S.L
e33c727e8b v3.10.3 2020-08-30 13:09:12 +01:00
Alex Lam S.L
f886b3fb2b fix corner case in loops & unused (#4083)
fixes #4082
2020-08-29 02:42:17 +08:00
Alex Lam S.L
b1cc15e85b fix corner case in sequences (#4080)
fixes #4079
2020-08-26 20:41:11 +08:00
Alex Lam S.L
3aa765e429 fix corner case in evaluate (#4078)
fixes #4077
2020-08-26 19:45:38 +08:00
Alex Lam S.L
93d084a1d1 fix corner case in loops & unused (#4076)
fixes #4075
2020-08-26 17:32:20 +08:00
Alex Lam S.L
c7a3e09407 enhance loops & unused (#4074)
- extend `ufuzz` generation of for-in loops
2020-08-26 09:32:55 +08:00
Alex Lam S.L
09525c7530 fix corner case in sequences (#4073) 2020-08-26 01:26:49 +08:00
Alex Lam S.L
a7e15fe73c streamline parenthesis logic (#4072) 2020-08-25 19:45:37 +08:00
Alex Lam S.L
a31c27c7cf fix corner case in collapse_vars (#4071)
fixes #4070
2020-08-25 17:23:36 +08:00
Alex Lam S.L
1caf7c7bd2 minor clean up (#4069) 2020-08-25 10:10:56 +08:00
Alex Lam S.L
0eb0c9b388 fix corner case in evaluate (#4068)
fixes #4067
2020-08-24 14:57:26 +08:00
Alex Lam S.L
7dc61cdc89 tidy up various interfaces (#4066) 2020-08-24 04:39:38 +08:00
Alex Lam S.L
af1b2f30c9 v3.10.2 2020-08-23 23:09:12 +08:00
Alex Lam S.L
37b4fc7e31 update domprops (#4065) 2020-08-23 23:06:15 +08:00
Alex Lam S.L
da85d102e3 enhance mangle.properties (#4064) 2020-08-23 08:45:39 +08:00
Alex Lam S.L
35fe1092d3 simplify traversal logic (#4063) 2020-08-23 05:45:35 +08:00
Alex Lam S.L
f2d486e771 enhance comparisons (#4062) 2020-08-23 01:03:48 +08:00
Alex Lam S.L
fee677786e fix corner case in collapse_vars (#4061) 2020-08-21 10:35:34 +08:00
Alex Lam S.L
aa83ecdb3b fix corner case in switches (#4060)
fixes #4059
2020-08-21 08:05:10 +08:00
Alex Lam S.L
a153176469 enhance conditionals & switches (#4058) 2020-08-21 00:35:39 +08:00
Alex Lam S.L
1c6384b6a5 improve ufuzz duty cycle heuristic (#4057) 2020-08-19 23:29:01 +08:00
Alex Lam S.L
e8db526f51 avoid setters during console.log() in sandbox (#4055)
fixes #4054
2020-08-19 06:14:41 +08:00
Alex Lam S.L
fa13ed4391 reject multiple defaults in switch (#4053)
fixes #4050
2020-08-17 10:09:12 +08:00
Alex Lam S.L
23f0dca992 fix corner cases in collapse_vars & dead_code (#4052)
fixes #4051
2020-08-17 05:54:27 +08:00
Alex Lam S.L
45ab3b51d8 clarify toplevel & global variable aliasing (#4046) 2020-08-10 06:39:28 +08:00
Alex Lam S.L
49670d216b fix corner case in collapse_vars (#4048)
fixes #4047
2020-08-10 05:48:56 +08:00
Alex Lam S.L
e2237d8cd2 improve ufuzz duty cycle heuristic (#4045) 2020-08-09 03:10:19 +08:00
Alex Lam S.L
91f078fe35 workaround incorrect workflow status (#4044) 2020-08-08 05:16:54 +08:00
Alex Lam S.L
a546cb881d improve ufuzz duty cycle on GitHub Actions (#4043) 2020-08-07 18:42:36 +08:00
Alex Lam S.L
84d5dffd9f tweak GitHub Actions (#4042) 2020-08-07 02:15:51 +08:00
Alex Lam S.L
a8e286f7e1 fix corner case in collapse_vars (#4041)
fixes #4040
2020-08-06 20:30:28 +08:00
Alex Lam S.L
9b05494ebc fix corner cases in aliasing of global variables (#4039)
fixes #4038
2020-08-06 09:39:50 +01:00
Alex Lam S.L
30ef20a208 tweak GitHub Actions (#4037) 2020-08-05 22:09:02 +08:00
Alex Lam S.L
a4002ef467 fix corner case in evaluate (#4036)
fixes #4035
2020-08-04 20:05:10 +08:00
Alex Lam S.L
9d758a216b v3.10.1 2020-08-02 21:08:48 +08:00
Alex Lam S.L
af13f8dd2c improve diagnostics upon AST validation failure (#4033) 2020-07-31 22:50:16 +08:00
Alex Lam S.L
88423f2574 validate against multiple parents on AST_Node (#4032)
- fix related issues in `global_defs`, `ie8` & `reduce_vars`
2020-07-31 08:09:19 +08:00
Alex Lam S.L
ee632a5519 fix corner case in reduce_vars (#4031)
fixes #4030
2020-07-31 08:05:09 +08:00
Alex Lam S.L
dfe47bcc42 fix corner case in ie8 & reduce_vars (#4029)
fixes #4028
2020-07-29 03:11:02 +08:00
Alex Lam S.L
6d3dcaa59e fix corner case in unused (#4026)
fixes #4025
2020-07-26 09:27:54 +08:00
Alex Lam S.L
1bc0df1569 fix corner case in hoist_props (#4024)
fixes #4023
2020-07-26 09:27:34 +08:00
Alex Lam S.L
a98ba994bd reduce ufuzz test cases that fail to minify() (#4021) 2020-07-21 17:22:18 +08:00
Alex Lam S.L
cd671221c5 fix corner case in ie8 & reduce_vars (#4020)
fixes #4019
2020-07-21 17:22:18 +08:00
Alex Lam S.L
bce3919748 fix corner case in unused (#4018)
fixes #4017
2020-07-21 17:21:58 +08:00
Alex Lam S.L
61b66e83f1 fix corner case in ie8 (#4016)
fixes #4015
2020-07-21 02:32:20 +08:00
Alex Lam S.L
a5db8cd14c fix corner case in collapse_vars (#4013)
fixes #4012
2020-07-20 23:28:13 +08:00
Alex Lam S.L
2021c2fa3e fix corner case in false positive detection (#4011) 2020-07-20 21:57:22 +08:00
Alex Lam S.L
484d3fd8c7 fix corner case in side_effects (#4009)
fixes #4008
2020-07-01 11:33:48 +08:00
Alex Lam S.L
3bf8699f95 fix corner case in inline (#4007)
fixes #4006
2020-06-29 09:06:23 +08:00
37 changed files with 3894 additions and 1160 deletions

View File

@@ -1,5 +1,8 @@
name: CI name: CI
on: [ push, pull_request ] on:
pull_request:
push:
branches: [ master ]
jobs: jobs:
test: test:
strategy: strategy:
@@ -19,7 +22,7 @@ jobs:
TYPE: ${{ matrix.script }} TYPE: ${{ matrix.script }}
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- uses: actions/cache@v1 - uses: actions/cache@v2
with: with:
path: tmp path: tmp
key: tmp ${{ matrix.script }} key: tmp ${{ matrix.script }}

View File

@@ -2,7 +2,11 @@ name: Fuzzing
on: on:
pull_request: pull_request:
schedule: schedule:
- cron: "*/8 * * * *" - cron: "*/5 * * * *"
env:
BASE_URL: https://api.github.com/repos/${{ github.repository }}
CAUSE: ${{ github.event_name }}
TOKEN: ${{ github.token }}
jobs: jobs:
ufuzz: ufuzz:
strategy: strategy:
@@ -32,4 +36,12 @@ jobs:
npm config set update-notifier false npm config set update-notifier false
npm --version npm --version
while !(npm install); do echo "'npm install' failed - retrying..."; done while !(npm install); do echo "'npm install' failed - retrying..."; done
node test/ufuzz/job 3600000 PERIOD=1800000
if [[ $CAUSE == "schedule" ]]; then
PERIOD=`node test/ufuzz/actions $BASE_URL $TOKEN`
fi
if (( $PERIOD == 0 )); then
echo "too many jobs in queue - skipping..."
else
node test/ufuzz/job $PERIOD
fi

View File

@@ -212,13 +212,12 @@ Example:
To enable the mangler you need to pass `--mangle` (`-m`). The following To enable the mangler you need to pass `--mangle` (`-m`). The following
(comma-separated) options are supported: (comma-separated) options are supported:
- `toplevel` (default `false`) -- mangle names declared in the top level scope. - `eval` (default `false`) -- mangle names visible in scopes where `eval` or
`with` are used.
- `eval` (default `false`) -- mangle names visible in scopes where `eval` or `with` are used. - `reserved` (default: `[]`) -- when mangling is enabled but you want to
prevent certain names from being mangled, you can declare those names with
When mangling is enabled but you want to prevent certain names from being `--mangle reserved` — pass a comma-separated list of names. For example:
mangled, you can declare those names with `--mangle reserved` — pass a
comma-separated list of names. For example:
uglifyjs ... -m reserved=['$','require','exports'] uglifyjs ... -m reserved=['$','require','exports']
@@ -657,8 +656,8 @@ to be `false` and all symbol names will be omitted.
- `hoist_props` (default: `true`) -- hoist properties from constant object and - `hoist_props` (default: `true`) -- hoist properties from constant object and
array literals into regular variables subject to a set of constraints. For example: array literals into regular variables subject to a set of constraints. For example:
`var o={p:1, q:2}; f(o.p, o.q);` is converted to `f(1, 2);`. Note: `hoist_props` `var o={p:1, q:2}; f(o.p, o.q);` is converted to `f(1, 2);`. Note: `hoist_props`
works best with `mangle` enabled, the `compress` option `passes` set to `2` or higher, works best with `toplevel` and `mangle` enabled, alongside with `compress` option
and the `compress` option `toplevel` enabled. `passes` set to `2` or higher.
- `hoist_vars` (default: `false`) -- hoist `var` declarations (this is `false` - `hoist_vars` (default: `false`) -- hoist `var` declarations (this is `false`
by default because it seems to increase the size of the output in general) by default because it seems to increase the size of the output in general)
@@ -1157,3 +1156,19 @@ To allow for better optimizations, the compiler makes various assumptions:
- Object properties can be added, removed and modified (not prevented with - Object properties can be added, removed and modified (not prevented with
`Object.defineProperty()`, `Object.defineProperties()`, `Object.freeze()`, `Object.defineProperty()`, `Object.defineProperties()`, `Object.freeze()`,
`Object.preventExtensions()` or `Object.seal()`). `Object.preventExtensions()` or `Object.seal()`).
- When `toplevel` is enabled, UglifyJS effectively assumes input code is wrapped
within `function(){ ... }`, thus forbids aliasing of declared global variables:
```js
A = "FAIL";
var B = "FAIL";
// can be `global`, `self`, `window` etc.
var top = function() {
return this;
}();
// "PASS"
top.A = "PASS";
console.log(A);
// "FAIL" after compress and/or mangle
top.B = "PASS";
console.log(B);
```

View File

@@ -120,6 +120,20 @@ var AST_Node = DEFNODE("Node", "start end", {
ctor.prototype._validate.call(this); ctor.prototype._validate.call(this);
} while (ctor = ctor.BASE); } while (ctor = ctor.BASE);
}, },
validate_ast: function() {
var marker = {};
this.walk(new TreeWalker(function(node) {
if (node.validate_visited === marker) {
throw new Error(string_template("cannot reuse {type} from [{file}:{line},{col}]", {
type: "AST_" + node.TYPE,
file: node.start.file,
line: node.start.line,
col: node.start.col,
}));
}
node.validate_visited = marker;
}));
},
}, null); }, null);
(AST_Node.log_function = function(fn, verbose) { (AST_Node.log_function = function(fn, verbose) {
@@ -274,10 +288,13 @@ var AST_LabeledStatement = DEFNODE("LabeledStatement", "label", {
var label = node.label; var label = node.label;
var def = this.label; var def = this.label;
node.walk(new TreeWalker(function(node) { node.walk(new TreeWalker(function(node) {
if (node instanceof AST_LoopControl && node.label && node.label.thedef === def) { if (node instanceof AST_LoopControl) {
if (!node.label || node.label.thedef !== def) return;
node.label.thedef = label; node.label.thedef = label;
label.references.push(node); label.references.push(node);
return true;
} }
if (node instanceof AST_Scope) return true;
})); }));
} }
return node; return node;
@@ -395,16 +412,16 @@ var AST_With = DEFNODE("With", "expression", {
/* -----[ scope and functions ]----- */ /* -----[ scope and functions ]----- */
var AST_Scope = DEFNODE("Scope", "variables functions uses_with uses_eval parent_scope enclosed cname", { var AST_Scope = DEFNODE("Scope", "cname enclosed uses_eval uses_with parent_scope functions variables make_def", {
$documentation: "Base class for all statements introducing a lexical scope", $documentation: "Base class for all statements introducing a lexical scope",
$propdoc: { $propdoc: {
variables: "[Object/S] a map of name -> SymbolDef for all variables/functions defined in this scope",
functions: "[Object/S] like `variables`, but only lists function declarations",
uses_with: "[boolean/S] tells whether this scope uses the `with` statement",
uses_eval: "[boolean/S] tells whether this scope contains a direct call to the global `eval`",
parent_scope: "[AST_Scope?/S] link to the parent scope",
enclosed: "[SymbolDef*/S] a list of all symbol definitions that are accessed from this scope or any subscopes",
cname: "[integer/S] current index for mangling variables (used internally by the mangler)", cname: "[integer/S] current index for mangling variables (used internally by the mangler)",
enclosed: "[SymbolDef*/S] a list of all symbol definitions that are accessed from this scope or any subscopes",
uses_eval: "[boolean/S] tells whether this scope contains a direct call to the global `eval`",
uses_with: "[boolean/S] tells whether this scope uses the `with` statement",
parent_scope: "[AST_Scope?/S] link to the parent scope",
functions: "[Object/S] like `variables`, but only lists function declarations",
variables: "[Object/S] a map of name -> SymbolDef for all variables/functions defined in this scope",
}, },
clone: function(deep) { clone: function(deep) {
var node = this._clone(deep); var node = this._clone(deep);

View File

@@ -167,7 +167,7 @@ merge(Compressor.prototype, {
if (def.global) for (var i = 0; i < def.orig.length; i++) if (def.global) for (var i = 0; i < def.orig.length; i++)
if (!this.toplevel[def.orig[i] instanceof AST_SymbolDefun ? "funcs" : "vars"]) if (!this.toplevel[def.orig[i] instanceof AST_SymbolDefun ? "funcs" : "vars"])
return true; return true;
return false; return def.undeclared;
}, },
compress: function(node) { compress: function(node) {
node = node.resolve_defines(this); node = node.resolve_defines(this);
@@ -554,13 +554,6 @@ merge(Compressor.prototype, {
if (is_arguments(def) && node.property instanceof AST_Number) def.reassigned = true; if (is_arguments(def) && node.property instanceof AST_Number) def.reassigned = true;
} }
var suppressor = new TreeWalker(function(node) {
if (!(node instanceof AST_Symbol)) return;
var d = node.definition();
if (!d) return;
if (node instanceof AST_SymbolRef) push_ref(d, node);
d.fixed = false;
});
def(AST_Accessor, function(tw, descend, compressor) { def(AST_Accessor, function(tw, descend, compressor) {
push(tw); push(tw);
reset_variables(tw, compressor, this); reset_variables(tw, compressor, this);
@@ -587,7 +580,10 @@ merge(Compressor.prototype, {
d.assignments++; d.assignments++;
var fixed = d.fixed; var fixed = d.fixed;
var value = eq ? node.right : node; var value = eq ? node.right : node;
if (is_modified(compressor, tw, node, value, 0)) return; if (is_modified(compressor, tw, node, value, 0)) {
d.fixed = false;
return;
}
var safe = eq || safe_to_read(tw, d); var safe = eq || safe_to_read(tw, d);
node.right.walk(tw); node.right.walk(tw);
if (safe && safe_to_assign(tw, d)) { if (safe && safe_to_assign(tw, d)) {
@@ -600,6 +596,7 @@ merge(Compressor.prototype, {
return node.right; return node.right;
}; };
} else { } else {
if (d.single_use) d.single_use = false;
sym.fixed = d.fixed = function() { sym.fixed = d.fixed = function() {
return make_node(AST_Binary, node, { return make_node(AST_Binary, node, {
operator: node.operator.slice(0, -1), operator: node.operator.slice(0, -1),
@@ -736,11 +733,19 @@ merge(Compressor.prototype, {
return true; return true;
}); });
def(AST_ForIn, function(tw) { def(AST_ForIn, function(tw) {
this.init.walk(suppressor);
this.object.walk(tw); this.object.walk(tw);
var saved_loop = tw.in_loop; var saved_loop = tw.in_loop;
tw.in_loop = this; tw.in_loop = this;
push(tw); push(tw);
var init = this.init;
init.walk(tw);
if (init instanceof AST_Var) {
init = init.definitions[0].name;
} else while (init instanceof AST_PropAccess) {
init = init.expression.tail_node();
}
var def = init.definition();
if (def) def.fixed = false;
this.body.walk(tw); this.body.walk(tw);
pop(tw); pop(tw);
tw.in_loop = saved_loop; tw.in_loop = saved_loop;
@@ -896,6 +901,7 @@ merge(Compressor.prototype, {
if (safe_to_read(tw, d) && safe_to_assign(tw, d)) { if (safe_to_read(tw, d) && safe_to_assign(tw, d)) {
push_ref(d, exp); push_ref(d, exp);
mark(tw, d); mark(tw, d);
if (d.single_use) d.single_use = false;
d.fixed = function() { d.fixed = function() {
return make_node(AST_Binary, node, { return make_node(AST_Binary, node, {
operator: node.operator.slice(0, -1), operator: node.operator.slice(0, -1),
@@ -1088,11 +1094,14 @@ merge(Compressor.prototype, {
// func(something) because that changes the meaning of // func(something) because that changes the meaning of
// the func (becomes lexical instead of global). // the func (becomes lexical instead of global).
function maintain_this_binding(compressor, parent, orig, val) { function maintain_this_binding(compressor, parent, orig, val) {
if (parent instanceof AST_UnaryPrefix && parent.operator == "delete" var wrap = false;
|| parent.TYPE == "Call" && parent.expression === orig && needs_unbinding(compressor, val)) { if (parent.TYPE == "Call") {
return make_sequence(orig, [ make_node(AST_Number, orig, { value: 0 }), val ]); wrap = parent.expression === orig && needs_unbinding(compressor, val);
} else if (parent instanceof AST_UnaryPrefix) {
wrap = parent.operator == "delete"
|| parent.operator == "typeof" && is_undeclared_ref(val);
} }
return val; return wrap ? make_sequence(orig, [ make_node(AST_Number, orig, { value: 0 }), val ]) : val;
} }
function merge_sequence(array, node) { function merge_sequence(array, node) {
@@ -1197,7 +1206,9 @@ merge(Compressor.prototype, {
function find_loop_scope_try() { function find_loop_scope_try() {
var node = compressor.self(), level = 0; var node = compressor.self(), level = 0;
do { do {
if (node instanceof AST_Catch || node instanceof AST_Finally) { if (node instanceof AST_Catch) {
if (!compressor.parent(level).bfinally) level++;
} else if (node instanceof AST_Finally) {
level++; level++;
} else if (node instanceof AST_IterationStatement) { } else if (node instanceof AST_IterationStatement) {
in_loop = true; in_loop = true;
@@ -1279,7 +1290,7 @@ merge(Compressor.prototype, {
col: node.start.col col: node.start.col
}); });
if (candidate instanceof AST_UnaryPostfix) { if (candidate instanceof AST_UnaryPostfix) {
lhs.definition().fixed = false; if (lhs instanceof AST_SymbolRef) lhs.definition().fixed = false;
return make_node(AST_UnaryPrefix, candidate, candidate); return make_node(AST_UnaryPrefix, candidate, candidate);
} }
if (candidate instanceof AST_VarDef) { if (candidate instanceof AST_VarDef) {
@@ -1379,6 +1390,7 @@ merge(Compressor.prototype, {
var scan_lhs = lhs && !side_effects && !is_lhs_read_only(lhs, compressor); var scan_lhs = lhs && !side_effects && !is_lhs_read_only(lhs, compressor);
var scan_rhs = foldable(candidate); var scan_rhs = foldable(candidate);
if (!scan_lhs && !scan_rhs) continue; if (!scan_lhs && !scan_rhs) continue;
var read_toplevel = false;
var modify_toplevel = false; var modify_toplevel = false;
// Locate symbols which may execute code outside of scanning range // Locate symbols which may execute code outside of scanning range
var lvalues = get_lvalues(candidate); var lvalues = get_lvalues(candidate);
@@ -1541,8 +1553,9 @@ merge(Compressor.prototype, {
return lvalues.has(node.name.name) || side_effects && may_modify(node.name); return lvalues.has(node.name.name) || side_effects && may_modify(node.name);
} }
var sym = is_lhs(node.left, node); var sym = is_lhs(node.left, node);
if (sym && lvalues.has(sym.name)) return true;
if (sym instanceof AST_PropAccess) return true; if (sym instanceof AST_PropAccess) return true;
if (!(sym instanceof AST_SymbolRef)) return false;
return lvalues.has(sym.name) || read_toplevel && compressor.exposed(sym.definition());
} }
function extract_args() { function extract_args() {
@@ -1825,7 +1838,9 @@ merge(Compressor.prototype, {
if (parent instanceof AST_SimpleStatement) return find_stop_unused(parent, level + 1); if (parent instanceof AST_SimpleStatement) return find_stop_unused(parent, level + 1);
if (parent instanceof AST_Switch) return find_stop_unused(parent, level + 1); if (parent instanceof AST_Switch) return find_stop_unused(parent, level + 1);
if (parent instanceof AST_Unary) return find_stop_unused(parent, level + 1); if (parent instanceof AST_Unary) return find_stop_unused(parent, level + 1);
if (parent instanceof AST_VarDef) return find_stop_unused(parent, level + 1); if (parent instanceof AST_VarDef) {
return may_throw(parent) ? node : find_stop_unused(parent, level + 1);
}
return null; return null;
} }
@@ -1929,19 +1944,21 @@ merge(Compressor.prototype, {
}; };
} }
function may_be_global(node) {
if (node instanceof AST_SymbolRef) {
node = node.fixed_value();
if (!node) return true;
}
if (node instanceof AST_Assign) return node.operator == "=" && may_be_global(node.right);
return node instanceof AST_PropAccess || node instanceof AST_This;
}
function get_lvalues(expr) { function get_lvalues(expr) {
var lvalues = new Dictionary(); var lvalues = new Dictionary();
if (candidate instanceof AST_VarDef) lvalues.add(candidate.name.name, lhs); if (expr instanceof AST_VarDef) lvalues.add(expr.name.name, lhs);
var scan_iife = scope instanceof AST_Toplevel; var find_arguments = scope.uses_arguments && !compressor.has_directive("use strict");
var scan_toplevel = scope instanceof AST_Toplevel;
var tw = new TreeWalker(function(node) { var tw = new TreeWalker(function(node) {
if (scan_iife && node.TYPE == "Call") {
var exp = node.expression;
if (exp instanceof AST_PropAccess) return;
if (exp instanceof AST_Function && !exp.contains_this()) return;
modify_toplevel = true;
scan_iife = false;
return;
}
var value; var value;
if (node instanceof AST_SymbolRef) { if (node instanceof AST_SymbolRef) {
value = node.fixed_value() || node; value = node.fixed_value() || node;
@@ -1949,6 +1966,28 @@ merge(Compressor.prototype, {
value = node; value = node;
} }
if (value) lvalues.add(node.name, is_modified(compressor, tw, node, value, 0)); if (value) lvalues.add(node.name, is_modified(compressor, tw, node, value, 0));
if (find_arguments && node instanceof AST_Sub) {
scope.argnames.forEach(function(argname) {
if (!compressor.option("reduce_vars") || argname.definition().assignments) {
lvalues.add(argname.name, true);
}
});
find_arguments = false;
}
if (!scan_toplevel) return;
if (node.TYPE == "Call") {
if (modify_toplevel) return;
var exp = node.expression;
if (exp instanceof AST_PropAccess) return;
if (exp instanceof AST_Function && !exp.contains_this()) return;
modify_toplevel = true;
} else if (node instanceof AST_PropAccess && may_be_global(node.expression)) {
if (node === lhs && !(expr instanceof AST_Unary)) {
modify_toplevel = true;
} else {
read_toplevel = true;
}
}
}); });
expr.walk(tw); expr.walk(tw);
return lvalues; return lvalues;
@@ -1958,9 +1997,12 @@ merge(Compressor.prototype, {
if (expr.name instanceof AST_SymbolFunarg) { if (expr.name instanceof AST_SymbolFunarg) {
var index = compressor.self().argnames.indexOf(expr.name); var index = compressor.self().argnames.indexOf(expr.name);
var args = compressor.parent().args; var args = compressor.parent().args;
if (args[index]) args[index] = make_node(AST_Number, args[index], { if (args[index]) {
args[index] = make_node(AST_Number, args[index], {
value: 0 value: 0
}); });
expr.name.definition().fixed = false;
}
return true; return true;
} }
var end = hit_stack.length - 1; var end = hit_stack.length - 1;
@@ -3065,7 +3107,7 @@ merge(Compressor.prototype, {
(function(def) { (function(def) {
function to_node(value, orig) { function to_node(value, orig) {
if (value instanceof AST_Node) return make_node(value.CTOR, orig, value); if (value instanceof AST_Node) return value.clone(true);
if (Array.isArray(value)) return make_node(AST_Array, orig, { if (Array.isArray(value)) return make_node(AST_Array, orig, {
elements: value.map(function(value) { elements: value.map(function(value) {
return to_node(value, orig); return to_node(value, orig);
@@ -3114,11 +3156,11 @@ merge(Compressor.prototype, {
return this.expression._find_defs(compressor, "." + this.property + suffix); return this.expression._find_defs(compressor, "." + this.property + suffix);
}); });
def(AST_SymbolDeclaration, function(compressor) { def(AST_SymbolDeclaration, function(compressor) {
if (!this.global()) return; if (!this.definition().global) return;
if (HOP(compressor.option("global_defs"), this.name)) warn(this); if (HOP(compressor.option("global_defs"), this.name)) warn(this);
}); });
def(AST_SymbolRef, function(compressor, suffix) { def(AST_SymbolRef, function(compressor, suffix) {
if (!this.global()) return; if (!this.definition().global) return;
var defines = compressor.option("global_defs"); var defines = compressor.option("global_defs");
var name = this.name + suffix; var name = this.name + suffix;
if (HOP(defines, name)) return to_node(defines[name], this); if (HOP(defines, name)) return to_node(defines[name], this);
@@ -3463,7 +3505,8 @@ merge(Compressor.prototype, {
var left = this.left._eval(compressor, ignore_side_effects, cached, depth); var left = this.left._eval(compressor, ignore_side_effects, cached, depth);
if (left === this.left) return this; if (left === this.left) return this;
if (this.operator == (left ? "||" : "&&")) return left; if (this.operator == (left ? "||" : "&&")) return left;
var right = this.right._eval(compressor, ignore_side_effects, cached, depth); var rhs_ignore_side_effects = ignore_side_effects && !(left && typeof left == "object");
var right = this.right._eval(compressor, rhs_ignore_side_effects, cached, depth);
if (right === this.right) return this; if (right === this.right) return this;
var result; var result;
switch (this.operator) { switch (this.operator) {
@@ -3530,6 +3573,7 @@ merge(Compressor.prototype, {
escaped[0].walk(new TreeWalker(function(node) { escaped[0].walk(new TreeWalker(function(node) {
if (found) return true; if (found) return true;
if (node === ref) return found = true; if (node === ref) return found = true;
if (node instanceof AST_Scope) return true;
})); }));
return found; return found;
default: default:
@@ -4416,7 +4460,7 @@ merge(Compressor.prototype, {
if (drop_funcs && node !== self && node instanceof AST_Defun) { if (drop_funcs && node !== self && node instanceof AST_Defun) {
var def = node.name.definition(); var def = node.name.definition();
if (!(def.id in in_use_ids)) { if (!(def.id in in_use_ids)) {
log(node.name, "Dropping unused function {name} [{file}:{line},{col}]", template(node.name)); log(node.name, "Dropping unused function {name}");
def.eliminated++; def.eliminated++;
return in_list ? List.skip : make_node(AST_EmptyStatement, node); return in_list ? List.skip : make_node(AST_EmptyStatement, node);
} }
@@ -4431,7 +4475,7 @@ merge(Compressor.prototype, {
if (!(sym.definition().id in in_use_ids)) { if (!(sym.definition().id in in_use_ids)) {
sym.__unused = true; sym.__unused = true;
if (trim) { if (trim) {
log(sym, "Dropping unused function argument {name} [{file}:{line},{col}]", template(sym)); log(sym, "Dropping unused function argument {name}");
a.pop(); a.pop();
} }
} else { } else {
@@ -4454,8 +4498,18 @@ merge(Compressor.prototype, {
var sym = def.name.definition(); var sym = def.name.definition();
if (!drop_vars || sym.id in in_use_ids) { if (!drop_vars || sym.id in in_use_ids) {
if (def.value && indexOf_assign(sym, def) < 0) { if (def.value && indexOf_assign(sym, def) < 0) {
def.value = def.value.drop_side_effect_free(compressor); var write_only = def.value.write_only;
if (def.value) def.value.tail_node().write_only = false; var value = def.value.drop_side_effect_free(compressor);
if (def.value !== value) {
def.value = value && make_sequence(def.value, [
value,
make_node(AST_Number, def.value, {
value: 0
}),
]);
} else if (def.value.write_only !== write_only) {
def.value.write_only = write_only;
}
} }
var old_def, var_defs = var_defs_by_id.get(sym.id); var old_def, var_defs = var_defs_by_id.get(sym.id);
if (!def.value) { if (!def.value) {
@@ -4524,7 +4578,7 @@ merge(Compressor.prototype, {
AST_Node.warn("Side effects in initialization of unused variable {name} [{file}:{line},{col}]", template(def.name)); AST_Node.warn("Side effects in initialization of unused variable {name} [{file}:{line},{col}]", template(def.name));
side_effects.push(value); side_effects.push(value);
} else { } else {
log(def.name, "Dropping unused variable {name} [{file}:{line},{col}]", template(def.name)); log(def.name, "Dropping unused variable {name}");
} }
sym.eliminated++; sym.eliminated++;
} }
@@ -4619,19 +4673,24 @@ merge(Compressor.prototype, {
return !block ? node : in_list ? List.splice(block.body) : block; return !block ? node : in_list ? List.splice(block.body) : block;
} else if (node instanceof AST_ForIn) { } else if (node instanceof AST_ForIn) {
if (!drop_vars || !compressor.option("loops")) return; if (!drop_vars || !compressor.option("loops")) return;
if (!(node.init instanceof AST_Definitions)) return;
var sym = node.init.definitions[0].name;
if (sym.definition().id in in_use_ids) return;
if (!is_empty(node.body)) return; if (!is_empty(node.body)) return;
log(sym, "Dropping unused loop variable {name} [{file}:{line},{col}]", template(sym)); var sym = node.init;
if (sym instanceof AST_Definitions) {
sym = sym.definitions[0].name;
} else while (sym instanceof AST_PropAccess) {
sym = sym.expression.tail_node();
}
var def = sym.definition();
if (!def) return;
if (def.scope !== self) return;
if (def.id in in_use_ids) return;
log(sym, "Dropping unused loop variable {name}");
var value = node.object.drop_side_effect_free(compressor); var value = node.object.drop_side_effect_free(compressor);
if (value) { if (!value) return in_list ? List.skip : make_node(AST_EmptyStatement, node);
AST_Node.warn("Side effects in object of for-in loop [{file}:{line},{col}]", template(sym)); AST_Node.warn("Side effects in object of for-in loop [{file}:{line},{col}]", value.start);
return make_node(AST_SimpleStatement, node, { return make_node(AST_SimpleStatement, node, {
body: value body: value
}); });
}
return in_list ? List.skip : make_node(AST_EmptyStatement, node);
} else if (node instanceof AST_Sequence) { } else if (node instanceof AST_Sequence) {
if (node.expressions.length == 1) return node.expressions[0]; if (node.expressions.length == 1) return node.expressions[0];
} }
@@ -4651,8 +4710,8 @@ merge(Compressor.prototype, {
drop_unused_call_args(call, compressor, fns_with_marked_args); drop_unused_call_args(call, compressor, fns_with_marked_args);
}); });
function log(sym, text, props) { function log(sym, text) {
AST_Node[sym.unreferenced() ? "warn" : "info"](text, props); AST_Node[sym.definition().references.length > 0 ? "info" : "warn"](text + " [{file}:{line},{col}]", template(sym));
} }
function template(sym) { function template(sym) {
@@ -4740,6 +4799,13 @@ merge(Compressor.prototype, {
if (track_assigns(node_def, sym) && is_lhs(sym, node) !== sym) add_assigns(node_def, sym); if (track_assigns(node_def, sym) && is_lhs(sym, node) !== sym) add_assigns(node_def, sym);
return true; return true;
} }
if (node instanceof AST_ForIn) {
if (!drop_vars || !compressor.option("loops")) return;
if (!is_empty(node.body)) return;
if (node.init.has_side_effects(compressor)) return;
node.object.walk(tw);
return true;
}
if (node instanceof AST_SymbolRef) { if (node instanceof AST_SymbolRef) {
node_def = node.definition(); node_def = node.definition();
if (!(node_def.id in in_use_ids)) { if (!(node_def.id in in_use_ids)) {
@@ -5063,6 +5129,7 @@ merge(Compressor.prototype, {
} }
})); }));
self.transform(new TreeTransformer(function(node, descend) { self.transform(new TreeTransformer(function(node, descend) {
if (node instanceof AST_Binary) return replace("right");
if (node instanceof AST_PropAccess) { if (node instanceof AST_PropAccess) {
if (!(node.expression instanceof AST_SymbolRef)) return; if (!(node.expression instanceof AST_SymbolRef)) return;
var defs = defs_by_id[node.expression.definition().id]; var defs = defs_by_id[node.expression.definition().id];
@@ -5078,10 +5145,15 @@ merge(Compressor.prototype, {
} }
if (node instanceof AST_Unary) { if (node instanceof AST_Unary) {
if (unary_side_effects[node.operator]) return; if (unary_side_effects[node.operator]) return;
if (!(node.expression instanceof AST_SymbolRef)) return; return replace("expression");
if (!(node.expression.definition().id in defs_by_id)) return; }
function replace(prop) {
var sym = node[prop];
if (!(sym instanceof AST_SymbolRef)) return;
if (!(sym.definition().id in defs_by_id)) return;
var opt = node.clone(); var opt = node.clone();
opt.expression = make_node(AST_Object, node, { opt[prop] = make_node(AST_Object, sym, {
properties: [] properties: []
}); });
return opt; return opt;
@@ -5303,10 +5375,7 @@ merge(Compressor.prototype, {
return make_sequence(this, [ expression, property ]); return make_sequence(this, [ expression, property ]);
}); });
def(AST_SymbolRef, function(compressor) { def(AST_SymbolRef, function(compressor) {
if (!this.is_declared(compressor)) return this; return this.is_declared(compressor) ? null : this;
var def = this.definition();
if (member(this, def.references)) def.replaced++;
return null;
}); });
def(AST_This, return_null); def(AST_This, return_null);
def(AST_Unary, function(compressor, first_in_statement) { def(AST_Unary, function(compressor, first_in_statement) {
@@ -5314,10 +5383,7 @@ merge(Compressor.prototype, {
this.write_only = !this.expression.has_side_effects(compressor); this.write_only = !this.expression.has_side_effects(compressor);
return this; return this;
} }
if (this.operator == "typeof" && this.expression instanceof AST_SymbolRef) { if (this.operator == "typeof" && this.expression instanceof AST_SymbolRef) return null;
this.expression.definition().replaced++;
return null;
}
var expression = this.expression.drop_side_effect_free(compressor, first_in_statement); var expression = this.expression.drop_side_effect_free(compressor, first_in_statement);
if (first_in_statement && expression && is_iife_call(expression)) { if (first_in_statement && expression && is_iife_call(expression)) {
if (expression === this.expression && this.operator == "!") return this; if (expression === this.expression && this.operator == "!") return this;
@@ -5875,28 +5941,57 @@ merge(Compressor.prototype, {
} }
body[0].body = decl.concat(body[0].body); body[0].body = decl.concat(body[0].body);
self.body = body; self.body = body;
if (body.length == 1 && (body[0] === exact_match || body[0] === default_branch)) { if (compressor.option("conditionals")) switch (body.length) {
var has_break = false; case 1:
var tw = new TreeWalker(function(node) { if (!no_break(body[0])) break;
if (has_break
|| node instanceof AST_Lambda
|| node instanceof AST_SimpleStatement) return true;
if (is_break(node, tw)) has_break = true;
});
self.walk(tw);
if (!has_break) {
var statements = body[0].body.slice();
var exp = body[0].expression; var exp = body[0].expression;
var statements = body[0].body.slice();
if (body[0] !== default_branch && body[0] !== exact_match) return make_node(AST_If, self, {
condition: make_node(AST_Binary, self, {
operator: "===",
left: self.expression,
right: exp,
}),
body: make_node(AST_BlockStatement, self, {
body: statements,
}),
alternative: null,
}).optimize(compressor);
if (exp) statements.unshift(make_node(AST_SimpleStatement, exp, { if (exp) statements.unshift(make_node(AST_SimpleStatement, exp, {
body: exp body: exp,
})); }));
statements.unshift(make_node(AST_SimpleStatement, self.expression, { statements.unshift(make_node(AST_SimpleStatement, self.expression, {
body:self.expression body:self.expression,
})); }));
return make_node(AST_BlockStatement, self, { return make_node(AST_BlockStatement, self, {
body: statements body: statements,
}).optimize(compressor); }).optimize(compressor);
} case 2:
if (!member(default_branch, body) || !no_break(body[1])) break;
var statements = body[0].body.slice();
var exclusive = statements.length && is_break(statements[statements.length - 1], compressor);
if (exclusive) statements.pop();
if (!all(statements, no_break)) break;
var alternative = body[1].body.length && make_node(AST_BlockStatement, body[1], body[1]);
var node = make_node(AST_If, self, {
condition: make_node(AST_Binary, self, body[0] === default_branch ? {
operator: "!==",
left: self.expression,
right: body[1].expression,
} : {
operator: "===",
left: self.expression,
right: body[0].expression,
}),
body: make_node(AST_BlockStatement, body[0], {
body: statements,
}),
alternative: exclusive && alternative || null,
});
if (!exclusive && alternative) node = make_node(AST_BlockStatement, self, {
body: [ node, alternative ],
});
return node.optimize(compressor);
} }
return self; return self;
@@ -5904,6 +5999,19 @@ merge(Compressor.prototype, {
return node instanceof AST_Break && tw.loopcontrol_target(node) === self; return node instanceof AST_Break && tw.loopcontrol_target(node) === self;
} }
function no_break(node) {
var found = false;
var tw = new TreeWalker(function(node) {
if (found
|| node instanceof AST_Lambda
|| node instanceof AST_SimpleStatement) return true;
if (is_break(node, tw)) found = true;
});
tw.push(self);
node.walk(tw);
return !found;
}
function eliminate_branch(branch, prev) { function eliminate_branch(branch, prev) {
if (prev && !aborts(prev)) { if (prev && !aborts(prev)) {
prev.body = prev.body.concat(branch.body); prev.body = prev.body.concat(branch.body);
@@ -6305,7 +6413,7 @@ merge(Compressor.prototype, {
} }
})); }));
var code = OutputStream(); var code = OutputStream();
AST_BlockStatement.prototype._codegen.call(fun, fun, code); AST_BlockStatement.prototype._codegen.call(fun, code);
self.args = [ self.args = [
make_node(AST_String, self, { make_node(AST_String, self, {
value: fun.argnames.map(function(arg) { value: fun.argnames.map(function(arg) {
@@ -6415,17 +6523,12 @@ merge(Compressor.prototype, {
function return_value(stat) { function return_value(stat) {
if (!stat) return make_node(AST_Undefined, self); if (!stat) return make_node(AST_Undefined, self);
if (stat instanceof AST_Return) { if (stat instanceof AST_Return) return stat.value || make_node(AST_Undefined, self);
if (!stat.value) return make_node(AST_Undefined, self); if (stat instanceof AST_SimpleStatement) return make_node(AST_UnaryPrefix, stat, {
return stat.value.clone(true);
}
if (stat instanceof AST_SimpleStatement) {
return make_node(AST_UnaryPrefix, stat, {
operator: "void", operator: "void",
expression: stat.body expression: stat.body
}); });
} }
}
function can_flatten_body(stat) { function can_flatten_body(stat) {
var len = fn.body.length; var len = fn.body.length;
@@ -6805,72 +6908,70 @@ merge(Compressor.prototype, {
var SIGN_OPS = makePredicate("+ -"); var SIGN_OPS = makePredicate("+ -");
var MULTIPLICATIVE_OPS = makePredicate("* / %"); var MULTIPLICATIVE_OPS = makePredicate("* / %");
OPT(AST_UnaryPrefix, function(self, compressor) { OPT(AST_UnaryPrefix, function(self, compressor) {
var e = self.expression; var op = self.operator;
var exp = self.expression;
if (compressor.option("evaluate") if (compressor.option("evaluate")
&& self.operator == "delete" && op == "delete"
&& !(e instanceof AST_SymbolRef && !(exp instanceof AST_SymbolRef
|| e instanceof AST_PropAccess || exp instanceof AST_PropAccess
|| is_identifier_atom(e))) { || is_identifier_atom(exp))) {
if (e instanceof AST_Sequence) { if (exp instanceof AST_Sequence) {
e = e.expressions.slice(); exp = exp.expressions.slice();
e.push(make_node(AST_True, self)); exp.push(make_node(AST_True, self));
return make_sequence(self, e).optimize(compressor); return make_sequence(self, exp).optimize(compressor);
} }
return make_sequence(self, [ e, make_node(AST_True, self) ]).optimize(compressor); return make_sequence(self, [ exp, make_node(AST_True, self) ]).optimize(compressor);
} }
if (compressor.option("sequences")) { if (compressor.option("sequences") && !(op == "typeof" && is_undeclared_ref(exp.tail_node()))) {
var seq = lift_sequence_in_expression(self, compressor); var seq = lift_sequence_in_expression(self, compressor);
if (seq !== self) return seq.optimize(compressor); if (seq !== self) return seq.optimize(compressor);
} }
if (compressor.option("side_effects") && self.operator == "void") { if (compressor.option("side_effects") && op == "void") {
e = e.drop_side_effect_free(compressor); exp = exp.drop_side_effect_free(compressor);
if (e) { if (!exp) return make_node(AST_Undefined, self).optimize(compressor);
self.expression = e; self.expression = exp;
return self; return self;
} else {
return make_node(AST_Undefined, self).optimize(compressor);
}
} }
if (compressor.option("booleans")) { if (compressor.option("booleans")) {
if (self.operator == "!" && e.is_truthy()) { if (op == "!" && exp.is_truthy()) {
return make_sequence(self, [ e, make_node(AST_False, self) ]).optimize(compressor); return make_sequence(self, [ exp, make_node(AST_False, self) ]).optimize(compressor);
} else if (compressor.in_boolean_context()) switch (self.operator) { } else if (compressor.in_boolean_context()) switch (op) {
case "!": case "!":
if (e instanceof AST_UnaryPrefix && e.operator == "!") { if (exp instanceof AST_UnaryPrefix && exp.operator == "!") {
// !!foo => foo, if we're in boolean context // !!foo => foo, if we're in boolean context
return e.expression; return exp.expression;
} }
if (e instanceof AST_Binary) { if (exp instanceof AST_Binary) {
self = best_of(compressor, self, e.negate(compressor, first_in_statement(compressor))); self = best_of(compressor, self, exp.negate(compressor, first_in_statement(compressor)));
} }
break; break;
case "typeof": case "typeof":
// typeof always returns a non-empty string, thus it's // typeof always returns a non-empty string, thus it's
// always true in booleans // always true in booleans
AST_Node.warn("Boolean expression always true [{file}:{line},{col}]", self.start); AST_Node.warn("Boolean expression always true [{file}:{line},{col}]", self.start);
return (e instanceof AST_SymbolRef ? make_node(AST_True, self) : make_sequence(self, [ return (exp instanceof AST_SymbolRef ? make_node(AST_True, self) : make_sequence(self, [
e, exp,
make_node(AST_True, self) make_node(AST_True, self)
])).optimize(compressor); ])).optimize(compressor);
} }
} }
if (self.operator == "-" && e instanceof AST_Infinity) e = e.transform(compressor); if (op == "-" && exp instanceof AST_Infinity) exp = exp.transform(compressor);
if (compressor.option("evaluate") if (compressor.option("evaluate")
&& e instanceof AST_Binary && exp instanceof AST_Binary
&& SIGN_OPS[self.operator] && SIGN_OPS[op]
&& MULTIPLICATIVE_OPS[e.operator] && MULTIPLICATIVE_OPS[exp.operator]
&& (e.left.is_constant() || !e.right.has_side_effects(compressor))) { && (exp.left.is_constant() || !exp.right.has_side_effects(compressor))) {
return make_node(AST_Binary, self, { return make_node(AST_Binary, self, {
operator: e.operator, operator: exp.operator,
left: make_node(AST_UnaryPrefix, e.left, { left: make_node(AST_UnaryPrefix, exp.left, {
operator: self.operator, operator: op,
expression: e.left expression: exp.left
}), }),
right: e.right right: exp.right
}); });
} }
// avoids infinite recursion of numerals // avoids infinite recursion of numerals
return self.operator == "-" && (e instanceof AST_Number || e instanceof AST_Infinity) return op == "-" && (exp instanceof AST_Number || exp instanceof AST_Infinity)
? self : try_evaluate(compressor, self); ? self : try_evaluate(compressor, self);
}); });
@@ -7027,7 +7128,7 @@ merge(Compressor.prototype, {
&& self.right instanceof AST_SymbolRef && self.right instanceof AST_SymbolRef
&& self.left.definition() === self.right.definition() && self.left.definition() === self.right.definition()
&& is_object(self.left)) { && is_object(self.left)) {
return make_node(self.operator[0] == "=" ? AST_True : AST_False, self); return make_node(self.operator[0] == "=" ? AST_True : AST_False, self).optimize(compressor);
} }
break; break;
case "&&": case "&&":
@@ -7099,11 +7200,11 @@ merge(Compressor.prototype, {
var parent = compressor.parent(); var parent = compressor.parent();
if (compressor.option("comparisons") && self.is_boolean(compressor)) { if (compressor.option("comparisons") && self.is_boolean(compressor)) {
if (!(parent instanceof AST_Binary) || parent instanceof AST_Assign) { if (!(parent instanceof AST_Binary) || parent instanceof AST_Assign) {
var negated = make_node(AST_UnaryPrefix, self, { var negated = best_of(compressor, self, make_node(AST_UnaryPrefix, self, {
operator: "!", operator: "!",
expression: self.negate(compressor, first_in_statement(compressor)) expression: self.negate(compressor, first_in_statement(compressor))
}); }));
self = best_of(compressor, self, negated); if (negated !== self) return negated;
} }
switch (self.operator) { switch (self.operator) {
case ">": reverse("<"); break; case ">": reverse("<"); break;
@@ -7424,6 +7525,7 @@ merge(Compressor.prototype, {
} }
break; break;
} }
if (!(parent instanceof AST_UnaryPrefix && parent.operator == "delete")) {
if (self.left instanceof AST_Number && !self.right.is_constant()) switch (self.operator) { if (self.left instanceof AST_Number && !self.right.is_constant()) switch (self.operator) {
// 0 + n => n // 0 + n => n
case "+": case "+":
@@ -7476,6 +7578,7 @@ merge(Compressor.prototype, {
break; break;
} }
} }
}
if (compressor.option("typeofs")) switch (self.operator) { if (compressor.option("typeofs")) switch (self.operator) {
case "&&": case "&&":
mark_locally_defined(self.left, self.right, null); mark_locally_defined(self.left, self.right, null);
@@ -7571,7 +7674,7 @@ merge(Compressor.prototype, {
} }
function is_indexFn(node) { function is_indexFn(node) {
return node instanceof AST_Call return node.TYPE == "Call"
&& node.expression instanceof AST_Dot && node.expression instanceof AST_Dot
&& indexFns[node.expression.property]; && indexFns[node.expression.property];
} }
@@ -7630,6 +7733,8 @@ merge(Compressor.prototype, {
single_use = false; single_use = false;
} else if (recursive_ref(compressor, def)) { } else if (recursive_ref(compressor, def)) {
single_use = false; single_use = false;
} else if (compressor.option("ie8") && fixed.name && def !== fixed.name.definition()) {
single_use = false;
} else if (def.scope !== self.scope || def.orig[0] instanceof AST_SymbolFunarg) { } else if (def.scope !== self.scope || def.orig[0] instanceof AST_SymbolFunarg) {
single_use = fixed.is_constant_expression(self.scope); single_use = fixed.is_constant_expression(self.scope);
if (single_use == "f") { if (single_use == "f") {
@@ -7638,8 +7743,6 @@ merge(Compressor.prototype, {
scope.inlined = true; scope.inlined = true;
} while (scope = scope.parent_scope); } while (scope = scope.parent_scope);
} }
} else if (compressor.option("ie8") && fixed.name && def !== fixed.name.definition()) {
single_use = false;
} }
if (single_use) fixed.parent_scope = self.scope; if (single_use) fixed.parent_scope = self.scope;
} else if (!fixed || !fixed.is_constant_expression()) { } else if (!fixed || !fixed.is_constant_expression()) {
@@ -7884,12 +7987,17 @@ merge(Compressor.prototype, {
} }
} }
} else if (self.left instanceof AST_SymbolRef) { } else if (self.left instanceof AST_SymbolRef) {
if (self.operator == "=" && self.left.equivalent_to(self.right)) return self.right; var parent;
if (self.operator == "=" && self.left.equivalent_to(self.right)
&& !((parent = compressor.parent()) instanceof AST_UnaryPrefix && parent.operator == "delete")) {
return self.right;
}
if (self.left.is_immutable()) return strip_assignment(); if (self.left.is_immutable()) return strip_assignment();
var def = self.left.definition(); var def = self.left.definition();
var scope = def.scope.resolve(); var scope = def.scope.resolve();
var local = scope === compressor.find_parent(AST_Lambda); var local = scope === compressor.find_parent(AST_Lambda);
var level = 0, node, parent = self; var level = 0, node;
parent = self;
do { do {
node = parent; node = parent;
parent = compressor.parent(level++); parent = compressor.parent(level++);
@@ -8428,7 +8536,9 @@ merge(Compressor.prototype, {
} }
} }
if (is_lhs(compressor.self(), parent)) return self; if (is_lhs(compressor.self(), parent)) return self;
if (compressor.option("sequences") && compressor.parent().TYPE != "Call") { if (compressor.option("sequences")
&& parent.TYPE != "Call"
&& !(parent instanceof AST_ForIn && parent.init === self)) {
var seq = lift_sequence_in_expression(self, compressor); var seq = lift_sequence_in_expression(self, compressor);
if (seq !== self) return seq.optimize(compressor); if (seq !== self) return seq.optimize(compressor);
} }
@@ -8539,8 +8649,11 @@ merge(Compressor.prototype, {
col: self.start.col col: self.start.col
}); });
} }
if (is_lhs(compressor.self(), compressor.parent())) return self; var parent = compressor.parent();
if (compressor.option("sequences") && compressor.parent().TYPE != "Call") { if (is_lhs(compressor.self(), parent)) return self;
if (compressor.option("sequences")
&& parent.TYPE != "Call"
&& !(parent instanceof AST_ForIn && parent.init === self)) {
var seq = lift_sequence_in_expression(self, compressor); var seq = lift_sequence_in_expression(self, compressor);
if (seq !== self) return seq.optimize(compressor); if (seq !== self) return seq.optimize(compressor);
} }

View File

@@ -178,13 +178,17 @@ function minify(files, options) {
toplevel = toplevel[action](option); toplevel = toplevel[action](option);
files[toplevel.start.file] = toplevel.print_to_string().replace(orig, ""); files[toplevel.start.file] = toplevel.print_to_string().replace(orig, "");
}); });
if (options.validate) toplevel.validate_ast();
if (timings) timings.rename = Date.now(); if (timings) timings.rename = Date.now();
if (options.rename) { if (options.rename) {
toplevel.figure_out_scope(options.mangle); toplevel.figure_out_scope(options.mangle);
toplevel.expand_names(options.mangle); toplevel.expand_names(options.mangle);
} }
if (timings) timings.compress = Date.now(); if (timings) timings.compress = Date.now();
if (options.compress) toplevel = new Compressor(options.compress).compress(toplevel); if (options.compress) {
toplevel = new Compressor(options.compress).compress(toplevel);
if (options.validate) toplevel.validate_ast();
}
if (timings) timings.scope = Date.now(); if (timings) timings.scope = Date.now();
if (options.mangle) toplevel.figure_out_scope(options.mangle); if (options.mangle) toplevel.figure_out_scope(options.mangle);
if (timings) timings.mangle = Date.now(); if (timings) timings.mangle = Date.now();
@@ -193,9 +197,7 @@ function minify(files, options) {
toplevel.mangle_names(options.mangle); toplevel.mangle_names(options.mangle);
} }
if (timings) timings.properties = Date.now(); if (timings) timings.properties = Date.now();
if (options.mangle && options.mangle.properties) { if (options.mangle && options.mangle.properties) mangle_properties(toplevel, options.mangle.properties);
toplevel = mangle_properties(toplevel, options.mangle.properties);
}
if (timings) timings.output = Date.now(); if (timings) timings.output = Date.now();
var result = {}; var result = {};
if (options.output.ast) { if (options.output.ast) {

View File

@@ -100,7 +100,7 @@ function OutputStream(options) {
} }
} }
var indentation = 0; var indentation = options.indent_start;
var current_col = 0; var current_col = 0;
var current_line = 1; var current_line = 1;
var current_pos = 0; var current_pos = 0;
@@ -191,10 +191,6 @@ function OutputStream(options) {
return name; return name;
} }
function make_indent(back) {
return repeat_string(" ", options.indent_start + indentation - back * options.indent_level);
}
/* -----[ beautification/minification ]----- */ /* -----[ beautification/minification ]----- */
var has_parens = false; var has_parens = false;
@@ -345,9 +341,7 @@ function OutputStream(options) {
}; };
var indent = options.beautify ? function(half) { var indent = options.beautify ? function(half) {
if (options.beautify) { print(repeat_string(" ", half ? indentation - (options.indent_level >> 1) : indentation));
print(make_indent(half ? 0.5 : 0));
}
} : noop; } : noop;
var with_indent = options.beautify ? function(col, cont) { var with_indent = options.beautify ? function(col, cont) {
@@ -575,9 +569,9 @@ function OutputStream(options) {
get : get, get : get,
toString : get, toString : get,
indent : indent, indent : indent,
indentation : function() { return indentation }, should_break : readonly ? noop : function() {
current_width : function() { return current_col - indentation }, return options.width && current_col - indentation >= options.width;
should_break : function() { return options.width && this.current_width() >= options.width }, },
has_parens : function() { return has_parens }, has_parens : function() { return has_parens },
newline : newline, newline : newline,
print : print, print : print,
@@ -630,13 +624,7 @@ function OutputStream(options) {
var use_asm = false; var use_asm = false;
AST_Node.DEFMETHOD("print", function(stream, force_parens) { AST_Node.DEFMETHOD("print", function(stream, force_parens) {
var self = this, generator = self._codegen; var self = this;
function doit() {
stream.prepend_comments(self);
self.add_source_map(stream);
generator(self, stream);
stream.append_comments(self);
}
stream.push_node(self); stream.push_node(self);
if (force_parens || self.needs_parens(stream)) { if (force_parens || self.needs_parens(stream)) {
stream.with_parens(doit); stream.with_parens(doit);
@@ -644,9 +632,14 @@ function OutputStream(options) {
doit(); doit();
} }
stream.pop_node(); stream.pop_node();
});
AST_Node.DEFMETHOD("_print", AST_Node.prototype.print);
function doit() {
stream.prepend_comments(self);
self.add_source_map(stream);
self._codegen(stream);
stream.append_comments(self);
}
});
AST_Node.DEFMETHOD("print_to_string", function(options) { AST_Node.DEFMETHOD("print_to_string", function(options) {
var s = OutputStream(options); var s = OutputStream(options);
this.print(s); this.print(s);
@@ -689,78 +682,66 @@ function OutputStream(options) {
PARENS(AST_Unary, function(output) { PARENS(AST_Unary, function(output) {
var p = output.parent(); var p = output.parent();
return p instanceof AST_PropAccess && p.expression === this return (p instanceof AST_Call || p instanceof AST_PropAccess) && p.expression === this;
|| p instanceof AST_Call && p.expression === this;
}); });
PARENS(AST_Sequence, function(output) { PARENS(AST_Sequence, function(output) {
var p = output.parent(); var p = output.parent();
// (foo, bar)() or foo(1, (2, 3), 4) // [ 1, (2, 3), 4 ] ==> [ 1, 3, 4 ]
return p instanceof AST_Call return p instanceof AST_Array
// !(foo, bar, baz)
|| p instanceof AST_Unary
// 1 + (2, 3) + 4 ==> 8 // 1 + (2, 3) + 4 ==> 8
|| p instanceof AST_Binary || p instanceof AST_Binary
// var a = (1, 2), b = a + a; ==> b == 4 // new (foo, bar) or foo(1, (2, 3), 4)
|| p instanceof AST_VarDef || p instanceof AST_Call
// (1, {foo:2}).foo or (1, {foo:2})["foo"] ==> 2
|| p instanceof AST_PropAccess && p.expression === this
// [ 1, (2, 3), 4 ] ==> [ 1, 3, 4 ]
|| p instanceof AST_Array
// { foo: (1, 2) }.foo ==> 2
|| p instanceof AST_ObjectProperty
// (false, true) ? (a = 10, b = 20) : (c = 30) // (false, true) ? (a = 10, b = 20) : (c = 30)
// ==> 20 (side effect, set a := 10 and b := 20) // ==> 20 (side effect, set a := 10 and b := 20)
|| p instanceof AST_Conditional; || p instanceof AST_Conditional
// { foo: (1, 2) }.foo ==> 2
|| p instanceof AST_ObjectProperty
// (1, {foo:2}).foo or (1, {foo:2})["foo"] ==> 2
|| p instanceof AST_PropAccess && p.expression === this
// !(foo, bar, baz)
|| p instanceof AST_Unary
// var a = (1, 2), b = a + a; ==> b == 4
|| p instanceof AST_VarDef;
}); });
PARENS(AST_Binary, function(output) { PARENS(AST_Binary, function(output) {
var p = output.parent(); var p = output.parent();
// (foo && bar)()
if (p instanceof AST_Call && p.expression === this)
return true;
// typeof (foo && bar)
if (p instanceof AST_Unary)
return true;
// (foo && bar)["prop"], (foo && bar).prop
if (p instanceof AST_PropAccess && p.expression === this)
return true;
// this deals with precedence: 3 * (2 + 1) // this deals with precedence: 3 * (2 + 1)
if (p instanceof AST_Binary) { if (p instanceof AST_Binary) {
var po = p.operator, pp = PRECEDENCE[po]; var po = p.operator, pp = PRECEDENCE[po];
var so = this.operator, sp = PRECEDENCE[so]; var so = this.operator, sp = PRECEDENCE[so];
if (pp > sp return pp > sp || (pp == sp && this === p.right);
|| (pp == sp
&& this === p.right)) {
return true;
}
} }
// (foo && bar)()
if (p instanceof AST_Call) return p.expression === this;
// (foo && bar)["prop"], (foo && bar).prop
if (p instanceof AST_PropAccess) return p.expression === this;
// typeof (foo && bar)
if (p instanceof AST_Unary) return true;
}); });
PARENS(AST_PropAccess, function(output) { PARENS(AST_PropAccess, function(output) {
var node = this;
var p = output.parent(); var p = output.parent();
if (p instanceof AST_New && p.expression === this) { if (p instanceof AST_New && p.expression === node) {
// i.e. new (foo.bar().baz) // i.e. new (foo().bar)
// //
// if there's one call into this subtree, then we need // if there's one call into this subtree, then we need
// parens around it too, otherwise the call will be // parens around it too, otherwise the call will be
// interpreted as passing the arguments to the upper New // interpreted as passing the arguments to the upper New
// expression. // expression.
var parens = false; do {
this.walk(new TreeWalker(function(node) { node = node.expression;
if (parens || node instanceof AST_Scope) return true; } while (node instanceof AST_PropAccess);
if (node instanceof AST_Call) { return node.TYPE == "Call";
parens = true;
return true;
}
}));
return parens;
} }
}); });
PARENS(AST_Call, function(output) { PARENS(AST_Call, function(output) {
var p = output.parent(); var p = output.parent();
if (p instanceof AST_New && p.expression === this) return true; if (p instanceof AST_New) return p.expression === this;
// https://bugs.webkit.org/show_bug.cgi?id=123506 // https://bugs.webkit.org/show_bug.cgi?id=123506
if (output.option('webkit')) { if (output.option('webkit')) {
var g = output.parent(1); var g = output.parent(1);
@@ -773,11 +754,12 @@ function OutputStream(options) {
}); });
PARENS(AST_New, function(output) { PARENS(AST_New, function(output) {
if (need_constructor_parens(this, output)) return false;
var p = output.parent(); var p = output.parent();
if (!need_constructor_parens(this, output) // (new foo)(bar)
&& (p instanceof AST_PropAccess // (new Date).getTime(), (new Date)["getTime"]() if (p instanceof AST_Call) return p.expression === this;
|| p instanceof AST_Call && p.expression === this)) // (new foo)(bar) // (new Date).getTime(), (new Date)["getTime"]()
return true; return p instanceof AST_PropAccess;
}); });
PARENS(AST_Number, function(output) { PARENS(AST_Number, function(output) {
@@ -786,36 +768,29 @@ function OutputStream(options) {
var value = this.value; var value = this.value;
// https://github.com/mishoo/UglifyJS/issues/115 // https://github.com/mishoo/UglifyJS/issues/115
// https://github.com/mishoo/UglifyJS/pull/1009 // https://github.com/mishoo/UglifyJS/pull/1009
if (value < 0 || /^0/.test(make_num(value))) { return value < 0 || /^0/.test(make_num(value));
return true;
}
} }
}); });
PARENS([ AST_Assign, AST_Conditional ], function(output) { PARENS([ AST_Assign, AST_Conditional ], function(output) {
var p = output.parent(); var p = output.parent();
// !(a = false) → true
if (p instanceof AST_Unary)
return true;
// 1 + (a = 2) + 3 → 6, side effect setting a = 2 // 1 + (a = 2) + 3 → 6, side effect setting a = 2
if (p instanceof AST_Binary && !(p instanceof AST_Assign)) if (p instanceof AST_Binary) return !(p instanceof AST_Assign);
return true;
// (a = func)() —or— new (a = Object)() // (a = func)() —or— new (a = Object)()
if (p instanceof AST_Call && p.expression === this) if (p instanceof AST_Call) return p.expression === this;
return true;
// (a = foo) ? bar : baz // (a = foo) ? bar : baz
if (p instanceof AST_Conditional && p.condition === this) if (p instanceof AST_Conditional) return p.condition === this;
return true;
// (a = foo)["prop"] —or— (a = foo).prop // (a = foo)["prop"] —or— (a = foo).prop
if (p instanceof AST_PropAccess && p.expression === this) if (p instanceof AST_PropAccess) return p.expression === this;
return true; // !(a = false) → true
if (p instanceof AST_Unary) return true;
}); });
/* -----[ PRINTERS ]----- */ /* -----[ PRINTERS ]----- */
DEFPRINT(AST_Directive, function(self, output) { DEFPRINT(AST_Directive, function(output) {
var quote = self.quote; var quote = this.quote;
var value = self.value; var value = this.value;
switch (output.option("quote_style")) { switch (output.option("quote_style")) {
case 0: case 0:
case 2: case 2:
@@ -828,7 +803,7 @@ function OutputStream(options) {
output.print(quote + value + quote); output.print(quote + value + quote);
output.semicolon(); output.semicolon();
}); });
DEFPRINT(AST_Debugger, function(self, output) { DEFPRINT(AST_Debugger, function(output) {
output.print("debugger"); output.print("debugger");
output.semicolon(); output.semicolon();
}); });
@@ -864,21 +839,21 @@ function OutputStream(options) {
force_statement(this.body, output); force_statement(this.body, output);
}); });
DEFPRINT(AST_Statement, function(self, output) { DEFPRINT(AST_Statement, function(output) {
self.body.print(output); this.body.print(output);
output.semicolon(); output.semicolon();
}); });
DEFPRINT(AST_Toplevel, function(self, output) { DEFPRINT(AST_Toplevel, function(output) {
display_body(self.body, true, output, true); display_body(this.body, true, output, true);
output.print(""); output.print("");
}); });
DEFPRINT(AST_LabeledStatement, function(self, output) { DEFPRINT(AST_LabeledStatement, function(output) {
self.label.print(output); this.label.print(output);
output.colon(); output.colon();
self.body.print(output); this.body.print(output);
}); });
DEFPRINT(AST_SimpleStatement, function(self, output) { DEFPRINT(AST_SimpleStatement, function(output) {
self.body.print(output); this.body.print(output);
output.semicolon(); output.semicolon();
}); });
function print_braced_empty(self, output) { function print_braced_empty(self, output) {
@@ -895,13 +870,14 @@ function OutputStream(options) {
}); });
} else print_braced_empty(self, output); } else print_braced_empty(self, output);
} }
DEFPRINT(AST_BlockStatement, function(self, output) { DEFPRINT(AST_BlockStatement, function(output) {
print_braced(self, output); print_braced(this, output);
}); });
DEFPRINT(AST_EmptyStatement, function(self, output) { DEFPRINT(AST_EmptyStatement, function(output) {
output.semicolon(); output.semicolon();
}); });
DEFPRINT(AST_Do, function(self, output) { DEFPRINT(AST_Do, function(output) {
var self = this;
output.print("do"); output.print("do");
output.space(); output.space();
make_block(self.body, output); make_block(self.body, output);
@@ -913,7 +889,8 @@ function OutputStream(options) {
}); });
output.semicolon(); output.semicolon();
}); });
DEFPRINT(AST_While, function(self, output) { DEFPRINT(AST_While, function(output) {
var self = this;
output.print("while"); output.print("while");
output.space(); output.space();
output.with_parens(function() { output.with_parens(function() {
@@ -922,7 +899,8 @@ function OutputStream(options) {
output.space(); output.space();
self._do_print_body(output); self._do_print_body(output);
}); });
DEFPRINT(AST_For, function(self, output) { DEFPRINT(AST_For, function(output) {
var self = this;
output.print("for"); output.print("for");
output.space(); output.space();
output.with_parens(function() { output.with_parens(function() {
@@ -951,7 +929,8 @@ function OutputStream(options) {
output.space(); output.space();
self._do_print_body(output); self._do_print_body(output);
}); });
DEFPRINT(AST_ForIn, function(self, output) { DEFPRINT(AST_ForIn, function(output) {
var self = this;
output.print("for"); output.print("for");
output.space(); output.space();
output.with_parens(function() { output.with_parens(function() {
@@ -964,7 +943,8 @@ function OutputStream(options) {
output.space(); output.space();
self._do_print_body(output); self._do_print_body(output);
}); });
DEFPRINT(AST_With, function(self, output) { DEFPRINT(AST_With, function(output) {
var self = this;
output.print("with"); output.print("with");
output.space(); output.space();
output.with_parens(function() { output.with_parens(function() {
@@ -975,7 +955,7 @@ function OutputStream(options) {
}); });
/* -----[ functions ]----- */ /* -----[ functions ]----- */
AST_Lambda.DEFMETHOD("_do_print", function(output, nokeyword) { DEFPRINT(AST_Lambda, function(output, nokeyword) {
var self = this; var self = this;
if (!nokeyword) { if (!nokeyword) {
output.print("function"); output.print("function");
@@ -993,32 +973,23 @@ function OutputStream(options) {
output.space(); output.space();
print_braced(self, output, true); print_braced(self, output, true);
}); });
DEFPRINT(AST_Lambda, function(self, output) {
self._do_print(output);
});
/* -----[ jumps ]----- */ /* -----[ jumps ]----- */
function print_jump(output, kind, target) { function print_jump(kind, prop) {
return function(output) {
output.print(kind); output.print(kind);
var target = this[prop];
if (target) { if (target) {
output.space(); output.space();
target.print(output); target.print(output);
} }
output.semicolon(); output.semicolon();
};
} }
DEFPRINT(AST_Return, print_jump("return", "value"));
DEFPRINT(AST_Return, function(self, output) { DEFPRINT(AST_Throw, print_jump("throw", "value"));
print_jump(output, "return", self.value); DEFPRINT(AST_Break, print_jump("break", "label"));
}); DEFPRINT(AST_Continue, print_jump("continue", "label"));
DEFPRINT(AST_Throw, function(self, output) {
print_jump(output, "throw", self.value);
});
DEFPRINT(AST_Break, function(self, output) {
print_jump(output, "break", self.label);
});
DEFPRINT(AST_Continue, function(self, output) {
print_jump(output, "continue", self.label);
});
/* -----[ if ]----- */ /* -----[ if ]----- */
function make_then(self, output) { function make_then(self, output) {
@@ -1047,7 +1018,8 @@ function OutputStream(options) {
} }
force_statement(self.body, output); force_statement(self.body, output);
} }
DEFPRINT(AST_If, function(self, output) { DEFPRINT(AST_If, function(output) {
var self = this;
output.print("if"); output.print("if");
output.space(); output.space();
output.with_parens(function() { output.with_parens(function() {
@@ -1069,7 +1041,8 @@ function OutputStream(options) {
}); });
/* -----[ switch ]----- */ /* -----[ switch ]----- */
DEFPRINT(AST_Switch, function(self, output) { DEFPRINT(AST_Switch, function(output) {
var self = this;
output.print("switch"); output.print("switch");
output.space(); output.space();
output.with_parens(function() { output.with_parens(function() {
@@ -1087,28 +1060,30 @@ function OutputStream(options) {
}); });
}); });
}); });
AST_SwitchBranch.DEFMETHOD("_do_print_body", function(output) { function print_branch_body(self, output) {
output.newline(); output.newline();
this.body.forEach(function(stmt) { self.body.forEach(function(stmt) {
output.indent(); output.indent();
stmt.print(output); stmt.print(output);
output.newline(); output.newline();
}); });
}); }
DEFPRINT(AST_Default, function(self, output) { DEFPRINT(AST_Default, function(output) {
output.print("default:"); output.print("default:");
self._do_print_body(output); print_branch_body(this, output);
}); });
DEFPRINT(AST_Case, function(self, output) { DEFPRINT(AST_Case, function(output) {
var self = this;
output.print("case"); output.print("case");
output.space(); output.space();
self.expression.print(output); self.expression.print(output);
output.print(":"); output.print(":");
self._do_print_body(output); print_branch_body(self, output);
}); });
/* -----[ exceptions ]----- */ /* -----[ exceptions ]----- */
DEFPRINT(AST_Try, function(self, output) { DEFPRINT(AST_Try, function(output) {
var self = this;
output.print("try"); output.print("try");
output.space(); output.space();
print_braced(self, output); print_braced(self, output);
@@ -1121,7 +1096,8 @@ function OutputStream(options) {
self.bfinally.print(output); self.bfinally.print(output);
} }
}); });
DEFPRINT(AST_Catch, function(self, output) { DEFPRINT(AST_Catch, function(output) {
var self = this;
output.print("catch"); output.print("catch");
output.space(); output.space();
output.with_parens(function() { output.with_parens(function() {
@@ -1130,13 +1106,14 @@ function OutputStream(options) {
output.space(); output.space();
print_braced(self, output); print_braced(self, output);
}); });
DEFPRINT(AST_Finally, function(self, output) { DEFPRINT(AST_Finally, function(output) {
output.print("finally"); output.print("finally");
output.space(); output.space();
print_braced(self, output); print_braced(this, output);
}); });
DEFPRINT(AST_Var, function(self, output) { DEFPRINT(AST_Var, function(output) {
var self = this;
output.print("var"); output.print("var");
output.space(); output.space();
self.definitions.forEach(function(def, i) { self.definitions.forEach(function(def, i) {
@@ -1161,7 +1138,8 @@ function OutputStream(options) {
node.print(output, parens); node.print(output, parens);
} }
DEFPRINT(AST_VarDef, function(self, output) { DEFPRINT(AST_VarDef, function(output) {
var self = this;
self.name.print(output); self.name.print(output);
if (self.value) { if (self.value) {
output.space(); output.space();
@@ -1174,10 +1152,7 @@ function OutputStream(options) {
}); });
/* -----[ other expressions ]----- */ /* -----[ other expressions ]----- */
DEFPRINT(AST_Call, function(self, output) { function print_call_args(self, output) {
self.expression.print(output);
if (self instanceof AST_New && !need_constructor_parens(self, output))
return;
if (self.expression instanceof AST_Call || self.expression instanceof AST_Lambda) { if (self.expression instanceof AST_Call || self.expression instanceof AST_Lambda) {
output.add_mapping(self.start); output.add_mapping(self.start);
} }
@@ -1187,14 +1162,20 @@ function OutputStream(options) {
expr.print(output); expr.print(output);
}); });
}); });
}
DEFPRINT(AST_Call, function(output) {
this.expression.print(output);
print_call_args(this, output);
}); });
DEFPRINT(AST_New, function(self, output) { DEFPRINT(AST_New, function(output) {
var self = this;
output.print("new"); output.print("new");
output.space(); output.space();
AST_Call.prototype._codegen(self, output); self.expression.print(output);
if (need_constructor_parens(self, output)) print_call_args(self, output);
}); });
DEFPRINT(AST_Sequence, function(self, output) { DEFPRINT(AST_Sequence, function(output) {
self.expressions.forEach(function(node, index) { this.expressions.forEach(function(node, index) {
if (index > 0) { if (index > 0) {
output.comma(); output.comma();
if (output.should_break()) { if (output.should_break()) {
@@ -1205,7 +1186,8 @@ function OutputStream(options) {
node.print(output); node.print(output);
}); });
}); });
DEFPRINT(AST_Dot, function(self, output) { DEFPRINT(AST_Dot, function(output) {
var self = this;
var expr = self.expression; var expr = self.expression;
expr.print(output); expr.print(output);
var prop = self.property; var prop = self.property;
@@ -1226,35 +1208,38 @@ function OutputStream(options) {
output.print_name(prop); output.print_name(prop);
} }
}); });
DEFPRINT(AST_Sub, function(self, output) { DEFPRINT(AST_Sub, function(output) {
self.expression.print(output); this.expression.print(output);
output.print("["); output.print("[");
self.property.print(output); this.property.print(output);
output.print("]"); output.print("]");
}); });
DEFPRINT(AST_UnaryPrefix, function(self, output) { DEFPRINT(AST_UnaryPrefix, function(output) {
var op = self.operator; var op = this.operator;
var exp = this.expression;
output.print(op); output.print(op);
if (/^[a-z]/i.test(op) if (/^[a-z]/i.test(op)
|| (/[+-]$/.test(op) || (/[+-]$/.test(op)
&& self.expression instanceof AST_UnaryPrefix && exp instanceof AST_UnaryPrefix
&& /^[+-]/.test(self.expression.operator))) { && /^[+-]/.test(exp.operator))) {
output.space(); output.space();
} }
self.expression.print(output); exp.print(output);
}); });
DEFPRINT(AST_UnaryPostfix, function(self, output) { DEFPRINT(AST_UnaryPostfix, function(output) {
self.expression.print(output); this.expression.print(output);
output.print(self.operator); output.print(this.operator);
}); });
DEFPRINT(AST_Binary, function(self, output) { DEFPRINT(AST_Binary, function(output) {
var self = this;
self.left.print(output); self.left.print(output);
output.space(); output.space();
output.print(self.operator); output.print(self.operator);
output.space(); output.space();
self.right.print(output); self.right.print(output);
}); });
DEFPRINT(AST_Conditional, function(self, output) { DEFPRINT(AST_Conditional, function(output) {
var self = this;
self.condition.print(output); self.condition.print(output);
output.space(); output.space();
output.print("?"); output.print("?");
@@ -1266,10 +1251,10 @@ function OutputStream(options) {
}); });
/* -----[ literals ]----- */ /* -----[ literals ]----- */
DEFPRINT(AST_Array, function(self, output) { DEFPRINT(AST_Array, function(output) {
output.with_square(function() { var a = this.elements, len = a.length;
var a = self.elements, len = a.length; output.with_square(len > 0 ? function() {
if (len > 0) output.space(); output.space();
a.forEach(function(exp, i) { a.forEach(function(exp, i) {
if (i) output.comma(); if (i) output.comma();
exp.print(output); exp.print(output);
@@ -1279,12 +1264,13 @@ function OutputStream(options) {
if (i === len - 1 && exp instanceof AST_Hole) if (i === len - 1 && exp instanceof AST_Hole)
output.comma(); output.comma();
}); });
if (len > 0) output.space(); output.space();
} : noop);
}); });
}); DEFPRINT(AST_Object, function(output) {
DEFPRINT(AST_Object, function(self, output) { var props = this.properties;
if (self.properties.length > 0) output.with_block(function() { if (props.length > 0) output.with_block(function() {
self.properties.forEach(function(prop, i) { props.forEach(function(prop, i) {
if (i) { if (i) {
output.print(","); output.print(",");
output.newline(); output.newline();
@@ -1294,7 +1280,7 @@ function OutputStream(options) {
}); });
output.newline(); output.newline();
}); });
else print_braced_empty(self, output); else print_braced_empty(this, output);
}); });
function print_property_name(key, quote, output) { function print_property_name(key, quote, output) {
@@ -1313,47 +1299,48 @@ function OutputStream(options) {
} }
} }
DEFPRINT(AST_ObjectKeyVal, function(self, output) { DEFPRINT(AST_ObjectKeyVal, function(output) {
var self = this;
print_property_name(self.key, self.quote, output); print_property_name(self.key, self.quote, output);
output.colon(); output.colon();
self.value.print(output); self.value.print(output);
}); });
AST_ObjectProperty.DEFMETHOD("_print_getter_setter", function(type, output) { function print_accessor(type) {
return function(output) {
var self = this;
output.print(type); output.print(type);
output.space(); output.space();
print_property_name(this.key.name, this.quote, output); print_property_name(self.key.name, self.quote, output);
this.value._do_print(output, true); self.value._codegen(output, true);
}); };
DEFPRINT(AST_ObjectSetter, function(self, output) { }
self._print_getter_setter("set", output); DEFPRINT(AST_ObjectGetter, print_accessor("get"));
}); DEFPRINT(AST_ObjectSetter, print_accessor("set"));
DEFPRINT(AST_ObjectGetter, function(self, output) { DEFPRINT(AST_Symbol, function(output) {
self._print_getter_setter("get", output); var def = this.definition();
}); output.print_name(def && def.mangled_name || this.name);
DEFPRINT(AST_Symbol, function(self, output) {
var def = self.definition();
output.print_name(def && def.mangled_name || self.name);
}); });
DEFPRINT(AST_Hole, noop); DEFPRINT(AST_Hole, noop);
DEFPRINT(AST_This, function(self, output) { DEFPRINT(AST_This, function(output) {
output.print("this"); output.print("this");
}); });
DEFPRINT(AST_Constant, function(self, output) { DEFPRINT(AST_Constant, function(output) {
output.print(self.value); output.print(this.value);
}); });
DEFPRINT(AST_String, function(self, output) { DEFPRINT(AST_String, function(output) {
output.print_string(self.value, self.quote); output.print_string(this.value, this.quote);
}); });
DEFPRINT(AST_Number, function(self, output) { DEFPRINT(AST_Number, function(output) {
if (use_asm && self.start && self.start.raw != null) { var start = this.start;
output.print(self.start.raw); if (use_asm && start && start.raw != null) {
output.print(start.raw);
} else { } else {
output.print(make_num(self.value)); output.print(make_num(this.value));
} }
}); });
DEFPRINT(AST_RegExp, function(self, output) { DEFPRINT(AST_RegExp, function(output) {
var regexp = self.value; var regexp = this.value;
var str = regexp.toString(); var str = regexp.toString();
var end = str.lastIndexOf("/"); var end = str.lastIndexOf("/");
if (regexp.raw_source) { if (regexp.raw_source) {
@@ -1387,7 +1374,7 @@ function OutputStream(options) {
} }
})); }));
var p = output.parent(); var p = output.parent();
if (p instanceof AST_Binary && /^in/.test(p.operator) && p.left === self) if (p instanceof AST_Binary && /^in/.test(p.operator) && p.left === this)
output.print(" "); output.print(" ");
}); });

View File

@@ -1092,7 +1092,7 @@ function parse($TEXT, options) {
function switch_body_() { function switch_body_() {
expect("{"); expect("{");
var a = [], cur = null, branch = null, tmp; var a = [], branch, cur, default_branch, tmp;
while (!is("punc", "}")) { while (!is("punc", "}")) {
if (is("eof")) expect_token("punc", "}"); if (is("eof")) expect_token("punc", "}");
if (is("keyword", "case")) { if (is("keyword", "case")) {
@@ -1107,12 +1107,14 @@ function parse($TEXT, options) {
expect(":"); expect(":");
} else if (is("keyword", "default")) { } else if (is("keyword", "default")) {
if (branch) branch.end = prev(); if (branch) branch.end = prev();
if (default_branch) croak("More than one default clause in switch statement");
cur = []; cur = [];
branch = new AST_Default({ branch = new AST_Default({
start : (tmp = S.token, next(), expect(":"), tmp), start : (tmp = S.token, next(), expect(":"), tmp),
body : cur body : cur
}); });
a.push(branch); a.push(branch);
default_branch = branch;
} else { } else {
if (!cur) unexpected(); if (!cur) unexpected();
cur.push(statement()); cur.push(statement());

View File

@@ -43,7 +43,8 @@
"use strict"; "use strict";
function find_builtins(reserved) { var builtins = function() {
var names = [];
// NaN will be included due to Number.NaN // NaN will be included due to Number.NaN
[ [
"null", "null",
@@ -67,19 +68,21 @@ function find_builtins(reserved) {
].forEach(function(ctor) { ].forEach(function(ctor) {
Object.getOwnPropertyNames(ctor).map(add); Object.getOwnPropertyNames(ctor).map(add);
if (ctor.prototype) { if (ctor.prototype) {
Object.getOwnPropertyNames(new ctor()).map(add);
Object.getOwnPropertyNames(ctor.prototype).map(add); Object.getOwnPropertyNames(ctor.prototype).map(add);
} }
}); });
return makePredicate(names);
function add(name) { function add(name) {
push_uniq(reserved, name); names.push(name);
}
} }
}();
function reserve_quoted_keys(ast, reserved) { function reserve_quoted_keys(ast, reserved) {
ast.walk(new TreeWalker(function(node) { ast.walk(new TreeWalker(function(node) {
if (node instanceof AST_ObjectKeyVal && node.quote) { if (node instanceof AST_ObjectKeyVal) {
add(node.key); if (node.quote) add(node.key);
} else if (node instanceof AST_Sub) { } else if (node instanceof AST_Sub) {
addStrings(node.property, add); addStrings(node.property, add);
} }
@@ -91,17 +94,14 @@ function reserve_quoted_keys(ast, reserved) {
} }
function addStrings(node, add) { function addStrings(node, add) {
node.walk(new TreeWalker(function(node) { if (node instanceof AST_Conditional) {
if (node instanceof AST_Sequence) { addStrings(node.consequent, add);
addStrings(node.alternative, add);
} else if (node instanceof AST_Sequence) {
addStrings(node.tail_node(), add); addStrings(node.tail_node(), add);
} else if (node instanceof AST_String) { } else if (node instanceof AST_String) {
add(node.value); add(node.value);
} else if (node instanceof AST_Conditional) {
addStrings(node.consequent, add);
addStrings(node.alternative, add);
} }
return true;
}));
} }
function mangle_properties(ast, options) { function mangle_properties(ast, options) {
@@ -110,21 +110,21 @@ function mangle_properties(ast, options) {
cache: null, cache: null,
debug: false, debug: false,
keep_quoted: false, keep_quoted: false,
only_cache: false,
regex: null, regex: null,
reserved: null, reserved: null,
}, true); }, true);
var reserved = options.reserved; var reserved = Object.create(options.builtins ? null : builtins);
if (!Array.isArray(reserved)) reserved = []; if (Array.isArray(options.reserved)) options.reserved.forEach(function(name) {
if (!options.builtins) find_builtins(reserved); reserved[name] = true;
});
var cname = -1; var cname = -1;
var cache; var cache;
if (options.cache) { if (options.cache) {
cache = options.cache.props; cache = options.cache.props;
cache.each(function(mangled_name) { cache.each(function(name) {
push_uniq(reserved, mangled_name); reserved[name] = true;
}); });
} else { } else {
cache = new Dictionary(); cache = new Dictionary();
@@ -139,62 +139,92 @@ function mangle_properties(ast, options) {
var debug_suffix; var debug_suffix;
if (debug) debug_suffix = options.debug === true ? "" : options.debug; if (debug) debug_suffix = options.debug === true ? "" : options.debug;
var names_to_mangle = []; var names_to_mangle = Object.create(null);
var unmangleable = []; var unmangleable = Object.create(reserved);
// step 1: find candidates to mangle // step 1: find candidates to mangle
ast.walk(new TreeWalker(function(node) { ast.walk(new TreeWalker(function(node) {
if (node instanceof AST_ObjectKeyVal) { if (node instanceof AST_Binary) {
if (node.operator == "in") addStrings(node.left, add);
} else if (node.TYPE == "Call") {
var exp = node.expression;
if (exp instanceof AST_Dot) switch (exp.property) {
case "defineProperty":
case "getOwnPropertyDescriptor":
if (node.args.length < 2) break;
exp = exp.expression;
if (!(exp instanceof AST_SymbolRef)) break;
if (exp.name != "Object") break;
if (!exp.definition().undeclared) break;
addStrings(node.args[1], add);
break;
case "hasOwnProperty":
if (node.args.length < 1) break;
addStrings(node.args[0], add);
break;
}
} else if (node instanceof AST_Dot) {
add(node.property);
} else if (node instanceof AST_ObjectKeyVal) {
add(node.key); add(node.key);
} else if (node instanceof AST_ObjectProperty) { } else if (node instanceof AST_ObjectProperty) {
// setter or getter, since KeyVal is handled above // setter or getter, since KeyVal is handled above
add(node.key.name); add(node.key.name);
} else if (node instanceof AST_Dot) {
add(node.property);
} else if (node instanceof AST_Sub) { } else if (node instanceof AST_Sub) {
addStrings(node.property, add); addStrings(node.property, add);
} else if (node instanceof AST_Call
&& node.expression.print_to_string() == "Object.defineProperty") {
addStrings(node.args[1], add);
} }
})); }));
// step 2: transform the tree, renaming properties // step 2: renaming properties
return ast.transform(new TreeTransformer(function(node) { ast.walk(new TreeWalker(function(node) {
if (node instanceof AST_ObjectKeyVal) { if (node instanceof AST_Binary) {
if (node.operator == "in") mangleStrings(node.left);
} else if (node.TYPE == "Call") {
var exp = node.expression;
if (exp instanceof AST_Dot) switch (exp.property) {
case "defineProperty":
case "getOwnPropertyDescriptor":
if (node.args.length < 2) break;
exp = exp.expression;
if (!(exp instanceof AST_SymbolRef)) break;
if (exp.name != "Object") break;
if (!exp.definition().undeclared) break;
mangleStrings(node.args[1]);
break;
case "hasOwnProperty":
if (node.args.length < 1) break;
mangleStrings(node.args[0]);
break;
}
} else if (node instanceof AST_Dot) {
node.property = mangle(node.property);
} else if (node instanceof AST_ObjectKeyVal) {
node.key = mangle(node.key); node.key = mangle(node.key);
} else if (node instanceof AST_ObjectProperty) { } else if (node instanceof AST_ObjectProperty) {
// setter or getter // setter or getter
node.key.name = mangle(node.key.name); node.key.name = mangle(node.key.name);
} else if (node instanceof AST_Dot) { } else if (node instanceof AST_Sub) {
node.property = mangle(node.property); if (!options.keep_quoted) mangleStrings(node.property);
} else if (!options.keep_quoted && node instanceof AST_Sub) {
node.property = mangleStrings(node.property);
} else if (node instanceof AST_Call
&& node.expression.print_to_string() == "Object.defineProperty") {
node.args[1] = mangleStrings(node.args[1]);
} }
})); }));
// only function declarations after this line // only function declarations after this line
function can_mangle(name) { function can_mangle(name) {
if (unmangleable.indexOf(name) >= 0) return false; if (unmangleable[name]) return false;
if (reserved.indexOf(name) >= 0) return false;
if (options.only_cache) return cache.has(name);
if (/^-?[0-9]+(\.[0-9]+)?(e[+-][0-9]+)?$/.test(name)) return false; if (/^-?[0-9]+(\.[0-9]+)?(e[+-][0-9]+)?$/.test(name)) return false;
return true; return true;
} }
function should_mangle(name) { function should_mangle(name) {
if (reserved[name]) return false;
if (regex && !regex.test(name)) return false; if (regex && !regex.test(name)) return false;
if (reserved.indexOf(name) >= 0) return false; return cache.has(name) || names_to_mangle[name];
return cache.has(name) || names_to_mangle.indexOf(name) >= 0;
} }
function add(name) { function add(name) {
if (can_mangle(name)) push_uniq(names_to_mangle, name); if (can_mangle(name)) names_to_mangle[name] = true;
if (!should_mangle(name)) push_uniq(unmangleable, name); if (!should_mangle(name)) unmangleable[name] = true;
} }
function mangle(name) { function mangle(name) {
@@ -218,17 +248,13 @@ function mangle_properties(ast, options) {
} }
function mangleStrings(node) { function mangleStrings(node) {
return node.transform(new TreeTransformer(function(node) {
if (node instanceof AST_Sequence) { if (node instanceof AST_Sequence) {
var last = node.expressions.length - 1; mangleStrings(node.expressions.tail_node());
node.expressions[last] = mangleStrings(node.expressions[last]);
} else if (node instanceof AST_String) { } else if (node instanceof AST_String) {
node.value = mangle(node.value); node.value = mangle(node.value);
} else if (node instanceof AST_Conditional) { } else if (node instanceof AST_Conditional) {
node.consequent = mangleStrings(node.consequent); mangleStrings(node.consequent);
node.alternative = mangleStrings(node.alternative); mangleStrings(node.alternative);
} }
return node;
}));
} }
} }

View File

@@ -96,8 +96,9 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options) {
// pass 1: setup scope chaining and handle definitions // pass 1: setup scope chaining and handle definitions
var self = this; var self = this;
var scope = self.parent_scope = null;
var defun = null; var defun = null;
var next_def_id = 0;
var scope = self.parent_scope = null;
var tw = new TreeWalker(function(node, descend) { var tw = new TreeWalker(function(node, descend) {
if (node instanceof AST_Catch) { if (node instanceof AST_Catch) {
var save_scope = scope; var save_scope = scope;
@@ -149,7 +150,9 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options) {
scope.def_variable(node).defun = defun; scope.def_variable(node).defun = defun;
} }
}); });
self.next_def_id = 0; self.make_def = function(orig, init) {
return new SymbolDef(++next_def_id, this, orig, init);
};
self.walk(tw); self.walk(tw);
// pass 2: find back references and eval // pass 2: find back references and eval
@@ -230,6 +233,7 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options) {
} else { } else {
new_def = scope.def_variable(node); new_def = scope.def_variable(node);
} }
old_def.defun = new_def.scope;
old_def.orig.concat(old_def.references).forEach(function(node) { old_def.orig.concat(old_def.references).forEach(function(node) {
node.thedef = new_def; node.thedef = new_def;
node.reference(options); node.reference(options);
@@ -239,12 +243,6 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options) {
} }
}); });
AST_Scope.DEFMETHOD("make_def", function(orig, init) {
var top = this;
while (top.parent_scope) top = top.parent_scope;
return new SymbolDef(++top.next_def_id, this, orig, init);
});
AST_Toplevel.DEFMETHOD("def_global", function(node) { AST_Toplevel.DEFMETHOD("def_global", function(node) {
var globals = this.globals, name = node.name; var globals = this.globals, name = node.name;
if (globals.has(name)) { if (globals.has(name)) {
@@ -258,23 +256,28 @@ AST_Toplevel.DEFMETHOD("def_global", function(node) {
} }
}); });
function init_scope_vars(scope, parent) {
scope.cname = -1; // the current index for mangling functions/variables
scope.enclosed = []; // variables from this or outer scope(s) that are referenced from this or inner scopes
scope.uses_eval = false; // will be set to true if this or nested scope uses the global `eval`
scope.uses_with = false; // will be set to true if this or some nested scope uses the `with` statement
scope.parent_scope = parent; // the parent scope (null if this is the top level)
scope.functions = new Dictionary(); // map name to AST_SymbolDefun (functions defined in this scope)
scope.variables = new Dictionary(); // map name to AST_SymbolVar (variables defined in this scope; includes functions)
if (parent) scope.make_def = parent.make_def; // top-level tracking of SymbolDef instances
}
AST_Scope.DEFMETHOD("init_scope_vars", function(parent_scope) { AST_Scope.DEFMETHOD("init_scope_vars", function(parent_scope) {
this.variables = new Dictionary(); // map name to AST_SymbolVar (variables defined in this scope; includes functions) init_scope_vars(this, parent_scope);
this.functions = new Dictionary(); // map name to AST_SymbolDefun (functions defined in this scope)
this.uses_with = false; // will be set to true if this or some nested scope uses the `with` statement
this.uses_eval = false; // will be set to true if this or nested scope uses the global `eval`
this.parent_scope = parent_scope; // the parent scope
this.enclosed = []; // a list of variables from this or outer scope(s) that are referenced from this or inner scopes
this.cname = -1; // the current index for mangling functions/variables
}); });
AST_Lambda.DEFMETHOD("init_scope_vars", function() { AST_Lambda.DEFMETHOD("init_scope_vars", function(parent_scope) {
AST_Scope.prototype.init_scope_vars.apply(this, arguments); init_scope_vars(this, parent_scope);
this.uses_arguments = false; this.uses_arguments = false;
this.def_variable(new AST_SymbolFunarg({ this.def_variable(new AST_SymbolFunarg({
name: "arguments", name: "arguments",
start: this.start, start: this.start,
end: this.end end: this.end,
})); }));
}); });
@@ -386,18 +389,10 @@ AST_Symbol.DEFMETHOD("unmangleable", function(options) {
// labels are always mangleable // labels are always mangleable
AST_Label.DEFMETHOD("unmangleable", return_false); AST_Label.DEFMETHOD("unmangleable", return_false);
AST_Symbol.DEFMETHOD("unreferenced", function() {
return !this.definition().references.length && !this.scope.pinned();
});
AST_Symbol.DEFMETHOD("definition", function() { AST_Symbol.DEFMETHOD("definition", function() {
return this.thedef; return this.thedef;
}); });
AST_Symbol.DEFMETHOD("global", function() {
return this.definition().global;
});
function _default_mangler_options(options) { function _default_mangler_options(options) {
options = defaults(options, { options = defaults(options, {
eval : false, eval : false,
@@ -557,22 +552,24 @@ AST_Sequence.DEFMETHOD("tail_node", function() {
AST_Toplevel.DEFMETHOD("compute_char_frequency", function(options) { AST_Toplevel.DEFMETHOD("compute_char_frequency", function(options) {
options = _default_mangler_options(options); options = _default_mangler_options(options);
base54.reset(); base54.reset();
var fn = AST_Symbol.prototype.add_source_map;
try { try {
AST_Node.prototype.print = function(stream, force_parens) { AST_Symbol.prototype.add_source_map = function() {
this._print(stream, force_parens); if (!this.unmangleable(options)) base54.consider(this.name, -1);
if (this instanceof AST_Symbol && !this.unmangleable(options)) {
base54.consider(this.name, -1);
} else if (options.properties) {
if (this instanceof AST_Dot) {
base54.consider(this.property, -1);
} else if (this instanceof AST_Sub) {
skip_string(this.property);
}
}
}; };
if (options.properties) {
AST_Dot.prototype.add_source_map = function() {
base54.consider(this.property, -1);
};
AST_Sub.prototype.add_source_map = function() {
skip_string(this.property);
};
}
base54.consider(this.print_to_string(), 1); base54.consider(this.print_to_string(), 1);
} finally { } finally {
AST_Node.prototype.print = AST_Node.prototype._print; AST_Symbol.prototype.add_source_map = fn;
delete AST_Dot.prototype.add_source_map;
delete AST_Sub.prototype.add_source_map;
} }
base54.sort(); base54.sort();

View File

@@ -112,51 +112,29 @@ function return_this() { return this; }
function return_null() { return null; } function return_null() { return null; }
var List = (function() { var List = (function() {
function List(a, f, backwards) { function List(a, f) {
var ret = [], top = [], i; var ret = [];
function doit() { for (var i = 0; i < a.length; i++) {
var val = f(a[i], i); var val = f(a[i], i);
var is_last = val instanceof Last; if (val === skip) continue;
if (is_last) val = val.v;
if (val instanceof AtTop) {
val = val.v;
if (val instanceof Splice) { if (val instanceof Splice) {
top.push.apply(top, backwards ? val.v.slice().reverse() : val.v); ret.push.apply(ret, val.v);
} else {
top.push(val);
}
} else if (val !== skip) {
if (val instanceof Splice) {
ret.push.apply(ret, backwards ? val.v.slice().reverse() : val.v);
} else { } else {
ret.push(val); ret.push(val);
} }
} }
return is_last; return ret;
}
if (Array.isArray(a)) {
if (backwards) {
for (i = a.length; --i >= 0;) if (doit()) break;
ret.reverse();
top.reverse();
} else {
for (i = 0; i < a.length; ++i) if (doit()) break;
}
} else {
for (i in a) if (HOP(a, i)) if (doit()) break;
}
return top.concat(ret);
} }
List.is_op = function(val) { List.is_op = function(val) {
return val === skip || val instanceof AtTop || val instanceof Last || val instanceof Splice; return val === skip || val instanceof Splice;
};
List.splice = function(val) {
return new Splice(val);
}; };
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 = {}; var skip = List.skip = {};
function AtTop(val) { this.v = val; } function Splice(val) {
function Splice(val) { this.v = val; } this.v = val;
function Last(val) { this.v = val; } }
return List; return List;
})(); })();

View File

@@ -3,7 +3,7 @@
"description": "JavaScript parser, mangler/compressor and beautifier toolkit", "description": "JavaScript parser, mangler/compressor and beautifier toolkit",
"author": "Mihai Bazon <mihai.bazon@gmail.com> (http://lisperator.net/)", "author": "Mihai Bazon <mihai.bazon@gmail.com> (http://lisperator.net/)",
"license": "BSD-2-Clause", "license": "BSD-2-Clause",
"version": "3.10.0", "version": "3.10.3",
"engines": { "engines": {
"node": ">=0.8.0" "node": ">=0.8.0"
}, },

View File

@@ -269,6 +269,7 @@ function test_case(test) {
quote_style: 3, quote_style: 3,
}); });
try { try {
input.validate_ast();
U.parse(input_code); U.parse(input_code);
} catch (ex) { } catch (ex) {
log([ log([
@@ -311,12 +312,10 @@ function test_case(test) {
if (test.mangle) { if (test.mangle) {
output.compute_char_frequency(test.mangle); output.compute_char_frequency(test.mangle);
output.mangle_names(test.mangle); output.mangle_names(test.mangle);
if (test.mangle.properties) { if (test.mangle.properties) U.mangle_properties(output, test.mangle.properties);
output = U.mangle_properties(output, test.mangle.properties);
} }
} var output_code = make_code(output, output_options);
output = make_code(output, output_options); if (expect != output_code) {
if (expect != output) {
log([ log([
"!!! failed", "!!! failed",
"---INPUT---", "---INPUT---",
@@ -329,14 +328,15 @@ function test_case(test) {
"", "",
].join("\n"), { ].join("\n"), {
input: input_formatted, input: input_formatted,
output: output, output: output_code,
expected: expect expected: expect
}); });
return false; return false;
} }
// expect == output // expect == output
try { try {
U.parse(output); output.validate_ast();
U.parse(output_code);
} catch (ex) { } catch (ex) {
log([ log([
"!!! Test matched expected result but cannot parse output", "!!! Test matched expected result but cannot parse output",
@@ -350,7 +350,7 @@ function test_case(test) {
"", "",
].join("\n"), { ].join("\n"), {
input: input_formatted, input: input_formatted,
output: output, output: output_code,
error: ex, error: ex,
}); });
return false; return false;
@@ -409,7 +409,7 @@ function test_case(test) {
}); });
return false; return false;
} }
actual = run_code(output, toplevel); actual = run_code(output_code, toplevel);
if (!sandbox.same_stdout(test.expect_stdout, actual)) { if (!sandbox.same_stdout(test.expect_stdout, actual)) {
log([ log([
"!!! failed", "!!! failed",

View File

@@ -1599,7 +1599,7 @@ collapse_vars_constants: {
} }
} }
collapse_vars_arguments: { collapse_vars_arguments_1: {
options = { options = {
booleans: true, booleans: true,
collapse_vars: true, collapse_vars: true,
@@ -1636,6 +1636,78 @@ collapse_vars_arguments: {
expect_stdout: true expect_stdout: true
} }
collapse_vars_arguments_2: {
options = {
collapse_vars: true,
}
input: {
function log(a, b) {
console.log(b);
}
function f(c) {
var d = arguments[0];
c = "FAIL";
log(c, d);
}
f();
f("PASS");
}
expect: {
function log(a, b) {
console.log(b);
}
function f(c) {
var d = arguments[0];
log(c = "FAIL", d);
}
f();
f("PASS");
}
expect_stdout: [
"undefined",
"PASS",
]
}
collapse_vars_arguments_3: {
options = {
collapse_vars: true,
}
input: {
function log(a, b) {
console.log(b);
}
function f(c) {
var args = arguments;
console.log(c);
var d = args[0];
c = "FAIL";
log(c, d);
}
f();
f("PASS");
}
expect: {
function log(a, b) {
console.log(b);
}
function f(c) {
var args = arguments;
console.log(c);
var d = args[0];
log(c = "FAIL", d);
}
f();
f("PASS");
}
expect_stdout: [
"undefined",
"undefined",
"PASS",
"PASS",
]
}
collapse_vars_short_circuit: { collapse_vars_short_circuit: {
options = { options = {
booleans: true, booleans: true,
@@ -8280,3 +8352,211 @@ issue_3976: {
} }
expect_stdout: "PASS" expect_stdout: "PASS"
} }
issue_4012: {
options = {
collapse_vars: true,
dead_code: true,
evaluate: true,
}
input: {
(function(a) {
try {
throw 2;
} catch (b) {
a = "PASS";
if (--b)
return;
if (3);
} finally {
console.log(a);
}
})();
}
expect: {
(function(a) {
try {
throw 2;
} catch (b) {
a = "PASS";
if (--b)
return;
if (3);
} finally {
console.log(a);
}
})();
}
expect_stdout: "PASS"
}
global_assign: {
options = {
collapse_vars: true,
}
input: {
this.A = "FAIL";
A = "PASS";
B = "FAIL";
console.log(A);
}
expect: {
this.A = "FAIL";
A = "PASS";
B = "FAIL";
console.log(A);
}
expect_stdout: "PASS"
}
global_read: {
options = {
collapse_vars: true,
}
input: {
var a = 0;
a = this.A;
A = 1;
a ? console.log("FAIL") : console.log("PASS");
}
expect: {
var a = 0;
a = this.A;
A = 1;
a ? console.log("FAIL") : console.log("PASS");
}
expect_stdout: "PASS"
}
issue_4038: {
options = {
collapse_vars: true,
}
input: {
var a = 0;
a = this;
a = a.A;
A = 1;
a ? console.log("FAIL") : console.log("PASS");
}
expect: {
var a = 0;
a = (a = this).A;
A = 1;
a ? console.log("FAIL") : console.log("PASS");
}
expect_stdout: "PASS"
}
issue_4040: {
options = {
collapse_vars: true,
toplevel: true,
}
input: {
var a = console.log("PASS") && a.p;
delete NaN;
}
expect: {
var a = console.log("PASS") && a.p;
delete NaN;
}
expect_stdout: "PASS"
}
issue_4047_1: {
options = {
collapse_vars: true,
evaluate: true,
inline: true,
reduce_vars: true,
sequences: true,
toplevel: true,
unused: true,
}
input: {
var b = 1;
console.log(+function(a) {
b = a;
(a >>= 0) && console.log("PASS");
}(--b + (0 !== typeof A)));
}
expect: {
var b = 1;
var a;
console.log((a = --b + ((a = 0) !== typeof A), +void ((a >>= 0) && console.log("PASS"))));
}
expect_stdout: [
"PASS",
"NaN",
]
}
issue_4047_2: {
options = {
collapse_vars: true,
evaluate: true,
inline: true,
passes: 2,
reduce_vars: true,
sequences: true,
toplevel: true,
unused: true,
}
input: {
var b = 1;
console.log(+function(a) {
b = a;
(a >>= 0) && console.log("PASS");
}(--b + (0 !== typeof A)));
}
expect: {
var a;
console.log((a = +(0 !== typeof A), +void ((a >>= 0) && console.log("PASS"))));
}
expect_stdout: [
"PASS",
"NaN",
]
}
issue_4051: {
options = {
collapse_vars: true,
}
input: {
try {
var a = (b = b.p, "FAIL"), b = b;
} catch (e) {}
console.log(a);
}
expect: {
try {
var a = (b = b.p, "FAIL"), b = b;
} catch (e) {}
console.log(a);
}
expect_stdout: "undefined"
}
issue_4070: {
options = {
collapse_vars: true,
pure_getters: "strict",
reduce_vars: true,
}
input: {
console.log(function f() {
function g() {}
g.p++;
return f.p = g.p;
}());
}
expect: {
console.log(function f() {
function g() {}
return f.p = ++g.p;
}());
}
expect_stdout: "NaN"
}

View File

@@ -123,6 +123,29 @@ self_comparison_3: {
] ]
} }
self_comparison_4: {
options = {
booleans: true,
comparisons: true,
reduce_vars: true,
toplevel: true,
unused: true,
}
input: {
var o = {};
console.log(o == o, o != o);
console.log(o === o, o !== o);
}
expect: {
console.log(!0, !1);
console.log(!0, !1);
}
expect_stdout: [
"true false",
"true false",
]
}
issue_2857_1: { issue_2857_1: {
options = { options = {
comparisons: true, comparisons: true,

View File

@@ -1341,3 +1341,24 @@ issue_3967: {
} }
expect_stdout: "PASS" expect_stdout: "PASS"
} }
issue_4051: {
options = {
dead_code: true,
}
input: {
try {
delete (A = A);
} catch (e) {
console.log("PASS");
}
}
expect: {
try {
delete (A = A);
} catch (e) {
console.log("PASS");
}
}
expect_stdout: "PASS"
}

View File

@@ -1729,7 +1729,7 @@ chained_3: {
} }
expect: { expect: {
console.log(function(a, b) { console.log(function(a, b) {
var c = 2; var c = b;
b++; b++;
return c; return c;
}(0, 2)); }(0, 2));
@@ -2718,7 +2718,7 @@ issue_3962_1: {
0..toString(); 0..toString();
} while (0); } while (0);
if (c) console.log("PASS"); if (c) console.log("PASS");
})((a--, 1)); }((a--, 1)), 0);
void 0; void 0;
} }
expect_stdout: "PASS" expect_stdout: "PASS"
@@ -2751,7 +2751,7 @@ issue_3962_2: {
0..toString(); 0..toString();
} while (0); } while (0);
if (c) console.log("PASS"); if (c) console.log("PASS");
})((a--, 1)); }((a--, 1)), 0);
} }
expect_stdout: "PASS" expect_stdout: "PASS"
} }
@@ -2789,3 +2789,62 @@ issue_3986: {
} }
expect_stdout: "0" expect_stdout: "0"
} }
issue_4017: {
options = {
pure_getters: "strict",
reduce_vars: true,
unused: true,
}
input: {
var a = 0;
console.log(function f() {
var b = c &= 0;
var c = a++ + (A = a);
var d = c && c[f];
}());
}
expect: {
var a = 0;
console.log(function() {
c &= 0;
var c = (a++, A = a, 0);
}());
}
expect_stdout: "undefined"
}
issue_4025: {
options = {
collapse_vars: true,
evaluate: true,
passes: 2,
reduce_vars: true,
toplevel: true,
unused: true,
}
input: {
var a = 0, b = 0, c = 0, d = a++;
try {
var e = console.log(c), f = b;
} finally {
var d = b = 1, d = c + 1;
c = 0;
}
console.log(a, b, d);
}
expect: {
var d, c = 0;
try {
console.log(c);
} finally {
d = c + 1;
c = 0;
}
console.log(1, 1, d);
}
expect_stdout: [
"0",
"1 1 1",
]
}

View File

@@ -2833,3 +2833,78 @@ issue_3997: {
} }
expect_stdout: "string" expect_stdout: "string"
} }
issue_4035: {
options = {
evaluate: true,
reduce_vars: true,
}
input: {
var a = 0;
(function() {
var b = --a;
console.log(delete (0 + b));
console.log(delete (1 * b));
console.log(delete (b + 0));
console.log(delete (b - 0));
console.log(delete (b / 1));
})();
}
expect: {
var a = 0;
(function() {
var b = --a;
console.log((0 + b, true));
console.log((1 * b, true));
console.log((0 + b, true));
console.log((b - 0, true));
console.log((b / 1, true));
})();
}
expect_stdout: [
"true",
"true",
"true",
"true",
"true",
]
}
issue_4067: {
options = {
evaluate: true,
reduce_vars: true,
unsafe: true,
}
input: {
(function(a) {
(function(b) {
b[0] += 0;
console.log(+a);
})(a);
})([]);
}
expect: {
(function(a) {
(function(b) {
b[0] += 0;
console.log(+a);
})(a);
})([]);
}
expect_stdout: "NaN"
}
issue_4077: {
options = {
evaluate: true,
unsafe: true,
}
input: {
console.log((a = []) - (a[0]++, 1) || "PASS");
}
expect: {
console.log((a = []) - (a[0]++, 1) || "PASS");
}
expect_stdout: "PASS"
}

View File

@@ -4747,3 +4747,34 @@ issue_3929: {
} }
expect_stdout: "function" expect_stdout: "function"
} }
issue_4006: {
options = {
dead_code: true,
evaluate: true,
inline: true,
keep_fargs: "strict",
reduce_vars: true,
sequences: true,
side_effects: true,
unused: true,
}
input: {
var a = 0;
(function() {
(function(b, c) {
for (var k in console.log(c), 0)
return b += 0;
})(0, --a);
return a ? 0 : --a;
})();
}
expect: {
var a = 0;
(function(c) {
for (var k in console.log(c), 0)
return;
})(--a), a || --a;
}
expect_stdout: "-1"
}

View File

@@ -12,6 +12,20 @@ must_replace: {
} }
} }
repeated_nodes: {
options = {
global_defs: {
"@N": "rand()",
},
}
input: {
console.log(N, N);
}
expect: {
console.log(rand(), rand());
}
}
keyword: { keyword: {
options = { options = {
global_defs: { global_defs: {

View File

@@ -1016,3 +1016,28 @@ issue_3945_2: {
} }
expect_stdout: "undefined" expect_stdout: "undefined"
} }
issue_4023: {
options = {
comparisons: true,
hoist_props: true,
inline: true,
reduce_vars: true,
toplevel: true,
typeofs: true,
unused: true,
}
input: {
function f() {
var a = function() {
return { p: 0 };
}();
return console.log("undefined" != typeof a);
}
f();
}
expect: {
console.log(void 0 !== {});
}
expect_stdout: "true"
}

View File

@@ -2544,12 +2544,12 @@ issue_3999: {
expect: { expect: {
(function() { (function() {
(function f() { (function f() {
for (var c = 0; c < 2; c++) for (var o = 0; o < 2; o++)
try { try {
f[0]; f[0];
} catch (f) { } catch (f) {
var f = 0; var f = 0;
console.log(c); console.log(o);
} }
})(); })();
})(typeof f); })(typeof f);
@@ -2593,3 +2593,124 @@ issue_4001: {
} }
expect_stdout: "undefined" expect_stdout: "undefined"
} }
issue_4015: {
rename = true
mangle = {
ie8: true,
toplevel: true,
}
input: {
var n, a = 0, b;
function f() {
try {
throw 0;
} catch (b) {
(function g() {
(function b() {
a++;
})();
})();
}
}
f();
console.log(a);
}
expect: {
var n, o = 0, c;
function t() {
try {
throw 0;
} catch (c) {
(function n() {
(function c() {
o++;
})();
})();
}
}
t();
console.log(o);
}
expect_stdout: "1"
}
issue_4019: {
options = {
reduce_vars: true,
toplevel: true,
unused: true,
}
mangle = {
ie8: true,
toplevel: true,
}
input: {
var a = function() {
try {
console.log("FAIL");
} catch (b) {}
}, a = (console.log(a.length), ++a);
}
expect: {
var o = function() {
try {
console.log("FAIL");
} catch (o) {}
}, o = (console.log(o.length), ++o);
}
expect_stdout: "0"
}
issue_4028: {
options = {
reduce_vars: true,
toplevel: true,
unused: true,
}
mangle = {
ie8: true,
}
input: {
function a() {
try {
A;
} catch (e) {}
}
var b = a += a;
console.log(typeof b);
}
expect: {
function a() {
try {
A;
} catch (a) {}
}
var b = a += a;
console.log(typeof b);
}
expect_stdout: "string"
}
issue_2737: {
options = {
ie8: true,
reduce_vars: true,
unused: true,
}
input: {
(function(a) {
a();
})(function f() {
console.log(typeof f);
});
}
expect: {
(function(a) {
a();
})(function f() {
console.log(typeof f);
});
}
expect_stdout: "function"
}

View File

@@ -756,7 +756,37 @@ empty_for_in_side_effects: {
expect_warnings: [ expect_warnings: [
"WARN: Dropping unused variable b [test/compress/loops.js:4,16]", "WARN: Dropping unused variable b [test/compress/loops.js:4,16]",
"INFO: Dropping unused loop variable a [test/compress/loops.js:1,17]", "INFO: Dropping unused loop variable a [test/compress/loops.js:1,17]",
"WARN: Side effects in object of for-in loop [test/compress/loops.js:1,17]", "WARN: Side effects in object of for-in loop [test/compress/loops.js:2,17]",
]
}
empty_for_in_prop_init: {
options = {
loops: true,
pure_getters: "strict",
unused: true,
}
input: {
console.log(function f() {
var a = "bar";
for ((a, f)[a] in console.log("foo"));
return a;
}());
}
expect: {
console.log(function() {
var a = "bar";
console.log("foo");
return a;
}());
}
expect_stdout: [
"foo",
"bar",
]
expect_warnings: [
"INFO: Dropping unused loop variable f [test/compress/loops.js:3,21]",
"WARN: Side effects in object of for-in loop [test/compress/loops.js:3,30]",
] ]
} }
@@ -933,3 +963,49 @@ issue_3634_2: {
} }
expect_stdout: "1" expect_stdout: "1"
} }
issue_4075: {
options = {
loops: true,
unused: true,
}
input: {
var a = "FAIL";
(function() {
for (a in { PASS: 0 });
})()
console.log(a);
}
expect: {
var a = "FAIL";
(function() {
for (a in { PASS: 0 });
})()
console.log(a);
}
expect_stdout: "PASS"
}
issue_4082: {
options = {
keep_fargs: "strict",
loops: true,
unused: true,
}
input: {
var a = "PASS";
(function(a) {
for (a in "foo")
var b;
})();
console.log(a);
}
expect: {
var a = "PASS";
(function(a) {
for (a in "foo");
})();
console.log(a);
}
expect_stdout: "PASS"
}

View File

@@ -130,7 +130,7 @@ evaluate_string_length: {
} }
} }
mangle_properties: { mangle_properties_1: {
mangle = { mangle = {
properties: { properties: {
keep_quoted: false, keep_quoted: false,
@@ -152,6 +152,53 @@ mangle_properties: {
} }
} }
mangle_properties_2: {
mangle = {
properties: {
reserved: [
"value",
]
},
}
input: {
var o = {
prop1: 1,
};
Object.defineProperty(o, "prop2", {
value: 2,
});
Object.defineProperties(o, {
prop3: {
value: 3,
},
});
console.log("prop1", o.prop1, "prop1" in o);
console.log("prop2", o.prop2, o.hasOwnProperty("prop2"));
console.log("prop3", o.prop3, Object.getOwnPropertyDescriptor(o, "prop3").value);
}
expect: {
var o = {
o: 1,
};
Object.defineProperty(o, "p", {
value: 2,
});
Object.defineProperties(o, {
r: {
value: 3,
},
});
console.log("prop1", o.o, "o" in o);
console.log("prop2", o.p, o.hasOwnProperty("p"));
console.log("prop3", o.r, Object.getOwnPropertyDescriptor(o, "r").value);
}
expect_stdout: [
"prop1 1 true",
"prop2 2 true",
"prop3 3 3",
]
}
mangle_unquoted_properties: { mangle_unquoted_properties: {
options = { options = {
evaluate: true, evaluate: true,

View File

@@ -2031,6 +2031,7 @@ issue_1670_4: {
issue_1670_5: { issue_1670_5: {
options = { options = {
conditionals: true,
dead_code: true, dead_code: true,
evaluate: true, evaluate: true,
keep_fargs: false, keep_fargs: false,
@@ -2062,6 +2063,7 @@ issue_1670_5: {
issue_1670_6: { issue_1670_6: {
options = { options = {
conditionals: true,
dead_code: true, dead_code: true,
evaluate: true, evaluate: true,
keep_fargs: false, keep_fargs: false,
@@ -7383,3 +7385,46 @@ issue_3974: {
} }
expect_stdout: "PASS" expect_stdout: "PASS"
} }
issue_4030: {
options = {
collapse_vars: true,
evaluate: true,
reduce_vars: true,
toplevel: true,
unused: true,
}
input: {
var a;
{
delete (a = "PASS");
A = "PASS";
}
console.log(A);
}
expect: {
A = "PASS";
console.log(A);
}
expect_stdout: "PASS"
}
global_assign: {
options = {
evaluate: true,
reduce_vars: true,
toplevel: true,
unused: true,
}
input: {
A = "FAIL";
this.A = "PASS";
console.log(A);
}
expect: {
A = "FAIL";
this.A = "PASS";
console.log(A);
}
expect_stdout: "PASS"
}

View File

@@ -80,3 +80,21 @@ log_global: {
} }
expect_stdout: "[object global]" expect_stdout: "[object global]"
} }
issue_4054: {
input: {
console.log({
set p(v) {
throw "FAIL";
},
});
}
expect: {
console.log({
set p(v) {
throw "FAIL";
},
});
}
expect_stdout: "{ p: [Setter] }"
}

View File

@@ -877,7 +877,7 @@ for_init_var: {
expect_stdout: "PASS" expect_stdout: "PASS"
} }
forin: { forin_1: {
options = { options = {
sequences: true, sequences: true,
} }
@@ -895,6 +895,49 @@ forin: {
expect_stdout: "PASS" expect_stdout: "PASS"
} }
forin_2: {
options = {
evaluate: true,
inline: true,
reduce_vars: true,
sequences: true,
toplevel: true,
unused: true,
}
input: {
var o = {
p: 1,
q: 2,
};
var k = "k";
for ((console.log("exp"), o)[function() {
console.log("prop");
return k;
}()] in function() {
console.log("obj");
return o;
}())
console.log(o.k, o[o.k]);
}
expect: {
var o = {
p: 1,
q: 2,
};
for ((console.log("exp"), o)[console.log("prop"), "k"] in console.log("obj"), o)
console.log(o.k, o[o.k]);
}
expect_stdout: [
"obj",
"exp",
"prop",
"p 1",
"exp",
"prop",
"q 2",
]
}
call: { call: {
options = { options = {
sequences: true, sequences: true,
@@ -1112,3 +1155,25 @@ issue_3703: {
} }
expect_stdout: "PASS" expect_stdout: "PASS"
} }
issue_4079: {
options = {
sequences: true,
side_effects: true,
}
input: {
try {
typeof (0, A);
} catch (e) {
console.log("PASS");
}
}
expect: {
try {
A;
} catch (e) {
console.log("PASS");
}
}
expect_stdout: "PASS"
}

View File

@@ -298,7 +298,7 @@ operator_in: {
expect_stdout: "PASS" expect_stdout: "PASS"
} }
issue_3983: { issue_3983_1: {
options = { options = {
collapse_vars: true, collapse_vars: true,
conditionals: true, conditionals: true,
@@ -323,7 +323,71 @@ issue_3983: {
} }
expect: { expect: {
var a = "PASS"; var a = "PASS";
g();
function g() {}
console.log(a); console.log(a);
} }
expect_stdout: "PASS" expect_stdout: "PASS"
} }
issue_3983_2: {
options = {
collapse_vars: true,
conditionals: true,
evaluate: true,
inline: true,
passes: 2,
reduce_vars: true,
side_effects: true,
toplevel: true,
unused: true,
}
input: {
var a = "PASS";
function f() {
g && g();
}
f();
function g() {
0 ? a : 0;
}
var b = a;
console.log(a);
}
expect: {
console.log("PASS");
}
expect_stdout: "PASS"
}
issue_4008: {
options = {
collapse_vars: true,
evaluate: true,
inline: true,
pure_getters: "strict",
reduce_vars: true,
side_effects: true,
toplevel: true,
}
input: {
var a = "PASS";
function f(b, b) {
console.log(b);
}
f && f(a && a[a]);
console.log(a);
}
expect: {
var a = "PASS";
function f(b, b) {
console.log(b);
}
f(a[a]);
console.log(a);
}
expect_stdout: [
"undefined",
"PASS",
]
}

View File

@@ -1,5 +1,6 @@
constant_switch_1: { constant_switch_1: {
options = { options = {
conditionals: true,
dead_code: true, dead_code: true,
evaluate: true, evaluate: true,
side_effects: true, side_effects: true,
@@ -19,6 +20,7 @@ constant_switch_1: {
constant_switch_2: { constant_switch_2: {
options = { options = {
conditionals: true,
dead_code: true, dead_code: true,
evaluate: true, evaluate: true,
side_effects: true, side_effects: true,
@@ -39,6 +41,7 @@ constant_switch_2: {
constant_switch_3: { constant_switch_3: {
options = { options = {
conditionals: true,
dead_code: true, dead_code: true,
evaluate: true, evaluate: true,
side_effects: true, side_effects: true,
@@ -60,6 +63,7 @@ constant_switch_3: {
constant_switch_4: { constant_switch_4: {
options = { options = {
conditionals: true,
dead_code: true, dead_code: true,
evaluate: true, evaluate: true,
side_effects: true, side_effects: true,
@@ -86,6 +90,7 @@ constant_switch_4: {
constant_switch_5: { constant_switch_5: {
options = { options = {
conditionals: true,
dead_code: true, dead_code: true,
evaluate: true, evaluate: true,
side_effects: true, side_effects: true,
@@ -120,6 +125,7 @@ constant_switch_5: {
constant_switch_6: { constant_switch_6: {
options = { options = {
conditionals: true,
dead_code: true, dead_code: true,
evaluate: true, evaluate: true,
side_effects: true, side_effects: true,
@@ -154,6 +160,7 @@ constant_switch_6: {
constant_switch_7: { constant_switch_7: {
options = { options = {
conditionals: true,
dead_code: true, dead_code: true,
evaluate: true, evaluate: true,
side_effects: true, side_effects: true,
@@ -197,6 +204,7 @@ constant_switch_7: {
constant_switch_8: { constant_switch_8: {
options = { options = {
conditionals: true,
dead_code: true, dead_code: true,
evaluate: true, evaluate: true,
side_effects: true, side_effects: true,
@@ -226,6 +234,7 @@ constant_switch_8: {
constant_switch_9: { constant_switch_9: {
options = { options = {
conditionals: true,
dead_code: true, dead_code: true,
evaluate: true, evaluate: true,
side_effects: true, side_effects: true,
@@ -315,6 +324,7 @@ keep_default: {
issue_1663: { issue_1663: {
options = { options = {
conditionals: true,
dead_code: true, dead_code: true,
evaluate: true, evaluate: true,
side_effects: true, side_effects: true,
@@ -551,6 +561,7 @@ issue_441_2: {
issue_1674: { issue_1674: {
options = { options = {
conditionals: true,
dead_code: true, dead_code: true,
evaluate: true, evaluate: true,
side_effects: true, side_effects: true,
@@ -876,6 +887,7 @@ beautify: {
issue_1758: { issue_1758: {
options = { options = {
conditionals: true,
dead_code: true, dead_code: true,
switches: true, switches: true,
} }
@@ -898,6 +910,7 @@ issue_1758: {
issue_2535: { issue_2535: {
options = { options = {
conditionals: true,
dead_code: true, dead_code: true,
evaluate: true, evaluate: true,
switches: true, switches: true,
@@ -919,6 +932,7 @@ issue_2535: {
issue_1750: { issue_1750: {
options = { options = {
conditionals: true,
dead_code: true, dead_code: true,
evaluate: true, evaluate: true,
switches: true, switches: true,
@@ -963,6 +977,7 @@ drop_switch_1: {
drop_switch_2: { drop_switch_2: {
options = { options = {
conditionals: true,
dead_code: true, dead_code: true,
switches: true, switches: true,
} }
@@ -1007,6 +1022,7 @@ drop_switch_3: {
drop_switch_4: { drop_switch_4: {
options = { options = {
conditionals: true,
dead_code: true, dead_code: true,
switches: true, switches: true,
} }
@@ -1028,3 +1044,140 @@ drop_switch_4: {
} }
expect_stdout: "PASS" expect_stdout: "PASS"
} }
drop_switch_5: {
options = {
conditionals: true,
dead_code: true,
switches: true,
}
input: {
switch (A) {
case B:
x();
default:
}
switch (C) {
default:
y();
case D:
}
}
expect: {
A === B && x();
C !== D && y();
}
}
drop_switch_6: {
options = {
conditionals: true,
dead_code: true,
switches: true,
}
input: {
switch (A) {
case B:
default:
x();
}
switch (C) {
default:
case D:
y();
}
}
expect: {
A === B;
x();
C !== D;
y();
}
}
drop_switch_7: {
options = {
conditionals: true,
dead_code: true,
switches: true,
}
input: {
switch (A) {
case B:
w();
default:
x();
}
switch (C) {
default:
y();
case D:
z();
}
}
expect: {
A === B && w();
x();
C !== D && y();
z();
}
}
drop_switch_8: {
options = {
conditionals: true,
dead_code: true,
switches: true,
}
input: {
switch (A) {
case B:
w();
break;
default:
x();
}
switch (C) {
default:
y();
break;
case D:
z();
}
}
expect: {
(A === B ? w : x)();
(C !== D ? y : z)();
}
}
issue_4059: {
options = {
conditionals: true,
dead_code: true,
evaluate: true,
switches: true,
}
input: {
switch (0) {
default:
case 1:
break;
case a:
break;
var a;
}
console.log("PASS");
}
expect: {
switch (0) {
default:
break;
case a:
break;
var a;
}
console.log("PASS");
}
expect_stdout: "PASS"
}

View File

@@ -0,0 +1,4 @@
switch (0) {
default:
default:
}

View File

@@ -330,7 +330,7 @@ describe("bin/uglifyjs", function() {
}); });
}); });
it("Should fail with invalid syntax", function(done) { it("Should fail with invalid syntax", function(done) {
var command = uglifyjscmd + ' test/input/invalid/simple.js'; var command = uglifyjscmd + " test/input/invalid/simple.js";
exec(command, function(err, stdout, stderr) { exec(command, function(err, stdout, stderr) {
assert.ok(err); assert.ok(err);
var lines = stderr.split(/\n/); var lines = stderr.split(/\n/);
@@ -342,7 +342,7 @@ describe("bin/uglifyjs", function() {
}); });
}); });
it("Should fail with correct marking of tabs", function(done) { it("Should fail with correct marking of tabs", function(done) {
var command = uglifyjscmd + ' test/input/invalid/tab.js'; var command = uglifyjscmd + " test/input/invalid/tab.js";
exec(command, function(err, stdout, stderr) { exec(command, function(err, stdout, stderr) {
assert.ok(err); assert.ok(err);
var lines = stderr.split(/\n/); var lines = stderr.split(/\n/);
@@ -354,7 +354,7 @@ describe("bin/uglifyjs", function() {
}); });
}); });
it("Should fail with correct marking at start of line", function(done) { it("Should fail with correct marking at start of line", function(done) {
var command = uglifyjscmd + ' test/input/invalid/eof.js'; var command = uglifyjscmd + " test/input/invalid/eof.js";
exec(command, function(err, stdout, stderr) { exec(command, function(err, stdout, stderr) {
assert.ok(err); assert.ok(err);
var lines = stderr.split(/\n/); var lines = stderr.split(/\n/);
@@ -366,7 +366,7 @@ describe("bin/uglifyjs", function() {
}); });
}); });
it("Should fail with a missing loop body", function(done) { it("Should fail with a missing loop body", function(done) {
var command = uglifyjscmd + ' test/input/invalid/loop-no-body.js'; var command = uglifyjscmd + " test/input/invalid/loop-no-body.js";
exec(command, function(err, stdout, stderr) { exec(command, function(err, stdout, stderr) {
assert.ok(err); assert.ok(err);
var lines = stderr.split(/\n/); var lines = stderr.split(/\n/);
@@ -378,7 +378,7 @@ describe("bin/uglifyjs", function() {
}); });
}); });
it("Should throw syntax error (5--)", function(done) { it("Should throw syntax error (5--)", function(done) {
var command = uglifyjscmd + ' test/input/invalid/assign_1.js'; var command = uglifyjscmd + " test/input/invalid/assign_1.js";
exec(command, function(err, stdout, stderr) { exec(command, function(err, stdout, stderr) {
assert.ok(err); assert.ok(err);
assert.strictEqual(stdout, ""); assert.strictEqual(stdout, "");
@@ -392,7 +392,7 @@ describe("bin/uglifyjs", function() {
}); });
}); });
it("Should throw syntax error (Math.random() /= 2)", function(done) { it("Should throw syntax error (Math.random() /= 2)", function(done) {
var command = uglifyjscmd + ' test/input/invalid/assign_2.js'; var command = uglifyjscmd + " test/input/invalid/assign_2.js";
exec(command, function(err, stdout, stderr) { exec(command, function(err, stdout, stderr) {
assert.ok(err); assert.ok(err);
assert.strictEqual(stdout, ""); assert.strictEqual(stdout, "");
@@ -406,7 +406,7 @@ describe("bin/uglifyjs", function() {
}); });
}); });
it("Should throw syntax error (++this)", function(done) { it("Should throw syntax error (++this)", function(done) {
var command = uglifyjscmd + ' test/input/invalid/assign_3.js'; var command = uglifyjscmd + " test/input/invalid/assign_3.js";
exec(command, function(err, stdout, stderr) { exec(command, function(err, stdout, stderr) {
assert.ok(err); assert.ok(err);
assert.strictEqual(stdout, ""); assert.strictEqual(stdout, "");
@@ -420,7 +420,7 @@ describe("bin/uglifyjs", function() {
}); });
}); });
it("Should throw syntax error (++null)", function(done) { it("Should throw syntax error (++null)", function(done) {
var command = uglifyjscmd + ' test/input/invalid/assign_4.js'; var command = uglifyjscmd + " test/input/invalid/assign_4.js";
exec(command, function(err, stdout, stderr) { exec(command, function(err, stdout, stderr) {
assert.ok(err); assert.ok(err);
assert.strictEqual(stdout, ""); assert.strictEqual(stdout, "");
@@ -434,7 +434,7 @@ describe("bin/uglifyjs", function() {
}); });
}); });
it("Should throw syntax error (a.=)", function(done) { it("Should throw syntax error (a.=)", function(done) {
var command = uglifyjscmd + ' test/input/invalid/dot_1.js'; var command = uglifyjscmd + " test/input/invalid/dot_1.js";
exec(command, function(err, stdout, stderr) { exec(command, function(err, stdout, stderr) {
assert.ok(err); assert.ok(err);
assert.strictEqual(stdout, ""); assert.strictEqual(stdout, "");
@@ -448,7 +448,7 @@ describe("bin/uglifyjs", function() {
}); });
}); });
it("Should throw syntax error (%.a)", function(done) { it("Should throw syntax error (%.a)", function(done) {
var command = uglifyjscmd + ' test/input/invalid/dot_2.js'; var command = uglifyjscmd + " test/input/invalid/dot_2.js";
exec(command, function(err, stdout, stderr) { exec(command, function(err, stdout, stderr) {
assert.ok(err); assert.ok(err);
assert.strictEqual(stdout, ""); assert.strictEqual(stdout, "");
@@ -462,7 +462,7 @@ describe("bin/uglifyjs", function() {
}); });
}); });
it("Should throw syntax error (a./();)", function(done) { it("Should throw syntax error (a./();)", function(done) {
var command = uglifyjscmd + ' test/input/invalid/dot_3.js'; var command = uglifyjscmd + " test/input/invalid/dot_3.js";
exec(command, function(err, stdout, stderr) { exec(command, function(err, stdout, stderr) {
assert.ok(err); assert.ok(err);
assert.strictEqual(stdout, ""); assert.strictEqual(stdout, "");
@@ -476,7 +476,7 @@ describe("bin/uglifyjs", function() {
}); });
}); });
it("Should throw syntax error ({%: 1})", function(done) { it("Should throw syntax error ({%: 1})", function(done) {
var command = uglifyjscmd + ' test/input/invalid/object.js'; var command = uglifyjscmd + " test/input/invalid/object.js";
exec(command, function(err, stdout, stderr) { exec(command, function(err, stdout, stderr) {
assert.ok(err); assert.ok(err);
assert.strictEqual(stdout, ""); assert.strictEqual(stdout, "");
@@ -490,7 +490,7 @@ describe("bin/uglifyjs", function() {
}); });
}); });
it("Should throw syntax error (delete x)", function(done) { it("Should throw syntax error (delete x)", function(done) {
var command = uglifyjscmd + ' test/input/invalid/delete.js'; var command = uglifyjscmd + " test/input/invalid/delete.js";
exec(command, function(err, stdout, stderr) { exec(command, function(err, stdout, stderr) {
assert.ok(err); assert.ok(err);
assert.strictEqual(stdout, ""); assert.strictEqual(stdout, "");
@@ -504,7 +504,7 @@ describe("bin/uglifyjs", function() {
}); });
}); });
it("Should throw syntax error (function g(arguments))", function(done) { it("Should throw syntax error (function g(arguments))", function(done) {
var command = uglifyjscmd + ' test/input/invalid/function_1.js'; var command = uglifyjscmd + " test/input/invalid/function_1.js";
exec(command, function(err, stdout, stderr) { exec(command, function(err, stdout, stderr) {
assert.ok(err); assert.ok(err);
assert.strictEqual(stdout, ""); assert.strictEqual(stdout, "");
@@ -518,7 +518,7 @@ describe("bin/uglifyjs", function() {
}); });
}); });
it("Should throw syntax error (function eval())", function(done) { it("Should throw syntax error (function eval())", function(done) {
var command = uglifyjscmd + ' test/input/invalid/function_2.js'; var command = uglifyjscmd + " test/input/invalid/function_2.js";
exec(command, function(err, stdout, stderr) { exec(command, function(err, stdout, stderr) {
assert.ok(err); assert.ok(err);
assert.strictEqual(stdout, ""); assert.strictEqual(stdout, "");
@@ -532,7 +532,7 @@ describe("bin/uglifyjs", function() {
}); });
}); });
it("Should throw syntax error (iife arguments())", function(done) { it("Should throw syntax error (iife arguments())", function(done) {
var command = uglifyjscmd + ' test/input/invalid/function_3.js'; var command = uglifyjscmd + " test/input/invalid/function_3.js";
exec(command, function(err, stdout, stderr) { exec(command, function(err, stdout, stderr) {
assert.ok(err); assert.ok(err);
assert.strictEqual(stdout, ""); assert.strictEqual(stdout, "");
@@ -546,7 +546,7 @@ describe("bin/uglifyjs", function() {
}); });
}); });
it("Should throw syntax error (catch (eval))", function(done) { it("Should throw syntax error (catch (eval))", function(done) {
var command = uglifyjscmd + ' test/input/invalid/try.js'; var command = uglifyjscmd + " test/input/invalid/try.js";
exec(command, function(err, stdout, stderr) { exec(command, function(err, stdout, stderr) {
assert.ok(err); assert.ok(err);
assert.strictEqual(stdout, ""); assert.strictEqual(stdout, "");
@@ -560,7 +560,7 @@ describe("bin/uglifyjs", function() {
}); });
}); });
it("Should throw syntax error (var eval)", function(done) { it("Should throw syntax error (var eval)", function(done) {
var command = uglifyjscmd + ' test/input/invalid/var.js'; var command = uglifyjscmd + " test/input/invalid/var.js";
exec(command, function(err, stdout, stderr) { exec(command, function(err, stdout, stderr) {
assert.ok(err); assert.ok(err);
assert.strictEqual(stdout, ""); assert.strictEqual(stdout, "");
@@ -574,7 +574,7 @@ describe("bin/uglifyjs", function() {
}); });
}); });
it("Should throw syntax error (else)", function(done) { it("Should throw syntax error (else)", function(done) {
var command = uglifyjscmd + ' test/input/invalid/else.js'; var command = uglifyjscmd + " test/input/invalid/else.js";
exec(command, function(err, stdout, stderr) { exec(command, function(err, stdout, stderr) {
assert.ok(err); assert.ok(err);
assert.strictEqual(stdout, ""); assert.strictEqual(stdout, "");
@@ -588,7 +588,7 @@ describe("bin/uglifyjs", function() {
}); });
}); });
it("Should throw syntax error (return)", function(done) { it("Should throw syntax error (return)", function(done) {
var command = uglifyjscmd + ' test/input/invalid/return.js'; var command = uglifyjscmd + " test/input/invalid/return.js";
exec(command, function(err, stdout, stderr) { exec(command, function(err, stdout, stderr) {
assert.ok(err); assert.ok(err);
assert.strictEqual(stdout, ""); assert.strictEqual(stdout, "");
@@ -602,7 +602,7 @@ describe("bin/uglifyjs", function() {
}); });
}); });
it("Should throw syntax error (for-in init)", function(done) { it("Should throw syntax error (for-in init)", function(done) {
var command = uglifyjscmd + ' test/input/invalid/for-in_1.js'; var command = uglifyjscmd + " test/input/invalid/for-in_1.js";
exec(command, function(err, stdout, stderr) { exec(command, function(err, stdout, stderr) {
assert.ok(err); assert.ok(err);
assert.strictEqual(stdout, ""); assert.strictEqual(stdout, "");
@@ -616,7 +616,7 @@ describe("bin/uglifyjs", function() {
}); });
}); });
it("Should throw syntax error (for-in var)", function(done) { it("Should throw syntax error (for-in var)", function(done) {
var command = uglifyjscmd + ' test/input/invalid/for-in_2.js'; var command = uglifyjscmd + " test/input/invalid/for-in_2.js";
exec(command, function(err, stdout, stderr) { exec(command, function(err, stdout, stderr) {
assert.ok(err); assert.ok(err);
assert.strictEqual(stdout, ""); assert.strictEqual(stdout, "");
@@ -629,6 +629,18 @@ describe("bin/uglifyjs", function() {
done(); done();
}); });
}); });
it("Should throw syntax error (switch defaults)", function(done) {
var command = uglifyjscmd + " test/input/invalid/switch.js";
exec(command, function(err, stdout, stderr) {
assert.ok(err);
var lines = stderr.split(/\n/);
assert.strictEqual(lines[0], "Parse error at test/input/invalid/switch.js:3,2");
assert.strictEqual(lines[1], " default:");
assert.strictEqual(lines[2], " ^");
assert.strictEqual(lines[3], "ERROR: More than one default clause in switch statement");
done();
});
});
it("Should handle literal string as source map input", function(done) { it("Should handle literal string as source map input", function(done) {
var command = [ var command = [
uglifyjscmd, uglifyjscmd,

View File

@@ -40,7 +40,7 @@ function createContext() {
arg.constructor.toString(); arg.constructor.toString();
if (level--) for (var key in arg) { if (level--) for (var key in arg) {
var desc = Object.getOwnPropertyDescriptor(arg, key); var desc = Object.getOwnPropertyDescriptor(arg, key);
if (!desc || !desc.get) arg[key] = safe_log(arg[key], level); if (!desc || !desc.get && !desc.set) arg[key] = safe_log(arg[key], level);
} }
} }
return arg; return arg;

49
test/ufuzz/actions.js Normal file
View File

@@ -0,0 +1,49 @@
require("../../tools/exit");
var get = require("https").get;
var parse = require("url").parse;
var base = process.argv[2];
var token = process.argv[3];
function read(url, callback) {
var options = parse(url);
options.headers = {
"Authorization": "Token " + token,
"User-Agent": "UglifyJS",
};
get(options, function(response) {
var chunks = [];
response.setEncoding("utf8");
response.on("data", function(chunk) {
chunks.push(chunk);
}).on("end", function() {
callback(JSON.parse(chunks.join("")));
});
});
}
var queued = 0, total = 0, earliest, now = Date.now();
process.on("beforeExit", function() {
if (queued > 3) {
process.stdout.write("0");
} else if (now - earliest > 0 && total > 1) {
process.stdout.write(Math.min(20 * (now - earliest) / (total - 1), 6300000).toFixed(0));
} else {
process.stdout.write("3600000");
}
});
read(base + "/actions/workflows/ufuzz.yml/runs?event=schedule", function(reply) {
reply.workflow_runs.filter(function(workflow) {
return /^(in_progress|queued|)$/.test(workflow.status);
}).forEach(function(workflow) {
read(workflow.jobs_url, function(reply) {
reply.jobs.forEach(function(job) {
if (job.status == "queued") queued++;
total++;
if (!job.started_at) return;
var start = new Date(job.started_at);
if (!(earliest < start)) earliest = start;
});
});
});
});

View File

@@ -197,6 +197,7 @@ BINARY_OPS = BINARY_OPS.concat(BINARY_OPS);
BINARY_OPS = BINARY_OPS.concat(BINARY_OPS); BINARY_OPS = BINARY_OPS.concat(BINARY_OPS);
BINARY_OPS = BINARY_OPS.concat(BINARY_OPS); BINARY_OPS = BINARY_OPS.concat(BINARY_OPS);
BINARY_OPS = BINARY_OPS.concat(BINARY_OPS); BINARY_OPS = BINARY_OPS.concat(BINARY_OPS);
BINARY_OPS = BINARY_OPS.concat(BINARY_OPS);
BINARY_OPS.push(" in "); BINARY_OPS.push(" in ");
var ASSIGNMENTS = [ var ASSIGNMENTS = [
@@ -495,11 +496,16 @@ function createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn
var label = createLabel(canBreak, canContinue); var label = createLabel(canBreak, canContinue);
canBreak = label.break || enableLoopControl(canBreak, CAN_BREAK); canBreak = label.break || enableLoopControl(canBreak, CAN_BREAK);
canContinue = label.continue || enableLoopControl(canContinue, CAN_CONTINUE); canContinue = label.continue || enableLoopControl(canContinue, CAN_CONTINUE);
var optElementVar = ""; var key = rng(10) ? "key" + loop : getVarName();
if (rng(5) > 1) { return [
optElementVar = "c = 1 + c; var " + createVarName(MANDATORY) + " = expr" + loop + "[key" + loop + "]; "; "{var expr" + loop + " = " + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + "; ",
} label.target + " for (",
return "{var expr" + loop + " = " + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + "; " + label.target + " for (var key" + loop + " in expr" + loop + ") {" + optElementVar + createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth) + "}}"; /^key/.test(key) ? "var " : "",
key + " in expr" + loop + ") {",
rng(5) > 1 ? "c = 1 + c; var " + createVarName(MANDATORY) + " = expr" + loop + "[" + key + "]; " : "",
createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth),
"}}",
].join("");
case STMT_SEMI: case STMT_SEMI:
return use_strict && rng(20) === 0 ? '"use strict";' : ";"; return use_strict && rng(20) === 0 ? '"use strict";' : ";";
case STMT_EXPR: case STMT_EXPR:
@@ -1136,18 +1142,6 @@ function log(options) {
errorln(original_result); errorln(original_result);
errorln("uglified result:"); errorln("uglified result:");
errorln(uglify_result); errorln(uglify_result);
errorln("//-------------------------------------------------------------");
var reduced = reduce_test(original_code, JSON.parse(options), {
verbose: false,
}).code;
if (reduced) {
errorln();
errorln("// reduced test case (output will differ)");
errorln();
errorln(reduced);
errorln();
errorln("//-------------------------------------------------------------");
}
} else { } else {
errorln("// !!! uglify failed !!!"); errorln("// !!! uglify failed !!!");
errorln(uglify_code); errorln(uglify_code);
@@ -1158,6 +1152,20 @@ function log(options) {
errorln(original_result); errorln(original_result);
} }
} }
errorln("//-------------------------------------------------------------");
var reduce_options = JSON.parse(options);
reduce_options.validate = true;
var reduced = reduce_test(original_code, reduce_options, {
verbose: false,
}).code;
if (reduced) {
errorln();
errorln("// reduced test case (output will differ)");
errorln();
errorln(reduced);
errorln();
errorln("//-------------------------------------------------------------");
}
errorln("minify(options):"); errorln("minify(options):");
errorln(JSON.stringify(JSON.parse(options), null, 2)); errorln(JSON.stringify(JSON.parse(options), null, 2));
errorln(); errorln();
@@ -1172,7 +1180,8 @@ function log(options) {
} }
function sort_globals(code) { function sort_globals(code) {
return "var " + sandbox.run_code("throw Object.keys(this).sort();" + code).join(",") + ";" + code; var globals = sandbox.run_code("throw Object.keys(this).sort();" + code);
return globals.length ? "var " + globals.join(",") + ";" + code : code;
} }
function fuzzy_match(original, uglified) { function fuzzy_match(original, uglified) {
@@ -1231,7 +1240,7 @@ function patch_try_catch(orig, toplevel) {
if (typeof result != "object" || typeof result.name != "string" || typeof result.message != "string") { if (typeof result != "object" || typeof result.name != "string" || typeof result.message != "string") {
if (!stack.filled && match[1]) stack.push({ if (!stack.filled && match[1]) stack.push({
code: code, code: code,
index: index, index: index && index - 1,
offset: offset, offset: offset,
tries: JSON.parse(JSON.stringify(tries)), tries: JSON.parse(JSON.stringify(tries)),
}); });

456
tools/domprops.html Normal file
View File

@@ -0,0 +1,456 @@
<!doctype html>
<html>
<body>
<script>
!function(G) {
var domprops = [];
var objs = [ G ];
var tagNames = [
"a",
"abbr",
"acronym",
"address",
"applet",
"area",
"article",
"aside",
"audio",
"b",
"base",
"basefont",
"bdi",
"bdo",
"bgsound",
"big",
"blink",
"blockquote",
"body",
"br",
"button",
"canvas",
"caption",
"center",
"checked",
"cite",
"code",
"col",
"colgroup",
"command",
"comment",
"compact",
"content",
"data",
"datalist",
"dd",
"declare",
"defer",
"del",
"details",
"dfn",
"dialog",
"dir",
"disabled",
"div",
"dl",
"dt",
"element",
"em",
"embed",
"fieldset",
"figcaption",
"figure",
"font",
"footer",
"form",
"frame",
"frameset",
"h1",
"h2",
"h3",
"h4",
"h5",
"h6",
"head",
"header",
"hgroup",
"hr",
"html",
"i",
"iframe",
"image",
"img",
"input",
"ins",
"isindex",
"ismap",
"kbd",
"keygen",
"label",
"legend",
"li",
"link",
"listing",
"main",
"map",
"mark",
"marquee",
"math",
"menu",
"menuitem",
"meta",
"meter",
"multicol",
"multiple",
"nav",
"nextid",
"nobr",
"noembed",
"noframes",
"nohref",
"noresize",
"noscript",
"noshade",
"nowrap",
"object",
"ol",
"optgroup",
"option",
"output",
"p",
"param",
"picture",
"plaintext",
"pre",
"progress",
"q",
"rb",
"readonly",
"rp",
"rt",
"rtc",
"ruby",
"s",
"samp",
"script",
"section",
"select",
"selected",
"shadow",
"slot",
"small",
"source",
"spacer",
"span",
"strike",
"strong",
"style",
"sub",
"summary",
"sup",
"svg",
"table",
"tbody",
"td",
"template",
"textarea",
"tfoot",
"th",
"thead",
"time",
"title",
"tr",
"track",
"tt",
"u",
"ul",
"var",
"video",
"wbr",
"xmp",
"XXX",
];
for (var n = 0; n < tagNames.length; n++) {
add(document.createElement(tagNames[n]));
}
var nsNames = {
"http://www.w3.org/1998/Math/MathML": [
"annotation",
"annotation-xml",
"maction",
"maligngroup",
"malignmark",
"math",
"menclose",
"merror",
"mfenced",
"mfrac",
"mglyph",
"mi",
"mlabeledtr",
"mlongdiv",
"mmultiscripts",
"mn",
"mo",
"mover",
"mpadded",
"mphantom",
"mprescripts",
"mroot",
"mrow",
"ms",
"mscarries",
"mscarry",
"msgroup",
"msline",
"mspace",
"msqrt",
"msrow",
"mstack",
"mstyle",
"msub",
"msubsup",
"msup",
"mtable",
"mtd",
"mtext",
"mtr",
"munder",
"munderover",
"none",
"semantics",
],
"http://www.w3.org/2000/svg": [
"a",
"altGlyph",
"altGlyphDef",
"altGlyphItem",
"animate",
"animateColor",
"animateMotion",
"animateTransform",
"circle",
"clipPath",
"color-profile",
"cursor",
"defs",
"desc",
"discard",
"ellipse",
"feBlend",
"feColorMatrix",
"feComponentTransfer",
"feComposite",
"feConvolveMatrix",
"feDiffuseLighting",
"feDisplacementMap",
"feDistantLight",
"feDropShadow",
"feFlood",
"feFuncA",
"feFuncB",
"feFuncG",
"feFuncR",
"feGaussianBlur",
"feImage",
"feMerge",
"feMergeNode",
"feMorphology",
"feOffset",
"fePointLight",
"feSpecularLighting",
"feSpotLight",
"feTile",
"feTurbulence",
"filter",
"font",
"font-face",
"font-face-format",
"font-face-name",
"font-face-src",
"font-face-uri",
"foreignObject",
"g",
"glyph",
"glyphRef",
"hatch",
"hatchpath",
"hkern",
"image",
"line",
"linearGradient",
"marker",
"mask",
"mesh",
"meshgradient",
"meshpatch",
"meshrow",
"metadata",
"missing-glyph",
"mpath",
"path",
"pattern",
"polygon",
"polyline",
"radialGradient",
"rect",
"script",
"set",
"solidcolor",
"stop",
"style",
"svg",
"switch",
"symbol",
"text",
"textPath",
"title",
"tref",
"tspan",
"unknown",
"use",
"view",
"vkern",
],
};
if (document.createElementNS) for (var ns in nsNames) {
for (var n = 0; n < nsNames[ns].length; n++) {
add(document.createElementNS(ns, nsNames[ns][n]));
}
}
var skips = [
G.alert,
G.back,
G.blur,
G.captureEvents,
G.clearImmediate,
G.clearInterval,
G.clearTimeout,
G.close,
G.confirm,
G.console,
G.dump,
G.fetch,
G.find,
G.focus,
G.forward,
G.getAttention,
G.history,
G.home,
G.location,
G.moveBy,
G.moveTo,
G.navigator,
G.open,
G.openDialog,
G.print,
G.process,
G.prompt,
G.resizeBy,
G.resizeTo,
G.setImmediate,
G.setInterval,
G.setTimeout,
G.showModalDialog,
G.sizeToContent,
G.stop,
];
var types = [];
var interfaces = [
"beforeunloadevent",
"compositionevent",
"customevent",
"devicemotionevent",
"deviceorientationevent",
"dragevent",
"event",
"events",
"focusevent",
"hashchangeevent",
"htmlevents",
"keyboardevent",
"messageevent",
"mouseevent",
"mouseevents",
"storageevent",
"svgevents",
"textevent",
"touchevent",
"uievent",
"uievents",
];
var i = 0, full = false;
var addEvent = document.createEvent ? function(type) {
if (~indexOf(types, type)) return;
types.push(type);
for (var j = 0; j < interfaces.length; j++) try {
var event = document.createEvent(interfaces[j]);
event.initEvent(type, true, true);
add(event);
} catch (e) {}
} : function() {};
var scanProperties = Object.getOwnPropertyNames ? function(o, fn) {
var names = Object.getOwnPropertyNames(o);
names.forEach(fn);
for (var k in o) if (!~indexOf(names, k)) fn(k);
} : function(o, fn) {
for (var k in o) fn(k);
};
setTimeout(function next() {
for (var j = 10; --j >= 0 && i < objs.length; i++) {
var o = objs[i];
var skip = ~indexOf(skips, o);
try {
scanProperties(o, function(k) {
if (!~indexOf(domprops, k)) domprops.push(k);
if (/^on/.test(k)) addEvent(k.slice(2));
if (!full) try {
add(o[k]);
} catch (e) {}
});
} catch (e) {}
if (skip || full) continue;
try {
add(o.__proto__);
} catch (e) {}
try {
add(o.prototype);
} catch (e) {}
try {
add(new o());
} catch (e) {}
try {
add(o());
} catch (e) {}
}
if (!full && objs.length > 20000) {
alert(objs.length);
full = true;
}
if (i < objs.length) {
setTimeout(next, 0);
} else {
document.write('<pre>[\n "' + domprops.sort().join('",\n "').replace(/&/g, "&amp;").replace(/</g, "&lt;") + '"\n]</pre>');
}
}, 0);
function add(o) {
if (o) switch (typeof o) {
case "function":
case "object":
if (!~indexOf(objs, o)) objs.push(o);
}
}
function indexOf(list, value) {
var j = list.length;
while (--j >= 0) {
if (list[j] === value) break;
}
return j;
}
}(function() {
return this;
}());
</script>
</body>
</html>

File diff suppressed because it is too large Load Diff

View File

@@ -1,540 +0,0 @@
<!doctype html>
<html>
<body>
<script>
!function() {
var names = [];
var scanned = [];
var to_scan = [];
function scan(obj) {
if (obj && typeof obj == "object" && !~scanned.indexOf(obj)) {
scanned.push(obj);
to_scan.push(obj);
}
}
scan(self);
[
"a",
"abbr",
"acronym",
"address",
"applet",
"area",
"article",
"aside",
"audio",
"b",
"base",
"basefont",
"bdi",
"bdo",
"bgsound",
"big",
"blink",
"blockquote",
"body",
"br",
"button",
"canvas",
"caption",
"center",
"checked",
"cite",
"code",
"col",
"colgroup",
"command",
"comment",
"compact",
"content",
"data",
"datalist",
"dd",
"declare",
"defer",
"del",
"details",
"dfn",
"dialog",
"dir",
"disabled",
"div",
"dl",
"dt",
"element",
"em",
"embed",
"fieldset",
"figcaption",
"figure",
"font",
"footer",
"form",
"frame",
"frameset",
"h1",
"h2",
"h3",
"h4",
"h5",
"h6",
"head",
"header",
"hgroup",
"hr",
"html",
"i",
"iframe",
"image",
"img",
"input",
"ins",
"isindex",
"ismap",
"kbd",
"keygen",
"label",
"legend",
"li",
"link",
"listing",
"main",
"map",
"mark",
"marquee",
"math",
"menu",
"menuitem",
"meta",
"meter",
"multicol",
"multiple",
"nav",
"nobr",
"noembed",
"noframes",
"nohref",
"noresize",
"noscript",
"noshade",
"nowrap",
"object",
"ol",
"optgroup",
"option",
"output",
"p",
"param",
"picture",
"plaintext",
"pre",
"progress",
"q",
"rb",
"readonly",
"rp",
"rt",
"rtc",
"ruby",
"s",
"samp",
"script",
"section",
"select",
"selected",
"shadow",
"small",
"source",
"spacer",
"span",
"strike",
"strong",
"style",
"sub",
"summary",
"sup",
"svg",
"table",
"tbody",
"td",
"template",
"textarea",
"tfoot",
"th",
"thead",
"time",
"title",
"tr",
"track",
"tt",
"u",
"ul",
"var",
"video",
"wbr",
"xmp",
"XXX",
].forEach(function(tag) {
scan(document.createElement(tag));
});
[
"abort",
"absolutedeviceorientation",
"activate",
"active",
"addsourcebuffer",
"addstream",
"addtrack",
"afterprint",
"afterscriptexecute",
"afterupdate",
"animationcancel",
"animationend",
"animationiteration",
"animationstart",
"appinstalled",
"audioend",
"audioprocess",
"audiostart",
"autocomplete",
"autocompleteerror",
"auxclick",
"beforeactivate",
"beforecopy",
"beforecut",
"beforedeactivate",
"beforeeditfocus",
"beforeinstallprompt",
"beforepaste",
"beforeprint",
"beforescriptexecute",
"beforeunload",
"beforeupdate",
"blocked",
"blur",
"bounce",
"boundary",
"cached",
"cancel",
"candidatewindowhide",
"candidatewindowshow",
"candidatewindowupdate",
"canplay",
"canplaythrough",
"cellchange",
"change",
"chargingchange",
"chargingtimechange",
"checking",
"click",
"close",
"compassneedscalibration",
"complete",
"connect",
"connecting",
"connectionstatechange",
"contextmenu",
"controllerchange",
"controlselect",
"copy",
"cuechange",
"cut",
"dataavailable",
"datachannel",
"datasetchanged",
"datasetcomplete",
"dblclick",
"deactivate",
"devicechange",
"devicelight",
"devicemotion",
"deviceorientation",
"deviceorientationabsolute",
"deviceproximity",
"dischargingtimechange",
"disconnect",
"display",
"downloading",
"drag",
"dragend",
"dragenter",
"dragexit",
"dragleave",
"dragover",
"dragstart",
"drop",
"durationchange",
"emptied",
"encrypted",
"end",
"ended",
"enter",
"enterpictureinpicture",
"error",
"errorupdate",
"exit",
"filterchange",
"finish",
"focus",
"focusin",
"focusout",
"freeze",
"fullscreenchange",
"fullscreenerror",
"gesturechange",
"gestureend",
"gesturestart",
"gotpointercapture",
"hashchange",
"help",
"icecandidate",
"iceconnectionstatechange",
"icegatheringstatechange",
"inactive",
"input",
"invalid",
"keydown",
"keypress",
"keyup",
"languagechange",
"layoutcomplete",
"leavepictureinpicture",
"levelchange",
"load",
"loadeddata",
"loadedmetadata",
"loadend",
"loading",
"loadingdone",
"loadingerror",
"loadstart",
"losecapture",
"lostpointercapture",
"mark",
"message",
"messageerror",
"mousedown",
"mouseenter",
"mouseleave",
"mousemove",
"mouseout",
"mouseover",
"mouseup",
"mousewheel",
"move",
"moveend",
"movestart",
"mozfullscreenchange",
"mozfullscreenerror",
"mozorientationchange",
"mozpointerlockchange",
"mozpointerlockerror",
"mscontentzoom",
"msfullscreenchange",
"msfullscreenerror",
"msgesturechange",
"msgesturedoubletap",
"msgestureend",
"msgesturehold",
"msgesturestart",
"msgesturetap",
"msgotpointercapture",
"msinertiastart",
"mslostpointercapture",
"msmanipulationstatechanged",
"msneedkey",
"msorientationchange",
"mspointercancel",
"mspointerdown",
"mspointerenter",
"mspointerhover",
"mspointerleave",
"mspointermove",
"mspointerout",
"mspointerover",
"mspointerup",
"mssitemodejumplistitemremoved",
"msthumbnailclick",
"negotiationneeded",
"nomatch",
"noupdate",
"obsolete",
"offline",
"online",
"open",
"orientationchange",
"pagechange",
"pagehide",
"pageshow",
"paste",
"pause",
"play",
"playing",
"pluginstreamstart",
"pointercancel",
"pointerdown",
"pointerenter",
"pointerleave",
"pointerlockchange",
"pointerlockerror",
"pointermove",
"pointerout",
"pointerover",
"pointerup",
"popstate",
"progress",
"propertychange",
"ratechange",
"reading",
"readystatechange",
"rejectionhandled",
"removesourcebuffer",
"removestream",
"removetrack",
"reset",
"resize",
"resizeend",
"resizestart",
"resourcetimingbufferfull",
"result",
"resume",
"rowenter",
"rowexit",
"rowsdelete",
"rowsinserted",
"scroll",
"search",
"seeked",
"seeking",
"select",
"selectionchange",
"selectstart",
"show",
"signalingstatechange",
"soundend",
"soundstart",
"sourceclose",
"sourceclosed",
"sourceended",
"sourceopen",
"speechend",
"speechstart",
"stalled",
"start",
"statechange",
"stop",
"storage",
"storagecommit",
"submit",
"success",
"suspend",
"textinput",
"timeout",
"timeupdate",
"toggle",
"touchcancel",
"touchend",
"touchmove",
"touchstart",
"track",
"transitioncancel",
"transitionend",
"transitionrun",
"transitionstart",
"unhandledrejection",
"unload",
"updateready",
"upgradeneeded",
"userproximity",
"versionchange",
"visibilitychange",
"voiceschanged",
"volumechange",
"vrdisplayactivate",
"vrdisplayconnect",
"vrdisplaydeactivate",
"vrdisplaydisconnect",
"vrdisplaypresentchange",
"waiting",
"waitingforkey",
"warning",
"webkitanimationend",
"webkitanimationiteration",
"webkitanimationstart",
"webkitcurrentplaybacktargetiswirelesschanged",
"webkitfullscreenchange",
"webkitfullscreenerror",
"webkitkeyadded",
"webkitkeyerror",
"webkitkeymessage",
"webkitneedkey",
"webkitorientationchange",
"webkitplaybacktargetavailabilitychanged",
"webkitpointerlockchange",
"webkitpointerlockerror",
"webkitresourcetimingbufferfull",
"webkittransitionend",
"wheel",
"zoom",
].forEach(function(type) {
[
"beforeunloadevent",
"compositionevent",
"customevent",
"devicemotionevent",
"deviceorientationevent",
"dragevent",
"event",
"events",
"focusevent",
"hashchangeevent",
"htmlevents",
"keyboardevent",
"messageevent",
"mouseevent",
"mouseevents",
"storageevent",
"svgevents",
"textevent",
"touchevent",
"uievent",
"uievents",
].forEach(function(interface) {
try {
var event = document.createEvent(interface);
event.initEvent(type, true, true);
scan(event);
} catch (e) {}
});
});
var obj;
while (obj = to_scan.shift()) {
var proto = obj;
do {
Object.getOwnPropertyNames(proto).forEach(function(name) {
var visited = ~names.indexOf(name);
if (!visited) names.push(name);
try {
scan(obj[name]);
if (visited) return;
if (/^create/.test(name)) {
scan(obj[name]());
}
if (/^[A-Z]/.test(name)) {
scan(new obj[name]());
}
} catch (e) {}
});
} while (proto = Object.getPrototypeOf(proto));
}
names.sort();
document.write('<pre>[\n "');
document.write(names.join('",\n "'));
document.write('"\n]</pre>');
}();
</script>
</body>
</html>