Compare commits

..

57 Commits

Author SHA1 Message Date
Alex Lam S.L
66e9039350 Merge pull request #1740 from alexlamsl/harmony-v2.8.19
Merging from master for 2.8.19
2017-03-31 13:28:56 +08:00
alexlamsl
d717bf9ce8 Merge branch 'master' into harmony 2017-03-31 12:54:03 +08:00
Alex Lam S.L
a84564d1a8 v2.8.19 2017-03-31 12:26:10 +08:00
Alex Lam S.L
c595b84032 fix catch symbol mangling (#1734)
Only need to look up the immediate non-block/catch scope for the same-name special case.

fixes #1733
2017-03-31 02:57:47 +08:00
Ondřej Španěl
5dea52266b [ES6] Implemented parse for export Name from Module variants. (#1701)
- add `AST_Export` new variants output
- add tests to `test/compress/`
- update `$propdoc` of `AST_Export` ("exported_names" & "module_name")
- add tests for `export ...  as ...` variants
2017-03-30 17:07:50 +08:00
Alex Lam S.L
7cb1adf455 remove paranthesis for -(x*y) (#1732) 2017-03-30 16:09:00 +08:00
Alex Lam S.L
7bea38a05d optimize try-catch-finally (#1731)
- eliminate empty blocks
- flatten out if try-block does not throw
2017-03-30 12:16:58 +08:00
Alex Lam S.L
0f910ee25c improve tests from #1726 (#1729) 2017-03-30 00:13:46 +08:00
Alex Lam S.L
beb9659778 speed up IIFE elimination (#1728)
- `side_effects` will clean up inner statements, so checking for an empty function body should suffice
- drop side effects when dropping `return` from statement
2017-03-29 23:27:35 +08:00
Alex Lam S.L
f1a833a7aa speed up equivalent_to() and AST_Switch (#1727) 2017-03-29 22:08:26 +08:00
Alex Lam S.L
2e41cd6394 fix missing parentheses around NaN/Infinity shorthands (#1726)
fixes #1724
fixes #1725
2017-03-29 20:53:03 +08:00
Alex Lam S.L
09f77c7d4d output optimal representations of NaN & Infinity (#1723)
- move these optimisations out from `Compressor` to `OutputStream`
- fixes behaviour inconsistency when running uglified code from global or module levels due to redefinition
2017-03-29 18:31:55 +08:00
Alex Lam S.L
fef0bf9ee0 improve beautified output of switch blocks (#1721) 2017-03-29 04:40:05 +08:00
Alex Lam S.L
ae740b933f v2.8.18 2017-03-29 03:13:30 +08:00
Alex Lam S.L
ec7f37f314 remove UGLIFY_DEBUG (#1720)
fixes #1719
2017-03-29 01:27:24 +08:00
Alex Lam S.L
fccefbeaca Merge pull request #1717 from alexlamsl/harmony-v2.8.17
Merging from master for 2.8.17
2017-03-29 01:01:28 +08:00
Alex Lam S.L
eb48a035e7 fix corner case in unused (#1718)
When fixing catch-related issue in #1715, it tries to optimise for duplicate definitions but did not take anonymous functions into account.

Remove such optimisation for now and we can cover this as a more general rule later.
2017-03-29 01:00:21 +08:00
alexlamsl
1e2b0aaa04 Merge branch 'master' into harmony-v2.8.17 2017-03-28 22:03:46 +08:00
Alex Lam S.L
6ab3224c0d v2.8.17 2017-03-28 21:49:04 +08:00
Alex Lam S.L
c909ffb715 fix unused on var of the same name within catch (#1716)
fixes #1715
2017-03-28 21:25:49 +08:00
Alex Lam S.L
f71f4905b0 fix is_number() on += (#1714)
fixes #1710
2017-03-28 17:08:16 +08:00
Alex Lam S.L
fb177a6312 drop anonymous function name when overshadowed by other declarations (#1712)
fixes #1709
2017-03-28 17:02:20 +08:00
Alex Lam S.L
65da9acce6 handle var within catch of the same name (#1711)
The following code prints `1`:

var a = 1;
!function(){
  a = 4;
  try{
    throw 2;
  } catch (a) {
    var a = 3;
  }
}();
console.log(a);

fixes #1708
2017-03-28 16:42:39 +08:00
Alex Lam S.L
67d0237f73 fix tail trimming of switch blocks (#1707)
now guarded under `dead_code`

fixes #1705
2017-03-28 03:59:13 +08:00
Alex Lam S.L
984a21704e fix mangle for variable declared within catch block (#1706)
fixes #1704
2017-03-28 03:26:35 +08:00
Alex Lam S.L
aa3f647656 ufuzz: workaround for Function.toString() v2 (#1700) 2017-03-27 21:49:08 +08:00
Alex Lam S.L
c526da59a1 has_side_effects() should take AST_Switch.expression into account (#1699)
fixes #1698
2017-03-27 18:09:35 +08:00
Alex Lam S.L
581630e0a7 fix typeof side effects (#1696)
`statement_to_expression()` drops `typeof` even if it operates on undeclared variables.

Since we now have `drop_side_effect_free()`, replace and remove this deprecated functionality.
2017-03-27 04:37:42 +08:00
Alex Lam S.L
f5952933a0 preserve side effects in switch expression (#1694)
fixes #1690
2017-03-27 02:32:46 +08:00
Alex Lam S.L
f001e4cb9d fix cascade on anonymous function reference (#1693)
Unlike normal variables and even function definitions, these cannot be reassigned, even though assignment expressions would "leak" the assigned value as normal.
2017-03-27 01:58:21 +08:00
Alex Lam S.L
57ce5bd9e0 handle overlapped variable definitions (#1691)
Process variable definitions with or without assigned values against:
- `arguments`
- named function arguments
- multiple definitions within same scope

Essentially demote variable declarations with no value assignments.

Also fixed invalid use of `AST_VarDef` over `arguments` - should use a member of `AST_SymbolDeclaration` instead.
2017-03-27 01:30:21 +08:00
Alex Lam S.L
861a79ac9f fix delete related issues in collapse_vars and reduce_vars (#1689) 2017-03-26 19:14:30 +08:00
Alex Lam S.L
00996afd2c ufuzz: workaround function name and toString() (#1688)
fixes #1686
2017-03-26 18:18:44 +08:00
Alex Lam S.L
e76fb354eb fix cascade on delete operator (#1687)
Conditions including strict mode would make `delete` return `true` or `false`, and are too complex to be evaluated by the compressor.

Suppress assignment folding into said operator.

fixes #1685
2017-03-26 18:08:44 +08:00
Alex Lam S.L
3276740779 fallthrough should not execute case expression (#1683)
- de-duplicate trailing cases only, avoid all potential side-effects
- enable switch statement fuzzing

fixes #1680
2017-03-26 16:52:38 +08:00
kzc
5509e51098 optimize conditional when condition symbol matches consequent (#1684) 2017-03-26 16:36:33 +08:00
Alex Lam S.L
94f84727ce suppress switch branch de-duplication upon side effects (#1682)
fixes #1679
2017-03-26 13:32:43 +08:00
Alex Lam S.L
8a4f86528f fix side-effects detection on switch statements (#1678)
extension of #1675
2017-03-26 12:05:44 +08:00
Peter van der Zee
adb0e882e9 Improve fuzzer. :) (#1665)
@qfox	Put value constants in a global constant			74c0fb9
 @qfox	And the other string based values as well			a5033c5
 @qfox	Be more strict about parameters, allow max to be optional			9c7ce70
 @qfox	Support a `V` (capital) flag to only log out at intervals			2d822c7
 @qfox	Fewer magic variables			a6a9a7c
 @qfox	Fix decrement such that a function is created when n=1			7e4b017
 @qfox	Add more values			64e596e
 @qfox	Make `b` appear more often			d33191a
 @qfox	Add functions that contain (only..) functions			29a86e3
 @qfox	Allow the block statement to contain multiple statements			7570484
 @qfox	Make the interval count a constant			d587ad8
 @qfox	Enable mangling, disable post-processing  …			4dc8d35
 @qfox	Add more simple value that may trigger syntactic errors			8496d58
 @qfox	Add `else` to some `if` statements			a4aed65
 @qfox	Move iife to expr generator, fix missing recursion arg			e453159
 @qfox	Improve output on error where it wasnt printing the last code properly			4565a1a
 @qfox	Add switch statement to generator			ceafa76
 @qfox	Add var statement, support optional comma for expr generator			b83921b
 @qfox	Expression generator should use a simple value instead of `0` as recu…  …			9d1a5c7
 @qfox	const -> var to keep things es5...			0143099
 @qfox	Add more simple values that may trigger edge cases			5e124f1
 @qfox	Add central name generator, take special care for global functions			aeb7682
 @qfox	Add some `return` and function declaration cases to statement generator			6c9c3cc
 @qfox	Exclude switches from generator for now			91124b2

Put value constants in a global constant

And the other string based values as well

Be more strict about parameters, allow max to be optional

Support a `V` (capital) flag to only log out at intervals

Fewer magic variables

Fix decrement such that a function is created when n=1

Add more values

Make `b` appear more often

Add functions that contain (only..) functions

Allow the block statement to contain multiple statements

Make the interval count a constant

Enable mangling, disable post-processing

Mangling is kind of the whole point...

Similarly, to beautify the minified code afterwards may supress bugs so it's probably best not to beautify the code prematurely. And there's no point anyways since you won't see it most of the time and only care about the main input anyways.

Add more simple value that may trigger syntactic errors

Add `else` to some `if` statements

Move iife to expr generator, fix missing recursion arg

Improve output on error where it wasnt printing the last code properly

Add switch statement to generator

Add var statement, support optional comma for expr generator

Expression generator should use a simple value instead of `0` as recursion default

const -> var to keep things es5...

Add more simple values that may trigger edge cases

Add central name generator, take special care for global functions

Add some `return` and function declaration cases to statement generator

Exclude switches from generator for now

Enable switch generation because #1667 was merged

Add typeof generator

Add some elision tests

Add a new edge case that returns an object explicitly

Add all binary ops to try and cover more paths

Forgot four binops and added `Math` to var name pool

Harden the incremental pre/postfix tests

Improve switch generator, allow `default` to appear at any clause index

Add try/catch/finally generation

Prevent function statements being generated

Add edge case with decremental op and a group

Disable switch generation until #1679 and #1680 are solved

Only allow `default` clause as last clause for now

Tentatively enable `throw`, `break` and `continue` statements when in valid contexts
2017-03-26 12:04:50 +08:00
kzc
6a54de79b5 optimize trivial arrow functions with a return statement in braces (#1681)
fixes #1676
2017-03-26 12:03:11 +08:00
Alex Lam S.L
f83d370f57 improve switch optimisations (#1677)
- correctly determine reachability of (default) branches
- gracefully handle multiple default branches
- optimise branches with duplicate bodies

fixes #376
fixes #441
fixes #1674
2017-03-26 05:15:46 +08:00
Alex Lam S.L
b19aa58cff fix has_side_effects() (#1675)
`AST_Try` is an `AST_Block`, so besides try block we also need to inspect catch and finally blocks for possible side effects.

Also extend this functionality to handle `AST_If` and `AST_LabeledStatement` while we are at it.

fixes #1673
2017-03-25 23:03:26 +08:00
Alex Lam S.L
0a65de89b9 fix reduce_vars on AST_Switch (#1671)
Take conditional nature of switch branches into account.

fixes #1670
2017-03-25 21:17:30 +08:00
Alex Lam S.L
6e86ee950d fix typeof side-effects (#1669)
`has_side_effects()` does not take `typeof`'s magical power of not tripping over undeclared variable into account.

fixes #1668
2017-03-25 17:40:18 +08:00
Alex Lam S.L
8ca2401ebe fix dead_code on AST_Switch (#1667)
Need to call `extract_declarations_from_unreachable_code()`.

fixes #1663
2017-03-25 16:21:42 +08:00
Alex Lam S.L
491f16c766 v2.8.16 2017-03-25 03:21:16 +08:00
Alex Lam S.L
a30092e20f fix invalid AST_For.init (#1657)
Turns out the only place in `Compressor` which can generate invalid `AST_For.init` is within `drop_unused()`, so focus the fix-up efforts.

supercedes #1652
fixes #1656
2017-03-25 03:18:36 +08:00
Alex Lam S.L
b1abe92e1a introduce ufuzz.js (#1655)
closes #1647
2017-03-25 01:46:12 +08:00
Alex Lam S.L
b454ce667e Update ISSUE_TEMPLATE.md 2017-03-24 23:12:58 +08:00
Alex Lam S.L
32283a0def fix cascade of evaluate optimisation (#1654)
Operator has changed, so break out from rest of the rules.

fixes #1649
2017-03-24 22:09:19 +08:00
Alex Lam S.L
ac51d4c5a0 fix corner case in AST_For.init (#1652)
Enforce `null` as value for empty initialisation blocks.

fixes #1648
2017-03-24 19:31:17 +08:00
Alex Lam S.L
0432a7abb9 fix assignment extraction from conditional (#1651)
fixes #1645
fixes #1646
2017-03-24 18:52:48 +08:00
Alex Lam S.L
f3a1694a41 fix assignment substitution in sequences (#1643)
take side effects of binary boolean operations into account

fixes #1639
2017-03-24 14:30:31 +08:00
Alex Lam S.L
2e0dc97003 improve error marker placement (#1644)
For AST_UnaryPrefix, points to the operator rather than end of expression.
2017-03-24 14:28:40 +08:00
Alex Lam S.L
701035621d fix expect_stdout (#1642)
`compress()` may modify input ASTs

add tests for #1627 & #1640
2017-03-24 13:19:50 +08:00
kzc
79334dda10 fix regression: CLI options with hyphens like -b ascii-only (#1640)
fixes #1637
2017-03-24 11:55:03 +08:00
Alex Lam S.L
e918748d88 improve collapsible value detection (#1638)
- #1634 bars variables with cross-scope references in between to collapse
- but if assigned value is side-effect-free, no states can be modified, so it is safe to move
2017-03-24 02:55:32 +08:00
43 changed files with 3488 additions and 328 deletions

View File

@@ -1,7 +1,9 @@
- Bug report or feature request?
- Bug report or feature request? <!-- Note: sub-optimal but correct code is not a bug -->
- `uglify-js` version (`uglifyjs -V`)
- JavaScript input - ideally as small as possible.
- The `uglifyjs` CLI command executed or `minify()` options used.
- An example of JavaScript output produced and/or the error or warning.
Note: the release version of `uglify-js` only supports ES5. Those wishing to minify ES6 should use the experimental [`harmony`](https://github.com/mishoo/UglifyJS2#harmony) branch.
<!--
Note: the release version of uglify-js only supports ES5. Those wishing
to minify ES6 should use the experimental harmony branch.
-->

View File

@@ -561,7 +561,7 @@ function getOptions(flag, constants) {
var ast;
try {
ast = UglifyJS.parse(x, { expression: true });
ast = UglifyJS.parse(x, { cli: true, expression: true });
} catch(ex) {
if (ex instanceof UglifyJS.JS_Parse_Error) {
print_error("Error parsing arguments for flag `" + flag + "': " + x);

View File

@@ -789,11 +789,13 @@ var AST_Import = DEFNODE("Import", "imported_name imported_names module_name", {
}
});
var AST_Export = DEFNODE("Export", "exported_definition exported_value is_default", {
var AST_Export = DEFNODE("Export", "exported_definition exported_value is_default exported_names module_name", {
$documentation: "An `export` statement",
$propdoc: {
exported_definition: "[AST_Defun|AST_Definitions|AST_DefClass?] An exported definition",
exported_value: "[AST_Node?] An exported value",
exported_names: "[AST_NameImport*?] List of exported names",
module_name: "[AST_String?] Name of the file to load exports from",
is_default: "[Boolean] Whether this is the default exported value of this module"
},
_walk: function (visitor) {

View File

@@ -197,11 +197,10 @@ merge(Compressor.prototype, {
});
AST_Node.DEFMETHOD("equivalent_to", function(node){
// XXX: this is a rather expensive way to test two node's equivalence:
return this.print_to_string() == node.print_to_string();
return this.TYPE == node.TYPE && this.print_to_string() == node.print_to_string();
});
AST_Node.DEFMETHOD("process_expression", function(insert) {
AST_Node.DEFMETHOD("process_expression", function(insert, compressor) {
var self = this;
var tt = new TreeTransformer(function(node) {
if (insert && node instanceof AST_SimpleStatement) {
@@ -210,6 +209,12 @@ merge(Compressor.prototype, {
});
}
if (!insert && node instanceof AST_Return) {
if (compressor) {
var value = node.value && node.value.drop_side_effect_free(compressor, true);
return value ? make_node(AST_SimpleStatement, node, {
body: value
}) : make_node(AST_EmptyStatement, node);
}
return make_node(AST_SimpleStatement, node, {
body: node.value || make_node(AST_Undefined, node)
});
@@ -261,7 +266,7 @@ merge(Compressor.prototype, {
if (node instanceof AST_SymbolRef) {
var d = node.definition();
d.references.push(node);
if (!d.fixed || !is_safe(d)
if (d.fixed === undefined || !is_safe(d)
|| is_modified(node, 0, d.fixed instanceof AST_Lambda)) {
d.fixed = false;
}
@@ -274,10 +279,10 @@ merge(Compressor.prototype, {
node.name.walk(suppressor);
} else {
var d = node.name.definition();
if (d.fixed === undefined) {
d.fixed = node.value || make_node(AST_Undefined, node);
if (d.fixed == null) {
d.fixed = node.value;
mark_as_safe(d);
} else {
} else if (node.value) {
d.fixed = false;
}
}
@@ -346,7 +351,7 @@ merge(Compressor.prototype, {
pop();
return true;
}
if (node instanceof AST_Catch) {
if (node instanceof AST_Catch || node instanceof AST_SwitchBranch) {
push();
descend();
pop();
@@ -362,7 +367,14 @@ merge(Compressor.prototype, {
function is_safe(def) {
for (var i = safe_ids.length, id = def.id; --i >= 0;) {
if (safe_ids[i][id]) return true;
if (safe_ids[i][id]) {
if (def.fixed == null) {
var orig = def.orig[0];
if (orig instanceof AST_SymbolFunarg || orig.name == "arguments") return false;
def.fixed = make_node(AST_Undefined, orig);
}
return true;
}
}
}
@@ -411,18 +423,17 @@ merge(Compressor.prototype, {
value: val
});
case "number":
if (isNaN(val)) {
return make_node(AST_NaN, orig);
}
if ((1 / val) < 0) {
return make_node(AST_UnaryPrefix, orig, {
if (isNaN(val)) return make_node(AST_NaN, orig);
if (isFinite(val)) {
return 1 / val < 0 ? make_node(AST_UnaryPrefix, orig, {
operator: "-",
expression: make_node(AST_Number, orig, { value: -val })
});
}) : make_node(AST_Number, orig, { value: val });
}
return make_node(AST_Number, orig, { value: val });
return val < 0 ? make_node(AST_UnaryPrefix, orig, {
operator: "-",
expression: make_node(AST_Infinity, orig)
}) : make_node(AST_Infinity, orig);
case "boolean":
return make_node(val ? AST_True : AST_False, orig);
case "undefined":
@@ -488,15 +499,6 @@ merge(Compressor.prototype, {
return x;
};
var readOnlyPrefix = makePredicate("! ~ + - void typeof");
function statement_to_expression(stat) {
if (stat.body instanceof AST_UnaryPrefix && readOnlyPrefix(stat.body.operator)) {
return stat.body.expression;
} else {
return stat.body;
}
}
function is_iife_call(node) {
if (node instanceof AST_Call && !(node instanceof AST_New)) {
return node.expression instanceof AST_Function || is_iife_call(node.expression);
@@ -606,9 +608,10 @@ merge(Compressor.prototype, {
// Restrict var replacement to constants if side effects encountered.
if (side_effects_encountered |= lvalues_encountered) continue;
var value_has_side_effects = var_decl.value.has_side_effects(compressor);
// Non-constant single use vars can only be replaced in same scope.
if (ref.scope.get_defun_scope() !== self) {
side_effects_encountered |= var_decl.value.has_side_effects(compressor);
side_effects_encountered |= value_has_side_effects;
continue;
}
@@ -635,6 +638,7 @@ merge(Compressor.prototype, {
|| (parent instanceof AST_If && node !== parent.condition)
|| (parent instanceof AST_Conditional && node !== parent.condition)
|| (node instanceof AST_SymbolRef
&& value_has_side_effects
&& !are_references_in_scope(node.definition(), self))
|| (parent instanceof AST_Binary
&& (parent.operator == "&&" || parent.operator == "||")
@@ -998,10 +1002,10 @@ merge(Compressor.prototype, {
};
statements.forEach(function(stat){
if (stat instanceof AST_SimpleStatement) {
if (seqLength(seq) >= compressor.sequences_limit) {
push_seq();
}
seq.push(seq.length > 0 ? statement_to_expression(stat) : stat.body);
if (seqLength(seq) >= compressor.sequences_limit) push_seq();
var body = stat.body;
if (seq.length > 0) body = body.drop_side_effect_free(compressor);
if (body) seq.push(body);
} else {
push_seq();
ret.push(stat);
@@ -1050,7 +1054,7 @@ merge(Compressor.prototype, {
stat.init = cons_seq(stat.init);
}
else if (!stat.init) {
stat.init = statement_to_expression(prev);
stat.init = prev.body.drop_side_effect_free(compressor);
ret.pop();
}
} catch(ex) {
@@ -1190,9 +1194,9 @@ merge(Compressor.prototype, {
&& this.left.is_number(compressor)
&& this.right.is_number(compressor);
});
var assign = makePredicate("-= *= /= %= &= |= ^= <<= >>= >>>=");
def(AST_Assign, function(compressor){
return assign(this.operator) || this.right.is_number(compressor);
return binary(this.operator.slice(0, -1))
|| this.operator == "=" && this.right.is_number(compressor);
});
def(AST_Seq, function(compressor){
return this.cdr.is_number(compressor);
@@ -1231,8 +1235,10 @@ merge(Compressor.prototype, {
node.DEFMETHOD("is_string", func);
});
var unary_side_effects = makePredicate("delete ++ --");
function isLHS(node, parent) {
return parent instanceof AST_Unary && (parent.operator == "++" || parent.operator == "--")
return parent instanceof AST_Unary && unary_side_effects(parent.operator)
|| parent instanceof AST_Assign && parent.left === node;
}
@@ -1636,14 +1642,37 @@ merge(Compressor.prototype, {
return false;
});
def(AST_Block, function(compressor){
for (var i = this.body.length; --i >= 0;) {
if (this.body[i].has_side_effects(compressor))
function any(list, compressor) {
for (var i = list.length; --i >= 0;)
if (list[i].has_side_effects(compressor))
return true;
}
return false;
});
}
def(AST_Block, function(compressor){
return any(this.body, compressor);
});
def(AST_Switch, function(compressor){
return this.expression.has_side_effects(compressor)
|| any(this.body, compressor);
});
def(AST_Case, function(compressor){
return this.expression.has_side_effects(compressor)
|| any(this.body, compressor);
});
def(AST_Try, function(compressor){
return any(this.body, compressor)
|| this.bcatch && this.bcatch.has_side_effects(compressor)
|| this.bfinally && this.bfinally.has_side_effects(compressor);
});
def(AST_If, function(compressor){
return this.condition.has_side_effects(compressor)
|| this.body && this.body.has_side_effects(compressor)
|| this.alternative && this.alternative.has_side_effects(compressor);
});
def(AST_LabeledStatement, function(compressor){
return this.body.has_side_effects(compressor);
});
def(AST_SimpleStatement, function(compressor){
return this.body.has_side_effects(compressor);
});
@@ -1662,17 +1691,14 @@ merge(Compressor.prototype, {
|| this.alternative.has_side_effects(compressor);
});
def(AST_Unary, function(compressor){
return member(this.operator, ["delete", "++", "--"])
return unary_side_effects(this.operator)
|| this.expression.has_side_effects(compressor);
});
def(AST_SymbolRef, function(compressor){
return this.global() && this.undeclared();
});
def(AST_Object, function(compressor){
for (var i = this.properties.length; --i >= 0;)
if (this.properties[i].has_side_effects(compressor))
return true;
return false;
return any(this.properties, compressor);
});
def(AST_ObjectProperty, function(compressor){
if (this.key instanceof AST_ObjectKeyVal &&
@@ -1681,10 +1707,7 @@ merge(Compressor.prototype, {
return this.value.has_side_effects(compressor);
});
def(AST_Array, function(compressor){
for (var i = this.elements.length; --i >= 0;)
if (this.elements[i].has_side_effects(compressor))
return true;
return false;
return any(this.elements, compressor);
});
def(AST_Dot, function(compressor){
if (!compressor.option("pure_getters")) return true;
@@ -1927,9 +1950,13 @@ merge(Compressor.prototype, {
function before(node, descend, in_list) {
if (node instanceof AST_Function
&& node.name
&& !compressor.option("keep_fnames")
&& !(node.name.definition().id in in_use_ids)) {
node.name = null;
&& !compressor.option("keep_fnames")) {
var def = node.name.definition();
// any declarations with same name will overshadow
// name of this anonymous function and can therefore
// never be used anywhere
if (!(def.id in in_use_ids) || def.orig.length > 1)
node.name = null;
}
if (node instanceof AST_Lambda && !(node instanceof AST_Accessor)) {
var trim = !compressor.option("keep_fargs");
@@ -1980,9 +2007,13 @@ merge(Compressor.prototype, {
var def = node.definitions.filter(function(def){
if (def.value) def.value = def.value.transform(tt);
if (def.name instanceof AST_Destructuring) return true;
if (def.name.definition().id in in_use_ids) return true;
if (!drop_vars && def.name.definition().global) return true;
var sym = def.name.definition();
if (sym.id in in_use_ids) return true;
if (!drop_vars && sym.global) return true;
if (sym.orig[0] instanceof AST_SymbolCatch) {
def.value = def.value && def.value.drop_side_effect_free(compressor);
return true;
}
var w = {
name : def.name.name,
file : def.name.start.file,
@@ -2069,6 +2100,9 @@ merge(Compressor.prototype, {
return in_list ? MAP.splice(body) : make_node(AST_BlockStatement, node, {
body: body
});
} else if (is_empty(node.init)) {
node.init = null;
return node;
}
}
if (node instanceof AST_BlockStatement) {
@@ -2247,7 +2281,7 @@ merge(Compressor.prototype, {
if (this.expression instanceof AST_Function
&& (!this.expression.name || !this.expression.name.definition().references.length)) {
var node = this.clone();
node.expression = node.expression.process_expression(false);
node.expression = node.expression.process_expression(false, compressor);
return node;
}
return this;
@@ -2266,6 +2300,7 @@ merge(Compressor.prototype, {
switch (this.operator) {
case "&&":
case "||":
if (right === this.right) return this;
var node = this.clone();
node.right = right;
return node;
@@ -2299,26 +2334,19 @@ merge(Compressor.prototype, {
return node;
});
def(AST_Unary, function(compressor, first_in_statement){
switch (this.operator) {
case "delete":
case "++":
case "--":
return this;
case "typeof":
if (this.expression instanceof AST_SymbolRef) return null;
default:
var expression = this.expression.drop_side_effect_free(compressor, first_in_statement);
if (first_in_statement
&& this instanceof AST_UnaryPrefix
&& is_iife_call(expression)) {
if (expression === this.expression && this.operator.length === 1) return this;
return make_node(AST_UnaryPrefix, this, {
operator: this.operator.length === 1 ? this.operator : "!",
expression: expression
});
}
return expression;
if (unary_side_effects(this.operator)) return this;
if (this.operator == "typeof" && this.expression instanceof AST_SymbolRef) return null;
var expression = this.expression.drop_side_effect_free(compressor, first_in_statement);
if (first_in_statement
&& this instanceof AST_UnaryPrefix
&& is_iife_call(expression)) {
if (expression === this.expression && this.operator.length === 1) return this;
return make_node(AST_UnaryPrefix, this, {
operator: this.operator.length === 1 ? this.operator : "!",
expression: expression
});
}
return expression;
});
def(AST_SymbolRef, function() {
return this.undeclared() ? this : null;
@@ -2526,8 +2554,8 @@ merge(Compressor.prototype, {
return make_node(AST_SimpleStatement, self, {
body: make_node(AST_Conditional, self, {
condition : self.condition,
consequent : statement_to_expression(self.body),
alternative : statement_to_expression(self.alternative)
consequent : self.body.body,
alternative : self.alternative.body
})
}).optimize(compressor);
}
@@ -2543,25 +2571,24 @@ merge(Compressor.prototype, {
body: make_node(AST_Binary, self, {
operator : "||",
left : negated,
right : statement_to_expression(self.body)
right : self.body.body
})
}).optimize(compressor);
return make_node(AST_SimpleStatement, self, {
body: make_node(AST_Binary, self, {
operator : "&&",
left : self.condition,
right : statement_to_expression(self.body)
right : self.body.body
})
}).optimize(compressor);
}
if (self.body instanceof AST_EmptyStatement
&& self.alternative
&& self.alternative instanceof AST_SimpleStatement) {
return make_node(AST_SimpleStatement, self, {
body: make_node(AST_Binary, self, {
operator : "||",
left : self.condition,
right : statement_to_expression(self.alternative)
right : self.alternative.body
})
}).optimize(compressor);
}
@@ -2611,107 +2638,105 @@ merge(Compressor.prototype, {
});
OPT(AST_Switch, function(self, compressor){
if (self.body.length == 0 && compressor.option("conditionals")) {
return make_node(AST_SimpleStatement, self, {
body: self.expression
}).transform(compressor);
var branch;
var value = self.expression.evaluate(compressor);
if (value !== self.expression) {
var expression = make_node_from_constant(value, self.expression).transform(compressor);
self.expression = best_of_expression(expression, self.expression);
}
for(;;) {
var last_branch = self.body[self.body.length - 1];
if (last_branch) {
var stat = last_branch.body[last_branch.body.length - 1]; // last statement
if (stat instanceof AST_Break && loop_body(compressor.loopcontrol_target(stat.label)) === self)
last_branch.body.pop();
if (last_branch instanceof AST_Default && last_branch.body.length == 0) {
self.body.pop();
if (!compressor.option("dead_code")) return self;
var decl = [];
var body = [];
var default_branch;
var exact_match;
var fallthrough;
for (var i = 0, len = self.body.length; i < len && !exact_match; i++) {
branch = self.body[i];
if (branch instanceof AST_Default) {
if (!default_branch) default_branch = branch;
else if (!fallthrough) {
extract_declarations_from_unreachable_code(compressor, branch, decl);
continue;
}
} else if (value !== self.expression) {
var exp = branch.expression.evaluate(compressor);
if (exp === value) {
exact_match = branch;
if (default_branch) {
body.splice(body.indexOf(default_branch), 1);
extract_declarations_from_unreachable_code(compressor, default_branch, decl);
default_branch = null;
}
} else if (exp !== branch.expression && !fallthrough) {
extract_declarations_from_unreachable_code(compressor, branch, decl);
continue;
}
}
break;
if (aborts(branch)) {
if (body.length > 0 && !fallthrough) {
var prev = body[body.length - 1];
if (prev.body.length == branch.body.length
&& make_node(AST_BlockStatement, prev, prev).equivalent_to(make_node(AST_BlockStatement, branch, branch)))
prev.body = [];
}
body.push(branch);
fallthrough = false;
} else {
body.push(branch);
fallthrough = true;
}
}
var value = self.expression.evaluate(compressor);
out: if (value !== self.expression) try {
// constant expression
var expression = make_node_from_constant(value, self.expression);
self.expression = best_of_expression(expression, self.expression);
if (!compressor.option("dead_code")) break out;
var in_if = false;
var in_block = false;
var started = false;
var stopped = false;
var ruined = false;
var tt = new TreeTransformer(function(node, descend, in_list){
if (node instanceof AST_Lambda || node instanceof AST_SimpleStatement) {
// no need to descend these node types
return node;
}
else if (node instanceof AST_Switch && node === self) {
node = node.clone();
descend(node, this);
return ruined ? node : make_node(AST_BlockStatement, node, {
body: node.body.reduce(function(a, branch){
return a.concat(branch.body);
}, [])
}).transform(compressor);
}
else if (node instanceof AST_If || node instanceof AST_Try) {
var save = in_if;
in_if = !in_block;
descend(node, this);
in_if = save;
return node;
}
else if (node instanceof AST_StatementWithBody || node instanceof AST_Switch) {
var save = in_block;
in_block = true;
descend(node, this);
in_block = save;
return node;
}
else if (node instanceof AST_Break && this.loopcontrol_target(node.label) === self) {
if (in_if) {
ruined = true;
return node;
}
if (in_block) return node;
stopped = true;
return in_list ? MAP.skip : make_node(AST_EmptyStatement, node);
}
else if (node instanceof AST_SwitchBranch && this.parent() === self) {
if (stopped) return MAP.skip;
if (node instanceof AST_Case) {
var exp = node.expression.evaluate(compressor);
if (exp === node.expression) {
// got a case with non-constant expression, baling out
throw self;
}
if (exp === value || started) {
started = true;
if (aborts(node)) stopped = true;
descend(node, this);
return node;
}
return MAP.skip;
}
descend(node, this);
return node;
}
for (; i < len && fallthrough; i++) {
branch = self.body[i];
exact_match.body = exact_match.body.concat(branch.body);
fallthrough = !aborts(exact_match);
}
while (i < len) extract_declarations_from_unreachable_code(compressor, self.body[i++], decl);
if (body.length > 0) {
body[0].body = decl.concat(body[0].body);
}
self.body = body;
while (branch = body[body.length - 1]) {
var stat = branch.body[branch.body.length - 1];
if (stat instanceof AST_Break && compressor.loopcontrol_target(stat.label) === self)
branch.body.pop();
if (branch.body.length || branch instanceof AST_Case
&& (default_branch || branch.expression.has_side_effects(compressor))) break;
if (body.pop() === default_branch) default_branch = null;
}
if (body.length == 0) {
return make_node(AST_BlockStatement, self, {
body: decl.concat(make_node(AST_SimpleStatement, self.expression, {
body: self.expression
}))
}).optimize(compressor);
}
if (body.length == 1 && (body[0] === exact_match || body[0] === default_branch)) {
var has_break = false;
var tw = new TreeWalker(function(node) {
if (has_break
|| node instanceof AST_Lambda
|| node instanceof AST_SimpleStatement) return true;
if (node instanceof AST_Break && tw.loopcontrol_target(node.label) === self)
has_break = true;
});
tt.stack = compressor.stack.slice(); // so that's able to see parent nodes
self = self.transform(tt);
} catch(ex) {
if (ex !== self) throw ex;
self.walk(tw);
if (!has_break) return make_node(AST_BlockStatement, self, body[0]).optimize(compressor);
}
return self;
});
OPT(AST_Case, function(self, compressor){
self.body = tighten_body(self.body, compressor);
return self;
});
OPT(AST_Try, function(self, compressor){
self.body = tighten_body(self.body, compressor);
if (self.bcatch && self.bfinally && all(self.bfinally.body, is_empty)) self.bfinally = null;
if (all(self.body, is_empty)) {
var body = [];
if (self.bcatch) extract_declarations_from_unreachable_code(compressor, self.bcatch, body);
if (self.bfinally) body = body.concat(self.bfinally.body);
return body.length > 0 ? make_node(AST_BlockStatement, self, {
body: body
}).optimize(compressor) : make_node(AST_EmptyStatement, self);
}
return self;
});
@@ -2997,11 +3022,9 @@ merge(Compressor.prototype, {
return AST_Seq.from_array(args).transform(compressor);
}
}
if (compressor.option("side_effects")) {
if (!AST_Block.prototype.has_side_effects.call(exp, compressor)) {
var args = self.args.concat(make_node(AST_Undefined, self));
return AST_Seq.from_array(args).transform(compressor);
}
if (compressor.option("side_effects") && all(exp.body, is_empty)) {
var args = self.args.concat(make_node(AST_Undefined, self));
return AST_Seq.from_array(args).transform(compressor);
}
}
if (compressor.option("drop_console")) {
@@ -3056,7 +3079,9 @@ merge(Compressor.prototype, {
&& (self.car.operator == "++" || self.car.operator == "--")) {
left = self.car.expression;
}
if (left) {
if (left
&& !(left instanceof AST_SymbolRef
&& left.definition().orig[0] instanceof AST_SymbolLambda)) {
var parent, field;
var cdr = self.cdr;
while (true) {
@@ -3072,9 +3097,14 @@ merge(Compressor.prototype, {
return car;
}
if (cdr instanceof AST_Binary && !(cdr instanceof AST_Assign)) {
field = cdr.left.is_constant() ? "right" : "left";
if (cdr.left.is_constant()) {
if (cdr.operator == "||" || cdr.operator == "&&") break;
field = "right";
} else {
field = "left";
}
} else if (cdr instanceof AST_Call
|| cdr instanceof AST_Unary && cdr.operator != "++" && cdr.operator != "--") {
|| cdr instanceof AST_Unary && !unary_side_effects(cdr.operator)) {
field = "expression";
} else break;
parent = cdr;
@@ -3139,14 +3169,23 @@ merge(Compressor.prototype, {
// typeof always returns a non-empty string, thus it's
// always true in booleans
compressor.warn("Boolean expression always true [{file}:{line},{col}]", self.start);
return make_node(AST_Seq, self, {
return (e instanceof AST_SymbolRef ? make_node(AST_True, self) : make_node(AST_Seq, self, {
car: e,
cdr: make_node(AST_True, self)
}).optimize(compressor);
})).optimize(compressor);
}
}
if (e instanceof AST_Binary
&& (self.operator == "+" || self.operator == "-")
&& (e.operator == "*" || e.operator == "/" || e.operator == "%")) {
self.expression = e.left;
e.left = self;
return e.optimize(compressor);
}
// avoids infinite recursion of numerals
if (self.operator != "-" || !(self.expression instanceof AST_Number)) {
if (self.operator != "-"
|| !(self.expression instanceof AST_Number
|| self.expression instanceof AST_Infinity)) {
var ev = self.evaluate(compressor);
if (ev !== self) {
ev = make_node_from_constant(ev, self).optimize(compressor);
@@ -3424,6 +3463,7 @@ merge(Compressor.prototype, {
left: self.left,
right: self.right.expression
});
break;
}
// -a + b => b - a
if (self.left instanceof AST_UnaryPrefix
@@ -3435,6 +3475,7 @@ merge(Compressor.prototype, {
left: self.right,
right: self.left.expression
});
break;
}
case "*":
associative = compressor.option("unsafe_math");
@@ -3576,9 +3617,9 @@ merge(Compressor.prototype, {
case "undefined":
return make_node(AST_Undefined, self).optimize(compressor);
case "NaN":
return make_node(AST_NaN, self).optimize(compressor);
return make_node(AST_NaN, self);
case "Infinity":
return make_node(AST_Infinity, self).optimize(compressor);
return make_node(AST_Infinity, self);
}
}
if (compressor.option("evaluate") && compressor.option("reduce_vars")) {
@@ -3606,14 +3647,6 @@ merge(Compressor.prototype, {
return self;
});
OPT(AST_Infinity, function (self, compressor) {
return make_node(AST_Binary, self, {
operator : '/',
left : make_node(AST_Number, self, {value: 1}),
right : make_node(AST_Number, self, {value: 0})
});
});
OPT(AST_Undefined, function(self, compressor){
if (compressor.option("unsafe")) {
var scope = compressor.find_parent(AST_Scope);
@@ -3698,21 +3731,30 @@ merge(Compressor.prototype, {
alternative: self.consequent
});
}
var condition = self.condition;
var consequent = self.consequent;
var alternative = self.alternative;
// x?x:y --> x||y
if (condition instanceof AST_SymbolRef
&& consequent instanceof AST_SymbolRef
&& condition.definition() === consequent.definition()) {
return make_node(AST_Binary, self, {
operator: "||",
left: condition,
right: alternative
});
}
// if (foo) exp = something; else exp = something_else;
// |
// v
// exp = foo ? something : something_else;
if (consequent instanceof AST_Assign
&& alternative instanceof AST_Assign
&& consequent.operator == alternative.operator
&& consequent.left.equivalent_to(alternative.left)
&& (!consequent.left.has_side_effects(compressor)
|| !self.condition.has_side_effects(compressor))
) {
/*
* Stuff like this:
* if (foo) exp = something; else exp = something_else;
* ==>
* exp = foo ? something : something_else;
*/
&& (!self.condition.has_side_effects(compressor)
|| consequent.operator == "="
&& !consequent.left.has_side_effects(compressor))) {
return make_node(AST_Assign, self, {
operator: consequent.operator,
left: consequent.left,
@@ -3943,6 +3985,14 @@ merge(Compressor.prototype, {
return self;
});
OPT(AST_Arrow, function(self, compressor){
if (self.body.length === 1 && self.body[0] instanceof AST_Return) {
var value = self.body[0].value;
self.body = value ? value : [];
}
return self;
});
OPT(AST_Class, function(self, compressor){
// HACK to avoid compress failure.
// AST_Class is not really an AST_Scope/AST_Block as it lacks a body.

View File

@@ -652,6 +652,15 @@ function OutputStream(options) {
&& this.operator !== "--";
});
PARENS([ AST_Infinity, AST_NaN ], function(output){
var p = output.parent();
return p instanceof AST_PropAccess && p.expression === this
|| p instanceof AST_Call && p.expression === this
|| p instanceof AST_Unary && p.operator != "+" && p.operator != "-"
|| p instanceof AST_Binary && p.right === this
&& (p.operator == "/" || p.operator == "%");
});
PARENS(AST_Seq, function(output){
var p = output.parent();
return p instanceof AST_Call // (foo, bar)() or foo(1, (2, 3), 4)
@@ -904,7 +913,7 @@ function OutputStream(options) {
output.print("for");
output.space();
output.with_parens(function(){
if (self.init && !(self.init instanceof AST_EmptyStatement)) {
if (self.init) {
if (self.init instanceof AST_Definitions) {
self.init.print(output);
} else {
@@ -1139,24 +1148,24 @@ function OutputStream(options) {
self.expression.print(output);
});
output.space();
if (self.body.length > 0) output.with_block(function(){
self.body.forEach(function(stmt, i){
if (i) output.newline();
var last = self.body.length - 1;
if (last < 0) output.print("{}");
else output.with_block(function(){
self.body.forEach(function(branch, i){
output.indent(true);
stmt.print(output);
branch.print(output);
if (i < last && branch.body.length > 0)
output.newline();
});
});
else output.print("{}");
});
AST_SwitchBranch.DEFMETHOD("_do_print_body", function(output){
if (this.body.length > 0) {
output.newline();
this.body.forEach(function(stmt){
output.indent();
stmt.print(output);
output.newline();
this.body.forEach(function(stmt){
output.indent();
stmt.print(output);
output.newline();
});
}
});
});
DEFPRINT(AST_Default, function(self, output){
output.print("default:");
@@ -1277,11 +1286,37 @@ function OutputStream(options) {
output.print("default");
output.space();
}
if (self.exported_value) {
if (self.exported_names) {
output.space();
if (self.exported_names.length === 1 && self.exported_names[0].name.name === "*") {
self.exported_names[0].print(output);
} else {
output.print("{");
self.exported_names.forEach(function (name_import, i) {
output.space();
name_import.print(output);
if (i < self.exported_names.length - 1) {
output.print(",");
output.space();
}
});
output.space();
output.print("}");
}
output.space();
}
else if (self.exported_value) {
self.exported_value.print(output);
} else if (self.exported_definition) {
self.exported_definition.print(output);
}
if (self.module_name) {
output.space();
output.print("from");
output.space();
self.module_name.print(output);
}
output.semicolon();
});
@@ -1591,10 +1626,18 @@ function OutputStream(options) {
});
DEFPRINT(AST_Hole, noop);
DEFPRINT(AST_Infinity, function(self, output){
output.print("Infinity");
output.print("1");
output.space();
output.print("/");
output.space();
output.print("0");
});
DEFPRINT(AST_NaN, function(self, output){
output.print("NaN");
output.print("0");
output.space();
output.print("/");
output.space();
output.print("0");
});
DEFPRINT(AST_This, function(self, output){
output.print("this");

View File

@@ -852,6 +852,7 @@ function parse($TEXT, options) {
html5_comments : true,
bare_returns : false,
shebang : true,
cli : false,
});
var S = {
@@ -2236,17 +2237,85 @@ function parse($TEXT, options) {
})
}
function import_nameAsterisk() {
var start = S.token;
var foreign_name;
var name;
next();
var end = prev();
name = new AST_SymbolImport({
name: '*',
start: start,
end: end,
});
foreign_name = new AST_SymbolImportForeign({
name: '*',
start: start,
end: end,
});
return new AST_NameImport({
start: start,
foreign_name: foreign_name,
name: name,
end: end,
})
}
function export_() {
var start = S.token;
var is_default;
var exported_value;
var exported_definition;
var exported_names;
if (is("keyword", "default")) {
is_default = true;
next();
}
if (is("punc", "{")) {
next();
exported_names = [];
while (!is("punc", "}")) {
exported_names.push(import_name());
if (is("punc", ",")) {
next();
}
}
next();
} else if (is("operator", "*")) {
var st = prev();
exported_names = [import_nameAsterisk()];
}
if (exported_names) {
expect_token("name", "from");
var mod_str = S.token;
if (mod_str.type !== 'string') {
unexpected();
}
next();
return new AST_Export({
start: start,
is_default: is_default,
exported_names: exported_names,
module_name: new AST_String({
start: mod_str,
value: mod_str.value,
quote: mod_str.quote,
end: mod_str,
}),
end: prev(),
});
}
var is_definition =
is("keyword", "var") || is("keyword", "let") || is("keyword", "const") ||
is("keyword", "class") || is("keyword", "function");
@@ -2418,7 +2487,7 @@ function parse($TEXT, options) {
function make_unary(ctor, op, expr) {
if ((op == "++" || op == "--") && !is_assignable(expr))
croak("Invalid use of " + op + " operator");
croak("Invalid use of " + op + " operator", null, ctor === AST_UnaryPrefix ? expr.start.col - 1 : null);
return new ctor({ operator: op, expression: expr });
};
@@ -2467,6 +2536,7 @@ function parse($TEXT, options) {
};
function is_assignable(expr) {
if (options.cli) return true;
return expr instanceof AST_PropAccess || expr instanceof AST_SymbolRef;
};

View File

@@ -83,9 +83,14 @@ SymbolDef.prototype = {
}
else if (!this.mangled_name && !this.unmangleable(options)) {
var s = this.scope;
if (!options.screw_ie8 && this.orig[0] instanceof AST_SymbolLambda)
var sym = this.orig[0];
if (!options.screw_ie8 && sym instanceof AST_SymbolLambda)
s = s.parent_scope;
this.mangled_name = s.next_mangled(options, this);
var def;
if (this.defun && (def = this.defun.variables.get(this.name))) {
this.mangled_name = def.mangled_name || def.name;
} else
this.mangled_name = s.next_mangled(options, this);
if (this.global && cache) {
cache.set(this.name, this.mangled_name);
}
@@ -111,8 +116,7 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){
if (node.is_block_scope()) {
var save_scope = scope;
scope = new AST_Scope(node);
scope.init_scope_vars();
scope.parent_scope = save_scope;
scope.init_scope_vars(save_scope);
if (!(node instanceof AST_Scope)) {
scope.uses_with = save_scope.uses_with;
scope.uses_eval = save_scope.uses_eval;
@@ -129,8 +133,8 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){
return true;
}
if (node instanceof AST_Scope) {
node.init_scope_vars();
var save_scope = node.parent_scope = scope;
node.init_scope_vars(scope);
var save_scope = scope;
var save_defun = defun;
var save_labels = labels;
defun = scope = node;
@@ -213,9 +217,17 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){
|| node instanceof AST_SymbolConst) {
var def = ((node instanceof AST_SymbolBlockDeclaration) ? scope : defun).def_variable(node, in_export, in_block);
def.destructuring = in_destructuring;
if (defun !== scope) {
node.mark_enclosed(options);
var def = scope.find_variable(node);
if (node.thedef !== def) {
node.thedef = def;
node.reference(options);
}
}
}
else if (node instanceof AST_SymbolCatch) {
scope.def_variable(node, in_export, in_block);
scope.def_variable(node, in_export, in_block).defun = defun;
}
else if (node instanceof AST_LabelRef) {
var sym = labels.get(node.name);
@@ -279,7 +291,7 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){
if (node instanceof AST_SymbolCatch) {
var name = node.name;
var refs = node.thedef.references;
var scope = node.thedef.scope.parent_scope.parent_scope;
var scope = node.thedef.defun;
var def = scope.find_variable(name) || self.globals.get(name) || scope.def_variable(node);
refs.forEach(function(ref) {
ref.thedef = def;
@@ -309,14 +321,14 @@ AST_Toplevel.DEFMETHOD("def_global", function(node){
}
});
AST_Scope.DEFMETHOD("init_scope_vars", function(){
this.variables = new Dictionary(); // map name to AST_SymbolVar (variables defined in this scope; includes functions)
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 = null; // 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_Scope.DEFMETHOD("init_scope_vars", function(parent_scope){
this.variables = new Dictionary(); // map name to AST_SymbolVar (variables defined in this scope; includes functions)
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_Node.DEFMETHOD("is_block_scope", function(){
@@ -339,15 +351,15 @@ AST_IterationStatement.DEFMETHOD("is_block_scope", function(){
AST_Lambda.DEFMETHOD("init_scope_vars", function(){
AST_Scope.prototype.init_scope_vars.apply(this, arguments);
this.uses_arguments = false;
var symbol = new AST_VarDef({ name: "arguments", start: this.start, end: this.end });
var def = new SymbolDef(this, this.variables.size(), symbol);
this.variables.set(symbol.name, def);
this.def_variable(new AST_SymbolVar({
name: "arguments",
start: this.start,
end: this.end
}));
});
AST_SymbolRef.DEFMETHOD("reference", function(options) {
AST_Symbol.DEFMETHOD("mark_enclosed", function(options) {
var def = this.definition();
def.references.push(this);
var s = this.scope;
while (s) {
push_uniq(s.enclosed, def);
@@ -361,6 +373,11 @@ AST_SymbolRef.DEFMETHOD("reference", function(options) {
}
});
AST_Symbol.DEFMETHOD("reference", function(options) {
this.definition().references.push(this);
this.mark_enclosed(options);
});
AST_Scope.DEFMETHOD("find_variable", function(name){
if (name instanceof AST_Symbol) name = name.name;
return this.variables.get(name)

View File

@@ -4,7 +4,7 @@
"homepage": "http://lisperator.net/uglifyjs",
"author": "Mihai Bazon <mihai.bazon@gmail.com> (http://lisperator.net/)",
"license": "BSD-2-Clause",
"version": "2.8.15",
"version": "2.8.19",
"engines": {
"node": ">=0.8.0"
},

View File

@@ -894,7 +894,8 @@ collapse_vars_unary: {
}
expect: {
function f0(o, p) {
delete o[p];
var x = o[p];
delete x;
}
function f1(n) {
return n > +!!n
@@ -1621,3 +1622,71 @@ issue_1631_3: {
}
expect_stdout: "6"
}
var_side_effects_1: {
options = {
collapse_vars: true,
}
input: {
var print = console.log.bind(console);
function foo(x) {
var twice = x * 2;
print('Foo:', twice);
}
foo(10);
}
expect: {
var print = console.log.bind(console);
function foo(x) {
print('Foo:', 2 * x);
}
foo(10);
}
expect_stdout: true
}
var_side_effects_2: {
options = {
collapse_vars: true,
}
input: {
var print = console.log.bind(console);
function foo(x) {
var twice = x.y * 2;
print('Foo:', twice);
}
foo({ y: 10 });
}
expect: {
var print = console.log.bind(console);
function foo(x) {
var twice = 2 * x.y;
print('Foo:', twice);
}
foo({ y: 10 });
}
expect_stdout: true
}
var_side_effects_3: {
options = {
collapse_vars: true,
pure_getters: true,
}
input: {
var print = console.log.bind(console);
function foo(x) {
var twice = x.y * 2;
print('Foo:', twice);
}
foo({ y: 10 });
}
expect: {
var print = console.log.bind(console);
function foo(x) {
print('Foo:', 2 * x.y);
}
foo({ y: 10 });
}
expect_stdout: true
}

View File

@@ -840,8 +840,8 @@ equality_conditionals_false: {
f(0, true, 0),
f(1, 2, 3),
f(1, null, 3),
f(NaN),
f(NaN, "foo");
f(0/0),
f(0/0, "foo");
}
expect_stdout: true
}
@@ -888,8 +888,77 @@ equality_conditionals_true: {
f(0, true, 0),
f(1, 2, 3),
f(1, null, 3),
f(NaN),
f(NaN, "foo");
f(0/0),
f(0/0, "foo");
}
expect_stdout: true
}
issue_1645_1: {
options = {
conditionals: true,
}
input: {
var a = 100, b = 10;
(b = a) ? a++ + (b += a) ? b += a : b += a : b ^= a;
console.log(a, b);
}
expect: {
var a = 100, b = 10;
(b = a) ? (a++ + (b += a), b += a) : b ^= a;
console.log(a,b);
}
expect_stdout: true
}
issue_1645_2: {
options = {
conditionals: true,
}
input: {
var a = 0;
function f() {
return a++;
}
f() ? a += 2 : a += 4;
console.log(a);
}
expect: {
var a = 0;
function f(){
return a++;
}
f() ? a += 2 : a += 4;
console.log(a);
}
expect_stdout: true
}
condition_symbol_matches_consequent: {
options = {
conditionals: true,
}
input: {
function foo(x, y) {
return x ? x : y;
}
function bar() {
return g ? g : h;
}
var g = 4;
var h = 5;
console.log(foo(3, null), foo(0, 7), foo(true, false), bar());
}
expect: {
function foo(x, y) {
return x || y;
}
function bar() {
return g || h;
}
var g = 4;
var h = 5;
console.log(foo(3, null), foo(0, 7), foo(true, false), bar());
}
expect_stdout: "3 7 true 4"
}

View File

@@ -236,3 +236,45 @@ dead_code_const_annotation_complex_scope: {
}
expect_stdout: true
}
try_catch_finally: {
options = {
conditionals: true,
dead_code: true,
evaluate: true,
}
input: {
var a = 1;
!function() {
try {
if (false) throw x;
} catch (a) {
var a = 2;
console.log("FAIL");
} finally {
a = 3;
console.log("PASS");
}
}();
try {
console.log(a);
} finally {
}
}
expect: {
var a = 1;
!function() {
var a;
a = 3;
console.log("PASS");
}();
try {
console.log(a);
} finally {
}
}
expect_stdout: [
"PASS",
"1",
]
}

View File

@@ -872,3 +872,186 @@ issue_1583: {
}
}
}
issue_1656: {
options = {
toplevel: true,
unused: true,
}
beautify = {
beautify: true,
}
input: {
for(var a=0;;);
}
expect_exact: "for (;;) ;"
}
issue_1709: {
options = {
unused: true,
}
input: {
console.log(
function x() {
var x = 1;
return x;
}(),
function y() {
const y = 2;
return y;
}(),
function z() {
function z() {}
return z;
}()
);
}
expect: {
console.log(
function() {
var x = 1;
return x;
}(),
function() {
const y = 2;
return y;
}(),
function() {
function z() {}
return z;
}()
);
}
expect_stdout: true
}
issue_1715_1: {
options = {
unused: true,
}
input: {
var a = 1;
function f() {
a++;
try {
x();
} catch (a) {
var a;
}
}
f();
console.log(a);
}
expect: {
var a = 1;
function f() {
a++;
try {
x();
} catch (a) {
var a;
}
}
f();
console.log(a);
}
expect_stdout: "1"
}
issue_1715_2: {
options = {
unused: true,
}
input: {
var a = 1;
function f() {
a++;
try {
x();
} catch (a) {
var a = 2;
}
}
f();
console.log(a);
}
expect: {
var a = 1;
function f() {
a++;
try {
x();
} catch (a) {
var a;
}
}
f();
console.log(a);
}
expect_stdout: "1"
}
issue_1715_3: {
options = {
unused: true,
}
input: {
var a = 1;
function f() {
a++;
try {
console;
} catch (a) {
var a = 2 + x();
}
}
f();
console.log(a);
}
expect: {
var a = 1;
function f() {
a++;
try {
console;
} catch (a) {
var a = x();
}
}
f();
console.log(a);
}
expect_stdout: "1"
}
issue_1715_4: {
options = {
unused: true,
}
input: {
var a = 1;
!function a() {
a++;
try {
x();
} catch (a) {
var a;
}
}();
console.log(a);
}
expect: {
var a = 1;
!function() {
a++;
try {
x();
} catch (a) {
var a;
}
}();
console.log(a);
}
expect_stdout: "1"
}

View File

@@ -52,7 +52,7 @@ and: {
a = 7;
a = false;
a = NaN;
a = 0/0;
a = 0;
a = void 0;
a = null;
@@ -67,7 +67,7 @@ and: {
a = 6 << condition && -4.5;
a = condition && false;
a = console.log("b") && NaN;
a = console.log("b") && 0/0;
a = console.log("c") && 0;
a = 2 * condition && void 0;
a = condition + 3 && null;
@@ -149,7 +149,7 @@ or: {
a = 6 << condition || -4.5;
a = condition || false;
a = console.log("b") || NaN;
a = console.log("b") || 0/0;
a = console.log("c") || 0;
a = 2 * condition || void 0;
a = condition + 3 || null;
@@ -196,8 +196,8 @@ negative_zero: {
console.log(
-0,
0,
1 / (-0),
1 / (-0)
-1/0,
-1/0
);
}
expect_stdout: true
@@ -217,8 +217,8 @@ positive_zero: {
console.log(
0,
-0,
1 / (0),
1 / (0)
1/0,
1/0
);
}
expect_stdout: true
@@ -302,15 +302,15 @@ pow_with_number_constants: {
var m = 3 ** -10; // Result will be 0.000016935087808430286, which is too long
}
expect: {
var a = NaN;
var a = 0/0;
var b = 1;
var c = 1;
var d = NaN;
var e = Infinity;
var d = 0/0;
var e = 1/0;
var f = 0;
var g = NaN;
var h = Infinity;
var i = -Infinity;
var g = 0/0;
var h = 1/0;
var i = -1/0;
var j = .125;
var k = .125;
var l = .25;
@@ -627,7 +627,7 @@ unsafe_array: {
[1, 2, 3, a][0] + 1,
2,
3,
NaN,
0/0,
"1,21",
5,
(void 0)[1] + 1
@@ -883,3 +883,16 @@ unsafe_charAt_noop: {
);
}
}
issue_1649: {
options = {
evaluate: true,
}
input: {
console.log(-1 + -1);
}
expect: {
console.log(-2);
}
expect_stdout: "-2";
}

View File

@@ -21,12 +21,13 @@ pow_with_number_constants: {
var f = 2 ** -Infinity;
}
expect: {
var a = 5 ** NaN;
// TODO: may need parentheses
var a = 5 ** 0/0;
var b = 42 ** +0;
var c = 42 ** -0;
var d = NaN ** 1;
var e = 2 ** (1/0);
var f = 2 ** -(1/0);
var d = 0/0 ** 1;
var e = 2 ** 1/0;
var f = 2 ** -1/0;
}
}

View File

@@ -199,6 +199,15 @@ export_statement: {
expect_exact: "export default 1;export var foo=4;export let foo=6;export const foo=6;export function foo(){};export class foo{};"
}
export_module_statement: {
input: {
export * from "a.js";
export {A} from "a.js";
export {A, B} from "a.js";
}
expect_exact: 'export*from"a.js";export{A}from"a.js";export{A,B}from"a.js";'
}
import_statement_mangling: {
mangle = { toplevel: true };
input: {

View File

@@ -195,11 +195,12 @@ assorted_Infinity_NaN_undefined_in_with_scope: {
sequences: false,
}
input: {
var f = console.log;
var o = {
undefined : 3,
NaN : 4,
Infinity : 5,
}
};
if (o) {
f(undefined, void 0);
f(NaN, 0/0);
@@ -216,25 +217,25 @@ assorted_Infinity_NaN_undefined_in_with_scope: {
}
}
expect: {
var o = {
var f = console.log, o = {
undefined : 3,
NaN : 4,
Infinity : 5
}
};
if (o) {
f(void 0, void 0);
f(NaN, NaN);
f(0/0, 0/0);
f(1/0, 1/0);
f(-(1/0), -(1/0));
f(NaN, NaN);
f(-1/0, -1/0);
f(0/0, 0/0);
}
with (o) {
f(undefined, void 0);
f(NaN, 0/0);
f(Infinity, 1/0);
f(-Infinity, -(1/0));
f(-Infinity, -1/0);
f(9 + undefined, 9 + void 0);
}
}
expect_stdout: true
}

View File

@@ -0,0 +1,88 @@
issue_1639_1: {
options = {
booleans: true,
cascade: true,
conditionals: true,
evaluate: true,
join_vars: true,
loops: true,
sequences: true,
side_effects: true,
}
input: {
var a = 100, b = 10;
var L1 = 5;
while (--L1 > 0) {
if ((--b), false) {
if (b) {
var ignore = 0;
}
}
}
console.log(a, b);
}
expect: {
for (var a = 100, b = 10, L1 = 5; --L1 > 0;)
if (--b, !1) var ignore = 0;
console.log(a, b);
}
expect_stdout: true
}
issue_1639_2: {
options = {
booleans: true,
cascade: true,
conditionals: true,
evaluate: true,
join_vars: true,
sequences: true,
side_effects: true,
}
input: {
var a = 100, b = 10;
function f19() {
if (++a, false)
if (a)
if (++a);
}
f19();
console.log(a, b);
}
expect: {
var a = 100, b = 10;
function f19() {
++a, 1;
}
f19(),
console.log(a, b);
}
expect_stdout: true
}
issue_1639_3: {
options = {
booleans: true,
cascade: true,
conditionals: true,
evaluate: true,
sequences: true,
side_effects: true,
}
input: {
var a = 100, b = 10;
a++ && false && a ? 0 : 0;
console.log(a, b);
}
expect: {
var a = 100, b = 10;
a++,
console.log(a, b);
}
expect_stdout: true
}

View File

@@ -0,0 +1,45 @@
f7: {
options = {
booleans: true,
cascade: true,
collapse_vars: true,
comparisons: true,
conditionals: true,
dead_code: true,
drop_debugger: true,
evaluate: true,
hoist_funs: true,
if_return: true,
join_vars: true,
loops: true,
negate_iife: true,
passes: 3,
properties: true,
reduce_vars: true,
sequences: true,
side_effects: true,
toplevel: true,
unused: true,
}
beautify = {
beautify: true,
}
input: {
var a = 100, b = 10;
function f22464() {
var brake146670 = 5;
while (((b = a) ? !a : ~a ? null : b += a) && --brake146670 > 0) {
}
}
f22464();
console.log(a, b);
}
expect_exact: [
"var a = 100, b = 10;",
"",
"!function() {",
" for (;b = a, !1; ) ;",
"}(), console.log(a, b);",
]
expect_stdout: true
}

161
test/compress/issue-1673.js Normal file
View File

@@ -0,0 +1,161 @@
side_effects_catch: {
options = {
reduce_vars: true,
side_effects: true,
unused: true,
}
input: {
function f() {
function g() {
try {
throw 0;
} catch (e) {
console.log("PASS");
}
}
g();
}
f();
}
expect: {
function f() {
(function() {
try {
throw 0;
} catch (e) {
console.log("PASS");
}
})();
}
f();
}
expect_stdout: "PASS"
}
side_effects_else: {
options = {
reduce_vars: true,
side_effects: true,
unused: true,
}
input: {
function f(x) {
function g() {
if (x);
else console.log("PASS");
}
g();
}
f(0);
}
expect: {
function f(x) {
(function() {
if (x);
else console.log("PASS");
})();
}
f(0);
}
expect_stdout: "PASS"
}
side_effects_finally: {
options = {
reduce_vars: true,
side_effects: true,
unused: true,
}
input: {
function f() {
function g() {
try {
x();
} catch (e) {
} finally {
console.log("PASS");
}
}
g();
}
f();
}
expect: {
function f() {
(function() {
try {
x();
} catch (e) {
} finally {
console.log("PASS");
}
})();
}
f();
}
expect_stdout: "PASS"
}
side_effects_label: {
options = {
reduce_vars: true,
side_effects: true,
unused: true,
}
input: {
function f(x) {
function g() {
L: {
console.log("PASS");
break L;
}
}
g();
}
f(0);
}
expect: {
function f(x) {
(function() {
L: {
console.log("PASS");
break L;
}
})();
}
f(0);
}
expect_stdout: "PASS"
}
side_effects_switch: {
options = {
reduce_vars: true,
side_effects: true,
unused: true,
}
input: {
function f() {
function g() {
switch (0) {
default:
case console.log("PASS"):
}
}
g();
}
f();
}
expect: {
function f() {
(function() {
switch (0) {
default:
case console.log("PASS"):
}
})();
}
f();
}
expect_stdout: "PASS"
}

347
test/compress/issue-1704.js Normal file
View File

@@ -0,0 +1,347 @@
mangle_catch: {
options = {
screw_ie8: true,
toplevel: false,
}
mangle = {
screw_ie8: true,
toplevel: false,
}
input: {
var a = "FAIL";
try {
throw 1;
} catch (args) {
a = "PASS";
}
console.log(a);
}
expect_exact: 'var a="FAIL";try{throw 1}catch(o){a="PASS"}console.log(a);'
expect_stdout: "PASS"
}
mangle_catch_ie8: {
options = {
screw_ie8: false,
toplevel: false,
}
mangle = {
screw_ie8: false,
toplevel: false,
}
input: {
var a = "FAIL";
try {
throw 1;
} catch (args) {
a = "PASS";
}
console.log(a);
}
expect_exact: 'var a="FAIL";try{throw 1}catch(args){a="PASS"}console.log(a);'
expect_stdout: "PASS"
}
mangle_catch_var: {
options = {
screw_ie8: true,
toplevel: false,
}
mangle = {
screw_ie8: true,
toplevel: false,
}
input: {
var a = "FAIL";
try {
throw 1;
} catch (args) {
var a = "PASS";
}
console.log(a);
}
expect_exact: 'var a="FAIL";try{throw 1}catch(o){var a="PASS"}console.log(a);'
expect_stdout: "PASS"
}
mangle_catch_var_ie8: {
options = {
screw_ie8: false,
toplevel: false,
}
mangle = {
screw_ie8: false,
toplevel: false,
}
input: {
var a = "FAIL";
try {
throw 1;
} catch (args) {
var a = "PASS";
}
console.log(a);
}
expect_exact: 'var a="FAIL";try{throw 1}catch(args){var a="PASS"}console.log(a);'
expect_stdout: "PASS"
}
mangle_catch_toplevel: {
options = {
screw_ie8: true,
toplevel: true,
}
mangle = {
screw_ie8: true,
toplevel: true,
}
input: {
var a = "FAIL";
try {
throw 1;
} catch (args) {
a = "PASS";
}
console.log(a);
}
expect_exact: 'var o="FAIL";try{throw 1}catch(c){o="PASS"}console.log(o);'
expect_stdout: "PASS"
}
mangle_catch_ie8_toplevel: {
options = {
screw_ie8: false,
toplevel: true,
}
mangle = {
screw_ie8: false,
toplevel: true,
}
input: {
var a = "FAIL";
try {
throw 1;
} catch (args) {
a = "PASS";
}
console.log(a);
}
expect_exact: 'var o="FAIL";try{throw 1}catch(c){o="PASS"}console.log(o);'
expect_stdout: "PASS"
}
mangle_catch_var_toplevel: {
options = {
screw_ie8: true,
toplevel: true,
}
mangle = {
screw_ie8: true,
toplevel: true,
}
input: {
var a = "FAIL";
try {
throw 1;
} catch (args) {
var a = "PASS";
}
console.log(a);
}
expect_exact: 'var o="FAIL";try{throw 1}catch(r){var o="PASS"}console.log(o);'
expect_stdout: "PASS"
}
mangle_catch_var_ie8_toplevel: {
options = {
screw_ie8: false,
toplevel: true,
}
mangle = {
screw_ie8: false,
toplevel: true,
}
input: {
var a = "FAIL";
try {
throw 1;
} catch (args) {
var a = "PASS";
}
console.log(a);
}
expect_exact: 'var o="FAIL";try{throw 1}catch(r){var o="PASS"}console.log(o);'
expect_stdout: "PASS"
}
mangle_catch_redef_1: {
options = {
screw_ie8: true,
toplevel: false,
}
mangle = {
screw_ie8: true,
toplevel: false,
}
input: {
var a = "PASS";
try {
throw "FAIL1";
} catch (a) {
var a = "FAIL2";
}
console.log(a);
}
expect_exact: 'var a="PASS";try{throw"FAIL1"}catch(a){var a="FAIL2"}console.log(a);'
expect_stdout: "PASS"
}
mangle_catch_redef_1_ie8: {
options = {
screw_ie8: false,
toplevel: false,
}
mangle = {
screw_ie8: false,
toplevel: false,
}
input: {
var a = "PASS";
try {
throw "FAIL1";
} catch (a) {
var a = "FAIL2";
}
console.log(a);
}
expect_exact: 'var a="PASS";try{throw"FAIL1"}catch(a){var a="FAIL2"}console.log(a);'
expect_stdout: "PASS"
}
mangle_catch_redef_1_toplevel: {
options = {
screw_ie8: true,
toplevel: true,
}
mangle = {
screw_ie8: true,
toplevel: true,
}
input: {
var a = "PASS";
try {
throw "FAIL1";
} catch (a) {
var a = "FAIL2";
}
console.log(a);
}
expect_exact: 'var o="PASS";try{throw"FAIL1"}catch(o){var o="FAIL2"}console.log(o);'
expect_stdout: "PASS"
}
mangle_catch_redef_1_ie8_toplevel: {
options = {
screw_ie8: false,
toplevel: true,
}
mangle = {
screw_ie8: false,
toplevel: true,
}
input: {
var a = "PASS";
try {
throw "FAIL1";
} catch (a) {
var a = "FAIL2";
}
console.log(a);
}
expect_exact: 'var o="PASS";try{throw"FAIL1"}catch(o){var o="FAIL2"}console.log(o);'
expect_stdout: "PASS"
}
mangle_catch_redef_2: {
options = {
screw_ie8: true,
toplevel: false,
}
mangle = {
screw_ie8: true,
toplevel: false,
}
input: {
try {
throw "FAIL1";
} catch (a) {
var a = "FAIL2";
}
console.log(a);
}
expect_exact: 'try{throw"FAIL1"}catch(a){var a="FAIL2"}console.log(a);'
expect_stdout: "undefined"
}
mangle_catch_redef_2_ie8: {
options = {
screw_ie8: false,
toplevel: false,
}
mangle = {
screw_ie8: false,
toplevel: false,
}
input: {
try {
throw "FAIL1";
} catch (a) {
var a = "FAIL2";
}
console.log(a);
}
expect_exact: 'try{throw"FAIL1"}catch(a){var a="FAIL2"}console.log(a);'
expect_stdout: "undefined"
}
mangle_catch_redef_2_toplevel: {
options = {
screw_ie8: true,
toplevel: true,
}
mangle = {
screw_ie8: true,
toplevel: true,
}
input: {
try {
throw "FAIL1";
} catch (a) {
var a = "FAIL2";
}
console.log(a);
}
expect_exact: 'try{throw"FAIL1"}catch(o){var o="FAIL2"}console.log(o);'
expect_stdout: "undefined"
}
mangle_catch_redef_2_ie8_toplevel: {
options = {
screw_ie8: false,
toplevel: true,
}
mangle = {
screw_ie8: false,
toplevel: true,
}
input: {
try {
throw "FAIL1";
} catch (a) {
var a = "FAIL2";
}
console.log(a);
}
expect_exact: 'try{throw"FAIL1"}catch(o){var o="FAIL2"}console.log(o);'
expect_stdout: "undefined"
}

View File

@@ -0,0 +1,97 @@
function_iife_catch: {
mangle = {
screw_ie8: true,
}
input: {
function f(n) {
!function() {
try {
throw 0;
} catch (n) {
var a = 1;
console.log(n, a);
}
}();
}
f();
}
expect_exact: "function f(o){!function(){try{throw 0}catch(c){var o=1;console.log(c,o)}}()}f();"
expect_stdout: "0 1"
}
function_iife_catch_ie8: {
mangle = {
screw_ie8: false,
}
input: {
function f(n) {
!function() {
try {
throw 0;
} catch (n) {
var a = 1;
console.log(n, a);
}
}();
}
f();
}
expect_exact: "function f(o){!function(){try{throw 0}catch(o){var c=1;console.log(o,c)}}()}f();"
expect_stdout: "0 1"
}
function_catch_catch: {
mangle = {
screw_ie8: true,
}
input: {
var o = 0;
function f() {
try {
throw 1;
} catch (c) {
try {
throw 2;
} catch (o) {
var o = 3;
console.log(o);
}
}
console.log(o);
}
f();
}
expect_exact: "var o=0;function f(){try{throw 1}catch(c){try{throw 2}catch(o){var o=3;console.log(o)}}console.log(o)}f();"
expect_stdout: [
"3",
"undefined",
]
}
function_catch_catch_ie8: {
mangle = {
screw_ie8: false,
}
input: {
var o = 0;
function f() {
try {
throw 1;
} catch (c) {
try {
throw 2;
} catch (o) {
var o = 3;
console.log(o);
}
}
console.log(o);
}
f();
}
expect_exact: "var o=0;function f(){try{throw 1}catch(c){try{throw 2}catch(o){var o=3;console.log(o)}}console.log(o)}f();"
expect_stdout: [
"3",
"undefined",
]
}

View File

@@ -6,7 +6,7 @@ NaN_and_Infinity_must_have_parens: {
}
expect: {
(1/0).toString();
NaN.toString(); // transformation to 0/0 dropped
(0/0).toString();
}
}
@@ -23,3 +23,105 @@ NaN_and_Infinity_should_not_be_replaced_when_they_are_redefined: {
NaN.toString();
}
}
beautify_off_1: {
options = {
evaluate: true,
}
beautify = {
beautify: false,
}
input: {
var NaN;
console.log(
null,
undefined,
Infinity,
NaN,
Infinity * undefined,
Infinity.toString(),
NaN.toString(),
(Infinity * undefined).toString()
);
}
expect_exact: "var NaN;console.log(null,void 0,1/0,NaN,0/0,(1/0).toString(),NaN.toString(),(0/0).toString());"
expect_stdout: true
}
beautify_off_2: {
options = {
evaluate: true,
}
beautify = {
beautify: false,
}
input: {
console.log(
null.toString(),
undefined.toString()
);
}
expect_exact: "console.log(null.toString(),(void 0).toString());"
}
beautify_on_1: {
options = {
evaluate: true,
}
beautify = {
beautify: true,
}
input: {
var NaN;
console.log(
null,
undefined,
Infinity,
NaN,
Infinity * undefined,
Infinity.toString(),
NaN.toString(),
(Infinity * undefined).toString()
);
}
expect_exact: [
"var NaN;",
"",
"console.log(null, void 0, 1 / 0, NaN, 0 / 0, (1 / 0).toString(), NaN.toString(), (0 / 0).toString());",
]
expect_stdout: true
}
beautify_on_2: {
options = {
evaluate: true,
}
beautify = {
beautify: true,
}
input: {
console.log(
null.toString(),
undefined.toString()
);
}
expect_exact: "console.log(null.toString(), (void 0).toString());"
}
issue_1724: {
input: {
var a = 0;
++a % Infinity | Infinity ? a++ : 0;
console.log(a);
}
expect_exact: "var a=0;++a%(1/0)|1/0?a++:0;console.log(a);"
expect_stdout: "2"
}
issue_1725: {
input: {
([].length === 0) % Infinity ? console.log("PASS") : console.log("FAIL");
}
expect_exact: '(0===[].length)%(1/0)?console.log("PASS"):console.log("FAIL");'
expect_stdout: "PASS"
}

View File

@@ -440,3 +440,21 @@ issue_186_beautify_bracketize_ie8: {
'}',
]
}
issue_1648: {
options = {
join_vars: true,
loops: true,
passes: 2,
sequences: true,
unused: true,
}
input: {
function f() {
x();
var b = 1;
while (1);
}
}
expect_exact: "function f(){for(x();1;);}"
}

View File

@@ -353,8 +353,9 @@ issue_1254_negate_iife_nested: {
issue_1288: {
options = {
negate_iife: true,
conditionals: true,
negate_iife: true,
side_effects: false,
};
input: {
if (w) ;
@@ -374,11 +375,11 @@ issue_1288: {
})(0);
}
expect: {
w || function f() {}();
x || function() {
w || !function f() {}();
x || !function() {
x = {};
}();
y ? function() {}() : function(z) {
y ? !function() {}() : !function(z) {
return z;
}(0);
}

View File

@@ -153,3 +153,52 @@ evaluate_4: {
);
}
}
issue_1710: {
options = {
evaluate: true,
}
input: {
var x = {};
console.log((x += 1) + -x);
}
expect: {
var x = {};
console.log((x += 1) + -x);
}
expect_stdout: true
}
unary_binary_parenthesis: {
input: {
var v = [ 0, 1, NaN, Infinity, null, undefined, true, false, "", "foo", /foo/ ];
v.forEach(function(x) {
v.forEach(function(y) {
console.log(
+(x*y),
+(x/y),
+(x%y),
-(x*y),
-(x/y),
-(x%y)
);
});
});
}
expect: {
var v = [ 0, 1, 0/0, 1/0, null, void 0, true, false, "", "foo", /foo/ ];
v.forEach(function(x) {
v.forEach(function(y) {
console.log(
+x*y,
+x/y,
+x%y,
-x*y,
-x/y,
-x%y
);
});
});
}
expect_stdout: true
}

View File

@@ -9,7 +9,19 @@ arrow_functions: {
a=>{return b;}
a => 'lel'; // Dropping the parens
}
expect_exact: "a=>b;(a,b)=>c;()=>b;a=>b=>c;a=>b=>c;()=>(b,c)=>d;a=>{return b};a=>\"lel\";"
expect_exact: "a=>b;(a,b)=>c;()=>b;a=>b=>c;a=>b=>c;()=>(b,c)=>d;a=>b;a=>\"lel\";"
}
arrow_return: {
input: {
() => {};
() => { return; };
a => { return 1; }
a => { return -b }
a => { return b; var b; }
(x, y) => { return x - y; }
}
expect_exact: "()=>{};()=>{};a=>1;a=>-b;a=>{return b;var b};(x,y)=>x-y;"
}
regression_arrow_functions_and_hoist: {

View File

@@ -77,7 +77,7 @@ sub_properties: {
a[3.14] = 3;
a.if = 4;
a["foo bar"] = 5;
a[NaN] = 6;
a[0/0] = 6;
a[null] = 7;
a[void 0] = 8;
}

View File

@@ -425,7 +425,7 @@ iife_new: {
expect_stdout: true
}
multi_def: {
multi_def_1: {
options = {
evaluate: true,
reduce_vars: true,
@@ -435,7 +435,7 @@ multi_def: {
if (a)
var b = 1;
else
var b = 2
var b = 2;
console.log(b + 1);
}
}
@@ -444,7 +444,7 @@ multi_def: {
if (a)
var b = 1;
else
var b = 2
var b = 2;
console.log(b + 1);
}
}
@@ -479,6 +479,33 @@ multi_def_2: {
}
}
multi_def_3: {
options = {
evaluate: true,
reduce_vars: true,
}
input: {
function f(a) {
var b = 2;
if (a)
var b;
else
var b;
console.log(b + 1);
}
}
expect: {
function f(a) {
var b = 2;
if (a)
var b;
else
var b;
console.log(3);
}
}
}
use_before_var: {
options = {
evaluate: true,
@@ -1364,3 +1391,466 @@ issue_1606: {
}
}
}
issue_1670_1: {
options = {
comparisons: true,
conditionals: true,
evaluate: true,
dead_code: true,
reduce_vars: true,
unused: true,
}
input: {
(function f() {
switch (1) {
case 0:
var a = true;
break;
default:
if (typeof a === "undefined") console.log("PASS");
else console.log("FAIL");
}
})();
}
expect: {
(function() {
var a;
void 0 === a ? console.log("PASS") : console.log("FAIL");
})();
}
expect_stdout: "PASS"
}
issue_1670_2: {
options = {
conditionals: true,
evaluate: true,
dead_code: true,
passes: 2,
reduce_vars: true,
unused: true,
}
input: {
(function f() {
switch (1) {
case 0:
var a = true;
break;
default:
if (typeof a === "undefined") console.log("PASS");
else console.log("FAIL");
}
})();
}
expect: {
(function() {
console.log("PASS");
})();
}
expect_stdout: "PASS"
}
issue_1670_3: {
options = {
comparisons: true,
conditionals: true,
evaluate: true,
dead_code: true,
reduce_vars: true,
unused: true,
}
input: {
(function f() {
switch (1) {
case 0:
var a = true;
break;
case 1:
if (typeof a === "undefined") console.log("PASS");
else console.log("FAIL");
}
})();
}
expect: {
(function() {
var a;
void 0 === a ? console.log("PASS") : console.log("FAIL");
})();
}
expect_stdout: "PASS"
}
issue_1670_4: {
options = {
conditionals: true,
evaluate: true,
dead_code: true,
passes: 2,
reduce_vars: true,
unused: true,
}
input: {
(function f() {
switch (1) {
case 0:
var a = true;
break;
case 1:
if (typeof a === "undefined") console.log("PASS");
else console.log("FAIL");
}
})();
}
expect: {
(function() {
console.log("PASS");
})();
}
expect_stdout: "PASS"
}
issue_1670_5: {
options = {
dead_code: true,
evaluate: true,
keep_fargs: false,
reduce_vars: true,
unused: true,
}
input: {
(function(a) {
switch (1) {
case a:
console.log(a);
break;
default:
console.log(2);
break;
}
})(1);
}
expect: {
(function() {
console.log(1);
})();
}
expect_stdout: "1"
}
issue_1670_6: {
options = {
dead_code: true,
evaluate: true,
keep_fargs: false,
reduce_vars: true,
unused: true,
}
input: {
(function(a) {
switch (1) {
case a = 1:
console.log(a);
break;
default:
console.log(2);
break;
}
})(1);
}
expect: {
(function(a) {
switch (1) {
case a = 1:
console.log(a);
break;
default:
console.log(2);
}
})(1);
}
expect_stdout: "1"
}
unary_delete: {
options = {
evaluate: true,
reduce_vars: true,
unused: true,
}
input: {
var b = 10;
function f() {
var a;
if (delete a) b--;
}
f();
console.log(b);
}
expect: {
var b = 10;
function f() {
var a;
if (delete a) b--;
}
f();
console.log(b);
}
expect_stdout: true
}
redefine_arguments_1: {
options = {
evaluate: true,
keep_fargs: false,
reduce_vars: true,
unused: true,
}
input: {
function f() {
var arguments;
return typeof arguments;
}
function g() {
var arguments = 42;
return typeof arguments;
}
function h(x) {
var arguments = x;
return typeof arguments;
}
console.log(f(), g(), h());
}
expect: {
function f() {
var arguments;
return typeof arguments;
}
function g() {
return"number";
}
function h(x) {
var arguments = x;
return typeof arguments;
}
console.log(f(), g(), h());
}
expect_stdout: "object number undefined"
}
redefine_arguments_2: {
options = {
evaluate: true,
keep_fargs: false,
reduce_vars: true,
side_effects: true,
toplevel: true,
unused: true,
}
input: {
function f() {
var arguments;
return typeof arguments;
}
function g() {
var arguments = 42;
return typeof arguments;
}
function h(x) {
var arguments = x;
return typeof arguments;
}
console.log(f(), g(), h());
}
expect: {
console.log(function() {
var arguments;
return typeof arguments;
}(), function() {
return"number";
}(), function(x) {
var arguments = x;
return typeof arguments;
}());
}
expect_stdout: "object number undefined"
}
redefine_arguments_3: {
options = {
evaluate: true,
keep_fargs: false,
passes: 3,
reduce_vars: true,
side_effects: true,
toplevel: true,
unused: true,
}
input: {
function f() {
var arguments;
return typeof arguments;
}
function g() {
var arguments = 42;
return typeof arguments;
}
function h(x) {
var arguments = x;
return typeof arguments;
}
console.log(f(), g(), h());
}
expect: {
console.log(function() {
var arguments;
return typeof arguments;
}(), "number", "undefined");
}
expect_stdout: "object number undefined"
}
redefine_farg_1: {
options = {
evaluate: true,
keep_fargs: false,
reduce_vars: true,
unused: true,
}
input: {
function f(a) {
var a;
return typeof a;
}
function g(a) {
var a = 42;
return typeof a;
}
function h(a, b) {
var a = b;
return typeof a;
}
console.log(f([]), g([]), h([]));
}
expect: {
function f(a) {
var a;
return typeof a;
}
function g() {
return"number";
}
function h(a, b) {
var a = b;
return typeof a;
}
console.log(f([]), g([]), h([]));
}
expect_stdout: "object number undefined"
}
redefine_farg_2: {
options = {
evaluate: true,
keep_fargs: false,
reduce_vars: true,
side_effects: true,
toplevel: true,
unused: true,
}
input: {
function f(a) {
var a;
return typeof a;
}
function g(a) {
var a = 42;
return typeof a;
}
function h(a, b) {
var a = b;
return typeof a;
}
console.log(f([]), g([]), h([]));
}
expect: {
console.log(function(a) {
var a;
return typeof a;
}([]), function() {
return "number";
}(),function(a, b) {
var a = b;
return typeof a;
}([]));
}
expect_stdout: "object number undefined"
}
redefine_farg_3: {
options = {
evaluate: true,
keep_fargs: false,
passes: 3,
reduce_vars: true,
side_effects: true,
toplevel: true,
unused: true,
}
input: {
function f(a) {
var a;
return typeof a;
}
function g(a) {
var a = 42;
return typeof a;
}
function h(a, b) {
var a = b;
return typeof a;
}
console.log(f([]), g([]), h([]));
}
expect: {
console.log(function(a) {
var a;
return typeof a;
}([]), "number", function(a) {
var a = void 0;
return typeof a;
}([]));
}
expect_stdout: "object number undefined"
}
delay_def: {
options = {
evaluate: true,
reduce_vars: true,
unused: true,
}
input: {
function f() {
return a;
var a;
}
function g() {
return a;
var a = 1;
}
console.log(f(), g());
}
expect: {
function f() {
return a;
var a;
}
function g() {
return a;
var a = 1;
}
console.log(f(), g());
}
expect_stdout: true
}

View File

@@ -204,13 +204,13 @@ issue_1586_1: {
input: {
function f() {
try {
x();
} catch (err) {
console.log(err.message);
}
}
}
expect_exact: "function f(){try{}catch(c){console.log(c.message)}}"
expect_stdout: true
expect_exact: "function f(){try{x()}catch(c){console.log(c.message)}}"
}
issue_1586_2: {
@@ -223,11 +223,11 @@ issue_1586_2: {
input: {
function f() {
try {
x();
} catch (err) {
console.log(err.message);
}
}
}
expect_exact: "function f(){try{}catch(c){console.log(c.message)}}"
expect_stdout: true
expect_exact: "function f(){try{x()}catch(c){console.log(c.message)}}"
}

View File

@@ -306,3 +306,137 @@ unsafe_undefined: {
}
}
}
issue_1685: {
options = {
cascade: true,
side_effects: true,
}
input: {
var a = 100, b = 10;
function f() {
var a = (a--, delete a && --b);
}
f();
console.log(a, b);
}
expect: {
var a = 100, b = 10;
function f() {
var a = (a--, delete a && --b);
}
f();
console.log(a, b);
}
expect_stdout: true
}
func_def_1: {
options = {
cascade: true,
side_effects: true,
}
input: {
function f() {
return f = 0, !!f;
}
console.log(f());
}
expect: {
function f() {
return !!(f = 0);
}
console.log(f());
}
expect_stdout: "false"
}
func_def_2: {
options = {
cascade: true,
side_effects: true,
}
input: {
console.log(function f() {
return f = 0, !!f;
}());
}
expect: {
console.log(function f() {
return f = 0, !!f;
}());
}
expect_stdout: "true"
}
func_def_3: {
options = {
cascade: true,
side_effects: true,
}
input: {
function f() {
function g() {}
return g = 0, !!g;
}
console.log(f());
}
expect: {
function f() {
function g() {}
return !!(g = 0);
}
console.log(f());
}
expect_stdout: "false"
}
func_def_4: {
options = {
cascade: true,
side_effects: true,
}
input: {
function f() {
function g() {
return g = 0, !!g;
}
return g();
}
console.log(f());
}
expect: {
function f() {
function g() {
return !!(g = 0);
}
return g();
}
console.log(f());
}
expect_stdout: "false"
}
func_def_5: {
options = {
cascade: true,
side_effects: true,
}
input: {
function f() {
return function g(){
return g = 0, !!g;
}();
}
console.log(f());
}
expect: {
function f() {
return function g(){
return g = 0, !!g;
}();
}
console.log(f());
}
expect_stdout: "true"
}

View File

@@ -258,3 +258,466 @@ keep_default: {
}
}
}
issue_1663: {
options = {
dead_code: true,
evaluate: true,
}
input: {
var a = 100, b = 10;
function f() {
switch (1) {
case 1:
b = a++;
return ++b;
default:
var b;
}
}
f();
console.log(a, b);
}
expect: {
var a = 100, b = 10;
function f() {
var b;
b = a++;
return ++b;
}
f();
console.log(a, b);
}
expect_stdout: true
}
drop_case: {
options = {
dead_code: true,
}
input: {
switch (foo) {
case 'bar': baz(); break;
case 'moo':
break;
}
}
expect: {
switch (foo) {
case 'bar': baz();
}
}
}
keep_case: {
options = {
dead_code: true,
}
input: {
switch (foo) {
case 'bar': baz(); break;
case moo:
break;
}
}
expect: {
switch (foo) {
case 'bar': baz(); break;
case moo:
}
}
}
issue_376: {
options = {
dead_code: true,
evaluate: true,
}
input: {
switch (true) {
case boolCondition:
console.log(1);
break;
case false:
console.log(2);
break;
}
}
expect: {
switch (true) {
case boolCondition:
console.log(1);
}
}
}
issue_441_1: {
options = {
dead_code: true,
}
input: {
switch (foo) {
case bar:
qux();
break;
case baz:
qux();
break;
default:
qux();
break;
}
}
expect: {
switch (foo) {
case bar:
case baz:
default:
qux();
}
}
}
issue_441_2: {
options = {
dead_code: true,
}
input: {
switch (foo) {
case bar:
// TODO: Fold into the case below
qux();
break;
case fall:
case baz:
qux();
break;
default:
qux();
break;
}
}
expect: {
switch (foo) {
case bar:
qux();
break;
case fall:
case baz:
default:
qux();
}
}
}
issue_1674: {
options = {
dead_code: true,
evaluate: true,
}
input: {
switch (0) {
default:
console.log("FAIL");
break;
case 0:
console.log("PASS");
break;
}
}
expect: {
console.log("PASS");
}
expect_stdout: "PASS"
}
issue_1679: {
options = {
dead_code: true,
evaluate: true,
}
input: {
var a = 100, b = 10;
function f() {
switch (--b) {
default:
case !function x() {}:
break;
case b--:
switch (0) {
default:
case a--:
}
break;
case (a++):
break;
}
}
f();
console.log(a, b);
}
expect: {
var a = 100, b = 10;
function f() {
switch (--b) {
default:
case !function x() {}:
break;
case b--:
switch (0) {
default:
case a--:
}
break;
case (a++):
}
}
f();
console.log(a, b);
}
expect_stdout: true
}
issue_1680_1: {
options = {
dead_code: true,
evaluate: true,
}
input: {
function f(x) {
console.log(x);
return x + 1;
}
switch (2) {
case f(0):
case f(1):
f(2);
case 2:
case f(3):
case f(4):
f(5);
}
}
expect: {
function f(x) {
console.log(x);
return x + 1;
}
switch (2) {
case f(0):
case f(1):
f(2);
case 2:
f(5);
}
}
expect_stdout: [
"0",
"1",
"2",
"5",
]
}
issue_1680_2: {
options = {
dead_code: true,
}
input: {
var a = 100, b = 10;
switch (b) {
case a--:
break;
case b:
var c;
break;
case a:
break;
case a--:
break;
}
console.log(a, b);
}
expect: {
var a = 100, b = 10;
switch (b) {
case a--:
break;
case b:
var c;
break;
case a:
case a--:
}
console.log(a, b);
}
expect_stdout: true
}
issue_1690_1: {
options = {
dead_code: true,
}
input: {
switch (console.log("PASS")) {}
}
expect: {
console.log("PASS");
}
expect_stdout: "PASS"
}
issue_1690_2: {
options = {
dead_code: false,
}
input: {
switch (console.log("PASS")) {}
}
expect: {
switch (console.log("PASS")) {}
}
expect_stdout: "PASS"
}
if_switch_typeof: {
options = {
conditionals: true,
dead_code: true,
side_effects: true,
}
input: {
if (a) switch(typeof b) {}
}
expect: {
a;
}
}
issue_1698: {
options = {
side_effects: true,
}
input: {
var a = 1;
!function() {
switch (a++) {}
}();
console.log(a);
}
expect: {
var a = 1;
!function() {
switch (a++) {}
}();
console.log(a);
}
expect_stdout: "2"
}
issue_1705_1: {
options = {
dead_code: true,
}
input: {
var a = 0;
switch (a) {
default:
console.log("FAIL");
case 0:
break;
}
}
expect: {
var a = 0;
switch (a) {
default:
console.log("FAIL");
case 0:
}
}
expect_stdout: true
}
issue_1705_2: {
options = {
dead_code: true,
evaluate: true,
reduce_vars: true,
sequences: true,
side_effects: true,
toplevel: true,
unused: true,
}
input: {
var a = 0;
switch (a) {
default:
console.log("FAIL");
case 0:
break;
}
}
expect: {
}
expect_stdout: true
}
issue_1705_3: {
options = {
dead_code: true,
}
input: {
switch (a) {
case 0:
break;
default:
break;
}
}
expect: {
a;
}
expect_stdout: true
}
beautify: {
beautify = {
beautify: true,
}
input: {
switch (a) {
case 0:
case 1:
break;
case 2:
default:
}
switch (b) {
case 3:
foo();
bar();
default:
break;
}
}
expect_exact: [
"switch (a) {",
" case 0:",
" case 1:",
" break;",
"",
" case 2:",
" default:",
"}",
"",
"switch (b) {",
" case 3:",
" foo();",
" bar();",
"",
" default:",
" break;",
"}",
]
}

View File

@@ -30,7 +30,6 @@ booleans_global_defs: {
expect: {
console.log(!0);
}
expect_stdout: true
}
condition_evaluate: {

View File

@@ -48,3 +48,15 @@ typeof_in_boolean_context: {
foo();
}
}
issue_1668: {
options = {
booleans: true,
}
input: {
if (typeof bar);
}
expect: {
if (!0);
}
}

View File

@@ -0,0 +1 @@
console.log(1 || 5--);

View File

@@ -0,0 +1 @@
console.log(2 || (Math.random() /= 2));

View File

@@ -0,0 +1 @@
console.log(3 || ++this);

View File

@@ -251,4 +251,59 @@ describe("bin/uglifyjs", function () {
done();
});
});
it("Should support hyphen as shorthand", function(done) {
var command = uglifyjscmd + ' test/input/issue-1431/sample.js -m keep-fnames=true';
exec(command, function (err, stdout) {
if (err) throw err;
assert.strictEqual(stdout, "function f(r){return function(){function n(n){return n*n}return r(n)}}function g(n){return n(1)+n(2)}console.log(f(g)()==5);\n");
done();
});
});
it("Should throw syntax error (5--)", function(done) {
var command = uglifyjscmd + ' test/input/invalid/assign_1.js';
exec(command, function (err, stdout, stderr) {
assert.ok(err);
assert.strictEqual(stdout, "");
assert.strictEqual(stderr.split(/\n/).slice(0, 4).join("\n"), [
"Parse error at test/input/invalid/assign_1.js:1,18",
"console.log(1 || 5--);",
" ^",
"SyntaxError: Invalid use of -- operator"
].join("\n"));
done();
});
});
it("Should throw syntax error (Math.random() /= 2)", function(done) {
var command = uglifyjscmd + ' test/input/invalid/assign_2.js';
exec(command, function (err, stdout, stderr) {
assert.ok(err);
assert.strictEqual(stdout, "");
assert.strictEqual(stderr.split(/\n/).slice(0, 4).join("\n"), [
"Parse error at test/input/invalid/assign_2.js:1,32",
"console.log(2 || (Math.random() /= 2));",
" ^",
"SyntaxError: Invalid assignment"
].join("\n"));
done();
});
});
it("Should throw syntax error (++this)", function(done) {
var command = uglifyjscmd + ' test/input/invalid/assign_3.js';
exec(command, function (err, stdout, stderr) {
assert.ok(err);
assert.strictEqual(stdout, "");
assert.strictEqual(stderr.split(/\n/).slice(0, 4).join("\n"), [
"Parse error at test/input/invalid/assign_3.js:1,18",
"console.log(3 || ++this);",
" ^",
"SyntaxError: Invalid use of ++ operator"
].join("\n"));
done();
});
});
});

View File

@@ -186,7 +186,7 @@ describe("Directives", function() {
});
it("Should test EXPECT_DIRECTIVE RegExp", function() {
var tests = [
[
["", true],
["'test';", true],
["'test';;", true],
@@ -195,11 +195,12 @@ describe("Directives", function() {
["'tests'; \n\t", true],
["'tests';\n\n", true],
["\n\n\"use strict\";\n\n", true]
];
for (var i = 0; i < tests.length; i++) {
assert.strictEqual(uglify.EXPECT_DIRECTIVE.test(tests[i][0]), tests[i][1], tests[i][0]);
}
].forEach(function(test) {
var out = uglify.OutputStream();
out.print(test[0]);
out.print_string("", null, true);
assert.strictEqual(out.get() === test[0] + ';""', test[1], test[0]);
});
});
it("Should only print 2 semicolons spread over 2 lines in beautify mode", function() {

40
test/mocha/export.js Normal file
View File

@@ -0,0 +1,40 @@
var assert = require("assert");
var uglify = require("../../");
describe("Export", function() {
it ("Should parse export directives", function() {
var inputs = [
['export * from "a.js"', ['*'], "a.js"],
['export {A} from "a.js"', ['A'], "a.js"],
['export {A as X} from "a.js"', ['X'], "a.js"],
['export {A as Foo, B} from "a.js"', ['Foo', 'B'], "a.js"],
['export {A, B} from "a.js"', ['A', 'B'], "a.js"],
];
var test = function(code) {
return uglify.parse(code, {fromString: true});
};
var extractNames = function(symbols) {
var ret = [];
for (var i = 0; i < symbols.length; i++) {
ret.push(symbols[i].name.name)
}
return ret;
};
for (var i = 0; i < inputs.length; i++) {
var ast = test(inputs[i][0]);
var names = inputs[i][1];
var filename = inputs[i][2];
assert(ast instanceof uglify.AST_Toplevel);
assert.equal(ast.body.length, 1);
var st = ast.body[0];
assert(st instanceof uglify.AST_Export);
var actualNames = extractNames(st.exported_names);
assert.deepEqual(actualNames, names);
assert.equal(st.module_name.value, filename)
}
})
});

View File

@@ -5,7 +5,7 @@ var path = require("path");
describe("minify() with input file globs", function() {
it("minify() with one input file glob string.", function() {
var result = Uglify.minify("test/input/issue-1242/foo.*");
assert.strictEqual(result.code, 'function foo(o){var n=2*o;print("Foo:",n)}var print=console.log.bind(console);');
assert.strictEqual(result.code, 'function foo(o){print("Foo:",2*o)}var print=console.log.bind(console);');
});
it("minify() with an array of one input file glob.", function() {
var result = Uglify.minify([
@@ -20,7 +20,7 @@ describe("minify() with input file globs", function() {
], {
compress: { toplevel: true }
});
assert.strictEqual(result.code, 'var print=console.log.bind(console),a=function(n){return 3*n}(3),b=function(n){return n/2}(12);print("qux",a,b),function(n){var o=2*n;print("Foo:",o)}(11);');
assert.strictEqual(result.code, 'var print=console.log.bind(console),a=function(n){return 3*n}(3),b=function(n){return n/2}(12);print("qux",a,b),function(n){print("Foo:",2*n)}(11);');
});
it("should throw with non-matching glob string", function() {
var glob = "test/input/issue-1242/blah.*";

View File

@@ -1,7 +1,5 @@
#! /usr/bin/env node
global.UGLIFY_DEBUG = true;
var U = require("../tools/node");
var path = require("path");
var fs = require("fs");
@@ -105,6 +103,23 @@ function run_compress_tests() {
function test_case(test) {
log_test(test.name);
U.base54.reset();
var output_options = test.beautify || {};
var expect;
if (test.expect) {
expect = make_code(as_toplevel(test.expect, test.mangle), output_options);
} else {
expect = test.expect_exact;
}
var input = as_toplevel(test.input, test.mangle);
var input_code = make_code(input, output_options);
var input_formatted = make_code(test.input, {
beautify: true,
quote_style: 3,
keep_quoted_props: true
});
if (test.mangle_props) {
input = U.mangle_properties(input, test.mangle_props);
}
var options = U.defaults(test.options, {
warnings: false
});
@@ -117,22 +132,6 @@ function run_compress_tests() {
if (!options.warnings) options.warnings = true;
}
var cmp = new U.Compressor(options, true);
var output_options = test.beautify || {};
var expect;
if (test.expect) {
expect = make_code(as_toplevel(test.expect, test.mangle), output_options);
} else {
expect = test.expect_exact;
}
var input = as_toplevel(test.input, test.mangle);
var input_code = make_code(test.input, {
beautify: true,
quote_style: 3,
keep_quoted_props: true
});
if (test.mangle_props) {
input = U.mangle_properties(input, test.mangle_props);
}
var output = cmp.compress(input);
output.figure_out_scope(test.mangle);
if (test.mangle) {
@@ -142,7 +141,7 @@ function run_compress_tests() {
output = make_code(output, output_options);
if (expect != output) {
log("!!! failed\n---INPUT---\n{input}\n---OUTPUT---\n{output}\n---EXPECTED---\n{expected}\n\n", {
input: input_code,
input: input_formatted,
output: output,
expected: expect
});
@@ -155,7 +154,7 @@ function run_compress_tests() {
var reparsed_ast = U.parse(output);
} catch (ex) {
log("!!! Test matched expected result but cannot parse output\n---INPUT---\n{input}\n---OUTPUT---\n{output}\n--REPARSE ERROR--\n{error}\n\n", {
input: input_code,
input: input_formatted,
output: output,
error: ex.toString(),
});
@@ -174,7 +173,7 @@ function run_compress_tests() {
var actual_warnings = JSON.stringify(warnings_emitted);
if (expected_warnings != actual_warnings) {
log("!!! failed\n---INPUT---\n{input}\n---EXPECTED WARNINGS---\n{expected_warnings}\n---ACTUAL WARNINGS---\n{actual_warnings}\n\n", {
input: input_code,
input: input_formatted,
expected_warnings: expected_warnings,
actual_warnings: actual_warnings,
});
@@ -183,13 +182,13 @@ function run_compress_tests() {
}
}
if (test.expect_stdout) {
var stdout = run_code(make_code(input, output_options));
var stdout = run_code(input_code);
if (test.expect_stdout === true) {
test.expect_stdout = stdout;
}
if (!same_stdout(test.expect_stdout, stdout)) {
log("!!! Invalid input or expected stdout\n---INPUT---\n{input}\n---EXPECTED {expected_type}---\n{expected}\n---ACTUAL {actual_type}---\n{actual}\n\n", {
input: input_code,
input: input_formatted,
expected_type: typeof test.expect_stdout == "string" ? "STDOUT" : "ERROR",
expected: test.expect_stdout,
actual_type: typeof stdout == "string" ? "STDOUT" : "ERROR",
@@ -201,7 +200,7 @@ function run_compress_tests() {
stdout = run_code(output);
if (!same_stdout(test.expect_stdout, stdout)) {
log("!!! failed\n---INPUT---\n{input}\n---EXPECTED {expected_type}---\n{expected}\n---ACTUAL {actual_type}---\n{actual}\n\n", {
input: input_code,
input: input_formatted,
expected_type: typeof test.expect_stdout == "string" ? "STDOUT" : "ERROR",
expected: test.expect_stdout,
actual_type: typeof stdout == "string" ? "STDOUT" : "ERROR",

477
test/ufuzz.js Normal file
View File

@@ -0,0 +1,477 @@
// ufuzz.js
// derived from https://github.com/qfox/uglyfuzzer by Peter van der Zee
"use strict";
// workaround for tty output truncation upon process.exit()
[process.stdout, process.stderr].forEach(function(stream){
if (stream._handle && stream._handle.setBlocking)
stream._handle.setBlocking(true);
});
var vm = require("vm");
var minify = require("..").minify;
var MAX_GENERATED_FUNCTIONS_PER_RUN = 1;
var MAX_GENERATION_RECURSION_DEPTH = 15;
var INTERVAL_COUNT = 100;
var VALUES = [
'true',
'false',
'22',
'0',
'-0', // 0/-0 !== 0
'23..toString()',
'24 .toString()',
'25. ',
'0x26.toString()',
'(-1)',
'NaN',
'undefined',
'Infinity',
'null',
'[]',
'[,0][1]', // an array with elisions... but this is always false
'([,0].length === 2)', // an array with elisions... this is always true
'({})', // wrapped the object causes too many syntax errors in statements
'"foo"',
'"bar"' ];
var BINARY_OPS_NO_COMMA = [
' + ', // spaces needed to disambiguate with ++ cases (could otherwise cause syntax errors)
' - ',
'/',
'*',
'&',
'|',
'^',
'<<',
'>>',
'>>>',
'%',
'&&',
'||',
'^' ];
var BINARY_OPS = [','].concat(BINARY_OPS_NO_COMMA);
var ASSIGNMENTS = [
'=',
'=',
'=',
'=',
'=',
'=',
'==',
'!=',
'===',
'!==',
'+=',
'-=',
'*=',
'/=',
'&=',
'|=',
'^=',
'<<=',
'>>=',
'>>>=',
'%=' ];
var UNARY_OPS = [
'--',
'++',
'~',
'!',
'void ',
'delete ', // should be safe, even `delete foo` and `delete f()` shouldn't crash
' - ',
' + ' ];
var NO_COMMA = true;
var MAYBE = true;
var NESTED = true;
var CAN_THROW = true;
var CANNOT_THROW = false;
var CAN_BREAK = true;
var CAN_CONTINUE = true;
var VAR_NAMES = [
'foo',
'bar',
'a',
'b',
'undefined', // fun!
'eval', // mmmm, ok, also fun!
'NaN', // mmmm, ok, also fun!
'Infinity', // the fun never ends!
'arguments', // this one is just creepy
'Math', // since Math is assumed to be a non-constructor/function it may trip certain cases
'let' ]; // maybe omit this, it's more a parser problem than minifier
var TYPEOF_OUTCOMES = [
'undefined',
'string',
'number',
'object',
'boolean',
'special',
'unknown',
'symbol',
'crap' ];
var FUNC_TOSTRING = [
"Function.prototype.toString = function() {",
" var ids = [];",
" return function() {",
" var i = ids.indexOf(this);",
" if (i < 0) {",
" i = ids.length;",
" ids.push(this);",
" }",
' return "[Function: __func_" + i + "__]";',
" }",
"}();",
""
].join("\n");
function run_code(code) {
var stdout = "";
var original_write = process.stdout.write;
process.stdout.write = function(chunk) {
stdout += chunk;
};
try {
new vm.Script(FUNC_TOSTRING + code).runInNewContext({
console: {
log: function() {
return console.log.apply(console, [].map.call(arguments, function(arg) {
return typeof arg == "function" ? "[Function]" : arg;
}));
}
}
}, { timeout: 5000 });
return stdout;
} catch (ex) {
return ex;
} finally {
process.stdout.write = original_write;
}
}
function rng(max) {
return Math.floor(max * Math.random());
}
function createFunctionDecls(n, recurmax, nested) {
if (--recurmax < 0) { return ';'; }
var s = '';
while (n-- > 0) {
s += createFunctionDecl(recurmax, nested) + '\n';
}
return s;
}
var funcs = 0;
function createFunctionDecl(recurmax, nested) {
if (--recurmax < 0) { return ';'; }
var func = funcs++;
var name = rng(5) > 0 ? 'f' + func : createVarName();
if (name === 'a' || name === 'b') name = 'f' + func; // quick hack to prevent assignment to func names of being called
if (!nested && name === 'undefined' || name === 'NaN' || name === 'Infinity') name = 'f' + func; // cant redefine these in global space
var s = '';
if (rng(5) === 1) {
// functions with functions. lower the recursion to prevent a mess.
s = 'function ' + name + '(){' + createFunctionDecls(rng(5) + 1, Math.ceil(recurmax / 2), NESTED) + '}\n';
} else {
// functions with statements
s = 'function ' + name + '(){' + createStatements(3, recurmax) + '}\n';
}
if (nested) s = '!' + nested; // avoid "function statements" (decl inside statements)
else s += name + '();'
return s;
}
function createStatements(n, recurmax, canThrow, canBreak, canContinue) {
if (--recurmax < 0) { return ';'; }
var s = '';
while (--n > 0) {
s += createStatement(recurmax, canThrow, canBreak, canContinue);
}
return s;
}
var loops = 0;
function createStatement(recurmax, canThrow, canBreak, canContinue) {
var loop = ++loops;
if (--recurmax < 0) { return ';'; }
switch (rng(16)) {
case 0:
return '{' + createStatements(rng(5) + 1, recurmax, canThrow, canBreak, canContinue) + '}';
case 1:
return 'if (' + createExpression(recurmax) + ')' + createStatement(recurmax, canThrow, canBreak, canContinue) + (rng(2) === 1 ? ' else ' + createStatement(recurmax, canThrow, canBreak, canContinue) : '');
case 2:
return '{var brake' + loop + ' = 5; do {' + createStatement(recurmax, canThrow, CAN_BREAK, CAN_CONTINUE) + '} while ((' + createExpression(recurmax) + ') && --brake' + loop + ' > 0);}';
case 3:
return '{var brake' + loop + ' = 5; while ((' + createExpression(recurmax) + ') && --brake' + loop + ' > 0)' + createStatement(recurmax, canThrow, CAN_BREAK, CAN_CONTINUE) + '}';
case 4:
return 'for (var brake' + loop + ' = 5; (' + createExpression(recurmax) + ') && brake' + loop + ' > 0; --brake' + loop + ')' + createStatement(recurmax, canThrow, CAN_BREAK, CAN_CONTINUE);
case 5:
return ';';
case 6:
return createExpression(recurmax) + ';';
case 7:
// note: case args are actual expressions
// note: default does not _need_ to be last
return 'switch (' + createExpression(recurmax) + ') { ' + createSwitchParts(recurmax, 4) + '}';
case 8:
return 'var ' + createVarName() + ';';
case 9:
// initializer can only have one expression
return 'var ' + createVarName() + ' = ' + createExpression(recurmax, NO_COMMA) + ';';
case 10:
// initializer can only have one expression
return 'var ' + createVarName() + ' = ' + createExpression(recurmax, NO_COMMA) + ', ' + createVarName() + ' = ' + createExpression(recurmax, NO_COMMA) + ';';
case 11:
if (canBreak && rng(5) === 0) return 'break;';
if (canContinue && rng(5) === 0) return 'continue;';
return 'return;';
case 12:
// must wrap in curlies to prevent orphaned `else` statement
if (canThrow && rng(5) === 0) return '{ throw ' + createExpression(recurmax) + '}';
return '{ return ' + createExpression(recurmax) + '}';
case 13:
// this is actually more like a parser test, but perhaps it hits some dead code elimination traps
// must wrap in curlies to prevent orphaned `else` statement
if (canThrow && rng(5) === 0) return '{ throw\n' + createExpression(recurmax) + '}';
return '{ return\n' + createExpression(recurmax) + '}';
case 14:
// "In non-strict mode code, functions can only be declared at top level, inside a block, or ..."
// (dont both with func decls in `if`; it's only a parser thing because you cant call them without a block)
return '{' + createFunctionDecl(recurmax, NESTED) + '}';
case 15:
return ';';
// catch var could cause some problems
// note: the "blocks" are syntactically mandatory for try/catch/finally
var s = 'try {' + createStatement(recurmax, CAN_THROW, canBreak, canContinue) + ' }';
var n = rng(3); // 0=only catch, 1=only finally, 2=catch+finally
if (n !== 1) s += ' catch (' + createVarName() + ') { ' + createStatements(3, recurmax, canBreak, canContinue) + ' }';
if (n !== 0) s += ' finally { ' + createStatements(3, recurmax, canBreak, canContinue) + ' }';
return s;
}
}
function createSwitchParts(recurmax, n) {
var hadDefault = false;
var s = '';
while (n-- > 0) {
hadDefault = n > 0;
if (hadDefault || rng(4) > 0) {
s += '' +
'case ' + createExpression(recurmax) + ':\n' +
createStatements(rng(3) + 1, recurmax, CANNOT_THROW, CAN_BREAK) +
'\n' +
(rng(10) > 0 ? ' break;' : '/* fall-through */') +
'\n';
} else {
hadDefault = true;
s += '' +
'default:\n' +
createStatements(rng(3) + 1, recurmax, CANNOT_THROW, CAN_BREAK) +
'\n';
}
}
return s;
}
function createExpression(recurmax, noComma) {
if (--recurmax < 0) {
return createValue(); // note: should return a simple non-recursing expression value!
}
switch (rng(12)) {
case 0:
return '(' + createUnaryOp() + (rng(2) === 1 ? 'a' : 'b') + ')';
case 1:
return '(a' + (rng(2) == 1 ? '++' : '--') + ')';
case 2:
return '(b ' + createAssignment() + ' a)';
case 3:
return '(' + rng(2) + ' === 1 ? a : b)';
case 4:
return createExpression(recurmax, noComma) + createBinaryOp(noComma) + createExpression(recurmax, noComma);
case 5:
return createValue();
case 6:
return '(' + createExpression(recurmax) + ')';
case 7:
return createExpression(recurmax, noComma) + '?(' + createExpression(recurmax) + '):(' + createExpression(recurmax) + ')';
case 8:
switch(rng(4)) {
case 0:
return '(function ' + createVarName(MAYBE) + '(){' + createStatements(rng(5) + 1, recurmax) + '})()';
case 1:
return '+function ' + createVarName(MAYBE) + '(){' + createStatements(rng(5) + 1, recurmax) + '}';
case 2:
return '!function ' + createVarName(MAYBE) + '(){' + createStatements(rng(5) + 1, recurmax) + '}';
case 3:
return 'void function ' + createVarName(MAYBE) + '(){' + createStatements(rng(5) + 1, recurmax) + '}';
default:
return 'void function ' + createVarName(MAYBE) + '(){' + createStatements(rng(5) + 1, recurmax) + '}';
}
case 9:
return createTypeofExpr(recurmax);
case 10:
// you could statically infer that this is just `Math`, regardless of the other expression
// I don't think Uglify does this at this time...
return ''+
'new function(){ \n' +
(rng(2) === 1 ? createExpression(recurmax) + '\n' : '') +
'return Math;\n' +
'}';
case 11:
// more like a parser test but perhaps comment nodes mess up the analysis?
switch (rng(5)) {
case 0:
return '(a/* ignore */++)';
case 1:
return '(b/* ignore */--)';
case 2:
return '(++/* ignore */a)';
case 3:
return '(--/* ignore */b)';
case 4:
// only groups that wrap a single variable return a "Reference", so this is still valid.
// may just be a parser edge case that is invisible to uglify...
return '(--(b))';
default:
return '(--/* ignore */b)';
}
}
}
function createTypeofExpr(recurmax) {
if (--recurmax < 0) {
return 'typeof undefined === "undefined"';
}
switch (rng(5)) {
case 0:
return '(typeof ' + createVarName() + ' === "' + TYPEOF_OUTCOMES[rng(TYPEOF_OUTCOMES.length)] + '")';
case 1:
return '(typeof ' + createVarName() + ' !== "' + TYPEOF_OUTCOMES[rng(TYPEOF_OUTCOMES.length)] + '")';
case 2:
return '(typeof ' + createVarName() + ' == "' + TYPEOF_OUTCOMES[rng(TYPEOF_OUTCOMES.length)] + '")';
case 3:
return '(typeof ' + createVarName() + ' != "' + TYPEOF_OUTCOMES[rng(TYPEOF_OUTCOMES.length)] + '")';
case 4:
return '(typeof ' + createVarName() + ')';
}
}
function createValue() {
return VALUES[rng(VALUES.length)];
}
function createBinaryOp(noComma) {
if (noComma) return BINARY_OPS_NO_COMMA[rng(BINARY_OPS_NO_COMMA.length)];
return BINARY_OPS[rng(BINARY_OPS.length)];
}
function createAssignment() {
return ASSIGNMENTS[rng(ASSIGNMENTS.length)];
}
function createUnaryOp() {
return UNARY_OPS[rng(UNARY_OPS.length)];
}
function createVarName(maybe) {
if (!maybe || rng(2) === 1) {
return VAR_NAMES[rng(VAR_NAMES.length)] + (rng(5) > 0 ? ++loops : '');
}
return '';
}
function log(ok) {
console.log("//=============================================================");
if (!ok) console.log("// !!!!!! Failed...");
console.log("// original code");
console.log("//");
console.log(original_code);
console.log();
console.log();
console.log("//-------------------------------------------------------------");
console.log("// original code (beautify'd)");
console.log("//");
console.log(beautify_code);
console.log();
console.log();
console.log("//-------------------------------------------------------------");
console.log("// uglified code");
console.log("//");
console.log(uglify_code);
console.log();
console.log();
console.log("original result:");
console.log(original_result);
console.log("beautified result:");
console.log(beautify_result);
console.log("uglified result:");
console.log(uglify_result);
if (!ok) console.log("!!!!!! Failed...");
}
var num_iterations = +process.argv[2] || 1/0;
var verbose = process.argv[3] === 'v' || process.argv[2] === 'v';
var verbose_interval = process.argv[3] === 'V' || process.argv[2] === 'V';
for (var round = 0; round < num_iterations; round++) {
var parse_error = false;
process.stdout.write(round + " of " + num_iterations + "\r");
var original_code = [
"var a = 100, b = 10;",
createFunctionDecls(rng(MAX_GENERATED_FUNCTIONS_PER_RUN) + 1, MAX_GENERATION_RECURSION_DEPTH),
"console.log(a, b);"
].join("\n");
var original_result = run_code(original_code);
try {
var beautify_code = minify(original_code, {
fromString: true,
mangle: false,
compress: false,
output: {
beautify: true,
bracketize: true,
},
}).code;
} catch(e) {
parse_error = 1;
}
var beautify_result = run_code(beautify_code);
try {
var uglify_code = minify(beautify_code, {
fromString: true,
mangle: true,
compress: {
passes: 3,
},
output: {
//beautify: true,
//bracketize: true,
},
}).code;
} catch(e) {
parse_error = 2;
}
var uglify_result = run_code(uglify_code);
var ok = !parse_error && original_result == beautify_result && original_result == uglify_result;
if (verbose || (verbose_interval && !(round % INTERVAL_COUNT)) || !ok) log(ok);
if (parse_error === 1) console.log('Parse error while beautifying');
if (parse_error === 2) console.log('Parse error while uglifying');
if (!ok) break;
}

View File

@@ -17,7 +17,3 @@ exports["string_template"] = string_template;
exports["tokenizer"] = tokenizer;
exports["is_identifier"] = is_identifier;
exports["SymbolDef"] = SymbolDef;
if (global.UGLIFY_DEBUG) {
exports["EXPECT_DIRECTIVE"] = EXPECT_DIRECTIVE;
}