terser 5.32.0 → 5.33.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.33.0
4
+
5
+ - `reduce_vars` improved when dealing with hoisted function definitions (#1544)
6
+
3
7
  ## v5.32.0
4
8
 
5
9
  - `import("module")` can now be input and output from ESTree AST (#1557)
@@ -13291,9 +13291,9 @@ function is_reachable(scope_node, defs) {
13291
13291
  }
13292
13292
 
13293
13293
  /** Check if a ref refers to the name of a function/class it's defined within */
13294
- function is_recursive_ref(compressor, def) {
13294
+ function is_recursive_ref(tw, def) {
13295
13295
  var node;
13296
- for (var i = 0; node = compressor.parent(i); i++) {
13296
+ for (var i = 0; node = tw.parent(i); i++) {
13297
13297
  if (node instanceof AST_Lambda || node instanceof AST_Class) {
13298
13298
  var name = node.name;
13299
13299
  if (name && name.definition() === def) {
@@ -16147,28 +16147,50 @@ function mark_lambda(tw, descend, compressor) {
16147
16147
  * // use defined_after
16148
16148
  * }
16149
16149
  *
16150
- * This function is called on the parent to handle this issue.
16150
+ * Or even indirectly:
16151
+ *
16152
+ * B();
16153
+ * var defined_after = true;
16154
+ * function A() {
16155
+ * // use defined_after
16156
+ * }
16157
+ * function B() {
16158
+ * A();
16159
+ * }
16160
+ *
16161
+ * Access a variable before declaration will either throw a ReferenceError
16162
+ * (if the variable is declared with `let` or `const`),
16163
+ * or get an `undefined` (if the variable is declared with `var`).
16164
+ *
16165
+ * If the variable is inlined into the function, the behavior will change.
16166
+ *
16167
+ * This function is called on the parent to disallow inlining of such variables,
16151
16168
  */
16152
16169
  function handle_defined_after_hoist(parent) {
16153
16170
  const defuns = [];
16154
16171
  walk(parent, node => {
16155
16172
  if (node === parent) return;
16156
- if (node instanceof AST_Defun) defuns.push(node);
16173
+ if (node instanceof AST_Defun) {
16174
+ defuns.push(node);
16175
+ return true;
16176
+ }
16157
16177
  if (
16158
16178
  node instanceof AST_Scope
16159
16179
  || node instanceof AST_SimpleStatement
16160
16180
  ) return true;
16161
16181
  });
16162
16182
 
16183
+ // `defun` id to array of `defun` it uses
16184
+ const defun_dependencies_map = new Map();
16185
+ // `defun` id to array of enclosing `def` that are used by the function
16186
+ const dependencies_map = new Map();
16187
+ // all symbol ids that will be tracked for read/write
16163
16188
  const symbols_of_interest = new Set();
16164
16189
  const defuns_of_interest = new Set();
16165
- const potential_conflicts = [];
16166
16190
 
16167
16191
  for (const defun of defuns) {
16168
16192
  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
- );
16193
+ const enclosing_defs = [];
16172
16194
 
16173
16195
  for (const def of defun.enclosed) {
16174
16196
  if (
@@ -16179,93 +16201,107 @@ function handle_defined_after_hoist(parent) {
16179
16201
  continue;
16180
16202
  }
16181
16203
 
16182
- // defun is hoisted, so always safe
16204
+ symbols_of_interest.add(def.id);
16205
+
16206
+ // found a reference to another function
16183
16207
  if (
16184
16208
  def.assignments === 0
16185
16209
  && def.orig.length === 1
16186
16210
  && def.orig[0] instanceof AST_SymbolDefun
16187
16211
  ) {
16188
- continue;
16189
- }
16212
+ defuns_of_interest.add(def.id);
16213
+ symbols_of_interest.add(def.id);
16214
+
16215
+ defuns_of_interest.add(fname_def.id);
16216
+ symbols_of_interest.add(fname_def.id);
16217
+
16218
+ if (!defun_dependencies_map.has(fname_def.id)) {
16219
+ defun_dependencies_map.set(fname_def.id, []);
16220
+ }
16221
+ defun_dependencies_map.get(fname_def.id).push(def.id);
16190
16222
 
16191
- if (found_self_ref_in_other_defuns) {
16192
- def.fixed = false;
16193
16223
  continue;
16194
16224
  }
16195
16225
 
16196
- // for the slower checks below this loop
16197
- potential_conflicts.push({ defun, def, fname_def });
16198
- symbols_of_interest.add(def.id);
16226
+ enclosing_defs.push(def);
16227
+ }
16228
+
16229
+ if (enclosing_defs.length) {
16230
+ dependencies_map.set(fname_def.id, enclosing_defs);
16231
+ defuns_of_interest.add(fname_def.id);
16199
16232
  symbols_of_interest.add(fname_def.id);
16200
- defuns_of_interest.add(defun);
16201
16233
  }
16202
16234
  }
16203
16235
 
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();
16236
+ // No defuns use outside constants
16237
+ if (!dependencies_map.size) {
16238
+ return;
16239
+ }
16213
16240
 
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;
16241
+ // Increment to count "symbols of interest" (defuns or defs) that we found.
16242
+ // These are tracked in AST order so we can check which is after which.
16243
+ let symbol_index = 1;
16244
+ // Map a defun ID to its first read (a `symbol_index`)
16245
+ const defun_first_read_map = new Map();
16246
+ // Map a symbol ID to its last write (a `symbol_index`)
16247
+ const symbol_last_write_map = new Map();
16220
16248
 
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
16249
+ walk_parent(parent, (node, walk_info) => {
16250
+ if (node instanceof AST_Symbol && node.thedef) {
16251
+ const id = node.definition().id;
16225
16252
 
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);
16253
+ symbol_index++;
16254
+
16255
+ // Track last-writes to symbols
16256
+ if (symbols_of_interest.has(id)) {
16257
+ if (node instanceof AST_SymbolDeclaration || is_lhs(node, walk_info.parent())) {
16258
+ symbol_last_write_map.set(id, symbol_index);
16233
16259
  }
16234
16260
  }
16235
- })));
16236
16261
 
16237
- for (const { def, defun, fname_def } of potential_conflicts) {
16238
- const defun_range = defun_ranges.get(defun);
16262
+ // Track first-reads of defuns (refined later)
16263
+ if (defuns_of_interest.has(id)) {
16264
+ if (!defun_first_read_map.has(id) && !is_recursive_ref(walk_info, id)) {
16265
+ defun_first_read_map.set(id, symbol_index);
16266
+ }
16267
+ }
16268
+ }
16269
+ });
16239
16270
 
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;
16271
+ // Refine `defun_first_read_map` to be as high as possible
16272
+ for (const [defun, defun_first_read] of defun_first_read_map) {
16273
+ // Update all depdencies of `defun`
16274
+ const queue = new Set(defun_dependencies_map.get(defun));
16275
+ for (const enclosed_defun of queue) {
16276
+ let enclosed_defun_first_read = defun_first_read_map.get(enclosed_defun);
16277
+ if (enclosed_defun_first_read != null && enclosed_defun_first_read < defun_first_read) {
16278
+ continue;
16279
+ }
16243
16280
 
16244
- for (;;) {
16245
- index = found_symbols.indexOf(sym_id, index);
16281
+ defun_first_read_map.set(enclosed_defun, defun_first_read);
16246
16282
 
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
- }
16283
+ for (const enclosed_enclosed_defun of defun_dependencies_map.get(enclosed_defun) || []) {
16284
+ queue.add(enclosed_enclosed_defun);
16285
+ }
16286
+ }
16287
+ }
16259
16288
 
16260
- return index;
16261
- };
16289
+ // ensure write-then-read order, otherwise clear `fixed`
16290
+ // 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.
16291
+ for (const [defun, defs] of dependencies_map) {
16292
+ const defun_first_read = defun_first_read_map.get(defun);
16293
+ if (defun_first_read === undefined) {
16294
+ continue;
16295
+ }
16262
16296
 
16263
- const read_defun_at = find(fname_def.id);
16264
- const wrote_def_at = find(def.id, read_defun_at + 1, true);
16297
+ for (const def of defs) {
16298
+ if (def.fixed === false) {
16299
+ continue;
16300
+ }
16265
16301
 
16266
- const wrote_def_after_reading_defun = read_defun_at != -1 && wrote_def_at != -1 && wrote_def_at > read_defun_at;
16302
+ let def_last_write = symbol_last_write_map.get(def.id) || 0;
16267
16303
 
16268
- if (wrote_def_after_reading_defun) {
16304
+ if (defun_first_read < def_last_write) {
16269
16305
  def.fixed = false;
16270
16306
  }
16271
16307
  }
@@ -333,9 +333,9 @@ export function is_reachable(scope_node, defs) {
333
333
  }
334
334
 
335
335
  /** Check if a ref refers to the name of a function/class it's defined within */
336
- export function is_recursive_ref(compressor, def) {
336
+ export function is_recursive_ref(tw, def) {
337
337
  var node;
338
- for (var i = 0; node = compressor.parent(i); i++) {
338
+ for (var i = 0; node = tw.parent(i); i++) {
339
339
  if (node instanceof AST_Lambda || node instanceof AST_Class) {
340
340
  var name = node.name;
341
341
  if (name && name.definition() === def) {
@@ -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/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.33.0",
8
8
  "engines": {
9
9
  "node": ">=10"
10
10
  },