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.
- package/CHANGELOG.md +36 -0
- package/README.md +21 -2
- package/dist/bundle.min.js +705 -213
- package/lib/ast.js +6 -4
- package/lib/compress/drop-side-effect-free.js +9 -12
- package/lib/compress/evaluate.js +15 -12
- package/lib/compress/index.js +368 -66
- package/lib/compress/inference.js +27 -13
- package/lib/compress/native-objects.js +1 -0
- package/lib/compress/tighten-body.js +29 -7
- package/lib/minify.js +3 -1
- package/lib/mozilla-ast.js +48 -6
- package/lib/output.js +81 -26
- package/lib/parse.js +24 -8
- package/lib/propmangle.js +57 -34
- package/lib/scope.js +42 -21
- package/lib/transform.js +2 -2
- package/package.json +9 -9
- package/tools/terser.d.ts +36 -0
package/lib/compress/index.js
CHANGED
@@ -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
|
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
|
-
|
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(
|
1620
|
-
body: self.expression
|
1621
|
-
}))
|
1810
|
+
body: decl.concat(statement(self.expression))
|
1622
1811
|
}).optimize(compressor);
|
1623
1812
|
}
|
1624
|
-
if (body.length == 1 && (
|
1625
|
-
|
1626
|
-
|
1627
|
-
|
1628
|
-
|
1629
|
-
|
1630
|
-
|
1631
|
-
|
1632
|
-
|
1633
|
-
|
1634
|
-
|
1635
|
-
|
1636
|
-
|
1637
|
-
|
1638
|
-
|
1639
|
-
|
1640
|
-
|
1641
|
-
|
1642
|
-
|
1643
|
-
|
1644
|
-
|
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 (
|
2468
|
-
|
2469
|
-
|
2470
|
-
|
2471
|
-
|
2472
|
-
|
2473
|
-
|
2474
|
-
|
2475
|
-
|
2476
|
-
return make_sequence(self, [
|
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 (
|
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
|
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
|
-
//
|
230
|
-
|
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 (
|
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 (
|
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 (
|
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);
|