terser 5.32.0 → 5.34.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,15 @@
1
1
  # Changelog
2
2
 
3
+ ## v5.34.0
4
+
5
+ - internal: stop assigning properties to objects they don't belong in
6
+ - internal: run compress tests in parallel
7
+ - `drop_console`: emit an empty function if the return value of `console.METHOD(...)` may be called.
8
+
9
+ ## v5.33.0
10
+
11
+ - `reduce_vars` improved when dealing with hoisted function definitions (#1544)
12
+
3
13
  ## v5.32.0
4
14
 
5
15
  - `import("module")` can now be input and output from ESTree AST (#1557)
@@ -2,7 +2,7 @@
2
2
  typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@jridgewell/source-map')) :
3
3
  typeof define === 'function' && define.amd ? define(['exports', '@jridgewell/source-map'], factory) :
4
4
  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.Terser = {}, global.sourceMap));
5
- }(this, (function (exports, sourceMap) { 'use strict';
5
+ })(this, (function (exports, sourceMap) { 'use strict';
6
6
 
7
7
  /***********************************************************************
8
8
 
@@ -11674,7 +11674,7 @@ function redefined_catch_def(def) {
11674
11674
  }
11675
11675
  }
11676
11676
 
11677
- AST_Scope.DEFMETHOD("figure_out_scope", function(options, { parent_scope = null, toplevel = this } = {}) {
11677
+ AST_Scope.DEFMETHOD("figure_out_scope", function(options, { parent_scope = undefined, toplevel = this } = {}) {
11678
11678
  options = defaults(options, {
11679
11679
  cache: null,
11680
11680
  ie8: false,
@@ -11998,7 +11998,7 @@ AST_Scope.DEFMETHOD("add_child_scope", function (scope) {
11998
11998
  scope.parent_scope = this;
11999
11999
 
12000
12000
  // Propagate to this.uses_arguments from arrow functions
12001
- if ((scope instanceof AST_Arrow) && !this.uses_arguments) {
12001
+ if ((scope instanceof AST_Arrow) && (this instanceof AST_Lambda && !this.uses_arguments)) {
12002
12002
  this.uses_arguments = walk(scope, node => {
12003
12003
  if (
12004
12004
  node instanceof AST_SymbolRef
@@ -13065,6 +13065,23 @@ function make_sequence(orig, expressions) {
13065
13065
  });
13066
13066
  }
13067
13067
 
13068
+ function make_empty_function(self) {
13069
+ return make_node(AST_Function, self, {
13070
+ uses_arguments: false,
13071
+ argnames: [],
13072
+ body: [],
13073
+ is_generator: false,
13074
+ async: false,
13075
+ variables: new Map(),
13076
+ uses_with: false,
13077
+ uses_eval: false,
13078
+ parent_scope: null,
13079
+ enclosed: [],
13080
+ cname: 0,
13081
+ block_scope: undefined,
13082
+ });
13083
+ }
13084
+
13068
13085
  function make_node_from_constant(val, orig) {
13069
13086
  switch (typeof val) {
13070
13087
  case "string":
@@ -13291,9 +13308,9 @@ function is_reachable(scope_node, defs) {
13291
13308
  }
13292
13309
 
13293
13310
  /** Check if a ref refers to the name of a function/class it's defined within */
13294
- function is_recursive_ref(compressor, def) {
13311
+ function is_recursive_ref(tw, def) {
13295
13312
  var node;
13296
- for (var i = 0; node = compressor.parent(i); i++) {
13313
+ for (var i = 0; node = tw.parent(i); i++) {
13297
13314
  if (node instanceof AST_Lambda || node instanceof AST_Class) {
13298
13315
  var name = node.name;
13299
13316
  if (name && name.definition() === def) {
@@ -14418,6 +14435,54 @@ function is_modified(compressor, tw, node, value, level, immutable) {
14418
14435
  }
14419
14436
  }
14420
14437
 
14438
+ /**
14439
+ * Check if a node may be used by the expression it's in
14440
+ * void (0, 1, {node}, 2) -> false
14441
+ * console.log(0, {node}) -> true
14442
+ */
14443
+ function is_used_in_expression(tw) {
14444
+ for (let p = -1, node, parent; node = tw.parent(p), parent = tw.parent(p + 1); p++) {
14445
+ if (parent instanceof AST_Sequence) {
14446
+ const nth_expression = parent.expressions.indexOf(node);
14447
+ if (nth_expression !== parent.expressions.length - 1) {
14448
+ // Detect (0, x.noThis)() constructs
14449
+ const grandparent = tw.parent(p + 2);
14450
+ if (
14451
+ parent.expressions.length > 2
14452
+ || parent.expressions.length === 1
14453
+ || !requires_sequence_to_maintain_binding(grandparent, parent, parent.expressions[1])
14454
+ ) {
14455
+ return false;
14456
+ }
14457
+ return true;
14458
+ } else {
14459
+ continue;
14460
+ }
14461
+ }
14462
+ if (parent instanceof AST_Unary) {
14463
+ const op = parent.operator;
14464
+ if (op === "void") {
14465
+ return false;
14466
+ }
14467
+ if (op === "typeof" || op === "+" || op === "-" || op === "!" || op === "~") {
14468
+ continue;
14469
+ }
14470
+ }
14471
+ if (
14472
+ parent instanceof AST_SimpleStatement
14473
+ || parent instanceof AST_LabeledStatement
14474
+ ) {
14475
+ return false;
14476
+ }
14477
+ if (parent instanceof AST_Scope) {
14478
+ return false;
14479
+ }
14480
+ return true;
14481
+ }
14482
+
14483
+ return true;
14484
+ }
14485
+
14421
14486
  /***********************************************************************
14422
14487
 
14423
14488
  A JavaScript tokenizer / parser / beautifier / compressor.
@@ -15672,45 +15737,6 @@ AST_Scope.DEFMETHOD("drop_unused", function(compressor) {
15672
15737
  }
15673
15738
  });
15674
15739
 
15675
- /**
15676
- * Check if a node may be used by the expression it's in
15677
- * void (0, 1, {node}, 2) -> false
15678
- * console.log(0, {node}) -> true
15679
- */
15680
- function is_used_in_expression(tw) {
15681
- for (let p = -1, node, parent; node = tw.parent(p), parent = tw.parent(p + 1); p++) {
15682
- if (parent instanceof AST_Sequence) {
15683
- const nth_expression = parent.expressions.indexOf(node);
15684
- if (nth_expression !== parent.expressions.length - 1) {
15685
- // Detect (0, x.noThis)() constructs
15686
- const grandparent = tw.parent(p + 2);
15687
- if (
15688
- parent.expressions.length > 2
15689
- || parent.expressions.length === 1
15690
- || !requires_sequence_to_maintain_binding(grandparent, parent, parent.expressions[1])
15691
- ) {
15692
- return false;
15693
- }
15694
- return true;
15695
- } else {
15696
- continue;
15697
- }
15698
- }
15699
- if (parent instanceof AST_Unary) {
15700
- const op = parent.operator;
15701
- if (op === "void") {
15702
- return false;
15703
- }
15704
- if (op === "typeof" || op === "+" || op === "-" || op === "!" || op === "~") {
15705
- continue;
15706
- }
15707
- }
15708
- return true;
15709
- }
15710
-
15711
- return true;
15712
- }
15713
-
15714
15740
  /***********************************************************************
15715
15741
 
15716
15742
  A JavaScript tokenizer / parser / beautifier / compressor.
@@ -16147,28 +16173,50 @@ function mark_lambda(tw, descend, compressor) {
16147
16173
  * // use defined_after
16148
16174
  * }
16149
16175
  *
16150
- * This function is called on the parent to handle this issue.
16176
+ * Or even indirectly:
16177
+ *
16178
+ * B();
16179
+ * var defined_after = true;
16180
+ * function A() {
16181
+ * // use defined_after
16182
+ * }
16183
+ * function B() {
16184
+ * A();
16185
+ * }
16186
+ *
16187
+ * Access a variable before declaration will either throw a ReferenceError
16188
+ * (if the variable is declared with `let` or `const`),
16189
+ * or get an `undefined` (if the variable is declared with `var`).
16190
+ *
16191
+ * If the variable is inlined into the function, the behavior will change.
16192
+ *
16193
+ * This function is called on the parent to disallow inlining of such variables,
16151
16194
  */
16152
16195
  function handle_defined_after_hoist(parent) {
16153
16196
  const defuns = [];
16154
16197
  walk(parent, node => {
16155
16198
  if (node === parent) return;
16156
- if (node instanceof AST_Defun) defuns.push(node);
16199
+ if (node instanceof AST_Defun) {
16200
+ defuns.push(node);
16201
+ return true;
16202
+ }
16157
16203
  if (
16158
16204
  node instanceof AST_Scope
16159
16205
  || node instanceof AST_SimpleStatement
16160
16206
  ) return true;
16161
16207
  });
16162
16208
 
16209
+ // `defun` id to array of `defun` it uses
16210
+ const defun_dependencies_map = new Map();
16211
+ // `defun` id to array of enclosing `def` that are used by the function
16212
+ const dependencies_map = new Map();
16213
+ // all symbol ids that will be tracked for read/write
16163
16214
  const symbols_of_interest = new Set();
16164
16215
  const defuns_of_interest = new Set();
16165
- const potential_conflicts = [];
16166
16216
 
16167
16217
  for (const defun of defuns) {
16168
16218
  const fname_def = defun.name.definition();
16169
- const found_self_ref_in_other_defuns = defuns.some(
16170
- d => d !== defun && d.enclosed.indexOf(fname_def) !== -1
16171
- );
16219
+ const enclosing_defs = [];
16172
16220
 
16173
16221
  for (const def of defun.enclosed) {
16174
16222
  if (
@@ -16179,93 +16227,107 @@ function handle_defined_after_hoist(parent) {
16179
16227
  continue;
16180
16228
  }
16181
16229
 
16182
- // defun is hoisted, so always safe
16230
+ symbols_of_interest.add(def.id);
16231
+
16232
+ // found a reference to another function
16183
16233
  if (
16184
16234
  def.assignments === 0
16185
16235
  && def.orig.length === 1
16186
16236
  && def.orig[0] instanceof AST_SymbolDefun
16187
16237
  ) {
16188
- continue;
16189
- }
16238
+ defuns_of_interest.add(def.id);
16239
+ symbols_of_interest.add(def.id);
16240
+
16241
+ defuns_of_interest.add(fname_def.id);
16242
+ symbols_of_interest.add(fname_def.id);
16243
+
16244
+ if (!defun_dependencies_map.has(fname_def.id)) {
16245
+ defun_dependencies_map.set(fname_def.id, []);
16246
+ }
16247
+ defun_dependencies_map.get(fname_def.id).push(def.id);
16190
16248
 
16191
- if (found_self_ref_in_other_defuns) {
16192
- def.fixed = false;
16193
16249
  continue;
16194
16250
  }
16195
16251
 
16196
- // for the slower checks below this loop
16197
- potential_conflicts.push({ defun, def, fname_def });
16198
- symbols_of_interest.add(def.id);
16252
+ enclosing_defs.push(def);
16253
+ }
16254
+
16255
+ if (enclosing_defs.length) {
16256
+ dependencies_map.set(fname_def.id, enclosing_defs);
16257
+ defuns_of_interest.add(fname_def.id);
16199
16258
  symbols_of_interest.add(fname_def.id);
16200
- defuns_of_interest.add(defun);
16201
16259
  }
16202
16260
  }
16203
16261
 
16204
- // linearize all symbols, and locate defs that are read after the defun
16205
- if (potential_conflicts.length) {
16206
- // All "symbols of interest", that is, defuns or defs, that we found.
16207
- // These are placed in order so we can check which is after which.
16208
- const found_symbols = [];
16209
- // Indices of `found_symbols` which are writes
16210
- const found_symbol_writes = new Set();
16211
- // Defun ranges are recorded because we don't care if a function uses the def internally
16212
- const defun_ranges = new Map();
16262
+ // No defuns use outside constants
16263
+ if (!dependencies_map.size) {
16264
+ return;
16265
+ }
16213
16266
 
16214
- let tw;
16215
- parent.walk((tw = new TreeWalker((node, descend) => {
16216
- if (node instanceof AST_Defun && defuns_of_interest.has(node)) {
16217
- const start = found_symbols.length;
16218
- descend();
16219
- const end = found_symbols.length;
16267
+ // Increment to count "symbols of interest" (defuns or defs) that we found.
16268
+ // These are tracked in AST order so we can check which is after which.
16269
+ let symbol_index = 1;
16270
+ // Map a defun ID to its first read (a `symbol_index`)
16271
+ const defun_first_read_map = new Map();
16272
+ // Map a symbol ID to its last write (a `symbol_index`)
16273
+ const symbol_last_write_map = new Map();
16220
16274
 
16221
- defun_ranges.set(node, { start, end });
16222
- return true;
16223
- }
16224
- // if we found a defun on the list, mark IN_DEFUN=id and descend
16275
+ walk_parent(parent, (node, walk_info) => {
16276
+ if (node instanceof AST_Symbol && node.thedef) {
16277
+ const id = node.definition().id;
16225
16278
 
16226
- if (node instanceof AST_Symbol && node.thedef) {
16227
- const id = node.definition().id;
16228
- if (symbols_of_interest.has(id)) {
16229
- if (node instanceof AST_SymbolDeclaration || is_lhs(node, tw)) {
16230
- found_symbol_writes.add(found_symbols.length);
16231
- }
16232
- found_symbols.push(id);
16279
+ symbol_index++;
16280
+
16281
+ // Track last-writes to symbols
16282
+ if (symbols_of_interest.has(id)) {
16283
+ if (node instanceof AST_SymbolDeclaration || is_lhs(node, walk_info.parent())) {
16284
+ symbol_last_write_map.set(id, symbol_index);
16233
16285
  }
16234
16286
  }
16235
- })));
16236
16287
 
16237
- for (const { def, defun, fname_def } of potential_conflicts) {
16238
- const defun_range = defun_ranges.get(defun);
16288
+ // Track first-reads of defuns (refined later)
16289
+ if (defuns_of_interest.has(id)) {
16290
+ if (!defun_first_read_map.has(id) && !is_recursive_ref(walk_info, id)) {
16291
+ defun_first_read_map.set(id, symbol_index);
16292
+ }
16293
+ }
16294
+ }
16295
+ });
16239
16296
 
16240
- // find the index in `found_symbols`, with some special rules:
16241
- const find = (sym_id, starting_at = 0, must_be_write = false) => {
16242
- let index = starting_at;
16297
+ // Refine `defun_first_read_map` to be as high as possible
16298
+ for (const [defun, defun_first_read] of defun_first_read_map) {
16299
+ // Update all depdencies of `defun`
16300
+ const queue = new Set(defun_dependencies_map.get(defun));
16301
+ for (const enclosed_defun of queue) {
16302
+ let enclosed_defun_first_read = defun_first_read_map.get(enclosed_defun);
16303
+ if (enclosed_defun_first_read != null && enclosed_defun_first_read < defun_first_read) {
16304
+ continue;
16305
+ }
16243
16306
 
16244
- for (;;) {
16245
- index = found_symbols.indexOf(sym_id, index);
16307
+ defun_first_read_map.set(enclosed_defun, defun_first_read);
16246
16308
 
16247
- if (index === -1) {
16248
- break;
16249
- } else if (index >= defun_range.start && index < defun_range.end) {
16250
- index = defun_range.end;
16251
- continue;
16252
- } else if (must_be_write && !found_symbol_writes.has(index)) {
16253
- index++;
16254
- continue;
16255
- } else {
16256
- break;
16257
- }
16258
- }
16309
+ for (const enclosed_enclosed_defun of defun_dependencies_map.get(enclosed_defun) || []) {
16310
+ queue.add(enclosed_enclosed_defun);
16311
+ }
16312
+ }
16313
+ }
16259
16314
 
16260
- return index;
16261
- };
16315
+ // ensure write-then-read order, otherwise clear `fixed`
16316
+ // This is safe because last-writes (found_symbol_writes) are assumed to be as late as possible, and first-reads (defun_first_read_map) are assumed to be as early as possible.
16317
+ for (const [defun, defs] of dependencies_map) {
16318
+ const defun_first_read = defun_first_read_map.get(defun);
16319
+ if (defun_first_read === undefined) {
16320
+ continue;
16321
+ }
16262
16322
 
16263
- const read_defun_at = find(fname_def.id);
16264
- const wrote_def_at = find(def.id, read_defun_at + 1, true);
16323
+ for (const def of defs) {
16324
+ if (def.fixed === false) {
16325
+ continue;
16326
+ }
16265
16327
 
16266
- const wrote_def_after_reading_defun = read_defun_at != -1 && wrote_def_at != -1 && wrote_def_at > read_defun_at;
16328
+ let def_last_write = symbol_last_write_map.get(def.id) || 0;
16267
16329
 
16268
- if (wrote_def_after_reading_defun) {
16330
+ if (defun_first_read < def_last_write) {
16269
16331
  def.fixed = false;
16270
16332
  }
16271
16333
  }
@@ -18899,9 +18961,8 @@ def_optimize(AST_Node, function(self) {
18899
18961
  });
18900
18962
 
18901
18963
  AST_Toplevel.DEFMETHOD("drop_console", function(options) {
18902
- var isArray = Array.isArray(options);
18903
-
18904
- return this.transform(new TreeTransformer(function(self) {
18964
+ const isArray = Array.isArray(options);
18965
+ const tt = new TreeTransformer(function(self) {
18905
18966
  if (self.TYPE !== "Call") {
18906
18967
  return;
18907
18968
  }
@@ -18912,18 +18973,35 @@ AST_Toplevel.DEFMETHOD("drop_console", function(options) {
18912
18973
  return;
18913
18974
  }
18914
18975
 
18915
- if (isArray && options.indexOf(exp.property) === -1) {
18976
+ if (isArray && !options.includes(exp.property)) {
18916
18977
  return;
18917
18978
  }
18918
18979
 
18919
18980
  var name = exp.expression;
18981
+ var depth = 2;
18920
18982
  while (name.expression) {
18921
18983
  name = name.expression;
18984
+ depth++;
18922
18985
  }
18986
+
18923
18987
  if (is_undeclared_ref(name) && name.name == "console") {
18924
- return make_node(AST_Undefined, self);
18988
+ if (
18989
+ depth === 3
18990
+ && !["call", "apply"].includes(exp.property)
18991
+ && is_used_in_expression(tt)
18992
+ ) {
18993
+ // a (used) call to Function.prototype methods (eg: console.log.bind(console))
18994
+ // but not .call and .apply which would also return undefined.
18995
+ exp.expression = make_empty_function(self);
18996
+ set_flag(exp.expression, SQUEEZED);
18997
+ self.args = [];
18998
+ } else {
18999
+ return make_node(AST_Undefined, self);
19000
+ }
18925
19001
  }
18926
- }));
19002
+ });
19003
+
19004
+ return this.transform(tt);
18927
19005
  });
18928
19006
 
18929
19007
  AST_Node.DEFMETHOD("equivalent_to", function(node) {
@@ -20300,10 +20378,7 @@ def_optimize(AST_Call, function(self, compressor) {
20300
20378
  && is_undeclared_ref(exp)
20301
20379
  && exp.name == "Function") {
20302
20380
  // new Function() => function(){}
20303
- if (self.args.length == 0) return make_node(AST_Function, self, {
20304
- argnames: [],
20305
- body: []
20306
- }).optimize(compressor);
20381
+ if (self.args.length == 0) return make_empty_function(self).optimize(compressor);
20307
20382
  if (self.args.every((x) => x instanceof AST_String)) {
20308
20383
  // quite a corner-case, but we can handle it:
20309
20384
  // https://github.com/mishoo/UglifyJS2/issues/203
@@ -22063,10 +22138,7 @@ def_optimize(AST_Dot, function(self, compressor) {
22063
22138
  });
22064
22139
  break;
22065
22140
  case "Function":
22066
- self.expression = make_node(AST_Function, self.expression, {
22067
- argnames: [],
22068
- body: []
22069
- });
22141
+ self.expression = make_empty_function(self.expression);
22070
22142
  break;
22071
22143
  case "Number":
22072
22144
  self.expression = make_node(AST_Number, self.expression, {
@@ -31611,7 +31683,9 @@ function mangle_properties(ast, options, annotated_props = find_annotated_props(
31611
31683
  } else if (node instanceof AST_ObjectProperty) {
31612
31684
  // setter, getter, method or class field
31613
31685
  if (!keep_quoted || !node.quote) {
31614
- node.key.name = mangle(node.key.name);
31686
+ if (!node.computed_key()) {
31687
+ node.key.name = mangle(node.key.name);
31688
+ }
31615
31689
  }
31616
31690
  } else if (node instanceof AST_Dot) {
31617
31691
  if (!keep_quoted || !node.quote) {
@@ -31988,7 +32062,7 @@ function* minify_sync_or_async(files, options, _fs_module) {
31988
32062
  if (node.block_scope) {
31989
32063
  node.block_scope.variables = undefined;
31990
32064
  node.block_scope.enclosed = undefined;
31991
- node.parent_scope = undefined;
32065
+ node.block_scope.parent_scope = undefined;
31992
32066
  }
31993
32067
  });
31994
32068
  }
@@ -32580,4 +32654,4 @@ exports._run_cli = run_cli;
32580
32654
  exports.minify = minify;
32581
32655
  exports.minify_sync = minify_sync;
32582
32656
 
32583
- })));
32657
+ }));
@@ -107,6 +107,23 @@ export function make_sequence(orig, expressions) {
107
107
  });
108
108
  }
109
109
 
110
+ export function make_empty_function(self) {
111
+ return make_node(AST_Function, self, {
112
+ uses_arguments: false,
113
+ argnames: [],
114
+ body: [],
115
+ is_generator: false,
116
+ async: false,
117
+ variables: new Map(),
118
+ uses_with: false,
119
+ uses_eval: false,
120
+ parent_scope: null,
121
+ enclosed: [],
122
+ cname: 0,
123
+ block_scope: undefined,
124
+ });
125
+ }
126
+
110
127
  export function make_node_from_constant(val, orig) {
111
128
  switch (typeof val) {
112
129
  case "string":
@@ -333,9 +350,9 @@ export function is_reachable(scope_node, defs) {
333
350
  }
334
351
 
335
352
  /** Check if a ref refers to the name of a function/class it's defined within */
336
- export function is_recursive_ref(compressor, def) {
353
+ export function is_recursive_ref(tw, def) {
337
354
  var node;
338
- for (var i = 0; node = compressor.parent(i); i++) {
355
+ for (var i = 0; node = tw.parent(i); i++) {
339
356
  if (node instanceof AST_Lambda || node instanceof AST_Class) {
340
357
  var name = node.name;
341
358
  if (name && name.definition() === def) {
@@ -103,8 +103,8 @@ import {
103
103
  is_empty,
104
104
  is_ref_of,
105
105
  can_be_evicted_from_block,
106
- requires_sequence_to_maintain_binding,
107
106
  } from "./common.js";
107
+ import { is_used_in_expression } from "./inference.js";
108
108
 
109
109
  const r_keep_assign = /keep_assign/;
110
110
 
@@ -503,42 +503,3 @@ AST_Scope.DEFMETHOD("drop_unused", function(compressor) {
503
503
  }
504
504
  }
505
505
  });
506
-
507
- /**
508
- * Check if a node may be used by the expression it's in
509
- * void (0, 1, {node}, 2) -> false
510
- * console.log(0, {node}) -> true
511
- */
512
- function is_used_in_expression(tw) {
513
- for (let p = -1, node, parent; node = tw.parent(p), parent = tw.parent(p + 1); p++) {
514
- if (parent instanceof AST_Sequence) {
515
- const nth_expression = parent.expressions.indexOf(node);
516
- if (nth_expression !== parent.expressions.length - 1) {
517
- // Detect (0, x.noThis)() constructs
518
- const grandparent = tw.parent(p + 2);
519
- if (
520
- parent.expressions.length > 2
521
- || parent.expressions.length === 1
522
- || !requires_sequence_to_maintain_binding(grandparent, parent, parent.expressions[1])
523
- ) {
524
- return false;
525
- }
526
- return true;
527
- } else {
528
- continue;
529
- }
530
- }
531
- if (parent instanceof AST_Unary) {
532
- const op = parent.operator;
533
- if (op === "void") {
534
- return false;
535
- }
536
- if (op === "typeof" || op === "+" || op === "-" || op === "!" || op === "~") {
537
- continue;
538
- }
539
- }
540
- return true;
541
- }
542
-
543
- return true;
544
- }
@@ -176,6 +176,7 @@ import {
176
176
  is_undefined,
177
177
  is_lhs,
178
178
  aborts,
179
+ is_used_in_expression,
179
180
  } from "./inference.js";
180
181
  import {
181
182
  SQUEEZED,
@@ -195,6 +196,7 @@ import {
195
196
  make_sequence,
196
197
  best_of,
197
198
  best_of_expression,
199
+ make_empty_function,
198
200
  make_node_from_constant,
199
201
  merge_sequence,
200
202
  get_simple_key,
@@ -510,9 +512,8 @@ def_optimize(AST_Node, function(self) {
510
512
  });
511
513
 
512
514
  AST_Toplevel.DEFMETHOD("drop_console", function(options) {
513
- var isArray = Array.isArray(options);
514
-
515
- return this.transform(new TreeTransformer(function(self) {
515
+ const isArray = Array.isArray(options);
516
+ const tt = new TreeTransformer(function(self) {
516
517
  if (self.TYPE !== "Call") {
517
518
  return;
518
519
  }
@@ -523,18 +524,35 @@ AST_Toplevel.DEFMETHOD("drop_console", function(options) {
523
524
  return;
524
525
  }
525
526
 
526
- if (isArray && options.indexOf(exp.property) === -1) {
527
+ if (isArray && !options.includes(exp.property)) {
527
528
  return;
528
529
  }
529
530
 
530
531
  var name = exp.expression;
532
+ var depth = 2;
531
533
  while (name.expression) {
532
534
  name = name.expression;
535
+ depth++;
533
536
  }
537
+
534
538
  if (is_undeclared_ref(name) && name.name == "console") {
535
- return make_node(AST_Undefined, self);
539
+ if (
540
+ depth === 3
541
+ && !["call", "apply"].includes(exp.property)
542
+ && is_used_in_expression(tt)
543
+ ) {
544
+ // a (used) call to Function.prototype methods (eg: console.log.bind(console))
545
+ // but not .call and .apply which would also return undefined.
546
+ exp.expression = make_empty_function(self);
547
+ set_flag(exp.expression, SQUEEZED);
548
+ self.args = [];
549
+ } else {
550
+ return make_node(AST_Undefined, self);
551
+ }
536
552
  }
537
- }));
553
+ });
554
+
555
+ return this.transform(tt);
538
556
  });
539
557
 
540
558
  AST_Node.DEFMETHOD("equivalent_to", function(node) {
@@ -1911,10 +1929,7 @@ def_optimize(AST_Call, function(self, compressor) {
1911
1929
  && is_undeclared_ref(exp)
1912
1930
  && exp.name == "Function") {
1913
1931
  // new Function() => function(){}
1914
- if (self.args.length == 0) return make_node(AST_Function, self, {
1915
- argnames: [],
1916
- body: []
1917
- }).optimize(compressor);
1932
+ if (self.args.length == 0) return make_empty_function(self).optimize(compressor);
1918
1933
  if (self.args.every((x) => x instanceof AST_String)) {
1919
1934
  // quite a corner-case, but we can handle it:
1920
1935
  // https://github.com/mishoo/UglifyJS2/issues/203
@@ -3674,10 +3689,7 @@ def_optimize(AST_Dot, function(self, compressor) {
3674
3689
  });
3675
3690
  break;
3676
3691
  case "Function":
3677
- self.expression = make_node(AST_Function, self.expression, {
3678
- argnames: [],
3679
- body: []
3680
- });
3692
+ self.expression = make_empty_function(self.expression);
3681
3693
  break;
3682
3694
  case "Number":
3683
3695
  self.expression = make_node(AST_Number, self.expression, {
@@ -121,7 +121,7 @@ import {
121
121
  member,
122
122
  has_annotation,
123
123
  } from "../utils/index.js";
124
- import { make_sequence, best_of_expression, read_property } from "./common.js";
124
+ import { make_sequence, best_of_expression, read_property, requires_sequence_to_maintain_binding } from "./common.js";
125
125
 
126
126
  import { INLINED, UNDEFINED, has_flag } from "./compressor-flags.js";
127
127
  import { pure_prop_access_globals, is_pure_native_fn, is_pure_native_method } from "./native-objects.js";
@@ -982,3 +982,51 @@ export function is_modified(compressor, tw, node, value, level, immutable) {
982
982
  return !immutable && is_modified(compressor, tw, parent, prop, level + 1);
983
983
  }
984
984
  }
985
+
986
+ /**
987
+ * Check if a node may be used by the expression it's in
988
+ * void (0, 1, {node}, 2) -> false
989
+ * console.log(0, {node}) -> true
990
+ */
991
+ export function is_used_in_expression(tw) {
992
+ for (let p = -1, node, parent; node = tw.parent(p), parent = tw.parent(p + 1); p++) {
993
+ if (parent instanceof AST_Sequence) {
994
+ const nth_expression = parent.expressions.indexOf(node);
995
+ if (nth_expression !== parent.expressions.length - 1) {
996
+ // Detect (0, x.noThis)() constructs
997
+ const grandparent = tw.parent(p + 2);
998
+ if (
999
+ parent.expressions.length > 2
1000
+ || parent.expressions.length === 1
1001
+ || !requires_sequence_to_maintain_binding(grandparent, parent, parent.expressions[1])
1002
+ ) {
1003
+ return false;
1004
+ }
1005
+ return true;
1006
+ } else {
1007
+ continue;
1008
+ }
1009
+ }
1010
+ if (parent instanceof AST_Unary) {
1011
+ const op = parent.operator;
1012
+ if (op === "void") {
1013
+ return false;
1014
+ }
1015
+ if (op === "typeof" || op === "+" || op === "-" || op === "!" || op === "~") {
1016
+ continue;
1017
+ }
1018
+ }
1019
+ if (
1020
+ parent instanceof AST_SimpleStatement
1021
+ || parent instanceof AST_LabeledStatement
1022
+ ) {
1023
+ return false;
1024
+ }
1025
+ if (parent instanceof AST_Scope) {
1026
+ return false;
1027
+ }
1028
+ return true;
1029
+ }
1030
+
1031
+ return true;
1032
+ }
@@ -94,8 +94,7 @@ import {
94
94
 
95
95
  walk,
96
96
  walk_body,
97
-
98
- TreeWalker,
97
+ walk_parent,
99
98
  } from "../ast.js";
100
99
  import { HOP, make_node, noop } from "../utils/index.js";
101
100
 
@@ -496,28 +495,50 @@ function mark_lambda(tw, descend, compressor) {
496
495
  * // use defined_after
497
496
  * }
498
497
  *
499
- * This function is called on the parent to handle this issue.
498
+ * Or even indirectly:
499
+ *
500
+ * B();
501
+ * var defined_after = true;
502
+ * function A() {
503
+ * // use defined_after
504
+ * }
505
+ * function B() {
506
+ * A();
507
+ * }
508
+ *
509
+ * Access a variable before declaration will either throw a ReferenceError
510
+ * (if the variable is declared with `let` or `const`),
511
+ * or get an `undefined` (if the variable is declared with `var`).
512
+ *
513
+ * If the variable is inlined into the function, the behavior will change.
514
+ *
515
+ * This function is called on the parent to disallow inlining of such variables,
500
516
  */
501
517
  function handle_defined_after_hoist(parent) {
502
518
  const defuns = [];
503
519
  walk(parent, node => {
504
520
  if (node === parent) return;
505
- if (node instanceof AST_Defun) defuns.push(node);
521
+ if (node instanceof AST_Defun) {
522
+ defuns.push(node);
523
+ return true;
524
+ }
506
525
  if (
507
526
  node instanceof AST_Scope
508
527
  || node instanceof AST_SimpleStatement
509
528
  ) return true;
510
529
  });
511
530
 
531
+ // `defun` id to array of `defun` it uses
532
+ const defun_dependencies_map = new Map();
533
+ // `defun` id to array of enclosing `def` that are used by the function
534
+ const dependencies_map = new Map();
535
+ // all symbol ids that will be tracked for read/write
512
536
  const symbols_of_interest = new Set();
513
537
  const defuns_of_interest = new Set();
514
- const potential_conflicts = [];
515
538
 
516
539
  for (const defun of defuns) {
517
540
  const fname_def = defun.name.definition();
518
- const found_self_ref_in_other_defuns = defuns.some(
519
- d => d !== defun && d.enclosed.indexOf(fname_def) !== -1
520
- );
541
+ const enclosing_defs = [];
521
542
 
522
543
  for (const def of defun.enclosed) {
523
544
  if (
@@ -528,93 +549,107 @@ function handle_defined_after_hoist(parent) {
528
549
  continue;
529
550
  }
530
551
 
531
- // defun is hoisted, so always safe
552
+ symbols_of_interest.add(def.id);
553
+
554
+ // found a reference to another function
532
555
  if (
533
556
  def.assignments === 0
534
557
  && def.orig.length === 1
535
558
  && def.orig[0] instanceof AST_SymbolDefun
536
559
  ) {
537
- continue;
538
- }
560
+ defuns_of_interest.add(def.id);
561
+ symbols_of_interest.add(def.id);
562
+
563
+ defuns_of_interest.add(fname_def.id);
564
+ symbols_of_interest.add(fname_def.id);
565
+
566
+ if (!defun_dependencies_map.has(fname_def.id)) {
567
+ defun_dependencies_map.set(fname_def.id, []);
568
+ }
569
+ defun_dependencies_map.get(fname_def.id).push(def.id);
539
570
 
540
- if (found_self_ref_in_other_defuns) {
541
- def.fixed = false;
542
571
  continue;
543
572
  }
544
573
 
545
- // for the slower checks below this loop
546
- potential_conflicts.push({ defun, def, fname_def });
547
- symbols_of_interest.add(def.id);
574
+ enclosing_defs.push(def);
575
+ }
576
+
577
+ if (enclosing_defs.length) {
578
+ dependencies_map.set(fname_def.id, enclosing_defs);
579
+ defuns_of_interest.add(fname_def.id);
548
580
  symbols_of_interest.add(fname_def.id);
549
- defuns_of_interest.add(defun);
550
581
  }
551
582
  }
552
583
 
553
- // linearize all symbols, and locate defs that are read after the defun
554
- if (potential_conflicts.length) {
555
- // All "symbols of interest", that is, defuns or defs, that we found.
556
- // These are placed in order so we can check which is after which.
557
- const found_symbols = [];
558
- // Indices of `found_symbols` which are writes
559
- const found_symbol_writes = new Set();
560
- // Defun ranges are recorded because we don't care if a function uses the def internally
561
- const defun_ranges = new Map();
562
-
563
- let tw;
564
- parent.walk((tw = new TreeWalker((node, descend) => {
565
- if (node instanceof AST_Defun && defuns_of_interest.has(node)) {
566
- const start = found_symbols.length;
567
- descend();
568
- const end = found_symbols.length;
569
-
570
- defun_ranges.set(node, { start, end });
571
- return true;
572
- }
573
- // if we found a defun on the list, mark IN_DEFUN=id and descend
574
-
575
- if (node instanceof AST_Symbol && node.thedef) {
576
- const id = node.definition().id;
577
- if (symbols_of_interest.has(id)) {
578
- if (node instanceof AST_SymbolDeclaration || is_lhs(node, tw)) {
579
- found_symbol_writes.add(found_symbols.length);
580
- }
581
- found_symbols.push(id);
584
+ // No defuns use outside constants
585
+ if (!dependencies_map.size) {
586
+ return;
587
+ }
588
+
589
+ // Increment to count "symbols of interest" (defuns or defs) that we found.
590
+ // These are tracked in AST order so we can check which is after which.
591
+ let symbol_index = 1;
592
+ // Map a defun ID to its first read (a `symbol_index`)
593
+ const defun_first_read_map = new Map();
594
+ // Map a symbol ID to its last write (a `symbol_index`)
595
+ const symbol_last_write_map = new Map();
596
+
597
+ walk_parent(parent, (node, walk_info) => {
598
+ if (node instanceof AST_Symbol && node.thedef) {
599
+ const id = node.definition().id;
600
+
601
+ symbol_index++;
602
+
603
+ // Track last-writes to symbols
604
+ if (symbols_of_interest.has(id)) {
605
+ if (node instanceof AST_SymbolDeclaration || is_lhs(node, walk_info.parent())) {
606
+ symbol_last_write_map.set(id, symbol_index);
582
607
  }
583
608
  }
584
- })));
585
-
586
- for (const { def, defun, fname_def } of potential_conflicts) {
587
- const defun_range = defun_ranges.get(defun);
588
-
589
- // find the index in `found_symbols`, with some special rules:
590
- const find = (sym_id, starting_at = 0, must_be_write = false) => {
591
- let index = starting_at;
592
-
593
- for (;;) {
594
- index = found_symbols.indexOf(sym_id, index);
595
-
596
- if (index === -1) {
597
- break;
598
- } else if (index >= defun_range.start && index < defun_range.end) {
599
- index = defun_range.end;
600
- continue;
601
- } else if (must_be_write && !found_symbol_writes.has(index)) {
602
- index++;
603
- continue;
604
- } else {
605
- break;
606
- }
609
+
610
+ // Track first-reads of defuns (refined later)
611
+ if (defuns_of_interest.has(id)) {
612
+ if (!defun_first_read_map.has(id) && !is_recursive_ref(walk_info, id)) {
613
+ defun_first_read_map.set(id, symbol_index);
607
614
  }
615
+ }
616
+ }
617
+ });
608
618
 
609
- return index;
610
- };
619
+ // Refine `defun_first_read_map` to be as high as possible
620
+ for (const [defun, defun_first_read] of defun_first_read_map) {
621
+ // Update all depdencies of `defun`
622
+ const queue = new Set(defun_dependencies_map.get(defun));
623
+ for (const enclosed_defun of queue) {
624
+ let enclosed_defun_first_read = defun_first_read_map.get(enclosed_defun);
625
+ if (enclosed_defun_first_read != null && enclosed_defun_first_read < defun_first_read) {
626
+ continue;
627
+ }
628
+
629
+ defun_first_read_map.set(enclosed_defun, defun_first_read);
611
630
 
612
- const read_defun_at = find(fname_def.id);
613
- const wrote_def_at = find(def.id, read_defun_at + 1, true);
631
+ for (const enclosed_enclosed_defun of defun_dependencies_map.get(enclosed_defun) || []) {
632
+ queue.add(enclosed_enclosed_defun);
633
+ }
634
+ }
635
+ }
636
+
637
+ // ensure write-then-read order, otherwise clear `fixed`
638
+ // This is safe because last-writes (found_symbol_writes) are assumed to be as late as possible, and first-reads (defun_first_read_map) are assumed to be as early as possible.
639
+ for (const [defun, defs] of dependencies_map) {
640
+ const defun_first_read = defun_first_read_map.get(defun);
641
+ if (defun_first_read === undefined) {
642
+ continue;
643
+ }
644
+
645
+ for (const def of defs) {
646
+ if (def.fixed === false) {
647
+ continue;
648
+ }
614
649
 
615
- const wrote_def_after_reading_defun = read_defun_at != -1 && wrote_def_at != -1 && wrote_def_at > read_defun_at;
650
+ let def_last_write = symbol_last_write_map.get(def.id) || 0;
616
651
 
617
- if (wrote_def_after_reading_defun) {
652
+ if (defun_first_read < def_last_write) {
618
653
  def.fixed = false;
619
654
  }
620
655
  }
package/lib/minify.js CHANGED
@@ -305,7 +305,7 @@ function* minify_sync_or_async(files, options, _fs_module) {
305
305
  if (node.block_scope) {
306
306
  node.block_scope.variables = undefined;
307
307
  node.block_scope.enclosed = undefined;
308
- node.parent_scope = undefined;
308
+ node.block_scope.parent_scope = undefined;
309
309
  }
310
310
  });
311
311
  }
package/lib/propmangle.js CHANGED
@@ -326,7 +326,9 @@ function mangle_properties(ast, options, annotated_props = find_annotated_props(
326
326
  } else if (node instanceof AST_ObjectProperty) {
327
327
  // setter, getter, method or class field
328
328
  if (!keep_quoted || !node.quote) {
329
- node.key.name = mangle(node.key.name);
329
+ if (!node.computed_key()) {
330
+ node.key.name = mangle(node.key.name);
331
+ }
330
332
  }
331
333
  } else if (node instanceof AST_Dot) {
332
334
  if (!keep_quoted || !node.quote) {
package/lib/scope.js CHANGED
@@ -200,7 +200,7 @@ function redefined_catch_def(def) {
200
200
  }
201
201
  }
202
202
 
203
- AST_Scope.DEFMETHOD("figure_out_scope", function(options, { parent_scope = null, toplevel = this } = {}) {
203
+ AST_Scope.DEFMETHOD("figure_out_scope", function(options, { parent_scope = undefined, toplevel = this } = {}) {
204
204
  options = defaults(options, {
205
205
  cache: null,
206
206
  ie8: false,
@@ -524,7 +524,7 @@ AST_Scope.DEFMETHOD("add_child_scope", function (scope) {
524
524
  scope.parent_scope = this;
525
525
 
526
526
  // Propagate to this.uses_arguments from arrow functions
527
- if ((scope instanceof AST_Arrow) && !this.uses_arguments) {
527
+ if ((scope instanceof AST_Arrow) && (this instanceof AST_Lambda && !this.uses_arguments)) {
528
528
  this.uses_arguments = walk(scope, node => {
529
529
  if (
530
530
  node instanceof AST_SymbolRef
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.32.0",
7
+ "version": "5.34.0",
8
8
  "engines": {
9
9
  "node": ">=10"
10
10
  },