terser 5.16.9 → 5.17.1

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,13 @@
1
1
  # Changelog
2
2
 
3
+ ## v5.17.1
4
+ - Fix evaluating `.length` when the source array might've been mutated
5
+
6
+ ## v5.17.0
7
+ - Drop vestigial `= undefined` default argument in IIFE calls (#1366)
8
+ - Evaluate known arrays' `.length` property when statically determinable
9
+ - Add `@__KEY__` annotation to mangle string literals (#1365)
10
+
3
11
  ## v5.16.9
4
12
  - Fix parentheses in output of optional chains (`a?.b`) (#1374)
5
13
  - More documentation on source maps (#1368)
package/README.md CHANGED
@@ -1188,6 +1188,7 @@ Annotations in Terser are a way to tell it to treat a certain function call diff
1188
1188
  * `/*@__INLINE__*/` - forces a function to be inlined somewhere.
1189
1189
  * `/*@__NOINLINE__*/` - Makes sure the called function is not inlined into the call site.
1190
1190
  * `/*@__PURE__*/` - Marks a function call as pure. That means, it can safely be dropped.
1191
+ * `/*@__KEY__*/` - Marks a string literal as a property to also mangle it when mangling properties.
1191
1192
 
1192
1193
  You can use either a `@` sign at the start, or a `#`.
1193
1194
 
@@ -1201,6 +1202,9 @@ function_always_inlined_here()
1201
1202
  function_cant_be_inlined_into_here()
1202
1203
 
1203
1204
  const x = /*#__PURE__*/i_am_dropped_if_x_is_not_used()
1205
+
1206
+ function lookup(object, key) { return object[key]; }
1207
+ lookup({ i_will_be_mangled_too: "bar" }, /*@__KEY__*/ "i_will_be_mangled_too");
1204
1208
  ```
1205
1209
 
1206
1210
  ### ESTree / SpiderMonkey AST
@@ -264,6 +264,10 @@ function set_annotation(node, annotation) {
264
264
  node._annotations |= annotation;
265
265
  }
266
266
 
267
+ function clear_annotation(node, annotation) {
268
+ node._annotations &= ~annotation;
269
+ }
270
+
267
271
  /***********************************************************************
268
272
 
269
273
  A JavaScript tokenizer / parser / beautifier / compressor.
@@ -2368,6 +2372,7 @@ function parse($TEXT, options) {
2368
2372
  value : tok.value,
2369
2373
  quote : tok.quote
2370
2374
  });
2375
+ annotate(ret);
2371
2376
  break;
2372
2377
  case "regexp":
2373
2378
  const [_, source, flags] = tok.value.match(/^\/(.*)\/(\w*)$/);
@@ -3254,6 +3259,10 @@ function parse($TEXT, options) {
3254
3259
  set_annotation(node, _NOINLINE);
3255
3260
  break;
3256
3261
  }
3262
+ if (/[@#]__KEY__/.test(comment.value)) {
3263
+ set_annotation(node, _KEY);
3264
+ break;
3265
+ }
3257
3266
  }
3258
3267
  }
3259
3268
  }
@@ -4506,11 +4515,14 @@ var AST_Destructuring = DEFNODE("Destructuring", "names is_array", function AST_
4506
4515
  },
4507
4516
  all_symbols: function() {
4508
4517
  var out = [];
4509
- this.walk(new TreeWalker(function (node) {
4510
- if (node instanceof AST_Symbol) {
4518
+ walk(this, node => {
4519
+ if (node instanceof AST_SymbolDeclaration) {
4511
4520
  out.push(node);
4512
4521
  }
4513
- }));
4522
+ if (node instanceof AST_Lambda) {
4523
+ return true;
4524
+ }
4525
+ });
4514
4526
  return out;
4515
4527
  }
4516
4528
  });
@@ -5041,6 +5053,13 @@ var AST_VarDef = DEFNODE("VarDef", "name value", function AST_VarDef(props) {
5041
5053
  if (this.value) push(this.value);
5042
5054
  push(this.name);
5043
5055
  },
5056
+ declarations_as_names() {
5057
+ if (this.name instanceof AST_SymbolDeclaration) {
5058
+ return [this];
5059
+ } else {
5060
+ return this.name.all_symbols();
5061
+ }
5062
+ }
5044
5063
  });
5045
5064
 
5046
5065
  var AST_NameMapping = DEFNODE("NameMapping", "foreign_name name", function AST_NameMapping(props) {
@@ -6363,6 +6382,7 @@ var AST_String = DEFNODE("String", "value quote", function AST_String(props) {
6363
6382
  this.quote = props.quote;
6364
6383
  this.start = props.start;
6365
6384
  this.end = props.end;
6385
+ this._annotations = props._annotations;
6366
6386
  }
6367
6387
 
6368
6388
  this.flags = 0;
@@ -6735,6 +6755,7 @@ class TreeTransformer extends TreeWalker {
6735
6755
  const _PURE = 0b00000001;
6736
6756
  const _INLINE = 0b00000010;
6737
6757
  const _NOINLINE = 0b00000100;
6758
+ const _KEY = 0b00001000;
6738
6759
 
6739
6760
  /***********************************************************************
6740
6761
 
@@ -14478,6 +14499,25 @@ const regexp_flags = new Set([
14478
14499
  def_eval(AST_PropAccess, function (compressor, depth) {
14479
14500
  let obj = this.expression._eval(compressor, depth + 1);
14480
14501
  if (obj === nullish || (this.optional && obj == null)) return nullish;
14502
+
14503
+ // `.length` of strings and arrays is always safe
14504
+ if (this.property === "length") {
14505
+ if (typeof obj === "string") {
14506
+ return obj.length;
14507
+ }
14508
+
14509
+ const is_spreadless_array =
14510
+ obj instanceof AST_Array
14511
+ && obj.elements.every(el => !(el instanceof AST_Expansion));
14512
+
14513
+ if (
14514
+ is_spreadless_array
14515
+ && obj.elements.every(el => !el.has_side_effects(compressor))
14516
+ ) {
14517
+ return obj.elements.length;
14518
+ }
14519
+ }
14520
+
14481
14521
  if (compressor.option("unsafe")) {
14482
14522
  var key = this.property;
14483
14523
  if (key instanceof AST_Node) {
@@ -14485,9 +14525,9 @@ def_eval(AST_PropAccess, function (compressor, depth) {
14485
14525
  if (key === this.property)
14486
14526
  return this;
14487
14527
  }
14528
+
14488
14529
  var exp = this.expression;
14489
14530
  if (is_undeclared_ref(exp)) {
14490
-
14491
14531
  var aa;
14492
14532
  var first_arg = exp.name === "hasOwnProperty"
14493
14533
  && key === "call"
@@ -16100,12 +16140,31 @@ function is_lhs_read_only(lhs) {
16100
16140
  return false;
16101
16141
  }
16102
16142
 
16103
- // Remove code which we know is unreachable.
16143
+ /** var a = 1 --> var a*/
16144
+ function remove_initializers(var_statement) {
16145
+ var decls = [];
16146
+ var_statement.definitions.forEach(function(def) {
16147
+ if (def.name instanceof AST_SymbolDeclaration) {
16148
+ def.value = null;
16149
+ decls.push(def);
16150
+ } else {
16151
+ def.declarations_as_names().forEach(name => {
16152
+ decls.push(make_node(AST_VarDef, def, {
16153
+ name,
16154
+ value: null
16155
+ }));
16156
+ });
16157
+ }
16158
+ });
16159
+ return decls.length ? make_node(AST_Var, var_statement, { definitions: decls }) : null;
16160
+ }
16161
+
16162
+ /** Called on code which we know is unreachable, to keep elements that affect outside of it. */
16104
16163
  function trim_unreachable_code(compressor, stat, target) {
16105
16164
  walk(stat, node => {
16106
16165
  if (node instanceof AST_Var) {
16107
- node.remove_initializers();
16108
- target.push(node);
16166
+ const no_initializers = remove_initializers(node);
16167
+ if (no_initializers) target.push(no_initializers);
16109
16168
  return true;
16110
16169
  }
16111
16170
  if (
@@ -19335,26 +19394,6 @@ def_optimize(AST_Try, function(self, compressor) {
19335
19394
  return self;
19336
19395
  });
19337
19396
 
19338
- AST_Definitions.DEFMETHOD("remove_initializers", function() {
19339
- var decls = [];
19340
- this.definitions.forEach(function(def) {
19341
- if (def.name instanceof AST_SymbolDeclaration) {
19342
- def.value = null;
19343
- decls.push(def);
19344
- } else {
19345
- walk(def.name, node => {
19346
- if (node instanceof AST_SymbolDeclaration) {
19347
- decls.push(make_node(AST_VarDef, def, {
19348
- name: node,
19349
- value: null
19350
- }));
19351
- }
19352
- });
19353
- }
19354
- });
19355
- this.definitions = decls;
19356
- });
19357
-
19358
19397
  AST_Definitions.DEFMETHOD("to_assignments", function(compressor) {
19359
19398
  var reduce_vars = compressor.option("reduce_vars");
19360
19399
  var assignments = [];
@@ -20572,13 +20611,20 @@ def_optimize(AST_DefaultAssign, function(self, compressor) {
20572
20611
 
20573
20612
  // `[x = undefined] = foo` ---> `[x] = foo`
20574
20613
  // `(arg = undefined) => ...` ---> `(arg) => ...` (unless `keep_fargs`)
20575
- if (
20576
- evaluateRight === undefined &&
20577
- (compressor.parent() instanceof AST_Lambda
20578
- ? compressor.option("keep_fargs") === false
20579
- : true)
20580
- ) {
20581
- self = self.left;
20614
+ // `((arg = undefined) => ...)()` ---> `((arg) => ...)()`
20615
+ let lambda, iife;
20616
+ if (evaluateRight === undefined) {
20617
+ if (
20618
+ (lambda = compressor.parent()) instanceof AST_Lambda
20619
+ ? (
20620
+ compressor.option("keep_fargs") === false
20621
+ || (iife = compressor.parent(1)).TYPE === "Call"
20622
+ && iife.expression === lambda
20623
+ )
20624
+ : true
20625
+ ) {
20626
+ self = self.left;
20627
+ }
20582
20628
  } else if (evaluateRight !== self.right) {
20583
20629
  evaluateRight = make_node_from_constant(evaluateRight, self.right);
20584
20630
  self.right = best_of_expression(evaluateRight, self.right);
@@ -29731,6 +29777,8 @@ function mangle_properties(ast, options) {
29731
29777
  addStrings(node.args[1], add);
29732
29778
  } else if (node instanceof AST_Binary && node.operator === "in") {
29733
29779
  addStrings(node.left, add);
29780
+ } else if (node instanceof AST_String && has_annotation(node, _KEY)) {
29781
+ add(node.value);
29734
29782
  }
29735
29783
  }));
29736
29784
 
@@ -29762,6 +29810,10 @@ function mangle_properties(ast, options) {
29762
29810
  node.args[1] = mangleStrings(node.args[1]);
29763
29811
  } else if (node instanceof AST_Binary && node.operator === "in") {
29764
29812
  node.left = mangleStrings(node.left);
29813
+ } else if (node instanceof AST_String && has_annotation(node, _KEY)) {
29814
+ // Clear _KEY annotation to prevent double mangling
29815
+ clear_annotation(node, _KEY);
29816
+ node.value = mangle(node.value);
29765
29817
  }
29766
29818
  }));
29767
29819
 
@@ -29827,6 +29879,8 @@ function mangle_properties(ast, options) {
29827
29879
  var last = node.expressions.length - 1;
29828
29880
  node.expressions[last] = mangleStrings(node.expressions[last]);
29829
29881
  } else if (node instanceof AST_String) {
29882
+ // Clear _KEY annotation to prevent double mangling
29883
+ clear_annotation(node, _KEY);
29830
29884
  node.value = mangle(node.value);
29831
29885
  } else if (node instanceof AST_Conditional) {
29832
29886
  node.consequent = mangleStrings(node.consequent);
package/lib/ast.js CHANGED
@@ -905,11 +905,14 @@ var AST_Destructuring = DEFNODE("Destructuring", "names is_array", function AST_
905
905
  },
906
906
  all_symbols: function() {
907
907
  var out = [];
908
- this.walk(new TreeWalker(function (node) {
909
- if (node instanceof AST_Symbol) {
908
+ walk(this, node => {
909
+ if (node instanceof AST_SymbolDeclaration) {
910
910
  out.push(node);
911
911
  }
912
- }));
912
+ if (node instanceof AST_Lambda) {
913
+ return true;
914
+ }
915
+ });
913
916
  return out;
914
917
  }
915
918
  });
@@ -1440,6 +1443,13 @@ var AST_VarDef = DEFNODE("VarDef", "name value", function AST_VarDef(props) {
1440
1443
  if (this.value) push(this.value);
1441
1444
  push(this.name);
1442
1445
  },
1446
+ declarations_as_names() {
1447
+ if (this.name instanceof AST_SymbolDeclaration) {
1448
+ return [this];
1449
+ } else {
1450
+ return this.name.all_symbols();
1451
+ }
1452
+ }
1443
1453
  });
1444
1454
 
1445
1455
  var AST_NameMapping = DEFNODE("NameMapping", "foreign_name name", function AST_NameMapping(props) {
@@ -2762,6 +2772,7 @@ var AST_String = DEFNODE("String", "value quote", function AST_String(props) {
2762
2772
  this.quote = props.quote;
2763
2773
  this.start = props.start;
2764
2774
  this.end = props.end;
2775
+ this._annotations = props._annotations;
2765
2776
  }
2766
2777
 
2767
2778
  this.flags = 0;
@@ -3134,6 +3145,7 @@ class TreeTransformer extends TreeWalker {
3134
3145
  const _PURE = 0b00000001;
3135
3146
  const _INLINE = 0b00000010;
3136
3147
  const _NOINLINE = 0b00000100;
3148
+ const _KEY = 0b00001000;
3137
3149
 
3138
3150
  export {
3139
3151
  AST_Accessor,
@@ -3278,4 +3290,5 @@ export {
3278
3290
  _INLINE,
3279
3291
  _NOINLINE,
3280
3292
  _PURE,
3293
+ _KEY,
3281
3294
  };
@@ -352,6 +352,25 @@ const regexp_flags = new Set([
352
352
  def_eval(AST_PropAccess, function (compressor, depth) {
353
353
  let obj = this.expression._eval(compressor, depth + 1);
354
354
  if (obj === nullish || (this.optional && obj == null)) return nullish;
355
+
356
+ // `.length` of strings and arrays is always safe
357
+ if (this.property === "length") {
358
+ if (typeof obj === "string") {
359
+ return obj.length;
360
+ }
361
+
362
+ const is_spreadless_array =
363
+ obj instanceof AST_Array
364
+ && obj.elements.every(el => !(el instanceof AST_Expansion));
365
+
366
+ if (
367
+ is_spreadless_array
368
+ && obj.elements.every(el => !el.has_side_effects(compressor))
369
+ ) {
370
+ return obj.elements.length;
371
+ }
372
+ }
373
+
355
374
  if (compressor.option("unsafe")) {
356
375
  var key = this.property;
357
376
  if (key instanceof AST_Node) {
@@ -359,9 +378,9 @@ def_eval(AST_PropAccess, function (compressor, depth) {
359
378
  if (key === this.property)
360
379
  return this;
361
380
  }
381
+
362
382
  var exp = this.expression;
363
383
  if (is_undeclared_ref(exp)) {
364
-
365
384
  var aa;
366
385
  var first_arg = exp.name === "hasOwnProperty"
367
386
  && key === "call"
@@ -1545,26 +1545,6 @@ def_optimize(AST_Try, function(self, compressor) {
1545
1545
  return self;
1546
1546
  });
1547
1547
 
1548
- AST_Definitions.DEFMETHOD("remove_initializers", function() {
1549
- var decls = [];
1550
- this.definitions.forEach(function(def) {
1551
- if (def.name instanceof AST_SymbolDeclaration) {
1552
- def.value = null;
1553
- decls.push(def);
1554
- } else {
1555
- walk(def.name, node => {
1556
- if (node instanceof AST_SymbolDeclaration) {
1557
- decls.push(make_node(AST_VarDef, def, {
1558
- name: node,
1559
- value: null
1560
- }));
1561
- }
1562
- });
1563
- }
1564
- });
1565
- this.definitions = decls;
1566
- });
1567
-
1568
1548
  AST_Definitions.DEFMETHOD("to_assignments", function(compressor) {
1569
1549
  var reduce_vars = compressor.option("reduce_vars");
1570
1550
  var assignments = [];
@@ -2782,13 +2762,20 @@ def_optimize(AST_DefaultAssign, function(self, compressor) {
2782
2762
 
2783
2763
  // `[x = undefined] = foo` ---> `[x] = foo`
2784
2764
  // `(arg = undefined) => ...` ---> `(arg) => ...` (unless `keep_fargs`)
2785
- if (
2786
- evaluateRight === undefined &&
2787
- (compressor.parent() instanceof AST_Lambda
2788
- ? compressor.option("keep_fargs") === false
2789
- : true)
2790
- ) {
2791
- self = self.left;
2765
+ // `((arg = undefined) => ...)()` ---> `((arg) => ...)()`
2766
+ let lambda, iife;
2767
+ if (evaluateRight === undefined) {
2768
+ if (
2769
+ (lambda = compressor.parent()) instanceof AST_Lambda
2770
+ ? (
2771
+ compressor.option("keep_fargs") === false
2772
+ || (iife = compressor.parent(1)).TYPE === "Call"
2773
+ && iife.expression === lambda
2774
+ )
2775
+ : true
2776
+ ) {
2777
+ self = self.left;
2778
+ }
2792
2779
  } else if (evaluateRight !== self.right) {
2793
2780
  evaluateRight = make_node_from_constant(evaluateRight, self.right);
2794
2781
  self.right = best_of_expression(evaluateRight, self.right);
@@ -170,12 +170,31 @@ function is_lhs_read_only(lhs) {
170
170
  return false;
171
171
  }
172
172
 
173
- // Remove code which we know is unreachable.
173
+ /** var a = 1 --> var a*/
174
+ function remove_initializers(var_statement) {
175
+ var decls = [];
176
+ var_statement.definitions.forEach(function(def) {
177
+ if (def.name instanceof AST_SymbolDeclaration) {
178
+ def.value = null;
179
+ decls.push(def);
180
+ } else {
181
+ def.declarations_as_names().forEach(name => {
182
+ decls.push(make_node(AST_VarDef, def, {
183
+ name,
184
+ value: null
185
+ }));
186
+ });
187
+ }
188
+ });
189
+ return decls.length ? make_node(AST_Var, var_statement, { definitions: decls }) : null;
190
+ }
191
+
192
+ /** Called on code which we know is unreachable, to keep elements that affect outside of it. */
174
193
  export function trim_unreachable_code(compressor, stat, target) {
175
194
  walk(stat, node => {
176
195
  if (node instanceof AST_Var) {
177
- node.remove_initializers();
178
- target.push(node);
196
+ const no_initializers = remove_initializers(node);
197
+ if (no_initializers) target.push(no_initializers);
179
198
  return true;
180
199
  }
181
200
  if (
package/lib/parse.js CHANGED
@@ -162,7 +162,8 @@ import {
162
162
  AST_Yield,
163
163
  _INLINE,
164
164
  _NOINLINE,
165
- _PURE
165
+ _PURE,
166
+ _KEY
166
167
  } from "./ast.js";
167
168
 
168
169
  var LATEST_RAW = ""; // Only used for numbers and template strings
@@ -2225,6 +2226,7 @@ function parse($TEXT, options) {
2225
2226
  value : tok.value,
2226
2227
  quote : tok.quote
2227
2228
  });
2229
+ annotate(ret);
2228
2230
  break;
2229
2231
  case "regexp":
2230
2232
  const [_, source, flags] = tok.value.match(/^\/(.*)\/(\w*)$/);
@@ -3111,6 +3113,10 @@ function parse($TEXT, options) {
3111
3113
  set_annotation(node, _NOINLINE);
3112
3114
  break;
3113
3115
  }
3116
+ if (/[@#]__KEY__/.test(comment.value)) {
3117
+ set_annotation(node, _KEY);
3118
+ break;
3119
+ }
3114
3120
  }
3115
3121
  }
3116
3122
  }
package/lib/propmangle.js CHANGED
@@ -47,6 +47,8 @@
47
47
  import {
48
48
  defaults,
49
49
  push_uniq,
50
+ has_annotation,
51
+ clear_annotation,
50
52
  } from "./utils/index.js";
51
53
  import { base54 } from "./scope.js";
52
54
  import {
@@ -67,6 +69,7 @@ import {
67
69
  AST_Sub,
68
70
  TreeTransformer,
69
71
  TreeWalker,
72
+ _KEY,
70
73
  } from "./ast.js";
71
74
  import { domprops } from "../tools/domprops.js";
72
75
 
@@ -263,6 +266,8 @@ function mangle_properties(ast, options) {
263
266
  addStrings(node.args[1], add);
264
267
  } else if (node instanceof AST_Binary && node.operator === "in") {
265
268
  addStrings(node.left, add);
269
+ } else if (node instanceof AST_String && has_annotation(node, _KEY)) {
270
+ add(node.value);
266
271
  }
267
272
  }));
268
273
 
@@ -296,6 +301,10 @@ function mangle_properties(ast, options) {
296
301
  node.args[1] = mangleStrings(node.args[1]);
297
302
  } else if (node instanceof AST_Binary && node.operator === "in") {
298
303
  node.left = mangleStrings(node.left);
304
+ } else if (node instanceof AST_String && has_annotation(node, _KEY)) {
305
+ // Clear _KEY annotation to prevent double mangling
306
+ clear_annotation(node, _KEY);
307
+ node.value = mangle(node.value);
299
308
  }
300
309
  }));
301
310
 
@@ -361,6 +370,8 @@ function mangle_properties(ast, options) {
361
370
  var last = node.expressions.length - 1;
362
371
  node.expressions[last] = mangleStrings(node.expressions[last]);
363
372
  } else if (node instanceof AST_String) {
373
+ // Clear _KEY annotation to prevent double mangling
374
+ clear_annotation(node, _KEY);
364
375
  node.value = mangle(node.value);
365
376
  } else if (node instanceof AST_Conditional) {
366
377
  node.consequent = mangleStrings(node.consequent);
@@ -262,6 +262,10 @@ function set_annotation(node, annotation) {
262
262
  node._annotations |= annotation;
263
263
  }
264
264
 
265
+ function clear_annotation(node, annotation) {
266
+ node._annotations &= ~annotation;
267
+ }
268
+
265
269
  export {
266
270
  characters,
267
271
  defaults,
@@ -286,5 +290,6 @@ export {
286
290
  sort_regexp_flags,
287
291
  string_template,
288
292
  has_annotation,
289
- set_annotation
293
+ set_annotation,
294
+ clear_annotation,
290
295
  };
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.16.9",
7
+ "version": "5.17.1",
8
8
  "engines": {
9
9
  "node": ">=10"
10
10
  },