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