UNPKG

react-carousel-query

Version:

A infinite carousel component made with react that handles the pagination for you.

1,378 lines (1,300 loc) 151 kB
/*********************************************************************** A JavaScript tokenizer / parser / beautifier / compressor. https://github.com/mishoo/UglifyJS2 -------------------------------- (C) --------------------------------- Author: Mihai Bazon <mihai.bazon@gmail.com> http://mihai.bazon.net/blog Distributed under the BSD license: Copyright 2012 (c) Mihai Bazon <mihai.bazon@gmail.com> Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ***********************************************************************/ import { AST_Accessor, AST_Array, AST_Arrow, AST_Assign, AST_BigInt, AST_Binary, AST_Block, AST_BlockStatement, AST_Boolean, AST_Break, AST_Call, AST_Catch, AST_Chain, AST_Class, AST_ClassProperty, AST_ClassStaticBlock, AST_ConciseMethod, AST_Conditional, AST_Const, AST_Constant, AST_Debugger, AST_Default, AST_DefaultAssign, AST_Definitions, AST_Defun, AST_Destructuring, AST_Directive, AST_Do, AST_Dot, AST_DWLoop, AST_EmptyStatement, AST_Exit, AST_Expansion, AST_Export, AST_False, AST_For, AST_ForIn, AST_Function, AST_Hole, AST_If, AST_Import, AST_Infinity, AST_LabeledStatement, AST_Lambda, AST_Let, AST_NaN, AST_New, AST_Node, AST_Null, AST_Number, AST_Object, AST_ObjectKeyVal, AST_ObjectProperty, AST_PrefixedTemplateString, AST_PropAccess, AST_RegExp, AST_Return, AST_Scope, AST_Sequence, AST_SimpleStatement, AST_Statement, AST_String, AST_Sub, AST_Switch, AST_SwitchBranch, AST_Symbol, AST_SymbolClassProperty, AST_SymbolDeclaration, AST_SymbolDefun, AST_SymbolExport, AST_SymbolFunarg, AST_SymbolLambda, AST_SymbolLet, AST_SymbolMethod, AST_SymbolRef, AST_TemplateString, AST_This, AST_Toplevel, AST_True, AST_Try, AST_Unary, AST_UnaryPostfix, AST_UnaryPrefix, AST_Undefined, AST_Var, AST_VarDef, AST_While, AST_With, AST_Yield, TreeTransformer, TreeWalker, walk, walk_abort, _NOINLINE, } from "../ast.js"; import { defaults, HOP, make_node, makePredicate, MAP, remove, return_false, return_true, regexp_source_fix, has_annotation, regexp_is_safe, } from "../utils/index.js"; import { first_in_statement } from "../utils/first_in_statement.js"; import { equivalent_to } from "../equivalent-to.js"; import { is_basic_identifier_string, JS_Parse_Error, parse, PRECEDENCE, } from "../parse.js"; import { OutputStream } from "../output.js"; import { base54, format_mangler_options } from "../scope.js"; import "../size.js"; import "./evaluate.js"; import "./drop-side-effect-free.js"; import "./drop-unused.js"; import "./reduce-vars.js"; import { is_undeclared_ref, bitwise_binop, lazy_op, is_nullish, is_undefined, is_lhs, aborts, is_used_in_expression, } from "./inference.js"; import { SQUEEZED, OPTIMIZED, CLEAR_BETWEEN_PASSES, TOP, UNDEFINED, UNUSED, TRUTHY, FALSY, has_flag, set_flag, clear_flag, } from "./compressor-flags.js"; import { make_sequence, best_of, best_of_expression, make_empty_function, make_node_from_constant, merge_sequence, get_simple_key, has_break_or_continue, maintain_this_binding, is_empty, is_identifier_atom, is_reachable, can_be_evicted_from_block, as_statement_array, is_func_expr, } from "./common.js"; import { tighten_body, trim_unreachable_code } from "./tighten-body.js"; import { inline_into_symbolref, inline_into_call } from "./inline.js"; import "./global-defs.js"; class Compressor extends TreeWalker { constructor(options, { false_by_default = false, mangle_options = false }) { super(); if (options.defaults !== undefined && !options.defaults) false_by_default = true; this.options = defaults(options, { arguments : false, arrows : !false_by_default, booleans : !false_by_default, booleans_as_integers : false, collapse_vars : !false_by_default, comparisons : !false_by_default, computed_props: !false_by_default, conditionals : !false_by_default, dead_code : !false_by_default, defaults : true, directives : !false_by_default, drop_console : false, drop_debugger : !false_by_default, ecma : 5, evaluate : !false_by_default, expression : false, global_defs : false, hoist_funs : false, hoist_props : !false_by_default, hoist_vars : false, ie8 : false, if_return : !false_by_default, inline : !false_by_default, join_vars : !false_by_default, keep_classnames: false, keep_fargs : true, keep_fnames : false, keep_infinity : false, lhs_constants : !false_by_default, loops : !false_by_default, module : false, negate_iife : !false_by_default, passes : 1, properties : !false_by_default, pure_getters : !false_by_default && "strict", pure_funcs : null, pure_new : false, reduce_funcs : !false_by_default, reduce_vars : !false_by_default, sequences : !false_by_default, side_effects : !false_by_default, switches : !false_by_default, top_retain : null, toplevel : !!(options && options["top_retain"]), typeofs : !false_by_default, unsafe : false, unsafe_arrows : false, unsafe_comps : false, unsafe_Function: false, unsafe_math : false, unsafe_symbols: false, unsafe_methods: false, unsafe_proto : false, unsafe_regexp : false, unsafe_undefined: false, unused : !false_by_default, warnings : false // legacy }, true); var global_defs = this.options["global_defs"]; if (typeof global_defs == "object") for (var key in global_defs) { if (key[0] === "@" && HOP(global_defs, key)) { global_defs[key.slice(1)] = parse(global_defs[key], { expression: true }); } } if (this.options["inline"] === true) this.options["inline"] = 3; var pure_funcs = this.options["pure_funcs"]; if (typeof pure_funcs == "function") { this.pure_funcs = pure_funcs; } else { this.pure_funcs = pure_funcs ? function(node) { return !pure_funcs.includes(node.expression.print_to_string()); } : return_true; } var top_retain = this.options["top_retain"]; if (top_retain instanceof RegExp) { this.top_retain = function(def) { return top_retain.test(def.name); }; } else if (typeof top_retain == "function") { this.top_retain = top_retain; } else if (top_retain) { if (typeof top_retain == "string") { top_retain = top_retain.split(/,/); } this.top_retain = function(def) { return top_retain.includes(def.name); }; } if (this.options["module"]) { this.directives["use strict"] = true; this.options["toplevel"] = true; } var toplevel = this.options["toplevel"]; this.toplevel = typeof toplevel == "string" ? { funcs: /funcs/.test(toplevel), vars: /vars/.test(toplevel) } : { funcs: toplevel, vars: toplevel }; var sequences = this.options["sequences"]; this.sequences_limit = sequences == 1 ? 800 : sequences | 0; this.evaluated_regexps = new Map(); this._toplevel = undefined; this._mangle_options = mangle_options ? format_mangler_options(mangle_options) : mangle_options; } mangle_options() { var nth_identifier = this._mangle_options && this._mangle_options.nth_identifier || base54; var module = this._mangle_options && this._mangle_options.module || this.option("module"); return { ie8: this.option("ie8"), nth_identifier, module }; } option(key) { return this.options[key]; } exposed(def) { if (def.export) return true; if (def.global) for (var i = 0, len = def.orig.length; i < len; i++) if (!this.toplevel[def.orig[i] instanceof AST_SymbolDefun ? "funcs" : "vars"]) return true; return false; } in_boolean_context() { if (!this.option("booleans")) return false; var self = this.self(); for (var i = 0, p; p = this.parent(i); i++) { if (p instanceof AST_SimpleStatement || p instanceof AST_Conditional && p.condition === self || p instanceof AST_DWLoop && p.condition === self || p instanceof AST_For && p.condition === self || p instanceof AST_If && p.condition === self || p instanceof AST_UnaryPrefix && p.operator == "!" && p.expression === self) { return true; } if ( p instanceof AST_Binary && ( p.operator == "&&" || p.operator == "||" || p.operator == "??" ) || p instanceof AST_Conditional || p.tail_node() === self ) { self = p; } else { return false; } } } in_32_bit_context() { if (!this.option("evaluate")) return false; var self = this.self(); for (var i = 0, p; p = this.parent(i); i++) { if (p instanceof AST_Binary && bitwise_binop.has(p.operator)) { return true; } if (p instanceof AST_UnaryPrefix) { return p.operator === "~"; } if ( p instanceof AST_Binary && ( p.operator == "&&" || p.operator == "||" || p.operator == "??" ) || p instanceof AST_Conditional && p.condition !== self || p.tail_node() === self ) { self = p; } else { return false; } } } in_computed_key() { if (!this.option("evaluate")) return false; var self = this.self(); for (var i = 0, p; p = this.parent(i); i++) { if (p instanceof AST_ObjectProperty && p.key === self) { return true; } } return false; } get_toplevel() { return this._toplevel; } compress(toplevel) { toplevel = toplevel.resolve_defines(this); this._toplevel = toplevel; if (this.option("expression")) { this._toplevel.process_expression(true); } var passes = +this.options.passes || 1; var min_count = 1 / 0; var stopping = false; var mangle = this.mangle_options(); for (var pass = 0; pass < passes; pass++) { this._toplevel.figure_out_scope(mangle); if (pass === 0 && this.option("drop_console")) { // must be run before reduce_vars and compress pass this._toplevel = this._toplevel.drop_console(this.option("drop_console")); } if (pass > 0 || this.option("reduce_vars")) { this._toplevel.reset_opt_flags(this); } this._toplevel = this._toplevel.transform(this); if (passes > 1) { let count = 0; walk(this._toplevel, () => { count++; }); if (count < min_count) { min_count = count; stopping = false; } else if (stopping) { break; } else { stopping = true; } } } if (this.option("expression")) { this._toplevel.process_expression(false); } toplevel = this._toplevel; this._toplevel = undefined; return toplevel; } before(node, descend) { if (has_flag(node, SQUEEZED)) return node; var was_scope = false; if (node instanceof AST_Scope) { node = node.hoist_properties(this); node = node.hoist_declarations(this); was_scope = true; } // Before https://github.com/mishoo/UglifyJS2/pull/1602 AST_Node.optimize() // would call AST_Node.transform() if a different instance of AST_Node is // produced after def_optimize(). // This corrupts TreeWalker.stack, which cause AST look-ups to malfunction. // Migrate and defer all children's AST_Node.transform() to below, which // will now happen after this parent AST_Node has been properly substituted // thus gives a consistent AST snapshot. descend(node, this); // Existing code relies on how AST_Node.optimize() worked, and omitting the // following replacement call would result in degraded efficiency of both // output and performance. descend(node, this); var opt = node.optimize(this); if (was_scope && opt instanceof AST_Scope) { opt.drop_unused(this); descend(opt, this); } if (opt === node) set_flag(opt, SQUEEZED); return opt; } /** Alternative to plain is_lhs() which doesn't work within .optimize() */ is_lhs() { const self = this.stack[this.stack.length - 1]; const parent = this.stack[this.stack.length - 2]; return is_lhs(self, parent); } } function def_optimize(node, optimizer) { node.DEFMETHOD("optimize", function(compressor) { var self = this; if (has_flag(self, OPTIMIZED)) return self; if (compressor.has_directive("use asm")) return self; var opt = optimizer(self, compressor); set_flag(opt, OPTIMIZED); return opt; }); } def_optimize(AST_Node, function(self) { return self; }); AST_Toplevel.DEFMETHOD("drop_console", function(options) { const isArray = Array.isArray(options); const tt = new TreeTransformer(function(self) { if (self.TYPE !== "Call") { return; } var exp = self.expression; if (!(exp instanceof AST_PropAccess)) { return; } if (isArray && !options.includes(exp.property)) { return; } var name = exp.expression; var depth = 2; while (name.expression) { name = name.expression; depth++; } if (is_undeclared_ref(name) && name.name == "console") { if ( depth === 3 && !["call", "apply"].includes(exp.property) && is_used_in_expression(tt) ) { // a (used) call to Function.prototype methods (eg: console.log.bind(console)) // but not .call and .apply which would also return undefined. exp.expression = make_empty_function(self); set_flag(exp.expression, SQUEEZED); self.args = []; } else { return make_node(AST_Undefined, self); } } }); return this.transform(tt); }); AST_Node.DEFMETHOD("equivalent_to", function(node) { return equivalent_to(this, node); }); AST_Scope.DEFMETHOD("process_expression", function(insert, compressor) { var self = this; var tt = new TreeTransformer(function(node) { if (insert && node instanceof AST_SimpleStatement) { return make_node(AST_Return, node, { value: node.body }); } if (!insert && node instanceof AST_Return) { if (compressor) { var value = node.value && node.value.drop_side_effect_free(compressor, true); return value ? make_node(AST_SimpleStatement, node, { body: value }) : make_node(AST_EmptyStatement, node); } return make_node(AST_SimpleStatement, node, { body: node.value || make_node(AST_UnaryPrefix, node, { operator: "void", expression: make_node(AST_Number, node, { value: 0 }) }) }); } if (node instanceof AST_Class || node instanceof AST_Lambda && node !== self) { return node; } if (node instanceof AST_Block) { var index = node.body.length - 1; if (index >= 0) { node.body[index] = node.body[index].transform(tt); } } else if (node instanceof AST_If) { node.body = node.body.transform(tt); if (node.alternative) { node.alternative = node.alternative.transform(tt); } } else if (node instanceof AST_With) { node.body = node.body.transform(tt); } return node; }); self.transform(tt); }); AST_Toplevel.DEFMETHOD("reset_opt_flags", function(compressor) { const self = this; const reduce_vars = compressor.option("reduce_vars"); const preparation = new TreeWalker(function(node, descend) { clear_flag(node, CLEAR_BETWEEN_PASSES); if (reduce_vars) { if (compressor.top_retain && node instanceof AST_Defun // Only functions are retained && preparation.parent() === self ) { set_flag(node, TOP); } return node.reduce_vars(preparation, descend, compressor); } }); // Stack of look-up tables to keep track of whether a `SymbolDef` has been // properly assigned before use: // - `push()` & `pop()` when visiting conditional branches preparation.safe_ids = Object.create(null); preparation.in_loop = null; preparation.loop_ids = new Map(); preparation.defs_to_safe_ids = new Map(); self.walk(preparation); }); AST_Symbol.DEFMETHOD("fixed_value", function() { var fixed = this.thedef.fixed; if (!fixed || fixed instanceof AST_Node) return fixed; return fixed(); }); AST_SymbolRef.DEFMETHOD("is_immutable", function() { var orig = this.definition().orig; return orig.length == 1 && orig[0] instanceof AST_SymbolLambda; }); function find_variable(compressor, name) { var scope, i = 0; while (scope = compressor.parent(i++)) { if (scope instanceof AST_Scope) break; if (scope instanceof AST_Catch && scope.argname) { scope = scope.argname.definition().scope; break; } } return scope.find_variable(name); } var global_names = makePredicate("Array Boolean clearInterval clearTimeout console Date decodeURI decodeURIComponent encodeURI encodeURIComponent Error escape eval EvalError Function isFinite isNaN JSON Math Number parseFloat parseInt RangeError ReferenceError RegExp Object setInterval setTimeout String SyntaxError TypeError unescape URIError"); AST_SymbolRef.DEFMETHOD("is_declared", function(compressor) { return !this.definition().undeclared || compressor.option("unsafe") && global_names.has(this.name); }); /* -----[ optimizers ]----- */ var directives = new Set(["use asm", "use strict"]); def_optimize(AST_Directive, function(self, compressor) { if (compressor.option("directives") && (!directives.has(self.value) || compressor.has_directive(self.value) !== self)) { return make_node(AST_EmptyStatement, self); } return self; }); def_optimize(AST_Debugger, function(self, compressor) { if (compressor.option("drop_debugger")) return make_node(AST_EmptyStatement, self); return self; }); def_optimize(AST_LabeledStatement, function(self, compressor) { if (self.body instanceof AST_Break && compressor.loopcontrol_target(self.body) === self.body) { return make_node(AST_EmptyStatement, self); } return self.label.references.length == 0 ? self.body : self; }); def_optimize(AST_Block, function(self, compressor) { tighten_body(self.body, compressor); return self; }); function can_be_extracted_from_if_block(node) { return !( node instanceof AST_Const || node instanceof AST_Let || node instanceof AST_Class ); } def_optimize(AST_BlockStatement, function(self, compressor) { tighten_body(self.body, compressor); switch (self.body.length) { case 1: if (!compressor.has_directive("use strict") && compressor.parent() instanceof AST_If && can_be_extracted_from_if_block(self.body[0]) || can_be_evicted_from_block(self.body[0])) { return self.body[0]; } break; case 0: return make_node(AST_EmptyStatement, self); } return self; }); function opt_AST_Lambda(self, compressor) { tighten_body(self.body, compressor); if (compressor.option("side_effects") && self.body.length == 1 && self.body[0] === compressor.has_directive("use strict")) { self.body.length = 0; } return self; } def_optimize(AST_Lambda, opt_AST_Lambda); AST_Scope.DEFMETHOD("hoist_declarations", function(compressor) { var self = this; if (compressor.has_directive("use asm")) return self; var hoist_funs = compressor.option("hoist_funs"); var hoist_vars = compressor.option("hoist_vars"); if (hoist_funs || hoist_vars) { var dirs = []; var hoisted = []; var vars = new Map(), vars_found = 0, var_decl = 0; // let's count var_decl first, we seem to waste a lot of // space if we hoist `var` when there's only one. walk(self, node => { if (node instanceof AST_Scope && node !== self) return true; if (node instanceof AST_Var) { ++var_decl; return true; } }); hoist_vars = hoist_vars && var_decl > 1; var tt = new TreeTransformer( function before(node) { if (node !== self) { if (node instanceof AST_Directive) { dirs.push(node); return make_node(AST_EmptyStatement, node); } if (hoist_funs && node instanceof AST_Defun && !(tt.parent() instanceof AST_Export) && tt.parent() === self) { hoisted.push(node); return make_node(AST_EmptyStatement, node); } if ( hoist_vars && node instanceof AST_Var && !node.definitions.some(def => def.name instanceof AST_Destructuring) ) { node.definitions.forEach(function(def) { vars.set(def.name.name, def); ++vars_found; }); var seq = node.to_assignments(compressor); var p = tt.parent(); if (p instanceof AST_ForIn && p.init === node) { if (seq == null) { var def = node.definitions[0].name; return make_node(AST_SymbolRef, def, def); } return seq; } if (p instanceof AST_For && p.init === node) { return seq; } if (!seq) return make_node(AST_EmptyStatement, node); return make_node(AST_SimpleStatement, node, { body: seq }); } if (node instanceof AST_Scope) return node; // to avoid descending in nested scopes } } ); self = self.transform(tt); if (vars_found > 0) { // collect only vars which don't show up in self's arguments list var defs = []; const is_lambda = self instanceof AST_Lambda; const args_as_names = is_lambda ? self.args_as_names() : null; vars.forEach((def, name) => { if (is_lambda && args_as_names.some((x) => x.name === def.name.name)) { vars.delete(name); } else { def = def.clone(); def.value = null; defs.push(def); vars.set(name, def); } }); if (defs.length > 0) { // try to merge in assignments for (var i = 0; i < self.body.length;) { if (self.body[i] instanceof AST_SimpleStatement) { var expr = self.body[i].body, sym, assign; if (expr instanceof AST_Assign && expr.operator == "=" && (sym = expr.left) instanceof AST_Symbol && vars.has(sym.name) ) { var def = vars.get(sym.name); if (def.value) break; def.value = expr.right; remove(defs, def); defs.push(def); self.body.splice(i, 1); continue; } if (expr instanceof AST_Sequence && (assign = expr.expressions[0]) instanceof AST_Assign && assign.operator == "=" && (sym = assign.left) instanceof AST_Symbol && vars.has(sym.name) ) { var def = vars.get(sym.name); if (def.value) break; def.value = assign.right; remove(defs, def); defs.push(def); self.body[i].body = make_sequence(expr, expr.expressions.slice(1)); continue; } } if (self.body[i] instanceof AST_EmptyStatement) { self.body.splice(i, 1); continue; } if (self.body[i] instanceof AST_BlockStatement) { self.body.splice(i, 1, ...self.body[i].body); continue; } break; } defs = make_node(AST_Var, self, { definitions: defs }); hoisted.push(defs); } } self.body = dirs.concat(hoisted, self.body); } return self; }); AST_Scope.DEFMETHOD("hoist_properties", function(compressor) { var self = this; if (!compressor.option("hoist_props") || compressor.has_directive("use asm")) return self; var top_retain = self instanceof AST_Toplevel && compressor.top_retain || return_false; var defs_by_id = new Map(); var hoister = new TreeTransformer(function(node, descend) { if (node instanceof AST_VarDef) { const sym = node.name; let def; let value; if (sym.scope === self && (def = sym.definition()).escaped != 1 && !def.assignments && !def.direct_access && !def.single_use && !compressor.exposed(def) && !top_retain(def) && (value = sym.fixed_value()) === node.value && value instanceof AST_Object && !value.properties.some(prop => prop instanceof AST_Expansion || prop.computed_key() ) ) { descend(node, this); const defs = new Map(); const assignments = []; value.properties.forEach(({ key, value }) => { const scope = hoister.find_scope(); const symbol = self.create_symbol(sym.CTOR, { source: sym, scope, conflict_scopes: new Set([ scope, ...sym.definition().references.map(ref => ref.scope) ]), tentative_name: sym.name + "_" + key }); defs.set(String(key), symbol.definition()); assignments.push(make_node(AST_VarDef, node, { name: symbol, value })); }); defs_by_id.set(def.id, defs); return MAP.splice(assignments); } } else if (node instanceof AST_PropAccess && node.expression instanceof AST_SymbolRef ) { const defs = defs_by_id.get(node.expression.definition().id); if (defs) { const def = defs.get(String(get_simple_key(node.property))); const sym = make_node(AST_SymbolRef, node, { name: def.name, scope: node.expression.scope, thedef: def }); sym.reference({}); return sym; } } }); return self.transform(hoister); }); def_optimize(AST_SimpleStatement, function(self, compressor) { if (compressor.option("side_effects")) { var body = self.body; var node = body.drop_side_effect_free(compressor, true); if (!node) { return make_node(AST_EmptyStatement, self); } if (node !== body) { return make_node(AST_SimpleStatement, self, { body: node }); } } return self; }); def_optimize(AST_While, function(self, compressor) { return compressor.option("loops") ? make_node(AST_For, self, self).optimize(compressor) : self; }); def_optimize(AST_Do, function(self, compressor) { if (!compressor.option("loops")) return self; var cond = self.condition.tail_node().evaluate(compressor); if (!(cond instanceof AST_Node)) { if (cond) return make_node(AST_For, self, { body: make_node(AST_BlockStatement, self.body, { body: [ self.body, make_node(AST_SimpleStatement, self.condition, { body: self.condition }) ] }) }).optimize(compressor); if (!has_break_or_continue(self, compressor.parent())) { return make_node(AST_BlockStatement, self.body, { body: [ self.body, make_node(AST_SimpleStatement, self.condition, { body: self.condition }) ] }).optimize(compressor); } } return self; }); function if_break_in_loop(self, compressor) { var first = self.body instanceof AST_BlockStatement ? self.body.body[0] : self.body; if (compressor.option("dead_code") && is_break(first)) { var body = []; if (self.init instanceof AST_Statement) { body.push(self.init); } else if (self.init) { body.push(make_node(AST_SimpleStatement, self.init, { body: self.init })); } if (self.condition) { body.push(make_node(AST_SimpleStatement, self.condition, { body: self.condition })); } trim_unreachable_code(compressor, self.body, body); return make_node(AST_BlockStatement, self, { body: body }); } if (first instanceof AST_If) { if (is_break(first.body)) { if (self.condition) { self.condition = make_node(AST_Binary, self.condition, { left: self.condition, operator: "&&", right: first.condition.negate(compressor), }); } else { self.condition = first.condition.negate(compressor); } drop_it(first.alternative); } else if (is_break(first.alternative)) { if (self.condition) { self.condition = make_node(AST_Binary, self.condition, { left: self.condition, operator: "&&", right: first.condition, }); } else { self.condition = first.condition; } drop_it(first.body); } } return self; function is_break(node) { return node instanceof AST_Break && compressor.loopcontrol_target(node) === compressor.self(); } function drop_it(rest) { rest = as_statement_array(rest); if (self.body instanceof AST_BlockStatement) { self.body = self.body.clone(); self.body.body = rest.concat(self.body.body.slice(1)); self.body = self.body.transform(compressor); } else { self.body = make_node(AST_BlockStatement, self.body, { body: rest }).transform(compressor); } self = if_break_in_loop(self, compressor); } } def_optimize(AST_For, function(self, compressor) { if (!compressor.option("loops")) return self; if (compressor.option("side_effects") && self.init) { self.init = self.init.drop_side_effect_free(compressor); } if (self.condition) { var cond = self.condition.evaluate(compressor); if (!(cond instanceof AST_Node)) { if (cond) self.condition = null; else if (!compressor.option("dead_code")) { var orig = self.condition; self.condition = make_node_from_constant(cond, self.condition); self.condition = best_of_expression(self.condition.transform(compressor), orig); } } if (compressor.option("dead_code")) { if (cond instanceof AST_Node) cond = self.condition.tail_node().evaluate(compressor); if (!cond) { var body = []; trim_unreachable_code(compressor, self.body, body); if (self.init instanceof AST_Statement) { body.push(self.init); } else if (self.init) { body.push(make_node(AST_SimpleStatement, self.init, { body: self.init })); } body.push(make_node(AST_SimpleStatement, self.condition, { body: self.condition })); return make_node(AST_BlockStatement, self, { body: body }).optimize(compressor); } } } return if_break_in_loop(self, compressor); }); def_optimize(AST_If, function(self, compressor) { if (is_empty(self.alternative)) self.alternative = null; if (!compressor.option("conditionals")) return self; // if condition can be statically determined, drop // one of the blocks. note, statically determined implies // “has no side effects”; also it doesn't work for cases like // `x && true`, though it probably should. var cond = self.condition.evaluate(compressor); if (!compressor.option("dead_code") && !(cond instanceof AST_Node)) { var orig = self.condition; self.condition = make_node_from_constant(cond, orig); self.condition = best_of_expression(self.condition.transform(compressor), orig); } if (compressor.option("dead_code")) { if (cond instanceof AST_Node) cond = self.condition.tail_node().evaluate(compressor); if (!cond) { var body = []; trim_unreachable_code(compressor, self.body, body); body.push(make_node(AST_SimpleStatement, self.condition, { body: self.condition })); if (self.alternative) body.push(self.alternative); return make_node(AST_BlockStatement, self, { body: body }).optimize(compressor); } else if (!(cond instanceof AST_Node)) { var body = []; body.push(make_node(AST_SimpleStatement, self.condition, { body: self.condition })); body.push(self.body); if (self.alternative) { trim_unreachable_code(compressor, self.alternative, body); } return make_node(AST_BlockStatement, self, { body: body }).optimize(compressor); } } var negated = self.condition.negate(compressor); var self_condition_length = self.condition.size(); var negated_length = negated.size(); var negated_is_best = negated_length < self_condition_length; if (self.alternative && negated_is_best) { negated_is_best = false; // because we already do the switch here. // no need to swap values of self_condition_length and negated_length // here because they are only used in an equality comparison later on. self.condition = negated; var tmp = self.body; self.body = self.alternative || make_node(AST_EmptyStatement, self); self.alternative = tmp; } if (is_empty(self.body) && is_empty(self.alternative)) { return make_node(AST_SimpleStatement, self.condition, { body: self.condition.clone() }).optimize(compressor); } if (self.body instanceof AST_SimpleStatement && self.alternative instanceof AST_SimpleStatement) { return make_node(AST_SimpleStatement, self, { body: make_node(AST_Conditional, self, { condition : self.condition, consequent : self.body.body, alternative : self.alternative.body }) }).optimize(compressor); } if (is_empty(self.alternative) && self.body instanceof AST_SimpleStatement) { if (self_condition_length === negated_length && !negated_is_best && self.condition instanceof AST_Binary && self.condition.operator == "||") { // although the code length of self.condition and negated are the same, // negated does not require additional surrounding parentheses. // see https://github.com/mishoo/UglifyJS2/issues/979 negated_is_best = true; } if (negated_is_best) return make_node(AST_SimpleStatement, self, { body: make_node(AST_Binary, self, { operator : "||", left : negated, right : self.body.body }) }).optimize(compressor); return make_node(AST_SimpleStatement, self, { body: make_node(AST_Binary, self, { operator : "&&", left : self.condition, right : self.body.body }) }).optimize(compressor); } if (self.body instanceof AST_EmptyStatement && self.alternative instanceof AST_SimpleStatement) { return make_node(AST_SimpleStatement, self, { body: make_node(AST_Binary, self, { operator : "||", left : self.condition, right : self.alternative.body }) }).optimize(compressor); } if (self.body instanceof AST_Exit && self.alternative instanceof AST_Exit && self.body.TYPE == self.alternative.TYPE) { return make_node(self.body.CTOR, self, { value: make_node(AST_Conditional, self, { condition : self.condition, consequent : self.body.value || make_node(AST_Undefined, self.body), alternative : self.alternative.value || make_node(AST_Undefined, self.alternative) }).transform(compressor) }).optimize(compressor); } if (self.body instanceof AST_If && !self.body.alternative && !self.alternative) { self = make_node(AST_If, self, { condition: make_node(AST_Binary, self.condition, { operator: "&&", left: self.condition, right: self.body.condition }), body: self.body.body, alternative: null }); } if (aborts(self.body)) { if (self.alternative) { var alt = self.alternative; self.alternative = null; return make_node(AST_BlockStatement, self, { body: [ self, alt ] }).optimize(compressor); } } if (aborts(self.alternative)) { var body = self.body; self.body = self.alternative; self.condition = negated_is_best ? negated : self.condition.negate(compressor); self.alternative = null; return make_node(AST_BlockStatement, self, { body: [ self, body ] }).optimize(compressor); } return self; }); def_optimize(AST_Switch, function(self, compressor) { if (!compressor.option("switches")) return self; var branch; var value = self.expression.evaluate(compressor); if (!(value instanceof AST_Node)) { var orig = self.expression; self.expression = make_node_from_constant(value, orig); self.expression = best_of_expression(self.expression.transform(compressor), orig); } if (!compressor.option("dead_code")) return self; if (value instanceof AST_Node) { value = self.expression.tail_node().evaluate(compressor); } var decl = []; var body = []; var default_branch; var exact_match; for (var i = 0, len = self.body.length; i < len && !exact_match; i++) { branch = self.body[i]; if (branch instanceof AST_Default) { if (!default_branch) { default_branch = branch; } else { eliminate_branch(branch, body[body.length - 1]); } } else if (!(value instanceof AST_Node)) { var exp = branch.expression.evaluate(compressor); if (!(exp instanceof AST_Node) && exp !== value) { eliminate_branch(branch, body[body.length - 1]); continue; } if (exp instanceof AST_Node && !exp.has_side_effects(compressor)) { exp = branch.expression.tail_node().evaluate(compressor); } if (exp === value) { exact_match = branch; if (default_branch) { var default_index = body.indexOf(default_branch); body.splice(default_index, 1); eliminate_branch(default_branch, body[default_index - 1]); default_branch = null; } } } body.push(branch); } while (i < len) eliminate_branch(self.body[i++], body[body.length - 1]); self.body = body; let default_or_exact = default_branch || exact_match; default_branch = null; exact_match = null; // group equivalent branches so they will be located next to each other, // that way the next micro-optimization will merge them. // ** bail micro-optimization if not a simple switch case with breaks if (body.every((branch, i) => (branch === default_or_exact || branch.expression instanceof AST_Constant) && (branch.body.length === 0 || aborts(branch) || body.length - 1 === i)) ) { for (let i = 0; i < body.length; i++) { const branch = body[i]; for (let j = i + 1; j < body.length; j++) { const next = body[j]; if (next.body.length === 0) continue; const last_branch = j === (body.length - 1); const equivalentBranch = branches_equivalent(next, branch, false); if (equivalentBranch || (last_branch && branches_equivalent(next, branch, true))) { if (!equivalentBranch && last_branch) { next.body.push(make_node(AST_Break)); } // let's find previous siblings with inert fallthrough... let x = j - 1; let fallthroughDepth = 0; while (x > i) { if (is_inert_body(body[x--])) { fallthroughDepth++; } else { break; } } const plucked = body.splice(j - fallthroughDepth, 1 + fallthroughDepth); body.splice(i + 1, 0, ...plucked); i += plucked.length; } } } } // merge equivalent branches in a row for (let i = 0; i < body.length; i++) { let branch = body[i]; if (branch.body.length === 0) continue; if (!aborts(branch)) continue; for (let j = i + 1; j < body.length; i++, j++) { let next = body[j]; if (next.body.length === 0) continue; if ( branches_equivalent(next, branch, false) || (j === body.length - 1 && branches_equivalent(next, branch, true)) ) { branch.body = []; branch = next; continue; } break; } } // Prune any empty branches at the end of the switch statement. { let i = body.length - 1; for (; i >= 0; i--) { let bbody = body[i].body; if (is_break(bbody[bbody.length - 1], compressor)) bbody.pop(); if (!is_inert_body(body[i])) break; } // i now points to the index of a branch that contains a body. By incrementing, it's // pointing to the first branch that's empty. i++; if (!default_or_exact || body.indexOf(default_or_exact) >= i) { // The default behavior is to do nothing. We can take advantage of that to // remove all case expressions that are side-effect free that also do // nothing, since they'll default to doing nothing. But we can't remove any // case expressions before one that would side-effect, since they may cause // the side-effect to be skipped. for (let j = body.length - 1; j >= i; j--) { let branch = body[j]; if (branch === default_or_exact) { default_or_exact = null; body.pop(); } else if (!branch.expression.has_side_effects(compressor)) { body.pop(); } else { break; } } } } // Prune side-effect free branches that fall into default. DEFAULT: if (default_or_exact) { let default_index = body.indexOf(default_or_exact); let default_body_index = default_index; for (; default_body_index < body.length - 1; default_body_index++) { if (!is_inert_body(body[default_body_index])) break; } if (default_body_index < body.length - 1) { break DEFAULT; }