terser 5.27.2 → 5.28.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,9 @@
1
1
  # Changelog
2
2
 
3
+ ## v5.28.0
4
+ - Optimise redundant or shrinkable bitwise operations (`|`, `^`, `&`, `>>`, `<<`)
5
+ - Evaluate some `BigInt` math operations
6
+
3
7
  ## v5.27.2
4
8
  - Recognise `this` as a reference to the surrounding class in `drop_unused`. Closes #1472
5
9
 
@@ -12778,8 +12778,8 @@ AST_Class.prototype._size = function () {
12778
12778
  };
12779
12779
 
12780
12780
  AST_ClassStaticBlock.prototype._size = function () {
12781
- // "class{}" + semicolons
12782
- return 7 + list_overhead(this.body);
12781
+ // "static{}" + semicolons
12782
+ return 8 + list_overhead(this.body);
12783
12783
  };
12784
12784
 
12785
12785
  AST_ClassProperty.prototype._size = function () {
@@ -13008,6 +13008,8 @@ function make_node_from_constant(val, orig) {
13008
13008
  operator: "-",
13009
13009
  expression: make_node(AST_Infinity, orig)
13010
13010
  }) : make_node(AST_Infinity, orig);
13011
+ case "bigint":
13012
+ return make_node(AST_BigInt, orig, { value: val.toString() });
13011
13013
  case "boolean":
13012
13014
  return make_node(val ? AST_True : AST_False, orig);
13013
13015
  case "undefined":
@@ -13486,6 +13488,7 @@ const is_pure_native_value = make_nested_lookup({
13486
13488
  const is_undeclared_ref = (node) =>
13487
13489
  node instanceof AST_SymbolRef && node.definition().undeclared;
13488
13490
 
13491
+ const bitwise_binop = makePredicate("<<< >> << & | ^ ~");
13489
13492
  const lazy_op = makePredicate("&& || ??");
13490
13493
  const unary_side_effects = makePredicate("delete ++ --");
13491
13494
 
@@ -13546,6 +13549,24 @@ const unary_side_effects = makePredicate("delete ++ --");
13546
13549
  node.DEFMETHOD("is_number", func);
13547
13550
  });
13548
13551
 
13552
+ // methods to determine if an expression is a 32 bit integer (IE results from bitwise ops, or is an integer constant fitting in that size
13553
+ (function(def_is_32_bit_integer) {
13554
+ def_is_32_bit_integer(AST_Node, return_false);
13555
+ def_is_32_bit_integer(AST_Number, function() {
13556
+ return this.value === (this.value | 0);
13557
+ });
13558
+ def_is_32_bit_integer(AST_UnaryPrefix, function() {
13559
+ return this.operator == "~" ? this.expression.is_number()
13560
+ : this.operator === "+" ? this.expression.is_32_bit_integer()
13561
+ : false;
13562
+ });
13563
+ def_is_32_bit_integer(AST_Binary, function() {
13564
+ return bitwise_binop.has(this.operator);
13565
+ });
13566
+ }(function (node, func) {
13567
+ node.DEFMETHOD("is_32_bit_integer", func);
13568
+ }));
13569
+
13549
13570
  // methods to determine if an expression has a string result type
13550
13571
  (function(def_is_string) {
13551
13572
  def_is_string(AST_Node, return_false);
@@ -14160,6 +14181,37 @@ function is_lhs(node, parent) {
14160
14181
  });
14161
14182
  });
14162
14183
 
14184
+ (function (def_bitwise_negate) {
14185
+ function basic_negation(exp) {
14186
+ return make_node(AST_UnaryPrefix, exp, {
14187
+ operator: "~",
14188
+ expression: exp
14189
+ });
14190
+ }
14191
+
14192
+ def_bitwise_negate(AST_Node, function() {
14193
+ return basic_negation(this);
14194
+ });
14195
+
14196
+ def_bitwise_negate(AST_Number, function() {
14197
+ const neg = ~this.value;
14198
+ if (neg.toString().length > this.value.toString().length) {
14199
+ return basic_negation(this);
14200
+ }
14201
+ return make_node(AST_Number, this, { value: neg });
14202
+ });
14203
+
14204
+ def_bitwise_negate(AST_UnaryPrefix, function(in_32_bit_context) {
14205
+ if (this.operator == "~" && (in_32_bit_context || this.expression.is_32_bit_integer())) {
14206
+ return this.expression;
14207
+ } else {
14208
+ return basic_negation(this);
14209
+ }
14210
+ });
14211
+ })(function (node, func) {
14212
+ node.DEFMETHOD("bitwise_negate", func);
14213
+ });
14214
+
14163
14215
  // Is the callee of this function pure?
14164
14216
  var global_pure_fns = makePredicate("Boolean decodeURI decodeURIComponent Date encodeURI encodeURIComponent Error escape EvalError isFinite isNaN Number Object parseFloat parseInt RangeError ReferenceError String SyntaxError TypeError unescape URIError");
14165
14217
  AST_Call.DEFMETHOD("is_callee_pure", function(compressor) {
@@ -14385,7 +14437,14 @@ def_eval(AST_Constant, function () {
14385
14437
  return this.getValue();
14386
14438
  });
14387
14439
 
14388
- def_eval(AST_BigInt, return_this);
14440
+ const supports_bigint = typeof BigInt === "function";
14441
+ def_eval(AST_BigInt, function () {
14442
+ if (supports_bigint) {
14443
+ return BigInt(this.value);
14444
+ } else {
14445
+ return this;
14446
+ }
14447
+ });
14389
14448
 
14390
14449
  def_eval(AST_RegExp, function (compressor) {
14391
14450
  let evaluated = compressor.evaluated_regexps.get(this.value);
@@ -14509,7 +14568,6 @@ def_eval(AST_Binary, function (compressor, depth) {
14509
14568
  var right = this.right._eval(compressor, depth);
14510
14569
  if (right === this.right)
14511
14570
  return this;
14512
- var result;
14513
14571
 
14514
14572
  if (left != null
14515
14573
  && right != null
@@ -14521,6 +14579,17 @@ def_eval(AST_Binary, function (compressor, depth) {
14521
14579
  return this;
14522
14580
  }
14523
14581
 
14582
+ // Do not mix BigInt and Number; Don't use `>>>` on BigInt or `/ 0n`
14583
+ if (
14584
+ (typeof left === "bigint") !== (typeof right === "bigint")
14585
+ || typeof left === "bigint"
14586
+ && (this.operator === ">>>"
14587
+ || this.operator === "/" && Number(right) === 0)
14588
+ ) {
14589
+ return this;
14590
+ }
14591
+
14592
+ var result;
14524
14593
  switch (this.operator) {
14525
14594
  case "&&": result = left && right; break;
14526
14595
  case "||": result = left || right; break;
@@ -14530,7 +14599,7 @@ def_eval(AST_Binary, function (compressor, depth) {
14530
14599
  case "^": result = left ^ right; break;
14531
14600
  case "+": result = left + right; break;
14532
14601
  case "*": result = left * right; break;
14533
- case "**": result = Math.pow(left, right); break;
14602
+ case "**": result = left ** right; break;
14534
14603
  case "/": result = left / right; break;
14535
14604
  case "%": result = left % right; break;
14536
14605
  case "-": result = left - right; break;
@@ -14548,7 +14617,7 @@ def_eval(AST_Binary, function (compressor, depth) {
14548
14617
  default:
14549
14618
  return this;
14550
14619
  }
14551
- if (isNaN(result) && compressor.find_parent(AST_With)) {
14620
+ if (typeof result === "number" && isNaN(result) && compressor.find_parent(AST_With)) {
14552
14621
  // leave original expression as is
14553
14622
  return this;
14554
14623
  }
@@ -18546,6 +18615,33 @@ class Compressor extends TreeWalker {
18546
18615
  }
18547
18616
  }
18548
18617
 
18618
+ in_32_bit_context() {
18619
+ if (!this.option("evaluate")) return false;
18620
+ var self = this.self();
18621
+ for (var i = 0, p; p = this.parent(i); i++) {
18622
+ if (p instanceof AST_Binary && bitwise_binop.has(p.operator)) {
18623
+ return true;
18624
+ }
18625
+ if (p instanceof AST_UnaryPrefix) {
18626
+ return p.operator === "~";
18627
+ }
18628
+ if (
18629
+ p instanceof AST_Binary
18630
+ && (
18631
+ p.operator == "&&"
18632
+ || p.operator == "||"
18633
+ || p.operator == "??"
18634
+ )
18635
+ || p instanceof AST_Conditional && p.condition !== self
18636
+ || p.tail_node() === self
18637
+ ) {
18638
+ self = p;
18639
+ } else {
18640
+ return false;
18641
+ }
18642
+ }
18643
+ }
18644
+
18549
18645
  get_toplevel() {
18550
18646
  return this._toplevel;
18551
18647
  }
@@ -20244,9 +20340,40 @@ def_optimize(AST_UnaryPrefix, function(self, compressor) {
20244
20340
  right: e.right
20245
20341
  });
20246
20342
  }
20247
- // avoids infinite recursion of numerals
20248
- if (self.operator != "-"
20249
- || !(e instanceof AST_Number || e instanceof AST_Infinity || e instanceof AST_BigInt)) {
20343
+
20344
+ if (compressor.option("evaluate")) {
20345
+ // ~~x => x (in 32-bit context)
20346
+ // ~~{32 bit integer} => {32 bit integer}
20347
+ if (
20348
+ self.operator === "~"
20349
+ && self.expression instanceof AST_UnaryPrefix
20350
+ && self.expression.operator === "~"
20351
+ && (compressor.in_32_bit_context() || self.expression.expression.is_32_bit_integer())
20352
+ ) {
20353
+ return self.expression.expression;
20354
+ }
20355
+
20356
+ // ~(x ^ y) => x ^ ~y
20357
+ if (
20358
+ self.operator === "~"
20359
+ && e instanceof AST_Binary
20360
+ && e.operator === "^"
20361
+ ) {
20362
+ if (e.left instanceof AST_UnaryPrefix && e.left.operator === "~") {
20363
+ // ~(~x ^ y) => x ^ y
20364
+ e.left = e.left.bitwise_negate(true);
20365
+ } else {
20366
+ e.right = e.right.bitwise_negate(true);
20367
+ }
20368
+ return e;
20369
+ }
20370
+ }
20371
+
20372
+ if (
20373
+ self.operator != "-"
20374
+ // avoid infinite recursion of numerals
20375
+ || !(e instanceof AST_Number || e instanceof AST_Infinity || e instanceof AST_BigInt)
20376
+ ) {
20250
20377
  var ev = self.evaluate(compressor);
20251
20378
  if (ev !== self) {
20252
20379
  ev = make_node_from_constant(ev, self).optimize(compressor);
@@ -20337,6 +20464,7 @@ def_optimize(AST_Binary, function(self, compressor) {
20337
20464
  self.left.equivalent_to(self.right)) {
20338
20465
  self.operator = self.operator.substr(0, 2);
20339
20466
  }
20467
+
20340
20468
  // XXX: intentionally falling down to the next case
20341
20469
  case "==":
20342
20470
  case "!=":
@@ -20378,6 +20506,55 @@ def_optimize(AST_Binary, function(self, compressor) {
20378
20506
  && self.left.definition() === self.right.definition()
20379
20507
  && is_object(self.left.fixed_value())) {
20380
20508
  return make_node(self.operator[0] == "=" ? AST_True : AST_False, self);
20509
+ } else if (self.left.is_32_bit_integer() && self.right.is_32_bit_integer()) {
20510
+ const not = node => make_node(AST_UnaryPrefix, node, {
20511
+ operator: "!",
20512
+ expression: node
20513
+ });
20514
+ const booleanify = (node, truthy) => {
20515
+ if (truthy) {
20516
+ return compressor.in_boolean_context()
20517
+ ? node
20518
+ : not(not(node));
20519
+ } else {
20520
+ return not(node);
20521
+ }
20522
+ };
20523
+
20524
+ // The only falsy 32-bit integer is 0
20525
+ if (self.left instanceof AST_Number && self.left.value === 0) {
20526
+ return booleanify(self.right, self.operator[0] === "!");
20527
+ }
20528
+ if (self.right instanceof AST_Number && self.right.value === 0) {
20529
+ return booleanify(self.left, self.operator[0] === "!");
20530
+ }
20531
+
20532
+ // Mask all-bits check
20533
+ // (x & 0xFF) != 0xFF => !(~x & 0xFF)
20534
+ let and_op, x, mask;
20535
+ if (
20536
+ (and_op =
20537
+ self.left instanceof AST_Binary ? self.left
20538
+ : self.right instanceof AST_Binary ? self.right : null)
20539
+ && (mask = and_op === self.left ? self.right : self.left)
20540
+ && and_op.operator === "&"
20541
+ && mask instanceof AST_Number
20542
+ && mask.is_32_bit_integer()
20543
+ && (x =
20544
+ and_op.left.equivalent_to(mask) ? and_op.right
20545
+ : and_op.right.equivalent_to(mask) ? and_op.left : null)
20546
+ ) {
20547
+ let optimized = booleanify(make_node(AST_Binary, self, {
20548
+ operator: "&",
20549
+ left: mask,
20550
+ right: make_node(AST_UnaryPrefix, self, {
20551
+ operator: "~",
20552
+ expression: x
20553
+ })
20554
+ }), self.operator[0] === "=");
20555
+
20556
+ return best_of(compressor, optimized, self);
20557
+ }
20381
20558
  }
20382
20559
  break;
20383
20560
  case "&&":
@@ -20751,6 +20928,157 @@ def_optimize(AST_Binary, function(self, compressor) {
20751
20928
  }
20752
20929
  }
20753
20930
  }
20931
+
20932
+ // bitwise ops
20933
+ if (bitwise_binop.has(self.operator)) {
20934
+ // Use De Morgan's laws
20935
+ // z & (X | y)
20936
+ // => z & X (given y & z === 0)
20937
+ // => z & X | {y & z} (given y & z !== 0)
20938
+ let y, z, x_node, y_node, z_node = self.left;
20939
+ if (
20940
+ self.operator === "&"
20941
+ && self.right instanceof AST_Binary
20942
+ && self.right.operator === "|"
20943
+ && typeof (z = self.left.evaluate(compressor)) === "number"
20944
+ ) {
20945
+ if (typeof (y = self.right.right.evaluate(compressor)) === "number") {
20946
+ // z & (X | y)
20947
+ x_node = self.right.left;
20948
+ y_node = self.right.right;
20949
+ } else if (typeof (y = self.right.left.evaluate(compressor)) === "number") {
20950
+ // z & (y | X)
20951
+ x_node = self.right.right;
20952
+ y_node = self.right.left;
20953
+ }
20954
+
20955
+ if ((y & z) === 0) {
20956
+ self = make_node(AST_Binary, self, {
20957
+ operator: self.operator,
20958
+ left: z_node,
20959
+ right: x_node
20960
+ });
20961
+ } else {
20962
+ const reordered_ops = make_node(AST_Binary, self, {
20963
+ operator: "|",
20964
+ left: make_node(AST_Binary, self, {
20965
+ operator: "&",
20966
+ left: x_node,
20967
+ right: z_node
20968
+ }),
20969
+ right: make_node_from_constant(y & z, y_node),
20970
+ });
20971
+
20972
+ self = best_of(compressor, self, reordered_ops);
20973
+ }
20974
+ }
20975
+
20976
+ // x ^ x => 0
20977
+ // x | x => 0 | x
20978
+ // x & x => 0 | x
20979
+ const same_operands = self.left.equivalent_to(self.right) && !self.left.has_side_effects(compressor);
20980
+ if (same_operands) {
20981
+ if (self.operator === "^") {
20982
+ return make_node(AST_Number, self, { value: 0 });
20983
+ }
20984
+ if (self.operator === "|" || self.operator === "&") {
20985
+ self.left = make_node(AST_Number, self, { value: 0 });
20986
+ self.operator = "|";
20987
+ }
20988
+ }
20989
+
20990
+
20991
+ // Shifts that do nothing
20992
+ // {anything} >> 0 => {anything} | 0
20993
+ // {anything} << 0 => {anything} | 0
20994
+ if (
20995
+ (self.operator === "<<" || self.operator === ">>")
20996
+ && self.right instanceof AST_Number && self.right.value === 0
20997
+ ) {
20998
+ self.operator = "|";
20999
+ }
21000
+
21001
+ // Find useless to-bitwise conversions
21002
+ // {32 bit integer} | 0 => {32 bit integer}
21003
+ // {32 bit integer} ^ 0 => {32 bit integer}
21004
+ const zero_side = self.right instanceof AST_Number && self.right.value === 0 ? self.right
21005
+ : self.left instanceof AST_Number && self.left.value === 0 ? self.left
21006
+ : null;
21007
+ const non_zero_side = zero_side && (zero_side === self.right ? self.left : self.right);
21008
+ if (
21009
+ zero_side
21010
+ && (self.operator === "|" || self.operator === "^")
21011
+ && (non_zero_side.is_32_bit_integer() || compressor.in_32_bit_context())
21012
+ ) {
21013
+ return non_zero_side;
21014
+ }
21015
+
21016
+ // {anything} & 0 => 0
21017
+ if (
21018
+ zero_side
21019
+ && self.operator === "&"
21020
+ && !non_zero_side.has_side_effects(compressor)
21021
+ ) {
21022
+ return zero_side;
21023
+ }
21024
+
21025
+ const is_full_mask = (node) =>
21026
+ node instanceof AST_Number && node.value === -1
21027
+ ||
21028
+ node instanceof AST_UnaryPrefix && (
21029
+ node.operator === "-"
21030
+ && node.expression instanceof AST_Number
21031
+ && node.expression.value === 1
21032
+ || node.operator === "~"
21033
+ && node.expression instanceof AST_Number
21034
+ && node.expression.value === 0);
21035
+
21036
+ const full_mask = is_full_mask(self.right) ? self.right
21037
+ : is_full_mask(self.left) ? self.left
21038
+ : null;
21039
+ const non_full_mask_side = full_mask && (full_mask === self.right ? self.left : self.right);
21040
+
21041
+ switch (self.operator) {
21042
+ case "|":
21043
+ // {anything} | -1 => -1
21044
+ if (full_mask && !non_full_mask_side.has_side_effects(compressor)) {
21045
+ return full_mask;
21046
+ }
21047
+
21048
+ break;
21049
+ case "&":
21050
+ // {32 bit integer} & -1 => {32 bit integer}
21051
+ if (
21052
+ full_mask
21053
+ && (non_full_mask_side.is_32_bit_integer() || compressor.in_32_bit_context())
21054
+ ) {
21055
+ return non_full_mask_side;
21056
+ }
21057
+
21058
+ break;
21059
+ case "^":
21060
+ // {anything} ^ -1 => ~{anything}
21061
+ if (full_mask) {
21062
+ return non_full_mask_side.bitwise_negate(compressor.in_32_bit_context());
21063
+ }
21064
+
21065
+ // ~x ^ ~y => x ^ y
21066
+ if (
21067
+ self.left instanceof AST_UnaryPrefix
21068
+ && self.left.operator === "~"
21069
+ && self.right instanceof AST_UnaryPrefix
21070
+ && self.right.operator === "~"
21071
+ ) {
21072
+ self = make_node(AST_Binary, self, {
21073
+ operator: "^",
21074
+ left: self.left.expression,
21075
+ right: self.right.expression
21076
+ });
21077
+ }
21078
+
21079
+ break;
21080
+ }
21081
+ }
20754
21082
  }
20755
21083
  // x && (y && z) ==> x && y && z
20756
21084
  // x || (y || z) ==> x || y || z
@@ -44,6 +44,7 @@
44
44
  import {
45
45
  AST_Array,
46
46
  AST_Arrow,
47
+ AST_BigInt,
47
48
  AST_BlockStatement,
48
49
  AST_Call,
49
50
  AST_Chain,
@@ -124,6 +125,8 @@ export function make_node_from_constant(val, orig) {
124
125
  operator: "-",
125
126
  expression: make_node(AST_Infinity, orig)
126
127
  }) : make_node(AST_Infinity, orig);
128
+ case "bigint":
129
+ return make_node(AST_BigInt, orig, { value: val.toString() });
127
130
  case "boolean":
128
131
  return make_node(val ? AST_True : AST_False, orig);
129
132
  case "undefined":
@@ -133,7 +133,14 @@ def_eval(AST_Constant, function () {
133
133
  return this.getValue();
134
134
  });
135
135
 
136
- def_eval(AST_BigInt, return_this);
136
+ const supports_bigint = typeof BigInt === "function";
137
+ def_eval(AST_BigInt, function () {
138
+ if (supports_bigint) {
139
+ return BigInt(this.value);
140
+ } else {
141
+ return this;
142
+ }
143
+ });
137
144
 
138
145
  def_eval(AST_RegExp, function (compressor) {
139
146
  let evaluated = compressor.evaluated_regexps.get(this.value);
@@ -257,7 +264,6 @@ def_eval(AST_Binary, function (compressor, depth) {
257
264
  var right = this.right._eval(compressor, depth);
258
265
  if (right === this.right)
259
266
  return this;
260
- var result;
261
267
 
262
268
  if (left != null
263
269
  && right != null
@@ -269,6 +275,17 @@ def_eval(AST_Binary, function (compressor, depth) {
269
275
  return this;
270
276
  }
271
277
 
278
+ // Do not mix BigInt and Number; Don't use `>>>` on BigInt or `/ 0n`
279
+ if (
280
+ (typeof left === "bigint") !== (typeof right === "bigint")
281
+ || typeof left === "bigint"
282
+ && (this.operator === ">>>"
283
+ || this.operator === "/" && Number(right) === 0)
284
+ ) {
285
+ return this;
286
+ }
287
+
288
+ var result;
272
289
  switch (this.operator) {
273
290
  case "&&": result = left && right; break;
274
291
  case "||": result = left || right; break;
@@ -278,7 +295,7 @@ def_eval(AST_Binary, function (compressor, depth) {
278
295
  case "^": result = left ^ right; break;
279
296
  case "+": result = left + right; break;
280
297
  case "*": result = left * right; break;
281
- case "**": result = Math.pow(left, right); break;
298
+ case "**": result = left ** right; break;
282
299
  case "/": result = left / right; break;
283
300
  case "%": result = left % right; break;
284
301
  case "-": result = left - right; break;
@@ -296,7 +313,7 @@ def_eval(AST_Binary, function (compressor, depth) {
296
313
  default:
297
314
  return this;
298
315
  }
299
- if (isNaN(result) && compressor.find_parent(AST_With)) {
316
+ if (typeof result === "number" && isNaN(result) && compressor.find_parent(AST_With)) {
300
317
  // leave original expression as is
301
318
  return this;
302
319
  }
@@ -170,6 +170,7 @@ import "./drop-unused.js";
170
170
  import "./reduce-vars.js";
171
171
  import {
172
172
  is_undeclared_ref,
173
+ bitwise_binop,
173
174
  lazy_op,
174
175
  is_nullish,
175
176
  is_undefined,
@@ -373,6 +374,33 @@ class Compressor extends TreeWalker {
373
374
  }
374
375
  }
375
376
 
377
+ in_32_bit_context() {
378
+ if (!this.option("evaluate")) return false;
379
+ var self = this.self();
380
+ for (var i = 0, p; p = this.parent(i); i++) {
381
+ if (p instanceof AST_Binary && bitwise_binop.has(p.operator)) {
382
+ return true;
383
+ }
384
+ if (p instanceof AST_UnaryPrefix) {
385
+ return p.operator === "~";
386
+ }
387
+ if (
388
+ p instanceof AST_Binary
389
+ && (
390
+ p.operator == "&&"
391
+ || p.operator == "||"
392
+ || p.operator == "??"
393
+ )
394
+ || p instanceof AST_Conditional && p.condition !== self
395
+ || p.tail_node() === self
396
+ ) {
397
+ self = p;
398
+ } else {
399
+ return false;
400
+ }
401
+ }
402
+ }
403
+
376
404
  get_toplevel() {
377
405
  return this._toplevel;
378
406
  }
@@ -2071,9 +2099,40 @@ def_optimize(AST_UnaryPrefix, function(self, compressor) {
2071
2099
  right: e.right
2072
2100
  });
2073
2101
  }
2074
- // avoids infinite recursion of numerals
2075
- if (self.operator != "-"
2076
- || !(e instanceof AST_Number || e instanceof AST_Infinity || e instanceof AST_BigInt)) {
2102
+
2103
+ if (compressor.option("evaluate")) {
2104
+ // ~~x => x (in 32-bit context)
2105
+ // ~~{32 bit integer} => {32 bit integer}
2106
+ if (
2107
+ self.operator === "~"
2108
+ && self.expression instanceof AST_UnaryPrefix
2109
+ && self.expression.operator === "~"
2110
+ && (compressor.in_32_bit_context() || self.expression.expression.is_32_bit_integer())
2111
+ ) {
2112
+ return self.expression.expression;
2113
+ }
2114
+
2115
+ // ~(x ^ y) => x ^ ~y
2116
+ if (
2117
+ self.operator === "~"
2118
+ && e instanceof AST_Binary
2119
+ && e.operator === "^"
2120
+ ) {
2121
+ if (e.left instanceof AST_UnaryPrefix && e.left.operator === "~") {
2122
+ // ~(~x ^ y) => x ^ y
2123
+ e.left = e.left.bitwise_negate(true);
2124
+ } else {
2125
+ e.right = e.right.bitwise_negate(true);
2126
+ }
2127
+ return e;
2128
+ }
2129
+ }
2130
+
2131
+ if (
2132
+ self.operator != "-"
2133
+ // avoid infinite recursion of numerals
2134
+ || !(e instanceof AST_Number || e instanceof AST_Infinity || e instanceof AST_BigInt)
2135
+ ) {
2077
2136
  var ev = self.evaluate(compressor);
2078
2137
  if (ev !== self) {
2079
2138
  ev = make_node_from_constant(ev, self).optimize(compressor);
@@ -2164,6 +2223,7 @@ def_optimize(AST_Binary, function(self, compressor) {
2164
2223
  self.left.equivalent_to(self.right)) {
2165
2224
  self.operator = self.operator.substr(0, 2);
2166
2225
  }
2226
+
2167
2227
  // XXX: intentionally falling down to the next case
2168
2228
  case "==":
2169
2229
  case "!=":
@@ -2205,6 +2265,55 @@ def_optimize(AST_Binary, function(self, compressor) {
2205
2265
  && self.left.definition() === self.right.definition()
2206
2266
  && is_object(self.left.fixed_value())) {
2207
2267
  return make_node(self.operator[0] == "=" ? AST_True : AST_False, self);
2268
+ } else if (self.left.is_32_bit_integer() && self.right.is_32_bit_integer()) {
2269
+ const not = node => make_node(AST_UnaryPrefix, node, {
2270
+ operator: "!",
2271
+ expression: node
2272
+ });
2273
+ const booleanify = (node, truthy) => {
2274
+ if (truthy) {
2275
+ return compressor.in_boolean_context()
2276
+ ? node
2277
+ : not(not(node));
2278
+ } else {
2279
+ return not(node);
2280
+ }
2281
+ };
2282
+
2283
+ // The only falsy 32-bit integer is 0
2284
+ if (self.left instanceof AST_Number && self.left.value === 0) {
2285
+ return booleanify(self.right, self.operator[0] === "!");
2286
+ }
2287
+ if (self.right instanceof AST_Number && self.right.value === 0) {
2288
+ return booleanify(self.left, self.operator[0] === "!");
2289
+ }
2290
+
2291
+ // Mask all-bits check
2292
+ // (x & 0xFF) != 0xFF => !(~x & 0xFF)
2293
+ let and_op, x, mask;
2294
+ if (
2295
+ (and_op =
2296
+ self.left instanceof AST_Binary ? self.left
2297
+ : self.right instanceof AST_Binary ? self.right : null)
2298
+ && (mask = and_op === self.left ? self.right : self.left)
2299
+ && and_op.operator === "&"
2300
+ && mask instanceof AST_Number
2301
+ && mask.is_32_bit_integer()
2302
+ && (x =
2303
+ and_op.left.equivalent_to(mask) ? and_op.right
2304
+ : and_op.right.equivalent_to(mask) ? and_op.left : null)
2305
+ ) {
2306
+ let optimized = booleanify(make_node(AST_Binary, self, {
2307
+ operator: "&",
2308
+ left: mask,
2309
+ right: make_node(AST_UnaryPrefix, self, {
2310
+ operator: "~",
2311
+ expression: x
2312
+ })
2313
+ }), self.operator[0] === "=");
2314
+
2315
+ return best_of(compressor, optimized, self);
2316
+ }
2208
2317
  }
2209
2318
  break;
2210
2319
  case "&&":
@@ -2578,6 +2687,157 @@ def_optimize(AST_Binary, function(self, compressor) {
2578
2687
  }
2579
2688
  }
2580
2689
  }
2690
+
2691
+ // bitwise ops
2692
+ if (bitwise_binop.has(self.operator)) {
2693
+ // Use De Morgan's laws
2694
+ // z & (X | y)
2695
+ // => z & X (given y & z === 0)
2696
+ // => z & X | {y & z} (given y & z !== 0)
2697
+ let y, z, x_node, y_node, z_node = self.left;
2698
+ if (
2699
+ self.operator === "&"
2700
+ && self.right instanceof AST_Binary
2701
+ && self.right.operator === "|"
2702
+ && typeof (z = self.left.evaluate(compressor)) === "number"
2703
+ ) {
2704
+ if (typeof (y = self.right.right.evaluate(compressor)) === "number") {
2705
+ // z & (X | y)
2706
+ x_node = self.right.left;
2707
+ y_node = self.right.right;
2708
+ } else if (typeof (y = self.right.left.evaluate(compressor)) === "number") {
2709
+ // z & (y | X)
2710
+ x_node = self.right.right;
2711
+ y_node = self.right.left;
2712
+ }
2713
+
2714
+ if ((y & z) === 0) {
2715
+ self = make_node(AST_Binary, self, {
2716
+ operator: self.operator,
2717
+ left: z_node,
2718
+ right: x_node
2719
+ });
2720
+ } else {
2721
+ const reordered_ops = make_node(AST_Binary, self, {
2722
+ operator: "|",
2723
+ left: make_node(AST_Binary, self, {
2724
+ operator: "&",
2725
+ left: x_node,
2726
+ right: z_node
2727
+ }),
2728
+ right: make_node_from_constant(y & z, y_node),
2729
+ });
2730
+
2731
+ self = best_of(compressor, self, reordered_ops);
2732
+ }
2733
+ }
2734
+
2735
+ // x ^ x => 0
2736
+ // x | x => 0 | x
2737
+ // x & x => 0 | x
2738
+ const same_operands = self.left.equivalent_to(self.right) && !self.left.has_side_effects(compressor);
2739
+ if (same_operands) {
2740
+ if (self.operator === "^") {
2741
+ return make_node(AST_Number, self, { value: 0 });
2742
+ }
2743
+ if (self.operator === "|" || self.operator === "&") {
2744
+ self.left = make_node(AST_Number, self, { value: 0 });
2745
+ self.operator = "|";
2746
+ }
2747
+ }
2748
+
2749
+
2750
+ // Shifts that do nothing
2751
+ // {anything} >> 0 => {anything} | 0
2752
+ // {anything} << 0 => {anything} | 0
2753
+ if (
2754
+ (self.operator === "<<" || self.operator === ">>")
2755
+ && self.right instanceof AST_Number && self.right.value === 0
2756
+ ) {
2757
+ self.operator = "|";
2758
+ }
2759
+
2760
+ // Find useless to-bitwise conversions
2761
+ // {32 bit integer} | 0 => {32 bit integer}
2762
+ // {32 bit integer} ^ 0 => {32 bit integer}
2763
+ const zero_side = self.right instanceof AST_Number && self.right.value === 0 ? self.right
2764
+ : self.left instanceof AST_Number && self.left.value === 0 ? self.left
2765
+ : null;
2766
+ const non_zero_side = zero_side && (zero_side === self.right ? self.left : self.right);
2767
+ if (
2768
+ zero_side
2769
+ && (self.operator === "|" || self.operator === "^")
2770
+ && (non_zero_side.is_32_bit_integer() || compressor.in_32_bit_context())
2771
+ ) {
2772
+ return non_zero_side;
2773
+ }
2774
+
2775
+ // {anything} & 0 => 0
2776
+ if (
2777
+ zero_side
2778
+ && self.operator === "&"
2779
+ && !non_zero_side.has_side_effects(compressor)
2780
+ ) {
2781
+ return zero_side;
2782
+ }
2783
+
2784
+ const is_full_mask = (node) =>
2785
+ node instanceof AST_Number && node.value === -1
2786
+ ||
2787
+ node instanceof AST_UnaryPrefix && (
2788
+ node.operator === "-"
2789
+ && node.expression instanceof AST_Number
2790
+ && node.expression.value === 1
2791
+ || node.operator === "~"
2792
+ && node.expression instanceof AST_Number
2793
+ && node.expression.value === 0);
2794
+
2795
+ const full_mask = is_full_mask(self.right) ? self.right
2796
+ : is_full_mask(self.left) ? self.left
2797
+ : null;
2798
+ const non_full_mask_side = full_mask && (full_mask === self.right ? self.left : self.right);
2799
+
2800
+ switch (self.operator) {
2801
+ case "|":
2802
+ // {anything} | -1 => -1
2803
+ if (full_mask && !non_full_mask_side.has_side_effects(compressor)) {
2804
+ return full_mask;
2805
+ }
2806
+
2807
+ break;
2808
+ case "&":
2809
+ // {32 bit integer} & -1 => {32 bit integer}
2810
+ if (
2811
+ full_mask
2812
+ && (non_full_mask_side.is_32_bit_integer() || compressor.in_32_bit_context())
2813
+ ) {
2814
+ return non_full_mask_side;
2815
+ }
2816
+
2817
+ break;
2818
+ case "^":
2819
+ // {anything} ^ -1 => ~{anything}
2820
+ if (full_mask) {
2821
+ return non_full_mask_side.bitwise_negate(compressor.in_32_bit_context());
2822
+ }
2823
+
2824
+ // ~x ^ ~y => x ^ y
2825
+ if (
2826
+ self.left instanceof AST_UnaryPrefix
2827
+ && self.left.operator === "~"
2828
+ && self.right instanceof AST_UnaryPrefix
2829
+ && self.right.operator === "~"
2830
+ ) {
2831
+ self = make_node(AST_Binary, self, {
2832
+ operator: "^",
2833
+ left: self.left.expression,
2834
+ right: self.right.expression
2835
+ });
2836
+ }
2837
+
2838
+ break;
2839
+ }
2840
+ }
2581
2841
  }
2582
2842
  // x && (y && z) ==> x && y && z
2583
2843
  // x || (y || z) ==> x || y || z
@@ -133,6 +133,7 @@ import { pure_prop_access_globals, is_pure_native_fn, is_pure_native_method } fr
133
133
  export const is_undeclared_ref = (node) =>
134
134
  node instanceof AST_SymbolRef && node.definition().undeclared;
135
135
 
136
+ export const bitwise_binop = makePredicate("<<< >> << & | ^ ~");
136
137
  export const lazy_op = makePredicate("&& || ??");
137
138
  export const unary_side_effects = makePredicate("delete ++ --");
138
139
 
@@ -193,6 +194,24 @@ export const unary_side_effects = makePredicate("delete ++ --");
193
194
  node.DEFMETHOD("is_number", func);
194
195
  });
195
196
 
197
+ // methods to determine if an expression is a 32 bit integer (IE results from bitwise ops, or is an integer constant fitting in that size
198
+ (function(def_is_32_bit_integer) {
199
+ def_is_32_bit_integer(AST_Node, return_false);
200
+ def_is_32_bit_integer(AST_Number, function() {
201
+ return this.value === (this.value | 0);
202
+ });
203
+ def_is_32_bit_integer(AST_UnaryPrefix, function() {
204
+ return this.operator == "~" ? this.expression.is_number()
205
+ : this.operator === "+" ? this.expression.is_32_bit_integer()
206
+ : false;
207
+ });
208
+ def_is_32_bit_integer(AST_Binary, function() {
209
+ return bitwise_binop.has(this.operator);
210
+ });
211
+ }(function (node, func) {
212
+ node.DEFMETHOD("is_32_bit_integer", func);
213
+ }));
214
+
196
215
  // methods to determine if an expression has a string result type
197
216
  (function(def_is_string) {
198
217
  def_is_string(AST_Node, return_false);
@@ -807,6 +826,37 @@ export function is_lhs(node, parent) {
807
826
  });
808
827
  });
809
828
 
829
+ (function (def_bitwise_negate) {
830
+ function basic_negation(exp) {
831
+ return make_node(AST_UnaryPrefix, exp, {
832
+ operator: "~",
833
+ expression: exp
834
+ });
835
+ }
836
+
837
+ def_bitwise_negate(AST_Node, function() {
838
+ return basic_negation(this);
839
+ });
840
+
841
+ def_bitwise_negate(AST_Number, function() {
842
+ const neg = ~this.value;
843
+ if (neg.toString().length > this.value.toString().length) {
844
+ return basic_negation(this);
845
+ }
846
+ return make_node(AST_Number, this, { value: neg });
847
+ });
848
+
849
+ def_bitwise_negate(AST_UnaryPrefix, function(in_32_bit_context) {
850
+ if (this.operator == "~" && (in_32_bit_context || this.expression.is_32_bit_integer())) {
851
+ return this.expression;
852
+ } else {
853
+ return basic_negation(this);
854
+ }
855
+ });
856
+ })(function (node, func) {
857
+ node.DEFMETHOD("bitwise_negate", func);
858
+ });
859
+
810
860
  // Is the callee of this function pure?
811
861
  var global_pure_fns = makePredicate("Boolean decodeURI decodeURIComponent Date encodeURI encodeURIComponent Error escape EvalError isFinite isNaN Number Object parseFloat parseInt RangeError ReferenceError String SyntaxError TypeError unescape URIError");
812
862
  AST_Call.DEFMETHOD("is_callee_pure", function(compressor) {
package/lib/size.js CHANGED
@@ -406,8 +406,8 @@ AST_Class.prototype._size = function () {
406
406
  };
407
407
 
408
408
  AST_ClassStaticBlock.prototype._size = function () {
409
- // "class{}" + semicolons
410
- return 7 + list_overhead(this.body);
409
+ // "static{}" + semicolons
410
+ return 8 + list_overhead(this.body);
411
411
  };
412
412
 
413
413
  AST_ClassProperty.prototype._size = function () {
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.27.2",
7
+ "version": "5.28.0",
8
8
  "engines": {
9
9
  "node": ">=10"
10
10
  },