source: imaps-frontend/node_modules/terser/lib/compress/index.js@ 79a0317

main
Last change on this file since 79a0317 was 79a0317, checked in by stefan toskovski <stefantoska84@…>, 3 days ago

F4 Finalna Verzija

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