terser 5.14.0 → 5.15.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,20 @@
1
1
  # Changelog
2
2
 
3
+ ## v5.15.0
4
+ - Basic support for ES2022 class static initializer blocks.
5
+ - Add `AudioWorkletNode` constructor options to domprops list (#1230)
6
+ - Make identity function inliner not inline `id(...expandedArgs)`
7
+
8
+ ## v5.14.2
9
+
10
+ - Security fix for RegExps that should not be evaluated (regexp DDOS)
11
+ - Source maps improvements (#1211)
12
+ - Performance improvements in long property access evaluation (#1213)
13
+
14
+ ## v5.14.1
15
+ - keep_numbers option added to TypeScript defs (#1208)
16
+ - Fixed parsing of nested template strings (#1204)
17
+
3
18
  ## v5.14.0
4
19
  - Switched to @jridgewell/source-map for sourcemap generation (#1190, #1181)
5
20
  - Fixed source maps with non-terminated segments (#1106)
@@ -202,6 +217,13 @@ Hotfix release, fixes package.json "engines" syntax
202
217
  - Module is now distributed as a dual package - You can `import` and `require()` too.
203
218
  - Inline improvements were made
204
219
 
220
+
221
+ -----
222
+
223
+ ## v4.8.1 (backport)
224
+
225
+ - Security fix for RegExps that should not be evaluated (regexp DDOS)
226
+
205
227
  ## v4.8.0
206
228
 
207
229
  - Support for numeric separators (`million = 1_000_000`) was added.
package/bin/terser.mjs ADDED
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env node
2
+
3
+ "use strict";
4
+
5
+ import "../tools/exit.cjs";
6
+
7
+ import fs from "fs"
8
+ import path from "path"
9
+ import program from "commander"
10
+
11
+ import { run_cli } from "../lib/cli.js"
12
+
13
+ const packageJson = {
14
+ name: "terser",
15
+ version: "experimental module CLI"
16
+ }
17
+
18
+ run_cli({ program, packageJson, fs, path }).catch((error) => {
19
+ console.error(error);
20
+ process.exitCode = 1;
21
+ });
@@ -253,7 +253,15 @@ function regexp_source_fix(source) {
253
253
  return (escaped ? "" : "\\") + lineTerminatorEscape[match];
254
254
  });
255
255
  }
256
- const all_flags = "gimuy";
256
+
257
+ // Subset of regexps that is not going to cause regexp based DDOS
258
+ // https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
259
+ const re_safe_regexp = /^[\\/|\0\s\w^$.[\]()]*$/;
260
+
261
+ /** Check if the regexp is safe for Terser to create without risking a RegExp DOS */
262
+ const regexp_is_safe = (source) => re_safe_regexp.test(source);
263
+
264
+ const all_flags = "dgimsuy";
257
265
  function sort_regexp_flags(flags) {
258
266
  const existing_flags = new Set(flags.split(""));
259
267
  let out = "";
@@ -323,7 +331,7 @@ function set_annotation(node, annotation) {
323
331
  ***********************************************************************/
324
332
 
325
333
  var LATEST_RAW = ""; // Only used for numbers and template strings
326
- var LATEST_TEMPLATE_END = true;
334
+ var TEMPLATE_RAWS = new Map(); // Raw template strings
327
335
 
328
336
  var KEYWORDS = "break case catch class const continue debugger default delete do else export extends finally for function if in instanceof let new return switch throw try typeof var void while with";
329
337
  var KEYWORDS_ATOM = "false null true";
@@ -854,8 +862,8 @@ function tokenizer($TEXT, filename, html5_comments, shebang) {
854
862
  next(true, true);
855
863
  S.brace_counter++;
856
864
  tok = token(begin ? "template_head" : "template_substitution", content);
857
- LATEST_RAW = raw;
858
- LATEST_TEMPLATE_END = false;
865
+ TEMPLATE_RAWS.set(tok, raw);
866
+ tok.template_end = false;
859
867
  return tok;
860
868
  }
861
869
 
@@ -871,8 +879,8 @@ function tokenizer($TEXT, filename, html5_comments, shebang) {
871
879
  }
872
880
  S.template_braces.pop();
873
881
  tok = token(begin ? "template_head" : "template_substitution", content);
874
- LATEST_RAW = raw;
875
- LATEST_TEMPLATE_END = true;
882
+ TEMPLATE_RAWS.set(tok, raw);
883
+ tok.template_end = true;
876
884
  return tok;
877
885
  });
878
886
 
@@ -2528,19 +2536,19 @@ function parse($TEXT, options) {
2528
2536
 
2529
2537
  segments.push(new AST_TemplateSegment({
2530
2538
  start: S.token,
2531
- raw: LATEST_RAW,
2539
+ raw: TEMPLATE_RAWS.get(S.token),
2532
2540
  value: S.token.value,
2533
2541
  end: S.token
2534
2542
  }));
2535
2543
 
2536
- while (!LATEST_TEMPLATE_END) {
2544
+ while (!S.token.template_end) {
2537
2545
  next();
2538
2546
  handle_regexp();
2539
2547
  segments.push(expression(true));
2540
2548
 
2541
2549
  segments.push(new AST_TemplateSegment({
2542
2550
  start: S.token,
2543
- raw: LATEST_RAW,
2551
+ raw: TEMPLATE_RAWS.get(S.token),
2544
2552
  value: S.token.value,
2545
2553
  end: S.token
2546
2554
  }));
@@ -2723,6 +2731,10 @@ function parse($TEXT, options) {
2723
2731
  var accessor_type = null;
2724
2732
 
2725
2733
  if (is_class && name === "static" && is_not_method_start()) {
2734
+ const static_block = class_static_block();
2735
+ if (static_block != null) {
2736
+ return static_block;
2737
+ }
2726
2738
  is_static = true;
2727
2739
  name = as_property_name();
2728
2740
  }
@@ -2829,6 +2841,25 @@ function parse($TEXT, options) {
2829
2841
  }
2830
2842
  }
2831
2843
 
2844
+ function class_static_block() {
2845
+ if (!is("punc", "{")) {
2846
+ return null;
2847
+ }
2848
+
2849
+ const start = S.token;
2850
+ const body = [];
2851
+
2852
+ next();
2853
+
2854
+ while (!is("punc", "}")) {
2855
+ body.push(statement());
2856
+ }
2857
+
2858
+ next();
2859
+
2860
+ return new AST_ClassStaticBlock({ start, body, end: prev() });
2861
+ }
2862
+
2832
2863
  function maybe_import_assertion() {
2833
2864
  if (is("name", "assert") && !has_newline_before(S.token)) {
2834
2865
  next();
@@ -3503,6 +3534,7 @@ function parse($TEXT, options) {
3503
3534
  } else {
3504
3535
  toplevel = new AST_Toplevel({ start: start, body: body, end: end });
3505
3536
  }
3537
+ TEMPLATE_RAWS = new Map();
3506
3538
  return toplevel;
3507
3539
  })();
3508
3540
 
@@ -3596,6 +3628,7 @@ const set_tok_flag = (tok, flag, truth) => {
3596
3628
  const TOK_FLAG_NLB = 0b0001;
3597
3629
  const TOK_FLAG_QUOTE_SINGLE = 0b0010;
3598
3630
  const TOK_FLAG_QUOTE_EXISTS = 0b0100;
3631
+ const TOK_FLAG_TEMPLATE_END = 0b1000;
3599
3632
 
3600
3633
  class AST_Token {
3601
3634
  constructor(type, value, line, col, pos, nlb, comments_before, comments_after, file) {
@@ -3631,6 +3664,14 @@ class AST_Token {
3631
3664
  set_tok_flag(this, TOK_FLAG_QUOTE_SINGLE, quote_type === "'");
3632
3665
  set_tok_flag(this, TOK_FLAG_QUOTE_EXISTS, !!quote_type);
3633
3666
  }
3667
+
3668
+ get template_end() {
3669
+ return has_tok_flag(this, TOK_FLAG_TEMPLATE_END);
3670
+ }
3671
+
3672
+ set template_end(new_template_end) {
3673
+ set_tok_flag(this, TOK_FLAG_TEMPLATE_END, new_template_end);
3674
+ }
3634
3675
  }
3635
3676
 
3636
3677
  var AST_Node = DEFNODE("Node", "start end", function AST_Node(props) {
@@ -5766,6 +5807,28 @@ var AST_DefClass = DEFNODE("DefClass", null, function AST_DefClass(props) {
5766
5807
  $documentation: "A class definition",
5767
5808
  }, AST_Class);
5768
5809
 
5810
+ var AST_ClassStaticBlock = DEFNODE("ClassStaticBlock", "body block_scope", function AST_ClassStaticBlock (props) {
5811
+ this.body = props.body;
5812
+ this.block_scope = props.block_scope;
5813
+ this.start = props.start;
5814
+ this.end = props.end;
5815
+ }, {
5816
+ $documentation: "A block containing statements to be executed in the context of the class",
5817
+ $propdoc: {
5818
+ body: "[AST_Statement*] an array of statements",
5819
+ },
5820
+ _walk: function(visitor) {
5821
+ return visitor._visit(this, function() {
5822
+ walk_body(this, visitor);
5823
+ });
5824
+ },
5825
+ _children_backwards(push) {
5826
+ let i = this.body.length;
5827
+ while (i--) push(this.body[i]);
5828
+ },
5829
+ clone: clone_block_scope,
5830
+ }, AST_Scope);
5831
+
5769
5832
  var AST_ClassExpression = DEFNODE("ClassExpression", null, function AST_ClassExpression(props) {
5770
5833
  if (props) {
5771
5834
  this.name = props.name;
@@ -6782,6 +6845,10 @@ def_transform(AST_Class, function(self, tw) {
6782
6845
  self.properties = do_list(self.properties, tw);
6783
6846
  });
6784
6847
 
6848
+ def_transform(AST_ClassStaticBlock, function(self, tw) {
6849
+ self.body = do_list(self.body, tw);
6850
+ });
6851
+
6785
6852
  def_transform(AST_Expansion, function(self, tw) {
6786
6853
  self.expression = self.expression.transform(tw);
6787
6854
  });
@@ -7141,6 +7208,14 @@ def_transform(AST_PrefixedTemplateString, function(self, tw) {
7141
7208
  });
7142
7209
  },
7143
7210
 
7211
+ StaticBlock: function(M) {
7212
+ return new AST_ClassStaticBlock({
7213
+ start : my_start_token(M),
7214
+ end : my_end_token(M),
7215
+ body : M.body.map(from_moz),
7216
+ });
7217
+ },
7218
+
7144
7219
  ArrayExpression: function(M) {
7145
7220
  return new AST_Array({
7146
7221
  start : my_start_token(M),
@@ -8280,6 +8355,13 @@ def_transform(AST_PrefixedTemplateString, function(self, tw) {
8280
8355
  };
8281
8356
  });
8282
8357
 
8358
+ def_to_moz(AST_ClassStaticBlock, function To_Moz_StaticBlock(M) {
8359
+ return {
8360
+ type: "StaticBlock",
8361
+ body: M.body.map(to_moz),
8362
+ };
8363
+ });
8364
+
8283
8365
  def_to_moz(AST_NewTarget, function To_Moz_MetaProperty() {
8284
8366
  return {
8285
8367
  type: "MetaProperty",
@@ -9630,12 +9712,14 @@ function OutputStream(options) {
9630
9712
  output.with_indent(output.next_indent(), function() {
9631
9713
  output.append_comments(self, true);
9632
9714
  });
9715
+ output.add_mapping(self.end);
9633
9716
  output.print("}");
9634
9717
  }
9635
9718
  function print_braced(self, output, allow_directives) {
9636
9719
  if (self.body.length > 0) {
9637
9720
  output.with_block(function() {
9638
9721
  display_body(self.body, false, output, allow_directives);
9722
+ output.add_mapping(self.end);
9639
9723
  });
9640
9724
  } else print_braced_empty(self, output);
9641
9725
  }
@@ -10575,6 +10659,11 @@ function OutputStream(options) {
10575
10659
  }
10576
10660
  self._print_getter_setter(type, false, output);
10577
10661
  });
10662
+ DEFPRINT(AST_ClassStaticBlock, function (self, output) {
10663
+ output.print("static");
10664
+ output.space();
10665
+ print_braced(self, output);
10666
+ });
10578
10667
  AST_Symbol.DEFMETHOD("_do_print", function(output) {
10579
10668
  var def = this.definition();
10580
10669
  output.print_name(def ? def.mangled_name || def.name : this.name);
@@ -12248,6 +12337,11 @@ AST_Class.prototype._size = function () {
12248
12337
  );
12249
12338
  };
12250
12339
 
12340
+ AST_ClassStaticBlock.prototype._size = function () {
12341
+ // "class{}" + semicolons
12342
+ return 7 + list_overhead(this.body);
12343
+ };
12344
+
12251
12345
  AST_ClassProperty.prototype._size = function () {
12252
12346
  return (
12253
12347
  static_size(this.static)
@@ -13118,6 +13212,9 @@ function is_nullish(node, compressor) {
13118
13212
  }
13119
13213
  return any(this.properties, compressor);
13120
13214
  });
13215
+ def_has_side_effects(AST_ClassStaticBlock, function(compressor) {
13216
+ return any(this.body, compressor);
13217
+ });
13121
13218
  def_has_side_effects(AST_Binary, function(compressor) {
13122
13219
  return this.left.has_side_effects(compressor)
13123
13220
  || this.right.has_side_effects(compressor);
@@ -13217,6 +13314,9 @@ function is_nullish(node, compressor) {
13217
13314
  if (this.extends && this.extends.may_throw(compressor)) return true;
13218
13315
  return any(this.properties, compressor);
13219
13316
  });
13317
+ def_may_throw(AST_ClassStaticBlock, function (compressor) {
13318
+ return any(this.body, compressor);
13319
+ });
13220
13320
 
13221
13321
  def_may_throw(AST_Array, function(compressor) {
13222
13322
  return any(this.elements, compressor);
@@ -13385,6 +13485,9 @@ function is_nullish(node, compressor) {
13385
13485
  if (prop.static && prop.value && !prop.value.is_constant_expression(scope)) {
13386
13486
  return false;
13387
13487
  }
13488
+ if (prop instanceof AST_ClassStaticBlock) {
13489
+ return false;
13490
+ }
13388
13491
  }
13389
13492
 
13390
13493
  return all_refs_local.call(this, scope);
@@ -13708,9 +13811,18 @@ const aborts = (thing) => thing && thing.aborts();
13708
13811
  }
13709
13812
  return null;
13710
13813
  }
13711
- def_aborts(AST_Import, function() { return null; });
13814
+ def_aborts(AST_Import, return_null);
13712
13815
  def_aborts(AST_BlockStatement, block_aborts);
13713
13816
  def_aborts(AST_SwitchBranch, block_aborts);
13817
+ def_aborts(AST_DefClass, function () {
13818
+ for (const prop of this.properties) {
13819
+ if (prop instanceof AST_ClassStaticBlock) {
13820
+ if (prop.aborts()) return prop;
13821
+ }
13822
+ }
13823
+ return null;
13824
+ });
13825
+ def_aborts(AST_ClassStaticBlock, block_aborts);
13714
13826
  def_aborts(AST_If, function() {
13715
13827
  return this.alternative && aborts(this.body) && aborts(this.alternative) && this;
13716
13828
  });
@@ -13841,7 +13953,7 @@ def_eval(AST_BigInt, return_this);
13841
13953
 
13842
13954
  def_eval(AST_RegExp, function (compressor) {
13843
13955
  let evaluated = compressor.evaluated_regexps.get(this.value);
13844
- if (evaluated === undefined) {
13956
+ if (evaluated === undefined && regexp_is_safe(this.value.source)) {
13845
13957
  try {
13846
13958
  const { source, flags } = this.value;
13847
13959
  evaluated = new RegExp(source, flags);
@@ -14054,7 +14166,7 @@ const regexp_flags = new Set([
14054
14166
  ]);
14055
14167
 
14056
14168
  def_eval(AST_PropAccess, function (compressor, depth) {
14057
- const obj = this.expression._eval(compressor, depth);
14169
+ let obj = this.expression._eval(compressor, depth + 1);
14058
14170
  if (obj === nullish || (this.optional && obj == null)) return nullish;
14059
14171
  if (compressor.option("unsafe")) {
14060
14172
  var key = this.property;
@@ -14064,7 +14176,6 @@ def_eval(AST_PropAccess, function (compressor, depth) {
14064
14176
  return this;
14065
14177
  }
14066
14178
  var exp = this.expression;
14067
- var val;
14068
14179
  if (is_undeclared_ref(exp)) {
14069
14180
 
14070
14181
  var aa;
@@ -14081,29 +14192,29 @@ def_eval(AST_PropAccess, function (compressor, depth) {
14081
14192
  }
14082
14193
  if (!is_pure_native_value(exp.name, key))
14083
14194
  return this;
14084
- val = global_objs[exp.name];
14195
+ obj = global_objs[exp.name];
14085
14196
  } else {
14086
- val = exp._eval(compressor, depth + 1);
14087
- if (val instanceof RegExp) {
14197
+ if (obj instanceof RegExp) {
14088
14198
  if (key == "source") {
14089
- return regexp_source_fix(val.source);
14199
+ return regexp_source_fix(obj.source);
14090
14200
  } else if (key == "flags" || regexp_flags.has(key)) {
14091
- return val[key];
14201
+ return obj[key];
14092
14202
  }
14093
14203
  }
14094
- if (!val || val === exp || !HOP(val, key))
14204
+ if (!obj || obj === exp || !HOP(obj, key))
14095
14205
  return this;
14096
- if (typeof val == "function")
14206
+
14207
+ if (typeof obj == "function")
14097
14208
  switch (key) {
14098
14209
  case "name":
14099
- return val.node.name ? val.node.name.name : "";
14210
+ return obj.node.name ? obj.node.name.name : "";
14100
14211
  case "length":
14101
- return val.node.length_property();
14212
+ return obj.node.length_property();
14102
14213
  default:
14103
14214
  return this;
14104
14215
  }
14105
14216
  }
14106
- return val[key];
14217
+ return obj[key];
14107
14218
  }
14108
14219
  return this;
14109
14220
  });
@@ -14290,6 +14401,14 @@ def_drop_side_effect_free(AST_Class, function (compressor) {
14290
14401
  if (trimmed_extends)
14291
14402
  with_effects.push(trimmed_extends);
14292
14403
  for (const prop of this.properties) {
14404
+ if (prop instanceof AST_ClassStaticBlock) {
14405
+ if (prop.body.some(stat => stat.has_side_effects(compressor))) {
14406
+ return this;
14407
+ } else {
14408
+ continue;
14409
+ }
14410
+ }
14411
+
14293
14412
  const trimmed_prop = prop.drop_side_effect_free(compressor);
14294
14413
  if (trimmed_prop)
14295
14414
  with_effects.push(trimmed_prop);
@@ -14801,6 +14920,10 @@ def_reduce_vars(AST_Class, function(tw, descend) {
14801
14920
  return true;
14802
14921
  });
14803
14922
 
14923
+ def_reduce_vars(AST_ClassStaticBlock, function(tw, descend, compressor) {
14924
+ reset_block_variables(compressor, this);
14925
+ });
14926
+
14804
14927
  def_reduce_vars(AST_Conditional, function(tw) {
14805
14928
  this.condition.walk(tw);
14806
14929
  push(tw);
@@ -16715,6 +16838,7 @@ function inline_into_call(self, fn, compressor) {
16715
16838
  fn.argnames.length === 1
16716
16839
  && (fn.argnames[0] instanceof AST_SymbolFunarg)
16717
16840
  && self.args.length < 2
16841
+ && !(self.args[0] instanceof AST_Expansion)
16718
16842
  && returned instanceof AST_SymbolRef
16719
16843
  && returned.name === fn.argnames[0].name
16720
16844
  ) {
@@ -17674,18 +17798,13 @@ AST_Scope.DEFMETHOD("drop_unused", function(compressor) {
17674
17798
  }
17675
17799
  if ((node instanceof AST_Defun || node instanceof AST_DefClass) && node !== self) {
17676
17800
  const def = node.name.definition();
17677
- let keep = def.global && !drop_funcs || in_use_ids.has(def.id);
17678
- if (!keep) {
17801
+ const keep = def.global && !drop_funcs || in_use_ids.has(def.id);
17802
+ // Class "extends" and static blocks may have side effects
17803
+ const has_side_effects = !keep
17804
+ && node instanceof AST_Class
17805
+ && node.has_side_effects(compressor);
17806
+ if (!keep && !has_side_effects) {
17679
17807
  def.eliminated++;
17680
- if (node instanceof AST_DefClass) {
17681
- // Classes might have extends with side effects
17682
- const side_effects = node.drop_side_effect_free(compressor);
17683
- if (side_effects) {
17684
- return make_node(AST_SimpleStatement, node, {
17685
- body: side_effects
17686
- });
17687
- }
17688
- }
17689
17808
  return in_list ? MAP.skip : make_node(AST_EmptyStatement, node);
17690
17809
  }
17691
17810
  }
@@ -18982,6 +19101,7 @@ def_optimize(AST_Call, function(self, compressor) {
18982
19101
  params.push(value);
18983
19102
  return arg !== value;
18984
19103
  })
19104
+ && regexp_is_safe(params[0])
18985
19105
  ) {
18986
19106
  let [ source, flags ] = params;
18987
19107
  source = regexp_source_fix(new RegExp(source).source);
@@ -20641,6 +20761,12 @@ def_optimize(AST_Dot, function(self, compressor) {
20641
20761
  const sub = self.flatten_object(self.property, compressor);
20642
20762
  if (sub) return sub.optimize(compressor);
20643
20763
  }
20764
+
20765
+ if (self.expression instanceof AST_PropAccess
20766
+ && parent instanceof AST_PropAccess) {
20767
+ return self;
20768
+ }
20769
+
20644
20770
  let ev = self.evaluate(compressor);
20645
20771
  if (ev !== self) {
20646
20772
  ev = make_node_from_constant(ev, self).optimize(compressor);
@@ -20757,6 +20883,11 @@ def_optimize(AST_Class, function(self) {
20757
20883
  return self;
20758
20884
  });
20759
20885
 
20886
+ def_optimize(AST_ClassStaticBlock, function(self, compressor) {
20887
+ tighten_body(self.body, compressor);
20888
+ return self;
20889
+ });
20890
+
20760
20891
  def_optimize(AST_Yield, function(self, compressor) {
20761
20892
  if (self.expression && !self.is_star && is_undefined(self.expression, compressor)) {
20762
20893
  self.expression = null;
@@ -27144,6 +27275,7 @@ var domprops = [
27144
27275
  "outlineStyle",
27145
27276
  "outlineWidth",
27146
27277
  "outputBuffer",
27278
+ "outputChannelCount",
27147
27279
  "outputLatency",
27148
27280
  "outputs",
27149
27281
  "overflow",
@@ -27231,6 +27363,7 @@ var domprops = [
27231
27363
  "palette",
27232
27364
  "pan",
27233
27365
  "panningModel",
27366
+ "parameterData",
27234
27367
  "parameters",
27235
27368
  "parent",
27236
27369
  "parentElement",
@@ -27406,6 +27539,7 @@ var domprops = [
27406
27539
  "processIceMessage",
27407
27540
  "processingEnd",
27408
27541
  "processingStart",
27542
+ "processorOptions",
27409
27543
  "product",
27410
27544
  "productId",
27411
27545
  "productName",
@@ -29846,7 +29980,9 @@ async function run_cli({ program, packageJson, fs, path }) {
29846
29980
  result.enclosed = value.block_scope.enclosed;
29847
29981
  }
29848
29982
  value.CTOR.PROPS.forEach(function(prop) {
29849
- result[prop] = value[prop];
29983
+ if (prop !== "block_scope") {
29984
+ result[prop] = value[prop];
29985
+ }
29850
29986
  });
29851
29987
  return result;
29852
29988
  }
package/lib/ast.js CHANGED
@@ -93,6 +93,7 @@ const set_tok_flag = (tok, flag, truth) => {
93
93
  const TOK_FLAG_NLB = 0b0001;
94
94
  const TOK_FLAG_QUOTE_SINGLE = 0b0010;
95
95
  const TOK_FLAG_QUOTE_EXISTS = 0b0100;
96
+ const TOK_FLAG_TEMPLATE_END = 0b1000;
96
97
 
97
98
  class AST_Token {
98
99
  constructor(type, value, line, col, pos, nlb, comments_before, comments_after, file) {
@@ -128,6 +129,14 @@ class AST_Token {
128
129
  set_tok_flag(this, TOK_FLAG_QUOTE_SINGLE, quote_type === "'");
129
130
  set_tok_flag(this, TOK_FLAG_QUOTE_EXISTS, !!quote_type);
130
131
  }
132
+
133
+ get template_end() {
134
+ return has_tok_flag(this, TOK_FLAG_TEMPLATE_END);
135
+ }
136
+
137
+ set template_end(new_template_end) {
138
+ set_tok_flag(this, TOK_FLAG_TEMPLATE_END, new_template_end);
139
+ }
131
140
  }
132
141
 
133
142
  var AST_Node = DEFNODE("Node", "start end", function AST_Node(props) {
@@ -2263,6 +2272,28 @@ var AST_DefClass = DEFNODE("DefClass", null, function AST_DefClass(props) {
2263
2272
  $documentation: "A class definition",
2264
2273
  }, AST_Class);
2265
2274
 
2275
+ var AST_ClassStaticBlock = DEFNODE("ClassStaticBlock", "body block_scope", function AST_ClassStaticBlock (props) {
2276
+ this.body = props.body;
2277
+ this.block_scope = props.block_scope;
2278
+ this.start = props.start;
2279
+ this.end = props.end;
2280
+ }, {
2281
+ $documentation: "A block containing statements to be executed in the context of the class",
2282
+ $propdoc: {
2283
+ body: "[AST_Statement*] an array of statements",
2284
+ },
2285
+ _walk: function(visitor) {
2286
+ return visitor._visit(this, function() {
2287
+ walk_body(this, visitor);
2288
+ });
2289
+ },
2290
+ _children_backwards(push) {
2291
+ let i = this.body.length;
2292
+ while (i--) push(this.body[i]);
2293
+ },
2294
+ clone: clone_block_scope,
2295
+ }, AST_Scope);
2296
+
2266
2297
  var AST_ClassExpression = DEFNODE("ClassExpression", null, function AST_ClassExpression(props) {
2267
2298
  if (props) {
2268
2299
  this.name = props.name;
@@ -3063,6 +3094,7 @@ export {
3063
3094
  AST_ClassExpression,
3064
3095
  AST_ClassPrivateProperty,
3065
3096
  AST_ClassProperty,
3097
+ AST_ClassStaticBlock,
3066
3098
  AST_ConciseMethod,
3067
3099
  AST_Conditional,
3068
3100
  AST_Const,
package/lib/cli.js CHANGED
@@ -279,7 +279,9 @@ export async function run_cli({ program, packageJson, fs, path }) {
279
279
  result.enclosed = value.block_scope.enclosed;
280
280
  }
281
281
  value.CTOR.PROPS.forEach(function(prop) {
282
- result[prop] = value[prop];
282
+ if (prop !== "block_scope") {
283
+ result[prop] = value[prop];
284
+ }
283
285
  });
284
286
  return result;
285
287
  }
@@ -50,6 +50,7 @@ import {
50
50
  AST_Call,
51
51
  AST_Chain,
52
52
  AST_Class,
53
+ AST_ClassStaticBlock,
53
54
  AST_ClassProperty,
54
55
  AST_ConciseMethod,
55
56
  AST_Conditional,
@@ -156,6 +157,14 @@ def_drop_side_effect_free(AST_Class, function (compressor) {
156
157
  if (trimmed_extends)
157
158
  with_effects.push(trimmed_extends);
158
159
  for (const prop of this.properties) {
160
+ if (prop instanceof AST_ClassStaticBlock) {
161
+ if (prop.body.some(stat => stat.has_side_effects(compressor))) {
162
+ return this;
163
+ } else {
164
+ continue;
165
+ }
166
+ }
167
+
159
168
  const trimmed_prop = prop.drop_side_effect_free(compressor);
160
169
  if (trimmed_prop)
161
170
  with_effects.push(trimmed_prop);
@@ -46,7 +46,8 @@ import {
46
46
  makePredicate,
47
47
  return_this,
48
48
  string_template,
49
- regexp_source_fix
49
+ regexp_source_fix,
50
+ regexp_is_safe,
50
51
  } from "../utils/index.js";
51
52
  import {
52
53
  AST_Array,
@@ -129,7 +130,7 @@ def_eval(AST_BigInt, return_this);
129
130
 
130
131
  def_eval(AST_RegExp, function (compressor) {
131
132
  let evaluated = compressor.evaluated_regexps.get(this.value);
132
- if (evaluated === undefined) {
133
+ if (evaluated === undefined && regexp_is_safe(this.value.source)) {
133
134
  try {
134
135
  const { source, flags } = this.value;
135
136
  evaluated = new RegExp(source, flags);
@@ -342,7 +343,7 @@ const regexp_flags = new Set([
342
343
  ]);
343
344
 
344
345
  def_eval(AST_PropAccess, function (compressor, depth) {
345
- const obj = this.expression._eval(compressor, depth);
346
+ let obj = this.expression._eval(compressor, depth + 1);
346
347
  if (obj === nullish || (this.optional && obj == null)) return nullish;
347
348
  if (compressor.option("unsafe")) {
348
349
  var key = this.property;
@@ -352,7 +353,6 @@ def_eval(AST_PropAccess, function (compressor, depth) {
352
353
  return this;
353
354
  }
354
355
  var exp = this.expression;
355
- var val;
356
356
  if (is_undeclared_ref(exp)) {
357
357
 
358
358
  var aa;
@@ -369,29 +369,29 @@ def_eval(AST_PropAccess, function (compressor, depth) {
369
369
  }
370
370
  if (!is_pure_native_value(exp.name, key))
371
371
  return this;
372
- val = global_objs[exp.name];
372
+ obj = global_objs[exp.name];
373
373
  } else {
374
- val = exp._eval(compressor, depth + 1);
375
- if (val instanceof RegExp) {
374
+ if (obj instanceof RegExp) {
376
375
  if (key == "source") {
377
- return regexp_source_fix(val.source);
376
+ return regexp_source_fix(obj.source);
378
377
  } else if (key == "flags" || regexp_flags.has(key)) {
379
- return val[key];
378
+ return obj[key];
380
379
  }
381
380
  }
382
- if (!val || val === exp || !HOP(val, key))
381
+ if (!obj || obj === exp || !HOP(obj, key))
383
382
  return this;
384
- if (typeof val == "function")
383
+
384
+ if (typeof obj == "function")
385
385
  switch (key) {
386
386
  case "name":
387
- return val.node.name ? val.node.name.name : "";
387
+ return obj.node.name ? obj.node.name.name : "";
388
388
  case "length":
389
- return val.node.length_property();
389
+ return obj.node.length_property();
390
390
  default:
391
391
  return this;
392
392
  }
393
393
  }
394
- return val[key];
394
+ return obj[key];
395
395
  }
396
396
  return this;
397
397
  });
@@ -58,6 +58,7 @@ import {
58
58
  AST_Class,
59
59
  AST_ClassExpression,
60
60
  AST_ClassProperty,
61
+ AST_ClassStaticBlock,
61
62
  AST_ConciseMethod,
62
63
  AST_Conditional,
63
64
  AST_Const,
@@ -158,6 +159,7 @@ import {
158
159
  return_true,
159
160
  regexp_source_fix,
160
161
  has_annotation,
162
+ regexp_is_safe,
161
163
  } from "../utils/index.js";
162
164
  import { first_in_statement } from "../utils/first_in_statement.js";
163
165
  import { equivalent_to } from "../equivalent-to.js";
@@ -832,18 +834,13 @@ AST_Scope.DEFMETHOD("drop_unused", function(compressor) {
832
834
  }
833
835
  if ((node instanceof AST_Defun || node instanceof AST_DefClass) && node !== self) {
834
836
  const def = node.name.definition();
835
- let keep = def.global && !drop_funcs || in_use_ids.has(def.id);
836
- if (!keep) {
837
+ const keep = def.global && !drop_funcs || in_use_ids.has(def.id);
838
+ // Class "extends" and static blocks may have side effects
839
+ const has_side_effects = !keep
840
+ && node instanceof AST_Class
841
+ && node.has_side_effects(compressor);
842
+ if (!keep && !has_side_effects) {
837
843
  def.eliminated++;
838
- if (node instanceof AST_DefClass) {
839
- // Classes might have extends with side effects
840
- const side_effects = node.drop_side_effect_free(compressor);
841
- if (side_effects) {
842
- return make_node(AST_SimpleStatement, node, {
843
- body: side_effects
844
- });
845
- }
846
- }
847
844
  return in_list ? MAP.skip : make_node(AST_EmptyStatement, node);
848
845
  }
849
846
  }
@@ -2140,6 +2137,7 @@ def_optimize(AST_Call, function(self, compressor) {
2140
2137
  params.push(value);
2141
2138
  return arg !== value;
2142
2139
  })
2140
+ && regexp_is_safe(params[0])
2143
2141
  ) {
2144
2142
  let [ source, flags ] = params;
2145
2143
  source = regexp_source_fix(new RegExp(source).source);
@@ -3799,6 +3797,12 @@ def_optimize(AST_Dot, function(self, compressor) {
3799
3797
  const sub = self.flatten_object(self.property, compressor);
3800
3798
  if (sub) return sub.optimize(compressor);
3801
3799
  }
3800
+
3801
+ if (self.expression instanceof AST_PropAccess
3802
+ && parent instanceof AST_PropAccess) {
3803
+ return self;
3804
+ }
3805
+
3802
3806
  let ev = self.evaluate(compressor);
3803
3807
  if (ev !== self) {
3804
3808
  ev = make_node_from_constant(ev, self).optimize(compressor);
@@ -3915,6 +3919,11 @@ def_optimize(AST_Class, function(self) {
3915
3919
  return self;
3916
3920
  });
3917
3921
 
3922
+ def_optimize(AST_ClassStaticBlock, function(self, compressor) {
3923
+ tighten_body(self.body, compressor);
3924
+ return self;
3925
+ });
3926
+
3918
3927
  def_optimize(AST_Yield, function(self, compressor) {
3919
3928
  if (self.expression && !self.is_star && is_undefined(self.expression, compressor)) {
3920
3929
  self.expression = null;
@@ -52,6 +52,8 @@ import {
52
52
  AST_Case,
53
53
  AST_Chain,
54
54
  AST_Class,
55
+ AST_DefClass,
56
+ AST_ClassStaticBlock,
55
57
  AST_ClassProperty,
56
58
  AST_ConciseMethod,
57
59
  AST_Conditional,
@@ -320,6 +322,9 @@ export function is_nullish(node, compressor) {
320
322
  }
321
323
  return any(this.properties, compressor);
322
324
  });
325
+ def_has_side_effects(AST_ClassStaticBlock, function(compressor) {
326
+ return any(this.body, compressor);
327
+ });
323
328
  def_has_side_effects(AST_Binary, function(compressor) {
324
329
  return this.left.has_side_effects(compressor)
325
330
  || this.right.has_side_effects(compressor);
@@ -419,6 +424,9 @@ export function is_nullish(node, compressor) {
419
424
  if (this.extends && this.extends.may_throw(compressor)) return true;
420
425
  return any(this.properties, compressor);
421
426
  });
427
+ def_may_throw(AST_ClassStaticBlock, function (compressor) {
428
+ return any(this.body, compressor);
429
+ });
422
430
 
423
431
  def_may_throw(AST_Array, function(compressor) {
424
432
  return any(this.elements, compressor);
@@ -587,6 +595,9 @@ export function is_nullish(node, compressor) {
587
595
  if (prop.static && prop.value && !prop.value.is_constant_expression(scope)) {
588
596
  return false;
589
597
  }
598
+ if (prop instanceof AST_ClassStaticBlock) {
599
+ return false;
600
+ }
590
601
  }
591
602
 
592
603
  return all_refs_local.call(this, scope);
@@ -910,9 +921,18 @@ export const aborts = (thing) => thing && thing.aborts();
910
921
  }
911
922
  return null;
912
923
  }
913
- def_aborts(AST_Import, function() { return null; });
924
+ def_aborts(AST_Import, return_null);
914
925
  def_aborts(AST_BlockStatement, block_aborts);
915
926
  def_aborts(AST_SwitchBranch, block_aborts);
927
+ def_aborts(AST_DefClass, function () {
928
+ for (const prop of this.properties) {
929
+ if (prop instanceof AST_ClassStaticBlock) {
930
+ if (prop.aborts()) return prop;
931
+ }
932
+ }
933
+ return null;
934
+ });
935
+ def_aborts(AST_ClassStaticBlock, block_aborts);
916
936
  def_aborts(AST_If, function() {
917
937
  return this.alternative && aborts(this.body) && aborts(this.alternative) && this;
918
938
  });
@@ -335,6 +335,7 @@ export function inline_into_call(self, fn, compressor) {
335
335
  fn.argnames.length === 1
336
336
  && (fn.argnames[0] instanceof AST_SymbolFunarg)
337
337
  && self.args.length < 2
338
+ && !(self.args[0] instanceof AST_Expansion)
338
339
  && returned instanceof AST_SymbolRef
339
340
  && returned.name === fn.argnames[0].name
340
341
  ) {
@@ -52,6 +52,7 @@ import {
52
52
  AST_Case,
53
53
  AST_Chain,
54
54
  AST_Class,
55
+ AST_ClassStaticBlock,
55
56
  AST_ClassExpression,
56
57
  AST_Conditional,
57
58
  AST_Default,
@@ -377,6 +378,10 @@ def_reduce_vars(AST_Class, function(tw, descend) {
377
378
  return true;
378
379
  });
379
380
 
381
+ def_reduce_vars(AST_ClassStaticBlock, function(tw, descend, compressor) {
382
+ reset_block_variables(compressor, this);
383
+ });
384
+
380
385
  def_reduce_vars(AST_Conditional, function(tw) {
381
386
  this.condition.walk(tw);
382
387
  push(tw);
@@ -60,6 +60,7 @@ import {
60
60
  AST_Catch,
61
61
  AST_Chain,
62
62
  AST_Class,
63
+ AST_ClassStaticBlock,
63
64
  AST_ClassExpression,
64
65
  AST_ClassProperty,
65
66
  AST_ClassPrivateProperty,
@@ -444,6 +445,14 @@ import { is_basic_identifier_string } from "./parse.js";
444
445
  });
445
446
  },
446
447
 
448
+ StaticBlock: function(M) {
449
+ return new AST_ClassStaticBlock({
450
+ start : my_start_token(M),
451
+ end : my_end_token(M),
452
+ body : M.body.map(from_moz),
453
+ });
454
+ },
455
+
447
456
  ArrayExpression: function(M) {
448
457
  return new AST_Array({
449
458
  start : my_start_token(M),
@@ -1583,6 +1592,13 @@ import { is_basic_identifier_string } from "./parse.js";
1583
1592
  };
1584
1593
  });
1585
1594
 
1595
+ def_to_moz(AST_ClassStaticBlock, function To_Moz_StaticBlock(M) {
1596
+ return {
1597
+ type: "StaticBlock",
1598
+ body: M.body.map(to_moz),
1599
+ };
1600
+ });
1601
+
1586
1602
  def_to_moz(AST_NewTarget, function To_Moz_MetaProperty() {
1587
1603
  return {
1588
1604
  type: "MetaProperty",
package/lib/output.js CHANGED
@@ -70,6 +70,7 @@ import {
70
70
  AST_ClassExpression,
71
71
  AST_ClassPrivateProperty,
72
72
  AST_ClassProperty,
73
+ AST_ClassStaticBlock,
73
74
  AST_ConciseMethod,
74
75
  AST_PrivateGetter,
75
76
  AST_PrivateMethod,
@@ -1247,12 +1248,14 @@ function OutputStream(options) {
1247
1248
  output.with_indent(output.next_indent(), function() {
1248
1249
  output.append_comments(self, true);
1249
1250
  });
1251
+ output.add_mapping(self.end);
1250
1252
  output.print("}");
1251
1253
  }
1252
1254
  function print_braced(self, output, allow_directives) {
1253
1255
  if (self.body.length > 0) {
1254
1256
  output.with_block(function() {
1255
1257
  display_body(self.body, false, output, allow_directives);
1258
+ output.add_mapping(self.end);
1256
1259
  });
1257
1260
  } else print_braced_empty(self, output);
1258
1261
  }
@@ -2192,6 +2195,11 @@ function OutputStream(options) {
2192
2195
  }
2193
2196
  self._print_getter_setter(type, false, output);
2194
2197
  });
2198
+ DEFPRINT(AST_ClassStaticBlock, function (self, output) {
2199
+ output.print("static");
2200
+ output.space();
2201
+ print_braced(self, output);
2202
+ });
2195
2203
  AST_Symbol.DEFMETHOD("_do_print", function(output) {
2196
2204
  var def = this.definition();
2197
2205
  output.print_name(def ? def.mangled_name || def.name : this.name);
package/lib/parse.js CHANGED
@@ -67,6 +67,7 @@ import {
67
67
  AST_ClassExpression,
68
68
  AST_ClassPrivateProperty,
69
69
  AST_ClassProperty,
70
+ AST_ClassStaticBlock,
70
71
  AST_ConciseMethod,
71
72
  AST_PrivateGetter,
72
73
  AST_PrivateMethod,
@@ -162,7 +163,7 @@ import {
162
163
  } from "./ast.js";
163
164
 
164
165
  var LATEST_RAW = ""; // Only used for numbers and template strings
165
- var LATEST_TEMPLATE_END = true;
166
+ var TEMPLATE_RAWS = new Map(); // Raw template strings
166
167
 
167
168
  var KEYWORDS = "break case catch class const continue debugger default delete do else export extends finally for function if in instanceof let new return switch throw try typeof var void while with";
168
169
  var KEYWORDS_ATOM = "false null true";
@@ -693,8 +694,8 @@ function tokenizer($TEXT, filename, html5_comments, shebang) {
693
694
  next(true, true);
694
695
  S.brace_counter++;
695
696
  tok = token(begin ? "template_head" : "template_substitution", content);
696
- LATEST_RAW = raw;
697
- LATEST_TEMPLATE_END = false;
697
+ TEMPLATE_RAWS.set(tok, raw);
698
+ tok.template_end = false;
698
699
  return tok;
699
700
  }
700
701
 
@@ -710,8 +711,8 @@ function tokenizer($TEXT, filename, html5_comments, shebang) {
710
711
  }
711
712
  S.template_braces.pop();
712
713
  tok = token(begin ? "template_head" : "template_substitution", content);
713
- LATEST_RAW = raw;
714
- LATEST_TEMPLATE_END = true;
714
+ TEMPLATE_RAWS.set(tok, raw);
715
+ tok.template_end = true;
715
716
  return tok;
716
717
  });
717
718
 
@@ -2367,19 +2368,19 @@ function parse($TEXT, options) {
2367
2368
 
2368
2369
  segments.push(new AST_TemplateSegment({
2369
2370
  start: S.token,
2370
- raw: LATEST_RAW,
2371
+ raw: TEMPLATE_RAWS.get(S.token),
2371
2372
  value: S.token.value,
2372
2373
  end: S.token
2373
2374
  }));
2374
2375
 
2375
- while (!LATEST_TEMPLATE_END) {
2376
+ while (!S.token.template_end) {
2376
2377
  next();
2377
2378
  handle_regexp();
2378
2379
  segments.push(expression(true));
2379
2380
 
2380
2381
  segments.push(new AST_TemplateSegment({
2381
2382
  start: S.token,
2382
- raw: LATEST_RAW,
2383
+ raw: TEMPLATE_RAWS.get(S.token),
2383
2384
  value: S.token.value,
2384
2385
  end: S.token
2385
2386
  }));
@@ -2562,6 +2563,10 @@ function parse($TEXT, options) {
2562
2563
  var accessor_type = null;
2563
2564
 
2564
2565
  if (is_class && name === "static" && is_not_method_start()) {
2566
+ const static_block = class_static_block();
2567
+ if (static_block != null) {
2568
+ return static_block;
2569
+ }
2565
2570
  is_static = true;
2566
2571
  name = as_property_name();
2567
2572
  }
@@ -2668,6 +2673,25 @@ function parse($TEXT, options) {
2668
2673
  }
2669
2674
  }
2670
2675
 
2676
+ function class_static_block() {
2677
+ if (!is("punc", "{")) {
2678
+ return null;
2679
+ }
2680
+
2681
+ const start = S.token;
2682
+ const body = [];
2683
+
2684
+ next();
2685
+
2686
+ while (!is("punc", "}")) {
2687
+ body.push(statement());
2688
+ }
2689
+
2690
+ next();
2691
+
2692
+ return new AST_ClassStaticBlock({ start, body, end: prev() });
2693
+ }
2694
+
2671
2695
  function maybe_import_assertion() {
2672
2696
  if (is("name", "assert") && !has_newline_before(S.token)) {
2673
2697
  next();
@@ -3342,6 +3366,7 @@ function parse($TEXT, options) {
3342
3366
  } else {
3343
3367
  toplevel = new AST_Toplevel({ start: start, body: body, end: end });
3344
3368
  }
3369
+ TEMPLATE_RAWS = new Map();
3345
3370
  return toplevel;
3346
3371
  })();
3347
3372
 
package/lib/size.js CHANGED
@@ -10,6 +10,7 @@ import {
10
10
  AST_Call,
11
11
  AST_Case,
12
12
  AST_Class,
13
+ AST_ClassStaticBlock,
13
14
  AST_ClassPrivateProperty,
14
15
  AST_ClassProperty,
15
16
  AST_ConciseMethod,
@@ -401,6 +402,11 @@ AST_Class.prototype._size = function () {
401
402
  );
402
403
  };
403
404
 
405
+ AST_ClassStaticBlock.prototype._size = function () {
406
+ // "class{}" + semicolons
407
+ return 7 + list_overhead(this.body);
408
+ };
409
+
404
410
  AST_ClassProperty.prototype._size = function () {
405
411
  return (
406
412
  static_size(this.static)
package/lib/transform.js CHANGED
@@ -53,6 +53,7 @@ import {
53
53
  AST_Catch,
54
54
  AST_Chain,
55
55
  AST_Class,
56
+ AST_ClassStaticBlock,
56
57
  AST_Conditional,
57
58
  AST_Definitions,
58
59
  AST_Destructuring,
@@ -285,6 +286,10 @@ def_transform(AST_Class, function(self, tw) {
285
286
  self.properties = do_list(self.properties, tw);
286
287
  });
287
288
 
289
+ def_transform(AST_ClassStaticBlock, function(self, tw) {
290
+ self.body = do_list(self.body, tw);
291
+ });
292
+
288
293
  def_transform(AST_Expansion, function(self, tw) {
289
294
  self.expression = self.expression.transform(tw);
290
295
  });
@@ -249,7 +249,15 @@ function regexp_source_fix(source) {
249
249
  return (escaped ? "" : "\\") + lineTerminatorEscape[match];
250
250
  });
251
251
  }
252
- const all_flags = "gimuy";
252
+
253
+ // Subset of regexps that is not going to cause regexp based DDOS
254
+ // https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
255
+ const re_safe_regexp = /^[\\/|\0\s\w^$.[\]()]*$/;
256
+
257
+ /** Check if the regexp is safe for Terser to create without risking a RegExp DOS */
258
+ export const regexp_is_safe = (source) => re_safe_regexp.test(source);
259
+
260
+ const all_flags = "dgimsuy";
253
261
  function sort_regexp_flags(flags) {
254
262
  const existing_flags = new Set(flags.split(""));
255
263
  let out = "";
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.14.0",
7
+ "version": "5.15.0",
8
8
  "engines": {
9
9
  "node": ">=10"
10
10
  },
package/tools/domprops.js CHANGED
@@ -6021,6 +6021,7 @@ export var domprops = [
6021
6021
  "outlineStyle",
6022
6022
  "outlineWidth",
6023
6023
  "outputBuffer",
6024
+ "outputChannelCount",
6024
6025
  "outputLatency",
6025
6026
  "outputs",
6026
6027
  "overflow",
@@ -6108,6 +6109,7 @@ export var domprops = [
6108
6109
  "palette",
6109
6110
  "pan",
6110
6111
  "panningModel",
6112
+ "parameterData",
6111
6113
  "parameters",
6112
6114
  "parent",
6113
6115
  "parentElement",
@@ -6283,6 +6285,7 @@ export var domprops = [
6283
6285
  "processIceMessage",
6284
6286
  "processingEnd",
6285
6287
  "processingStart",
6288
+ "processorOptions",
6286
6289
  "product",
6287
6290
  "productId",
6288
6291
  "productName",
package/tools/terser.d.ts CHANGED
@@ -145,6 +145,7 @@ export interface FormatOptions {
145
145
  }) => boolean );
146
146
  ecma?: ECMA;
147
147
  ie8?: boolean;
148
+ keep_numbers?: boolean;
148
149
  indent_level?: number;
149
150
  indent_start?: number;
150
151
  inline_script?: boolean;