terser 5.44.0 → 5.45.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # Changelog
2
2
 
3
+ ## v5.45.0
4
+
5
+ - Produce `void 0` instead of `undefined`, which is more safe
6
+
7
+ ## v5.44.1
8
+
9
+ - fix bitwise optimization changing the result of `&&`, `||`
10
+ - switches: make sure `var` is extracted from a deleted default case
11
+
3
12
  ## v5.44.0
4
13
 
5
14
  - Support `using` and `await using` declarations (#1635)
@@ -132,6 +132,15 @@ function make_node(ctor, orig, props) {
132
132
  return new ctor(props);
133
133
  }
134
134
 
135
+ /** Makes a `void 0` expression. Use instead of AST_Undefined which may conflict
136
+ * with an existing variable called `undefined` */
137
+ function make_void_0(orig) {
138
+ return make_node(AST_UnaryPrefix, orig, {
139
+ operator: "void",
140
+ expression: make_node(AST_Number, orig, { value: 0 })
141
+ });
142
+ }
143
+
135
144
  function push_uniq(array, el) {
136
145
  if (!array.includes(el))
137
146
  array.push(el);
@@ -13558,7 +13567,7 @@ function make_node_from_constant(val, orig) {
13558
13567
  case "boolean":
13559
13568
  return make_node(val ? AST_True : AST_False, orig);
13560
13569
  case "undefined":
13561
- return make_node(AST_Undefined, orig);
13570
+ return make_void_0(orig);
13562
13571
  default:
13563
13572
  if (val === null) {
13564
13573
  return make_node(AST_Null, orig, { value: null });
@@ -16408,7 +16417,7 @@ function safe_to_read(tw, def) {
16408
16417
  if (def.fixed == null) {
16409
16418
  var orig = def.orig[0];
16410
16419
  if (orig instanceof AST_SymbolFunarg || orig.name == "arguments") return false;
16411
- def.fixed = make_node(AST_Undefined, orig);
16420
+ def.fixed = make_void_0(orig);
16412
16421
  }
16413
16422
  return true;
16414
16423
  }
@@ -16706,7 +16715,7 @@ function mark_lambda(tw, descend, compressor) {
16706
16715
  if (d.orig.length > 1) return;
16707
16716
  if (d.fixed === undefined && (!this.uses_arguments || tw.has_directive("use strict"))) {
16708
16717
  d.fixed = function() {
16709
- return iife.args[i] || make_node(AST_Undefined, iife);
16718
+ return iife.args[i] || make_void_0(iife);
16710
16719
  };
16711
16720
  tw.loop_ids.set(d.id, tw.in_loop);
16712
16721
  mark(tw, d, true);
@@ -17186,8 +17195,8 @@ function remove_initializers(var_statement) {
17186
17195
  return decls.length ? make_node(AST_Var, var_statement, { definitions: decls }) : null;
17187
17196
  }
17188
17197
 
17189
- /** Called on code which we know is unreachable, to keep elements that affect outside of it. */
17190
- function trim_unreachable_code(compressor, stat, target) {
17198
+ /** Called on code which won't be executed but has an effect outside of itself: `var`, `function` statements, `export`, `import`. */
17199
+ function extract_from_unreachable_code(compressor, stat, target) {
17191
17200
  walk(stat, node => {
17192
17201
  if (node instanceof AST_Var) {
17193
17202
  const no_initializers = remove_initializers(node);
@@ -17212,7 +17221,8 @@ function trim_unreachable_code(compressor, stat, target) {
17212
17221
  target.push(node);
17213
17222
  return true;
17214
17223
  }
17215
- if (node instanceof AST_Scope) {
17224
+ if (node instanceof AST_Scope || node instanceof AST_Class) {
17225
+ // Do not go into nested scopes
17216
17226
  return true;
17217
17227
  }
17218
17228
  });
@@ -17629,7 +17639,7 @@ function tighten_body(statements, compressor) {
17629
17639
  }
17630
17640
  } else {
17631
17641
  if (!arg) {
17632
- arg = make_node(AST_Undefined, sym).transform(compressor);
17642
+ arg = make_void_0(sym).transform(compressor);
17633
17643
  } else if (arg instanceof AST_Lambda && arg.pinned()
17634
17644
  || has_overlapping_symbol(fn, arg, fn_strict)) {
17635
17645
  arg = null;
@@ -17869,7 +17879,7 @@ function tighten_body(statements, compressor) {
17869
17879
  found = true;
17870
17880
  if (node instanceof AST_VarDef) {
17871
17881
  node.value = node.name instanceof AST_SymbolConst
17872
- ? make_node(AST_Undefined, node.value) // `const` always needs value.
17882
+ ? make_void_0(node.value) // `const` always needs value.
17873
17883
  : null;
17874
17884
  return node;
17875
17885
  }
@@ -18236,7 +18246,7 @@ function tighten_body(statements, compressor) {
18236
18246
  CHANGED = n != len;
18237
18247
  if (has_quit)
18238
18248
  has_quit.forEach(function (stat) {
18239
- trim_unreachable_code(compressor, stat, statements);
18249
+ extract_from_unreachable_code(compressor, stat, statements);
18240
18250
  });
18241
18251
  }
18242
18252
 
@@ -18308,7 +18318,7 @@ function tighten_body(statements, compressor) {
18308
18318
  var stat = statements[i];
18309
18319
  if (prev) {
18310
18320
  if (stat instanceof AST_Exit) {
18311
- stat.value = cons_seq(stat.value || make_node(AST_Undefined, stat).transform(compressor));
18321
+ stat.value = cons_seq(stat.value || make_void_0(stat).transform(compressor));
18312
18322
  } else if (stat instanceof AST_For) {
18313
18323
  if (!(stat.init instanceof AST_DefinitionsLike)) {
18314
18324
  const abort = walk(prev.body, node => {
@@ -18812,7 +18822,7 @@ function inline_into_call(self, compressor) {
18812
18822
  if (returned) {
18813
18823
  returned = returned.clone(true);
18814
18824
  } else {
18815
- returned = make_node(AST_Undefined, self);
18825
+ returned = make_void_0(self);
18816
18826
  }
18817
18827
  const args = self.args.concat(returned);
18818
18828
  return make_sequence(self, args).optimize(compressor);
@@ -18828,7 +18838,7 @@ function inline_into_call(self, compressor) {
18828
18838
  && returned.name === fn.argnames[0].name
18829
18839
  ) {
18830
18840
  const replacement =
18831
- (self.args[0] || make_node(AST_Undefined)).optimize(compressor);
18841
+ (self.args[0] || make_void_0()).optimize(compressor);
18832
18842
 
18833
18843
  let parent;
18834
18844
  if (
@@ -18910,7 +18920,7 @@ function inline_into_call(self, compressor) {
18910
18920
 
18911
18921
  const can_drop_this_call = is_regular_func && compressor.option("side_effects") && fn.body.every(is_empty);
18912
18922
  if (can_drop_this_call) {
18913
- var args = self.args.concat(make_node(AST_Undefined, self));
18923
+ var args = self.args.concat(make_void_0(self));
18914
18924
  return make_sequence(self, args).optimize(compressor);
18915
18925
  }
18916
18926
 
@@ -18929,9 +18939,9 @@ function inline_into_call(self, compressor) {
18929
18939
  return self;
18930
18940
 
18931
18941
  function return_value(stat) {
18932
- if (!stat) return make_node(AST_Undefined, self);
18942
+ if (!stat) return make_void_0(self);
18933
18943
  if (stat instanceof AST_Return) {
18934
- if (!stat.value) return make_node(AST_Undefined, self);
18944
+ if (!stat.value) return make_void_0(self);
18935
18945
  return stat.value.clone(true);
18936
18946
  }
18937
18947
  if (stat instanceof AST_SimpleStatement) {
@@ -19077,7 +19087,7 @@ function inline_into_call(self, compressor) {
19077
19087
  } else {
19078
19088
  var symbol = make_node(AST_SymbolVar, name, name);
19079
19089
  name.definition().orig.push(symbol);
19080
- if (!value && in_loop) value = make_node(AST_Undefined, self);
19090
+ if (!value && in_loop) value = make_void_0(self);
19081
19091
  append_var(decls, expressions, symbol, value);
19082
19092
  }
19083
19093
  }
@@ -19104,7 +19114,7 @@ function inline_into_call(self, compressor) {
19104
19114
  operator: "=",
19105
19115
  logical: false,
19106
19116
  left: sym,
19107
- right: make_node(AST_Undefined, name)
19117
+ right: make_void_0(name),
19108
19118
  }));
19109
19119
  }
19110
19120
  }
@@ -19417,6 +19427,11 @@ class Compressor extends TreeWalker {
19417
19427
  }
19418
19428
  }
19419
19429
 
19430
+ /** True if compressor.self()'s result will be turned into a 32-bit integer.
19431
+ * ex:
19432
+ * ~{expr}
19433
+ * (1, 2, {expr}) | 0
19434
+ **/
19420
19435
  in_32_bit_context(other_operand_must_be_number) {
19421
19436
  if (!this.option("evaluate")) return false;
19422
19437
  var self = this.self();
@@ -19434,9 +19449,10 @@ class Compressor extends TreeWalker {
19434
19449
  if (
19435
19450
  p instanceof AST_Binary
19436
19451
  && (
19437
- p.operator == "&&"
19438
- || p.operator == "||"
19439
- || p.operator == "??"
19452
+ // Don't talk about p.left. Can change branch taken
19453
+ p.operator == "&&" && p.right === self
19454
+ || p.operator == "||" && p.right === self
19455
+ || p.operator == "??" && p.right === self
19440
19456
  )
19441
19457
  || p instanceof AST_Conditional && p.condition !== self
19442
19458
  || p.tail_node() === self
@@ -19595,7 +19611,7 @@ AST_Toplevel.DEFMETHOD("drop_console", function(options) {
19595
19611
  set_flag(exp.expression, SQUEEZED);
19596
19612
  self.args = [];
19597
19613
  } else {
19598
- return make_node(AST_Undefined, self);
19614
+ return make_void_0(self);
19599
19615
  }
19600
19616
  }
19601
19617
  });
@@ -19623,12 +19639,7 @@ AST_Scope.DEFMETHOD("process_expression", function(insert, compressor) {
19623
19639
  : make_node(AST_EmptyStatement, node);
19624
19640
  }
19625
19641
  return make_node(AST_SimpleStatement, node, {
19626
- body: node.value || make_node(AST_UnaryPrefix, node, {
19627
- operator: "void",
19628
- expression: make_node(AST_Number, node, {
19629
- value: 0
19630
- })
19631
- })
19642
+ body: node.value || make_void_0(node)
19632
19643
  });
19633
19644
  }
19634
19645
  if (node instanceof AST_Class || node instanceof AST_Lambda && node !== self) {
@@ -20039,7 +20050,7 @@ function if_break_in_loop(self, compressor) {
20039
20050
  body: self.condition
20040
20051
  }));
20041
20052
  }
20042
- trim_unreachable_code(compressor, self.body, body);
20053
+ extract_from_unreachable_code(compressor, self.body, body);
20043
20054
  return make_node(AST_BlockStatement, self, {
20044
20055
  body: body
20045
20056
  });
@@ -20110,7 +20121,7 @@ def_optimize(AST_For, function(self, compressor) {
20110
20121
  if (cond instanceof AST_Node) cond = self.condition.tail_node().evaluate(compressor);
20111
20122
  if (!cond) {
20112
20123
  var body = [];
20113
- trim_unreachable_code(compressor, self.body, body);
20124
+ extract_from_unreachable_code(compressor, self.body, body);
20114
20125
  if (self.init instanceof AST_Statement) {
20115
20126
  body.push(self.init);
20116
20127
  } else if (self.init) {
@@ -20146,7 +20157,7 @@ def_optimize(AST_If, function(self, compressor) {
20146
20157
  if (cond instanceof AST_Node) cond = self.condition.tail_node().evaluate(compressor);
20147
20158
  if (!cond) {
20148
20159
  var body = [];
20149
- trim_unreachable_code(compressor, self.body, body);
20160
+ extract_from_unreachable_code(compressor, self.body, body);
20150
20161
  body.push(make_node(AST_SimpleStatement, self.condition, {
20151
20162
  body: self.condition
20152
20163
  }));
@@ -20159,7 +20170,7 @@ def_optimize(AST_If, function(self, compressor) {
20159
20170
  }));
20160
20171
  body.push(self.body);
20161
20172
  if (self.alternative) {
20162
- trim_unreachable_code(compressor, self.alternative, body);
20173
+ extract_from_unreachable_code(compressor, self.alternative, body);
20163
20174
  }
20164
20175
  return make_node(AST_BlockStatement, self, { body: body }).optimize(compressor);
20165
20176
  }
@@ -20231,8 +20242,8 @@ def_optimize(AST_If, function(self, compressor) {
20231
20242
  return make_node(self.body.CTOR, self, {
20232
20243
  value: make_node(AST_Conditional, self, {
20233
20244
  condition : self.condition,
20234
- consequent : self.body.value || make_node(AST_Undefined, self.body),
20235
- alternative : self.alternative.value || make_node(AST_Undefined, self.alternative)
20245
+ consequent : self.body.value || make_void_0(self.body),
20246
+ alternative : self.alternative.value || make_void_0(self.alternative),
20236
20247
  }).transform(compressor)
20237
20248
  }).optimize(compressor);
20238
20249
  }
@@ -20287,6 +20298,9 @@ def_optimize(AST_Switch, function(self, compressor) {
20287
20298
  var body = [];
20288
20299
  var default_branch;
20289
20300
  var exact_match;
20301
+ // - compress self.body into `body`
20302
+ // - find and deduplicate default branch
20303
+ // - find the exact match (`case 1234` inside `switch(1234)`)
20290
20304
  for (var i = 0, len = self.body.length; i < len && !exact_match; i++) {
20291
20305
  branch = self.body[i];
20292
20306
  if (branch instanceof AST_Default) {
@@ -20316,6 +20330,7 @@ def_optimize(AST_Switch, function(self, compressor) {
20316
20330
  }
20317
20331
  body.push(branch);
20318
20332
  }
20333
+ // i < len if we found an exact_match. eliminate the rest
20319
20334
  while (i < len) eliminate_branch(self.body[i++], body[body.length - 1]);
20320
20335
  self.body = body;
20321
20336
 
@@ -20387,7 +20402,7 @@ def_optimize(AST_Switch, function(self, compressor) {
20387
20402
  let i = body.length - 1;
20388
20403
  for (; i >= 0; i--) {
20389
20404
  let bbody = body[i].body;
20390
- if (is_break(bbody[bbody.length - 1], compressor)) bbody.pop();
20405
+ while (is_break(bbody[bbody.length - 1], compressor)) bbody.pop();
20391
20406
  if (!is_inert_body(body[i])) break;
20392
20407
  }
20393
20408
  // i now points to the index of a branch that contains a body. By incrementing, it's
@@ -20403,9 +20418,9 @@ def_optimize(AST_Switch, function(self, compressor) {
20403
20418
  let branch = body[j];
20404
20419
  if (branch === default_or_exact) {
20405
20420
  default_or_exact = null;
20406
- body.pop();
20421
+ eliminate_branch(body.pop());
20407
20422
  } else if (!branch.expression.has_side_effects(compressor)) {
20408
- body.pop();
20423
+ eliminate_branch(body.pop());
20409
20424
  } else {
20410
20425
  break;
20411
20426
  }
@@ -20519,15 +20534,16 @@ def_optimize(AST_Switch, function(self, compressor) {
20519
20534
  // and there's a side-effect somewhere. Just let the below paths take care of it.
20520
20535
  }
20521
20536
 
20537
+ // Reintegrate `decl` (var statements)
20522
20538
  if (body.length > 0) {
20523
20539
  body[0].body = decl.concat(body[0].body);
20524
20540
  }
20525
-
20526
20541
  if (body.length == 0) {
20527
20542
  return make_node(AST_BlockStatement, self, {
20528
20543
  body: decl.concat(statement(self.expression))
20529
20544
  }).optimize(compressor);
20530
20545
  }
20546
+
20531
20547
  if (body.length == 1 && !has_nested_break(self)) {
20532
20548
  // This is the last case body, and we've already pruned any breaks, so it's
20533
20549
  // safe to hoist.
@@ -20607,7 +20623,7 @@ def_optimize(AST_Switch, function(self, compressor) {
20607
20623
  if (prev && !aborts(prev)) {
20608
20624
  prev.body = prev.body.concat(branch.body);
20609
20625
  } else {
20610
- trim_unreachable_code(compressor, branch, decl);
20626
+ extract_from_unreachable_code(compressor, branch, decl);
20611
20627
  }
20612
20628
  }
20613
20629
  function branches_equivalent(branch, prev, insertBreak) {
@@ -20661,7 +20677,7 @@ def_optimize(AST_Try, function(self, compressor) {
20661
20677
  if (compressor.option("dead_code") && self.body.body.every(is_empty)) {
20662
20678
  var body = [];
20663
20679
  if (self.bcatch) {
20664
- trim_unreachable_code(compressor, self.bcatch, body);
20680
+ extract_from_unreachable_code(compressor, self.bcatch, body);
20665
20681
  }
20666
20682
  if (self.bfinally) body.push(...self.bfinally.body);
20667
20683
  return make_node(AST_BlockStatement, self, {
@@ -20780,7 +20796,7 @@ def_optimize(AST_Call, function(self, compressor) {
20780
20796
  const value = condition.evaluate(compressor);
20781
20797
 
20782
20798
  if (value === 1 || value === true) {
20783
- return make_node(AST_Undefined, self);
20799
+ return make_void_0(self).optimize(compressor);
20784
20800
  }
20785
20801
  }
20786
20802
  }
@@ -21142,6 +21158,10 @@ def_optimize(AST_UnaryPrefix, function(self, compressor) {
21142
21158
  ) {
21143
21159
  return make_sequence(self, [e, make_node(AST_True, self)]).optimize(compressor);
21144
21160
  }
21161
+ // Short-circuit common `void 0`
21162
+ if (self.operator === "void" && e instanceof AST_Number && e.value === 0) {
21163
+ return unsafe_undefined_ref(self, compressor) || self;
21164
+ }
21145
21165
  var seq = self.lift_sequences(compressor);
21146
21166
  if (seq !== self) {
21147
21167
  return seq;
@@ -21152,7 +21172,7 @@ def_optimize(AST_UnaryPrefix, function(self, compressor) {
21152
21172
  self.expression = e;
21153
21173
  return self;
21154
21174
  } else {
21155
- return make_node(AST_Undefined, self).optimize(compressor);
21175
+ return make_void_0(self).optimize(compressor);
21156
21176
  }
21157
21177
  }
21158
21178
  if (compressor.in_boolean_context()) {
@@ -21338,7 +21358,7 @@ def_optimize(AST_Binary, function(self, compressor) {
21338
21358
  if (expr instanceof AST_SymbolRef ? expr.is_declared(compressor)
21339
21359
  : !(expr instanceof AST_PropAccess && compressor.option("ie8"))) {
21340
21360
  self.right = expr;
21341
- self.left = make_node(AST_Undefined, self.left).optimize(compressor);
21361
+ self.left = make_void_0(self.left).optimize(compressor);
21342
21362
  if (self.operator.length == 2) self.operator += "=";
21343
21363
  }
21344
21364
  } else if (compressor.option("typeofs")
@@ -21351,7 +21371,7 @@ def_optimize(AST_Binary, function(self, compressor) {
21351
21371
  if (expr instanceof AST_SymbolRef ? expr.is_declared(compressor)
21352
21372
  : !(expr instanceof AST_PropAccess && compressor.option("ie8"))) {
21353
21373
  self.left = expr;
21354
- self.right = make_node(AST_Undefined, self.right).optimize(compressor);
21374
+ self.right = make_void_0(self.right).optimize(compressor);
21355
21375
  if (self.operator.length == 2) self.operator += "=";
21356
21376
  }
21357
21377
  } else if (self.left instanceof AST_SymbolRef
@@ -21992,7 +22012,8 @@ function is_atomic(lhs, self) {
21992
22012
  return lhs instanceof AST_SymbolRef || lhs.TYPE === self.TYPE;
21993
22013
  }
21994
22014
 
21995
- def_optimize(AST_Undefined, function(self, compressor) {
22015
+ /** Apply the `unsafe_undefined` option: find a variable called `undefined` and turn `self` into a reference to it. */
22016
+ function unsafe_undefined_ref(self, compressor) {
21996
22017
  if (compressor.option("unsafe_undefined")) {
21997
22018
  var undef = find_variable(compressor, "undefined");
21998
22019
  if (undef) {
@@ -22005,14 +22026,15 @@ def_optimize(AST_Undefined, function(self, compressor) {
22005
22026
  return ref;
22006
22027
  }
22007
22028
  }
22029
+ return null;
22030
+ }
22031
+
22032
+ def_optimize(AST_Undefined, function(self, compressor) {
22033
+ var symbolref = unsafe_undefined_ref(self, compressor);
22034
+ if (symbolref) return symbolref;
22008
22035
  var lhs = compressor.is_lhs();
22009
22036
  if (lhs && is_atomic(lhs, self)) return self;
22010
- return make_node(AST_UnaryPrefix, self, {
22011
- operator: "void",
22012
- expression: make_node(AST_Number, self, {
22013
- value: 0
22014
- })
22015
- });
22037
+ return make_void_0(self);
22016
22038
  });
22017
22039
 
22018
22040
  def_optimize(AST_Infinity, function(self, compressor) {
@@ -22699,7 +22721,7 @@ def_optimize(AST_Sub, function(self, compressor) {
22699
22721
  }
22700
22722
  }
22701
22723
  if (retValue instanceof AST_Expansion) break FLATTEN;
22702
- retValue = retValue instanceof AST_Hole ? make_node(AST_Undefined, retValue) : retValue;
22724
+ retValue = retValue instanceof AST_Hole ? make_void_0(retValue) : retValue;
22703
22725
  if (!flatten) values.unshift(retValue);
22704
22726
  while (--i >= 0) {
22705
22727
  var value = elements[i];
@@ -22738,7 +22760,7 @@ def_optimize(AST_Chain, function (self, compressor) {
22738
22760
  if (parent instanceof AST_UnaryPrefix && parent.operator === "delete") {
22739
22761
  return make_node_from_constant(0, self);
22740
22762
  }
22741
- return make_node(AST_Undefined, self);
22763
+ return make_void_0(self).optimize(compressor);
22742
22764
  }
22743
22765
  if (
22744
22766
  self.expression instanceof AST_PropAccess
@@ -87,7 +87,7 @@ import {
87
87
  walk_abort,
88
88
  walk_parent,
89
89
  } from "../ast.js";
90
- import { make_node, regexp_source_fix, string_template, makePredicate } from "../utils/index.js";
90
+ import { make_node, make_void_0, regexp_source_fix, string_template, makePredicate } from "../utils/index.js";
91
91
  import { first_in_statement } from "../utils/first_in_statement.js";
92
92
  import { has_flag, TOP } from "./compressor-flags.js";
93
93
 
@@ -148,7 +148,7 @@ export function make_node_from_constant(val, orig) {
148
148
  case "boolean":
149
149
  return make_node(val ? AST_True : AST_False, orig);
150
150
  case "undefined":
151
- return make_node(AST_Undefined, orig);
151
+ return make_void_0(orig);
152
152
  default:
153
153
  if (val === null) {
154
154
  return make_node(AST_Null, orig, { value: null });
@@ -146,6 +146,7 @@ import {
146
146
  defaults,
147
147
  HOP,
148
148
  make_node,
149
+ make_void_0,
149
150
  makePredicate,
150
151
  MAP,
151
152
  remove,
@@ -211,7 +212,7 @@ import {
211
212
  as_statement_array,
212
213
  is_func_expr,
213
214
  } from "./common.js";
214
- import { tighten_body, trim_unreachable_code } from "./tighten-body.js";
215
+ import { tighten_body, extract_from_unreachable_code } from "./tighten-body.js";
215
216
  import { inline_into_symbolref, inline_into_call } from "./inline.js";
216
217
  import "./global-defs.js";
217
218
 
@@ -378,6 +379,11 @@ class Compressor extends TreeWalker {
378
379
  }
379
380
  }
380
381
 
382
+ /** True if compressor.self()'s result will be turned into a 32-bit integer.
383
+ * ex:
384
+ * ~{expr}
385
+ * (1, 2, {expr}) | 0
386
+ **/
381
387
  in_32_bit_context(other_operand_must_be_number) {
382
388
  if (!this.option("evaluate")) return false;
383
389
  var self = this.self();
@@ -395,9 +401,10 @@ class Compressor extends TreeWalker {
395
401
  if (
396
402
  p instanceof AST_Binary
397
403
  && (
398
- p.operator == "&&"
399
- || p.operator == "||"
400
- || p.operator == "??"
404
+ // Don't talk about p.left. Can change branch taken
405
+ p.operator == "&&" && p.right === self
406
+ || p.operator == "||" && p.right === self
407
+ || p.operator == "??" && p.right === self
401
408
  )
402
409
  || p instanceof AST_Conditional && p.condition !== self
403
410
  || p.tail_node() === self
@@ -556,7 +563,7 @@ AST_Toplevel.DEFMETHOD("drop_console", function(options) {
556
563
  set_flag(exp.expression, SQUEEZED);
557
564
  self.args = [];
558
565
  } else {
559
- return make_node(AST_Undefined, self);
566
+ return make_void_0(self);
560
567
  }
561
568
  }
562
569
  });
@@ -584,12 +591,7 @@ AST_Scope.DEFMETHOD("process_expression", function(insert, compressor) {
584
591
  : make_node(AST_EmptyStatement, node);
585
592
  }
586
593
  return make_node(AST_SimpleStatement, node, {
587
- body: node.value || make_node(AST_UnaryPrefix, node, {
588
- operator: "void",
589
- expression: make_node(AST_Number, node, {
590
- value: 0
591
- })
592
- })
594
+ body: node.value || make_void_0(node)
593
595
  });
594
596
  }
595
597
  if (node instanceof AST_Class || node instanceof AST_Lambda && node !== self) {
@@ -1000,7 +1002,7 @@ function if_break_in_loop(self, compressor) {
1000
1002
  body: self.condition
1001
1003
  }));
1002
1004
  }
1003
- trim_unreachable_code(compressor, self.body, body);
1005
+ extract_from_unreachable_code(compressor, self.body, body);
1004
1006
  return make_node(AST_BlockStatement, self, {
1005
1007
  body: body
1006
1008
  });
@@ -1071,7 +1073,7 @@ def_optimize(AST_For, function(self, compressor) {
1071
1073
  if (cond instanceof AST_Node) cond = self.condition.tail_node().evaluate(compressor);
1072
1074
  if (!cond) {
1073
1075
  var body = [];
1074
- trim_unreachable_code(compressor, self.body, body);
1076
+ extract_from_unreachable_code(compressor, self.body, body);
1075
1077
  if (self.init instanceof AST_Statement) {
1076
1078
  body.push(self.init);
1077
1079
  } else if (self.init) {
@@ -1107,7 +1109,7 @@ def_optimize(AST_If, function(self, compressor) {
1107
1109
  if (cond instanceof AST_Node) cond = self.condition.tail_node().evaluate(compressor);
1108
1110
  if (!cond) {
1109
1111
  var body = [];
1110
- trim_unreachable_code(compressor, self.body, body);
1112
+ extract_from_unreachable_code(compressor, self.body, body);
1111
1113
  body.push(make_node(AST_SimpleStatement, self.condition, {
1112
1114
  body: self.condition
1113
1115
  }));
@@ -1120,7 +1122,7 @@ def_optimize(AST_If, function(self, compressor) {
1120
1122
  }));
1121
1123
  body.push(self.body);
1122
1124
  if (self.alternative) {
1123
- trim_unreachable_code(compressor, self.alternative, body);
1125
+ extract_from_unreachable_code(compressor, self.alternative, body);
1124
1126
  }
1125
1127
  return make_node(AST_BlockStatement, self, { body: body }).optimize(compressor);
1126
1128
  }
@@ -1192,8 +1194,8 @@ def_optimize(AST_If, function(self, compressor) {
1192
1194
  return make_node(self.body.CTOR, self, {
1193
1195
  value: make_node(AST_Conditional, self, {
1194
1196
  condition : self.condition,
1195
- consequent : self.body.value || make_node(AST_Undefined, self.body),
1196
- alternative : self.alternative.value || make_node(AST_Undefined, self.alternative)
1197
+ consequent : self.body.value || make_void_0(self.body),
1198
+ alternative : self.alternative.value || make_void_0(self.alternative),
1197
1199
  }).transform(compressor)
1198
1200
  }).optimize(compressor);
1199
1201
  }
@@ -1248,6 +1250,9 @@ def_optimize(AST_Switch, function(self, compressor) {
1248
1250
  var body = [];
1249
1251
  var default_branch;
1250
1252
  var exact_match;
1253
+ // - compress self.body into `body`
1254
+ // - find and deduplicate default branch
1255
+ // - find the exact match (`case 1234` inside `switch(1234)`)
1251
1256
  for (var i = 0, len = self.body.length; i < len && !exact_match; i++) {
1252
1257
  branch = self.body[i];
1253
1258
  if (branch instanceof AST_Default) {
@@ -1277,6 +1282,7 @@ def_optimize(AST_Switch, function(self, compressor) {
1277
1282
  }
1278
1283
  body.push(branch);
1279
1284
  }
1285
+ // i < len if we found an exact_match. eliminate the rest
1280
1286
  while (i < len) eliminate_branch(self.body[i++], body[body.length - 1]);
1281
1287
  self.body = body;
1282
1288
 
@@ -1348,7 +1354,7 @@ def_optimize(AST_Switch, function(self, compressor) {
1348
1354
  let i = body.length - 1;
1349
1355
  for (; i >= 0; i--) {
1350
1356
  let bbody = body[i].body;
1351
- if (is_break(bbody[bbody.length - 1], compressor)) bbody.pop();
1357
+ while (is_break(bbody[bbody.length - 1], compressor)) bbody.pop();
1352
1358
  if (!is_inert_body(body[i])) break;
1353
1359
  }
1354
1360
  // i now points to the index of a branch that contains a body. By incrementing, it's
@@ -1364,9 +1370,9 @@ def_optimize(AST_Switch, function(self, compressor) {
1364
1370
  let branch = body[j];
1365
1371
  if (branch === default_or_exact) {
1366
1372
  default_or_exact = null;
1367
- body.pop();
1373
+ eliminate_branch(body.pop());
1368
1374
  } else if (!branch.expression.has_side_effects(compressor)) {
1369
- body.pop();
1375
+ eliminate_branch(body.pop());
1370
1376
  } else {
1371
1377
  break;
1372
1378
  }
@@ -1480,15 +1486,16 @@ def_optimize(AST_Switch, function(self, compressor) {
1480
1486
  // and there's a side-effect somewhere. Just let the below paths take care of it.
1481
1487
  }
1482
1488
 
1489
+ // Reintegrate `decl` (var statements)
1483
1490
  if (body.length > 0) {
1484
1491
  body[0].body = decl.concat(body[0].body);
1485
1492
  }
1486
-
1487
1493
  if (body.length == 0) {
1488
1494
  return make_node(AST_BlockStatement, self, {
1489
1495
  body: decl.concat(statement(self.expression))
1490
1496
  }).optimize(compressor);
1491
1497
  }
1498
+
1492
1499
  if (body.length == 1 && !has_nested_break(self)) {
1493
1500
  // This is the last case body, and we've already pruned any breaks, so it's
1494
1501
  // safe to hoist.
@@ -1568,7 +1575,7 @@ def_optimize(AST_Switch, function(self, compressor) {
1568
1575
  if (prev && !aborts(prev)) {
1569
1576
  prev.body = prev.body.concat(branch.body);
1570
1577
  } else {
1571
- trim_unreachable_code(compressor, branch, decl);
1578
+ extract_from_unreachable_code(compressor, branch, decl);
1572
1579
  }
1573
1580
  }
1574
1581
  function branches_equivalent(branch, prev, insertBreak) {
@@ -1622,7 +1629,7 @@ def_optimize(AST_Try, function(self, compressor) {
1622
1629
  if (compressor.option("dead_code") && self.body.body.every(is_empty)) {
1623
1630
  var body = [];
1624
1631
  if (self.bcatch) {
1625
- trim_unreachable_code(compressor, self.bcatch, body);
1632
+ extract_from_unreachable_code(compressor, self.bcatch, body);
1626
1633
  }
1627
1634
  if (self.bfinally) body.push(...self.bfinally.body);
1628
1635
  return make_node(AST_BlockStatement, self, {
@@ -1741,7 +1748,7 @@ def_optimize(AST_Call, function(self, compressor) {
1741
1748
  const value = condition.evaluate(compressor);
1742
1749
 
1743
1750
  if (value === 1 || value === true) {
1744
- return make_node(AST_Undefined, self);
1751
+ return make_void_0(self).optimize(compressor);
1745
1752
  }
1746
1753
  }
1747
1754
  }
@@ -2103,6 +2110,10 @@ def_optimize(AST_UnaryPrefix, function(self, compressor) {
2103
2110
  ) {
2104
2111
  return make_sequence(self, [e, make_node(AST_True, self)]).optimize(compressor);
2105
2112
  }
2113
+ // Short-circuit common `void 0`
2114
+ if (self.operator === "void" && e instanceof AST_Number && e.value === 0) {
2115
+ return unsafe_undefined_ref(self, compressor) || self;
2116
+ }
2106
2117
  var seq = self.lift_sequences(compressor);
2107
2118
  if (seq !== self) {
2108
2119
  return seq;
@@ -2113,7 +2124,7 @@ def_optimize(AST_UnaryPrefix, function(self, compressor) {
2113
2124
  self.expression = e;
2114
2125
  return self;
2115
2126
  } else {
2116
- return make_node(AST_Undefined, self).optimize(compressor);
2127
+ return make_void_0(self).optimize(compressor);
2117
2128
  }
2118
2129
  }
2119
2130
  if (compressor.in_boolean_context()) {
@@ -2299,7 +2310,7 @@ def_optimize(AST_Binary, function(self, compressor) {
2299
2310
  if (expr instanceof AST_SymbolRef ? expr.is_declared(compressor)
2300
2311
  : !(expr instanceof AST_PropAccess && compressor.option("ie8"))) {
2301
2312
  self.right = expr;
2302
- self.left = make_node(AST_Undefined, self.left).optimize(compressor);
2313
+ self.left = make_void_0(self.left).optimize(compressor);
2303
2314
  if (self.operator.length == 2) self.operator += "=";
2304
2315
  }
2305
2316
  } else if (compressor.option("typeofs")
@@ -2312,7 +2323,7 @@ def_optimize(AST_Binary, function(self, compressor) {
2312
2323
  if (expr instanceof AST_SymbolRef ? expr.is_declared(compressor)
2313
2324
  : !(expr instanceof AST_PropAccess && compressor.option("ie8"))) {
2314
2325
  self.left = expr;
2315
- self.right = make_node(AST_Undefined, self.right).optimize(compressor);
2326
+ self.right = make_void_0(self.right).optimize(compressor);
2316
2327
  if (self.operator.length == 2) self.operator += "=";
2317
2328
  }
2318
2329
  } else if (self.left instanceof AST_SymbolRef
@@ -2953,7 +2964,8 @@ function is_atomic(lhs, self) {
2953
2964
  return lhs instanceof AST_SymbolRef || lhs.TYPE === self.TYPE;
2954
2965
  }
2955
2966
 
2956
- def_optimize(AST_Undefined, function(self, compressor) {
2967
+ /** Apply the `unsafe_undefined` option: find a variable called `undefined` and turn `self` into a reference to it. */
2968
+ function unsafe_undefined_ref(self, compressor) {
2957
2969
  if (compressor.option("unsafe_undefined")) {
2958
2970
  var undef = find_variable(compressor, "undefined");
2959
2971
  if (undef) {
@@ -2966,14 +2978,15 @@ def_optimize(AST_Undefined, function(self, compressor) {
2966
2978
  return ref;
2967
2979
  }
2968
2980
  }
2981
+ return null;
2982
+ }
2983
+
2984
+ def_optimize(AST_Undefined, function(self, compressor) {
2985
+ var symbolref = unsafe_undefined_ref(self, compressor);
2986
+ if (symbolref) return symbolref;
2969
2987
  var lhs = compressor.is_lhs();
2970
2988
  if (lhs && is_atomic(lhs, self)) return self;
2971
- return make_node(AST_UnaryPrefix, self, {
2972
- operator: "void",
2973
- expression: make_node(AST_Number, self, {
2974
- value: 0
2975
- })
2976
- });
2989
+ return make_void_0(self);
2977
2990
  });
2978
2991
 
2979
2992
  def_optimize(AST_Infinity, function(self, compressor) {
@@ -3660,7 +3673,7 @@ def_optimize(AST_Sub, function(self, compressor) {
3660
3673
  }
3661
3674
  }
3662
3675
  if (retValue instanceof AST_Expansion) break FLATTEN;
3663
- retValue = retValue instanceof AST_Hole ? make_node(AST_Undefined, retValue) : retValue;
3676
+ retValue = retValue instanceof AST_Hole ? make_void_0(retValue) : retValue;
3664
3677
  if (!flatten) values.unshift(retValue);
3665
3678
  while (--i >= 0) {
3666
3679
  var value = elements[i];
@@ -3699,7 +3712,7 @@ def_optimize(AST_Chain, function (self, compressor) {
3699
3712
  if (parent instanceof AST_UnaryPrefix && parent.operator === "delete") {
3700
3713
  return make_node_from_constant(0, self);
3701
3714
  }
3702
- return make_node(AST_Undefined, self);
3715
+ return make_void_0(self).optimize(compressor);
3703
3716
  }
3704
3717
  if (
3705
3718
  self.expression instanceof AST_PropAccess
@@ -76,7 +76,6 @@ import {
76
76
  AST_This,
77
77
  AST_Toplevel,
78
78
  AST_UnaryPrefix,
79
- AST_Undefined,
80
79
  AST_Var,
81
80
  AST_VarDef,
82
81
 
@@ -86,7 +85,7 @@ import {
86
85
  _NOINLINE,
87
86
  _PURE,
88
87
  } from "../ast.js";
89
- import { make_node, has_annotation } from "../utils/index.js";
88
+ import { make_node, make_void_0, has_annotation } from "../utils/index.js";
90
89
  import "../size.js";
91
90
 
92
91
  import "./evaluate.js";
@@ -358,7 +357,7 @@ export function inline_into_call(self, compressor) {
358
357
  if (returned) {
359
358
  returned = returned.clone(true);
360
359
  } else {
361
- returned = make_node(AST_Undefined, self);
360
+ returned = make_void_0(self);
362
361
  }
363
362
  const args = self.args.concat(returned);
364
363
  return make_sequence(self, args).optimize(compressor);
@@ -374,7 +373,7 @@ export function inline_into_call(self, compressor) {
374
373
  && returned.name === fn.argnames[0].name
375
374
  ) {
376
375
  const replacement =
377
- (self.args[0] || make_node(AST_Undefined)).optimize(compressor);
376
+ (self.args[0] || make_void_0()).optimize(compressor);
378
377
 
379
378
  let parent;
380
379
  if (
@@ -456,7 +455,7 @@ export function inline_into_call(self, compressor) {
456
455
 
457
456
  const can_drop_this_call = is_regular_func && compressor.option("side_effects") && fn.body.every(is_empty);
458
457
  if (can_drop_this_call) {
459
- var args = self.args.concat(make_node(AST_Undefined, self));
458
+ var args = self.args.concat(make_void_0(self));
460
459
  return make_sequence(self, args).optimize(compressor);
461
460
  }
462
461
 
@@ -475,9 +474,9 @@ export function inline_into_call(self, compressor) {
475
474
  return self;
476
475
 
477
476
  function return_value(stat) {
478
- if (!stat) return make_node(AST_Undefined, self);
477
+ if (!stat) return make_void_0(self);
479
478
  if (stat instanceof AST_Return) {
480
- if (!stat.value) return make_node(AST_Undefined, self);
479
+ if (!stat.value) return make_void_0(self);
481
480
  return stat.value.clone(true);
482
481
  }
483
482
  if (stat instanceof AST_SimpleStatement) {
@@ -623,7 +622,7 @@ export function inline_into_call(self, compressor) {
623
622
  } else {
624
623
  var symbol = make_node(AST_SymbolVar, name, name);
625
624
  name.definition().orig.push(symbol);
626
- if (!value && in_loop) value = make_node(AST_Undefined, self);
625
+ if (!value && in_loop) value = make_void_0(self);
627
626
  append_var(decls, expressions, symbol, value);
628
627
  }
629
628
  }
@@ -650,7 +649,7 @@ export function inline_into_call(self, compressor) {
650
649
  operator: "=",
651
650
  logical: false,
652
651
  left: sym,
653
- right: make_node(AST_Undefined, name)
652
+ right: make_void_0(name),
654
653
  }));
655
654
  }
656
655
  }
@@ -87,7 +87,6 @@ import {
87
87
  AST_Try,
88
88
  AST_Unary,
89
89
  AST_UnaryPrefix,
90
- AST_Undefined,
91
90
  AST_UsingDef,
92
91
  AST_VarDef,
93
92
  AST_VarDefLike,
@@ -98,7 +97,7 @@ import {
98
97
  walk_body,
99
98
  walk_parent,
100
99
  } from "../ast.js";
101
- import { HOP, make_node, noop } from "../utils/index.js";
100
+ import { HOP, make_node, make_void_0, noop } from "../utils/index.js";
102
101
 
103
102
  import { lazy_op, is_modified, is_lhs } from "./inference.js";
104
103
  import { INLINED, clear_flag } from "./compressor-flags.js";
@@ -172,7 +171,7 @@ function safe_to_read(tw, def) {
172
171
  if (def.fixed == null) {
173
172
  var orig = def.orig[0];
174
173
  if (orig instanceof AST_SymbolFunarg || orig.name == "arguments") return false;
175
- def.fixed = make_node(AST_Undefined, orig);
174
+ def.fixed = make_void_0(orig);
176
175
  }
177
176
  return true;
178
177
  }
@@ -470,7 +469,7 @@ function mark_lambda(tw, descend, compressor) {
470
469
  if (d.orig.length > 1) return;
471
470
  if (d.fixed === undefined && (!this.uses_arguments || tw.has_directive("use strict"))) {
472
471
  d.fixed = function() {
473
- return iife.args[i] || make_node(AST_Undefined, iife);
472
+ return iife.args[i] || make_void_0(iife);
474
473
  };
475
474
  tw.loop_ids.set(d.id, tw.in_loop);
476
475
  mark(tw, d, true);
@@ -106,7 +106,6 @@ import {
106
106
  AST_Unary,
107
107
  AST_UnaryPostfix,
108
108
  AST_UnaryPrefix,
109
- AST_Undefined,
110
109
  AST_Using,
111
110
  AST_Var,
112
111
  AST_VarDef,
@@ -122,6 +121,7 @@ import {
122
121
  } from "../ast.js";
123
122
  import {
124
123
  make_node,
124
+ make_void_0,
125
125
  MAP,
126
126
  member,
127
127
  remove,
@@ -191,8 +191,8 @@ function remove_initializers(var_statement) {
191
191
  return decls.length ? make_node(AST_Var, var_statement, { definitions: decls }) : null;
192
192
  }
193
193
 
194
- /** Called on code which we know is unreachable, to keep elements that affect outside of it. */
195
- export function trim_unreachable_code(compressor, stat, target) {
194
+ /** Called on code which won't be executed but has an effect outside of itself: `var`, `function` statements, `export`, `import`. */
195
+ export function extract_from_unreachable_code(compressor, stat, target) {
196
196
  walk(stat, node => {
197
197
  if (node instanceof AST_Var) {
198
198
  const no_initializers = remove_initializers(node);
@@ -217,7 +217,8 @@ export function trim_unreachable_code(compressor, stat, target) {
217
217
  target.push(node);
218
218
  return true;
219
219
  }
220
- if (node instanceof AST_Scope) {
220
+ if (node instanceof AST_Scope || node instanceof AST_Class) {
221
+ // Do not go into nested scopes
221
222
  return true;
222
223
  }
223
224
  });
@@ -634,7 +635,7 @@ export function tighten_body(statements, compressor) {
634
635
  }
635
636
  } else {
636
637
  if (!arg) {
637
- arg = make_node(AST_Undefined, sym).transform(compressor);
638
+ arg = make_void_0(sym).transform(compressor);
638
639
  } else if (arg instanceof AST_Lambda && arg.pinned()
639
640
  || has_overlapping_symbol(fn, arg, fn_strict)) {
640
641
  arg = null;
@@ -874,7 +875,7 @@ export function tighten_body(statements, compressor) {
874
875
  found = true;
875
876
  if (node instanceof AST_VarDef) {
876
877
  node.value = node.name instanceof AST_SymbolConst
877
- ? make_node(AST_Undefined, node.value) // `const` always needs value.
878
+ ? make_void_0(node.value) // `const` always needs value.
878
879
  : null;
879
880
  return node;
880
881
  }
@@ -1241,7 +1242,7 @@ export function tighten_body(statements, compressor) {
1241
1242
  CHANGED = n != len;
1242
1243
  if (has_quit)
1243
1244
  has_quit.forEach(function (stat) {
1244
- trim_unreachable_code(compressor, stat, statements);
1245
+ extract_from_unreachable_code(compressor, stat, statements);
1245
1246
  });
1246
1247
  }
1247
1248
 
@@ -1313,7 +1314,7 @@ export function tighten_body(statements, compressor) {
1313
1314
  var stat = statements[i];
1314
1315
  if (prev) {
1315
1316
  if (stat instanceof AST_Exit) {
1316
- stat.value = cons_seq(stat.value || make_node(AST_Undefined, stat).transform(compressor));
1317
+ stat.value = cons_seq(stat.value || make_void_0(stat).transform(compressor));
1317
1318
  } else if (stat instanceof AST_For) {
1318
1319
  if (!(stat.init instanceof AST_DefinitionsLike)) {
1319
1320
  const abort = walk(prev.body, node => {
@@ -43,7 +43,7 @@
43
43
 
44
44
  "use strict";
45
45
 
46
- import { AST_Node } from "../ast.js";
46
+ import { AST_Node, AST_Number, AST_UnaryPrefix } from "../ast.js";
47
47
 
48
48
  function characters(str) {
49
49
  return str.split("");
@@ -130,6 +130,15 @@ function make_node(ctor, orig, props) {
130
130
  return new ctor(props);
131
131
  }
132
132
 
133
+ /** Makes a `void 0` expression. Use instead of AST_Undefined which may conflict
134
+ * with an existing variable called `undefined` */
135
+ function make_void_0(orig) {
136
+ return make_node(AST_UnaryPrefix, orig, {
137
+ operator: "void",
138
+ expression: make_node(AST_Number, orig, { value: 0 })
139
+ });
140
+ }
141
+
133
142
  function push_uniq(array, el) {
134
143
  if (!array.includes(el))
135
144
  array.push(el);
@@ -272,6 +281,7 @@ export {
272
281
  HOP,
273
282
  keep_name,
274
283
  make_node,
284
+ make_void_0,
275
285
  makePredicate,
276
286
  map_add,
277
287
  map_from_object,
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "homepage": "https://terser.org",
5
5
  "author": "Mihai Bazon <mihai.bazon@gmail.com> (http://lisperator.net/)",
6
6
  "license": "BSD-2-Clause",
7
- "version": "5.44.0",
7
+ "version": "5.45.0",
8
8
  "engines": {
9
9
  "node": ">=10"
10
10
  },
@@ -61,6 +61,10 @@
61
61
  "semver": "^7.5.1",
62
62
  "source-map": "~0.8.0-beta.0"
63
63
  },
64
+ "overrides": {
65
+ "serialize-javascript": "6.0.2",
66
+ "js-yaml": "4.1.1"
67
+ },
64
68
  "scripts": {
65
69
  "test": "node test/compress.js && mocha test/mocha",
66
70
  "test:compress": "node test/compress.js",