improve handling of non-trivial assignment values (#5227)
authorAlex Lam S.L <alexlamsl@gmail.com>
Wed, 22 Dec 2021 23:55:06 +0000 (23:55 +0000)
committerGitHub <noreply@github.com>
Wed, 22 Dec 2021 23:55:06 +0000 (07:55 +0800)
13 files changed:
lib/compress.js
test/compress/assignments.js
test/compress/collapse_vars.js
test/compress/default-values.js
test/compress/destructured.js
test/compress/drop-unused.js
test/compress/exponentiation.js
test/compress/functions.js
test/compress/hoist_vars.js
test/compress/join_vars.js
test/compress/keep_fargs.js
test/compress/sequences.js
test/mocha/sourcemaps.js

index 850866c..5a8f9ad 100644 (file)
@@ -1397,9 +1397,7 @@ Compressor.prototype.compress = function(node) {
                             operator: "+",
                             expression: make_ref(exp, fixed)
                         }),
-                        right: make_node(AST_Number, node, {
-                            value: 1
-                        })
+                        right: make_node(AST_Number, node, { value: 1 }),
                     });
                 };
                 d.fixed.assigns = fixed && fixed.assigns ? fixed.assigns.slice() : [];
@@ -1410,7 +1408,7 @@ Compressor.prototype.compress = function(node) {
                     exp.fixed = function() {
                         return make_node(AST_UnaryPrefix, node, {
                             operator: "+",
-                            expression: make_ref(exp, fixed)
+                            expression: make_ref(exp, fixed),
                         });
                     };
                     exp.fixed.assigns = fixed && fixed.assigns;
@@ -1501,11 +1499,11 @@ Compressor.prototype.compress = function(node) {
         var fixed = this.definition().fixed;
         if (fixed) {
             if (this.fixed) fixed = this.fixed;
-            return fixed instanceof AST_Node ? fixed : fixed();
+            return (fixed instanceof AST_Node ? fixed : fixed()).tail_node();
         }
         fixed = fixed === 0 && this.fixed;
         if (!fixed) return fixed;
-        var value = fixed instanceof AST_Node ? fixed : fixed();
+        var value = (fixed instanceof AST_Node ? fixed : fixed()).tail_node();
         return value.is_constant() && value;
     });
 
@@ -1699,7 +1697,7 @@ Compressor.prototype.compress = function(node) {
 
     function merge_sequence(array, node) {
         if (node instanceof AST_Sequence) {
-            array.push.apply(array, node.expressions);
+            [].push.apply(array, node.expressions);
         } else {
             array.push(node);
         }
@@ -1813,6 +1811,31 @@ Compressor.prototype.compress = function(node) {
         return true;
     }
 
+    // Certain combination of unused name + side effect leads to invalid AST:
+    //    https://github.com/mishoo/UglifyJS/issues/44
+    //    https://github.com/mishoo/UglifyJS/issues/1838
+    //    https://github.com/mishoo/UglifyJS/issues/3371
+    // We fix it at this stage by moving the `var` outside the `for`.
+    function patch_for_init(node, in_list) {
+        var block;
+        if (node.init instanceof AST_BlockStatement) {
+            block = node.init;
+            node.init = block.body.pop();
+            block.body.push(node);
+        }
+        if (node.init instanceof AST_Defun) {
+            if (!block) block = make_node(AST_BlockStatement, node, { body: [ node ] });
+            block.body.splice(-1, 0, node.init);
+            node.init = null;
+        } else if (node.init instanceof AST_SimpleStatement) {
+            node.init = node.init.body;
+        } else if (is_empty(node.init)) {
+            node.init = null;
+        }
+        if (!block) return;
+        return in_list ? List.splice(block.body) : block;
+    }
+
     function tighten_body(statements, compressor) {
         var in_lambda = last_of(compressor, function(node) {
             return node instanceof AST_Lambda;
@@ -1972,12 +1995,12 @@ Compressor.prototype.compress = function(node) {
                         var def = candidate.name.definition();
                         if (def.references.length - def.replaced == 1 && !compressor.exposed(def)) {
                             def.replaced++;
-                            return maintain_this_binding(compressor, parent, node, candidate.value);
+                            return maintain_this_binding(compressor, parent, node, rvalue);
                         }
                         return make_node(AST_Assign, candidate, {
                             operator: "=",
                             left: make_node(AST_SymbolRef, candidate.name, candidate.name),
-                            right: candidate.value,
+                            right: rvalue,
                         });
                     }
                     var assign = candidate;
@@ -1986,7 +2009,9 @@ Compressor.prototype.compress = function(node) {
                         if (!(assign instanceof AST_Assign)) break;
                         assign = assign.right;
                     }
-                    return candidate;
+                    assign = candidate.clone();
+                    assign.right = rvalue;
+                    return assign;
                 }
                 // These node types have child nodes that execute sequentially,
                 // but are otherwise not safe to scan into or beyond them.
@@ -2083,7 +2108,9 @@ Compressor.prototype.compress = function(node) {
                 }
                 // Skip (non-executed) functions and (leading) default case in switch statements
                 if (node instanceof AST_Default || node instanceof AST_Scope) return node;
-            }, patch_sequence);
+            }, function(node) {
+                return patch_sequence(node, multi_replacer);
+            });
             while (--stat_index >= 0) {
                 // Treat parameters as collapsible in IIFE, i.e.
                 //   function(a, b){ ... }(x());
@@ -2122,7 +2149,14 @@ Compressor.prototype.compress = function(node) {
                     var well_defined = true;
                     var lvalues = get_lvalues(candidate);
                     var lhs_local = is_lhs_local(lhs);
-                    var rvalue = get_rvalue(candidate);
+                    var rhs_value = get_rvalue(candidate);
+                    var rvalue;
+                    if (rhs_value instanceof AST_Sequence
+                        && !(candidate instanceof AST_Assign && candidate.operator != "=")) {
+                        rvalue = rhs_value.tail_node();
+                    } else {
+                        rvalue = rhs_value;
+                    }
                     if (!side_effects) side_effects = value_has_side_effects();
                     var check_destructured = in_try || !lhs_local ? function(node) {
                         return node instanceof AST_Destructured;
@@ -2163,7 +2197,7 @@ Compressor.prototype.compress = function(node) {
                         value_def.single_use = false;
                         changed = true;
                     }
-                    if (replaced && !remove_candidate(candidate)) statements.splice(stat_index, 1);
+                    if (replaced) remove_candidate(candidate);
                 }
             }
             return changed;
@@ -2768,6 +2802,7 @@ Compressor.prototype.compress = function(node) {
                     return;
                 }
                 if (remaining < 1) return;
+                rhs = rhs.tail_node();
                 var value = rhs instanceof AST_Assign && rhs.operator == "=" ? rhs.left : rhs;
                 if (!(value instanceof AST_SymbolRef)) return;
                 var def = value.definition();
@@ -3002,24 +3037,21 @@ Compressor.prototype.compress = function(node) {
             }
 
             function remove_candidate(expr) {
+                var value = rvalue === rhs_value ? null : make_sequence(rhs_value, rhs_value.expressions.slice(0, -1));
                 var index = expr.name_index;
                 if (index >= 0) {
                     var argname = scope.argnames[index];
                     if (argname instanceof AST_DefaultValue) {
-                        argname.value = make_node(AST_Number, argname, {
-                            value: 0
-                        });
+                        argname.value = value || make_node(AST_Number, argname, { value: 0 });
                         argname.name.definition().fixed = false;
                     } else {
                         var args = compressor.parent().args;
                         if (args[index]) {
-                            args[index] = make_node(AST_Number, args[index], {
-                                value: 0
-                            });
+                            args[index] = value || make_node(AST_Number, args[index], { value: 0 });
                             argname.definition().fixed = false;
                         }
                     }
-                    return true;
+                    return;
                 }
                 var end = hit_stack.length - 1;
                 if (hit_stack[end - 1].body === hit_stack[end]) end--;
@@ -3034,20 +3066,46 @@ Compressor.prototype.compress = function(node) {
                         if (value_def) value_def.replaced++;
                         node = node.clone();
                         node.value = null;
-                        return node;
+                        return value ? List.splice([ value, node ]) : node;
+                    }
+                    if (!value) return in_list ? List.skip : null;
+                    return is_statement(node) ? make_node(AST_SimpleStatement, value, { body: value }) : value;
+                }, function(node, in_list) {
+                    if (node instanceof AST_Definitions) {
+                        var body = [], defns = node.definitions;
+                        for (var index = 0, pos = 0; index < defns.length; index++) {
+                            var defn = defns[index];
+                            if (defn instanceof AST_VarDef) continue;
+                            flush();
+                            pos = index + 1;
+                            body.push(make_node(AST_SimpleStatement, defn, { body: defn }));
+                        }
+                        if (pos == 0) return;
+                        flush();
+                        if (body.length == 1) return body[0];
+                        return in_list ? List.splice(body) : make_node(AST_BlockStatement, node, { body: body });
+                    }
+                    if (node instanceof AST_For) return patch_for_init(node, in_list);
+                    return patch_sequence(node, this);
+
+                    function flush() {
+                        if (pos < index) {
+                            var cropped = node.clone();
+                            cropped.definitions = defns.slice(pos, index);
+                            body.push(cropped);
+                        }
                     }
-                    return in_list ? List.skip : null;
-                }, patch_sequence);
+                });
                 abort = false;
                 hit = false;
                 hit_index = 0;
-                return statements[stat_index].transform(tt);
+                if (!(statements[stat_index] = statements[stat_index].transform(tt))) statements.splice(stat_index, 1);
             }
 
-            function patch_sequence(node) {
+            function patch_sequence(node, tt) {
                 if (node instanceof AST_Sequence) switch (node.expressions.length) {
                   case 0: return null;
-                  case 1: return maintain_this_binding(compressor, this.parent(), node, node.expressions[0]);
+                  case 1: return maintain_this_binding(compressor, tt.parent(), node, node.expressions[0]);
                 }
             }
 
@@ -3779,11 +3837,18 @@ Compressor.prototype.compress = function(node) {
                 } else if (stat instanceof AST_If) {
                     stat.condition = join_assigns_expr(stat.condition);
                 } else if (stat instanceof AST_SimpleStatement) {
-                    var exprs = join_assigns(prev, stat.body);
+                    var exprs = join_assigns(prev, stat.body), next;
                     if (exprs) {
                         changed = true;
                         if (!exprs.length) continue;
                         stat.body = make_sequence(stat.body, exprs);
+                    } else if (prev instanceof AST_Definitions
+                        && (next = statements[i + 1])
+                        && prev.TYPE == next.TYPE
+                        && (next = next.definitions[0]).value) {
+                        changed = true;
+                        next.value = make_sequence(stat, [ stat.body, next.value ]);
+                        continue;
                     }
                 } else if (stat instanceof AST_Switch) {
                     stat.expression = join_assigns_expr(stat.expression);
@@ -6690,7 +6755,7 @@ Compressor.prototype.compress = function(node) {
                             value = null;
                             trim_defns.push(def);
                         }
-                        var old_def;
+                        var old_def, fn;
                         if (!value && !(node instanceof AST_Let)) {
                             if (parent instanceof AST_ExportDeclaration) {
                                 flush();
@@ -6704,17 +6769,18 @@ Compressor.prototype.compress = function(node) {
                         } else if (compressor.option("functions")
                             && !compressor.option("ie")
                             && drop_sym
+                            && value
                             && var_defs[sym.id] == 1
                             && sym.assignments == 0
-                            && value instanceof AST_LambdaExpression
+                            && (fn = value.tail_node()) instanceof AST_LambdaExpression
                             && !is_arguments(sym)
-                            && !is_arrow(value)
-                            && assigned_once(value, sym.references)
-                            && can_declare_defun(value)
-                            && (old_def = rename_def(value, def.name.name)) !== false) {
+                            && !is_arrow(fn)
+                            && assigned_once(fn, sym.references)
+                            && can_declare_defun(fn)
+                            && (old_def = rename_def(fn, def.name.name)) !== false) {
                             AST_Node.warn("Declaring {name} as function [{file}:{line},{col}]", template(def.name));
                             var ctor;
-                            switch (value.CTOR) {
+                            switch (fn.CTOR) {
                               case AST_AsyncFunction:
                                 ctor = AST_AsyncDefun;
                                 break;
@@ -6728,7 +6794,7 @@ Compressor.prototype.compress = function(node) {
                                 ctor = AST_GeneratorDefun;
                                 break;
                             }
-                            var defun = make_node(ctor, def, value);
+                            var defun = make_node(ctor, def, fn);
                             defun.name = make_node(AST_SymbolDefun, def.name, def.name);
                             var name_def = def.name.scope.resolve().def_function(defun.name);
                             if (old_def) old_def.forEach(function(node) {
@@ -6737,6 +6803,7 @@ Compressor.prototype.compress = function(node) {
                                 node.reference();
                             });
                             body.push(defun);
+                            if (value !== fn) [].push.apply(side_effects, value.expressions.slice(0, -1));
                         } else {
                             if (drop_sym
                                 && var_defs[sym.id] > 1
@@ -6759,7 +6826,7 @@ Compressor.prototype.compress = function(node) {
                             head.push(def);
                         }
                     } else {
-                        value = value && !value.single_use && value.drop_side_effect_free(compressor);
+                        value = value && value.drop_side_effect_free(compressor);
                         if (value) {
                             AST_Node.warn("Side effects in initialization of unused variable {name} [{file}:{line},{col}]", template(def.name));
                             side_effects.push(value);
@@ -6867,13 +6934,18 @@ Compressor.prototype.compress = function(node) {
                         }
                     }
                   default:
+                    var seq;
+                    if (tail.length > 0 && (seq = tail[0].value) instanceof AST_Sequence) {
+                        tail[0].value = seq.tail_node();
+                        body.push(make_node(AST_SimpleStatement, node, {
+                            body: make_sequence(seq, seq.expressions.slice(0, -1)),
+                        }));
+                    }
                     node.definitions = head.concat(tail);
                     body.push(node);
                 }
                 if (side_effects.length > 0) {
-                    body.push(make_node(AST_SimpleStatement, node, {
-                        body: make_sequence(node, side_effects)
-                    }));
+                    body.push(make_node(AST_SimpleStatement, node, { body: make_sequence(node, side_effects) }));
                 }
                 return insert_statements(body, node, in_list);
             }
@@ -6922,33 +6994,7 @@ Compressor.prototype.compress = function(node) {
             }
         }, function(node, in_list) {
             if (node instanceof AST_BlockStatement) return trim_block(node, tt.parent(), in_list);
-            // Certain combination of unused name + side effect leads to invalid AST:
-            //    https://github.com/mishoo/UglifyJS/issues/44
-            //    https://github.com/mishoo/UglifyJS/issues/1838
-            //    https://github.com/mishoo/UglifyJS/issues/3371
-            // We fix it at this stage by moving the `var` outside the `for`.
-            if (node instanceof AST_For) {
-                var block;
-                if (node.init instanceof AST_BlockStatement) {
-                    block = node.init;
-                    node.init = block.body.pop();
-                    block.body.push(node);
-                }
-                if (node.init instanceof AST_Defun) {
-                    if (!block) {
-                        block = make_node(AST_BlockStatement, node, {
-                            body: [ node ]
-                        });
-                    }
-                    block.body.splice(-1, 0, node.init);
-                    node.init = null;
-                } else if (node.init instanceof AST_SimpleStatement) {
-                    node.init = node.init.body;
-                } else if (is_empty(node.init)) {
-                    node.init = null;
-                }
-                return !block ? node : in_list ? List.splice(block.body) : block;
-            }
+            if (node instanceof AST_For) return patch_for_init(node, in_list);
             if (node instanceof AST_ForIn) {
                 if (!drop_vars || !compressor.option("loops")) return;
                 if (!is_empty(node.body)) return;
@@ -8749,15 +8795,21 @@ Compressor.prototype.compress = function(node) {
         var body = [], var_defs = [], refs = [];
         var body_exprs = sequencesize(self.body, body, var_defs, refs);
         var alt_exprs = sequencesize(self.alternative, body, var_defs, refs);
-        if (body_exprs && alt_exprs) {
+        if (body_exprs instanceof AST_BlockStatement || alt_exprs instanceof AST_BlockStatement) {
+            if (var_defs.length > 0) body.push(make_node(AST_Var, self, { definitions: var_defs }));
+            body.push(self);
+            if (body_exprs instanceof AST_BlockStatement) self.body = body_exprs;
+            if (alt_exprs instanceof AST_BlockStatement) self.alternative = alt_exprs;
+            return make_node(AST_BlockStatement, self, { body: body }).optimize(compressor);
+        } else if (body_exprs && alt_exprs) {
             if (var_defs.length > 0) body.push(make_node(AST_Var, self, { definitions: var_defs }));
             if (body_exprs.length == 0) {
                 body.push(make_node(AST_SimpleStatement, self.condition, {
                     body: alt_exprs.length > 0 ? make_node(AST_Binary, self, {
-                        operator : "||",
-                        left     : self.condition,
-                        right    : make_sequence(self.alternative, alt_exprs)
-                    }).transform(compressor) : self.condition.clone()
+                        operator: "||",
+                        left: self.condition,
+                        right: make_sequence(self.alternative, alt_exprs),
+                    }).transform(compressor) : self.condition.clone(),
                 }).optimize(compressor));
             } else if (alt_exprs.length == 0) {
                 if (self_condition_length === negated_length && !negated_is_best
@@ -8769,79 +8821,64 @@ Compressor.prototype.compress = function(node) {
                 }
                 body.push(make_node(AST_SimpleStatement, self, {
                     body: make_node(AST_Binary, self, {
-                        operator : negated_is_best ? "||" : "&&",
-                        left     : negated_is_best ? negated : self.condition,
-                        right    : make_sequence(self.body, body_exprs)
-                    }).transform(compressor)
+                        operator: negated_is_best ? "||" : "&&",
+                        left: negated_is_best ? negated : self.condition,
+                        right: make_sequence(self.body, body_exprs),
+                    }).transform(compressor),
                 }).optimize(compressor));
             } else {
                 body.push(make_node(AST_SimpleStatement, self, {
                     body: make_node(AST_Conditional, self, {
-                        condition   : self.condition,
-                        consequent  : make_sequence(self.body, body_exprs),
-                        alternative : make_sequence(self.alternative, alt_exprs)
-                    })
+                        condition: self.condition,
+                        consequent: make_sequence(self.body, body_exprs),
+                        alternative: make_sequence(self.alternative, alt_exprs),
+                    }),
                 }).optimize(compressor));
             }
             refs.forEach(function(ref) {
                 ref.definition().references.push(ref);
             });
-            return make_node(AST_BlockStatement, self, {
-                body: body
-            }).optimize(compressor);
+            return make_node(AST_BlockStatement, self, { body: body }).optimize(compressor);
         }
-        if (is_empty(self.body)) {
-            self = make_node(AST_If, self, {
-                condition: negated,
-                body: self.alternative,
-                alternative: null
-            });
-        }
-        if (self.body instanceof AST_Exit
-            && self.alternative instanceof AST_Exit
-            && self.body.TYPE == self.alternative.TYPE) {
+        if (is_empty(self.body)) self = make_node(AST_If, self, {
+            condition: negated,
+            body: self.alternative,
+            alternative: null,
+        });
+        if (self.alternative instanceof AST_Exit && self.body.TYPE == self.alternative.TYPE) {
             var exit = make_node(self.body.CTOR, self, {
                 value: make_node(AST_Conditional, self, {
-                    condition   : self.condition,
-                    consequent  : self.body.value || make_node(AST_Undefined, self.body).transform(compressor),
-                    alternative : self.alternative.value || make_node(AST_Undefined, self.alternative).transform(compressor)
-                })
+                    condition: self.condition,
+                    consequent: self.body.value || make_node(AST_Undefined, self.body).transform(compressor),
+                    alternative: self.alternative.value
+                        || make_node(AST_Undefined, self.alternative).transform(compressor),
+                }),
             });
-            if (exit instanceof AST_Return) {
-                exit.in_bool = self.body.in_bool || self.alternative.in_bool;
-            }
+            if (exit instanceof AST_Return) exit.in_bool = self.body.in_bool || self.alternative.in_bool;
             return exit;
         }
-        if (self.body instanceof AST_If
-            && !self.body.alternative
-            && !self.alternative) {
+        if (self.body instanceof AST_If && !self.body.alternative && !self.alternative) {
             self = make_node(AST_If, self, {
                 condition: make_node(AST_Binary, self.condition, {
                     operator: "&&",
                     left: self.condition,
-                    right: self.body.condition
+                    right: self.body.condition,
                 }),
                 body: self.body.body,
-                alternative: null
+                alternative: null,
             });
         }
-        if (aborts(self.body)) {
-            if (self.alternative) {
-                var alt = self.alternative;
-                self.alternative = null;
-                return make_node(AST_BlockStatement, self, {
-                    body: [ self, alt ]
-                }).optimize(compressor);
-            }
+        if (aborts(self.body) && self.alternative) {
+            var alt = self.alternative;
+            self.alternative = null;
+            return make_node(AST_BlockStatement, self, { body: [ self, alt ] }).optimize(compressor);
         }
         if (aborts(self.alternative)) {
             var body = self.body;
             self.body = self.alternative;
             self.condition = negated_is_best ? negated : self.condition.negate(compressor);
             self.alternative = null;
-            return make_node(AST_BlockStatement, self, {
-                body: [ self, body ]
-            }).optimize(compressor);
+            return make_node(AST_BlockStatement, self, { body: [ self, body ] }).optimize(compressor);
         }
         if (compressor.option("typeofs")) mark_locally_defined(self.condition, self.body, self.alternative);
         return self;
@@ -8852,10 +8889,19 @@ Compressor.prototype.compress = function(node) {
                 var exprs = [];
                 for (var i = 0; i < stat.body.length; i++) {
                     var line = stat.body[i];
+                    if (line instanceof AST_EmptyStatement) continue;
+                    if (line instanceof AST_Exit) {
+                        if (exprs.length == 0) return;
+                        line = line.clone();
+                        exprs.push(line.value || make_node(AST_Undefined, line).transform(compressor));
+                        line.value = make_sequence(stat, exprs);
+                        var block = stat.clone();
+                        block.body = block.body.slice(i + 1);
+                        block.body.unshift(line);
+                        return block;
+                    }
                     if (line instanceof AST_LambdaDefinition) {
                         defuns.push(line);
-                    } else if (line instanceof AST_EmptyStatement) {
-                        continue;
                     } else if (line instanceof AST_SimpleStatement) {
                         if (!compressor.option("sequences") && exprs.length > 0) return;
                         exprs.push(line.body);
@@ -9320,9 +9366,7 @@ Compressor.prototype.compress = function(node) {
                         args[pos++] = make_sequence(call, side_effects);
                         side_effects = [];
                     } else {
-                        args[pos++] = make_node(AST_Number, args[i], {
-                            value: 0
-                        });
+                        args[pos++] = make_node(AST_Number, args[i], { value: 0 });
                         continue;
                     }
                 }
@@ -9924,10 +9968,14 @@ Compressor.prototype.compress = function(node) {
             for (var i = 0; i < len; i++) {
                 var line = fn.body[i];
                 if (line instanceof AST_Var) {
-                    var assigned = var_assigned || !declarations_only(line);
-                    if (assigned) {
+                    if (var_assigned) {
+                        if (!stat) continue;
+                        if (!(stat instanceof AST_SimpleStatement)) return false;
+                        if (!declarations_only(line)) stat = null;
+                    } else if (!declarations_only(line)) {
+                        if (stat && !(stat instanceof AST_SimpleStatement)) return false;
+                        stat = null;
                         var_assigned = true;
-                        if (stat) return false;
                     }
                 } else if (line instanceof AST_AsyncDefun
                     || line instanceof AST_Defun
@@ -10191,7 +10239,7 @@ Compressor.prototype.compress = function(node) {
 
         function flatten_vars(decls, expressions) {
             var args = [ insert, 0 ];
-            var decl_var = [], expr_var = [], expr_loop = [];
+            var decl_var = [], expr_var = [], expr_loop = [], exprs = [];
             for (var i = 0; i < fn.body.length; i++) {
                 var stat = fn.body[i];
                 if (stat instanceof AST_LambdaDefinition) {
@@ -10209,11 +10257,20 @@ Compressor.prototype.compress = function(node) {
                     }
                     continue;
                 }
-                if (!(stat instanceof AST_Var)) continue;
+                if (!(stat instanceof AST_Var)) {
+                    if (stat instanceof AST_SimpleStatement) exprs.push(stat.body);
+                    continue;
+                }
                 for (var j = 0; j < stat.definitions.length; j++) {
                     var var_def = stat.definitions[j];
                     var name = flatten_var(var_def.name);
-                    append_var(decl_var, expr_var, name, var_def.value);
+                    var value = var_def.value;
+                    if (value && exprs.length > 0) {
+                        exprs.push(value);
+                        value = make_sequence(var_def, exprs);
+                        exprs = [];
+                    }
+                    append_var(decl_var, expr_var, name, value);
                     if (in_loop && !arg_used.has(name.name)) {
                         var def = fn.variables.get(name.name);
                         var sym = make_node(AST_SymbolRef, name, name);
@@ -12211,9 +12268,7 @@ Compressor.prototype.compress = function(node) {
         }
 
         function pop_seq(node) {
-            if (!(node instanceof AST_Sequence)) return make_node(AST_Number, node, {
-                value: 0
-            });
+            if (!(node instanceof AST_Sequence)) return make_node(AST_Number, node, { value: 0 });
             return make_sequence(node, node.expressions.slice(0, -1));
         }
     });
@@ -12526,35 +12581,25 @@ Compressor.prototype.compress = function(node) {
             var exp = self.expression.expression;
             if (is_undeclared_ref(exp)) switch (exp.name) {
               case "Array":
-                self.expression = make_node(AST_Array, self.expression, {
-                    elements: []
-                });
+                self.expression = make_node(AST_Array, self.expression, { elements: [] });
                 break;
               case "Function":
                 self.expression = make_node(AST_Function, self.expression, {
                     argnames: [],
-                    body: []
+                    body: [],
                 }).init_vars(exp.scope);
                 break;
               case "Number":
-                self.expression = make_node(AST_Number, self.expression, {
-                    value: 0
-                });
+                self.expression = make_node(AST_Number, self.expression, { value: 0 });
                 break;
               case "Object":
-                self.expression = make_node(AST_Object, self.expression, {
-                    properties: []
-                });
+                self.expression = make_node(AST_Object, self.expression, { properties: [] });
                 break;
               case "RegExp":
-                self.expression = make_node(AST_RegExp, self.expression, {
-                    value: /t/
-                });
+                self.expression = make_node(AST_RegExp, self.expression, { value: /t/ });
                 break;
               case "String":
-                self.expression = make_node(AST_String, self.expression, {
-                    value: ""
-                });
+                self.expression = make_node(AST_String, self.expression, { value: "" });
                 break;
             }
         }
index 37c6741..9e83172 100644 (file)
@@ -489,7 +489,7 @@ logical_assignments: {
     node_version: ">=15"
 }
 
-logical_collapse_vars: {
+logical_collapse_vars_1: {
     options = {
         collapse_vars: true,
     }
@@ -509,6 +509,27 @@ logical_collapse_vars: {
     node_version: ">=15"
 }
 
+logical_collapse_vars_2: {
+    options = {
+        collapse_vars: true,
+    }
+    input: {
+        var a = "PASS";
+        (function(b) {
+            b ||= (a = "FAIL", {});
+            return b;
+        })(console).log(a);
+    }
+    expect: {
+        var a = "PASS";
+        (function(b) {
+            return b ||= (a = "FAIL", {});
+        })(console).log(a);
+    }
+    expect_stdout: "PASS"
+    node_version: ">=15"
+}
+
 logical_reduce_vars: {
     options = {
         evaluate: true,
index c048559..03a375b 100644 (file)
@@ -346,9 +346,7 @@ collapse_vars_if: {
             return "x" != "Bar" + x / 4 ? g9 : g5;
         }
         function f3(x) {
-            if (x)
-                return 1;
-            return 2;
+            return x ? 1 : 2;
         }
     }
 }
@@ -1912,6 +1910,42 @@ collapse_vars_regexp: {
     ]
 }
 
+collapse_arg_sequence: {
+    options = {
+        collapse_vars: true,
+        unused: true,
+    }
+    input: {
+        (function(a) {
+            a("foo");
+        })((console.log("bar"), console.log));
+    }
+    expect: {
+        (function(a) {
+            (0, console.log)("foo");
+        })(console.log("bar"));
+    }
+    expect_stdout: [
+        "bar",
+        "foo",
+    ]
+}
+
+collapse_for_init: {
+    options = {
+        collapse_vars: true,
+        toplevel: true,
+    }
+    input: {
+        for (var a = (Math, console), b = a.log("PASS"); b;);
+    }
+    expect: {
+        Math;
+        for (var a, b = console.log("PASS"); b;);
+    }
+    expect_stdout: "PASS"
+}
+
 issue_1537: {
     options = {
         collapse_vars: true,
@@ -2667,6 +2701,24 @@ chained_4: {
     expect_stdout: "foo undefined"
 }
 
+chained_5: {
+    options = {
+        collapse_vars: true,
+    }
+    input: {
+        var a = "PASS";
+        var a = (console, console.log(a));
+        a && ++a;
+    }
+    expect: {
+        var a = "PASS";
+        console;
+        var a;
+        (a = console.log(a)) && ++a;
+    }
+    expect_stdout: "PASS"
+}
+
 boolean_binary_1: {
     options = {
         collapse_vars: true,
@@ -2906,6 +2958,43 @@ compound_assignment_2: {
     expect_stdout: "4"
 }
 
+compound_assignment_3: {
+    options = {
+        collapse_vars: true,
+    }
+    input: {
+        var a = 1;
+        a += (console.log("PASS"), 2);
+        a.p;
+    }
+    expect: {
+        var a = 1;
+        (a += (console.log("PASS"), 2)).p;
+    }
+    expect_stdout: "PASS"
+}
+
+compound_assignment_4: {
+    options = {
+        collapse_vars: true,
+        evaluate: true,
+    }
+    input: {
+        A = "PASS";
+        var a = "";
+        a += (a = "FAIL", A);
+        a.p;
+        console.log(a);
+    }
+    expect: {
+        A = "PASS";
+        var a = "";
+        (a += (a = "FAIL", A)).p;
+        console.log(a);
+    }
+    expect_stdout: "PASS"
+}
+
 issue_2187_1: {
     options = {
         collapse_vars: true,
@@ -3030,10 +3119,10 @@ issue_2203_2: {
             a: "FAIL",
             b: function() {
                 return function(c) {
-                    return (String, (Object, function() {
+                    return (Object, function() {
                         return this;
-                    }())).a;
-                }();
+                    }()).a;
+                }(String);
             }
         }.b());
     }
@@ -3764,17 +3853,17 @@ issue_2437_1: {
         console.log(foo());
     }
     expect: {
-        console.log(function() {
-            if (xhrDesc) {
-                var result = !!(req = new XMLHttpRequest()).onreadystatechange;
-                return Object.defineProperty(XMLHttpRequest.prototype, "onreadystatechange", xhrDesc || {}),
-                    result;
-            }
-            var req = new XMLHttpRequest(), detectFunc = function(){};
-            return req.onreadystatechange = detectFunc,
-            result = req[SYMBOL_FAKE_ONREADYSTATECHANGE_1] === detectFunc,
-            req.onreadystatechange = null, result;
-        }());
+        var req, detectFunc, result;
+        console.log((
+            xhrDesc ? (result = !!(req = new XMLHttpRequest).onreadystatechange,
+                Object.defineProperty(XMLHttpRequest.prototype, "onreadystatechange", xhrDesc||{})
+            ) : (
+                (req = new XMLHttpRequest).onreadystatechange = detectFunc = function(){},
+                result = req[SYMBOL_FAKE_ONREADYSTATECHANGE_1] === detectFunc,
+                req.onreadystatechange = null
+            ),
+            result
+        ));
     }
 }
 
@@ -3815,15 +3904,15 @@ issue_2437_2: {
         foo();
     }
     expect: {
-        !function() {
-            if (xhrDesc)
-                return (req = new XMLHttpRequest()).onreadystatechange,
-                    Object.defineProperty(XMLHttpRequest.prototype, "onreadystatechange", xhrDesc || {});
-            var req = new XMLHttpRequest();
-            req.onreadystatechange = function(){},
+        var req;
+        xhrDesc ? (
+            (req = new XMLHttpRequest).onreadystatechange,
+            Object.defineProperty(XMLHttpRequest.prototype, "onreadystatechange", xhrDesc || {})
+        ) : (
+            (req = new XMLHttpRequest).onreadystatechange = function(){},
             req[SYMBOL_FAKE_ONREADYSTATECHANGE_1],
-            req.onreadystatechange = null;
-        }();
+            req.onreadystatechange = null
+        );
     }
 }
 
@@ -6445,7 +6534,8 @@ assign_undeclared: {
         console.log(typeof B);
     }
     expect: {
-        B = new (console.log(42), function() {})();
+        console.log(42);
+        B = new function() {}();
         console.log(typeof B);
     }
     expect_stdout: [
@@ -9141,9 +9231,7 @@ issue_4908: {
         console.log(d[1]);
     }
     expect: {
-        var a = 0, b;
-        console || a++;
-        var c = a, d = [ (d = a) && d, d += 42 ];
+        var a = 0, b, c = (console || a++, a), d = [ (d = a) && d, d += 42 ];
         console.log(d[1]);
     }
     expect_stdout: "42"
index b888672..f43374b 100644 (file)
@@ -158,6 +158,28 @@ process_boolean_returns: {
     node_version: ">=6"
 }
 
+collapse_arg_sequence: {
+    options = {
+        collapse_vars: true,
+        unused: true,
+    }
+    input: {
+        (function(a = (console.log("bar"), console.log)) {
+            a("foo");
+        })();
+    }
+    expect: {
+        (function(a = console.log("bar")) {
+            (0, console.log)("foo");
+        })();
+    }
+    expect_stdout: [
+        "bar",
+        "foo",
+    ]
+    node_version: ">=6"
+}
+
 collapse_value_1: {
     options = {
         collapse_vars: true,
index a13d920..f246500 100644 (file)
@@ -395,7 +395,8 @@ funarg_unused_6_keep_fargs: {
     }
     expect: {
         (function() {
-            var {} = (console, 42);
+            console;
+            var {} = 42;
         })();
         console.log(typeof a);
     }
index 2c4b735..324c8f3 100644 (file)
@@ -1728,7 +1728,8 @@ issue_2768: {
     }
     expect: {
         var a = "FAIL";
-        var c = (d = a, void (d && (a = "PASS")));
+        d = a;
+        var c = void (d && (a = "PASS"));
         var d;
         console.log(a, typeof c);
     }
@@ -2382,7 +2383,8 @@ issue_3664: {
     }
     expect: {
         console.log(function() {
-            var a, b = (a = (a = [ b && console.log("FAIL") ]).p = 0, 0);
+            a = (a = [ b && console.log("FAIL") ]).p = 0;
+            var a, b = 0;
             return "PASS";
         }());
     }
@@ -2551,10 +2553,9 @@ issue_3899: {
         console.log(typeof a);
     }
     expect: {
-        function a() {
+        console.log(typeof function() {
             return 2;
-        }
-        console.log(typeof a);
+        });
     }
     expect_stdout: "function"
 }
index 702add8..995dc8c 100644 (file)
@@ -99,8 +99,8 @@ issue_4664: {
     expect: {
         (function f() {
             new function(a) {
-                console.log(typeof f, a, typeof this);
-            }((A = 0, 2 ** 30));
+                console.log(typeof f, 2 ** 30, typeof this);
+            }(A = 0);
         })();
     }
     expect_stdout: "function 1073741824 object"
index dff92d0..e9c2547 100644 (file)
@@ -3039,18 +3039,17 @@ issue_2437: {
         console.log(foo());
     }
     expect: {
-        console.log(function() {
-            if (xhrDesc) {
-                var result = !!(req = new XMLHttpRequest()).onreadystatechange;
-                return Object.defineProperty(XMLHttpRequest.prototype, "onreadystatechange", xhrDesc || {}),
-                    result;
-            }
-            function detectFunc() {}
-            var req = new XMLHttpRequest();
-            return req.onreadystatechange = detectFunc,
-            result = req[SYMBOL_FAKE_ONREADYSTATECHANGE_1] === detectFunc,
-            req.onreadystatechange = null, result;
-        }());
+        var req, detectFunc, result;
+        console.log((
+            xhrDesc ? (
+                result = !!(req = new XMLHttpRequest).onreadystatechange,
+                Object.defineProperty(XMLHttpRequest.prototype, "onreadystatechange", xhrDesc || {})
+            ) : (
+                (req = new XMLHttpRequest).onreadystatechange = detectFunc = function(){},
+                result = req[SYMBOL_FAKE_ONREADYSTATECHANGE_1] === detectFunc,req.onreadystatechange = null
+            ),
+            result
+        ));
     }
 }
 
@@ -3826,16 +3825,14 @@ inlined_single_use: {
     }
     expect: {
         console.log(function(f) {
-            var a = function() {
+            a = function() {
                 A;
-            };
-            var b = function() {
+            },
+            b = function() {
                 a(B);
-            };
-            (function() {
-                b;
-            });
-            return;
+            },
+            void 0;
+            var a, b;
         }());
     }
     expect_stdout: "undefined"
@@ -5290,6 +5287,42 @@ direct_inline_catch_redefined: {
     expect_stdout: true
 }
 
+statement_var_inline: {
+    options = {
+        inline: true,
+        join_vars: true,
+        unused: true,
+    }
+    input: {
+        function f() {
+            (function() {
+                var a = {};
+                function g() {
+                    a.p;
+                }
+                g(console.log("PASS"));
+                var b = function h(c) {
+                    c && c.q;
+                }();
+            })();
+        }
+        f();
+    }
+    expect: {
+        function f() {
+            var c, a = {};
+            function g() {
+                a.p;
+            }
+            g(console.log("PASS"));
+            c && c.q;
+            return;
+        }
+        f();
+    }
+    expect_stdout: "PASS"
+}
+
 issue_4171_1: {
     options = {
         functions: true,
index 3a2844a..b9b72c5 100644 (file)
@@ -282,7 +282,7 @@ issue_4736: {
         (function() {
             (function() {
                 0,
-                console.log(1073741824);
+                console.log(1 << 30);
             })();
         })();
     }
@@ -336,7 +336,7 @@ issue_4859: {
     }
     expect: {
         (function f(a) {
-            console.log(Infinity);
+            console.log(2 + 1 / 0);
             return f;
         })();
     }
index e2c54be..3d6ec41 100644 (file)
@@ -1282,10 +1282,7 @@ assign_sequence_var: {
         console.log(a, b, c);
     }
     expect: {
-        var a = 0, b = 1;
-        console.log(a),
-        a++;
-        var b = 2, c = 3;
+        var a = 0, b = 1, c = (console.log(a), a++, b = 2, 3);
         console.log(a, b, c);
     }
     expect_stdout: [
index 2c7bf27..ea42ce2 100644 (file)
@@ -181,10 +181,10 @@ issue_2203_2: {
             a: "FAIL",
             b: function() {
                 return function() {
-                    return (String, (Object, function() {
+                    return (Object, function() {
                         return this;
-                    }())).a;
-                }();
+                    }()).a;
+                }(String);
             }
         }.b());
     }
index cc05fd7..556e2d7 100644 (file)
@@ -910,9 +910,7 @@ hoist_decl: {
         var d;
     }
     expect: {
-        var a;
-        w();
-        var b = x(), c, d;
+        var a, b = (w(), x()), c, d;
         for (y(); 0;) z();
     }
 }
index 24a60f2..dbe542e 100644 (file)
@@ -205,8 +205,8 @@ describe("sourcemaps", function() {
             });
             if (result.error) throw result.error;
             assert.strictEqual(result.code, [
-                "var Foo=function(){console.log(3)};new Foo;var bar=function(o){return o};",
-                "//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbInN0ZGluIiwiMSJdLCJuYW1lcyI6WyJGb28iLCJjb25zb2xlIiwibG9nIiwiYmFyIl0sIm1hcHBpbmdzIjoiQUFBQSxJQUFNQSxJQUFJLFdBQWdCQyxRQUFRQyxJQUFJLElBQVMsSUFBSUYsSUNBbkQsSUFBSUcsSUFDQSxTQUFjQSxHQUNWLE9BQU9BIn0=",
+                "var Foo=function(){console.log(3)},bar=(new Foo,function(o){return o});",
+                "//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbInN0ZGluIiwiMSJdLCJuYW1lcyI6WyJGb28iLCJjb25zb2xlIiwibG9nIiwiYmFyIl0sIm1hcHBpbmdzIjoiQUFBQSxJQUFNQSxJQUFJLFdBQWdCQyxRQUFRQyxJQUFJLElDQWxDQyxLREEyQyxJQUFJSCxJQ0MvQyxTQUFjRyxHQUNWLE9BQU9BIn0=",
             ].join("\n"));
             assert.deepEqual(result.warnings, [ "WARN: inline source map not found: 1" ]);
         });