terser 5.7.2 → 5.11.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.
@@ -53,7 +53,6 @@ import {
53
53
  AST_Boolean,
54
54
  AST_Break,
55
55
  AST_Call,
56
- AST_Case,
57
56
  AST_Catch,
58
57
  AST_Chain,
59
58
  AST_Class,
@@ -109,6 +108,7 @@ import {
109
108
  AST_String,
110
109
  AST_Sub,
111
110
  AST_Switch,
111
+ AST_SwitchBranch,
112
112
  AST_Symbol,
113
113
  AST_SymbolBlockDeclaration,
114
114
  AST_SymbolCatch,
@@ -390,7 +390,8 @@ class Compressor extends TreeWalker {
390
390
  var passes = +this.options.passes || 1;
391
391
  var min_count = 1 / 0;
392
392
  var stopping = false;
393
- var mangle = { ie8: this.option("ie8") };
393
+ var nth_identifier = this.mangle_options && this.mangle_options.nth_identifier || base54;
394
+ var mangle = { ie8: this.option("ie8"), nth_identifier: nth_identifier };
394
395
  for (var pass = 0; pass < passes; pass++) {
395
396
  this._toplevel.figure_out_scope(mangle);
396
397
  if (pass === 0 && this.option("drop_console")) {
@@ -1592,58 +1593,294 @@ def_optimize(AST_Switch, function(self, compressor) {
1592
1593
  }
1593
1594
  }
1594
1595
  }
1595
- if (aborts(branch)) {
1596
- var prev = body[body.length - 1];
1597
- if (aborts(prev) && prev.body.length == branch.body.length
1598
- && make_node(AST_BlockStatement, prev, prev).equivalent_to(make_node(AST_BlockStatement, branch, branch))) {
1599
- prev.body = [];
1600
- }
1601
- }
1602
1596
  body.push(branch);
1603
1597
  }
1604
1598
  while (i < len) eliminate_branch(self.body[i++], body[body.length - 1]);
1599
+ self.body = body;
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 instanceof AST_Constant)
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
1644
+ for (let i = 0; i < body.length; i++) {
1645
+ let branch = body[i];
1646
+ if (branch.body.length === 0) continue;
1647
+ if (!aborts(branch)) continue;
1648
+
1649
+ for (let j = i + 1; j < body.length; i++, j++) {
1650
+ let next = body[j];
1651
+ if (next.body.length === 0) continue;
1652
+ if (
1653
+ branches_equivalent(next, branch, false)
1654
+ || (j === body.length - 1 && branches_equivalent(next, branch, true))
1655
+ ) {
1656
+ branch.body = [];
1657
+ branch = next;
1658
+ continue;
1659
+ }
1660
+ break;
1661
+ }
1662
+ }
1663
+
1664
+ // Prune any empty branches at the end of the switch statement.
1665
+ {
1666
+ let i = body.length - 1;
1667
+ for (; i >= 0; i--) {
1668
+ let bbody = body[i].body;
1669
+ if (is_break(bbody[bbody.length - 1], compressor)) bbody.pop();
1670
+ if (!is_inert_body(body[i])) break;
1671
+ }
1672
+ // i now points to the index of a branch that contains a body. By incrementing, it's
1673
+ // pointing to the first branch that's empty.
1674
+ i++;
1675
+ if (!default_or_exact || body.indexOf(default_or_exact) >= i) {
1676
+ // The default behavior is to do nothing. We can take advantage of that to
1677
+ // remove all case expressions that are side-effect free that also do
1678
+ // nothing, since they'll default to doing nothing. But we can't remove any
1679
+ // case expressions before one that would side-effect, since they may cause
1680
+ // the side-effect to be skipped.
1681
+ for (let j = body.length - 1; j >= i; j--) {
1682
+ let branch = body[j];
1683
+ if (branch === default_or_exact) {
1684
+ default_or_exact = null;
1685
+ body.pop();
1686
+ } else if (!branch.expression.has_side_effects(compressor)) {
1687
+ body.pop();
1688
+ } else {
1689
+ break;
1690
+ }
1691
+ }
1692
+ }
1693
+ }
1694
+
1695
+
1696
+ // Prune side-effect free branches that fall into default.
1697
+ DEFAULT: if (default_or_exact) {
1698
+ let default_index = body.indexOf(default_or_exact);
1699
+ let default_body_index = default_index;
1700
+ for (; default_body_index < body.length - 1; default_body_index++) {
1701
+ if (!is_inert_body(body[default_body_index])) break;
1702
+ }
1703
+ if (default_body_index < body.length - 1) {
1704
+ break DEFAULT;
1705
+ }
1706
+
1707
+ let side_effect_index = body.length - 1;
1708
+ for (; side_effect_index >= 0; side_effect_index--) {
1709
+ let branch = body[side_effect_index];
1710
+ if (branch === default_or_exact) continue;
1711
+ if (branch.expression.has_side_effects(compressor)) break;
1712
+ }
1713
+ // If the default behavior comes after any side-effect case expressions,
1714
+ // then we can fold all side-effect free cases into the default branch.
1715
+ // If the side-effect case is after the default, then any side-effect
1716
+ // free cases could prevent the side-effect from occurring.
1717
+ if (default_body_index > side_effect_index) {
1718
+ let prev_body_index = default_index - 1;
1719
+ for (; prev_body_index >= 0; prev_body_index--) {
1720
+ if (!is_inert_body(body[prev_body_index])) break;
1721
+ }
1722
+ let before = Math.max(side_effect_index, prev_body_index) + 1;
1723
+ let after = default_index;
1724
+ if (side_effect_index > default_index) {
1725
+ // If the default falls into the same body as a side-effect
1726
+ // case, then we need preserve that case and only prune the
1727
+ // cases after it.
1728
+ after = side_effect_index;
1729
+ body[side_effect_index].body = body[default_body_index].body;
1730
+ } else {
1731
+ // The default will be the last branch.
1732
+ default_or_exact.body = body[default_body_index].body;
1733
+ }
1734
+
1735
+ // Prune everything after the default (or last side-effect case)
1736
+ // until the next case with a body.
1737
+ body.splice(after + 1, default_body_index - after);
1738
+ // Prune everything before the default that falls into it.
1739
+ body.splice(before, default_index - before);
1740
+ }
1741
+ }
1742
+
1743
+ // See if we can remove the switch entirely if all cases (the default) fall into the same case body.
1744
+ DEFAULT: if (default_or_exact) {
1745
+ let i = body.findIndex(branch => !is_inert_body(branch));
1746
+ let caseBody;
1747
+ // `i` is equal to one of the following:
1748
+ // - `-1`, there is no body in the switch statement.
1749
+ // - `body.length - 1`, all cases fall into the same body.
1750
+ // - anything else, there are multiple bodies in the switch.
1751
+ if (i === body.length - 1) {
1752
+ // All cases fall into the case body.
1753
+ let branch = body[i];
1754
+ if (has_nested_break(self)) break DEFAULT;
1755
+
1756
+ // This is the last case body, and we've already pruned any breaks, so it's
1757
+ // safe to hoist.
1758
+ caseBody = make_node(AST_BlockStatement, branch, {
1759
+ body: branch.body
1760
+ });
1761
+ branch.body = [];
1762
+ } else if (i !== -1) {
1763
+ // If there are multiple bodies, then we cannot optimize anything.
1764
+ break DEFAULT;
1765
+ }
1766
+
1767
+ let sideEffect = body.find(branch => {
1768
+ return (
1769
+ branch !== default_or_exact
1770
+ && branch.expression.has_side_effects(compressor)
1771
+ );
1772
+ });
1773
+ // If no cases cause a side-effect, we can eliminate the switch entirely.
1774
+ if (!sideEffect) {
1775
+ return make_node(AST_BlockStatement, self, {
1776
+ body: decl.concat(
1777
+ statement(self.expression),
1778
+ default_or_exact.expression ? statement(default_or_exact.expression) : [],
1779
+ caseBody || []
1780
+ )
1781
+ }).optimize(compressor);
1782
+ }
1783
+
1784
+ // If we're this far, either there was no body or all cases fell into the same body.
1785
+ // If there was no body, then we don't need a default branch (because the default is
1786
+ // do nothing). If there was a body, we'll extract it to after the switch, so the
1787
+ // switch's new default is to do nothing and we can still prune it.
1788
+ const default_index = body.indexOf(default_or_exact);
1789
+ body.splice(default_index, 1);
1790
+ default_or_exact = null;
1791
+
1792
+ if (caseBody) {
1793
+ // Recurse into switch statement one more time so that we can append the case body
1794
+ // outside of the switch. This recursion will only happen once since we've pruned
1795
+ // the default case.
1796
+ return make_node(AST_BlockStatement, self, {
1797
+ body: decl.concat(self, caseBody)
1798
+ }).optimize(compressor);
1799
+ }
1800
+ // If we fall here, there is a default branch somewhere, there are no case bodies,
1801
+ // and there's a side-effect somewhere. Just let the below paths take care of it.
1802
+ }
1803
+
1605
1804
  if (body.length > 0) {
1606
1805
  body[0].body = decl.concat(body[0].body);
1607
1806
  }
1608
- self.body = body;
1609
- while (branch = body[body.length - 1]) {
1610
- var stat = branch.body[branch.body.length - 1];
1611
- if (stat instanceof AST_Break && compressor.loopcontrol_target(stat) === self)
1612
- branch.body.pop();
1613
- if (branch.body.length || branch instanceof AST_Case
1614
- && (default_branch || branch.expression.has_side_effects(compressor))) break;
1615
- if (body.pop() === default_branch) default_branch = null;
1616
- }
1807
+
1617
1808
  if (body.length == 0) {
1618
1809
  return make_node(AST_BlockStatement, self, {
1619
- body: decl.concat(make_node(AST_SimpleStatement, self.expression, {
1620
- body: self.expression
1621
- }))
1810
+ body: decl.concat(statement(self.expression))
1622
1811
  }).optimize(compressor);
1623
1812
  }
1624
- if (body.length == 1 && (body[0] === exact_match || body[0] === default_branch)) {
1625
- var has_break = false;
1626
- var tw = new TreeWalker(function(node) {
1627
- if (has_break
1628
- || node instanceof AST_Lambda
1629
- || node instanceof AST_SimpleStatement) return true;
1630
- if (node instanceof AST_Break && tw.loopcontrol_target(node) === self)
1631
- has_break = true;
1632
- });
1633
- self.walk(tw);
1634
- if (!has_break) {
1635
- var statements = body[0].body.slice();
1636
- var exp = body[0].expression;
1637
- if (exp) statements.unshift(make_node(AST_SimpleStatement, exp, {
1638
- body: exp
1639
- }));
1640
- statements.unshift(make_node(AST_SimpleStatement, self.expression, {
1641
- body:self.expression
1642
- }));
1643
- return make_node(AST_BlockStatement, self, {
1644
- body: statements
1813
+ if (body.length == 1 && !has_nested_break(self)) {
1814
+ // This is the last case body, and we've already pruned any breaks, so it's
1815
+ // safe to hoist.
1816
+ let branch = body[0];
1817
+ return make_node(AST_If, self, {
1818
+ condition: make_node(AST_Binary, self, {
1819
+ operator: "===",
1820
+ left: self.expression,
1821
+ right: branch.expression,
1822
+ }),
1823
+ body: make_node(AST_BlockStatement, branch, {
1824
+ body: branch.body
1825
+ }),
1826
+ alternative: null
1827
+ }).optimize(compressor);
1828
+ }
1829
+ if (body.length === 2 && default_or_exact && !has_nested_break(self)) {
1830
+ let branch = body[0] === default_or_exact ? body[1] : body[0];
1831
+ let exact_exp = default_or_exact.expression && statement(default_or_exact.expression);
1832
+ if (aborts(body[0])) {
1833
+ // Only the first branch body could have a break (at the last statement)
1834
+ let first = body[0];
1835
+ if (is_break(first.body[first.body.length - 1], compressor)) {
1836
+ first.body.pop();
1837
+ }
1838
+ return make_node(AST_If, self, {
1839
+ condition: make_node(AST_Binary, self, {
1840
+ operator: "===",
1841
+ left: self.expression,
1842
+ right: branch.expression,
1843
+ }),
1844
+ body: make_node(AST_BlockStatement, branch, {
1845
+ body: branch.body
1846
+ }),
1847
+ alternative: make_node(AST_BlockStatement, default_or_exact, {
1848
+ body: [].concat(
1849
+ exact_exp || [],
1850
+ default_or_exact.body
1851
+ )
1852
+ })
1645
1853
  }).optimize(compressor);
1646
1854
  }
1855
+ let operator = "===";
1856
+ let consequent = make_node(AST_BlockStatement, branch, {
1857
+ body: branch.body,
1858
+ });
1859
+ let always = make_node(AST_BlockStatement, default_or_exact, {
1860
+ body: [].concat(
1861
+ exact_exp || [],
1862
+ default_or_exact.body
1863
+ )
1864
+ });
1865
+ if (body[0] === default_or_exact) {
1866
+ operator = "!==";
1867
+ let tmp = always;
1868
+ always = consequent;
1869
+ consequent = tmp;
1870
+ }
1871
+ return make_node(AST_BlockStatement, self, {
1872
+ body: [
1873
+ make_node(AST_If, self, {
1874
+ condition: make_node(AST_Binary, self, {
1875
+ operator: operator,
1876
+ left: self.expression,
1877
+ right: branch.expression,
1878
+ }),
1879
+ body: consequent,
1880
+ alternative: null
1881
+ })
1882
+ ].concat(always)
1883
+ }).optimize(compressor);
1647
1884
  }
1648
1885
  return self;
1649
1886
 
@@ -1654,6 +1891,50 @@ def_optimize(AST_Switch, function(self, compressor) {
1654
1891
  trim_unreachable_code(compressor, branch, decl);
1655
1892
  }
1656
1893
  }
1894
+ function branches_equivalent(branch, prev, insertBreak) {
1895
+ let bbody = branch.body;
1896
+ let pbody = prev.body;
1897
+ if (insertBreak) {
1898
+ bbody = bbody.concat(make_node(AST_Break));
1899
+ }
1900
+ if (bbody.length !== pbody.length) return false;
1901
+ let bblock = make_node(AST_BlockStatement, branch, { body: bbody });
1902
+ let pblock = make_node(AST_BlockStatement, prev, { body: pbody });
1903
+ return bblock.equivalent_to(pblock);
1904
+ }
1905
+ function statement(expression) {
1906
+ return make_node(AST_SimpleStatement, expression, {
1907
+ body: expression
1908
+ });
1909
+ }
1910
+ function has_nested_break(root) {
1911
+ let has_break = false;
1912
+ let tw = new TreeWalker(node => {
1913
+ if (has_break) return true;
1914
+ if (node instanceof AST_Lambda) return true;
1915
+ if (node instanceof AST_SimpleStatement) return true;
1916
+ if (!is_break(node, tw)) return;
1917
+ let parent = tw.parent();
1918
+ if (
1919
+ parent instanceof AST_SwitchBranch
1920
+ && parent.body[parent.body.length - 1] === node
1921
+ ) {
1922
+ return;
1923
+ }
1924
+ has_break = true;
1925
+ });
1926
+ root.walk(tw);
1927
+ return has_break;
1928
+ }
1929
+ function is_break(node, stack) {
1930
+ return node instanceof AST_Break
1931
+ && stack.loopcontrol_target(node) === self;
1932
+ }
1933
+ function is_inert_body(branch) {
1934
+ return !aborts(branch) && !make_node(AST_BlockStatement, branch, {
1935
+ body: branch.body
1936
+ }).has_side_effects(compressor);
1937
+ }
1657
1938
  });
1658
1939
 
1659
1940
  def_optimize(AST_Try, function(self, compressor) {
@@ -1774,10 +2055,6 @@ def_optimize(AST_Call, function(self, compressor) {
1774
2055
  }
1775
2056
  }
1776
2057
 
1777
- if (self.optional && is_nullish(fn, compressor)) {
1778
- return make_node(AST_Undefined, self);
1779
- }
1780
-
1781
2058
  var is_func = fn instanceof AST_Lambda;
1782
2059
 
1783
2060
  if (is_func && fn.pinned()) return self;
@@ -1820,6 +2097,14 @@ def_optimize(AST_Call, function(self, compressor) {
1820
2097
  }
1821
2098
 
1822
2099
  if (compressor.option("unsafe")) {
2100
+ if (exp instanceof AST_Dot && exp.start.value === "Array" && exp.property === "from" && self.args.length === 1) {
2101
+ const [argument] = self.args;
2102
+ if (argument instanceof AST_Array) {
2103
+ return make_node(AST_Array, argument, {
2104
+ elements: argument.elements
2105
+ }).optimize(compressor);
2106
+ }
2107
+ }
1823
2108
  if (is_undeclared_ref(exp)) switch (exp.name) {
1824
2109
  case "Array":
1825
2110
  if (self.args.length != 1) {
@@ -2025,6 +2310,7 @@ def_optimize(AST_Call, function(self, compressor) {
2025
2310
  argnames: [],
2026
2311
  body: []
2027
2312
  }).optimize(compressor);
2313
+ var nth_identifier = compressor.mangle_options && compressor.mangle_options.nth_identifier || base54;
2028
2314
  if (self.args.every((x) => x instanceof AST_String)) {
2029
2315
  // quite a corner-case, but we can handle it:
2030
2316
  // https://github.com/mishoo/UglifyJS2/issues/203
@@ -2034,14 +2320,13 @@ def_optimize(AST_Call, function(self, compressor) {
2034
2320
  return arg.value;
2035
2321
  }).join(",") + "){" + self.args[self.args.length - 1].value + "})";
2036
2322
  var ast = parse(code);
2037
- var mangle = { ie8: compressor.option("ie8") };
2323
+ var mangle = { ie8: compressor.option("ie8"), nth_identifier: nth_identifier };
2038
2324
  ast.figure_out_scope(mangle);
2039
2325
  var comp = new Compressor(compressor.options, {
2040
2326
  mangle_options: compressor.mangle_options
2041
2327
  });
2042
2328
  ast = ast.transform(comp);
2043
2329
  ast.figure_out_scope(mangle);
2044
- base54.reset();
2045
2330
  ast.compute_char_frequency(mangle);
2046
2331
  ast.mangle_names(mangle);
2047
2332
  var fun;
@@ -2166,6 +2451,7 @@ def_optimize(AST_Call, function(self, compressor) {
2166
2451
  if (can_inline && has_annotation(self, _INLINE)) {
2167
2452
  set_flag(fn, SQUEEZED);
2168
2453
  fn = make_node(fn.CTOR === AST_Defun ? AST_Function : fn.CTOR, fn, fn);
2454
+ fn = fn.clone(true);
2169
2455
  fn.figure_out_scope({}, {
2170
2456
  parent_scope: find_scope(compressor),
2171
2457
  toplevel: compressor.get_toplevel()
@@ -2464,16 +2750,16 @@ def_optimize(AST_UnaryPostfix, function(self, compressor) {
2464
2750
 
2465
2751
  def_optimize(AST_UnaryPrefix, function(self, compressor) {
2466
2752
  var e = self.expression;
2467
- if (self.operator == "delete"
2468
- && !(e instanceof AST_SymbolRef
2469
- || e instanceof AST_PropAccess
2470
- || is_identifier_atom(e))) {
2471
- if (e instanceof AST_Sequence) {
2472
- const exprs = e.expressions.slice();
2473
- exprs.push(make_node(AST_True, self));
2474
- return make_sequence(self, exprs).optimize(compressor);
2475
- }
2476
- return make_sequence(self, [ e, make_node(AST_True, self) ]).optimize(compressor);
2753
+ if (
2754
+ self.operator == "delete" &&
2755
+ !(
2756
+ e instanceof AST_SymbolRef ||
2757
+ e instanceof AST_PropAccess ||
2758
+ e instanceof AST_Chain ||
2759
+ is_identifier_atom(e)
2760
+ )
2761
+ ) {
2762
+ return make_sequence(self, [e, make_node(AST_True, self)]).optimize(compressor);
2477
2763
  }
2478
2764
  var seq = self.lift_sequences(compressor);
2479
2765
  if (seq !== self) {
@@ -3305,7 +3591,15 @@ function is_reachable(self, defs) {
3305
3591
  if (node instanceof AST_Scope && node !== self) {
3306
3592
  var parent = info.parent();
3307
3593
 
3308
- if (parent instanceof AST_Call && parent.expression === node) return;
3594
+ if (
3595
+ parent instanceof AST_Call
3596
+ && parent.expression === node
3597
+ // Async/Generators aren't guaranteed to sync evaluate all of
3598
+ // their body steps, so it's possible they close over the variable.
3599
+ && !(node.async || node.is_generator)
3600
+ ) {
3601
+ return;
3602
+ }
3309
3603
 
3310
3604
  if (walk(node, find_ref)) return walk_abort;
3311
3605
 
@@ -3953,14 +4247,20 @@ def_optimize(AST_Sub, function(self, compressor) {
3953
4247
  ev = make_node_from_constant(ev, self).optimize(compressor);
3954
4248
  return best_of(compressor, ev, self);
3955
4249
  }
3956
- if (self.optional && is_nullish(self.expression, compressor)) {
3957
- return make_node(AST_Undefined, self);
3958
- }
3959
4250
  return self;
3960
4251
  });
3961
4252
 
3962
4253
  def_optimize(AST_Chain, function (self, compressor) {
3963
- self.expression = self.expression.optimize(compressor);
4254
+ if (is_nullish(self.expression, compressor)) {
4255
+ let parent = compressor.parent();
4256
+ // It's valid to delete a nullish optional chain, but if we optimized
4257
+ // this to `delete undefined` then it would appear to be a syntax error
4258
+ // when we try to optimize the delete. Thankfully, `delete 0` is fine.
4259
+ if (parent instanceof AST_UnaryPrefix && parent.operator === "delete") {
4260
+ return make_node_from_constant(0, self);
4261
+ }
4262
+ return make_node(AST_Undefined, self);
4263
+ }
3964
4264
  return self;
3965
4265
  });
3966
4266
 
@@ -4027,9 +4327,6 @@ def_optimize(AST_Dot, function(self, compressor) {
4027
4327
  ev = make_node_from_constant(ev, self).optimize(compressor);
4028
4328
  return best_of(compressor, ev, self);
4029
4329
  }
4030
- if (self.optional && is_nullish(self.expression, compressor)) {
4031
- return make_node(AST_Undefined, self);
4032
- }
4033
4330
  return self;
4034
4331
  });
4035
4332
 
@@ -4089,9 +4386,11 @@ function inline_object_prop_spread(props, compressor) {
4089
4386
  // non-iterable value silently does nothing; it is thus safe
4090
4387
  // to remove. AST_String is the only iterable AST_Constant.
4091
4388
  props.splice(i, 1);
4389
+ i--;
4092
4390
  } else if (is_nullish(expr, compressor)) {
4093
4391
  // Likewise, null and undefined can be silently removed.
4094
4392
  props.splice(i, 1);
4393
+ i--;
4095
4394
  }
4096
4395
  }
4097
4396
  }
@@ -4239,12 +4538,15 @@ function lift_key(self, compressor) {
4239
4538
  if (self.key.value == "constructor"
4240
4539
  && compressor.parent() instanceof AST_Class) return self;
4241
4540
  if (self instanceof AST_ObjectKeyVal) {
4541
+ self.quote = self.key.quote;
4242
4542
  self.key = self.key.value;
4243
4543
  } else if (self instanceof AST_ClassProperty) {
4544
+ self.quote = self.key.quote;
4244
4545
  self.key = make_node(AST_SymbolClassProperty, self.key, {
4245
4546
  name: self.key.value
4246
4547
  });
4247
4548
  } else {
4549
+ self.quote = self.key.quote;
4248
4550
  self.key = make_node(AST_SymbolMethod, self.key, {
4249
4551
  name: self.key.value
4250
4552
  });
@@ -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);
@@ -154,6 +154,7 @@ export const is_pure_native_fn = make_nested_lookup({
154
154
  "isExtensible",
155
155
  "isFrozen",
156
156
  "isSealed",
157
+ "hasOwn",
157
158
  "keys",
158
159
  ],
159
160
  String: [