source: frontend/node_modules/terser/lib/compress/index.js

Last change on this file was 9af201e, checked in by MBK <marija.karapandzova@…>, 12 days ago

Fix frontend appearance

  • Property mode set to 100644
File size: 149.9 KB
Line 
1/***********************************************************************
2
3 A JavaScript tokenizer / parser / beautifier / compressor.
4 https://github.com/mishoo/UglifyJS2
5
6 -------------------------------- (C) ---------------------------------
7
8 Author: Mihai Bazon
9 <mihai.bazon@gmail.com>
10 http://mihai.bazon.net/blog
11
12 Distributed under the BSD license:
13
14 Copyright 2012 (c) Mihai Bazon <mihai.bazon@gmail.com>
15
16 Redistribution and use in source and binary forms, with or without
17 modification, are permitted provided that the following conditions
18 are met:
19
20 * Redistributions of source code must retain the above
21 copyright notice, this list of conditions and the following
22 disclaimer.
23
24 * Redistributions in binary form must reproduce the above
25 copyright notice, this list of conditions and the following
26 disclaimer in the documentation and/or other materials
27 provided with the distribution.
28
29 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER “AS IS” AND ANY
30 EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
31 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
32 PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE
33 LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
34 OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
35 PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
36 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
37 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
38 TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
39 THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
40 SUCH DAMAGE.
41
42 ***********************************************************************/
43
44import {
45 AST_Accessor,
46 AST_Array,
47 AST_Arrow,
48 AST_Assign,
49 AST_BigInt,
50 AST_Binary,
51 AST_Block,
52 AST_BlockStatement,
53 AST_Boolean,
54 AST_Break,
55 AST_Call,
56 AST_Catch,
57 AST_Chain,
58 AST_Class,
59 AST_ClassProperty,
60 AST_ClassStaticBlock,
61 AST_ConciseMethod,
62 AST_Conditional,
63 AST_Const,
64 AST_Constant,
65 AST_Debugger,
66 AST_Default,
67 AST_DefaultAssign,
68 AST_Definitions,
69 AST_Defun,
70 AST_Destructuring,
71 AST_Directive,
72 AST_Do,
73 AST_Dot,
74 AST_DotHash,
75 AST_DWLoop,
76 AST_EmptyStatement,
77 AST_Exit,
78 AST_Expansion,
79 AST_Export,
80 AST_False,
81 AST_For,
82 AST_ForIn,
83 AST_Function,
84 AST_Hole,
85 AST_If,
86 AST_Import,
87 AST_Infinity,
88 AST_LabeledStatement,
89 AST_Lambda,
90 AST_Let,
91 AST_NaN,
92 AST_New,
93 AST_Node,
94 AST_Null,
95 AST_Number,
96 AST_Object,
97 AST_ObjectKeyVal,
98 AST_ObjectProperty,
99 AST_PrefixedTemplateString,
100 AST_PropAccess,
101 AST_RegExp,
102 AST_Return,
103 AST_Scope,
104 AST_Sequence,
105 AST_SimpleStatement,
106 AST_Statement,
107 AST_String,
108 AST_Sub,
109 AST_Switch,
110 AST_SwitchBranch,
111 AST_Symbol,
112 AST_SymbolClassProperty,
113 AST_SymbolDeclaration,
114 AST_SymbolDefun,
115 AST_SymbolExport,
116 AST_SymbolFunarg,
117 AST_SymbolLambda,
118 AST_SymbolLet,
119 AST_SymbolMethod,
120 AST_SymbolRef,
121 AST_SymbolUsing,
122 AST_TemplateString,
123 AST_This,
124 AST_Toplevel,
125 AST_True,
126 AST_Try,
127 AST_Unary,
128 AST_UnaryPostfix,
129 AST_UnaryPrefix,
130 AST_Undefined,
131 AST_Using,
132 AST_Var,
133 AST_VarDef,
134 AST_While,
135 AST_With,
136 AST_Yield,
137
138 TreeTransformer,
139 TreeWalker,
140 walk,
141 walk_abort,
142
143 _NOINLINE,
144} from "../ast.js";
145import {
146 defaults,
147 HOP,
148 make_node,
149 make_void_0,
150 makePredicate,
151 MAP,
152 remove,
153 return_false,
154 return_true,
155 regexp_source_fix,
156 has_annotation,
157 regexp_is_safe,
158} from "../utils/index.js";
159import { first_in_statement } from "../utils/first_in_statement.js";
160import { equivalent_to } from "../equivalent-to.js";
161import {
162 is_basic_identifier_string,
163 JS_Parse_Error,
164 parse,
165 PRECEDENCE,
166} from "../parse.js";
167import { OutputStream } from "../output.js";
168import { base54, format_mangler_options } from "../scope.js";
169import "../size.js";
170
171import "./evaluate.js";
172import "./drop-side-effect-free.js";
173import "./drop-unused.js";
174import "./reduce-vars.js";
175import {
176 is_undeclared_ref,
177 bitwise_binop,
178 lazy_op,
179 is_nullish,
180 is_undefined,
181 is_lhs,
182 aborts,
183 is_used_in_expression,
184} from "./inference.js";
185import {
186 SQUEEZED,
187 OPTIMIZED,
188 CLEAR_BETWEEN_PASSES,
189 TOP,
190 UNDEFINED,
191 UNUSED,
192 TRUTHY,
193 FALSY,
194 has_flag,
195 set_flag,
196 clear_flag,
197} from "./compressor-flags.js";
198import {
199 make_sequence,
200 best_of,
201 best_of_expression,
202 make_empty_function,
203 make_node_from_constant,
204 merge_sequence,
205 get_simple_key,
206 has_break_or_continue,
207 maintain_this_binding,
208 is_empty,
209 is_identifier_atom,
210 is_reachable,
211 can_be_evicted_from_block,
212 as_statement_array,
213 is_func_expr,
214} from "./common.js";
215import { tighten_body, extract_from_unreachable_code } from "./tighten-body.js";
216import { inline_into_symbolref, inline_into_call } from "./inline.js";
217import "./global-defs.js";
218import { is_pure_native_fn, is_pure_native_method, is_pure_native_static_fn, is_pure_native_static_property, pure_access_globals } from "./native-objects.js";
219
220class Compressor extends TreeWalker {
221 constructor(options, { false_by_default = false, mangle_options = false }) {
222 super();
223 if (options.defaults !== undefined && !options.defaults) false_by_default = true;
224 this.options = defaults(options, {
225 arguments : false,
226 arrows : !false_by_default,
227 booleans : !false_by_default,
228 booleans_as_integers : false,
229 collapse_vars : !false_by_default,
230 comparisons : !false_by_default,
231 computed_props: !false_by_default,
232 conditionals : !false_by_default,
233 dead_code : !false_by_default,
234 defaults : true,
235 directives : !false_by_default,
236 drop_console : false,
237 drop_debugger : !false_by_default,
238 ecma : 5,
239 builtins_ecma : 5,
240 builtins_pure : false,
241 evaluate : !false_by_default,
242 expression : false,
243 global_defs : false,
244 hoist_funs : false,
245 hoist_props : !false_by_default,
246 hoist_vars : false,
247 ie8 : false,
248 if_return : !false_by_default,
249 inline : !false_by_default,
250 join_vars : !false_by_default,
251 keep_classnames: false,
252 keep_fargs : true,
253 keep_fnames : false,
254 keep_infinity : false,
255 lhs_constants : !false_by_default,
256 loops : !false_by_default,
257 module : false,
258 negate_iife : !false_by_default,
259 passes : 1,
260 properties : !false_by_default,
261 pure_getters : !false_by_default && "strict",
262 pure_funcs : null,
263 pure_new : false,
264 reduce_funcs : !false_by_default,
265 reduce_vars : !false_by_default,
266 sequences : !false_by_default,
267 side_effects : !false_by_default,
268 switches : !false_by_default,
269 top_retain : null,
270 toplevel : !!(options && options["top_retain"]),
271 typeofs : !false_by_default,
272 unsafe : false,
273 unsafe_arrows : false,
274 unsafe_comps : false,
275 unsafe_Function: false,
276 unsafe_math : false,
277 unsafe_symbols: false,
278 unsafe_methods: false,
279 unsafe_proto : false,
280 unsafe_regexp : false,
281 unsafe_undefined: false,
282 unused : !false_by_default,
283 warnings : false // legacy
284 }, true);
285 var global_defs = this.options["global_defs"];
286 if (typeof global_defs == "object") for (var key in global_defs) {
287 if (key[0] === "@" && HOP(global_defs, key)) {
288 global_defs[key.slice(1)] = parse(global_defs[key], {
289 expression: true
290 });
291 }
292 }
293 if (this.options["inline"] === true) this.options["inline"] = 3;
294 var pure_funcs = this.options["pure_funcs"];
295 if (typeof pure_funcs == "function") {
296 this.pure_funcs = pure_funcs;
297 } else {
298 this.pure_funcs = pure_funcs ? function(node) {
299 return !pure_funcs.includes(node.expression.print_to_string());
300 } : return_true;
301 }
302 var top_retain = this.options["top_retain"];
303 if (top_retain instanceof RegExp) {
304 this.top_retain = function(def) {
305 return top_retain.test(def.name);
306 };
307 } else if (typeof top_retain == "function") {
308 this.top_retain = top_retain;
309 } else if (top_retain) {
310 if (typeof top_retain == "string") {
311 top_retain = top_retain.split(/,/);
312 }
313 this.top_retain = function(def) {
314 return top_retain.includes(def.name);
315 };
316 }
317 if (this.options["module"]) {
318 this.directives["use strict"] = true;
319 this.options["toplevel"] = true;
320 }
321 var toplevel = this.options["toplevel"];
322 this.toplevel = typeof toplevel == "string" ? {
323 funcs: /funcs/.test(toplevel),
324 vars: /vars/.test(toplevel)
325 } : {
326 funcs: toplevel,
327 vars: toplevel
328 };
329 var sequences = this.options["sequences"];
330 this.sequences_limit = sequences == 1 ? 800 : sequences | 0;
331 this.evaluated_regexps = new Map();
332 this._toplevel = undefined;
333 this._mangle_options = mangle_options
334 ? format_mangler_options(mangle_options)
335 : mangle_options;
336
337 this.pure_access_globals = pure_access_globals(this);
338 this.is_pure_native_fn = is_pure_native_fn(this);
339 this.is_pure_native_method = is_pure_native_method(this);
340 this.is_pure_native_static_fn = is_pure_native_static_fn(this);
341 this.is_pure_native_static_property = is_pure_native_static_property(this);
342 }
343
344 mangle_options() {
345 var nth_identifier = this._mangle_options && this._mangle_options.nth_identifier || base54;
346 var module = this._mangle_options && this._mangle_options.module || this.option("module");
347 return { ie8: this.option("ie8"), nth_identifier, module };
348 }
349
350 option(key) {
351 return this.options[key];
352 }
353
354 exposed(def) {
355 if (def.export) return true;
356 if (def.global) for (var i = 0, len = def.orig.length; i < len; i++)
357 if (!this.toplevel[def.orig[i] instanceof AST_SymbolDefun ? "funcs" : "vars"])
358 return true;
359 return false;
360 }
361
362 in_boolean_context() {
363 if (!this.option("booleans")) return false;
364 var self = this.self();
365 for (var i = 0, p; p = this.parent(i); i++) {
366 if (p instanceof AST_SimpleStatement
367 || p instanceof AST_Conditional && p.condition === self
368 || p instanceof AST_DWLoop && p.condition === self
369 || p instanceof AST_For && p.condition === self
370 || p instanceof AST_If && p.condition === self
371 || p instanceof AST_UnaryPrefix && p.operator == "!" && p.expression === self) {
372 return true;
373 }
374 if (
375 p instanceof AST_Binary
376 && (
377 p.operator == "&&"
378 || p.operator == "||"
379 || p.operator == "??"
380 )
381 || p instanceof AST_Conditional
382 || p.tail_node() === self
383 ) {
384 self = p;
385 } else {
386 return false;
387 }
388 }
389 }
390
391 /** True if compressor.self()'s result will be turned into a 32-bit integer.
392 * ex:
393 * ~{expr}
394 * (1, 2, {expr}) | 0
395 **/
396 in_32_bit_context(other_operand_must_be_number) {
397 if (!this.option("evaluate")) return false;
398 var self = this.self();
399 for (var i = 0, p; p = this.parent(i); i++) {
400 if (p instanceof AST_Binary && bitwise_binop.has(p.operator)) {
401 if (other_operand_must_be_number) {
402 return (self === p.left ? p.right : p.left).is_number(this);
403 } else {
404 return true;
405 }
406 }
407 if (p instanceof AST_UnaryPrefix) {
408 return p.operator === "~";
409 }
410 if (
411 p instanceof AST_Binary
412 && (
413 // Don't talk about p.left. Can change branch taken
414 p.operator == "&&" && p.right === self
415 || p.operator == "||" && p.right === self
416 || p.operator == "??" && p.right === self
417 )
418 || p instanceof AST_Conditional && p.condition !== self
419 || p.tail_node() === self
420 ) {
421 self = p;
422 } else {
423 return false;
424 }
425 }
426 }
427
428 in_computed_key() {
429 if (!this.option("evaluate")) return false;
430 var self = this.self();
431 for (var i = 0, p; p = this.parent(i); i++) {
432 if (p instanceof AST_ObjectProperty && p.key === self) {
433 return true;
434 }
435 }
436 return false;
437 }
438
439 get_toplevel() {
440 return this._toplevel;
441 }
442
443 compress(toplevel) {
444 toplevel = toplevel.resolve_defines(this);
445 this._toplevel = toplevel;
446 if (this.option("expression")) {
447 this._toplevel.process_expression(true);
448 }
449 var passes = +this.options.passes || 1;
450 var min_count = 1 / 0;
451 var stopping = false;
452 var mangle = this.mangle_options();
453 for (var pass = 0; pass < passes; pass++) {
454 this._toplevel.figure_out_scope(mangle);
455 if (pass === 0 && this.option("drop_console")) {
456 // must be run before reduce_vars and compress pass
457 this._toplevel = this._toplevel.drop_console(this.option("drop_console"));
458 }
459 if (pass > 0 || this.option("reduce_vars")) {
460 this._toplevel.reset_opt_flags(this);
461 }
462 this._toplevel = this._toplevel.transform(this);
463 if (passes > 1) {
464 let count = 0;
465 walk(this._toplevel, () => { count++; });
466 if (count < min_count) {
467 min_count = count;
468 stopping = false;
469 } else if (stopping) {
470 break;
471 } else {
472 stopping = true;
473 }
474 }
475 }
476 if (this.option("expression")) {
477 this._toplevel.process_expression(false);
478 }
479 toplevel = this._toplevel;
480 this._toplevel = undefined;
481 return toplevel;
482 }
483
484 before(node, descend) {
485 if (has_flag(node, SQUEEZED)) return node;
486 var was_scope = false;
487 if (node instanceof AST_Scope) {
488 node = node.hoist_properties(this);
489 node = node.hoist_declarations(this);
490 was_scope = true;
491 }
492 // Before https://github.com/mishoo/UglifyJS2/pull/1602 AST_Node.optimize()
493 // would call AST_Node.transform() if a different instance of AST_Node is
494 // produced after def_optimize().
495 // This corrupts TreeWalker.stack, which cause AST look-ups to malfunction.
496 // Migrate and defer all children's AST_Node.transform() to below, which
497 // will now happen after this parent AST_Node has been properly substituted
498 // thus gives a consistent AST snapshot.
499 descend(node, this);
500 // Existing code relies on how AST_Node.optimize() worked, and omitting the
501 // following replacement call would result in degraded efficiency of both
502 // output and performance.
503 descend(node, this);
504 var opt = node.optimize(this);
505 if (was_scope && opt instanceof AST_Scope) {
506 opt.drop_unused(this);
507 descend(opt, this);
508 }
509 if (opt === node) set_flag(opt, SQUEEZED);
510 return opt;
511 }
512
513 /** Alternative to plain is_lhs() which doesn't work within .optimize() */
514 is_lhs() {
515 const self = this.stack[this.stack.length - 1];
516 const parent = this.stack[this.stack.length - 2];
517 return is_lhs(self, parent);
518 }
519}
520
521
522function def_optimize(node, optimizer) {
523 node.DEFMETHOD("optimize", function(compressor) {
524 var self = this;
525 if (has_flag(self, OPTIMIZED)) return self;
526 if (compressor.has_directive("use asm")) return self;
527 var opt = optimizer(self, compressor);
528 set_flag(opt, OPTIMIZED);
529 return opt;
530 });
531}
532
533def_optimize(AST_Node, function(self) {
534 return self;
535});
536
537AST_Toplevel.DEFMETHOD("drop_console", function(options) {
538 const isArray = Array.isArray(options);
539 const tt = new TreeTransformer(function(self) {
540 if (self.TYPE !== "Call") {
541 return;
542 }
543
544 var exp = self.expression;
545
546 if (!(exp instanceof AST_PropAccess)) {
547 return;
548 }
549
550 var name = exp.expression;
551 var property = exp.property;
552 var depth = 2;
553 while (name.expression) {
554 property = name.property;
555 name = name.expression;
556 depth++;
557 }
558
559 if (isArray && !options.includes(property)) {
560 return;
561 }
562
563 if (is_undeclared_ref(name) && name.name == "console") {
564 if (
565 depth === 3
566 && !["call", "apply"].includes(exp.property)
567 && is_used_in_expression(tt)
568 ) {
569 // a (used) call to Function.prototype methods (eg: console.log.bind(console))
570 // but not .call and .apply which would also return undefined.
571 exp.expression = make_empty_function(self);
572 set_flag(exp.expression, SQUEEZED);
573 self.args = [];
574 } else {
575 return make_void_0(self);
576 }
577 }
578 });
579
580 return this.transform(tt);
581});
582
583AST_Node.DEFMETHOD("equivalent_to", function(node) {
584 return equivalent_to(this, node);
585});
586
587AST_Scope.DEFMETHOD("process_expression", function(insert, compressor) {
588 var self = this;
589 var tt = new TreeTransformer(function(node) {
590 if (insert && node instanceof AST_SimpleStatement) {
591 return make_node(AST_Return, node, {
592 value: node.body
593 });
594 }
595 if (!insert && node instanceof AST_Return) {
596 if (compressor) {
597 var value = node.value && node.value.drop_side_effect_free(compressor, true);
598 return value
599 ? make_node(AST_SimpleStatement, node, { body: value })
600 : make_node(AST_EmptyStatement, node);
601 }
602 return make_node(AST_SimpleStatement, node, {
603 body: node.value || make_void_0(node)
604 });
605 }
606 if (node instanceof AST_Class || node instanceof AST_Lambda && node !== self) {
607 return node;
608 }
609 if (node instanceof AST_Block) {
610 var index = node.body.length - 1;
611 if (index >= 0) {
612 node.body[index] = node.body[index].transform(tt);
613 }
614 } else if (node instanceof AST_If) {
615 node.body = node.body.transform(tt);
616 if (node.alternative) {
617 node.alternative = node.alternative.transform(tt);
618 }
619 } else if (node instanceof AST_With) {
620 node.body = node.body.transform(tt);
621 }
622 return node;
623 });
624 self.transform(tt);
625});
626
627AST_Toplevel.DEFMETHOD("reset_opt_flags", function(compressor) {
628 const self = this;
629 const reduce_vars = compressor.option("reduce_vars");
630
631 const preparation = new TreeWalker(function(node, descend) {
632 clear_flag(node, CLEAR_BETWEEN_PASSES);
633 if (reduce_vars) {
634 if (compressor.top_retain
635 && node instanceof AST_Defun // Only functions are retained
636 && preparation.parent() === self
637 ) {
638 set_flag(node, TOP);
639 }
640 return node.reduce_vars(preparation, descend, compressor);
641 }
642 });
643 // Stack of look-up tables to keep track of whether a `SymbolDef` has been
644 // properly assigned before use:
645 // - `push()` & `pop()` when visiting conditional branches
646 preparation.safe_ids = Object.create(null);
647 preparation.in_loop = null;
648 preparation.loop_ids = new Map();
649 preparation.defs_to_safe_ids = new Map();
650 self.walk(preparation);
651});
652
653AST_Symbol.DEFMETHOD("fixed_value", function() {
654 var fixed = this.thedef.fixed;
655 if (!fixed || fixed instanceof AST_Node) return fixed;
656 return fixed();
657});
658
659AST_SymbolRef.DEFMETHOD("is_immutable", function() {
660 var orig = this.definition().orig;
661 return orig.length == 1 && orig[0] instanceof AST_SymbolLambda;
662});
663
664function find_variable(compressor, name) {
665 var scope, i = 0;
666 while (scope = compressor.parent(i++)) {
667 if (scope instanceof AST_Scope) break;
668 if (scope instanceof AST_Catch && scope.argname) {
669 scope = scope.argname.definition().scope;
670 break;
671 }
672 }
673 return scope.find_variable(name);
674}
675
676AST_SymbolRef.DEFMETHOD("is_declared", function(compressor) {
677 return !this.definition().undeclared
678 || (compressor.option("unsafe") || compressor.option("builtins_pure")) && compressor.pure_access_globals(this.name);
679});
680
681/* -----[ optimizers ]----- */
682
683var directives = new Set(["use asm", "use strict"]);
684def_optimize(AST_Directive, function(self, compressor) {
685 if (compressor.option("directives")
686 && (!directives.has(self.value) || compressor.has_directive(self.value) !== self)) {
687 return make_node(AST_EmptyStatement, self);
688 }
689 return self;
690});
691
692def_optimize(AST_Debugger, function(self, compressor) {
693 if (compressor.option("drop_debugger"))
694 return make_node(AST_EmptyStatement, self);
695 return self;
696});
697
698def_optimize(AST_LabeledStatement, function(self, compressor) {
699 if (self.body instanceof AST_Break
700 && compressor.loopcontrol_target(self.body) === self.body) {
701 return make_node(AST_EmptyStatement, self);
702 }
703 return self.label.references.length == 0 ? self.body : self;
704});
705
706def_optimize(AST_Block, function(self, compressor) {
707 tighten_body(self.body, compressor);
708 return self;
709});
710
711function can_be_extracted_from_if_block(node) {
712 return !(
713 node instanceof AST_Const
714 || node instanceof AST_Let
715 || node instanceof AST_Using
716 || node instanceof AST_Class
717 );
718}
719
720def_optimize(AST_BlockStatement, function(self, compressor) {
721 tighten_body(self.body, compressor);
722 switch (self.body.length) {
723 case 1:
724 if (!compressor.has_directive("use strict")
725 && compressor.parent() instanceof AST_If
726 && can_be_extracted_from_if_block(self.body[0])
727 || can_be_evicted_from_block(self.body[0])) {
728 return self.body[0];
729 }
730 break;
731 case 0: return make_node(AST_EmptyStatement, self);
732 }
733 return self;
734});
735
736function opt_AST_Lambda(self, compressor) {
737 tighten_body(self.body, compressor);
738 if (compressor.option("side_effects")
739 && self.body.length == 1
740 && self.body[0] === compressor.has_directive("use strict")) {
741 self.body.length = 0;
742 }
743 return self;
744}
745def_optimize(AST_Lambda, opt_AST_Lambda);
746
747AST_Scope.DEFMETHOD("hoist_declarations", function(compressor) {
748 var self = this;
749 if (compressor.has_directive("use asm")) return self;
750
751 var hoist_funs = compressor.option("hoist_funs");
752 var hoist_vars = compressor.option("hoist_vars");
753
754 if (hoist_funs || hoist_vars) {
755 var dirs = [];
756 var hoisted = [];
757 var vars = new Map(), vars_found = 0, var_decl = 0;
758 // let's count var_decl first, we seem to waste a lot of
759 // space if we hoist `var` when there's only one.
760 walk(self, node => {
761 if (node instanceof AST_Scope && node !== self)
762 return true;
763 if (node instanceof AST_Var) {
764 ++var_decl;
765 return true;
766 }
767 });
768 hoist_vars = hoist_vars && var_decl > 1;
769 var tt = new TreeTransformer(
770 function before(node) {
771 if (node !== self) {
772 if (node instanceof AST_Directive) {
773 dirs.push(node);
774 return make_node(AST_EmptyStatement, node);
775 }
776 if (hoist_funs && node instanceof AST_Defun
777 && !(tt.parent() instanceof AST_Export)
778 && tt.parent() === self) {
779 hoisted.push(node);
780 return make_node(AST_EmptyStatement, node);
781 }
782 if (
783 hoist_vars
784 && node instanceof AST_Var
785 && !node.definitions.some(def => def.name instanceof AST_Destructuring)
786 ) {
787 node.definitions.forEach(function(def) {
788 vars.set(def.name.name, def);
789 ++vars_found;
790 });
791 var seq = node.to_assignments(compressor);
792 var p = tt.parent();
793 if (p instanceof AST_ForIn && p.init === node) {
794 if (seq == null) {
795 var def = node.definitions[0].name;
796 return make_node(AST_SymbolRef, def, def);
797 }
798 return seq;
799 }
800 if (p instanceof AST_For && p.init === node) {
801 return seq;
802 }
803 if (!seq) return make_node(AST_EmptyStatement, node);
804 return make_node(AST_SimpleStatement, node, {
805 body: seq
806 });
807 }
808 if (node instanceof AST_Scope)
809 return node; // to avoid descending in nested scopes
810 }
811 }
812 );
813 self = self.transform(tt);
814 if (vars_found > 0) {
815 // collect only vars which don't show up in self's arguments list
816 var defs = [];
817 const is_lambda = self instanceof AST_Lambda;
818 const args_as_names = is_lambda ? self.args_as_names() : null;
819 vars.forEach((def, name) => {
820 if (is_lambda && args_as_names.some((x) => x.name === def.name.name)) {
821 vars.delete(name);
822 } else {
823 def = def.clone();
824 def.value = null;
825 defs.push(def);
826 vars.set(name, def);
827 }
828 });
829 if (defs.length > 0) {
830 // try to merge in assignments
831 for (var i = 0; i < self.body.length;) {
832 if (self.body[i] instanceof AST_SimpleStatement) {
833 var expr = self.body[i].body, sym, assign;
834 if (expr instanceof AST_Assign
835 && expr.operator == "="
836 && (sym = expr.left) instanceof AST_Symbol
837 && vars.has(sym.name)
838 ) {
839 var def = vars.get(sym.name);
840 if (def.value) break;
841 def.value = expr.right;
842 remove(defs, def);
843 defs.push(def);
844 self.body.splice(i, 1);
845 continue;
846 }
847 if (expr instanceof AST_Sequence
848 && (assign = expr.expressions[0]) instanceof AST_Assign
849 && assign.operator == "="
850 && (sym = assign.left) instanceof AST_Symbol
851 && vars.has(sym.name)
852 ) {
853 var def = vars.get(sym.name);
854 if (def.value) break;
855 def.value = assign.right;
856 remove(defs, def);
857 defs.push(def);
858 self.body[i].body = make_sequence(expr, expr.expressions.slice(1));
859 continue;
860 }
861 }
862 if (self.body[i] instanceof AST_EmptyStatement) {
863 self.body.splice(i, 1);
864 continue;
865 }
866 if (self.body[i] instanceof AST_BlockStatement) {
867 self.body.splice(i, 1, ...self.body[i].body);
868 continue;
869 }
870 break;
871 }
872 defs = make_node(AST_Var, self, {
873 definitions: defs
874 });
875 hoisted.push(defs);
876 }
877 }
878 self.body = dirs.concat(hoisted, self.body);
879 }
880 return self;
881});
882
883AST_Scope.DEFMETHOD("hoist_properties", function(compressor) {
884 var self = this;
885 if (!compressor.option("hoist_props") || compressor.has_directive("use asm")) return self;
886 var top_retain = self instanceof AST_Toplevel && compressor.top_retain || return_false;
887 var defs_by_id = new Map();
888 var hoister = new TreeTransformer(function(node, descend) {
889 if (node instanceof AST_VarDef) {
890 const sym = node.name;
891 let def;
892 let value;
893 if (sym.scope === self
894 && !(sym instanceof AST_SymbolUsing)
895 && (def = sym.definition()).escaped != 1
896 && !def.assignments
897 && !def.direct_access
898 && !def.single_use
899 && !compressor.exposed(def)
900 && !top_retain(def)
901 && (value = sym.fixed_value()) === node.value
902 && value instanceof AST_Object
903 && !value.properties.some(prop =>
904 prop instanceof AST_Expansion || prop.computed_key()
905 )
906 ) {
907 descend(node, this);
908 const defs = new Map();
909 const assignments = [];
910 value.properties.forEach(({ key, value }) => {
911 const scope = hoister.find_scope();
912 const symbol = self.create_symbol(sym.CTOR, {
913 source: sym,
914 scope,
915 conflict_scopes: new Set([
916 scope,
917 ...sym.definition().references.map(ref => ref.scope)
918 ]),
919 tentative_name: sym.name + "_" + key
920 });
921
922 defs.set(String(key), symbol.definition());
923
924 assignments.push(make_node(AST_VarDef, node, {
925 name: symbol,
926 value
927 }));
928 });
929 defs_by_id.set(def.id, defs);
930 return MAP.splice(assignments);
931 }
932 } else if (node instanceof AST_PropAccess
933 && node.expression instanceof AST_SymbolRef
934 ) {
935 const defs = defs_by_id.get(node.expression.definition().id);
936 if (defs) {
937 const def = defs.get(String(get_simple_key(node.property)));
938 const sym = make_node(AST_SymbolRef, node, {
939 name: def.name,
940 scope: node.expression.scope,
941 thedef: def
942 });
943 sym.reference({});
944 return sym;
945 }
946 }
947 });
948 return self.transform(hoister);
949});
950
951def_optimize(AST_SimpleStatement, function(self, compressor) {
952 if (compressor.option("side_effects")) {
953 var body = self.body;
954 var node = body.drop_side_effect_free(compressor, true);
955 if (!node) {
956 return make_node(AST_EmptyStatement, self);
957 }
958 if (node !== body) {
959 return make_node(AST_SimpleStatement, self, { body: node });
960 }
961 }
962 return self;
963});
964
965def_optimize(AST_While, function(self, compressor) {
966 return compressor.option("loops") ? make_node(AST_For, self, self).optimize(compressor) : self;
967});
968
969def_optimize(AST_Do, function(self, compressor) {
970 if (!compressor.option("loops")) return self;
971 var cond = self.condition.tail_node().evaluate(compressor);
972 if (!(cond instanceof AST_Node)) {
973 if (cond) return make_node(AST_For, self, {
974 body: make_node(AST_BlockStatement, self.body, {
975 body: [
976 self.body,
977 make_node(AST_SimpleStatement, self.condition, {
978 body: self.condition
979 })
980 ]
981 })
982 }).optimize(compressor);
983 if (!has_break_or_continue(self, compressor.parent())) {
984 return make_node(AST_BlockStatement, self.body, {
985 body: [
986 self.body,
987 make_node(AST_SimpleStatement, self.condition, {
988 body: self.condition
989 })
990 ]
991 }).optimize(compressor);
992 }
993 }
994 return self;
995});
996
997function if_break_in_loop(self, compressor) {
998 var first = self.body instanceof AST_BlockStatement ? self.body.body[0] : self.body;
999 if (compressor.option("dead_code") && is_break(first)) {
1000 var body = [];
1001 if (self.init instanceof AST_Statement) {
1002 body.push(self.init);
1003 } else if (self.init) {
1004 body.push(make_node(AST_SimpleStatement, self.init, {
1005 body: self.init
1006 }));
1007 }
1008 if (self.condition) {
1009 body.push(make_node(AST_SimpleStatement, self.condition, {
1010 body: self.condition
1011 }));
1012 }
1013 extract_from_unreachable_code(compressor, self.body, body);
1014 return make_node(AST_BlockStatement, self, {
1015 body: body
1016 });
1017 }
1018 if (first instanceof AST_If) {
1019 if (is_break(first.body)) {
1020 if (self.condition) {
1021 self.condition = make_node(AST_Binary, self.condition, {
1022 left: self.condition,
1023 operator: "&&",
1024 right: first.condition.negate(compressor),
1025 });
1026 } else {
1027 self.condition = first.condition.negate(compressor);
1028 }
1029 drop_it(first.alternative);
1030 } else if (is_break(first.alternative)) {
1031 if (self.condition) {
1032 self.condition = make_node(AST_Binary, self.condition, {
1033 left: self.condition,
1034 operator: "&&",
1035 right: first.condition,
1036 });
1037 } else {
1038 self.condition = first.condition;
1039 }
1040 drop_it(first.body);
1041 }
1042 }
1043 return self;
1044
1045 function is_break(node) {
1046 return node instanceof AST_Break
1047 && compressor.loopcontrol_target(node) === compressor.self();
1048 }
1049
1050 function drop_it(rest) {
1051 rest = as_statement_array(rest);
1052 if (self.body instanceof AST_BlockStatement) {
1053 self.body = self.body.clone();
1054 self.body.body = rest.concat(self.body.body.slice(1));
1055 self.body = self.body.transform(compressor);
1056 } else {
1057 self.body = make_node(AST_BlockStatement, self.body, {
1058 body: rest
1059 }).transform(compressor);
1060 }
1061 self = if_break_in_loop(self, compressor);
1062 }
1063}
1064
1065def_optimize(AST_For, function(self, compressor) {
1066 if (!compressor.option("loops")) return self;
1067 if (compressor.option("side_effects") && self.init) {
1068 self.init = self.init.drop_side_effect_free(compressor);
1069 }
1070 if (self.condition) {
1071 var cond = self.condition.evaluate(compressor);
1072 if (!(cond instanceof AST_Node)) {
1073 if (cond) self.condition = null;
1074 else if (!compressor.option("dead_code")) {
1075 var orig = self.condition;
1076 self.condition = make_node_from_constant(cond, self.condition);
1077 self.condition = best_of_expression(self.condition.transform(compressor), orig);
1078 }
1079 }
1080 if (compressor.option("dead_code")) {
1081 if (cond instanceof AST_Node) cond = self.condition.tail_node().evaluate(compressor);
1082 if (!cond) {
1083 var body = [];
1084 extract_from_unreachable_code(compressor, self.body, body);
1085 if (self.init instanceof AST_Statement) {
1086 body.push(self.init);
1087 } else if (self.init) {
1088 body.push(make_node(AST_SimpleStatement, self.init, {
1089 body: self.init
1090 }));
1091 }
1092 body.push(make_node(AST_SimpleStatement, self.condition, {
1093 body: self.condition
1094 }));
1095 return make_node(AST_BlockStatement, self, { body: body }).optimize(compressor);
1096 }
1097 }
1098 }
1099 return if_break_in_loop(self, compressor);
1100});
1101
1102def_optimize(AST_If, function(self, compressor) {
1103 if (is_empty(self.alternative)) self.alternative = null;
1104
1105 if (!compressor.option("conditionals")) return self;
1106 // if condition can be statically determined, drop
1107 // one of the blocks. note, statically determined implies
1108 // “has no side effects”; also it doesn't work for cases like
1109 // `x && true`, though it probably should.
1110 var cond = self.condition.evaluate(compressor);
1111 if (!compressor.option("dead_code") && !(cond instanceof AST_Node)) {
1112 var orig = self.condition;
1113 self.condition = make_node_from_constant(cond, orig);
1114 self.condition = best_of_expression(self.condition.transform(compressor), orig);
1115 }
1116 if (compressor.option("dead_code")) {
1117 if (cond instanceof AST_Node) cond = self.condition.tail_node().evaluate(compressor);
1118 if (!cond) {
1119 var body = [];
1120 extract_from_unreachable_code(compressor, self.body, body);
1121 body.push(make_node(AST_SimpleStatement, self.condition, {
1122 body: self.condition
1123 }));
1124 if (self.alternative) body.push(self.alternative);
1125 return make_node(AST_BlockStatement, self, { body: body }).optimize(compressor);
1126 } else if (!(cond instanceof AST_Node)) {
1127 var body = [];
1128 body.push(make_node(AST_SimpleStatement, self.condition, {
1129 body: self.condition
1130 }));
1131 body.push(self.body);
1132 if (self.alternative) {
1133 extract_from_unreachable_code(compressor, self.alternative, body);
1134 }
1135 return make_node(AST_BlockStatement, self, { body: body }).optimize(compressor);
1136 }
1137 }
1138 var negated = self.condition.negate(compressor);
1139 var self_condition_length = self.condition.size();
1140 var negated_length = negated.size();
1141 var negated_is_best = negated_length < self_condition_length;
1142 if (self.alternative && negated_is_best) {
1143 negated_is_best = false; // because we already do the switch here.
1144 // no need to swap values of self_condition_length and negated_length
1145 // here because they are only used in an equality comparison later on.
1146 self.condition = negated;
1147 var tmp = self.body;
1148 self.body = self.alternative || make_node(AST_EmptyStatement, self);
1149 self.alternative = tmp;
1150 }
1151 if (is_empty(self.body) && is_empty(self.alternative)) {
1152 return make_node(AST_SimpleStatement, self.condition, {
1153 body: self.condition.clone()
1154 }).optimize(compressor);
1155 }
1156 if (self.body instanceof AST_SimpleStatement
1157 && self.alternative instanceof AST_SimpleStatement) {
1158 return make_node(AST_SimpleStatement, self, {
1159 body: make_node(AST_Conditional, self, {
1160 condition : self.condition,
1161 consequent : self.body.body,
1162 alternative : self.alternative.body
1163 })
1164 }).optimize(compressor);
1165 }
1166 if (is_empty(self.alternative) && self.body instanceof AST_SimpleStatement) {
1167 if (self_condition_length === negated_length && !negated_is_best
1168 && self.condition instanceof AST_Binary && self.condition.operator == "||") {
1169 // although the code length of self.condition and negated are the same,
1170 // negated does not require additional surrounding parentheses.
1171 // see https://github.com/mishoo/UglifyJS2/issues/979
1172 negated_is_best = true;
1173 }
1174 if (negated_is_best) return make_node(AST_SimpleStatement, self, {
1175 body: make_node(AST_Binary, self, {
1176 operator : "||",
1177 left : negated,
1178 right : self.body.body
1179 })
1180 }).optimize(compressor);
1181 return make_node(AST_SimpleStatement, self, {
1182 body: make_node(AST_Binary, self, {
1183 operator : "&&",
1184 left : self.condition,
1185 right : self.body.body
1186 })
1187 }).optimize(compressor);
1188 }
1189 if (self.body instanceof AST_EmptyStatement
1190 && self.alternative instanceof AST_SimpleStatement) {
1191 return make_node(AST_SimpleStatement, self, {
1192 body: make_node(AST_Binary, self, {
1193 operator : "||",
1194 left : self.condition,
1195 right : self.alternative.body
1196 })
1197 }).optimize(compressor);
1198 }
1199 if (self.body instanceof AST_Exit
1200 && self.alternative instanceof AST_Exit
1201 && self.body.TYPE == self.alternative.TYPE) {
1202 return make_node(self.body.CTOR, self, {
1203 value: make_node(AST_Conditional, self, {
1204 condition : self.condition,
1205 consequent : self.body.value || make_void_0(self.body),
1206 alternative : self.alternative.value || make_void_0(self.alternative),
1207 }).transform(compressor)
1208 }).optimize(compressor);
1209 }
1210 if (self.body instanceof AST_If
1211 && !self.body.alternative
1212 && !self.alternative) {
1213 self = make_node(AST_If, self, {
1214 condition: make_node(AST_Binary, self.condition, {
1215 operator: "&&",
1216 left: self.condition,
1217 right: self.body.condition
1218 }),
1219 body: self.body.body,
1220 alternative: null
1221 });
1222 }
1223 if (aborts(self.body)) {
1224 if (self.alternative) {
1225 var alt = self.alternative;
1226 self.alternative = null;
1227 return make_node(AST_BlockStatement, self, {
1228 body: [ self, alt ]
1229 }).optimize(compressor);
1230 }
1231 }
1232 if (aborts(self.alternative)) {
1233 var body = self.body;
1234 self.body = self.alternative;
1235 self.condition = negated_is_best ? negated : self.condition.negate(compressor);
1236 self.alternative = null;
1237 return make_node(AST_BlockStatement, self, {
1238 body: [ self, body ]
1239 }).optimize(compressor);
1240 }
1241 return self;
1242});
1243
1244def_optimize(AST_Switch, function(self, compressor) {
1245 if (!compressor.option("switches")) return self;
1246 var branch;
1247 var value = self.expression.evaluate(compressor);
1248 if (!(value instanceof AST_Node)) {
1249 var orig = self.expression;
1250 self.expression = make_node_from_constant(value, orig);
1251 self.expression = best_of_expression(self.expression.transform(compressor), orig);
1252 }
1253 if (!compressor.option("dead_code")) return self;
1254 if (value instanceof AST_Node) {
1255 value = self.expression.tail_node().evaluate(compressor);
1256 }
1257 var decl = [];
1258 var body = [];
1259 var default_branch;
1260 var exact_match;
1261 // - compress self.body into `body`
1262 // - find and deduplicate default branch
1263 // - find the exact match (`case 1234` inside `switch(1234)`)
1264 for (var i = 0, len = self.body.length; i < len && !exact_match; i++) {
1265 branch = self.body[i];
1266 if (branch instanceof AST_Default) {
1267 if (!default_branch) {
1268 default_branch = branch;
1269 } else {
1270 eliminate_branch(branch, body[body.length - 1]);
1271 }
1272 } else if (!(value instanceof AST_Node)) {
1273 var exp = branch.expression.evaluate(compressor);
1274 if (!(exp instanceof AST_Node) && exp !== value) {
1275 eliminate_branch(branch, body[body.length - 1]);
1276 continue;
1277 }
1278 if (exp instanceof AST_Node && !exp.has_side_effects(compressor)) {
1279 exp = branch.expression.tail_node().evaluate(compressor);
1280 }
1281 if (exp === value) {
1282 exact_match = branch;
1283 if (default_branch) {
1284 var default_index = body.indexOf(default_branch);
1285 body.splice(default_index, 1);
1286 eliminate_branch(default_branch, body[default_index - 1]);
1287 default_branch = null;
1288 }
1289 }
1290 }
1291 body.push(branch);
1292 }
1293 // i < len if we found an exact_match. eliminate the rest
1294 while (i < len) eliminate_branch(self.body[i++], body[body.length - 1]);
1295 self.body = body;
1296
1297 let default_or_exact = default_branch || exact_match;
1298 default_branch = null;
1299 exact_match = null;
1300
1301 // group equivalent branches so they will be located next to each other,
1302 // that way the next micro-optimization will merge them.
1303 // ** bail micro-optimization if not a simple switch case with breaks
1304 if (body.every((branch, i) =>
1305 (branch === default_or_exact || branch.expression instanceof AST_Constant)
1306 && (branch.body.length === 0 || aborts(branch) || body.length - 1 === i))
1307 ) {
1308 for (let i = 0; i < body.length; i++) {
1309 const branch = body[i];
1310 for (let j = i + 1; j < body.length; j++) {
1311 const next = body[j];
1312 if (next.body.length === 0) continue;
1313 const last_branch = j === (body.length - 1);
1314 const equivalentBranch = branches_equivalent(next, branch, false);
1315 if (equivalentBranch || (last_branch && branches_equivalent(next, branch, true))) {
1316 if (!equivalentBranch && last_branch) {
1317 next.body.push(make_node(AST_Break));
1318 }
1319
1320 // let's find previous siblings with inert fallthrough...
1321 let x = j - 1;
1322 let fallthroughDepth = 0;
1323 while (x > i) {
1324 if (is_inert_body(body[x--])) {
1325 fallthroughDepth++;
1326 } else {
1327 break;
1328 }
1329 }
1330
1331 const plucked = body.splice(j - fallthroughDepth, 1 + fallthroughDepth);
1332 body.splice(i + 1, 0, ...plucked);
1333 i += plucked.length;
1334 }
1335 }
1336 }
1337 }
1338
1339 // merge equivalent branches in a row
1340 for (let i = 0; i < body.length; i++) {
1341 let branch = body[i];
1342 if (branch.body.length === 0) continue;
1343 if (!aborts(branch)) continue;
1344
1345 for (let j = i + 1; j < body.length; i++, j++) {
1346 let next = body[j];
1347 if (next.body.length === 0) continue;
1348 if (
1349 branches_equivalent(next, branch, false)
1350 || (j === body.length - 1 && branches_equivalent(next, branch, true))
1351 ) {
1352 branch.body = [];
1353 branch = next;
1354 continue;
1355 }
1356 break;
1357 }
1358 }
1359
1360 // Prune any empty branches at the end of the switch statement.
1361 {
1362 let i = body.length - 1;
1363 for (; i >= 0; i--) {
1364 let bbody = body[i].body;
1365 while (is_break(bbody[bbody.length - 1], compressor)) bbody.pop();
1366 if (!is_inert_body(body[i])) break;
1367 }
1368 // i now points to the index of a branch that contains a body. By incrementing, it's
1369 // pointing to the first branch that's empty.
1370 i++;
1371 if (!default_or_exact || body.indexOf(default_or_exact) >= i) {
1372 // The default behavior is to do nothing. We can take advantage of that to
1373 // remove all case expressions that are side-effect free that also do
1374 // nothing, since they'll default to doing nothing. But we can't remove any
1375 // case expressions before one that would side-effect, since they may cause
1376 // the side-effect to be skipped.
1377 for (let j = body.length - 1; j >= i; j--) {
1378 let branch = body[j];
1379 if (branch === default_or_exact) {
1380 default_or_exact = null;
1381 eliminate_branch(body.pop());
1382 } else if (!branch.expression.has_side_effects(compressor)) {
1383 eliminate_branch(body.pop());
1384 } else {
1385 break;
1386 }
1387 }
1388 }
1389 }
1390
1391
1392 // Prune side-effect free branches that fall into default.
1393 DEFAULT: if (default_or_exact) {
1394 let default_index = body.indexOf(default_or_exact);
1395 let default_body_index = default_index;
1396 for (; default_body_index < body.length - 1; default_body_index++) {
1397 if (!is_inert_body(body[default_body_index])) break;
1398 }
1399 if (default_body_index < body.length - 1) {
1400 break DEFAULT;
1401 }
1402
1403 let side_effect_index = body.length - 1;
1404 for (; side_effect_index >= 0; side_effect_index--) {
1405 let branch = body[side_effect_index];
1406 if (branch === default_or_exact) continue;
1407 if (branch.expression.has_side_effects(compressor)) break;
1408 }
1409 // If the default behavior comes after any side-effect case expressions,
1410 // then we can fold all side-effect free cases into the default branch.
1411 // If the side-effect case is after the default, then any side-effect
1412 // free cases could prevent the side-effect from occurring.
1413 if (default_body_index > side_effect_index) {
1414 let prev_body_index = default_index - 1;
1415 for (; prev_body_index >= 0; prev_body_index--) {
1416 if (!is_inert_body(body[prev_body_index])) break;
1417 }
1418 let before = Math.max(side_effect_index, prev_body_index) + 1;
1419 let after = default_index;
1420 if (side_effect_index > default_index) {
1421 // If the default falls into the same body as a side-effect
1422 // case, then we need preserve that case and only prune the
1423 // cases after it.
1424 after = side_effect_index;
1425 body[side_effect_index].body = body[default_body_index].body;
1426 } else {
1427 // The default will be the last branch.
1428 default_or_exact.body = body[default_body_index].body;
1429 }
1430
1431 // Prune everything after the default (or last side-effect case)
1432 // until the next case with a body.
1433 body.splice(after + 1, default_body_index - after);
1434 // Prune everything before the default that falls into it.
1435 body.splice(before, default_index - before);
1436 }
1437 }
1438
1439 // See if we can remove the switch entirely if all cases (the default) fall into the same case body.
1440 DEFAULT: if (default_or_exact) {
1441 let i = body.findIndex(branch => !is_inert_body(branch));
1442 let caseBody;
1443 // `i` is equal to one of the following:
1444 // - `-1`, there is no body in the switch statement.
1445 // - `body.length - 1`, all cases fall into the same body.
1446 // - anything else, there are multiple bodies in the switch.
1447 if (i === body.length - 1) {
1448 // All cases fall into the case body.
1449 let branch = body[i];
1450 if (has_nested_break(self)) break DEFAULT;
1451
1452 // This is the last case body, and we've already pruned any breaks, so it's
1453 // safe to hoist.
1454 caseBody = make_node(AST_BlockStatement, branch, {
1455 body: branch.body
1456 });
1457 branch.body = [];
1458 } else if (i !== -1) {
1459 // If there are multiple bodies, then we cannot optimize anything.
1460 break DEFAULT;
1461 }
1462
1463 let sideEffect = body.find(
1464 branch => branch !== default_or_exact && branch.expression.has_side_effects(compressor)
1465 );
1466 // If no cases cause a side-effect, we can eliminate the switch entirely.
1467 if (!sideEffect) {
1468 return make_node(AST_BlockStatement, self, {
1469 body: decl.concat(
1470 statement(self.expression),
1471 default_or_exact.expression ? statement(default_or_exact.expression) : [],
1472 caseBody || []
1473 )
1474 }).optimize(compressor);
1475 }
1476
1477 // If we're this far, either there was no body or all cases fell into the same body.
1478 // If there was no body, then we don't need a default branch (because the default is
1479 // do nothing). If there was a body, we'll extract it to after the switch, so the
1480 // switch's new default is to do nothing and we can still prune it.
1481 const default_index = body.indexOf(default_or_exact);
1482 body.splice(default_index, 1);
1483 default_or_exact = null;
1484
1485 if (caseBody) {
1486 // Recurse into switch statement one more time so that we can append the case body
1487 // outside of the switch. This recursion will only happen once since we've pruned
1488 // the default case.
1489 return make_node(AST_BlockStatement, self, {
1490 body: decl.concat(self, caseBody)
1491 }).optimize(compressor);
1492 }
1493 // If we fall here, there is a default branch somewhere, there are no case bodies,
1494 // and there's a side-effect somewhere. Just let the below paths take care of it.
1495 }
1496
1497 // Reintegrate `decl` (var statements)
1498 if (body.length > 0) {
1499 body[0].body = decl.concat(body[0].body);
1500 }
1501 if (body.length == 0) {
1502 return make_node(AST_BlockStatement, self, {
1503 body: decl.concat(statement(self.expression))
1504 }).optimize(compressor);
1505 }
1506
1507 if (body.length == 1 && !has_nested_break(self)) {
1508 // This is the last case body, and we've already pruned any breaks, so it's
1509 // safe to hoist.
1510 let branch = body[0];
1511 return make_node(AST_If, self, {
1512 condition: make_node(AST_Binary, self, {
1513 operator: "===",
1514 left: self.expression,
1515 right: branch.expression,
1516 }),
1517 body: make_node(AST_BlockStatement, branch, {
1518 body: branch.body
1519 }),
1520 alternative: null
1521 }).optimize(compressor);
1522 }
1523 if (body.length === 2 && default_or_exact && !has_nested_break(self)) {
1524 let branch = body[0] === default_or_exact ? body[1] : body[0];
1525 let exact_exp = default_or_exact.expression && statement(default_or_exact.expression);
1526 if (aborts(body[0])) {
1527 // Only the first branch body could have a break (at the last statement)
1528 let first = body[0];
1529 if (is_break(first.body[first.body.length - 1], compressor)) {
1530 first.body.pop();
1531 }
1532 return make_node(AST_If, self, {
1533 condition: make_node(AST_Binary, self, {
1534 operator: "===",
1535 left: self.expression,
1536 right: branch.expression,
1537 }),
1538 body: make_node(AST_BlockStatement, branch, {
1539 body: branch.body
1540 }),
1541 alternative: make_node(AST_BlockStatement, default_or_exact, {
1542 body: [].concat(
1543 exact_exp || [],
1544 default_or_exact.body
1545 )
1546 })
1547 }).optimize(compressor);
1548 }
1549 let operator = "===";
1550 let consequent = make_node(AST_BlockStatement, branch, {
1551 body: branch.body,
1552 });
1553 let always = make_node(AST_BlockStatement, default_or_exact, {
1554 body: [].concat(
1555 exact_exp || [],
1556 default_or_exact.body
1557 )
1558 });
1559 if (body[0] === default_or_exact) {
1560 operator = "!==";
1561 let tmp = always;
1562 always = consequent;
1563 consequent = tmp;
1564 }
1565 return make_node(AST_BlockStatement, self, {
1566 body: [
1567 make_node(AST_If, self, {
1568 condition: make_node(AST_Binary, self, {
1569 operator: operator,
1570 left: self.expression,
1571 right: branch.expression,
1572 }),
1573 body: consequent,
1574 alternative: null,
1575 }),
1576 always,
1577 ],
1578 }).optimize(compressor);
1579 }
1580 return self;
1581
1582 function eliminate_branch(branch, prev) {
1583 if (prev && !aborts(prev)) {
1584 prev.body = prev.body.concat(branch.body);
1585 } else {
1586 extract_from_unreachable_code(compressor, branch, decl);
1587 }
1588 }
1589 function branches_equivalent(branch, prev, insertBreak) {
1590 let bbody = branch.body;
1591 let pbody = prev.body;
1592 if (insertBreak) {
1593 bbody = bbody.concat(make_node(AST_Break));
1594 }
1595 if (bbody.length !== pbody.length) return false;
1596 let bblock = make_node(AST_BlockStatement, branch, { body: bbody });
1597 let pblock = make_node(AST_BlockStatement, prev, { body: pbody });
1598 return bblock.equivalent_to(pblock);
1599 }
1600 function statement(body) {
1601 return make_node(AST_SimpleStatement, body, { body });
1602 }
1603 function has_nested_break(root) {
1604 let has_break = false;
1605
1606 let tw = new TreeWalker(node => {
1607 if (has_break) return true;
1608 if (node instanceof AST_Lambda) return true;
1609 if (node instanceof AST_SimpleStatement) return true;
1610 if (!is_break(node, tw)) return;
1611 let parent = tw.parent();
1612 if (
1613 parent instanceof AST_SwitchBranch
1614 && parent.body[parent.body.length - 1] === node
1615 ) {
1616 return;
1617 }
1618 has_break = true;
1619 });
1620 root.walk(tw);
1621 return has_break;
1622 }
1623 function is_break(node, stack) {
1624 return node instanceof AST_Break
1625 && stack.loopcontrol_target(node) === self;
1626 }
1627 function is_inert_body(branch) {
1628 return !aborts(branch) && !make_node(AST_BlockStatement, branch, {
1629 body: branch.body
1630 }).has_side_effects(compressor);
1631 }
1632});
1633
1634def_optimize(AST_Try, function(self, compressor) {
1635 if (self.bcatch && self.bfinally && self.bfinally.body.every(is_empty)) self.bfinally = null;
1636
1637 if (compressor.option("dead_code") && self.body.body.every(is_empty)) {
1638 var body = [];
1639 if (self.bcatch) {
1640 extract_from_unreachable_code(compressor, self.bcatch, body);
1641 }
1642 if (self.bfinally) body.push(...self.bfinally.body);
1643 return make_node(AST_BlockStatement, self, {
1644 body: body
1645 }).optimize(compressor);
1646 }
1647 return self;
1648});
1649
1650AST_Definitions.DEFMETHOD("to_assignments", function(compressor) {
1651 var reduce_vars = compressor.option("reduce_vars");
1652 var assignments = [];
1653
1654 for (const def of this.definitions) {
1655 if (def.value) {
1656 var name = make_node(AST_SymbolRef, def.name, def.name);
1657 assignments.push(make_node(AST_Assign, def, {
1658 operator : "=",
1659 logical: false,
1660 left : name,
1661 right : def.value
1662 }));
1663 if (reduce_vars) name.definition().fixed = false;
1664 }
1665 const thedef = def.name.definition();
1666 thedef.eliminated++;
1667 thedef.replaced--;
1668 }
1669
1670 if (assignments.length == 0) return null;
1671 return make_sequence(this, assignments);
1672});
1673
1674def_optimize(AST_Definitions, function(self) {
1675 if (self.definitions.length == 0) {
1676 return make_node(AST_EmptyStatement, self);
1677 }
1678 return self;
1679});
1680
1681def_optimize(AST_VarDef, function(self, compressor) {
1682 if (
1683 self.name instanceof AST_SymbolLet
1684 && self.value != null
1685 && is_undefined(self.value, compressor)
1686 ) {
1687 self.value = null;
1688 }
1689 return self;
1690});
1691
1692def_optimize(AST_Import, function(self) {
1693 return self;
1694});
1695
1696def_optimize(AST_Call, function(self, compressor) {
1697 var exp = self.expression;
1698 var fn = exp;
1699 inline_array_like_spread(self.args);
1700 var simple_args = self.args.every((arg) => !(arg instanceof AST_Expansion));
1701
1702 if (compressor.option("reduce_vars") && fn instanceof AST_SymbolRef) {
1703 fn = fn.fixed_value();
1704 }
1705
1706 var is_func = fn instanceof AST_Lambda;
1707
1708 if (is_func && fn.pinned()) return self;
1709
1710 if (compressor.option("unused")
1711 && simple_args
1712 && is_func
1713 && !fn.uses_arguments) {
1714 var pos = 0, last = 0;
1715 for (var i = 0, len = self.args.length; i < len; i++) {
1716 if (fn.argnames[i] instanceof AST_Expansion) {
1717 if (has_flag(fn.argnames[i].expression, UNUSED)) while (i < len) {
1718 var node = self.args[i++].drop_side_effect_free(compressor);
1719 if (node) {
1720 self.args[pos++] = node;
1721 }
1722 } else while (i < len) {
1723 self.args[pos++] = self.args[i++];
1724 }
1725 last = pos;
1726 break;
1727 }
1728 var trim = i >= fn.argnames.length;
1729 if (trim || has_flag(fn.argnames[i], UNUSED)) {
1730 var node = self.args[i].drop_side_effect_free(compressor);
1731 if (node) {
1732 self.args[pos++] = node;
1733 } else if (!trim) {
1734 self.args[pos++] = make_node(AST_Number, self.args[i], {
1735 value: 0
1736 });
1737 continue;
1738 }
1739 } else {
1740 self.args[pos++] = self.args[i];
1741 }
1742 last = pos;
1743 }
1744 self.args.length = last;
1745 }
1746
1747 if (
1748 exp instanceof AST_Dot
1749 && exp.expression instanceof AST_SymbolRef
1750 && exp.expression.name === "console"
1751 && exp.expression.definition().undeclared
1752 && exp.property === "assert"
1753 ) {
1754 const condition = self.args[0];
1755 if (condition) {
1756 const value = condition.evaluate(compressor);
1757
1758 if (value === 1 || value === true) {
1759 return make_void_0(self).optimize(compressor);
1760 }
1761 }
1762 }
1763
1764 if (compressor.option("unsafe") && !exp.contains_optional()) {
1765 if (exp instanceof AST_Dot && exp.start.value === "Array" && exp.property === "from" && self.args.length === 1) {
1766 const [argument] = self.args;
1767 if (argument instanceof AST_Array) {
1768 return make_node(AST_Array, argument, {
1769 elements: argument.elements
1770 }).optimize(compressor);
1771 }
1772 }
1773 if (is_undeclared_ref(exp)) switch (exp.name) {
1774 case "Array":
1775 if (self.args.length != 1) {
1776 return make_node(AST_Array, self, {
1777 elements: self.args
1778 }).optimize(compressor);
1779 } else if (self.args[0] instanceof AST_Number && self.args[0].value <= 11) {
1780 const elements = [];
1781 for (let i = 0; i < self.args[0].value; i++) elements.push(new AST_Hole);
1782 return new AST_Array({ elements });
1783 }
1784 break;
1785 case "Object":
1786 if (self.args.length == 0) {
1787 return make_node(AST_Object, self, {
1788 properties: []
1789 });
1790 }
1791 break;
1792 case "String":
1793 if (self.args.length == 0) return make_node(AST_String, self, {
1794 value: ""
1795 });
1796 if (self.args.length <= 1) return make_node(AST_Binary, self, {
1797 left: self.args[0],
1798 operator: "+",
1799 right: make_node(AST_String, self, { value: "" })
1800 }).optimize(compressor);
1801 break;
1802 case "Number":
1803 if (self.args.length == 0) return make_node(AST_Number, self, {
1804 value: 0
1805 });
1806 if (self.args.length == 1 && compressor.option("unsafe_math")) {
1807 return make_node(AST_UnaryPrefix, self, {
1808 expression: self.args[0],
1809 operator: "+"
1810 }).optimize(compressor);
1811 }
1812 break;
1813 case "Symbol":
1814 if (self.args.length == 1 && self.args[0] instanceof AST_String && compressor.option("unsafe_symbols"))
1815 self.args.length = 0;
1816 break;
1817 case "Boolean":
1818 if (self.args.length == 0) return make_node(AST_False, self);
1819 if (self.args.length == 1) return make_node(AST_UnaryPrefix, self, {
1820 expression: make_node(AST_UnaryPrefix, self, {
1821 expression: self.args[0],
1822 operator: "!"
1823 }),
1824 operator: "!"
1825 }).optimize(compressor);
1826 break;
1827 case "RegExp":
1828 var params = [];
1829 if (self.args.length >= 1
1830 && self.args.length <= 2
1831 && self.args.every((arg) => {
1832 var value = arg.evaluate(compressor);
1833 params.push(value);
1834 return arg !== value;
1835 })
1836 && regexp_is_safe(params[0])
1837 ) {
1838 let [ source, flags ] = params;
1839 source = regexp_source_fix(new RegExp(source).source);
1840 const rx = make_node(AST_RegExp, self, {
1841 value: { source, flags }
1842 });
1843 if (rx._eval(compressor) !== rx) {
1844 return rx;
1845 }
1846 }
1847 break;
1848 } else if (exp instanceof AST_Dot) switch(exp.property) {
1849 case "toString":
1850 if (self.args.length == 0 && !exp.expression.may_throw_on_access(compressor)) {
1851 return make_node(AST_Binary, self, {
1852 left: make_node(AST_String, self, { value: "" }),
1853 operator: "+",
1854 right: exp.expression
1855 }).optimize(compressor);
1856 }
1857 break;
1858 case "join":
1859 if (exp.expression instanceof AST_Array) EXIT: {
1860 var separator;
1861 if (self.args.length > 0) {
1862 separator = self.args[0].evaluate(compressor);
1863 if (separator === self.args[0]) break EXIT; // not a constant
1864 }
1865 var elements = [];
1866 var consts = [];
1867 for (var i = 0, len = exp.expression.elements.length; i < len; i++) {
1868 var el = exp.expression.elements[i];
1869 if (el instanceof AST_Expansion) break EXIT;
1870 var value = el.evaluate(compressor);
1871 if (value !== el) {
1872 consts.push(value);
1873 } else {
1874 if (consts.length > 0) {
1875 elements.push(make_node(AST_String, self, {
1876 value: consts.join(separator)
1877 }));
1878 consts.length = 0;
1879 }
1880 elements.push(el);
1881 }
1882 }
1883 if (consts.length > 0) {
1884 elements.push(make_node(AST_String, self, {
1885 value: consts.join(separator)
1886 }));
1887 }
1888 if (elements.length == 0) return make_node(AST_String, self, { value: "" });
1889 if (elements.length == 1) {
1890 if (elements[0].is_string(compressor)) {
1891 return elements[0];
1892 }
1893 return make_node(AST_Binary, elements[0], {
1894 operator : "+",
1895 left : make_node(AST_String, self, { value: "" }),
1896 right : elements[0]
1897 });
1898 }
1899 if (separator == "") {
1900 var first;
1901 if (elements[0].is_string(compressor)
1902 || elements[1].is_string(compressor)) {
1903 first = elements.shift();
1904 } else {
1905 first = make_node(AST_String, self, { value: "" });
1906 }
1907 return elements.reduce(function(prev, el) {
1908 return make_node(AST_Binary, el, {
1909 operator : "+",
1910 left : prev,
1911 right : el
1912 });
1913 }, first).optimize(compressor);
1914 }
1915 // need this awkward cloning to not affect original element
1916 // best_of will decide which one to get through.
1917 var node = self.clone();
1918 node.expression = node.expression.clone();
1919 node.expression.expression = node.expression.expression.clone();
1920 node.expression.expression.elements = elements;
1921 return best_of(compressor, self, node);
1922 }
1923 break;
1924 case "charAt":
1925 if (exp.expression.is_string(compressor)) {
1926 var arg = self.args[0];
1927 var index = arg ? arg.evaluate(compressor) : 0;
1928 if (index !== arg) {
1929 return make_node(AST_Sub, exp, {
1930 expression: exp.expression,
1931 property: make_node_from_constant(index | 0, arg || exp)
1932 }).optimize(compressor);
1933 }
1934 }
1935 break;
1936 case "apply":
1937 if (self.args.length == 2 && self.args[1] instanceof AST_Array) {
1938 var args = self.args[1].elements.slice();
1939 args.unshift(self.args[0]);
1940 return make_node(AST_Call, self, {
1941 expression: make_node(AST_Dot, exp, {
1942 expression: exp.expression,
1943 optional: false,
1944 property: "call"
1945 }),
1946 args: args
1947 }).optimize(compressor);
1948 }
1949 break;
1950 case "call":
1951 var func = exp.expression;
1952 if (func instanceof AST_SymbolRef) {
1953 func = func.fixed_value();
1954 }
1955 if (func instanceof AST_Lambda && !func.contains_this()) {
1956 return (self.args.length ? make_sequence(this, [
1957 self.args[0],
1958 make_node(AST_Call, self, {
1959 expression: exp.expression,
1960 args: self.args.slice(1)
1961 })
1962 ]) : make_node(AST_Call, self, {
1963 expression: exp.expression,
1964 args: []
1965 })).optimize(compressor);
1966 }
1967 break;
1968 }
1969 }
1970
1971 if (compressor.option("unsafe_Function")
1972 && is_undeclared_ref(exp)
1973 && exp.name == "Function") {
1974 // new Function() => function(){}
1975 if (self.args.length == 0) return make_empty_function(self).optimize(compressor);
1976 if (self.args.every((x) => x instanceof AST_String)) {
1977 // quite a corner-case, but we can handle it:
1978 // https://github.com/mishoo/UglifyJS2/issues/203
1979 // if the code argument is a constant, then we can minify it.
1980 try {
1981 var code = "n(function(" + self.args.slice(0, -1).map(function(arg) {
1982 return arg.value;
1983 }).join(",") + "){" + self.args[self.args.length - 1].value + "})";
1984 var ast = parse(code);
1985 var mangle = compressor.mangle_options();
1986 ast.figure_out_scope(mangle);
1987 var comp = new Compressor(compressor.options, {
1988 mangle_options: compressor._mangle_options
1989 });
1990 ast = ast.transform(comp);
1991 ast.figure_out_scope(mangle);
1992 ast.compute_char_frequency(mangle);
1993 ast.mangle_names(mangle);
1994 var fun;
1995 walk(ast, node => {
1996 if (is_func_expr(node)) {
1997 fun = node;
1998 return walk_abort;
1999 }
2000 });
2001 var code = OutputStream();
2002 AST_BlockStatement.prototype._codegen.call(fun, fun, code);
2003 self.args = [
2004 make_node(AST_String, self, {
2005 value: fun.argnames.map(function(arg) {
2006 return arg.print_to_string();
2007 }).join(",")
2008 }),
2009 make_node(AST_String, self.args[self.args.length - 1], {
2010 value: code.get().replace(/^{|}$/g, "")
2011 })
2012 ];
2013 return self;
2014 } catch (ex) {
2015 if (!(ex instanceof JS_Parse_Error)) {
2016 throw ex;
2017 }
2018
2019 // Otherwise, it crashes at runtime. Or maybe it's nonstandard syntax.
2020 }
2021 }
2022 }
2023
2024 return inline_into_call(self, compressor);
2025});
2026
2027/** Does this node contain optional property access or optional call? */
2028AST_Node.DEFMETHOD("contains_optional", function() {
2029 if (
2030 this instanceof AST_PropAccess
2031 || this instanceof AST_Call
2032 || this instanceof AST_Chain
2033 ) {
2034 if (this.optional) {
2035 return true;
2036 } else {
2037 return this.expression.contains_optional();
2038 }
2039 } else {
2040 return false;
2041 }
2042});
2043
2044def_optimize(AST_New, function(self, compressor) {
2045 if (
2046 compressor.option("unsafe") &&
2047 is_undeclared_ref(self.expression) &&
2048 ["Object", "RegExp", "Function", "Error", "Array"].includes(self.expression.name)
2049 ) return make_node(AST_Call, self, self).transform(compressor);
2050 return self;
2051});
2052
2053def_optimize(AST_Sequence, function(self, compressor) {
2054 if (!compressor.option("side_effects")) return self;
2055 var expressions = [];
2056 filter_for_side_effects();
2057 var end = expressions.length - 1;
2058 trim_right_for_undefined();
2059 if (end == 0) {
2060 self = maintain_this_binding(compressor.parent(), compressor.self(), expressions[0]);
2061 if (!(self instanceof AST_Sequence)) self = self.optimize(compressor);
2062 return self;
2063 }
2064 self.expressions = expressions;
2065 return self;
2066
2067 function filter_for_side_effects() {
2068 var first = first_in_statement(compressor);
2069 var last = self.expressions.length - 1;
2070 self.expressions.forEach(function(expr, index) {
2071 if (index < last) expr = expr.drop_side_effect_free(compressor, first);
2072 if (expr) {
2073 merge_sequence(expressions, expr);
2074 first = false;
2075 }
2076 });
2077 }
2078
2079 function trim_right_for_undefined() {
2080 while (end > 0 && is_undefined(expressions[end], compressor)) end--;
2081 if (end < expressions.length - 1) {
2082 expressions[end] = make_node(AST_UnaryPrefix, self, {
2083 operator : "void",
2084 expression : expressions[end]
2085 });
2086 expressions.length = end + 1;
2087 }
2088 }
2089});
2090
2091AST_Unary.DEFMETHOD("lift_sequences", function(compressor) {
2092 if (compressor.option("sequences")) {
2093 if (this.expression instanceof AST_Sequence) {
2094 var x = this.expression.expressions.slice();
2095 var e = this.clone();
2096 e.expression = x.pop();
2097 x.push(e);
2098 return make_sequence(this, x).optimize(compressor);
2099 }
2100 }
2101 return this;
2102});
2103
2104def_optimize(AST_UnaryPostfix, function(self, compressor) {
2105 return self.lift_sequences(compressor);
2106});
2107
2108def_optimize(AST_UnaryPrefix, function(self, compressor) {
2109 var e = self.expression;
2110 if (
2111 self.operator == "delete" &&
2112 !(
2113 e instanceof AST_SymbolRef ||
2114 e instanceof AST_PropAccess ||
2115 e instanceof AST_Chain ||
2116 is_identifier_atom(e)
2117 )
2118 ) {
2119 return make_sequence(self, [e, make_node(AST_True, self)]).optimize(compressor);
2120 }
2121 // Short-circuit common `void 0`
2122 if (self.operator === "void" && e instanceof AST_Number && e.value === 0) {
2123 return unsafe_undefined_ref(self, compressor) || self;
2124 }
2125 var seq = self.lift_sequences(compressor);
2126 if (seq !== self) {
2127 return seq;
2128 }
2129 if (compressor.option("side_effects") && self.operator == "void") {
2130 e = e.drop_side_effect_free(compressor);
2131 if (e) {
2132 self.expression = e;
2133 return self;
2134 } else {
2135 return make_void_0(self).optimize(compressor);
2136 }
2137 }
2138 if (compressor.in_boolean_context()) {
2139 switch (self.operator) {
2140 case "!":
2141 if (e instanceof AST_UnaryPrefix && e.operator == "!") {
2142 // !!foo ==> foo, if we're in boolean context
2143 return e.expression;
2144 }
2145 if (e instanceof AST_Binary) {
2146 self = best_of(compressor, self, e.negate(compressor, first_in_statement(compressor)));
2147 }
2148 break;
2149 case "typeof":
2150 // typeof always returns a non-empty string, thus it's
2151 // always true in booleans
2152 // And we don't need to check if it's undeclared, because in typeof, that's OK
2153 return (e instanceof AST_SymbolRef ? make_node(AST_True, self) : make_sequence(self, [
2154 e,
2155 make_node(AST_True, self)
2156 ])).optimize(compressor);
2157 }
2158 }
2159 if (self.operator == "-" && e instanceof AST_Infinity) {
2160 e = e.transform(compressor);
2161 }
2162 if (e instanceof AST_Binary
2163 && (self.operator == "+" || self.operator == "-")
2164 && (e.operator == "*" || e.operator == "/" || e.operator == "%")) {
2165 return make_node(AST_Binary, self, {
2166 operator: e.operator,
2167 left: make_node(AST_UnaryPrefix, e.left, {
2168 operator: self.operator,
2169 expression: e.left
2170 }),
2171 right: e.right
2172 });
2173 }
2174
2175 if (compressor.option("evaluate")) {
2176 // ~~x => x (in 32-bit context)
2177 // ~~{32 bit integer} => {32 bit integer}
2178 if (
2179 self.operator === "~"
2180 && self.expression instanceof AST_UnaryPrefix
2181 && self.expression.operator === "~"
2182 && (compressor.in_32_bit_context(false) || self.expression.expression.is_32_bit_integer(compressor))
2183 ) {
2184 return self.expression.expression;
2185 }
2186
2187 // ~(x ^ y) => x ^ ~y
2188 if (
2189 self.operator === "~"
2190 && e instanceof AST_Binary
2191 && e.operator === "^"
2192 ) {
2193 if (e.left instanceof AST_UnaryPrefix && e.left.operator === "~") {
2194 // ~(~x ^ y) => x ^ y
2195 e.left = e.left.bitwise_negate(compressor, true);
2196 } else {
2197 e.right = e.right.bitwise_negate(compressor, true);
2198 }
2199 return e;
2200 }
2201 }
2202
2203 if (
2204 self.operator != "-"
2205 // avoid infinite recursion of numerals
2206 || !(e instanceof AST_Number || e instanceof AST_Infinity || e instanceof AST_BigInt)
2207 ) {
2208 var ev = self.evaluate(compressor);
2209 if (ev !== self) {
2210 ev = make_node_from_constant(ev, self).optimize(compressor);
2211 return best_of(compressor, ev, self);
2212 }
2213 }
2214 return self;
2215});
2216
2217AST_Binary.DEFMETHOD("lift_sequences", function(compressor) {
2218 if (compressor.option("sequences")) {
2219 if (this.left instanceof AST_Sequence) {
2220 var x = this.left.expressions.slice();
2221 var e = this.clone();
2222 e.left = x.pop();
2223 x.push(e);
2224 return make_sequence(this, x).optimize(compressor);
2225 }
2226 if (this.right instanceof AST_Sequence && !this.left.has_side_effects(compressor)) {
2227 var assign = this.operator == "=" && this.left instanceof AST_SymbolRef;
2228 var x = this.right.expressions;
2229 var last = x.length - 1;
2230 for (var i = 0; i < last; i++) {
2231 if (!assign && x[i].has_side_effects(compressor)) break;
2232 }
2233 if (i == last) {
2234 x = x.slice();
2235 var e = this.clone();
2236 e.right = x.pop();
2237 x.push(e);
2238 return make_sequence(this, x).optimize(compressor);
2239 } else if (i > 0) {
2240 var e = this.clone();
2241 e.right = make_sequence(this.right, x.slice(i));
2242 x = x.slice(0, i);
2243 x.push(e);
2244 return make_sequence(this, x).optimize(compressor);
2245 }
2246 }
2247 }
2248 return this;
2249});
2250
2251var commutativeOperators = makePredicate("== === != !== * & | ^");
2252function is_object(node) {
2253 return node instanceof AST_Array
2254 || node instanceof AST_Lambda
2255 || node instanceof AST_Object
2256 || node instanceof AST_Class;
2257}
2258
2259def_optimize(AST_Binary, function(self, compressor) {
2260 function reversible() {
2261 return self.left.is_constant()
2262 || self.right.is_constant()
2263 || !self.left.has_side_effects(compressor)
2264 && !self.right.has_side_effects(compressor);
2265 }
2266 function reverse(op) {
2267 if (reversible()) {
2268 if (op) self.operator = op;
2269 var tmp = self.left;
2270 self.left = self.right;
2271 self.right = tmp;
2272 }
2273 }
2274 if (compressor.option("lhs_constants") && commutativeOperators.has(self.operator)) {
2275 if (self.right.is_constant()
2276 && !self.left.is_constant()) {
2277 // if right is a constant, whatever side effects the
2278 // left side might have could not influence the
2279 // result. hence, force switch.
2280
2281 if (!(self.left instanceof AST_Binary
2282 && PRECEDENCE[self.left.operator] >= PRECEDENCE[self.operator])) {
2283 reverse();
2284 }
2285 }
2286 }
2287 self = self.lift_sequences(compressor);
2288 if (compressor.option("comparisons")) switch (self.operator) {
2289 case "===":
2290 case "!==":
2291 var is_strict_comparison = true;
2292 if (
2293 (self.left.is_string(compressor) && self.right.is_string(compressor)) ||
2294 (self.left.is_number(compressor) && self.right.is_number(compressor)) ||
2295 (self.left.is_bigint(compressor) && self.right.is_bigint(compressor)) ||
2296 (self.left.is_boolean() && self.right.is_boolean()) ||
2297 self.left.equivalent_to(self.right)
2298 ) {
2299 self.operator = self.operator.substr(0, 2);
2300 }
2301
2302 // XXX: intentionally falling down to the next case
2303 case "==":
2304 case "!=":
2305 // void 0 == x => null == x
2306 if (!is_strict_comparison && is_undefined(self.left, compressor)) {
2307 self.left = make_node(AST_Null, self.left);
2308 // x == void 0 => x == null
2309 } else if (!is_strict_comparison && is_undefined(self.right, compressor)) {
2310 self.right = make_node(AST_Null, self.right);
2311 } else if (compressor.option("typeofs")
2312 // "undefined" == typeof x => undefined === x
2313 && self.left instanceof AST_String
2314 && self.left.value == "undefined"
2315 && self.right instanceof AST_UnaryPrefix
2316 && self.right.operator == "typeof") {
2317 var expr = self.right.expression;
2318 if (expr instanceof AST_SymbolRef ? expr.is_declared(compressor)
2319 : !(expr instanceof AST_PropAccess && compressor.option("ie8"))) {
2320 self.right = expr;
2321 self.left = make_void_0(self.left).optimize(compressor);
2322 if (self.operator.length == 2) self.operator += "=";
2323 }
2324 } else if (compressor.option("typeofs")
2325 // typeof x === "undefined" => x === undefined
2326 && self.left instanceof AST_UnaryPrefix
2327 && self.left.operator == "typeof"
2328 && self.right instanceof AST_String
2329 && self.right.value == "undefined") {
2330 var expr = self.left.expression;
2331 if (expr instanceof AST_SymbolRef ? expr.is_declared(compressor)
2332 : !(expr instanceof AST_PropAccess && compressor.option("ie8"))) {
2333 self.left = expr;
2334 self.right = make_void_0(self.right).optimize(compressor);
2335 if (self.operator.length == 2) self.operator += "=";
2336 }
2337 } else if (self.left instanceof AST_SymbolRef
2338 // obj !== obj => false
2339 && self.right instanceof AST_SymbolRef
2340 && self.left.definition() === self.right.definition()
2341 && is_object(self.left.fixed_value())) {
2342 return make_node(self.operator[0] == "=" ? AST_True : AST_False, self);
2343 } else if (self.left.is_32_bit_integer(compressor) && self.right.is_32_bit_integer(compressor)) {
2344 const not = node => make_node(AST_UnaryPrefix, node, {
2345 operator: "!",
2346 expression: node
2347 });
2348 const booleanify = (node, truthy) => {
2349 if (truthy) {
2350 return compressor.in_boolean_context()
2351 ? node
2352 : not(not(node));
2353 } else {
2354 return not(node);
2355 }
2356 };
2357
2358 // The only falsy 32-bit integer is 0
2359 if (self.left instanceof AST_Number && self.left.value === 0) {
2360 return booleanify(self.right, self.operator[0] === "!");
2361 }
2362 if (self.right instanceof AST_Number && self.right.value === 0) {
2363 return booleanify(self.left, self.operator[0] === "!");
2364 }
2365
2366 // Mask all-bits check
2367 // (x & 0xFF) != 0xFF => !(~x & 0xFF)
2368 let and_op, x, mask;
2369 if (
2370 (and_op =
2371 self.left instanceof AST_Binary ? self.left
2372 : self.right instanceof AST_Binary ? self.right : null)
2373 && (mask = and_op === self.left ? self.right : self.left)
2374 && and_op.operator === "&"
2375 && mask instanceof AST_Number
2376 && mask.is_32_bit_integer(compressor)
2377 && (x =
2378 and_op.left.equivalent_to(mask) ? and_op.right
2379 : and_op.right.equivalent_to(mask) ? and_op.left : null)
2380 ) {
2381 let optimized = booleanify(make_node(AST_Binary, self, {
2382 operator: "&",
2383 left: mask,
2384 right: make_node(AST_UnaryPrefix, self, {
2385 operator: "~",
2386 expression: x
2387 })
2388 }), self.operator[0] === "!");
2389
2390 return best_of(compressor, optimized, self);
2391 }
2392 }
2393 break;
2394 case "&&":
2395 case "||":
2396 var lhs = self.left;
2397 if (lhs.operator == self.operator) {
2398 lhs = lhs.right;
2399 }
2400 if (lhs instanceof AST_Binary
2401 && lhs.operator == (self.operator == "&&" ? "!==" : "===")
2402 && self.right instanceof AST_Binary
2403 && lhs.operator == self.right.operator
2404 && (is_undefined(lhs.left, compressor) && self.right.left instanceof AST_Null
2405 || lhs.left instanceof AST_Null && is_undefined(self.right.left, compressor))
2406 && !lhs.right.has_side_effects(compressor)
2407 && lhs.right.equivalent_to(self.right.right)) {
2408 var combined = make_node(AST_Binary, self, {
2409 operator: lhs.operator.slice(0, -1),
2410 left: make_node(AST_Null, self),
2411 right: lhs.right
2412 });
2413 if (lhs !== self.left) {
2414 combined = make_node(AST_Binary, self, {
2415 operator: self.operator,
2416 left: self.left.left,
2417 right: combined
2418 });
2419 }
2420 return combined;
2421 }
2422 break;
2423 }
2424 if (self.operator == "+" && compressor.in_boolean_context()) {
2425 var ll = self.left.evaluate(compressor);
2426 var rr = self.right.evaluate(compressor);
2427 if (ll && typeof ll == "string") {
2428 return make_sequence(self, [
2429 self.right,
2430 make_node(AST_True, self)
2431 ]).optimize(compressor);
2432 }
2433 if (rr && typeof rr == "string") {
2434 return make_sequence(self, [
2435 self.left,
2436 make_node(AST_True, self)
2437 ]).optimize(compressor);
2438 }
2439 }
2440 if (compressor.option("comparisons") && self.is_boolean()) {
2441 if (!(compressor.parent() instanceof AST_Binary)
2442 || compressor.parent() instanceof AST_Assign) {
2443 var negated = make_node(AST_UnaryPrefix, self, {
2444 operator: "!",
2445 expression: self.negate(compressor, first_in_statement(compressor))
2446 });
2447 self = best_of(compressor, self, negated);
2448 }
2449 if (compressor.option("unsafe_comps")) {
2450 switch (self.operator) {
2451 case "<": reverse(">"); break;
2452 case "<=": reverse(">="); break;
2453 }
2454 }
2455 }
2456 if (self.operator == "+") {
2457 if (self.right instanceof AST_String
2458 && self.right.getValue() == ""
2459 && self.left.is_string(compressor)) {
2460 return self.left;
2461 }
2462 if (self.left instanceof AST_String
2463 && self.left.getValue() == ""
2464 && self.right.is_string(compressor)) {
2465 return self.right;
2466 }
2467 if (self.left instanceof AST_Binary
2468 && self.left.operator == "+"
2469 && self.left.left instanceof AST_String
2470 && self.left.left.getValue() == ""
2471 && self.right.is_string(compressor)) {
2472 self.left = self.left.right;
2473 return self;
2474 }
2475 }
2476 if (compressor.option("evaluate")) {
2477 switch (self.operator) {
2478 case "&&":
2479 var ll = has_flag(self.left, TRUTHY)
2480 ? true
2481 : has_flag(self.left, FALSY)
2482 ? false
2483 : self.left.evaluate(compressor);
2484 if (!ll) {
2485 return maintain_this_binding(compressor.parent(), compressor.self(), self.left).optimize(compressor);
2486 } else if (!(ll instanceof AST_Node)) {
2487 return make_sequence(self, [ self.left, self.right ]).optimize(compressor);
2488 }
2489 var rr = self.right.evaluate(compressor);
2490 if (!rr) {
2491 if (compressor.in_boolean_context()) {
2492 return make_sequence(self, [
2493 self.left,
2494 make_node(AST_False, self)
2495 ]).optimize(compressor);
2496 } else {
2497 set_flag(self, FALSY);
2498 }
2499 } else if (!(rr instanceof AST_Node)) {
2500 var parent = compressor.parent();
2501 if (parent.operator == "&&" && parent.left === compressor.self() || compressor.in_boolean_context()) {
2502 return self.left.optimize(compressor);
2503 }
2504 }
2505 // x || false && y ---> x ? y : false
2506 if (self.left.operator == "||") {
2507 var lr = self.left.right.evaluate(compressor);
2508 if (!lr) return make_node(AST_Conditional, self, {
2509 condition: self.left.left,
2510 consequent: self.right,
2511 alternative: self.left.right
2512 }).optimize(compressor);
2513 }
2514 break;
2515 case "||":
2516 var ll = has_flag(self.left, TRUTHY)
2517 ? true
2518 : has_flag(self.left, FALSY)
2519 ? false
2520 : self.left.evaluate(compressor);
2521 if (!ll) {
2522 return make_sequence(self, [ self.left, self.right ]).optimize(compressor);
2523 } else if (!(ll instanceof AST_Node)) {
2524 return maintain_this_binding(compressor.parent(), compressor.self(), self.left).optimize(compressor);
2525 }
2526 var rr = self.right.evaluate(compressor);
2527 if (!rr) {
2528 var parent = compressor.parent();
2529 if (parent.operator == "||" && parent.left === compressor.self() || compressor.in_boolean_context()) {
2530 return self.left.optimize(compressor);
2531 }
2532 } else if (!(rr instanceof AST_Node)) {
2533 if (compressor.in_boolean_context()) {
2534 return make_sequence(self, [
2535 self.left,
2536 make_node(AST_True, self)
2537 ]).optimize(compressor);
2538 } else {
2539 set_flag(self, TRUTHY);
2540 }
2541 }
2542 if (self.left.operator == "&&") {
2543 var lr = self.left.right.evaluate(compressor);
2544 if (lr && !(lr instanceof AST_Node)) return make_node(AST_Conditional, self, {
2545 condition: self.left.left,
2546 consequent: self.left.right,
2547 alternative: self.right
2548 }).optimize(compressor);
2549 }
2550 break;
2551 case "??":
2552 if (is_nullish(self.left, compressor)) {
2553 return self.right;
2554 }
2555
2556 var ll = self.left.evaluate(compressor);
2557 if (!(ll instanceof AST_Node)) {
2558 // if we know the value for sure we can simply compute right away.
2559 return ll == null ? self.right : self.left;
2560 }
2561
2562 if (compressor.in_boolean_context()) {
2563 const rr = self.right.evaluate(compressor);
2564 if (!(rr instanceof AST_Node) && !rr) {
2565 return self.left;
2566 }
2567 }
2568 }
2569 var associative = true;
2570 switch (self.operator) {
2571 case "+":
2572 // (x + "foo") + "bar" => x + "foobar"
2573 if (self.right instanceof AST_Constant
2574 && self.left instanceof AST_Binary
2575 && self.left.operator == "+"
2576 && self.left.is_string(compressor)) {
2577 var binary = make_node(AST_Binary, self, {
2578 operator: "+",
2579 left: self.left.right,
2580 right: self.right,
2581 });
2582 var r = binary.optimize(compressor);
2583 if (binary !== r) {
2584 self = make_node(AST_Binary, self, {
2585 operator: "+",
2586 left: self.left.left,
2587 right: r
2588 });
2589 }
2590 }
2591 // (x + "foo") + ("bar" + y) => (x + "foobar") + y
2592 if (self.left instanceof AST_Binary
2593 && self.left.operator == "+"
2594 && self.left.is_string(compressor)
2595 && self.right instanceof AST_Binary
2596 && self.right.operator == "+"
2597 && self.right.is_string(compressor)) {
2598 var binary = make_node(AST_Binary, self, {
2599 operator: "+",
2600 left: self.left.right,
2601 right: self.right.left,
2602 });
2603 var m = binary.optimize(compressor);
2604 if (binary !== m) {
2605 self = make_node(AST_Binary, self, {
2606 operator: "+",
2607 left: make_node(AST_Binary, self.left, {
2608 operator: "+",
2609 left: self.left.left,
2610 right: m
2611 }),
2612 right: self.right.right
2613 });
2614 }
2615 }
2616 // a + -b => a - b
2617 if (self.right instanceof AST_UnaryPrefix
2618 && self.right.operator == "-"
2619 && self.left.is_number_or_bigint(compressor)) {
2620 self = make_node(AST_Binary, self, {
2621 operator: "-",
2622 left: self.left,
2623 right: self.right.expression
2624 });
2625 break;
2626 }
2627 // -a + b => b - a
2628 if (self.left instanceof AST_UnaryPrefix
2629 && self.left.operator == "-"
2630 && reversible()
2631 && self.right.is_number_or_bigint(compressor)) {
2632 self = make_node(AST_Binary, self, {
2633 operator: "-",
2634 left: self.right,
2635 right: self.left.expression
2636 });
2637 break;
2638 }
2639 // `foo${bar}baz` + 1 => `foo${bar}baz1`
2640 if (self.left instanceof AST_TemplateString) {
2641 var l = self.left;
2642 var r = self.right.evaluate(compressor);
2643 if (r != self.right) {
2644 l.segments[l.segments.length - 1].value += String(r);
2645 return l;
2646 }
2647 }
2648 // 1 + `foo${bar}baz` => `1foo${bar}baz`
2649 if (self.right instanceof AST_TemplateString) {
2650 var r = self.right;
2651 var l = self.left.evaluate(compressor);
2652 if (l != self.left) {
2653 r.segments[0].value = String(l) + r.segments[0].value;
2654 return r;
2655 }
2656 }
2657 // `1${bar}2` + `foo${bar}baz` => `1${bar}2foo${bar}baz`
2658 if (self.left instanceof AST_TemplateString
2659 && self.right instanceof AST_TemplateString) {
2660 var l = self.left;
2661 var segments = l.segments;
2662 var r = self.right;
2663 segments[segments.length - 1].value += r.segments[0].value;
2664 for (var i = 1; i < r.segments.length; i++) {
2665 segments.push(r.segments[i]);
2666 }
2667 return l;
2668 }
2669 case "*":
2670 associative = compressor.option("unsafe_math");
2671 case "&":
2672 case "|":
2673 case "^":
2674 // a + +b => +b + a
2675 if (
2676 self.left.is_number_or_bigint(compressor)
2677 && self.right.is_number_or_bigint(compressor)
2678 && reversible()
2679 && !(self.left instanceof AST_Binary
2680 && self.left.operator != self.operator
2681 && PRECEDENCE[self.left.operator] >= PRECEDENCE[self.operator])) {
2682 var reversed = make_node(AST_Binary, self, {
2683 operator: self.operator,
2684 left: self.right,
2685 right: self.left
2686 });
2687 if (self.right instanceof AST_Constant
2688 && !(self.left instanceof AST_Constant)) {
2689 self = best_of(compressor, reversed, self);
2690 } else {
2691 self = best_of(compressor, self, reversed);
2692 }
2693 }
2694 if (associative && self.is_number_or_bigint(compressor)) {
2695 // a + (b + c) => (a + b) + c
2696 if (self.right instanceof AST_Binary
2697 && self.right.operator == self.operator) {
2698 self = make_node(AST_Binary, self, {
2699 operator: self.operator,
2700 left: make_node(AST_Binary, self.left, {
2701 operator: self.operator,
2702 left: self.left,
2703 right: self.right.left,
2704 start: self.left.start,
2705 end: self.right.left.end
2706 }),
2707 right: self.right.right
2708 });
2709 }
2710 // (n + 2) + 3 => 5 + n
2711 // (2 * n) * 3 => 6 + n
2712 if (self.right instanceof AST_Constant
2713 && self.left instanceof AST_Binary
2714 && self.left.operator == self.operator) {
2715 if (self.left.left instanceof AST_Constant) {
2716 self = make_node(AST_Binary, self, {
2717 operator: self.operator,
2718 left: make_node(AST_Binary, self.left, {
2719 operator: self.operator,
2720 left: self.left.left,
2721 right: self.right,
2722 start: self.left.left.start,
2723 end: self.right.end
2724 }),
2725 right: self.left.right
2726 });
2727 } else if (self.left.right instanceof AST_Constant) {
2728 self = make_node(AST_Binary, self, {
2729 operator: self.operator,
2730 left: make_node(AST_Binary, self.left, {
2731 operator: self.operator,
2732 left: self.left.right,
2733 right: self.right,
2734 start: self.left.right.start,
2735 end: self.right.end
2736 }),
2737 right: self.left.left
2738 });
2739 }
2740 }
2741 // (a | 1) | (2 | d) => (3 | a) | b
2742 if (self.left instanceof AST_Binary
2743 && self.left.operator == self.operator
2744 && self.left.right instanceof AST_Constant
2745 && self.right instanceof AST_Binary
2746 && self.right.operator == self.operator
2747 && self.right.left instanceof AST_Constant) {
2748 self = make_node(AST_Binary, self, {
2749 operator: self.operator,
2750 left: make_node(AST_Binary, self.left, {
2751 operator: self.operator,
2752 left: make_node(AST_Binary, self.left.left, {
2753 operator: self.operator,
2754 left: self.left.right,
2755 right: self.right.left,
2756 start: self.left.right.start,
2757 end: self.right.left.end
2758 }),
2759 right: self.left.left
2760 }),
2761 right: self.right.right
2762 });
2763 }
2764 }
2765 }
2766
2767 // bitwise ops
2768 if (bitwise_binop.has(self.operator)) {
2769 // Use De Morgan's laws
2770 // z & (X | y)
2771 // => z & X (given y & z === 0)
2772 // => z & X | {y & z} (given y & z !== 0)
2773 let y, z, x_node, y_node, z_node = self.left;
2774 if (
2775 self.operator === "&"
2776 && self.right instanceof AST_Binary
2777 && self.right.operator === "|"
2778 && typeof (z = self.left.evaluate(compressor)) === "number"
2779 ) {
2780 if (typeof (y = self.right.right.evaluate(compressor)) === "number") {
2781 // z & (X | y)
2782 x_node = self.right.left;
2783 y_node = self.right.right;
2784 } else if (typeof (y = self.right.left.evaluate(compressor)) === "number") {
2785 // z & (y | X)
2786 x_node = self.right.right;
2787 y_node = self.right.left;
2788 }
2789
2790 if (x_node && y_node) {
2791 if ((y & z) === 0) {
2792 self = make_node(AST_Binary, self, {
2793 operator: self.operator,
2794 left: z_node,
2795 right: x_node
2796 });
2797 } else {
2798 const reordered_ops = make_node(AST_Binary, self, {
2799 operator: "|",
2800 left: make_node(AST_Binary, self, {
2801 operator: "&",
2802 left: x_node,
2803 right: z_node
2804 }),
2805 right: make_node_from_constant(y & z, y_node),
2806 });
2807
2808 self = best_of(compressor, self, reordered_ops);
2809 }
2810 }
2811 }
2812
2813 // x | x => 0 | x
2814 // x & x => 0 | x
2815 if (
2816 (self.operator === "|" || self.operator === "&")
2817 && self.left.equivalent_to(self.right)
2818 && !self.left.has_side_effects(compressor)
2819 && compressor.in_32_bit_context(true)
2820 ) {
2821 self.left = make_node(AST_Number, self, { value: 0 });
2822 self.operator = "|";
2823 }
2824
2825 // ~x ^ ~y => x ^ y
2826 if (
2827 self.operator === "^"
2828 && self.left instanceof AST_UnaryPrefix
2829 && self.left.operator === "~"
2830 && self.right instanceof AST_UnaryPrefix
2831 && self.right.operator === "~"
2832 ) {
2833 self = make_node(AST_Binary, self, {
2834 operator: "^",
2835 left: self.left.expression,
2836 right: self.right.expression
2837 });
2838 }
2839
2840
2841 // Shifts that do nothing
2842 // {anything} >> 0 => {anything} | 0
2843 // {anything} << 0 => {anything} | 0
2844 if (
2845 (self.operator === "<<" || self.operator === ">>")
2846 && self.right instanceof AST_Number && self.right.value === 0
2847 ) {
2848 self.operator = "|";
2849 }
2850
2851 // Find useless to-bitwise conversions
2852 // {32 bit integer} | 0 => {32 bit integer}
2853 // {32 bit integer} ^ 0 => {32 bit integer}
2854 const zero_side = self.right instanceof AST_Number && self.right.value === 0 ? self.right
2855 : self.left instanceof AST_Number && self.left.value === 0 ? self.left
2856 : null;
2857 const non_zero_side = zero_side && (zero_side === self.right ? self.left : self.right);
2858 if (
2859 zero_side
2860 && (self.operator === "|" || self.operator === "^")
2861 && (non_zero_side.is_32_bit_integer(compressor) || compressor.in_32_bit_context(true))
2862 ) {
2863 return non_zero_side;
2864 }
2865
2866 // {anything} & 0 => 0
2867 if (
2868 zero_side
2869 && self.operator === "&"
2870 && !non_zero_side.has_side_effects(compressor)
2871 && non_zero_side.is_32_bit_integer(compressor)
2872 ) {
2873 return zero_side;
2874 }
2875
2876 // ~0 is all ones, as well as -1.
2877 // We can ellide some operations with it.
2878 const is_full_mask = (node) =>
2879 node instanceof AST_Number && node.value === -1
2880 ||
2881 node instanceof AST_UnaryPrefix
2882 && node.operator === "-"
2883 && node.expression instanceof AST_Number
2884 && node.expression.value === 1;
2885
2886 const full_mask = is_full_mask(self.right) ? self.right
2887 : is_full_mask(self.left) ? self.left
2888 : null;
2889 const other_side = (full_mask === self.right ? self.left : self.right);
2890
2891 // {32 bit integer} & -1 => {32 bit integer}
2892 if (
2893 full_mask
2894 && self.operator === "&"
2895 && (
2896 other_side.is_32_bit_integer(compressor)
2897 || compressor.in_32_bit_context(true)
2898 )
2899 ) {
2900 return other_side;
2901 }
2902
2903 // {anything} ^ -1 => ~{anything}
2904 if (
2905 full_mask
2906 && self.operator === "^"
2907 && (
2908 other_side.is_32_bit_integer(compressor)
2909 || compressor.in_32_bit_context(true)
2910 )
2911 ) {
2912 return other_side.bitwise_negate(compressor);
2913 }
2914 }
2915 }
2916 // x && (y && z) ==> x && y && z
2917 // x || (y || z) ==> x || y || z
2918 // x + ("y" + z) ==> x + "y" + z
2919 // "x" + (y + "z")==> "x" + y + "z"
2920 if (self.right instanceof AST_Binary
2921 && self.right.operator == self.operator
2922 && (lazy_op.has(self.operator)
2923 || (self.operator == "+"
2924 && (self.right.left.is_string(compressor)
2925 || (self.left.is_string(compressor)
2926 && self.right.right.is_string(compressor)))))
2927 ) {
2928 self.left = make_node(AST_Binary, self.left, {
2929 operator : self.operator,
2930 left : self.left.transform(compressor),
2931 right : self.right.left.transform(compressor)
2932 });
2933 self.right = self.right.right.transform(compressor);
2934 return self.transform(compressor);
2935 }
2936 var ev = self.evaluate(compressor);
2937 if (ev !== self) {
2938 ev = make_node_from_constant(ev, self).optimize(compressor);
2939 return best_of(compressor, ev, self);
2940 }
2941 return self;
2942});
2943
2944def_optimize(AST_SymbolExport, function(self) {
2945 return self;
2946});
2947
2948def_optimize(AST_SymbolRef, function(self, compressor) {
2949 if (
2950 !compressor.option("ie8")
2951 && is_undeclared_ref(self)
2952 && !compressor.find_parent(AST_With)
2953 ) {
2954 switch (self.name) {
2955 case "undefined":
2956 return make_node(AST_Undefined, self).optimize(compressor);
2957 case "NaN":
2958 return make_node(AST_NaN, self).optimize(compressor);
2959 case "Infinity":
2960 return make_node(AST_Infinity, self).optimize(compressor);
2961 }
2962 }
2963
2964 if (compressor.option("reduce_vars") && !compressor.is_lhs()) {
2965 return inline_into_symbolref(self, compressor);
2966 } else {
2967 return self;
2968 }
2969});
2970
2971function is_atomic(lhs, self) {
2972 return lhs instanceof AST_SymbolRef || lhs.TYPE === self.TYPE;
2973}
2974
2975/** Apply the `unsafe_undefined` option: find a variable called `undefined` and turn `self` into a reference to it. */
2976function unsafe_undefined_ref(self, compressor) {
2977 if (compressor.option("unsafe_undefined")) {
2978 var undef = find_variable(compressor, "undefined");
2979 if (undef) {
2980 var ref = make_node(AST_SymbolRef, self, {
2981 name : "undefined",
2982 scope : undef.scope,
2983 thedef : undef
2984 });
2985 set_flag(ref, UNDEFINED);
2986 return ref;
2987 }
2988 }
2989 return null;
2990}
2991
2992def_optimize(AST_Undefined, function(self, compressor) {
2993 var symbolref = unsafe_undefined_ref(self, compressor);
2994 if (symbolref) return symbolref;
2995 var lhs = compressor.is_lhs();
2996 if (lhs && is_atomic(lhs, self)) return self;
2997 return make_void_0(self);
2998});
2999
3000def_optimize(AST_Infinity, function(self, compressor) {
3001 var lhs = compressor.is_lhs();
3002 if (lhs && is_atomic(lhs, self)) return self;
3003 if (
3004 compressor.option("keep_infinity")
3005 && !(lhs && !is_atomic(lhs, self))
3006 && !find_variable(compressor, "Infinity")
3007 ) {
3008 return self;
3009 }
3010 return make_node(AST_Binary, self, {
3011 operator: "/",
3012 left: make_node(AST_Number, self, {
3013 value: 1
3014 }),
3015 right: make_node(AST_Number, self, {
3016 value: 0
3017 })
3018 });
3019});
3020
3021def_optimize(AST_NaN, function(self, compressor) {
3022 var lhs = compressor.is_lhs();
3023 if (lhs && !is_atomic(lhs, self)
3024 || find_variable(compressor, "NaN")) {
3025 return make_node(AST_Binary, self, {
3026 operator: "/",
3027 left: make_node(AST_Number, self, {
3028 value: 0
3029 }),
3030 right: make_node(AST_Number, self, {
3031 value: 0
3032 })
3033 });
3034 }
3035 return self;
3036});
3037
3038const ASSIGN_OPS = makePredicate("+ - / * % >> << >>> | ^ &");
3039const ASSIGN_OPS_COMMUTATIVE = makePredicate("* | ^ &");
3040def_optimize(AST_Assign, function(self, compressor) {
3041 if (self.logical) {
3042 return self.lift_sequences(compressor);
3043 }
3044
3045 var def;
3046 // x = x ---> x
3047 if (
3048 self.operator === "="
3049 && self.left instanceof AST_SymbolRef
3050 && self.left.name !== "arguments"
3051 && !(def = self.left.definition()).undeclared
3052 && self.right.equivalent_to(self.left)
3053 ) {
3054 return self.right;
3055 }
3056
3057 if (compressor.option("dead_code")
3058 && self.left instanceof AST_SymbolRef
3059 && (def = self.left.definition()).scope === compressor.find_parent(AST_Lambda)) {
3060 var level = 0, node, parent = self;
3061 do {
3062 node = parent;
3063 parent = compressor.parent(level++);
3064 if (parent instanceof AST_Exit) {
3065 if (in_try(level, parent)) break;
3066 if (is_reachable(def.scope, [ def ])) break;
3067 if (self.operator == "=") return self.right;
3068 def.fixed = false;
3069 return make_node(AST_Binary, self, {
3070 operator: self.operator.slice(0, -1),
3071 left: self.left,
3072 right: self.right
3073 }).optimize(compressor);
3074 }
3075 } while (parent instanceof AST_Binary && parent.right === node
3076 || parent instanceof AST_Sequence && parent.tail_node() === node);
3077 }
3078 self = self.lift_sequences(compressor);
3079
3080 if (self.operator == "=" && self.left instanceof AST_SymbolRef && self.right instanceof AST_Binary) {
3081 // x = expr1 OP expr2
3082 if (self.right.left instanceof AST_SymbolRef
3083 && self.right.left.name == self.left.name
3084 && ASSIGN_OPS.has(self.right.operator)) {
3085 // x = x - 2 ---> x -= 2
3086 self.operator = self.right.operator + "=";
3087 self.right = self.right.right;
3088 } else if (self.right.right instanceof AST_SymbolRef
3089 && self.right.right.name == self.left.name
3090 && ASSIGN_OPS_COMMUTATIVE.has(self.right.operator)
3091 && !self.right.left.has_side_effects(compressor)) {
3092 // x = 2 & x ---> x &= 2
3093 self.operator = self.right.operator + "=";
3094 self.right = self.right.left;
3095 }
3096 }
3097 return self;
3098
3099 function in_try(level, node) {
3100 function may_assignment_throw() {
3101 const right = self.right;
3102 self.right = make_node(AST_Null, right);
3103 const may_throw = node.may_throw(compressor);
3104 self.right = right;
3105
3106 return may_throw;
3107 }
3108
3109 var stop_at = self.left.definition().scope.get_defun_scope();
3110 var parent;
3111 while ((parent = compressor.parent(level++)) !== stop_at) {
3112 if (parent instanceof AST_Try) {
3113 if (parent.bfinally) return true;
3114 if (parent.bcatch && may_assignment_throw()) return true;
3115 }
3116 }
3117 }
3118});
3119
3120def_optimize(AST_DefaultAssign, function(self, compressor) {
3121 if (!compressor.option("evaluate")) {
3122 return self;
3123 }
3124 var evaluateRight = self.right.evaluate(compressor);
3125
3126 // `[x = undefined] = foo` ---> `[x] = foo`
3127 // `(arg = undefined) => ...` ---> `(arg) => ...` (unless `keep_fargs`)
3128 // `((arg = undefined) => ...)()` ---> `((arg) => ...)()`
3129 let lambda, iife;
3130 if (evaluateRight === undefined) {
3131 if (
3132 (lambda = compressor.parent()) instanceof AST_Lambda
3133 ? (
3134 compressor.option("keep_fargs") === false
3135 || (iife = compressor.parent(1)).TYPE === "Call"
3136 && iife.expression === lambda
3137 )
3138 : true
3139 ) {
3140 self = self.left;
3141 }
3142 } else if (evaluateRight !== self.right) {
3143 evaluateRight = make_node_from_constant(evaluateRight, self.right);
3144 self.right = best_of_expression(evaluateRight, self.right);
3145 }
3146
3147 return self;
3148});
3149
3150function is_nullish_check(check, check_subject, compressor) {
3151 if (check_subject.may_throw(compressor)) return false;
3152
3153 let nullish_side;
3154
3155 // foo == null
3156 if (
3157 check instanceof AST_Binary
3158 && check.operator === "=="
3159 // which side is nullish?
3160 && (
3161 (nullish_side = is_nullish(check.left, compressor) && check.left)
3162 || (nullish_side = is_nullish(check.right, compressor) && check.right)
3163 )
3164 // is the other side the same as the check_subject
3165 && (
3166 nullish_side === check.left
3167 ? check.right
3168 : check.left
3169 ).equivalent_to(check_subject)
3170 ) {
3171 return true;
3172 }
3173
3174 // foo === null || foo === undefined
3175 if (check instanceof AST_Binary && check.operator === "||") {
3176 let null_cmp;
3177 let undefined_cmp;
3178
3179 const find_comparison = cmp => {
3180 if (!(
3181 cmp instanceof AST_Binary
3182 && (cmp.operator === "===" || cmp.operator === "==")
3183 )) {
3184 return false;
3185 }
3186
3187 let found = 0;
3188 let defined_side;
3189
3190 if (cmp.left instanceof AST_Null) {
3191 found++;
3192 null_cmp = cmp;
3193 defined_side = cmp.right;
3194 }
3195 if (cmp.right instanceof AST_Null) {
3196 found++;
3197 null_cmp = cmp;
3198 defined_side = cmp.left;
3199 }
3200 if (is_undefined(cmp.left, compressor)) {
3201 found++;
3202 undefined_cmp = cmp;
3203 defined_side = cmp.right;
3204 }
3205 if (is_undefined(cmp.right, compressor)) {
3206 found++;
3207 undefined_cmp = cmp;
3208 defined_side = cmp.left;
3209 }
3210
3211 if (found !== 1) {
3212 return false;
3213 }
3214
3215 if (!defined_side.equivalent_to(check_subject)) {
3216 return false;
3217 }
3218
3219 return true;
3220 };
3221
3222 if (!find_comparison(check.left)) return false;
3223 if (!find_comparison(check.right)) return false;
3224
3225 if (null_cmp && undefined_cmp && null_cmp !== undefined_cmp) {
3226 return true;
3227 }
3228 }
3229
3230 return false;
3231}
3232
3233def_optimize(AST_Conditional, function(self, compressor) {
3234 if (!compressor.option("conditionals")) return self;
3235 // This looks like lift_sequences(), should probably be under "sequences"
3236 if (self.condition instanceof AST_Sequence) {
3237 var expressions = self.condition.expressions.slice();
3238 self.condition = expressions.pop();
3239 expressions.push(self);
3240 return make_sequence(self, expressions);
3241 }
3242 var cond = self.condition.evaluate(compressor);
3243 if (cond !== self.condition) {
3244 if (cond) {
3245 return maintain_this_binding(compressor.parent(), compressor.self(), self.consequent);
3246 } else {
3247 return maintain_this_binding(compressor.parent(), compressor.self(), self.alternative);
3248 }
3249 }
3250 var negated = cond.negate(compressor, first_in_statement(compressor));
3251 if (best_of(compressor, cond, negated) === negated) {
3252 self = make_node(AST_Conditional, self, {
3253 condition: negated,
3254 consequent: self.alternative,
3255 alternative: self.consequent
3256 });
3257 }
3258 var condition = self.condition;
3259 var consequent = self.consequent;
3260 var alternative = self.alternative;
3261 // x?x:y --> x||y
3262 if (condition instanceof AST_SymbolRef
3263 && consequent instanceof AST_SymbolRef
3264 && condition.definition() === consequent.definition()) {
3265 return make_node(AST_Binary, self, {
3266 operator: "||",
3267 left: condition,
3268 right: alternative
3269 });
3270 }
3271 // if (foo) exp = something; else exp = something_else;
3272 // |
3273 // v
3274 // exp = foo ? something : something_else;
3275 if (
3276 consequent instanceof AST_Assign
3277 && alternative instanceof AST_Assign
3278 && consequent.operator === alternative.operator
3279 && consequent.logical === alternative.logical
3280 && consequent.left.equivalent_to(alternative.left)
3281 && (!self.condition.has_side_effects(compressor)
3282 || consequent.operator == "="
3283 && !consequent.left.has_side_effects(compressor))
3284 ) {
3285 return make_node(AST_Assign, self, {
3286 operator: consequent.operator,
3287 left: consequent.left,
3288 logical: consequent.logical,
3289 right: make_node(AST_Conditional, self, {
3290 condition: self.condition,
3291 consequent: consequent.right,
3292 alternative: alternative.right
3293 })
3294 });
3295 }
3296 // x ? y(a) : y(b) --> y(x ? a : b)
3297 var arg_index;
3298 if (consequent instanceof AST_Call
3299 && alternative.TYPE === consequent.TYPE
3300 && consequent.args.length > 0
3301 && consequent.args.length == alternative.args.length
3302 && consequent.expression.equivalent_to(alternative.expression)
3303 && !self.condition.has_side_effects(compressor)
3304 && !consequent.expression.has_side_effects(compressor)
3305 && typeof (arg_index = single_arg_diff()) == "number") {
3306 var node = consequent.clone();
3307 node.args[arg_index] = make_node(AST_Conditional, self, {
3308 condition: self.condition,
3309 consequent: consequent.args[arg_index],
3310 alternative: alternative.args[arg_index]
3311 });
3312 return node;
3313 }
3314 // a ? b : c ? b : d --> (a || c) ? b : d
3315 if (alternative instanceof AST_Conditional
3316 && consequent.equivalent_to(alternative.consequent)) {
3317 return make_node(AST_Conditional, self, {
3318 condition: make_node(AST_Binary, self, {
3319 operator: "||",
3320 left: condition,
3321 right: alternative.condition
3322 }),
3323 consequent: consequent,
3324 alternative: alternative.alternative
3325 }).optimize(compressor);
3326 }
3327
3328 // a == null ? b : a -> a ?? b
3329 if (
3330 compressor.option("ecma") >= 2020 &&
3331 is_nullish_check(condition, alternative, compressor)
3332 ) {
3333 return make_node(AST_Binary, self, {
3334 operator: "??",
3335 left: alternative,
3336 right: consequent
3337 }).optimize(compressor);
3338 }
3339
3340 // a ? b : (c, b) --> (a || c), b
3341 if (alternative instanceof AST_Sequence
3342 && consequent.equivalent_to(alternative.expressions[alternative.expressions.length - 1])) {
3343 return make_sequence(self, [
3344 make_node(AST_Binary, self, {
3345 operator: "||",
3346 left: condition,
3347 right: make_sequence(self, alternative.expressions.slice(0, -1))
3348 }),
3349 consequent
3350 ]).optimize(compressor);
3351 }
3352 // a ? b : (c && b) --> (a || c) && b
3353 if (alternative instanceof AST_Binary
3354 && alternative.operator == "&&"
3355 && consequent.equivalent_to(alternative.right)) {
3356 return make_node(AST_Binary, self, {
3357 operator: "&&",
3358 left: make_node(AST_Binary, self, {
3359 operator: "||",
3360 left: condition,
3361 right: alternative.left
3362 }),
3363 right: consequent
3364 }).optimize(compressor);
3365 }
3366 // x?y?z:a:a --> x&&y?z:a
3367 if (consequent instanceof AST_Conditional
3368 && consequent.alternative.equivalent_to(alternative)) {
3369 return make_node(AST_Conditional, self, {
3370 condition: make_node(AST_Binary, self, {
3371 left: self.condition,
3372 operator: "&&",
3373 right: consequent.condition
3374 }),
3375 consequent: consequent.consequent,
3376 alternative: alternative
3377 });
3378 }
3379 // x ? y : y --> x, y
3380 if (consequent.equivalent_to(alternative)) {
3381 return make_sequence(self, [
3382 self.condition,
3383 consequent
3384 ]).optimize(compressor);
3385 }
3386 // x ? y || z : z --> x && y || z
3387 if (consequent instanceof AST_Binary
3388 && consequent.operator == "||"
3389 && consequent.right.equivalent_to(alternative)) {
3390 return make_node(AST_Binary, self, {
3391 operator: "||",
3392 left: make_node(AST_Binary, self, {
3393 operator: "&&",
3394 left: self.condition,
3395 right: consequent.left
3396 }),
3397 right: alternative
3398 }).optimize(compressor);
3399 }
3400
3401 const in_bool = compressor.in_boolean_context();
3402 if (is_true(self.consequent)) {
3403 if (is_false(self.alternative)) {
3404 // c ? true : false ---> !!c
3405 return booleanize(self.condition);
3406 }
3407 // c ? true : x ---> !!c || x
3408 return make_node(AST_Binary, self, {
3409 operator: "||",
3410 left: booleanize(self.condition),
3411 right: self.alternative
3412 });
3413 }
3414 if (is_false(self.consequent)) {
3415 if (is_true(self.alternative)) {
3416 // c ? false : true ---> !c
3417 return booleanize(self.condition.negate(compressor));
3418 }
3419 // c ? false : x ---> !c && x
3420 return make_node(AST_Binary, self, {
3421 operator: "&&",
3422 left: booleanize(self.condition.negate(compressor)),
3423 right: self.alternative
3424 });
3425 }
3426 if (is_true(self.alternative)) {
3427 // c ? x : true ---> !c || x
3428 return make_node(AST_Binary, self, {
3429 operator: "||",
3430 left: booleanize(self.condition.negate(compressor)),
3431 right: self.consequent
3432 });
3433 }
3434 if (is_false(self.alternative)) {
3435 // c ? x : false ---> !!c && x
3436 return make_node(AST_Binary, self, {
3437 operator: "&&",
3438 left: booleanize(self.condition),
3439 right: self.consequent
3440 });
3441 }
3442
3443 return self;
3444
3445 function booleanize(node) {
3446 if (node.is_boolean()) return node;
3447 // !!expression
3448 return make_node(AST_UnaryPrefix, node, {
3449 operator: "!",
3450 expression: node.negate(compressor)
3451 });
3452 }
3453
3454 // AST_True or !0
3455 function is_true(node) {
3456 return node instanceof AST_True
3457 || in_bool
3458 && node instanceof AST_Constant
3459 && node.getValue()
3460 || (node instanceof AST_UnaryPrefix
3461 && node.operator == "!"
3462 && node.expression instanceof AST_Constant
3463 && !node.expression.getValue());
3464 }
3465 // AST_False or !1
3466 function is_false(node) {
3467 return node instanceof AST_False
3468 || in_bool
3469 && node instanceof AST_Constant
3470 && !node.getValue()
3471 || (node instanceof AST_UnaryPrefix
3472 && node.operator == "!"
3473 && node.expression instanceof AST_Constant
3474 && node.expression.getValue());
3475 }
3476
3477 function single_arg_diff() {
3478 var a = consequent.args;
3479 var b = alternative.args;
3480 for (var i = 0, len = a.length; i < len; i++) {
3481 if (a[i] instanceof AST_Expansion) return;
3482 if (!a[i].equivalent_to(b[i])) {
3483 if (b[i] instanceof AST_Expansion) return;
3484 for (var j = i + 1; j < len; j++) {
3485 if (a[j] instanceof AST_Expansion) return;
3486 if (!a[j].equivalent_to(b[j])) return;
3487 }
3488 return i;
3489 }
3490 }
3491 }
3492});
3493
3494def_optimize(AST_Boolean, function(self, compressor) {
3495 if (compressor.in_boolean_context()) return make_node(AST_Number, self, {
3496 value: +self.value
3497 });
3498 var p = compressor.parent();
3499 if (compressor.option("booleans_as_integers")) {
3500 if (p instanceof AST_Binary && (p.operator == "===" || p.operator == "!==")) {
3501 p.operator = p.operator.replace(/=$/, "");
3502 }
3503 return make_node(AST_Number, self, {
3504 value: +self.value
3505 });
3506 }
3507 if (compressor.option("booleans")) {
3508 if (p instanceof AST_Binary && (p.operator == "=="
3509 || p.operator == "!=")) {
3510 return make_node(AST_Number, self, {
3511 value: +self.value
3512 });
3513 }
3514 return make_node(AST_UnaryPrefix, self, {
3515 operator: "!",
3516 expression: make_node(AST_Number, self, {
3517 value: 1 - self.value
3518 })
3519 });
3520 }
3521 return self;
3522});
3523
3524function safe_to_flatten(value, compressor) {
3525 if (value instanceof AST_SymbolRef) {
3526 value = value.fixed_value();
3527 }
3528 if (!value) return false;
3529 if (!(value instanceof AST_Lambda || value instanceof AST_Class)) return true;
3530 if (!(value instanceof AST_Lambda && value.contains_this())) return true;
3531 return compressor.parent() instanceof AST_New;
3532}
3533
3534AST_PropAccess.DEFMETHOD("flatten_object", function(key, compressor) {
3535 if (!compressor.option("properties")) return;
3536 if (key === "__proto__") return;
3537 if (this instanceof AST_DotHash) return;
3538
3539 var arrows = compressor.option("unsafe_arrows") && compressor.option("ecma") >= 2015;
3540 var expr = this.expression;
3541 if (expr instanceof AST_Object) {
3542 var props = expr.properties;
3543
3544 for (var i = props.length; --i >= 0;) {
3545 var prop = props[i];
3546
3547 if ("" + (prop instanceof AST_ConciseMethod ? prop.key.name : prop.key) == key) {
3548 const all_props_flattenable = props.every((p) =>
3549 (p instanceof AST_ObjectKeyVal
3550 || arrows && p instanceof AST_ConciseMethod && !p.value.is_generator
3551 )
3552 && !p.computed_key()
3553 );
3554
3555 if (!all_props_flattenable) return;
3556 if (!safe_to_flatten(prop.value, compressor)) return;
3557
3558 return make_node(AST_Sub, this, {
3559 expression: make_node(AST_Array, expr, {
3560 elements: props.map(function(prop) {
3561 var v = prop.value;
3562 if (v instanceof AST_Accessor) {
3563 v = make_node(AST_Function, v, v);
3564 }
3565
3566 var k = prop.key;
3567 if (k instanceof AST_Node && !(k instanceof AST_SymbolMethod)) {
3568 return make_sequence(prop, [ k, v ]);
3569 }
3570
3571 return v;
3572 })
3573 }),
3574 property: make_node(AST_Number, this, {
3575 value: i
3576 })
3577 });
3578 }
3579 }
3580 }
3581});
3582
3583def_optimize(AST_Sub, function(self, compressor) {
3584 var expr = self.expression;
3585 var prop = self.property;
3586 if (compressor.option("properties")) {
3587 var key = prop.evaluate(compressor);
3588 if (key !== prop) {
3589 if (typeof key == "string") {
3590 if (key == "undefined") {
3591 key = undefined;
3592 } else {
3593 var value = parseFloat(key);
3594 if (value.toString() == key) {
3595 key = value;
3596 }
3597 }
3598 }
3599 prop = self.property = best_of_expression(
3600 prop,
3601 make_node_from_constant(key, prop).transform(compressor)
3602 );
3603 var property = "" + key;
3604 if (is_basic_identifier_string(property)
3605 && property.length <= prop.size() + 1) {
3606 return make_node(AST_Dot, self, {
3607 expression: expr,
3608 optional: self.optional,
3609 property: property,
3610 quote: prop.quote,
3611 }).optimize(compressor);
3612 }
3613 }
3614 }
3615 var fn;
3616 OPT_ARGUMENTS: if (compressor.option("arguments")
3617 && expr instanceof AST_SymbolRef
3618 && expr.name == "arguments"
3619 && expr.definition().orig.length == 1
3620 && (fn = expr.scope) instanceof AST_Lambda
3621 && fn.uses_arguments
3622 && !(fn instanceof AST_Arrow)
3623 && prop instanceof AST_Number) {
3624 var index = prop.getValue();
3625 var params = new Set();
3626 var argnames = fn.argnames;
3627 for (var n = 0; n < argnames.length; n++) {
3628 if (!(argnames[n] instanceof AST_SymbolFunarg)) {
3629 break OPT_ARGUMENTS; // destructuring parameter - bail
3630 }
3631 var param = argnames[n].name;
3632 if (params.has(param)) {
3633 break OPT_ARGUMENTS; // duplicate parameter - bail
3634 }
3635 params.add(param);
3636 }
3637 var argname = fn.argnames[index];
3638 if (argname && compressor.has_directive("use strict")) {
3639 var def = argname.definition();
3640 if (!compressor.option("reduce_vars") || def.assignments || def.orig.length > 1) {
3641 argname = null;
3642 }
3643 } else if (!argname && !compressor.option("keep_fargs") && index < fn.argnames.length + 5) {
3644 while (index >= fn.argnames.length) {
3645 argname = fn.create_symbol(AST_SymbolFunarg, {
3646 source: fn,
3647 scope: fn,
3648 tentative_name: "argument_" + fn.argnames.length,
3649 });
3650 fn.argnames.push(argname);
3651 }
3652 }
3653 if (argname) {
3654 var sym = make_node(AST_SymbolRef, self, argname);
3655 sym.reference({});
3656 clear_flag(argname, UNUSED);
3657 return sym;
3658 }
3659 }
3660 if (compressor.is_lhs()) return self;
3661 if (key !== prop) {
3662 var sub = self.flatten_object(property, compressor);
3663 if (sub) {
3664 expr = self.expression = sub.expression;
3665 prop = self.property = sub.property;
3666 }
3667 }
3668 if (compressor.option("properties") && compressor.option("side_effects")
3669 && prop instanceof AST_Number && expr instanceof AST_Array) {
3670 var index = prop.getValue();
3671 var elements = expr.elements;
3672 var retValue = elements[index];
3673 FLATTEN: if (safe_to_flatten(retValue, compressor)) {
3674 var flatten = true;
3675 var values = [];
3676 for (var i = elements.length; --i > index;) {
3677 var value = elements[i].drop_side_effect_free(compressor);
3678 if (value) {
3679 values.unshift(value);
3680 if (flatten && value.has_side_effects(compressor)) flatten = false;
3681 }
3682 }
3683 if (retValue instanceof AST_Expansion) break FLATTEN;
3684 retValue = retValue instanceof AST_Hole ? make_void_0(retValue) : retValue;
3685 if (!flatten) values.unshift(retValue);
3686 while (--i >= 0) {
3687 var value = elements[i];
3688 if (value instanceof AST_Expansion) break FLATTEN;
3689 value = value.drop_side_effect_free(compressor);
3690 if (value) values.unshift(value);
3691 else index--;
3692 }
3693 if (flatten) {
3694 values.push(retValue);
3695 return make_sequence(self, values).optimize(compressor);
3696 } else return make_node(AST_Sub, self, {
3697 expression: make_node(AST_Array, expr, {
3698 elements: values
3699 }),
3700 property: make_node(AST_Number, prop, {
3701 value: index
3702 })
3703 });
3704 }
3705 }
3706 var ev = self.evaluate(compressor);
3707 if (ev !== self) {
3708 ev = make_node_from_constant(ev, self).optimize(compressor);
3709 return best_of(compressor, ev, self);
3710 }
3711 return self;
3712});
3713
3714def_optimize(AST_Chain, function (self, compressor) {
3715 if (is_nullish(self.expression, compressor)) {
3716 let parent = compressor.parent();
3717 // It's valid to delete a nullish optional chain, but if we optimized
3718 // this to `delete undefined` then it would appear to be a syntax error
3719 // when we try to optimize the delete. Thankfully, `delete 0` is fine.
3720 if (parent instanceof AST_UnaryPrefix && parent.operator === "delete") {
3721 return make_node_from_constant(0, self);
3722 }
3723 return make_void_0(self).optimize(compressor);
3724 }
3725 if (
3726 self.expression instanceof AST_PropAccess
3727 || self.expression instanceof AST_Call
3728 ) {
3729 return self;
3730 } else {
3731 // Keep the AST valid, in case the child swapped itself
3732 return self.expression;
3733 }
3734});
3735
3736def_optimize(AST_Dot, function(self, compressor) {
3737 const parent = compressor.parent();
3738 if (compressor.is_lhs()) return self;
3739 if (compressor.option("unsafe_proto")
3740 && self.expression instanceof AST_Dot
3741 && self.expression.property == "prototype") {
3742 var exp = self.expression.expression;
3743 if (is_undeclared_ref(exp)) switch (exp.name) {
3744 case "Array":
3745 self.expression = make_node(AST_Array, self.expression, {
3746 elements: []
3747 });
3748 break;
3749 case "Function":
3750 self.expression = make_empty_function(self.expression);
3751 break;
3752 case "Number":
3753 self.expression = make_node(AST_Number, self.expression, {
3754 value: 0
3755 });
3756 break;
3757 case "Object":
3758 self.expression = make_node(AST_Object, self.expression, {
3759 properties: []
3760 });
3761 break;
3762 case "RegExp":
3763 self.expression = make_node(AST_RegExp, self.expression, {
3764 value: { source: "t", flags: "" }
3765 });
3766 break;
3767 case "String":
3768 self.expression = make_node(AST_String, self.expression, {
3769 value: ""
3770 });
3771 break;
3772 }
3773 }
3774 if (!(parent instanceof AST_Call) || !has_annotation(parent, _NOINLINE)) {
3775 const sub = self.flatten_object(self.property, compressor);
3776 if (sub) return sub.optimize(compressor);
3777 }
3778
3779 if (self.expression instanceof AST_PropAccess
3780 && parent instanceof AST_PropAccess) {
3781 return self;
3782 }
3783
3784 let ev = self.evaluate(compressor);
3785 if (ev !== self) {
3786 ev = make_node_from_constant(ev, self).optimize(compressor);
3787 return best_of(compressor, ev, self);
3788 }
3789 return self;
3790});
3791
3792function literals_in_boolean_context(self, compressor) {
3793 if (compressor.in_boolean_context()) {
3794 return best_of(compressor, self, make_sequence(self, [
3795 self,
3796 make_node(AST_True, self)
3797 ]).optimize(compressor));
3798 }
3799 return self;
3800}
3801
3802function inline_array_like_spread(elements) {
3803 for (var i = 0; i < elements.length; i++) {
3804 var el = elements[i];
3805 if (el instanceof AST_Expansion) {
3806 var expr = el.expression;
3807 if (
3808 expr instanceof AST_Array
3809 && !expr.elements.some(elm => elm instanceof AST_Hole)
3810 ) {
3811 elements.splice(i, 1, ...expr.elements);
3812 // Step back one, as the element at i is now new.
3813 i--;
3814 }
3815 // In array-like spread, spreading a non-iterable value is TypeError.
3816 // We therefore can’t optimize anything else, unlike with object spread.
3817 }
3818 }
3819}
3820
3821def_optimize(AST_Array, function(self, compressor) {
3822 var optimized = literals_in_boolean_context(self, compressor);
3823 if (optimized !== self) {
3824 return optimized;
3825 }
3826 inline_array_like_spread(self.elements);
3827 return self;
3828});
3829
3830function inline_object_prop_spread(props) {
3831 for (var i = 0; i < props.length; i++) {
3832 var prop = props[i];
3833 if (prop instanceof AST_Expansion) {
3834 const expr = prop.expression;
3835 if (
3836 expr instanceof AST_Object
3837 && expr.properties.every(prop => prop instanceof AST_ObjectKeyVal)
3838 ) {
3839 props.splice(i, 1, ...expr.properties);
3840 // Step back one, as the property at i is now new.
3841 i--;
3842 } else if ((
3843 // `expr.is_constant()` returns `false` for `AST_RegExp`, so need both.
3844 expr instanceof AST_Constant
3845 || expr.is_constant()
3846 ) && !(expr instanceof AST_String)) {
3847 // Unlike array-like spread, in object spread, spreading a
3848 // non-iterable value silently does nothing; it is thus safe
3849 // to remove. AST_String is the only iterable constant.
3850 props.splice(i, 1);
3851 i--;
3852 }
3853 }
3854 }
3855}
3856
3857def_optimize(AST_Object, function(self, compressor) {
3858 var optimized = literals_in_boolean_context(self, compressor);
3859 if (optimized !== self) {
3860 return optimized;
3861 }
3862 inline_object_prop_spread(self.properties);
3863 return self;
3864});
3865
3866def_optimize(AST_RegExp, literals_in_boolean_context);
3867
3868def_optimize(AST_Return, function(self, compressor) {
3869 if (self.value && is_undefined(self.value, compressor)) {
3870 self.value = null;
3871 }
3872 return self;
3873});
3874
3875def_optimize(AST_Arrow, opt_AST_Lambda);
3876
3877def_optimize(AST_Function, function(self, compressor) {
3878 self = opt_AST_Lambda(self, compressor);
3879 if (compressor.option("unsafe_arrows")
3880 && compressor.option("ecma") >= 2015
3881 && !self.name
3882 && !self.is_generator
3883 && !self.uses_arguments
3884 && !self.pinned()) {
3885 const uses_this = walk(self, node => {
3886 if (node instanceof AST_This) return walk_abort;
3887 });
3888 if (!uses_this) return make_node(AST_Arrow, self, self).optimize(compressor);
3889 }
3890 return self;
3891});
3892
3893def_optimize(AST_Class, function(self) {
3894 for (let i = 0; i < self.properties.length; i++) {
3895 const prop = self.properties[i];
3896 if (prop instanceof AST_ClassStaticBlock && prop.body.length == 0) {
3897 self.properties.splice(i, 1);
3898 i--;
3899 }
3900 }
3901
3902 return self;
3903});
3904
3905def_optimize(AST_ClassStaticBlock, function(self, compressor) {
3906 tighten_body(self.body, compressor);
3907 return self;
3908});
3909
3910def_optimize(AST_Yield, function(self, compressor) {
3911 if (self.expression && !self.is_star && is_undefined(self.expression, compressor)) {
3912 self.expression = null;
3913 }
3914 return self;
3915});
3916
3917def_optimize(AST_TemplateString, function(self, compressor) {
3918 if (
3919 !compressor.option("evaluate")
3920 || compressor.parent() instanceof AST_PrefixedTemplateString
3921 ) {
3922 return self;
3923 }
3924
3925 var segments = [];
3926 for (var i = 0; i < self.segments.length; i++) {
3927 var segment = self.segments[i];
3928 if (segment instanceof AST_Node) {
3929 var result = segment.evaluate(compressor);
3930 // Evaluate to constant value
3931 // Constant value shorter than ${segment}
3932 if (result !== segment && (result + "").length <= segment.size() + "${}".length) {
3933 // There should always be a previous and next segment if segment is a node
3934 segments[segments.length - 1].value = segments[segments.length - 1].value + result + self.segments[++i].value;
3935 continue;
3936 }
3937 // `before ${`innerBefore ${any} innerAfter`} after` => `before innerBefore ${any} innerAfter after`
3938 // TODO:
3939 // `before ${'test' + foo} after` => `before innerBefore ${any} innerAfter after`
3940 // `before ${foo + 'test} after` => `before innerBefore ${any} innerAfter after`
3941 if (segment instanceof AST_TemplateString) {
3942 var inners = segment.segments;
3943 segments[segments.length - 1].value += inners[0].value;
3944 for (var j = 1; j < inners.length; j++) {
3945 segment = inners[j];
3946 segments.push(segment);
3947 }
3948 continue;
3949 }
3950 }
3951 segments.push(segment);
3952 }
3953 self.segments = segments;
3954
3955 // `foo` => "foo"
3956 if (segments.length == 1) {
3957 return make_node(AST_String, self, segments[0]);
3958 }
3959
3960 if (
3961 segments.length === 3
3962 && segments[1] instanceof AST_Node
3963 && (
3964 segments[1].is_string(compressor)
3965 || segments[1].is_number_or_bigint(compressor)
3966 || is_nullish(segments[1], compressor)
3967 || compressor.option("unsafe")
3968 )
3969 ) {
3970 // `foo${bar}` => "foo" + bar
3971 if (segments[2].value === "") {
3972 return make_node(AST_Binary, self, {
3973 operator: "+",
3974 left: make_node(AST_String, self, {
3975 value: segments[0].value,
3976 }),
3977 right: segments[1],
3978 });
3979 }
3980 // `${bar}baz` => bar + "baz"
3981 if (segments[0].value === "") {
3982 return make_node(AST_Binary, self, {
3983 operator: "+",
3984 left: segments[1],
3985 right: make_node(AST_String, self, {
3986 value: segments[2].value,
3987 }),
3988 });
3989 }
3990 }
3991 return self;
3992});
3993
3994def_optimize(AST_PrefixedTemplateString, function(self) {
3995 return self;
3996});
3997
3998// ["p"]:1 ---> p:1
3999// [42]:1 ---> 42:1
4000function lift_key(self, compressor) {
4001 if (!compressor.option("computed_props")) return self;
4002 // save a comparison in the typical case
4003 if (!(self.key instanceof AST_Constant)) return self;
4004 // allow certain acceptable props as not all AST_Constants are true constants
4005 if (self.key instanceof AST_String || self.key instanceof AST_Number) {
4006 const key = self.key.value.toString();
4007
4008 if (key === "__proto__") return self;
4009 if (key == "constructor"
4010 && compressor.parent() instanceof AST_Class) return self;
4011 if (self instanceof AST_ObjectKeyVal) {
4012 self.quote = self.key.quote;
4013 self.key = key;
4014 } else if (self instanceof AST_ClassProperty) {
4015 self.quote = self.key.quote;
4016 self.key = make_node(AST_SymbolClassProperty, self.key, {
4017 name: key,
4018 });
4019 } else {
4020 self.quote = self.key.quote;
4021 self.key = make_node(AST_SymbolMethod, self.key, {
4022 name: key,
4023 });
4024 }
4025 }
4026 return self;
4027}
4028
4029def_optimize(AST_ObjectProperty, lift_key);
4030
4031def_optimize(AST_ConciseMethod, function(self, compressor) {
4032 lift_key(self, compressor);
4033 // p(){return x;} ---> p:()=>x
4034 if (compressor.option("arrows")
4035 && compressor.parent() instanceof AST_Object
4036 && !self.value.is_generator
4037 && !self.value.uses_arguments
4038 && !self.value.pinned()
4039 && self.value.body.length == 1
4040 && self.value.body[0] instanceof AST_Return
4041 && self.value.body[0].value
4042 && !self.value.contains_this()) {
4043 var arrow = make_node(AST_Arrow, self.value, self.value);
4044 arrow.async = self.value.async;
4045 arrow.is_generator = self.value.is_generator;
4046 return make_node(AST_ObjectKeyVal, self, {
4047 key: self.key instanceof AST_SymbolMethod ? self.key.name : self.key,
4048 value: arrow,
4049 quote: self.quote,
4050 });
4051 }
4052 return self;
4053});
4054
4055def_optimize(AST_ObjectKeyVal, function(self, compressor) {
4056 lift_key(self, compressor);
4057 // p:function(){} ---> p(){}
4058 // p:function*(){} ---> *p(){}
4059 // p:async function(){} ---> async p(){}
4060 // p:()=>{} ---> p(){}
4061 // p:async()=>{} ---> async p(){}
4062 var unsafe_methods = compressor.option("unsafe_methods");
4063 if (unsafe_methods
4064 && compressor.option("ecma") >= 2015
4065 && (!(unsafe_methods instanceof RegExp) || unsafe_methods.test(self.key + ""))) {
4066 var key = self.key;
4067 var value = self.value;
4068 var is_arrow_with_block = value instanceof AST_Arrow
4069 && Array.isArray(value.body)
4070 && !value.contains_this();
4071 if ((is_arrow_with_block || value instanceof AST_Function) && !value.name) {
4072 return make_node(AST_ConciseMethod, self, {
4073 key: key instanceof AST_Node ? key : make_node(AST_SymbolMethod, self, {
4074 name: key,
4075 }),
4076 value: make_node(AST_Accessor, value, value),
4077 quote: self.quote,
4078 });
4079 }
4080 }
4081 return self;
4082});
4083
4084def_optimize(AST_Destructuring, function(self, compressor) {
4085 if (compressor.option("pure_getters") == true
4086 && compressor.option("unused")
4087 && !self.is_array
4088 && Array.isArray(self.names)
4089 && !is_destructuring_export_decl(compressor)
4090 && !(self.names[self.names.length - 1] instanceof AST_Expansion)) {
4091 var keep = [];
4092 for (var i = 0; i < self.names.length; i++) {
4093 var elem = self.names[i];
4094 if (!(elem instanceof AST_ObjectKeyVal
4095 && typeof elem.key == "string"
4096 && elem.value instanceof AST_SymbolDeclaration
4097 && !should_retain(compressor, elem.value.definition()))) {
4098 keep.push(elem);
4099 }
4100 }
4101 if (keep.length != self.names.length) {
4102 self.names = keep;
4103 }
4104 }
4105 return self;
4106
4107 function is_destructuring_export_decl(compressor) {
4108 var ancestors = [/^VarDef$/, /^(Const|Let|Var)$/, /^Export$/];
4109 for (var a = 0, p = 0, len = ancestors.length; a < len; p++) {
4110 var parent = compressor.parent(p);
4111 if (!parent) return false;
4112 if (a === 0 && parent.TYPE == "Destructuring") continue;
4113 if (!ancestors[a].test(parent.TYPE)) {
4114 return false;
4115 }
4116 a++;
4117 }
4118 return true;
4119 }
4120
4121 function should_retain(compressor, def) {
4122 if (def.references.length) return true;
4123 if (!def.global) return false;
4124 if (compressor.toplevel.vars) {
4125 if (compressor.top_retain) {
4126 return compressor.top_retain(def);
4127 }
4128 return false;
4129 }
4130 return true;
4131 }
4132});
4133
4134export {
4135 Compressor,
4136};
Note: See TracBrowser for help on using the repository browser.