Compare commits

...

29 Commits

Author SHA1 Message Date
Alex Lam S.L
f1f4a4dd82 v3.13.4 2021-04-12 04:33:40 +08:00
Alex Lam S.L
ca49f6f41a fix corner case in collapse_vars (#4853)
fixes #4852
2021-04-08 17:50:40 +08:00
Alex Lam S.L
8a82822654 fix corner case in if_return (#4851)
fixes #4848
2021-04-08 08:57:59 +08:00
Alex Lam S.L
ebe4e1ad28 fix corner case in unused (#4850)
fixes #4849
2021-04-08 06:31:15 +08:00
Alex Lam S.L
a37ca558dd reject invalid for await syntax (#4847) 2021-04-07 22:37:15 +08:00
Alex Lam S.L
73a564343b preserve compatibility of quote_style (#4845) 2021-04-07 06:49:12 +08:00
Alex Lam S.L
4870747306 speed up OutputStream (#4844) 2021-04-07 02:57:23 +08:00
Alex Lam S.L
b179a2459f parse octal literals correctly (#4843) 2021-04-07 02:23:35 +08:00
Alex Lam S.L
231c3d7c84 clean up OutputStream (#4842) 2021-04-06 21:34:27 +08:00
Alex Lam S.L
aed758ed5c enhance comparisons & reduce_vars (#4841) 2021-04-03 22:06:05 +08:00
Alex Lam S.L
0df028187d fix corner case in hoist_vars (#4840)
fixes #4839
2021-04-03 10:07:18 +08:00
Alex Lam S.L
10fbf8e295 support mangle.properties on class (#4838) 2021-04-03 06:00:19 +08:00
Alex Lam S.L
cf38b52afa parse import.meta correctly (#4836) 2021-04-03 04:31:29 +08:00
Alex Lam S.L
e755d01a0b fix corner case in unused (#4835)
fixes #4834
2021-04-02 03:55:53 +08:00
Alex Lam S.L
4084948d3b suppress invalid AST transform in --reduce-test (#4833) 2021-04-01 18:52:29 +08:00
Alex Lam S.L
cea1fb5c58 fix corner case in properties (#4832)
fixes #4831
2021-04-01 14:39:51 +08:00
Alex Lam S.L
1947a21824 fix corner case in properties (#4830)
fixes #4829
2021-04-01 07:05:50 +08:00
Alex Lam S.L
6335b5fd8a v3.13.3 2021-03-29 06:58:06 +08:00
Alex Lam S.L
daa8319b8a fix corner cases with logical assignment operators (#4828)
fixes #4827
2021-03-28 03:44:45 +08:00
Alex Lam S.L
d5599604e8 enhance collapse_vars (#4826) 2021-03-27 22:14:37 +08:00
Alex Lam S.L
072933f1d5 diagnose GitHub Actions (#4825) 2021-03-25 18:44:39 +08:00
Alex Lam S.L
39df3a1680 fix corner case in functions (#4824)
fixes #4823
2021-03-25 08:49:01 +08:00
Alex Lam S.L
03c5ecb2e3 fix corner cases with class (#4822)
fixes #4821
2021-03-25 04:36:50 +08:00
Alex Lam S.L
40ef074cb3 fix corner case in comparisons (#4820)
fixes #4819
2021-03-24 10:10:02 +08:00
Alex Lam S.L
78e3936cd4 fix corner case in inline (#4818)
fixes #4817
2021-03-23 22:33:24 +08:00
Alex Lam S.L
e7be38b42a fix corner cases with logical assignment operators (#4816)
fixes #4815
2021-03-23 13:02:45 +08:00
Alex Lam S.L
44394e61c9 workaround toString() quirks on global context (#4814) 2021-03-23 11:15:41 +08:00
Alex Lam S.L
f9055df44d support logical assignment operators (#4813) 2021-03-23 04:59:43 +08:00
Alex Lam S.L
51bdb7281b improve global context enumeration under sandbox (#4812)
fixes #4811
2021-03-22 22:43:33 +08:00
28 changed files with 1340 additions and 273 deletions

View File

@@ -814,14 +814,91 @@ merge(Compressor.prototype, {
def(AST_Assign, function(tw, descend, compressor) {
var node = this;
var left = node.left;
if (node.operator == "=" && left.equivalent_to(node.right) && !left.has_side_effects(compressor)) {
node.right.walk(tw);
walk_prop(left);
node.__drop = true;
} else if (!(left instanceof AST_Destructured || left instanceof AST_SymbolRef)) {
var scan = left instanceof AST_Destructured || left instanceof AST_SymbolRef;
switch (node.operator) {
case "=":
if (left.equivalent_to(node.right) && !left.has_side_effects(compressor)) {
node.right.walk(tw);
walk_prop(left);
node.__drop = true;
return true;
}
if (scan) {
walk_assign();
return true;
}
mark_assignment_to_arguments(left);
return;
} else if (node.operator == "=") {
case "&&=":
case "||=":
case "??=":
left.walk(tw);
push(tw);
if (scan) {
walk_assign();
} else {
mark_assignment_to_arguments(left);
node.right.walk(tw);
}
pop(tw);
return true;
default:
if (!scan) {
mark_assignment_to_arguments(left);
return;
}
var d = left.definition();
d.assignments++;
var fixed = d.fixed;
if (is_modified(compressor, tw, node, node, 0)) {
d.fixed = false;
return;
}
var safe = safe_to_read(tw, d);
node.right.walk(tw);
if (safe && !left.in_arg && safe_to_assign(tw, d)) {
push_ref(d, left);
mark(tw, d);
if (d.single_use) d.single_use = false;
left.fixed = d.fixed = function() {
return make_node(AST_Binary, node, {
operator: node.operator.slice(0, -1),
left: make_ref(left, fixed),
right: node.right
});
};
left.fixed.assigns = !fixed || !fixed.assigns ? [] : fixed.assigns.slice();
left.fixed.assigns.push(node);
} else {
left.walk(tw);
d.fixed = false;
}
return true;
}
function walk_prop(lhs) {
if (lhs instanceof AST_Dot) {
walk_prop(lhs.expression);
} else if (lhs instanceof AST_Sub) {
walk_prop(lhs.expression);
lhs.property.walk(tw);
} else if (lhs instanceof AST_SymbolRef) {
var d = lhs.definition();
push_ref(d, lhs);
if (d.fixed) {
lhs.fixed = d.fixed;
if (lhs.fixed.assigns) {
lhs.fixed.assigns.push(node);
} else {
lhs.fixed.assigns = [ node ];
}
}
} else {
lhs.walk(tw);
}
}
function walk_assign() {
node.right.walk(tw);
scan_declaration(tw, compressor, left, function() {
return node.right;
@@ -849,56 +926,6 @@ merge(Compressor.prototype, {
d.fixed = false;
}
});
} else {
var d = left.definition();
d.assignments++;
var fixed = d.fixed;
if (is_modified(compressor, tw, node, node, 0)) {
d.fixed = false;
return;
}
var safe = safe_to_read(tw, d);
node.right.walk(tw);
if (safe && !left.in_arg && safe_to_assign(tw, d)) {
push_ref(d, left);
mark(tw, d);
if (d.single_use) d.single_use = false;
left.fixed = d.fixed = function() {
return make_node(AST_Binary, node, {
operator: node.operator.slice(0, -1),
left: make_ref(left, fixed),
right: node.right
});
};
left.fixed.assigns = !fixed || !fixed.assigns ? [] : fixed.assigns.slice();
left.fixed.assigns.push(node);
} else {
left.walk(tw);
d.fixed = false;
}
}
return true;
function walk_prop(lhs) {
if (lhs instanceof AST_Dot) {
walk_prop(lhs.expression);
} else if (lhs instanceof AST_Sub) {
walk_prop(lhs.expression);
lhs.property.walk(tw);
} else if (lhs instanceof AST_SymbolRef) {
var d = lhs.definition();
push_ref(d, lhs);
if (d.fixed) {
lhs.fixed = d.fixed;
if (lhs.fixed.assigns) {
lhs.fixed.assigns.push(node);
} else {
lhs.fixed.assigns = [ node ];
}
}
} else {
lhs.walk(tw);
}
}
});
def(AST_Binary, function(tw) {
@@ -971,7 +998,7 @@ merge(Compressor.prototype, {
if (prop.key instanceof AST_Node) prop.key.walk(tw);
return prop.value;
}).forEach(function(prop) {
if (prop.static) {
if (prop.static && (prop.value instanceof AST_Lambda || !prop.value.contains_this())) {
prop.value.walk(tw);
} else {
push(tw);
@@ -1799,6 +1826,18 @@ merge(Compressor.prototype, {
can_replace = replace;
return signal_abort(node);
}
// Scan but don't replace inside block scope with colliding variable
if (node instanceof AST_BlockScope
&& !(node instanceof AST_Scope)
&& !(node.variables && node.variables.all(function(def) {
return !lvalues.has(def.name);
}))) {
var replace = can_replace;
can_replace = false;
if (!handle_custom_scan_order(node, scanner)) descend(node, scanner);
can_replace = replace;
return signal_abort(node);
}
return handle_custom_scan_order(node, scanner);
}, signal_abort);
var multi_replacer = new TreeTransformer(function(node) {
@@ -1827,7 +1866,7 @@ merge(Compressor.prototype, {
return null;
}
default:
return;
return handle_custom_scan_order(node, multi_replacer);
}
}
// Replace variable when found
@@ -1937,13 +1976,6 @@ merge(Compressor.prototype, {
}
// Skip (non-executed) functions
if (node instanceof AST_Scope) return node;
// Stop upon collision with block-scoped variables
if (!(node.variables && node.variables.all(function(def) {
return !lvalues.has(def.name);
}))) {
abort = true;
return node;
}
// Scan object only in a for-in/of statement
if (node instanceof AST_ForEnumeration) {
node.object = node.object.transform(tt);
@@ -1979,7 +2011,9 @@ merge(Compressor.prototype, {
function should_stop(node, parent) {
if (node === rvalue) return true;
if (parent instanceof AST_For) return node !== parent.init;
if (parent instanceof AST_For) {
if (node !== parent.init) return true;
}
if (node instanceof AST_Assign) {
return node.operator != "=" && lhs.equivalent_to(node.left);
}
@@ -2014,7 +2048,8 @@ merge(Compressor.prototype, {
}
function in_conditional(node, parent) {
if (parent instanceof AST_Binary) return lazy_op[parent.operator] && parent.left !== node;
if (parent instanceof AST_Assign) return parent.left !== node && lazy_op[parent.operator.slice(0, -1)];
if (parent instanceof AST_Binary) return parent.left !== node && lazy_op[parent.operator];
if (parent instanceof AST_Case) return parent.expression !== node;
if (parent instanceof AST_Conditional) return parent.condition !== node;
return parent instanceof AST_If && parent.condition !== node;
@@ -2356,21 +2391,36 @@ merge(Compressor.prototype, {
return null;
}
function find_stop_logical(parent, op, level) {
var node;
do {
node = parent;
parent = scanner.parent(++level);
} while (parent instanceof AST_Assign && parent.operator.slice(0, -1) == op
|| parent instanceof AST_Binary && parent.operator == op);
return node;
}
function find_stop_value(node, level) {
var parent = scanner.parent(level);
if (parent instanceof AST_Array) return find_stop_value(parent, level + 1);
if (parent instanceof AST_Assign) return may_throw(parent) || parent.left.match_symbol(function(ref) {
return ref instanceof AST_SymbolRef && (lhs.name == ref.name || value_def.name == ref.name);
}) ? node : find_stop_value(parent, level + 1);
if (parent instanceof AST_Binary) {
if (lazy_op[parent.operator] && parent.left !== node) {
do {
node = parent;
parent = scanner.parent(++level);
} while (parent instanceof AST_Binary && parent.operator == node.operator);
return node;
if (parent instanceof AST_Assign) {
if (may_throw(parent)) return node;
if (parent.left.match_symbol(function(ref) {
return ref instanceof AST_SymbolRef && (lhs.name == ref.name || value_def.name == ref.name);
})) return node;
var op;
if (parent.left === node || !lazy_op[op = parent.operator.slice(0, -1)]) {
return find_stop_value(parent, level + 1);
}
return find_stop_value(parent, level + 1);
return find_stop_logical(parent, op, level);
}
if (parent instanceof AST_Binary) {
var op;
if (parent.left === node || !lazy_op[op = parent.operator]) {
return find_stop_value(parent, level + 1);
}
return find_stop_logical(parent, op, level);
}
if (parent instanceof AST_Call) return parent;
if (parent instanceof AST_Case) {
@@ -3004,7 +3054,7 @@ merge(Compressor.prototype, {
});
}
function can_merge_flow(ab) {
function can_drop_abort(ab) {
if (ab instanceof AST_Return) return in_lambda && is_return_void(ab.value);
if (!(ab instanceof AST_LoopControl)) return false;
var lct = compressor.loopcontrol_target(ab);
@@ -3013,16 +3063,35 @@ merge(Compressor.prototype, {
return match_target(lct);
}
function can_merge_flow(ab) {
if (!can_drop_abort(ab)) return false;
for (var j = statements.length; --j > i;) {
var stat = statements[j];
if (stat instanceof AST_DefClass) {
if (stat.name.definition().preinit) return false;
} else if (stat instanceof AST_Const || stat instanceof AST_Let) {
if (!all(stat.definitions, function(defn) {
return !defn.name.match_symbol(function(node) {
return node instanceof AST_SymbolDeclaration && node.definition().preinit;
});
})) return false;
}
}
return true;
}
function extract_functions() {
var defuns = [];
var lexical = false;
var tail = statements.splice(i + 1).filter(function(stat) {
if (stat instanceof AST_LambdaDefinition) {
defuns.push(stat);
return false;
}
if (is_lexical_definition(stat)) lexical = true;
return true;
});
[].push.apply(all(tail, safe_to_trim) ? statements : tail, defuns);
[].push.apply(lexical ? tail : statements, defuns);
return tail;
}
@@ -3659,20 +3728,25 @@ merge(Compressor.prototype, {
(function(def) {
def(AST_Node, return_false);
def(AST_Array, return_true);
def(AST_Assign, function(compressor) {
return this.operator != "=" || this.right.is_defined(compressor);
});
def(AST_Binary, function(compressor) {
switch (this.operator) {
function is_binary_defined(compressor, op, node) {
switch (op) {
case "&&":
return this.left.is_defined(compressor) && this.right.is_defined(compressor);
return node.left.is_defined(compressor) && node.right.is_defined(compressor);
case "||":
return this.left.is_truthy() || this.right.is_defined(compressor);
return node.left.is_truthy() || node.right.is_defined(compressor);
case "??":
return this.left.is_defined(compressor) || this.right.is_defined(compressor);
return node.left.is_defined(compressor) || node.right.is_defined(compressor);
default:
return true;
}
}
def(AST_Assign, function(compressor) {
var op = this.operator;
if (op == "=") return this.right.is_defined(compressor);
return is_binary_defined(compressor, op.slice(0, -1), this);
});
def(AST_Binary, function(compressor) {
return is_binary_defined(compressor, this.operator, this);
});
def(AST_Conditional, function(compressor) {
return this.consequent.is_defined(compressor) && this.alternative.is_defined(compressor);
@@ -5285,8 +5359,9 @@ merge(Compressor.prototype, {
var tw = new TreeWalker(function(node, descend) {
if (node instanceof AST_Assign) {
var lhs = node.left;
var rhs = node.right;
if (lhs instanceof AST_Destructured) {
node.right.walk(tw);
rhs.walk(tw);
var marker = new TreeWalker(function(node) {
if (node instanceof AST_Destructured) return;
if (node instanceof AST_DefaultValue) {
@@ -5314,9 +5389,17 @@ merge(Compressor.prototype, {
lhs.walk(marker);
return true;
}
if (lazy_op[node.operator.slice(0, -1)]) {
lhs.walk(tw);
push();
rhs.walk(tw);
if (lhs instanceof AST_SymbolRef) mark(lhs);
pop();
return true;
}
if (lhs instanceof AST_SymbolRef) {
if (node.operator != "=") mark(lhs, true);
node.right.walk(tw);
rhs.walk(tw);
mark(lhs);
return true;
}
@@ -6191,6 +6274,7 @@ merge(Compressor.prototype, {
&& var_defs[sym.id] == 1
&& sym.assignments == 0
&& value instanceof AST_LambdaExpression
&& !is_arguments(sym)
&& !is_arrow(value)
&& assigned_once(value, sym.references)
&& can_declare_defun(value)
@@ -6872,7 +6956,9 @@ merge(Compressor.prototype, {
&& vars.has(sym.name)) {
var def = vars.get(sym.name);
if (def.value) break;
def.value = expr.right.clone();
var value = expr.right;
if (value instanceof AST_Sequence) value = value.clone();
def.value = value;
remove(defs, def);
defs.push(def);
body.shift();
@@ -7211,9 +7297,14 @@ merge(Compressor.prototype, {
if (compressor.has_directive("use strict") && expr.is_constant()) return this;
}
if (left.has_side_effects(compressor)) return this;
this.write_only = true;
if (root_expr(left).is_constant_expression(compressor.find_parent(AST_Scope))) {
return this.right.drop_side_effect_free(compressor);
var right = this.right;
if (lazy_op[this.operator.slice(0, -1)]) {
this.write_only = !right.has_side_effects(compressor);
} else {
this.write_only = true;
if (root_expr(left).is_constant_expression(compressor.find_parent(AST_Scope))) {
return right.drop_side_effect_free(compressor);
}
}
return this;
});
@@ -7340,10 +7431,18 @@ merge(Compressor.prototype, {
});
def(AST_Class, function(compressor, first_in_statement) {
var exprs = [], values = [];
this.properties.forEach(function(prop) {
var props = this.properties;
for (var i = 0; i < props.length; i++) {
var prop = props[i];
if (prop.key instanceof AST_Node) exprs.push(prop.key);
if (prop instanceof AST_ClassField && prop.static && prop.value) values.push(prop.value);
});
if (prop instanceof AST_ClassField
&& prop.static
&& prop.value
&& !(prop.value instanceof AST_Lambda)) {
if (prop.value.contains_this()) return this;
values.push(prop.value);
}
}
var base = this.extends;
if (base) {
if (base instanceof AST_SymbolRef) base = base.fixed_value();
@@ -8349,16 +8448,19 @@ merge(Compressor.prototype, {
if (!all(args, function(arg) {
return !(arg instanceof AST_Spread);
})) return;
var argnames = fn.argnames;
var is_iife = fn === exp && !fn.name;
if (fn.rest) {
if (!(is_iife && compressor.option("rests"))) return;
var insert = fn.argnames.length;
var insert = argnames.length;
args = args.slice(0, insert);
while (args.length < insert) args.push(make_node(AST_Undefined, call).optimize(compressor));
args.push(make_node(AST_Array, call, { elements: call.args.slice(insert) }));
call.args = args;
fn.argnames = fn.argnames.concat(fn.rest);
argnames = argnames.concat(fn.rest);
fn.rest = null;
} else {
args = args.slice();
argnames = argnames.slice();
}
var pos = 0, last = 0;
var drop_defaults = is_iife && compressor.option("default_values");
@@ -8374,14 +8476,14 @@ merge(Compressor.prototype, {
} : return_false;
var side_effects = [];
for (var i = 0; i < args.length; i++) {
var argname = fn.argnames[i];
var argname = argnames[i];
if (drop_defaults && argname instanceof AST_DefaultValue && args[i].is_defined(compressor)) {
fn.argnames[i] = argname = argname.name;
argnames[i] = argname = argname.name;
}
if (!argname || "__unused" in argname) {
var node = args[i].drop_side_effect_free(compressor);
if (drop_fargs(argname)) {
if (argname) fn.argnames.splice(i, 1);
if (argname) argnames.splice(i, 1);
args.splice(i, 1);
if (node) side_effects.push(node);
i--;
@@ -8403,7 +8505,7 @@ merge(Compressor.prototype, {
}
} else if (drop_fargs(argname, args[i])) {
var node = args[i].drop_side_effect_free(compressor);
fn.argnames.splice(i, 1);
argnames.splice(i, 1);
args.splice(i, 1);
if (node) side_effects.push(node);
i--;
@@ -8415,15 +8517,17 @@ merge(Compressor.prototype, {
}
last = pos;
}
for (; i < fn.argnames.length; i++) {
if (drop_fargs(fn.argnames[i])) fn.argnames.splice(i--, 1);
for (; i < argnames.length; i++) {
if (drop_fargs(argnames[i])) argnames.splice(i--, 1);
}
fn.argnames = argnames;
args.length = last;
call.args = args;
if (!side_effects.length) return;
var arg = make_sequence(call, side_effects);
args.push(args.length < fn.argnames.length ? make_node(AST_UnaryPrefix, call, {
args.push(args.length < argnames.length ? make_node(AST_UnaryPrefix, call, {
operator: "void",
expression: arg
expression: arg,
}) : arg);
}
@@ -8752,7 +8856,7 @@ merge(Compressor.prototype, {
var can_inline = can_drop && compressor.option("inline") && !self.is_expr_pure(compressor);
if (can_inline && stat instanceof AST_Return) {
var value = stat.value;
if (exp === fn && (!value || value.is_constant_expression()) && safe_from_await_yield(fn)) {
if (exp === fn && !fn.name && (!value || value.is_constant_expression()) && safe_from_await_yield(fn)) {
return make_sequence(self, convert_args(value)).optimize(compressor);
}
}
@@ -9308,6 +9412,7 @@ merge(Compressor.prototype, {
var seq = lift_sequence_in_expression(self, compressor);
if (seq !== self) return seq.optimize(compressor);
}
if (compressor.option("unused")) drop_unused_call_args(self, compressor);
if (compressor.option("unsafe")) {
var exp = self.expression;
if (is_undeclared_ref(exp)) {
@@ -9589,6 +9694,7 @@ merge(Compressor.prototype, {
if (node instanceof AST_Sequence) return is_object(node.tail_node());
if (node instanceof AST_SymbolRef) return is_object(node.fixed_value());
return node instanceof AST_Array
|| node instanceof AST_Class
|| node instanceof AST_Lambda
|| node instanceof AST_New
|| node instanceof AST_Object;
@@ -11352,7 +11458,7 @@ merge(Compressor.prototype, {
AST_Arrow.DEFMETHOD("contains_this", return_false);
AST_AsyncArrow.DEFMETHOD("contains_this", return_false);
AST_Scope.DEFMETHOD("contains_this", function() {
AST_Node.DEFMETHOD("contains_this", function() {
var result;
var self = this;
self.walk(new TreeWalker(function(node) {
@@ -11379,15 +11485,18 @@ merge(Compressor.prototype, {
if (prop.key != key) continue;
if (!all(props, can_hoist_property)) break;
if (!safe_to_flatten(prop.value, compressor)) break;
props = props.map(function(prop) {
return prop.value;
});
if (prop instanceof AST_ObjectMethod
&& prop.value instanceof AST_Function
&& !(compressor.parent() instanceof AST_Call)) {
if (prop.value.uses_arguments) break;
props[i] = make_node(AST_Arrow, prop.value, prop.value);
}
return make_node(AST_Sub, this, {
expression: make_node(AST_Array, expr, {
elements: props.map(function(prop) {
return prop.value;
})
}),
property: make_node(AST_Number, this, {
value: i
})
expression: make_node(AST_Array, expr, { elements: props }),
property: make_node(AST_Number, this, { value: i }),
});
}
}

View File

@@ -49,8 +49,6 @@ function is_some_comments(comment) {
}
function OutputStream(options) {
var readonly = !options;
options = defaults(options, {
annotations : false,
ascii_only : false,
@@ -103,12 +101,35 @@ function OutputStream(options) {
}
}
var indentation = options.indent_start;
var current_col = 0;
var current_line = 1;
var current_pos = 0;
var OUTPUT = "";
var indentation = options.indent_start;
var last;
var line_end = 0;
var line_fixed = true;
var mappings = options.source_map && [];
var mapping_name;
var mapping_token;
var might_need_space;
var might_need_semicolon;
var need_newline_indented = false;
var need_space = false;
var newline_insert = -1;
var stack;
var OUTPUT;
function reset() {
last = "";
might_need_space = false;
might_need_semicolon = false;
stack = [];
var str = OUTPUT;
OUTPUT = "";
return str;
}
reset();
var to_utf8 = options.ascii_only ? function(str, identifier) {
if (identifier) str = str.replace(/[\ud800-\udbff][\udc00-\udfff]/g, function(ch) {
return "\\u{" + (ch.charCodeAt(0) - 0xd7c0 << 10 | ch.charCodeAt(1) - 0xdc00).toString(16) + "}";
@@ -141,6 +162,25 @@ function OutputStream(options) {
return j == 0 ? str : s + str.slice(j);
};
function quote_single(str) {
return "'" + str.replace(/\x27/g, "\\'") + "'";
}
function quote_double(str) {
return '"' + str.replace(/\x22/g, '\\"') + '"';
}
var quote_string = [
null,
quote_single,
quote_double,
function(str, quote) {
return quote == "'" ? quote_single(str) : quote_double(str);
},
][options.quote_style] || function(str, quote, dq, sq) {
return dq > sq ? quote_single(str) : quote_double(str);
};
function make_string(str, quote) {
var dq = 0, sq = 0;
str = str.replace(/[\\\b\f\n\r\v\t\x22\x27\u2028\u2029\0\ufeff]/g, function(s, i) {
@@ -162,54 +202,11 @@ function OutputStream(options) {
}
return s;
});
function quote_single() {
return "'" + str.replace(/\x27/g, "\\'") + "'";
}
function quote_double() {
return '"' + str.replace(/\x22/g, '\\"') + '"';
}
str = to_utf8(str);
switch (options.quote_style) {
case 1:
return quote_single();
case 2:
return quote_double();
case 3:
return quote == "'" ? quote_single() : quote_double();
default:
return dq > sq ? quote_single() : quote_double();
}
}
function encode_string(str, quote) {
var ret = make_string(str, quote);
if (options.inline_script) {
ret = ret.replace(/<\x2f(script)([>\/\t\n\f\r ])/gi, "<\\/$1$2");
ret = ret.replace(/\x3c!--/g, "\\x3c!--");
ret = ret.replace(/--\x3e/g, "--\\x3e");
}
return ret;
}
function make_name(name) {
name = name.toString();
name = to_utf8(name, true);
return name;
return quote_string(to_utf8(str), quote, dq, sq);
}
/* -----[ beautification/minification ]----- */
var has_parens = false;
var line_end = 0;
var line_fixed = true;
var might_need_space = false;
var might_need_semicolon = false;
var need_newline_indented = false;
var need_space = false;
var newline_insert = -1;
var last = "";
var mapping_token, mapping_name, mappings = options.source_map && [];
var adjust_mappings = mappings ? function(line, col) {
mappings.forEach(function(mapping) {
mapping.line += line;
@@ -257,8 +254,14 @@ function OutputStream(options) {
var requireSemicolonChars = makePredicate("( [ + * / - , .");
function print(str) {
str = String(str);
var print = options.beautify
|| options.comments
|| options.max_line_len
|| options.preserve_line
|| options.shebang
|| !options.semicolons
|| options.source_map
|| options.width ? function(str) {
var ch = str.charAt(0);
if (need_newline_indented && ch) {
need_newline_indented = false;
@@ -328,7 +331,6 @@ function OutputStream(options) {
}
OUTPUT += str;
has_parens = str.slice(-1) == "(";
current_pos += str.length;
var a = str.split(/\r?\n/), n = a.length - 1;
current_line += n;
@@ -338,7 +340,30 @@ function OutputStream(options) {
current_col = a[n].length;
}
last = str;
}
} : function(str) {
var ch = str.charAt(0);
var prev = last.slice(-1);
if (might_need_semicolon) {
might_need_semicolon = false;
if (prev == ":" && ch == "}" || (!ch || ";}".indexOf(ch) < 0) && prev != ";") {
OUTPUT += ";";
might_need_space = false;
}
}
if (might_need_space) {
if (is_identifier_char(prev) && (is_identifier_char(ch) || ch == "\\")
|| (ch == "/" && ch == prev)
|| ((ch == "+" || ch == "-") && ch == last)
|| str == "--" && last == "!"
|| str == "in" && prev == "/"
|| last == "--" && ch == ">") {
OUTPUT += " ";
}
if (prev != "<" || str != "!") might_need_space = false;
}
OUTPUT += str;
last = str;
};
var space = options.beautify ? function() {
print(" ");
@@ -351,14 +376,12 @@ function OutputStream(options) {
print(repeat_string(" ", half ? indentation - (options.indent_level >> 1) : indentation));
} : noop;
var with_indent = options.beautify ? function(col, cont) {
if (col === true) col = next_indent();
var with_indent = options.beautify ? function(cont) {
var save_indentation = indentation;
indentation = col;
var ret = cont();
indentation += options.indent_level;
cont();
indentation = save_indentation;
return ret;
} : function(col, cont) { return cont() };
} : function(cont) { cont() };
var may_add_newline = options.max_line_len || options.preserve_line ? function() {
fix_line();
@@ -387,41 +410,28 @@ function OutputStream(options) {
print(";");
}
function next_indent() {
return indentation + options.indent_level;
}
function with_block(cont) {
var ret;
print("{");
newline();
with_indent(next_indent(), function() {
ret = cont();
});
with_indent(cont);
indent();
print("}");
return ret;
}
function with_parens(cont) {
print("(");
may_add_newline();
//XXX: still nice to have that for argument lists
//var ret = with_indent(current_col, cont);
var ret = cont();
cont();
may_add_newline();
print(")");
return ret;
}
function with_square(cont) {
print("[");
may_add_newline();
//var ret = with_indent(current_col, cont);
var ret = cont();
cont();
may_add_newline();
print("]");
return ret;
}
function comma() {
@@ -553,15 +563,14 @@ function OutputStream(options) {
if (OUTPUT.length > insert) newline_insert = insert;
}
var stack = [];
return {
get : get,
toString : get,
reset : reset,
indent : indent,
should_break : readonly ? noop : function() {
return options.width && current_col - indentation >= options.width;
},
has_parens : function() { return has_parens },
should_break : options.width ? function() {
return current_col - indentation >= options.width;
} : return_false,
has_parens : function() { return last.slice(-1) == "(" },
newline : newline,
print : print,
space : space,
@@ -571,20 +580,21 @@ function OutputStream(options) {
semicolon : semicolon,
force_semicolon : force_semicolon,
to_utf8 : to_utf8,
print_name : function(name) { print(make_name(name)) },
print_string : function(str, quote) { print(encode_string(str, quote)) },
next_indent : next_indent,
print_name : function(name) { print(to_utf8(name.toString(), true)) },
print_string : options.inline_script ? function(str, quote) {
str = make_string(str, quote).replace(/<\x2f(script)([>\/\t\n\f\r ])/gi, "<\\/$1$2");
print(str.replace(/\x3c!--/g, "\\x3c!--").replace(/--\x3e/g, "--\\x3e"));
} : function(str, quote) {
print(make_string(str, quote));
},
with_indent : with_indent,
with_block : with_block,
with_parens : with_parens,
with_square : with_square,
add_mapping : add_mapping,
option : function(opt) { return options[opt] },
prepend_comments: readonly ? noop : prepend_comments,
append_comments : readonly || comment_filter === return_false ? noop : append_comments,
line : function() { return current_line },
col : function() { return current_col },
pos : function() { return current_pos },
prepend_comments: options.comments || options.shebang ? prepend_comments : noop,
append_comments : options.comments ? append_comments : noop,
push_node : function(node) { stack.push(node) },
pop_node : options.preserve_line ? function() {
var node = stack.pop();
@@ -629,10 +639,19 @@ function OutputStream(options) {
stream.append_comments(self);
}
});
var readonly = OutputStream({
inline_script: false,
shebang: false,
width: false,
});
AST_Node.DEFMETHOD("print_to_string", function(options) {
var s = OutputStream(options);
this.print(s);
return s.get();
if (options) {
var stream = OutputStream(options);
this.print(stream);
return stream.get();
}
this.print(readonly);
return readonly.reset();
});
/* -----[ PARENTHESES ]----- */
@@ -915,7 +934,7 @@ function OutputStream(options) {
});
function print_braced_empty(self, output) {
output.print("{");
output.with_indent(output.next_indent(), function() {
output.with_indent(function() {
output.append_comments(self, true);
});
output.print("}");
@@ -1481,11 +1500,7 @@ function OutputStream(options) {
output.print_string(prop);
output.print("]");
} else {
if (expr instanceof AST_Number && expr.value >= 0) {
if (!/[xa-f.)]/i.test(output.last())) {
output.print(".");
}
}
if (expr instanceof AST_Number && !/[ex.)]/i.test(output.last())) output.print(".");
output.print(".");
// the name after dot would be mapped about here.
output.add_mapping(self.end);
@@ -1735,7 +1750,7 @@ function OutputStream(options) {
output.print("`");
});
DEFPRINT(AST_Constant, function(output) {
output.print(this.value);
output.print("" + this.value);
});
DEFPRINT(AST_String, function(output) {
output.print_string(this.value, this.quote);
@@ -1791,7 +1806,7 @@ function OutputStream(options) {
function force_statement(stat, output) {
if (output.option("braces") && !(stat instanceof AST_Const || stat instanceof AST_Let)) {
make_block(stat, output);
} else if (!stat || stat instanceof AST_EmptyStatement) {
} else if (stat instanceof AST_EmptyStatement) {
output.force_semicolon();
} else {
stat.print(output);
@@ -1842,11 +1857,11 @@ function OutputStream(options) {
}
function make_block(stmt, output) {
if (!stmt || stmt instanceof AST_EmptyStatement)
output.print("{}");
else if (stmt instanceof AST_BlockStatement)
if (stmt instanceof AST_EmptyStatement) {
print_braced_empty(stmt, output);
} else if (stmt instanceof AST_BlockStatement) {
stmt.print(output);
else output.with_block(function() {
} else output.with_block(function() {
output.indent();
stmt.print(output);
output.newline();

View File

@@ -104,12 +104,15 @@ var OPERATORS = makePredicate([
">>=",
"<<=",
">>>=",
"&=",
"|=",
"^=",
"&=",
"&&",
"||",
"??",
"&&=",
"||=",
"??=",
]);
var NEWLINE_CHARS = "\n\r\u2028\u2029";
@@ -348,7 +351,7 @@ function tokenizer($TEXT, filename, html5_comments, shebang) {
function read_while(pred) {
var ret = "", ch;
while ((ch = peek()) && pred(ch)) ret += next();
while ((ch = peek()) && pred(ch, ret)) ret += next();
return ret;
}
@@ -356,24 +359,27 @@ function tokenizer($TEXT, filename, html5_comments, shebang) {
js_error(err, filename, S.tokline, S.tokcol, S.tokpos);
}
function is_octal(num) {
return /^0[0-7_]+$/.test(num);
}
function read_num(prefix) {
var has_e = false, after_e = false, has_x = false, has_dot = prefix == ".";
var num = read_while(function(ch) {
var code = ch.charCodeAt(0);
switch (code) {
case 120: case 88: // xX
var num = read_while(function(ch, str) {
switch (ch) {
case "x": case "X":
return has_x ? false : (has_x = true);
case 101: case 69: // eE
case "e": case "E":
return has_x ? true : has_e ? false : (has_e = after_e = true);
case 43: case 45: // +-
case "+": case "-":
return after_e;
case (after_e = false, 46): // .
return (!has_dot && !has_x && !has_e) ? (has_dot = true) : false;
case (after_e = false, "."):
return has_dot || has_e || has_x || is_octal(str) ? false : (has_dot = true);
}
return is_digit(code) || /[_0-9a-fo]/i.test(ch);
return /[_0-9a-dfo]/i.test(ch);
});
if (prefix) num = prefix + num;
if (/^0[0-7_]+$/.test(num)) {
if (is_octal(num)) {
if (next_token.has_directive("use strict")) parse_error("Legacy octal literals are not allowed in strict mode");
} else {
num = num.replace(has_x ? /([1-9a-f]|.0)_(?=[0-9a-f])/gi : /([1-9]|.0)_(?=[0-9])/gi, "$1");
@@ -653,7 +659,7 @@ var UNARY_PREFIX = makePredicate("typeof void delete -- ++ ! ~ - +");
var UNARY_POSTFIX = makePredicate("-- ++");
var ASSIGNMENT = makePredicate("= += -= /= *= %= **= >>= <<= >>>= |= ^= &=");
var ASSIGNMENT = makePredicate("= += -= /= *= %= **= >>= <<= >>>= &= |= ^= &&= ||= ??=");
var PRECEDENCE = function(a, ret) {
for (var i = 0; i < a.length;) {
@@ -851,7 +857,8 @@ function parse($TEXT, options) {
next();
return export_();
case "import":
if (!is_token(peek(), "punc", "(")) {
var token = peek();
if (!(token.type == "punc" && /^[(.]$/.test(token.value))) {
next();
return import_();
}
@@ -1182,7 +1189,7 @@ function parse($TEXT, options) {
var await = is("name", "await") && next();
expect("(");
var init = null;
if (!is("punc", ";")) {
if (await || !is("punc", ";")) {
init = is("keyword", "const")
? (next(), const_(true))
: is("keyword", "let")

View File

@@ -81,7 +81,9 @@ var builtins = function() {
function reserve_quoted_keys(ast, reserved) {
ast.walk(new TreeWalker(function(node) {
if (node instanceof AST_ObjectProperty) {
if (node instanceof AST_ClassProperty) {
if (node.start && node.start.quote) add(node.key);
} else if (node instanceof AST_ObjectProperty) {
if (node.start && node.start.quote) add(node.key);
} else if (node instanceof AST_Sub) {
addStrings(node.property, add);
@@ -163,6 +165,8 @@ function mangle_properties(ast, options) {
addStrings(node.args[0], add);
break;
}
} else if (node instanceof AST_ClassProperty) {
if (typeof node.key == "string") add(node.key);
} else if (node instanceof AST_Dot) {
add(node.property);
} else if (node instanceof AST_ObjectProperty) {
@@ -193,6 +197,8 @@ function mangle_properties(ast, options) {
mangleStrings(node.args[0]);
break;
}
} else if (node instanceof AST_ClassProperty) {
if (typeof node.key == "string") node.key = mangle(node.key);
} else if (node instanceof AST_Dot) {
node.property = mangle(node.property);
} else if (node instanceof AST_ObjectProperty) {
@@ -222,9 +228,7 @@ function mangle_properties(ast, options) {
}
function mangle(name) {
if (!should_mangle(name)) {
return name;
}
if (!should_mangle(name)) return name;
var mangled = cache.get(name);
if (!mangled) {
if (debug) {
@@ -236,6 +240,7 @@ function mangle_properties(ast, options) {
if (!mangled) do {
mangled = base54(++cname);
} while (!can_mangle(mangled));
if (/^#/.test(name)) mangled = "#" + mangled;
cache.set(name, mangled);
}
return mangled;

View File

@@ -273,16 +273,18 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options) {
return true;
}
if (node instanceof AST_SymbolDeclaration) {
var def = node.definition();
def.preinit = def.references.length;
if (node instanceof AST_SymbolCatch) {
// ensure mangling works if `catch` reuses a scope variable
var def = node.definition().redefined();
if (def) for (var s = node.scope; s; s = s.parent_scope) {
push_uniq(s.enclosed, def);
if (s === def.scope) break;
var redef = def.redefined();
if (redef) for (var s = node.scope; s; s = s.parent_scope) {
push_uniq(s.enclosed, redef);
if (s === redef.scope) break;
}
} else if (node instanceof AST_SymbolConst) {
// ensure compression works if `const` reuses a scope variable
var redef = node.definition().redefined();
var redef = def.redefined();
if (redef) redef.const_redefs = true;
}
if (node.name != "arguments") return true;

View File

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

View File

@@ -475,3 +475,200 @@ issue_4521: {
}
expect_stdout: "42"
}
logical_assignments: {
input: {
var a = 42, b = null, c;
a &&= "foo";
b ||= "bar";
c ??= "baz";
console.log(a, b, c);
}
expect_exact: 'var a=42,b=null,c;a&&="foo";b||="bar";c??="baz";console.log(a,b,c);'
expect_stdout: "foo bar baz"
node_version: ">=15"
}
logical_collapse_vars: {
options = {
collapse_vars: true,
}
input: {
var a = "FAIL", b = false;
a = "PASS";
b ??= a;
console.log(a);
}
expect: {
var a = "FAIL", b = false;
a = "PASS";
b ??= a;
console.log(a);
}
expect_stdout: "PASS"
node_version: ">=15"
}
logical_reduce_vars: {
options = {
evaluate: true,
reduce_vars: true,
toplevel: true,
unused: true,
}
input: {
var a = "PASS", b = 42;
b ??= a = "FAIL";
console.log(a);
}
expect: {
var a = "PASS", b = 42;
b ??= a = "FAIL";
console.log(a);
}
expect_stdout: "PASS"
node_version: ">=15"
}
logical_side_effects: {
options = {
side_effects: true,
toplevel: true,
unused: true,
}
input: {
var a = "PASS", b = 42;
b ??= a = "FAIL";
console.log(a);
}
expect: {
var a = "PASS", b = 42;
b ??= a = "FAIL";
console.log(a);
}
expect_stdout: "PASS"
node_version: ">=15"
}
issue_4815_1: {
options = {
evaluate: true,
reduce_vars: true,
toplevel: true,
unused: true,
}
input: {
var a = "PASS";
42..p &&= a = "FAIL";
console.log(a);
}
expect: {
var a = "PASS";
42..p &&= a = "FAIL";
console.log(a);
}
expect_stdout: "PASS"
node_version: ">=15"
}
issue_4815_2: {
options = {
pure_getters: "strict",
side_effects: true,
}
input: {
var a = "PASS";
42..p &&= a = "FAIL";
console.log(a);
}
expect: {
var a = "PASS";
42..p &&= a = "FAIL";
console.log(a);
}
expect_stdout: "PASS"
node_version: ">=15"
}
issue_4819: {
options = {
comparisons: true,
}
input: {
console.log(void 0 === ([].p &&= 42));
}
expect: {
console.log(void 0 === ([].p &&= 42));
}
expect_stdout: "true"
node_version: ">=15"
}
issue_4827_1: {
options = {
collapse_vars: true,
toplevel: true,
}
input: {
A = "FAIL";
var a = A, b = "PASS", c;
c &&= b = a, console.log(b);
}
expect: {
A = "FAIL";
var a = A, b = "PASS", c;
c &&= b = a, console.log(b);
}
expect_stdout: "PASS"
node_version: ">=15"
}
issue_4827_2: {
options = {
collapse_vars: true,
inline: true,
reduce_vars: true,
side_effects: true,
toplevel: true,
unused: true,
}
input: {
var a = 0, b = "PASS";
function f(c) {
a++,
c &&= b = a;
}
f();
console.log(b);
}
expect: {
var a = 0, b = "PASS";
a++,
c &&= b = a;
var c;
console.log(b);
}
expect_stdout: "PASS"
node_version: ">=15"
}
issue_4827_3: {
options = {
merge_vars: true,
toplevel: true,
}
input: {
var a = 0, b, c;
a++;
c &&= b = a;
console.log(b);
}
expect: {
var a = 0, b, c;
a++;
c &&= b = a;
console.log(b);
}
expect_stdout: "undefined"
node_version: ">=15"
}

View File

@@ -583,6 +583,31 @@ single_use_6: {
node_version: ">=4"
}
single_use_7: {
options = {
passes: 2,
reduce_vars: true,
toplevel: true,
unused: true,
}
input: {
"use strict";
class A {
static foo() {}
}
var a = "foo" in A;
console.log(a);
}
expect: {
"use strict";
console.log("foo" in class {
static foo() {}
});
}
expect_stdout: "true"
node_version: ">=4"
}
collapse_non_strict: {
options = {
collapse_vars: true,
@@ -654,6 +679,32 @@ collapse_rhs_static: {
node_version: ">=12"
}
self_comparison: {
options = {
booleans: true,
comparisons: true,
reduce_vars: true,
toplevel: true,
unused: true,
}
input: {
"use strict";
class A {}
console.log(A == A, A != A);
console.log(A === A, A !== A);
}
expect: {
"use strict";
console.log(!0, !1);
console.log(!0, !1);
}
expect_stdout: [
"true false",
"true false",
]
node_version: ">=4"
}
property_side_effects: {
options = {
inline: true,
@@ -1296,3 +1347,197 @@ issue_4756: {
]
node_version: ">=12"
}
issue_4821_1: {
options = {
reduce_vars: true,
toplevel: true,
unused: true,
}
input: {
var a;
class A {
static p = void (a = this);
}
console.log(typeof a);
}
expect: {
var a;
class A {
static p = void (a = this);
}
console.log(typeof a);
}
expect_stdout: "function"
node_version: ">=12"
}
issue_4821_2: {
options = {
side_effects: true,
toplevel: true,
unused: true,
}
input: {
var a;
class A {
static p = void (a = this);
}
console.log(typeof a);
}
expect: {
var a;
(class {
static p = void (a = this);
});
console.log(typeof a);
}
expect_stdout: "function"
node_version: ">=12"
}
issue_4829_1: {
options = {
properties: true,
}
input: {
"use strict";
try {
class A extends { f(){} }.f {}
} catch (e) {
console.log("PASS");
}
}
expect: {
"use strict";
try {
class A extends [ () => {} ][0] {}
} catch (e) {
console.log("PASS");
}
}
expect_stdout: "PASS"
node_version: ">=4"
}
issue_4829_2: {
options = {
properties: true,
}
input: {
"use strict";
try {
class A extends {
f() {
return arguments;
},
}.f {}
} catch (e) {
console.log("PASS");
}
}
expect: {
"use strict";
try {
class A extends {
f() {
return arguments;
},
}.f {}
} catch (e) {
console.log("PASS");
}
}
expect_stdout: "PASS"
node_version: ">=4"
}
mangle_properties: {
mangle = {
properties: {
keep_quoted: true,
},
}
input: {
class A {
static #P = "PASS";
static get Q() {
return this.#P;
}
#p(n) {
return (this["q"] = n) * this.r;
}
set q(v) {
this.r = v + 1;
}
r = this.#p(6);
}
console.log(A.Q, new A().r);
}
expect: {
class A {
static #t = "PASS";
static get s() {
return this.#t;
}
#i(t) {
return (this["q"] = t) * this.e;
}
set q(t) {
this.e = t + 1;
}
e = this.#i(6);
}
console.log(A.s, new A().e);
}
expect_stdout: "PASS 42"
node_version: ">=14.6"
}
issue_4848: {
options = {
if_return: true,
}
input: {
"use strict";
function f(a) {
a(function() {
new A();
});
if (!console)
return;
class A {
constructor() {
console.log("PASS");
}
}
}
var g;
f(function(h) {
g = h;
});
g();
}
expect: {
"use strict";
function f(a) {
a(function() {
new A();
});
if (!console)
return;
class A {
constructor() {
console.log("PASS");
}
}
}
var g;
f(function(h) {
g = h;
});
g();
}
expect_stdout: "PASS"
node_version: ">=4"
}

View File

@@ -2868,7 +2868,7 @@ lvalues_def: {
expect_stdout: true
}
compound_assignment: {
compound_assignment_1: {
options = {
collapse_vars: true,
}
@@ -2887,6 +2887,23 @@ compound_assignment: {
expect_stdout: "4"
}
compound_assignment_2: {
options = {
collapse_vars: true,
}
input: {
var a;
a = 1;
for (a += a + 2; console.log(a););
}
expect: {
var a;
a = 1;
for (a += a + 2; console.log(a););
}
expect_stdout: "4"
}
issue_2187_1: {
options = {
collapse_vars: true,
@@ -8880,3 +8897,38 @@ issue_4806: {
}
expect_stdout: "PASS"
}
issue_4852: {
options = {
collapse_vars: true,
}
input: {
var a = "PASS";
(function(b) {
switch (b = a) {
case 42:
try {
console;
} catch (b) {
b.p;
}
case console.log(b):
}
})("FAIL");
}
expect: {
var a = "PASS";
(function(b) {
switch (a) {
case 42:
try {
console;
} catch (b) {
b.p;
}
case console.log(a):
}
})("FAIL");
}
expect_stdout: "PASS"
}

View File

@@ -1498,3 +1498,40 @@ issue_4691: {
}
expect_stdout: "PASS"
}
issue_4848: {
options = {
if_return: true,
}
input: {
function f(a) {
a(function() {
console.log(b);
});
if (!console)
return;
const b = "PASS";
}
var g;
f(function(h) {
g = h;
});
g();
}
expect: {
function f(a) {
a(function() {
console.log(b);
});
if (!console)
return;
const b = "PASS";
}
var g;
f(function(h) {
g = h;
});
g();
}
expect_stdout: "PASS"
}

View File

@@ -1661,3 +1661,23 @@ issue_4588_2_evaluate: {
expect_stdout: "1"
node_version: ">=6"
}
issue_4817: {
options = {
ie8: true,
inline: true,
unused: true,
}
input: {
(function f(a = console.log(typeof f)) {
return 42;
})();
}
expect: {
(function f(a = console.log(typeof f)) {
return 42;
})();
}
expect_stdout: "function"
node_version: ">=6"
}

View File

@@ -3357,3 +3357,34 @@ issue_4806_3: {
}
expect_stdout: "PASS"
}
issue_4834: {
options = {
inline: true,
keep_fargs: false,
pure_getters: "strict",
reduce_vars: true,
side_effects: true,
toplevel: true,
unused: true,
}
input: {
try {
new function(a, b) {
b;
b.p;
}(42);
} catch (e) {
console.log("PASS");
}
}
expect: {
try {
void b.p;
} catch (e) {
console.log("PASS");
}
var b;
}
expect_stdout: "PASS"
}

View File

@@ -99,8 +99,8 @@ issue_4664: {
expect: {
(function f() {
new function(a) {
console.log(typeof f, 2 ** 30, typeof this);
}(0, A = 0);
console.log(typeof f, 1073741824, typeof this);
}(A = 0);
})();
}
expect_stdout: "function 1073741824 object"

View File

@@ -5991,3 +5991,33 @@ issue_4788: {
}
expect_stdout: "PASS"
}
issue_4823: {
options = {
functions: true,
reduce_vars: true,
unused: true,
}
input: {
console.log(typeof function() {
{
function f() {}
var arguments = f();
function g() {}
var arguments = g;
}
return f && arguments;
}());
}
expect: {
console.log(typeof function() {
{
function f() {}
arguments = f();
var arguments = function() {};
}
return f && arguments;
}());
}
expect_stdout: "function"
}

View File

@@ -140,7 +140,6 @@ issue_4487: {
functions: true,
hoist_vars: true,
keep_fnames: true,
passes: 2,
reduce_vars: true,
toplevel: true,
unused: true,
@@ -240,3 +239,29 @@ issue_4736: {
}
expect_stdout: "1073741824"
}
issue_4839: {
options = {
evaluate: true,
hoist_vars: true,
keep_fargs: false,
reduce_vars: true,
toplevel: true,
unused: true,
}
input: {
var o = function(a, b) {
return b && b;
}("foo");
for (var k in o)
throw "FAIL";
console.log("PASS");
}
expect: {
var k, o = void 0;
for (k in o)
throw "FAIL";
console.log("PASS");
}
expect_stdout: "PASS"
}

View File

@@ -54,13 +54,22 @@ dynamic_nought: {
expect_exact: "import(foo);"
}
import_meta: {
import_meta_1: {
input: {
console.log(import.meta, import.meta.url);
}
expect_exact: "console.log(import.meta,import.meta.url);"
}
import_meta_2: {
input: {
import.meta.url.split("/").forEach(function(part, index) {
console.log(index, part);
});
}
expect_exact: 'import.meta.url.split("/").forEach(function(part,index){console.log(index,part)});'
}
same_quotes: {
beautify = {
beautify: true,

View File

@@ -1506,3 +1506,43 @@ issue_4691: {
expect_stdout: "PASS"
node_version: ">=4"
}
issue_4848: {
options = {
if_return: true,
}
input: {
"use strict";
function f(a) {
a(function() {
console.log(b);
});
if (!console)
return;
let b = "PASS";
}
var g;
f(function(h) {
g = h;
});
g();
}
expect: {
"use strict";
function f(a) {
a(function() {
console.log(b);
});
if (!console)
return;
let b = "PASS";
}
var g;
f(function(h) {
g = h;
});
g();
}
expect_stdout: "PASS"
node_version: ">=4"
}

View File

@@ -104,6 +104,40 @@ parentheses_for_prototype_functions_galio: {
expect_stdout: true
}
octal: {
beautify = {
beautify: true,
}
input: {
(function() {
console.log(052);
console.log(-052);
console.log(018);
console.log(-018);
console.log(052.toFixed(0));
console.log(-052.toFixed(0));
console.log(018..toFixed(0));
console.log(-018..toFixed(0));
})();
}
expect_exact: [
"(function() {",
" console.log(42);",
" console.log(-42);",
" console.log(18);",
" console.log(-18);",
" console.log(42..toFixed(0));",
" console.log(-42..toFixed(0));",
" console.log(18..toFixed(0));",
" console.log(-18..toFixed(0));",
"})();",
]
expect_stdout: true
}
comparisons: {
options = {
comparisons: true,

View File

@@ -1400,3 +1400,49 @@ object_super: {
expect_stdout: "PASS"
node_version: ">=4"
}
issue_4831_1: {
options = {
properties: true,
}
input: {
console.log({
f() {
return arguments;
},
}.f("PASS")[0]);
}
expect: {
console.log([
function() {
return arguments;
},
][0]("PASS")[0]);
}
expect_stdout: "PASS"
node_version: ">=4"
}
issue_4831_2: {
options = {
properties: true,
}
input: {
var f = {
f() {
return arguments;
},
}.f;
console.log(f("PASS")[0]);
}
expect: {
var f = {
f() {
return arguments;
},
}.f;
console.log(f("PASS")[0]);
}
expect_stdout: "PASS"
node_version: ">=4"
}

View File

@@ -172,3 +172,32 @@ issue_4054: {
}
expect_stdout: "{ p: [Setter] }"
}
issue_4811_1: {
input: {
for (var PASS in this);
console.log(PASS, this, {} < this);
}
expect: {
for (var PASS in this);
console.log(PASS, this, {} < this);
}
expect_stdout: "PASS [object global] true"
}
issue_4811_2: {
options = {
side_effects: true,
}
input: {
(async function() {});
for (var PASS in this);
console.log(PASS, this, {} < this);
}
expect: {
for (var PASS in this);
console.log(PASS, this, {} < this);
}
expect_stdout: "PASS [object global] true"
node_version: ">=8"
}

View File

@@ -1045,3 +1045,26 @@ issue_4614: {
expect_stdout: true
node_version: ">=6"
}
issue_4849: {
options = {
reduce_vars: true,
unused: true,
}
input: {
while (function() {
while (!console);
}(new function(a) {
console.log(typeof { ...a });
}(function() {})));
}
expect: {
while (function() {
while (!console);
}(function(a) {
console.log(typeof { ...function() {} });
}()));
}
expect_stdout: "object"
node_version: ">=8"
}

View File

@@ -0,0 +1 @@
for await (; console.log(42););

View File

@@ -679,6 +679,20 @@ describe("bin/uglifyjs", function() {
done();
});
});
it("Should throw syntax error (for-await)", function(done) {
var command = uglifyjscmd + " test/input/invalid/for-await.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/for-await.js:1,11",
"for await (; console.log(42););",
" ^",
"ERROR: Unexpected token: punc «;»",
].join("\n"));
done();
});
});
it("Should throw syntax error (switch defaults)", function(done) {
var command = uglifyjscmd + " test/input/invalid/switch.js";
exec(command, function(err, stdout, stderr) {

View File

@@ -183,6 +183,24 @@ describe("test/reduce.js", function() {
"// }",
].join("\n"));
});
it("Should reduce `for (const ... in ...)` without invalid intermediate AST", function() {
if (semver.satisfies(process.version, "<4")) return;
var code = [
"var a = 0;",
"",
"for (const b in [ 1, 2, 3 ]) {",
" a = +a + 1 - .2;",
" console.log(a);",
"}",
].join("\n");
var result = reduce_test(code, {
compress: {
unsafe_math: true,
},
});
if (result.error) throw result.error;
assert.deepEqual(result.warnings, []);
});
it("Should reduce infinite loops with reasonable performance", function() {
if (semver.satisfies(process.version, "<=0.10")) return;
this.timeout(120000);
@@ -361,4 +379,39 @@ describe("test/reduce.js", function() {
if (result.error) throw result.error;
assert.strictEqual(result.code, read("test/input/reduce/destructured_catch.reduced.js"));
});
it("Should not enumerate `toString` over global context", function() {
if (semver.satisfies(process.version, "<8")) return;
var code = [
"(async function() {});",
"for (var k in this);",
"console.log(k, 42 + this);",
].join("\n");
var result = reduce_test(code, {
mangle: false,
});
if (result.error) throw result.error;
assert.strictEqual(result.code, [
"// Can't reproduce test failure",
"// minify options: {",
'// "mangle": false',
"// }",
].join("\n"));
});
it("Should reduce object with method syntax without invalid intermediate AST", function() {
if (semver.satisfies(process.version, "<4")) return;
var code = [
"console.log({",
" f() {",
" return 1 - .8;",
" },",
"}.f());",
].join("\n");
var result = reduce_test(code, {
compress: {
unsafe_math: true,
},
});
if (result.error) throw result.error;
assert.deepEqual(result.warnings, []);
});
});

View File

@@ -316,10 +316,11 @@ module.exports = function reduce_test(testcase, minify_options, reduce_options)
var expr;
switch ((node.start._permute * steps | 0) % 3) {
case 0:
if (!(node.init instanceof U.AST_Definitions
&& node.init.definitions[0].name instanceof U.AST_Destructured)) {
expr = node.init;
if (node.init instanceof U.AST_Definitions) {
if (node.init instanceof U.AST_Const) break;
if (node.init.definitions[0].name instanceof U.AST_Destructured) break;
}
expr = node.init;
break;
case 1:
expr = node.object;
@@ -484,23 +485,27 @@ module.exports = function reduce_test(testcase, minify_options, reduce_options)
CHANGED = true;
return newNode;
}, function(node, in_list) {
if (node instanceof U.AST_Sequence) {
if (node instanceof U.AST_Definitions) {
// remove empty var statement
if (node.definitions.length == 0) return in_list ? List.skip : new U.AST_EmptyStatement({
start: {},
});
} else if (node instanceof U.AST_ObjectMethod) {
if (!/Function$/.test(node.value.TYPE)) return new U.AST_ObjectKeyVal({
key: node.key,
value: node.value,
start: {},
});
} else if (node instanceof U.AST_Sequence) {
// expand single-element sequence
if (node.expressions.length == 1) return node.expressions[0];
}
else if (node instanceof U.AST_Try) {
} else if (node instanceof U.AST_Try) {
// expand orphaned try block
if (!node.bcatch && !node.bfinally) return new U.AST_BlockStatement({
body: node.body,
start: {},
});
}
else if (node instanceof U.AST_Definitions) {
// remove empty var statement
if (node.definitions.length == 0) return in_list ? List.skip : new U.AST_EmptyStatement({
start: {},
});
}
});
var diff_error_message;

View File

@@ -1,3 +1,24 @@
echo "::group::GitHub Environment Variables"
echo "CI: $CI"
echo "GITHUB_WORKFLOW: $GITHUB_WORKFLOW"
echo "GITHUB_RUN_ID: $GITHUB_RUN_ID"
echo "GITHUB_RUN_NUMBER: $GITHUB_RUN_NUMBER"
echo "GITHUB_ACTION: $GITHUB_ACTION"
echo "GITHUB_ACTIONS: $GITHUB_ACTIONS"
echo "GITHUB_ACTOR: $GITHUB_ACTOR"
echo "GITHUB_REPOSITORY: $GITHUB_REPOSITORY"
echo "GITHUB_EVENT_NAME: $GITHUB_EVENT_NAME"
echo "GITHUB_EVENT_PATH: $GITHUB_EVENT_PATH"
echo "GITHUB_WORKSPACE: $GITHUB_WORKSPACE"
echo "GITHUB_SHA: $GITHUB_SHA"
echo "GITHUB_REF: $GITHUB_REF"
echo "GITHUB_HEAD_REF: $GITHUB_HEAD_REF"
echo "GITHUB_BASE_REF: $GITHUB_BASE_REF"
echo "GITHUB_SERVER_URL: $GITHUB_SERVER_URL"
echo "GITHUB_API_URL: $GITHUB_API_URL"
echo "GITHUB_GRAPHQL_URL: $GITHUB_GRAPHQL_URL"
echo "::endgroup::"
if command -v timeout &> /dev/null; then NATIVE=1; fi
timeout() {
T=$1

View File

@@ -55,6 +55,8 @@ exports.patch_module_statements = function(code) {
if (!header) return "";
if (header.length == 1) return "0, " + header;
return header.slice(0, -1) + " _" + ++count + header.slice(-1);
}).replace(/\bimport\.meta\b/g, function() {
return '({ url: "https://example.com/path/index.html" })';
}).replace(/\bimport\b(?:\s*([^('"]+)\bfrom\b)?\s*(['"]).*?\2(?:$|\n|;)/g, function(match, symbols) {
if (symbols) {
if (!/^[{*]/.test(symbols)) symbols = "default:" + symbols;
@@ -200,9 +202,13 @@ function setup(global, builtins, setup_log, setup_tty) {
});
Object.defineProperties(global, props);
// for Node.js v8+
global.toString = function() {
return "[object global]";
};
if (global.toString !== Object.prototype.toString) {
global.__proto__ = Object.defineProperty(Object.create(global.__proto__), "toString", {
value: function() {
return "[object global]";
},
});
}
function self() {
return this;
@@ -216,7 +222,7 @@ function setup(global, builtins, setup_log, setup_tty) {
if (arg === global) return "[object global]";
if (/Error$/.test(arg.name)) return arg.toString();
if (typeof arg.then == "function") return "[object Promise]";
arg.constructor.toString();
if (arg.constructor) arg.constructor.toString();
var index = cache.original.indexOf(arg);
if (index >= 0) return cache.replaced[index];
if (--cache.level < 0) return "[object Object]";

View File

@@ -149,6 +149,7 @@ var SUPPORT = function(matrix) {
for_of: "for (var a of []);",
generator: "function* f(){}",
let: "let a;",
logical_assignment: "[].p ??= 0;",
new_target: "function f() { new.target; }",
nullish: "0 ?? 0",
rest: "var [...a] = [];",
@@ -203,12 +204,20 @@ var VALUES = [
'"function"',
"this",
];
VALUES = VALUES.concat(VALUES);
VALUES = VALUES.concat(VALUES);
VALUES = VALUES.concat(VALUES);
if (SUPPORT.bigint) VALUES = VALUES.concat([
"(!0o644n)",
"([3n][0] > 2)",
"(-42n).toString()",
"Number(0XDEADn << 16n | 0xbeefn)",
]);
VALUES = VALUES.concat(VALUES);
VALUES = VALUES.concat(VALUES);
VALUES = VALUES.concat(VALUES);
VALUES = VALUES.concat(VALUES);
VALUES.push("import.meta");
var BINARY_OPS = [
" + ", // spaces needed to disambiguate with ++ cases (could otherwise cause syntax errors)
@@ -262,10 +271,13 @@ ASSIGNMENTS = ASSIGNMENTS.concat([
">>=",
">>>=",
]);
if (SUPPORT.exponentiation) {
ASSIGNMENTS = ASSIGNMENTS.concat(ASSIGNMENTS);
ASSIGNMENTS.push("**=");
}
ASSIGNMENTS = ASSIGNMENTS.concat(ASSIGNMENTS);
if (SUPPORT.exponentiation) ASSIGNMENTS.push("**=");
if (SUPPORT.logical_assignment) ASSIGNMENTS = ASSIGNMENTS.concat([
"&&=",
"||=",
"??=",
]);
var UNARY_SAFE = [
"+",
@@ -2202,8 +2214,7 @@ function log(options) {
function sort_globals(code) {
var globals = run_code("throw Object.keys(this).sort(" + function(global) {
return function(m, n) {
return (n == "toString") - (m == "toString")
|| (typeof global[n] == "function") - (typeof global[m] == "function")
return (typeof global[n] == "function") - (typeof global[m] == "function")
|| (m < n ? -1 : m > n ? 1 : 0);
};
} + "(this));" + code);