terser 5.17.7 → 5.18.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,11 @@
1
1
  # Changelog
2
2
 
3
+ ## v7.18.1
4
+ - Fix major performance issue caused by hoisted defuns' scopes bugfix.
5
+
6
+ ## v7.18.0
7
+ - Add new `/*@__MANGLE_PROP__*/` annotation, to mark properties that should be mangled.
8
+
3
9
  ## v5.17.7
4
10
  - Update some dependencies
5
11
  - Add consistent sorting for `v` RegExp flag
package/README.md CHANGED
@@ -52,7 +52,9 @@ From NPM for programmatic use:
52
52
 
53
53
  <!-- CLI_USAGE:START -->
54
54
 
55
- terser [input files] [options]
55
+ ```
56
+ terser [input files] [options]
57
+ ```
56
58
 
57
59
  Terser can take multiple input files. It's recommended that you pass the
58
60
  input files first, then pass the options. Terser will parse input files
@@ -106,6 +108,7 @@ a double dash to prevent input files being used as option arguments:
106
108
  `strict` disables quoted properties
107
109
  being automatically reserved.
108
110
  `regex` Only mangle matched property names.
111
+ `only_annotated` Only mangle properties defined with /*@__MANGLE_PROP__*/.
109
112
  `reserved` List of names that should not be mangled.
110
113
  -f, --format [options] Specify format options.
111
114
  `preamble` Preamble to prepend to the output. You
@@ -673,6 +676,10 @@ If you're using the `X-SourceMap` header instead, you can just omit `sourceMap.u
673
676
 
674
677
  If you happen to need the source map as a raw object, set `sourceMap.asObject` to `true`.
675
678
 
679
+ <!-- API_REFERENCE:END -->
680
+
681
+ <!-- OPTIONS:START -->
682
+
676
683
  ## Parse options
677
684
 
678
685
  - `bare_returns` (default `false`) -- support top level `return` statements
@@ -1061,8 +1068,14 @@ as "output options".
1061
1068
  function expressions that are passed as arguments, in parenthesis. See
1062
1069
  [OptimizeJS](https://github.com/nolanlawson/optimize-js) for more details.
1063
1070
 
1071
+
1072
+ <!-- OPTIONS:END -->
1073
+
1074
+
1064
1075
  # Miscellaneous
1065
1076
 
1077
+ <!-- MISCELLANEOUS:START -->
1078
+
1066
1079
  ### Keeping copyright notices or other comments
1067
1080
 
1068
1081
  You can pass `--comments` to retain certain comments in the output. By
@@ -1189,6 +1202,7 @@ Annotations in Terser are a way to tell it to treat a certain function call diff
1189
1202
  * `/*@__NOINLINE__*/` - Makes sure the called function is not inlined into the call site.
1190
1203
  * `/*@__PURE__*/` - Marks a function call as pure. That means, it can safely be dropped.
1191
1204
  * `/*@__KEY__*/` - Marks a string literal as a property to also mangle it when mangling properties.
1205
+ * `/*@__MANGLE_PROP__*/` - Opts-in an object property (or class field) for mangling, when the property mangler is enabled.
1192
1206
 
1193
1207
  You can use either a `@` sign at the start, or a `#`.
1194
1208
 
@@ -1326,19 +1340,27 @@ $ rm -rf node_modules yarn.lock
1326
1340
  $ yarn
1327
1341
  ```
1328
1342
 
1329
- <!-- API_REFERENCE:END -->
1343
+ <!-- MISCELLANEOUS:END -->
1330
1344
 
1331
1345
  # Reporting issues
1332
1346
 
1333
- In the terser CLI we use [source-map-support](https://npmjs.com/source-map-support) to produce good error stacks. In your own app, you're expected to enable source-map-support (read their docs) to have nice stack traces that will help you write good issues.
1347
+ <!-- REPORTING_ISSUES:START -->
1348
+
1349
+ ## A minimal, reproducible example
1350
+
1351
+ You're expected to provide a [minimal reproducible example] of input code that will demonstrate your issue.
1352
+
1353
+ To get to this example, you can remove bits of your code and stop if your issue ceases to reproduce.
1334
1354
 
1335
1355
  ## Obtaining the source code given to Terser
1336
1356
 
1337
- Because users often don't control the call to `await minify()` or its arguments, Terser provides a `TERSER_DEBUG_DIR` environment variable to make terser output some debug logs. If you're using a bundler or a project that includes a bundler and are not sure what went wrong with your code, pass that variable like so:
1357
+ Because users often don't control the call to `await minify()` or its arguments, Terser provides a `TERSER_DEBUG_DIR` environment variable to make terser output some debug logs.
1338
1358
 
1339
- ```
1340
- $ TERSER_DEBUG_DIR=/path/to/logs command-that-uses-terser
1341
- $ ls /path/to/logs
1359
+ These logs will contain the input code and options of each `minify()` call.
1360
+
1361
+ ```bash
1362
+ TERSER_DEBUG_DIR=/tmp/terser-log-dir command-that-uses-terser
1363
+ ls /tmp/terser-log-dir
1342
1364
  terser-debug-123456.log
1343
1365
  ```
1344
1366
 
@@ -1348,6 +1370,12 @@ If you're not sure how to set an environment variable on your shell (the above e
1348
1370
  > npx cross-env TERSER_DEBUG_DIR=/path/to/logs command-that-uses-terser
1349
1371
  ```
1350
1372
 
1373
+ ## Stack traces
1374
+
1375
+ In the terser CLI we use [source-map-support](https://npmjs.com/source-map-support) to produce good error stacks. In your own app, you're expected to enable source-map-support (read their docs) to have nice stack traces that will help you write good issues.
1376
+
1377
+ <!-- REPORTING_ISSUES:END -->
1378
+
1351
1379
  # README.md Patrons:
1352
1380
 
1353
1381
  *note*: <s>You can support this project on patreon: [link]</s> **The Terser Patreon is shutting down in favor of opencollective**. Check out [PATRONS.md](https://github.com/terser/terser/blob/master/PATRONS.md) for our first-tier patrons.
@@ -2372,7 +2372,7 @@ function parse($TEXT, options) {
2372
2372
  value : tok.value,
2373
2373
  quote : tok.quote
2374
2374
  });
2375
- annotate(ret);
2375
+ annotate(ret);
2376
2376
  break;
2377
2377
  case "regexp":
2378
2378
  const [_, source, flags] = tok.value.match(/^\/(.*)\/(\w*)$/);
@@ -2684,13 +2684,14 @@ function parse($TEXT, options) {
2684
2684
  }
2685
2685
 
2686
2686
  // Create property
2687
- a.push(new AST_ObjectKeyVal({
2687
+ const kv = new AST_ObjectKeyVal({
2688
2688
  start: start,
2689
2689
  quote: start.quote,
2690
2690
  key: name instanceof AST_Node ? name : "" + name,
2691
2691
  value: value,
2692
2692
  end: prev()
2693
- }));
2693
+ });
2694
+ a.push(annotate(kv));
2694
2695
  }
2695
2696
  next();
2696
2697
  return new AST_Object({ properties: a });
@@ -2803,26 +2804,26 @@ function parse($TEXT, options) {
2803
2804
  : AST_ObjectSetter;
2804
2805
 
2805
2806
  name = get_symbol_ast(name);
2806
- return new AccessorClass({
2807
+ return annotate(new AccessorClass({
2807
2808
  start,
2808
2809
  static: is_static,
2809
2810
  key: name,
2810
2811
  quote: name instanceof AST_SymbolMethod ? property_token.quote : undefined,
2811
2812
  value: create_accessor(),
2812
2813
  end: prev()
2813
- });
2814
+ }));
2814
2815
  } else {
2815
2816
  const AccessorClass = accessor_type === "get"
2816
2817
  ? AST_PrivateGetter
2817
2818
  : AST_PrivateSetter;
2818
2819
 
2819
- return new AccessorClass({
2820
+ return annotate(new AccessorClass({
2820
2821
  start,
2821
2822
  static: is_static,
2822
2823
  key: get_symbol_ast(name),
2823
2824
  value: create_accessor(),
2824
2825
  end: prev(),
2825
- });
2826
+ }));
2826
2827
  }
2827
2828
  }
2828
2829
 
@@ -2842,7 +2843,7 @@ function parse($TEXT, options) {
2842
2843
  value : create_accessor(is_generator, is_async),
2843
2844
  end : prev()
2844
2845
  });
2845
- return node;
2846
+ return annotate(node);
2846
2847
  }
2847
2848
 
2848
2849
  if (is_class) {
@@ -2855,14 +2856,16 @@ function parse($TEXT, options) {
2855
2856
  : AST_ClassProperty;
2856
2857
  if (is("operator", "=")) {
2857
2858
  next();
2858
- return new AST_ClassPropertyVariant({
2859
- start,
2860
- static: is_static,
2861
- quote,
2862
- key,
2863
- value: expression(false),
2864
- end: prev()
2865
- });
2859
+ return annotate(
2860
+ new AST_ClassPropertyVariant({
2861
+ start,
2862
+ static: is_static,
2863
+ quote,
2864
+ key,
2865
+ value: expression(false),
2866
+ end: prev()
2867
+ })
2868
+ );
2866
2869
  } else if (
2867
2870
  is("name")
2868
2871
  || is("privatename")
@@ -2870,13 +2873,15 @@ function parse($TEXT, options) {
2870
2873
  || is("punc", ";")
2871
2874
  || is("punc", "}")
2872
2875
  ) {
2873
- return new AST_ClassPropertyVariant({
2874
- start,
2875
- static: is_static,
2876
- quote,
2877
- key,
2878
- end: prev()
2879
- });
2876
+ return annotate(
2877
+ new AST_ClassPropertyVariant({
2878
+ start,
2879
+ static: is_static,
2880
+ quote,
2881
+ key,
2882
+ end: prev()
2883
+ })
2884
+ );
2880
2885
  }
2881
2886
  }
2882
2887
  }
@@ -3239,10 +3244,9 @@ function parse($TEXT, options) {
3239
3244
  }
3240
3245
 
3241
3246
  // Annotate AST_Call, AST_Lambda or AST_New with the special comments
3242
- function annotate(node) {
3243
- var start = node.start;
3244
- var comments = start.comments_before;
3245
- const comments_outside_parens = outer_comments_before_counts.get(start);
3247
+ function annotate(node, before_token = node.start) {
3248
+ var comments = before_token.comments_before;
3249
+ const comments_outside_parens = outer_comments_before_counts.get(before_token);
3246
3250
  var i = comments_outside_parens != null ? comments_outside_parens : comments.length;
3247
3251
  while (--i >= 0) {
3248
3252
  var comment = comments[i];
@@ -3263,8 +3267,13 @@ function parse($TEXT, options) {
3263
3267
  set_annotation(node, _KEY);
3264
3268
  break;
3265
3269
  }
3270
+ if (/[@#]__MANGLE_PROP__/.test(comment.value)) {
3271
+ set_annotation(node, _MANGLEPROP);
3272
+ break;
3273
+ }
3266
3274
  }
3267
3275
  }
3276
+ return node;
3268
3277
  }
3269
3278
 
3270
3279
  var subscripts = function(expr, allow_calls, is_chain) {
@@ -5623,6 +5632,7 @@ var AST_ObjectProperty = DEFNODE("ObjectProperty", "key value", function AST_Obj
5623
5632
  this.value = props.value;
5624
5633
  this.start = props.start;
5625
5634
  this.end = props.end;
5635
+ this._annotations = props._annotations;
5626
5636
  }
5627
5637
 
5628
5638
  this.flags = 0;
@@ -5652,6 +5662,7 @@ var AST_ObjectKeyVal = DEFNODE("ObjectKeyVal", "quote", function AST_ObjectKeyVa
5652
5662
  this.value = props.value;
5653
5663
  this.start = props.start;
5654
5664
  this.end = props.end;
5665
+ this._annotations = props._annotations;
5655
5666
  }
5656
5667
 
5657
5668
  this.flags = 0;
@@ -5713,6 +5724,7 @@ var AST_ObjectSetter = DEFNODE("ObjectSetter", "quote static", function AST_Obje
5713
5724
  this.value = props.value;
5714
5725
  this.start = props.start;
5715
5726
  this.end = props.end;
5727
+ this._annotations = props._annotations;
5716
5728
  }
5717
5729
 
5718
5730
  this.flags = 0;
@@ -5735,6 +5747,7 @@ var AST_ObjectGetter = DEFNODE("ObjectGetter", "quote static", function AST_Obje
5735
5747
  this.value = props.value;
5736
5748
  this.start = props.start;
5737
5749
  this.end = props.end;
5750
+ this._annotations = props._annotations;
5738
5751
  }
5739
5752
 
5740
5753
  this.flags = 0;
@@ -5762,6 +5775,7 @@ var AST_ConciseMethod = DEFNODE(
5762
5775
  this.value = props.value;
5763
5776
  this.start = props.start;
5764
5777
  this.end = props.end;
5778
+ this._annotations = props._annotations;
5765
5779
  }
5766
5780
 
5767
5781
  this.flags = 0;
@@ -5884,6 +5898,7 @@ var AST_ClassProperty = DEFNODE("ClassProperty", "static quote", function AST_Cl
5884
5898
  this.value = props.value;
5885
5899
  this.start = props.start;
5886
5900
  this.end = props.end;
5901
+ this._annotations = props._annotations;
5887
5902
  }
5888
5903
 
5889
5904
  this.flags = 0;
@@ -6787,10 +6802,11 @@ class TreeTransformer extends TreeWalker {
6787
6802
  }
6788
6803
  }
6789
6804
 
6790
- const _PURE = 0b00000001;
6791
- const _INLINE = 0b00000010;
6792
- const _NOINLINE = 0b00000100;
6793
- const _KEY = 0b00001000;
6805
+ const _PURE = 0b00000001;
6806
+ const _INLINE = 0b00000010;
6807
+ const _NOINLINE = 0b00000100;
6808
+ const _KEY = 0b00001000;
6809
+ const _MANGLEPROP = 0b00010000;
6794
6810
 
6795
6811
  /***********************************************************************
6796
6812
 
@@ -15918,6 +15934,10 @@ function handle_defined_after_hoist(parent) {
15918
15934
  ) return true;
15919
15935
  });
15920
15936
 
15937
+ const symbols_of_interest = new Set();
15938
+ const defuns_of_interest = new Set();
15939
+ const potential_conflicts = [];
15940
+
15921
15941
  for (const defun of defuns) {
15922
15942
  const fname_def = defun.name.definition();
15923
15943
  const found_self_ref_in_other_defuns = defuns.some(
@@ -15947,35 +15967,77 @@ function handle_defined_after_hoist(parent) {
15947
15967
  continue;
15948
15968
  }
15949
15969
 
15950
- // Detect `call_defun(); var used_in_defun = X`
15951
- // Because `used_in_defun` is not certainly X when it's defined after.
15952
- let found_defun_ref = false;
15953
- let found_def_after_defun = false;
15954
- walk_parent(parent, (node, info) => {
15955
- if (node === defun) return true;
15970
+ // for the slower checks below this loop
15971
+ potential_conflicts.push({ defun, def, fname_def });
15972
+ symbols_of_interest.add(def.id);
15973
+ symbols_of_interest.add(fname_def.id);
15974
+ defuns_of_interest.add(defun);
15975
+ }
15976
+ }
15956
15977
 
15957
- // Step 1: find `call_defun()` or other refs to the defun
15958
- if (
15959
- !found_defun_ref
15960
- && node.thedef === fname_def
15961
- && node instanceof AST_Symbol
15962
- ) {
15963
- found_defun_ref = true;
15978
+ // linearize all symbols, and locate defs that are read after the defun
15979
+ if (potential_conflicts.length) {
15980
+ // All "symbols of interest", that is, defuns or defs, that we found.
15981
+ // These are placed in order so we can check which is after which.
15982
+ const found_symbols = [];
15983
+ // Indices of `found_symbols` which are writes
15984
+ const found_symbol_writes = new Set();
15985
+ // Defun ranges are recorded because we don't care if a function uses the def internally
15986
+ const defun_ranges = new Map();
15987
+
15988
+ let tw;
15989
+ parent.walk((tw = new TreeWalker((node, descend) => {
15990
+ if (node instanceof AST_Defun && defuns_of_interest.has(node)) {
15991
+ const start = found_symbols.length;
15992
+ descend();
15993
+ const end = found_symbols.length;
15994
+
15995
+ defun_ranges.set(node, { start, end });
15996
+ return true;
15997
+ }
15998
+ // if we found a defun on the list, mark IN_DEFUN=id and descend
15999
+
16000
+ if (node instanceof AST_Symbol && node.thedef) {
16001
+ const id = node.definition().id;
16002
+ if (symbols_of_interest.has(id)) {
16003
+ if (node instanceof AST_SymbolDeclaration || is_lhs(node, tw)) {
16004
+ found_symbol_writes.add(found_symbols.length);
16005
+ }
16006
+ found_symbols.push(id);
15964
16007
  }
16008
+ }
16009
+ })));
16010
+
16011
+ for (const { def, defun, fname_def } of potential_conflicts) {
16012
+ const defun_range = defun_ranges.get(defun);
16013
+
16014
+ // find the index in `found_symbols`, with some special rules:
16015
+ const find = (sym_id, starting_at = 0, must_be_write = false) => {
16016
+ const index = found_symbols.indexOf(sym_id, starting_at);
15965
16017
 
15966
- // Step 2: if Step 1 occurred, find a var the defun uses
15967
16018
  if (
15968
- found_defun_ref
15969
- && node.thedef === def
15970
- && (node instanceof AST_SymbolDeclaration
15971
- || is_lhs(node, info))
16019
+ defun_range
16020
+ && index >= defun_range.start
16021
+ && index < defun_range.end
15972
16022
  ) {
15973
- found_def_after_defun = true;
15974
- return walk_abort;
16023
+ return find(sym_id, defun_range.end, must_be_write);
16024
+ } else if (
16025
+ must_be_write
16026
+ && index >= 0
16027
+ && !found_symbol_writes.has(index)
16028
+ ) {
16029
+ return find(sym_id, index + 1, must_be_write);
16030
+ } else {
16031
+ return index;
15975
16032
  }
15976
- });
16033
+ };
15977
16034
 
15978
- if (found_def_after_defun) {
16035
+ const read_defun_at = find(fname_def.id);
16036
+ const wrote_def_at = find(def.id, read_defun_at + 1, true);
16037
+
16038
+ const wrote_def_after_reading_defun = read_defun_at != -1 && wrote_def_at != -1 && wrote_def_at > read_defun_at;
16039
+
16040
+ if (wrote_def_after_reading_defun) {
15979
16041
  def.fixed = false;
15980
16042
  }
15981
16043
  }
@@ -29819,6 +29881,7 @@ function mangle_properties(ast, options) {
29819
29881
  regex: null,
29820
29882
  reserved: null,
29821
29883
  undeclared: false,
29884
+ only_annotated: false,
29822
29885
  }, true);
29823
29886
 
29824
29887
  var nth_identifier = options.nth_identifier;
@@ -29837,6 +29900,7 @@ function mangle_properties(ast, options) {
29837
29900
  cache = new Map();
29838
29901
  }
29839
29902
 
29903
+ var only_annotated = options.only_annotated;
29840
29904
  var regex = options.regex && new RegExp(options.regex);
29841
29905
 
29842
29906
  // note debug is either false (disabled), or a string of the debug suffix to use (enabled).
@@ -29848,6 +29912,7 @@ function mangle_properties(ast, options) {
29848
29912
  debug_name_suffix = (options.debug === true ? "" : options.debug);
29849
29913
  }
29850
29914
 
29915
+ var annotated_names = new Set();
29851
29916
  var names_to_mangle = new Set();
29852
29917
  var unmangleable = new Set();
29853
29918
  // Track each already-mangled name to prevent nth_identifier from generating
@@ -29856,7 +29921,36 @@ function mangle_properties(ast, options) {
29856
29921
 
29857
29922
  var keep_quoted = !!options.keep_quoted;
29858
29923
 
29859
- // step 1: find candidates to mangle
29924
+ // Step 1: Find all annotated /*@__MANGLEPROP__*/
29925
+ walk(ast, node => {
29926
+ if (
29927
+ node instanceof AST_ClassPrivateProperty
29928
+ || node instanceof AST_PrivateMethod
29929
+ || node instanceof AST_PrivateGetter
29930
+ || node instanceof AST_PrivateSetter
29931
+ || node instanceof AST_DotHash
29932
+ ) ; else if (node instanceof AST_ObjectKeyVal) {
29933
+ if (
29934
+ typeof node.key == "string"
29935
+ && has_annotation(node, _MANGLEPROP)
29936
+ && can_mangle(node.key)
29937
+ ) {
29938
+ annotated_names.add(node.key);
29939
+ clear_annotation(node, _MANGLEPROP);
29940
+ }
29941
+ } else if (node instanceof AST_ObjectProperty) {
29942
+ // setter or getter, since KeyVal is handled above
29943
+ if (
29944
+ has_annotation(node, _MANGLEPROP)
29945
+ && can_mangle(node.key.name)
29946
+ ) {
29947
+ annotated_names.add(node.key.name);
29948
+ clear_annotation(node, _MANGLEPROP);
29949
+ }
29950
+ }
29951
+ });
29952
+
29953
+ // step 2: find candidates to mangle
29860
29954
  ast.walk(new TreeWalker(function(node) {
29861
29955
  if (
29862
29956
  node instanceof AST_ClassPrivateProperty
@@ -29948,15 +30042,19 @@ function mangle_properties(ast, options) {
29948
30042
  }
29949
30043
 
29950
30044
  function should_mangle(name) {
29951
- if (regex && !regex.test(name)) return false;
30045
+ if (only_annotated && !annotated_names.has(name)) return false;
30046
+ if (regex && !regex.test(name)) {
30047
+ return annotated_names.has(name);
30048
+ }
29952
30049
  if (reserved.has(name)) return false;
29953
30050
  return cache.has(name)
29954
30051
  || names_to_mangle.has(name);
29955
30052
  }
29956
30053
 
29957
30054
  function add(name) {
29958
- if (can_mangle(name))
30055
+ if (can_mangle(name)) {
29959
30056
  names_to_mangle.add(name);
30057
+ }
29960
30058
 
29961
30059
  if (!should_mangle(name)) {
29962
30060
  unmangleable.add(name);
package/lib/ast.js CHANGED
@@ -2013,6 +2013,7 @@ var AST_ObjectProperty = DEFNODE("ObjectProperty", "key value", function AST_Obj
2013
2013
  this.value = props.value;
2014
2014
  this.start = props.start;
2015
2015
  this.end = props.end;
2016
+ this._annotations = props._annotations;
2016
2017
  }
2017
2018
 
2018
2019
  this.flags = 0;
@@ -2042,6 +2043,7 @@ var AST_ObjectKeyVal = DEFNODE("ObjectKeyVal", "quote", function AST_ObjectKeyVa
2042
2043
  this.value = props.value;
2043
2044
  this.start = props.start;
2044
2045
  this.end = props.end;
2046
+ this._annotations = props._annotations;
2045
2047
  }
2046
2048
 
2047
2049
  this.flags = 0;
@@ -2103,6 +2105,7 @@ var AST_ObjectSetter = DEFNODE("ObjectSetter", "quote static", function AST_Obje
2103
2105
  this.value = props.value;
2104
2106
  this.start = props.start;
2105
2107
  this.end = props.end;
2108
+ this._annotations = props._annotations;
2106
2109
  }
2107
2110
 
2108
2111
  this.flags = 0;
@@ -2125,6 +2128,7 @@ var AST_ObjectGetter = DEFNODE("ObjectGetter", "quote static", function AST_Obje
2125
2128
  this.value = props.value;
2126
2129
  this.start = props.start;
2127
2130
  this.end = props.end;
2131
+ this._annotations = props._annotations;
2128
2132
  }
2129
2133
 
2130
2134
  this.flags = 0;
@@ -2152,6 +2156,7 @@ var AST_ConciseMethod = DEFNODE(
2152
2156
  this.value = props.value;
2153
2157
  this.start = props.start;
2154
2158
  this.end = props.end;
2159
+ this._annotations = props._annotations;
2155
2160
  }
2156
2161
 
2157
2162
  this.flags = 0;
@@ -2274,6 +2279,7 @@ var AST_ClassProperty = DEFNODE("ClassProperty", "static quote", function AST_Cl
2274
2279
  this.value = props.value;
2275
2280
  this.start = props.start;
2276
2281
  this.end = props.end;
2282
+ this._annotations = props._annotations;
2277
2283
  }
2278
2284
 
2279
2285
  this.flags = 0;
@@ -3177,10 +3183,11 @@ class TreeTransformer extends TreeWalker {
3177
3183
  }
3178
3184
  }
3179
3185
 
3180
- const _PURE = 0b00000001;
3181
- const _INLINE = 0b00000010;
3182
- const _NOINLINE = 0b00000100;
3183
- const _KEY = 0b00001000;
3186
+ const _PURE = 0b00000001;
3187
+ const _INLINE = 0b00000010;
3188
+ const _NOINLINE = 0b00000100;
3189
+ const _KEY = 0b00001000;
3190
+ const _MANGLEPROP = 0b00010000;
3184
3191
 
3185
3192
  export {
3186
3193
  AST_Accessor,
@@ -3326,4 +3333,5 @@ export {
3326
3333
  _NOINLINE,
3327
3334
  _PURE,
3328
3335
  _KEY,
3336
+ _MANGLEPROP,
3329
3337
  };
@@ -93,10 +93,10 @@ import {
93
93
  AST_Yield,
94
94
 
95
95
  walk,
96
- walk_parent,
97
- walk_abort,
98
96
  walk_body,
99
97
 
98
+ TreeWalker,
99
+
100
100
  _INLINE,
101
101
  _NOINLINE,
102
102
  _PURE
@@ -513,6 +513,10 @@ function handle_defined_after_hoist(parent) {
513
513
  ) return true;
514
514
  });
515
515
 
516
+ const symbols_of_interest = new Set();
517
+ const defuns_of_interest = new Set();
518
+ const potential_conflicts = [];
519
+
516
520
  for (const defun of defuns) {
517
521
  const fname_def = defun.name.definition();
518
522
  const found_self_ref_in_other_defuns = defuns.some(
@@ -542,35 +546,77 @@ function handle_defined_after_hoist(parent) {
542
546
  continue;
543
547
  }
544
548
 
545
- // Detect `call_defun(); var used_in_defun = X`
546
- // Because `used_in_defun` is not certainly X when it's defined after.
547
- let found_defun_ref = false;
548
- let found_def_after_defun = false;
549
- walk_parent(parent, (node, info) => {
550
- if (node === defun) return true;
549
+ // for the slower checks below this loop
550
+ potential_conflicts.push({ defun, def, fname_def });
551
+ symbols_of_interest.add(def.id);
552
+ symbols_of_interest.add(fname_def.id);
553
+ defuns_of_interest.add(defun);
554
+ }
555
+ }
551
556
 
552
- // Step 1: find `call_defun()` or other refs to the defun
553
- if (
554
- !found_defun_ref
555
- && node.thedef === fname_def
556
- && node instanceof AST_Symbol
557
- ) {
558
- found_defun_ref = true;
557
+ // linearize all symbols, and locate defs that are read after the defun
558
+ if (potential_conflicts.length) {
559
+ // All "symbols of interest", that is, defuns or defs, that we found.
560
+ // These are placed in order so we can check which is after which.
561
+ const found_symbols = [];
562
+ // Indices of `found_symbols` which are writes
563
+ const found_symbol_writes = new Set();
564
+ // Defun ranges are recorded because we don't care if a function uses the def internally
565
+ const defun_ranges = new Map();
566
+
567
+ let tw;
568
+ parent.walk((tw = new TreeWalker((node, descend) => {
569
+ if (node instanceof AST_Defun && defuns_of_interest.has(node)) {
570
+ const start = found_symbols.length;
571
+ descend();
572
+ const end = found_symbols.length;
573
+
574
+ defun_ranges.set(node, { start, end });
575
+ return true;
576
+ }
577
+ // if we found a defun on the list, mark IN_DEFUN=id and descend
578
+
579
+ if (node instanceof AST_Symbol && node.thedef) {
580
+ const id = node.definition().id;
581
+ if (symbols_of_interest.has(id)) {
582
+ if (node instanceof AST_SymbolDeclaration || is_lhs(node, tw)) {
583
+ found_symbol_writes.add(found_symbols.length);
584
+ }
585
+ found_symbols.push(id);
559
586
  }
587
+ }
588
+ })));
589
+
590
+ for (const { def, defun, fname_def } of potential_conflicts) {
591
+ const defun_range = defun_ranges.get(defun);
592
+
593
+ // find the index in `found_symbols`, with some special rules:
594
+ const find = (sym_id, starting_at = 0, must_be_write = false) => {
595
+ const index = found_symbols.indexOf(sym_id, starting_at);
560
596
 
561
- // Step 2: if Step 1 occurred, find a var the defun uses
562
597
  if (
563
- found_defun_ref
564
- && node.thedef === def
565
- && (node instanceof AST_SymbolDeclaration
566
- || is_lhs(node, info))
598
+ defun_range
599
+ && index >= defun_range.start
600
+ && index < defun_range.end
567
601
  ) {
568
- found_def_after_defun = true;
569
- return walk_abort;
602
+ return find(sym_id, defun_range.end, must_be_write);
603
+ } else if (
604
+ must_be_write
605
+ && index >= 0
606
+ && !found_symbol_writes.has(index)
607
+ ) {
608
+ return find(sym_id, index + 1, must_be_write);
609
+ } else {
610
+ return index;
570
611
  }
571
- });
612
+ };
613
+
614
+ const read_defun_at = find(fname_def.id);
615
+ const wrote_def_at = find(def.id, read_defun_at + 1, true);
616
+
617
+ const wrote_def_after_reading_defun = read_defun_at != -1 && wrote_def_at != -1 && wrote_def_at > read_defun_at;
572
618
 
573
- if (found_def_after_defun) {
619
+ if (wrote_def_after_reading_defun) {
574
620
  def.fixed = false;
575
621
  }
576
622
  }
package/lib/parse.js CHANGED
@@ -163,7 +163,8 @@ import {
163
163
  _INLINE,
164
164
  _NOINLINE,
165
165
  _PURE,
166
- _KEY
166
+ _KEY,
167
+ _MANGLEPROP,
167
168
  } from "./ast.js";
168
169
 
169
170
  var LATEST_RAW = ""; // Only used for numbers and template strings
@@ -2226,7 +2227,7 @@ function parse($TEXT, options) {
2226
2227
  value : tok.value,
2227
2228
  quote : tok.quote
2228
2229
  });
2229
- annotate(ret);
2230
+ annotate(ret);
2230
2231
  break;
2231
2232
  case "regexp":
2232
2233
  const [_, source, flags] = tok.value.match(/^\/(.*)\/(\w*)$/);
@@ -2538,13 +2539,14 @@ function parse($TEXT, options) {
2538
2539
  }
2539
2540
 
2540
2541
  // Create property
2541
- a.push(new AST_ObjectKeyVal({
2542
+ const kv = new AST_ObjectKeyVal({
2542
2543
  start: start,
2543
2544
  quote: start.quote,
2544
2545
  key: name instanceof AST_Node ? name : "" + name,
2545
2546
  value: value,
2546
2547
  end: prev()
2547
- }));
2548
+ });
2549
+ a.push(annotate(kv));
2548
2550
  }
2549
2551
  next();
2550
2552
  return new AST_Object({ properties: a });
@@ -2657,26 +2659,26 @@ function parse($TEXT, options) {
2657
2659
  : AST_ObjectSetter;
2658
2660
 
2659
2661
  name = get_symbol_ast(name);
2660
- return new AccessorClass({
2662
+ return annotate(new AccessorClass({
2661
2663
  start,
2662
2664
  static: is_static,
2663
2665
  key: name,
2664
2666
  quote: name instanceof AST_SymbolMethod ? property_token.quote : undefined,
2665
2667
  value: create_accessor(),
2666
2668
  end: prev()
2667
- });
2669
+ }));
2668
2670
  } else {
2669
2671
  const AccessorClass = accessor_type === "get"
2670
2672
  ? AST_PrivateGetter
2671
2673
  : AST_PrivateSetter;
2672
2674
 
2673
- return new AccessorClass({
2675
+ return annotate(new AccessorClass({
2674
2676
  start,
2675
2677
  static: is_static,
2676
2678
  key: get_symbol_ast(name),
2677
2679
  value: create_accessor(),
2678
2680
  end: prev(),
2679
- });
2681
+ }));
2680
2682
  }
2681
2683
  }
2682
2684
 
@@ -2696,7 +2698,7 @@ function parse($TEXT, options) {
2696
2698
  value : create_accessor(is_generator, is_async),
2697
2699
  end : prev()
2698
2700
  });
2699
- return node;
2701
+ return annotate(node);
2700
2702
  }
2701
2703
 
2702
2704
  if (is_class) {
@@ -2709,14 +2711,16 @@ function parse($TEXT, options) {
2709
2711
  : AST_ClassProperty;
2710
2712
  if (is("operator", "=")) {
2711
2713
  next();
2712
- return new AST_ClassPropertyVariant({
2713
- start,
2714
- static: is_static,
2715
- quote,
2716
- key,
2717
- value: expression(false),
2718
- end: prev()
2719
- });
2714
+ return annotate(
2715
+ new AST_ClassPropertyVariant({
2716
+ start,
2717
+ static: is_static,
2718
+ quote,
2719
+ key,
2720
+ value: expression(false),
2721
+ end: prev()
2722
+ })
2723
+ );
2720
2724
  } else if (
2721
2725
  is("name")
2722
2726
  || is("privatename")
@@ -2724,13 +2728,15 @@ function parse($TEXT, options) {
2724
2728
  || is("punc", ";")
2725
2729
  || is("punc", "}")
2726
2730
  ) {
2727
- return new AST_ClassPropertyVariant({
2728
- start,
2729
- static: is_static,
2730
- quote,
2731
- key,
2732
- end: prev()
2733
- });
2731
+ return annotate(
2732
+ new AST_ClassPropertyVariant({
2733
+ start,
2734
+ static: is_static,
2735
+ quote,
2736
+ key,
2737
+ end: prev()
2738
+ })
2739
+ );
2734
2740
  }
2735
2741
  }
2736
2742
  }
@@ -3093,10 +3099,9 @@ function parse($TEXT, options) {
3093
3099
  }
3094
3100
 
3095
3101
  // Annotate AST_Call, AST_Lambda or AST_New with the special comments
3096
- function annotate(node) {
3097
- var start = node.start;
3098
- var comments = start.comments_before;
3099
- const comments_outside_parens = outer_comments_before_counts.get(start);
3102
+ function annotate(node, before_token = node.start) {
3103
+ var comments = before_token.comments_before;
3104
+ const comments_outside_parens = outer_comments_before_counts.get(before_token);
3100
3105
  var i = comments_outside_parens != null ? comments_outside_parens : comments.length;
3101
3106
  while (--i >= 0) {
3102
3107
  var comment = comments[i];
@@ -3117,8 +3122,13 @@ function parse($TEXT, options) {
3117
3122
  set_annotation(node, _KEY);
3118
3123
  break;
3119
3124
  }
3125
+ if (/[@#]__MANGLE_PROP__/.test(comment.value)) {
3126
+ set_annotation(node, _MANGLEPROP);
3127
+ break;
3128
+ }
3120
3129
  }
3121
3130
  }
3131
+ return node;
3122
3132
  }
3123
3133
 
3124
3134
  var subscripts = function(expr, allow_calls, is_chain) {
package/lib/propmangle.js CHANGED
@@ -70,6 +70,9 @@ import {
70
70
  TreeTransformer,
71
71
  TreeWalker,
72
72
  _KEY,
73
+ _MANGLEPROP,
74
+
75
+ walk,
73
76
  } from "./ast.js";
74
77
  import { domprops } from "../tools/domprops.js";
75
78
 
@@ -188,6 +191,7 @@ function mangle_properties(ast, options) {
188
191
  regex: null,
189
192
  reserved: null,
190
193
  undeclared: false,
194
+ only_annotated: false,
191
195
  }, true);
192
196
 
193
197
  var nth_identifier = options.nth_identifier;
@@ -206,6 +210,7 @@ function mangle_properties(ast, options) {
206
210
  cache = new Map();
207
211
  }
208
212
 
213
+ var only_annotated = options.only_annotated;
209
214
  var regex = options.regex && new RegExp(options.regex);
210
215
 
211
216
  // note debug is either false (disabled), or a string of the debug suffix to use (enabled).
@@ -217,6 +222,7 @@ function mangle_properties(ast, options) {
217
222
  debug_name_suffix = (options.debug === true ? "" : options.debug);
218
223
  }
219
224
 
225
+ var annotated_names = new Set();
220
226
  var names_to_mangle = new Set();
221
227
  var unmangleable = new Set();
222
228
  // Track each already-mangled name to prevent nth_identifier from generating
@@ -225,7 +231,38 @@ function mangle_properties(ast, options) {
225
231
 
226
232
  var keep_quoted = !!options.keep_quoted;
227
233
 
228
- // step 1: find candidates to mangle
234
+ // Step 1: Find all annotated /*@__MANGLEPROP__*/
235
+ walk(ast, node => {
236
+ if (
237
+ node instanceof AST_ClassPrivateProperty
238
+ || node instanceof AST_PrivateMethod
239
+ || node instanceof AST_PrivateGetter
240
+ || node instanceof AST_PrivateSetter
241
+ || node instanceof AST_DotHash
242
+ ) {
243
+ // handled by mangle_private_properties
244
+ } else if (node instanceof AST_ObjectKeyVal) {
245
+ if (
246
+ typeof node.key == "string"
247
+ && has_annotation(node, _MANGLEPROP)
248
+ && can_mangle(node.key)
249
+ ) {
250
+ annotated_names.add(node.key);
251
+ clear_annotation(node, _MANGLEPROP);
252
+ }
253
+ } else if (node instanceof AST_ObjectProperty) {
254
+ // setter or getter, since KeyVal is handled above
255
+ if (
256
+ has_annotation(node, _MANGLEPROP)
257
+ && can_mangle(node.key.name)
258
+ ) {
259
+ annotated_names.add(node.key.name);
260
+ clear_annotation(node, _MANGLEPROP);
261
+ }
262
+ }
263
+ });
264
+
265
+ // step 2: find candidates to mangle
229
266
  ast.walk(new TreeWalker(function(node) {
230
267
  if (
231
268
  node instanceof AST_ClassPrivateProperty
@@ -321,15 +358,19 @@ function mangle_properties(ast, options) {
321
358
  }
322
359
 
323
360
  function should_mangle(name) {
324
- if (regex && !regex.test(name)) return false;
361
+ if (only_annotated && !annotated_names.has(name)) return false;
362
+ if (regex && !regex.test(name)) {
363
+ return annotated_names.has(name);
364
+ }
325
365
  if (reserved.has(name)) return false;
326
366
  return cache.has(name)
327
367
  || names_to_mangle.has(name);
328
368
  }
329
369
 
330
370
  function add(name) {
331
- if (can_mangle(name))
371
+ if (can_mangle(name)) {
332
372
  names_to_mangle.add(name);
373
+ }
333
374
 
334
375
  if (!should_mangle(name)) {
335
376
  unmangleable.add(name);
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.17.7",
7
+ "version": "5.18.1",
8
8
  "engines": {
9
9
  "node": ">=10"
10
10
  },