terser 5.8.0 → 5.9.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.9.0
4
+
5
+ - Collapsing switch cases with the same bodies (even if they're not next to each other) (#1070).
6
+ - Fix evaluation of optional chain expressions (#1062)
7
+ - Fix mangling collision in ESM exports (#1063)
8
+ - Fix issue with mutating function objects after a second pass (#1047)
9
+ - Fix for inlining object spread `{ ...obj }` (#1071)
10
+ - Typescript typings fix (#1069)
11
+
3
12
  ## v5.8.0
4
13
 
5
14
  - Fixed shadowing variables while moving code in some cases (#1065)
@@ -11277,9 +11277,8 @@ function is_undefined(node, compressor) {
11277
11277
  );
11278
11278
  }
11279
11279
 
11280
- // Find out if something is == null
11281
- // Used to optimize ?. and ??
11282
- function is_nullish(node, compressor) {
11280
+ // Is the node explicitly null or undefined.
11281
+ function is_null_or_undefined(node, compressor) {
11283
11282
  let fixed;
11284
11283
  return (
11285
11284
  node instanceof AST_Null
@@ -11289,13 +11288,29 @@ function is_nullish(node, compressor) {
11289
11288
  && (fixed = node.definition().fixed) instanceof AST_Node
11290
11289
  && is_nullish(fixed, compressor)
11291
11290
  )
11292
- // Recurse into those optional chains!
11293
- || node instanceof AST_PropAccess && node.optional && is_nullish(node.expression, compressor)
11294
- || node instanceof AST_Call && node.optional && is_nullish(node.expression, compressor)
11295
- || node instanceof AST_Chain && is_nullish(node.expression, compressor)
11296
11291
  );
11297
11292
  }
11298
11293
 
11294
+ // Find out if this expression is optionally chained from a base-point that we
11295
+ // can statically analyze as null or undefined.
11296
+ function is_nullish_shortcircuited(node, compressor) {
11297
+ if (node instanceof AST_PropAccess || node instanceof AST_Call) {
11298
+ return (
11299
+ (node.optional && is_null_or_undefined(node.expression, compressor))
11300
+ || is_nullish_shortcircuited(node.expression, compressor)
11301
+ );
11302
+ }
11303
+ if (node instanceof AST_Chain) return is_nullish_shortcircuited(node.expression, compressor);
11304
+ return false;
11305
+ }
11306
+
11307
+ // Find out if something is == null, or can short circuit into nullish.
11308
+ // Used to optimize ?. and ??
11309
+ function is_nullish(node, compressor) {
11310
+ if (is_null_or_undefined(node, compressor)) return true;
11311
+ return is_nullish_shortcircuited(node, compressor);
11312
+ }
11313
+
11299
11314
  // Determine if expression might cause side effects
11300
11315
  // If there's a possibility that a node may change something when it's executed, this returns true
11301
11316
  (function(def_has_side_effects) {
@@ -11403,13 +11418,12 @@ function is_nullish(node, compressor) {
11403
11418
  return any(this.elements, compressor);
11404
11419
  });
11405
11420
  def_has_side_effects(AST_Dot, function(compressor) {
11421
+ if (is_nullish(this, compressor)) return false;
11406
11422
  return !this.optional && this.expression.may_throw_on_access(compressor)
11407
11423
  || this.expression.has_side_effects(compressor);
11408
11424
  });
11409
11425
  def_has_side_effects(AST_Sub, function(compressor) {
11410
- if (this.optional && is_nullish(this.expression, compressor)) {
11411
- return false;
11412
- }
11426
+ if (is_nullish(this, compressor)) return false;
11413
11427
 
11414
11428
  return !this.optional && this.expression.may_throw_on_access(compressor)
11415
11429
  || this.expression.has_side_effects(compressor)
@@ -11477,7 +11491,7 @@ function is_nullish(node, compressor) {
11477
11491
  return any(this.body, compressor);
11478
11492
  });
11479
11493
  def_may_throw(AST_Call, function(compressor) {
11480
- if (this.optional && is_nullish(this.expression, compressor)) return false;
11494
+ if (is_nullish(this, compressor)) return false;
11481
11495
  if (any(this.args, compressor)) return true;
11482
11496
  if (this.is_callee_pure(compressor)) return false;
11483
11497
  if (this.expression.may_throw(compressor)) return true;
@@ -11536,12 +11550,12 @@ function is_nullish(node, compressor) {
11536
11550
  return this.body.may_throw(compressor);
11537
11551
  });
11538
11552
  def_may_throw(AST_Dot, function(compressor) {
11553
+ if (is_nullish(this, compressor)) return false;
11539
11554
  return !this.optional && this.expression.may_throw_on_access(compressor)
11540
11555
  || this.expression.may_throw(compressor);
11541
11556
  });
11542
11557
  def_may_throw(AST_Sub, function(compressor) {
11543
- if (this.optional && is_nullish(this.expression, compressor)) return false;
11544
-
11558
+ if (is_nullish(this, compressor)) return false;
11545
11559
  return !this.optional && this.expression.may_throw_on_access(compressor)
11546
11560
  || this.expression.may_throw(compressor)
11547
11561
  || this.property.may_throw(compressor);
@@ -12033,6 +12047,9 @@ function def_eval(node, func) {
12033
12047
  node.DEFMETHOD("_eval", func);
12034
12048
  }
12035
12049
 
12050
+ // Used to propagate a nullish short-circuit signal upwards through the chain.
12051
+ const nullish = Symbol("This AST_Chain is nullish");
12052
+
12036
12053
  // If the node has been successfully reduced to a constant,
12037
12054
  // then its value is returned; otherwise the element itself
12038
12055
  // is returned.
@@ -12044,7 +12061,7 @@ AST_Node.DEFMETHOD("evaluate", function (compressor) {
12044
12061
  var val = this._eval(compressor, 1);
12045
12062
  if (!val || val instanceof RegExp)
12046
12063
  return val;
12047
- if (typeof val == "function" || typeof val == "object")
12064
+ if (typeof val == "function" || typeof val == "object" || val == nullish)
12048
12065
  return this;
12049
12066
  return val;
12050
12067
  });
@@ -12289,11 +12306,8 @@ const regexp_flags = new Set([
12289
12306
  ]);
12290
12307
 
12291
12308
  def_eval(AST_PropAccess, function (compressor, depth) {
12292
- if (this.optional) {
12293
- const obj = this.expression._eval(compressor, depth);
12294
- if (obj == null)
12295
- return undefined;
12296
- }
12309
+ const obj = this.expression._eval(compressor, depth);
12310
+ if (obj === nullish || (this.optional && obj == null)) return nullish;
12297
12311
  if (compressor.option("unsafe")) {
12298
12312
  var key = this.property;
12299
12313
  if (key instanceof AST_Node) {
@@ -12348,16 +12362,19 @@ def_eval(AST_PropAccess, function (compressor, depth) {
12348
12362
 
12349
12363
  def_eval(AST_Chain, function (compressor, depth) {
12350
12364
  const evaluated = this.expression._eval(compressor, depth);
12351
- return evaluated === this.expression ? this : evaluated;
12365
+ return evaluated === nullish
12366
+ ? undefined
12367
+ : evaluated === this.expression
12368
+ ? this
12369
+ : evaluated;
12352
12370
  });
12353
12371
 
12354
12372
  def_eval(AST_Call, function (compressor, depth) {
12355
12373
  var exp = this.expression;
12356
- if (this.optional) {
12357
- const callee = this.expression._eval(compressor, depth);
12358
- if (callee == null)
12359
- return undefined;
12360
- }
12374
+
12375
+ const callee = exp._eval(compressor, depth);
12376
+ if (callee === nullish || (this.optional && callee == null)) return nullish;
12377
+
12361
12378
  if (compressor.option("unsafe") && exp instanceof AST_PropAccess) {
12362
12379
  var key = exp.property;
12363
12380
  if (key instanceof AST_Node) {
@@ -12489,8 +12506,8 @@ def_drop_side_effect_free(AST_Constant, return_null);
12489
12506
  def_drop_side_effect_free(AST_This, return_null);
12490
12507
 
12491
12508
  def_drop_side_effect_free(AST_Call, function (compressor, first_in_statement) {
12492
- if (this.optional && is_nullish(this.expression, compressor)) {
12493
- return make_node(AST_Undefined, this);
12509
+ if (is_nullish_shortcircuited(this, compressor)) {
12510
+ return this.expression.drop_side_effect_free(compressor, first_in_statement);
12494
12511
  }
12495
12512
 
12496
12513
  if (!this.is_callee_pure(compressor)) {
@@ -12666,21 +12683,19 @@ def_drop_side_effect_free(AST_Array, function (compressor, first_in_statement) {
12666
12683
  });
12667
12684
 
12668
12685
  def_drop_side_effect_free(AST_Dot, function (compressor, first_in_statement) {
12669
- if (this.optional) {
12670
- return is_nullish(this.expression, compressor) ? make_node(AST_Undefined, this) : this;
12686
+ if (is_nullish_shortcircuited(this, compressor)) {
12687
+ return this.expression.drop_side_effect_free(compressor, first_in_statement);
12671
12688
  }
12672
- if (this.expression.may_throw_on_access(compressor))
12673
- return this;
12689
+ if (this.expression.may_throw_on_access(compressor)) return this;
12674
12690
 
12675
12691
  return this.expression.drop_side_effect_free(compressor, first_in_statement);
12676
12692
  });
12677
12693
 
12678
12694
  def_drop_side_effect_free(AST_Sub, function (compressor, first_in_statement) {
12679
- if (this.optional) {
12680
- return is_nullish(this.expression, compressor) ? make_node(AST_Undefined, this) : this;
12695
+ if (is_nullish_shortcircuited(this, compressor)) {
12696
+ return this.expression.drop_side_effect_free(compressor, first_in_statement);
12681
12697
  }
12682
- if (this.expression.may_throw_on_access(compressor))
12683
- return this;
12698
+ if (this.expression.may_throw_on_access(compressor)) return this;
12684
12699
 
12685
12700
  var expression = this.expression.drop_side_effect_free(compressor, first_in_statement);
12686
12701
  if (!expression)
@@ -16108,6 +16123,49 @@ def_optimize(AST_Switch, function(self, compressor) {
16108
16123
  while (i < len) eliminate_branch(self.body[i++], body[body.length - 1]);
16109
16124
  self.body = body;
16110
16125
 
16126
+ let default_or_exact = default_branch || exact_match;
16127
+ default_branch = null;
16128
+ exact_match = null;
16129
+
16130
+ // group equivalent branches so they will be located next to each other,
16131
+ // that way the next micro-optimization will merge them.
16132
+ // ** bail micro-optimization if not a simple switch case with breaks
16133
+ if (body.every((branch, i) =>
16134
+ (branch === default_or_exact || !branch.expression.has_side_effects(compressor))
16135
+ && (branch.body.length === 0 || aborts(branch) || body.length - 1 === i))
16136
+ ) {
16137
+ for (let i = 0; i < body.length; i++) {
16138
+ const branch = body[i];
16139
+ for (let j = i + 1; j < body.length; j++) {
16140
+ const next = body[j];
16141
+ if (next.body.length === 0) continue;
16142
+ const last_branch = j === (body.length - 1);
16143
+ const equivalentBranch = branches_equivalent(next, branch, false);
16144
+ if (equivalentBranch || (last_branch && branches_equivalent(next, branch, true))) {
16145
+ if (!equivalentBranch && last_branch) {
16146
+ next.body.push(make_node(AST_Break));
16147
+ }
16148
+
16149
+ // let's find previous siblings with inert fallthrough...
16150
+ let x = j - 1;
16151
+ let fallthroughDepth = 0;
16152
+ while (x > i) {
16153
+ if (is_inert_body(body[x--])) {
16154
+ fallthroughDepth++;
16155
+ } else {
16156
+ break;
16157
+ }
16158
+ }
16159
+
16160
+ const plucked = body.splice(j - fallthroughDepth, 1 + fallthroughDepth);
16161
+ body.splice(i + 1, 0, ...plucked);
16162
+ i += plucked.length;
16163
+ }
16164
+ }
16165
+ }
16166
+ }
16167
+
16168
+ // merge equivalent branches in a row
16111
16169
  for (let i = 0; i < body.length; i++) {
16112
16170
  let branch = body[i];
16113
16171
  if (branch.body.length === 0) continue;
@@ -16128,10 +16186,6 @@ def_optimize(AST_Switch, function(self, compressor) {
16128
16186
  }
16129
16187
  }
16130
16188
 
16131
- let default_or_exact = default_branch || exact_match;
16132
- default_branch = null;
16133
- exact_match = null;
16134
-
16135
16189
  // Prune any empty branches at the end of the switch statement.
16136
16190
  {
16137
16191
  let i = body.length - 1;
@@ -16522,10 +16576,6 @@ def_optimize(AST_Call, function(self, compressor) {
16522
16576
  }
16523
16577
  }
16524
16578
 
16525
- if (self.optional && is_nullish(fn, compressor)) {
16526
- return make_node(AST_Undefined, self);
16527
- }
16528
-
16529
16579
  var is_func = fn instanceof AST_Lambda;
16530
16580
 
16531
16581
  if (is_func && fn.pinned()) return self;
@@ -16922,6 +16972,7 @@ def_optimize(AST_Call, function(self, compressor) {
16922
16972
  if (can_inline && has_annotation(self, _INLINE)) {
16923
16973
  set_flag(fn, SQUEEZED);
16924
16974
  fn = make_node(fn.CTOR === AST_Defun ? AST_Function : fn.CTOR, fn, fn);
16975
+ fn = fn.clone(true);
16925
16976
  fn.figure_out_scope({}, {
16926
16977
  parent_scope: find_scope(compressor),
16927
16978
  toplevel: compressor.get_toplevel()
@@ -18709,14 +18760,11 @@ def_optimize(AST_Sub, function(self, compressor) {
18709
18760
  ev = make_node_from_constant(ev, self).optimize(compressor);
18710
18761
  return best_of(compressor, ev, self);
18711
18762
  }
18712
- if (self.optional && is_nullish(self.expression, compressor)) {
18713
- return make_node(AST_Undefined, self);
18714
- }
18715
18763
  return self;
18716
18764
  });
18717
18765
 
18718
18766
  def_optimize(AST_Chain, function (self, compressor) {
18719
- self.expression = self.expression.optimize(compressor);
18767
+ if (is_nullish(self.expression, compressor)) return make_node(AST_Undefined, self);
18720
18768
  return self;
18721
18769
  });
18722
18770
 
@@ -18783,9 +18831,6 @@ def_optimize(AST_Dot, function(self, compressor) {
18783
18831
  ev = make_node_from_constant(ev, self).optimize(compressor);
18784
18832
  return best_of(compressor, ev, self);
18785
18833
  }
18786
- if (self.optional && is_nullish(self.expression, compressor)) {
18787
- return make_node(AST_Undefined, self);
18788
- }
18789
18834
  return self;
18790
18835
  });
18791
18836
 
@@ -18845,9 +18890,11 @@ function inline_object_prop_spread(props, compressor) {
18845
18890
  // non-iterable value silently does nothing; it is thus safe
18846
18891
  // to remove. AST_String is the only iterable AST_Constant.
18847
18892
  props.splice(i, 1);
18893
+ i--;
18848
18894
  } else if (is_nullish(expr, compressor)) {
18849
18895
  // Likewise, null and undefined can be silently removed.
18850
18896
  props.splice(i, 1);
18897
+ i--;
18851
18898
  }
18852
18899
  }
18853
18900
  }
@@ -27166,9 +27213,6 @@ function mangle_properties(ast, options) {
27166
27213
  var cache;
27167
27214
  if (options.cache) {
27168
27215
  cache = options.cache.props;
27169
- cache.forEach(function(mangled_name) {
27170
- reserved.add(mangled_name);
27171
- });
27172
27216
  } else {
27173
27217
  cache = new Map();
27174
27218
  }
@@ -27186,6 +27230,9 @@ function mangle_properties(ast, options) {
27186
27230
 
27187
27231
  var names_to_mangle = new Set();
27188
27232
  var unmangleable = new Set();
27233
+ // Track each already-mangled name to prevent nth_identifier from generating
27234
+ // the same name.
27235
+ cache.forEach((mangled_name) => unmangleable.add(mangled_name));
27189
27236
 
27190
27237
  var keep_quoted = !!options.keep_quoted;
27191
27238
 
@@ -27429,6 +27476,7 @@ async function minify(files, options) {
27429
27476
  keep_classnames: false,
27430
27477
  keep_fnames: false,
27431
27478
  module: false,
27479
+ nth_identifier: base54,
27432
27480
  properties: false,
27433
27481
  reserved: [],
27434
27482
  safari10: false,
@@ -73,13 +73,12 @@ import {
73
73
  AST_TemplateString,
74
74
  AST_This,
75
75
  AST_Unary,
76
- AST_Undefined,
77
76
  } from "../ast.js";
78
77
  import { make_node, return_null, return_this } from "../utils/index.js";
79
78
  import { first_in_statement } from "../utils/first_in_statement.js";
80
79
 
81
80
  import { pure_prop_access_globals } from "./native-objects.js";
82
- import { lazy_op, unary_side_effects, is_nullish } from "./inference.js";
81
+ import { lazy_op, unary_side_effects, is_nullish_shortcircuited } from "./inference.js";
83
82
  import { WRITE_ONLY, set_flag, clear_flag } from "./compressor-flags.js";
84
83
  import { make_sequence, is_func_expr, is_iife_call } from "./common.js";
85
84
 
@@ -121,8 +120,8 @@ def_drop_side_effect_free(AST_Constant, return_null);
121
120
  def_drop_side_effect_free(AST_This, return_null);
122
121
 
123
122
  def_drop_side_effect_free(AST_Call, function (compressor, first_in_statement) {
124
- if (this.optional && is_nullish(this.expression, compressor)) {
125
- return make_node(AST_Undefined, this);
123
+ if (is_nullish_shortcircuited(this, compressor)) {
124
+ return this.expression.drop_side_effect_free(compressor, first_in_statement);
126
125
  }
127
126
 
128
127
  if (!this.is_callee_pure(compressor)) {
@@ -298,21 +297,19 @@ def_drop_side_effect_free(AST_Array, function (compressor, first_in_statement) {
298
297
  });
299
298
 
300
299
  def_drop_side_effect_free(AST_Dot, function (compressor, first_in_statement) {
301
- if (this.optional) {
302
- return is_nullish(this.expression, compressor) ? make_node(AST_Undefined, this) : this;
300
+ if (is_nullish_shortcircuited(this, compressor)) {
301
+ return this.expression.drop_side_effect_free(compressor, first_in_statement);
303
302
  }
304
- if (this.expression.may_throw_on_access(compressor))
305
- return this;
303
+ if (this.expression.may_throw_on_access(compressor)) return this;
306
304
 
307
305
  return this.expression.drop_side_effect_free(compressor, first_in_statement);
308
306
  });
309
307
 
310
308
  def_drop_side_effect_free(AST_Sub, function (compressor, first_in_statement) {
311
- if (this.optional) {
312
- return is_nullish(this.expression, compressor) ? make_node(AST_Undefined, this) : this;
309
+ if (is_nullish_shortcircuited(this, compressor)) {
310
+ return this.expression.drop_side_effect_free(compressor, first_in_statement);
313
311
  }
314
- if (this.expression.may_throw_on_access(compressor))
315
- return this;
312
+ if (this.expression.may_throw_on_access(compressor)) return this;
316
313
 
317
314
  var expression = this.expression.drop_side_effect_free(compressor, first_in_statement);
318
315
  if (!expression)
@@ -82,6 +82,9 @@ function def_eval(node, func) {
82
82
  node.DEFMETHOD("_eval", func);
83
83
  }
84
84
 
85
+ // Used to propagate a nullish short-circuit signal upwards through the chain.
86
+ export const nullish = Symbol("This AST_Chain is nullish");
87
+
85
88
  // If the node has been successfully reduced to a constant,
86
89
  // then its value is returned; otherwise the element itself
87
90
  // is returned.
@@ -93,7 +96,7 @@ AST_Node.DEFMETHOD("evaluate", function (compressor) {
93
96
  var val = this._eval(compressor, 1);
94
97
  if (!val || val instanceof RegExp)
95
98
  return val;
96
- if (typeof val == "function" || typeof val == "object")
99
+ if (typeof val == "function" || typeof val == "object" || val == nullish)
97
100
  return this;
98
101
  return val;
99
102
  });
@@ -338,11 +341,8 @@ const regexp_flags = new Set([
338
341
  ]);
339
342
 
340
343
  def_eval(AST_PropAccess, function (compressor, depth) {
341
- if (this.optional) {
342
- const obj = this.expression._eval(compressor, depth);
343
- if (obj == null)
344
- return undefined;
345
- }
344
+ const obj = this.expression._eval(compressor, depth);
345
+ if (obj === nullish || (this.optional && obj == null)) return nullish;
346
346
  if (compressor.option("unsafe")) {
347
347
  var key = this.property;
348
348
  if (key instanceof AST_Node) {
@@ -397,16 +397,19 @@ def_eval(AST_PropAccess, function (compressor, depth) {
397
397
 
398
398
  def_eval(AST_Chain, function (compressor, depth) {
399
399
  const evaluated = this.expression._eval(compressor, depth);
400
- return evaluated === this.expression ? this : evaluated;
400
+ return evaluated === nullish
401
+ ? undefined
402
+ : evaluated === this.expression
403
+ ? this
404
+ : evaluated;
401
405
  });
402
406
 
403
407
  def_eval(AST_Call, function (compressor, depth) {
404
408
  var exp = this.expression;
405
- if (this.optional) {
406
- const callee = this.expression._eval(compressor, depth);
407
- if (callee == null)
408
- return undefined;
409
- }
409
+
410
+ const callee = exp._eval(compressor, depth);
411
+ if (callee === nullish || (this.optional && callee == null)) return nullish;
412
+
410
413
  if (compressor.option("unsafe") && exp instanceof AST_PropAccess) {
411
414
  var key = exp.property;
412
415
  if (key instanceof AST_Node) {
@@ -1598,6 +1598,49 @@ def_optimize(AST_Switch, function(self, compressor) {
1598
1598
  while (i < len) eliminate_branch(self.body[i++], body[body.length - 1]);
1599
1599
  self.body = body;
1600
1600
 
1601
+ let default_or_exact = default_branch || exact_match;
1602
+ default_branch = null;
1603
+ exact_match = null;
1604
+
1605
+ // group equivalent branches so they will be located next to each other,
1606
+ // that way the next micro-optimization will merge them.
1607
+ // ** bail micro-optimization if not a simple switch case with breaks
1608
+ if (body.every((branch, i) =>
1609
+ (branch === default_or_exact || !branch.expression.has_side_effects(compressor))
1610
+ && (branch.body.length === 0 || aborts(branch) || body.length - 1 === i))
1611
+ ) {
1612
+ for (let i = 0; i < body.length; i++) {
1613
+ const branch = body[i];
1614
+ for (let j = i + 1; j < body.length; j++) {
1615
+ const next = body[j];
1616
+ if (next.body.length === 0) continue;
1617
+ const last_branch = j === (body.length - 1);
1618
+ const equivalentBranch = branches_equivalent(next, branch, false);
1619
+ if (equivalentBranch || (last_branch && branches_equivalent(next, branch, true))) {
1620
+ if (!equivalentBranch && last_branch) {
1621
+ next.body.push(make_node(AST_Break));
1622
+ }
1623
+
1624
+ // let's find previous siblings with inert fallthrough...
1625
+ let x = j - 1;
1626
+ let fallthroughDepth = 0;
1627
+ while (x > i) {
1628
+ if (is_inert_body(body[x--])) {
1629
+ fallthroughDepth++;
1630
+ } else {
1631
+ break;
1632
+ }
1633
+ }
1634
+
1635
+ const plucked = body.splice(j - fallthroughDepth, 1 + fallthroughDepth);
1636
+ body.splice(i + 1, 0, ...plucked);
1637
+ i += plucked.length;
1638
+ }
1639
+ }
1640
+ }
1641
+ }
1642
+
1643
+ // merge equivalent branches in a row
1601
1644
  for (let i = 0; i < body.length; i++) {
1602
1645
  let branch = body[i];
1603
1646
  if (branch.body.length === 0) continue;
@@ -1618,10 +1661,6 @@ def_optimize(AST_Switch, function(self, compressor) {
1618
1661
  }
1619
1662
  }
1620
1663
 
1621
- let default_or_exact = default_branch || exact_match;
1622
- default_branch = null;
1623
- exact_match = null;
1624
-
1625
1664
  // Prune any empty branches at the end of the switch statement.
1626
1665
  {
1627
1666
  let i = body.length - 1;
@@ -2012,10 +2051,6 @@ def_optimize(AST_Call, function(self, compressor) {
2012
2051
  }
2013
2052
  }
2014
2053
 
2015
- if (self.optional && is_nullish(fn, compressor)) {
2016
- return make_node(AST_Undefined, self);
2017
- }
2018
-
2019
2054
  var is_func = fn instanceof AST_Lambda;
2020
2055
 
2021
2056
  if (is_func && fn.pinned()) return self;
@@ -2412,6 +2447,7 @@ def_optimize(AST_Call, function(self, compressor) {
2412
2447
  if (can_inline && has_annotation(self, _INLINE)) {
2413
2448
  set_flag(fn, SQUEEZED);
2414
2449
  fn = make_node(fn.CTOR === AST_Defun ? AST_Function : fn.CTOR, fn, fn);
2450
+ fn = fn.clone(true);
2415
2451
  fn.figure_out_scope({}, {
2416
2452
  parent_scope: find_scope(compressor),
2417
2453
  toplevel: compressor.get_toplevel()
@@ -4199,14 +4235,11 @@ def_optimize(AST_Sub, function(self, compressor) {
4199
4235
  ev = make_node_from_constant(ev, self).optimize(compressor);
4200
4236
  return best_of(compressor, ev, self);
4201
4237
  }
4202
- if (self.optional && is_nullish(self.expression, compressor)) {
4203
- return make_node(AST_Undefined, self);
4204
- }
4205
4238
  return self;
4206
4239
  });
4207
4240
 
4208
4241
  def_optimize(AST_Chain, function (self, compressor) {
4209
- self.expression = self.expression.optimize(compressor);
4242
+ if (is_nullish(self.expression, compressor)) return make_node(AST_Undefined, self);
4210
4243
  return self;
4211
4244
  });
4212
4245
 
@@ -4273,9 +4306,6 @@ def_optimize(AST_Dot, function(self, compressor) {
4273
4306
  ev = make_node_from_constant(ev, self).optimize(compressor);
4274
4307
  return best_of(compressor, ev, self);
4275
4308
  }
4276
- if (self.optional && is_nullish(self.expression, compressor)) {
4277
- return make_node(AST_Undefined, self);
4278
- }
4279
4309
  return self;
4280
4310
  });
4281
4311
 
@@ -4335,9 +4365,11 @@ function inline_object_prop_spread(props, compressor) {
4335
4365
  // non-iterable value silently does nothing; it is thus safe
4336
4366
  // to remove. AST_String is the only iterable AST_Constant.
4337
4367
  props.splice(i, 1);
4368
+ i--;
4338
4369
  } else if (is_nullish(expr, compressor)) {
4339
4370
  // Likewise, null and undefined can be silently removed.
4340
4371
  props.splice(i, 1);
4372
+ i--;
4341
4373
  }
4342
4374
  }
4343
4375
  }
@@ -226,9 +226,8 @@ export function is_undefined(node, compressor) {
226
226
  );
227
227
  }
228
228
 
229
- // Find out if something is == null
230
- // Used to optimize ?. and ??
231
- export function is_nullish(node, compressor) {
229
+ // Is the node explicitly null or undefined.
230
+ function is_null_or_undefined(node, compressor) {
232
231
  let fixed;
233
232
  return (
234
233
  node instanceof AST_Null
@@ -238,13 +237,29 @@ export function is_nullish(node, compressor) {
238
237
  && (fixed = node.definition().fixed) instanceof AST_Node
239
238
  && is_nullish(fixed, compressor)
240
239
  )
241
- // Recurse into those optional chains!
242
- || node instanceof AST_PropAccess && node.optional && is_nullish(node.expression, compressor)
243
- || node instanceof AST_Call && node.optional && is_nullish(node.expression, compressor)
244
- || node instanceof AST_Chain && is_nullish(node.expression, compressor)
245
240
  );
246
241
  }
247
242
 
243
+ // Find out if this expression is optionally chained from a base-point that we
244
+ // can statically analyze as null or undefined.
245
+ export function is_nullish_shortcircuited(node, compressor) {
246
+ if (node instanceof AST_PropAccess || node instanceof AST_Call) {
247
+ return (
248
+ (node.optional && is_null_or_undefined(node.expression, compressor))
249
+ || is_nullish_shortcircuited(node.expression, compressor)
250
+ );
251
+ }
252
+ if (node instanceof AST_Chain) return is_nullish_shortcircuited(node.expression, compressor);
253
+ return false;
254
+ }
255
+
256
+ // Find out if something is == null, or can short circuit into nullish.
257
+ // Used to optimize ?. and ??
258
+ export function is_nullish(node, compressor) {
259
+ if (is_null_or_undefined(node, compressor)) return true;
260
+ return is_nullish_shortcircuited(node, compressor);
261
+ }
262
+
248
263
  // Determine if expression might cause side effects
249
264
  // If there's a possibility that a node may change something when it's executed, this returns true
250
265
  (function(def_has_side_effects) {
@@ -352,13 +367,12 @@ export function is_nullish(node, compressor) {
352
367
  return any(this.elements, compressor);
353
368
  });
354
369
  def_has_side_effects(AST_Dot, function(compressor) {
370
+ if (is_nullish(this, compressor)) return false;
355
371
  return !this.optional && this.expression.may_throw_on_access(compressor)
356
372
  || this.expression.has_side_effects(compressor);
357
373
  });
358
374
  def_has_side_effects(AST_Sub, function(compressor) {
359
- if (this.optional && is_nullish(this.expression, compressor)) {
360
- return false;
361
- }
375
+ if (is_nullish(this, compressor)) return false;
362
376
 
363
377
  return !this.optional && this.expression.may_throw_on_access(compressor)
364
378
  || this.expression.has_side_effects(compressor)
@@ -426,7 +440,7 @@ export function is_nullish(node, compressor) {
426
440
  return any(this.body, compressor);
427
441
  });
428
442
  def_may_throw(AST_Call, function(compressor) {
429
- if (this.optional && is_nullish(this.expression, compressor)) return false;
443
+ if (is_nullish(this, compressor)) return false;
430
444
  if (any(this.args, compressor)) return true;
431
445
  if (this.is_callee_pure(compressor)) return false;
432
446
  if (this.expression.may_throw(compressor)) return true;
@@ -485,12 +499,12 @@ export function is_nullish(node, compressor) {
485
499
  return this.body.may_throw(compressor);
486
500
  });
487
501
  def_may_throw(AST_Dot, function(compressor) {
502
+ if (is_nullish(this, compressor)) return false;
488
503
  return !this.optional && this.expression.may_throw_on_access(compressor)
489
504
  || this.expression.may_throw(compressor);
490
505
  });
491
506
  def_may_throw(AST_Sub, function(compressor) {
492
- if (this.optional && is_nullish(this.expression, compressor)) return false;
493
-
507
+ if (is_nullish(this, compressor)) return false;
494
508
  return !this.optional && this.expression.may_throw_on_access(compressor)
495
509
  || this.expression.may_throw(compressor)
496
510
  || this.property.may_throw(compressor);
package/lib/minify.js CHANGED
@@ -11,6 +11,7 @@ import { AST_Toplevel, AST_Node } from "./ast.js";
11
11
  import { parse } from "./parse.js";
12
12
  import { OutputStream } from "./output.js";
13
13
  import { Compressor } from "./compress/index.js";
14
+ import { base54 } from "./scope.js";
14
15
  import { SourceMap } from "./sourcemap.js";
15
16
  import {
16
17
  mangle_properties,
@@ -113,6 +114,7 @@ async function minify(files, options) {
113
114
  keep_classnames: false,
114
115
  keep_fnames: false,
115
116
  module: false,
117
+ nth_identifier: base54,
116
118
  properties: false,
117
119
  reserved: [],
118
120
  safari10: false,
package/lib/propmangle.js CHANGED
@@ -197,9 +197,6 @@ function mangle_properties(ast, options) {
197
197
  var cache;
198
198
  if (options.cache) {
199
199
  cache = options.cache.props;
200
- cache.forEach(function(mangled_name) {
201
- reserved.add(mangled_name);
202
- });
203
200
  } else {
204
201
  cache = new Map();
205
202
  }
@@ -217,6 +214,9 @@ function mangle_properties(ast, options) {
217
214
 
218
215
  var names_to_mangle = new Set();
219
216
  var unmangleable = new Set();
217
+ // Track each already-mangled name to prevent nth_identifier from generating
218
+ // the same name.
219
+ cache.forEach((mangled_name) => unmangleable.add(mangled_name));
220
220
 
221
221
  var keep_quoted = !!options.keep_quoted;
222
222
 
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.8.0",
7
+ "version": "5.9.0",
8
8
  "engines": {
9
9
  "node": ">=10"
10
10
  },
package/bin/terser.mjs DELETED
@@ -1,21 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- "use strict";
4
-
5
- import "../tools/exit.cjs";
6
-
7
- import fs from "fs"
8
- import path from "path"
9
- import program from "commander"
10
-
11
- import { run_cli } from "../lib/cli.js"
12
-
13
- const packageJson = {
14
- name: "terser",
15
- version: "experimental module CLI"
16
- }
17
-
18
- run_cli({ program, packageJson, fs, path }).catch((error) => {
19
- console.error(error);
20
- process.exitCode = 1;
21
- });