| | |
| | | var global_defs = this.options["global_defs"]; |
| | | if (typeof global_defs == "object") for (var key in global_defs) { |
| | | if (/^@/.test(key) && HOP(global_defs, key)) { |
| | | global_defs[key.slice(1)] = parse(global_defs[key], { |
| | | expression: true |
| | | }); |
| | | global_defs[key.slice(1)] = parse(global_defs[key], { expression: true }); |
| | | } |
| | | } |
| | | if (this.options["inline"] === true) this.options["inline"] = 3; |
| | | if (this.options["inline"] === true) this.options["inline"] = 4; |
| | | this.drop_fargs = this.options["keep_fargs"] ? return_false : function(lambda, parent) { |
| | | if (lambda.length_read) return false; |
| | | var name = lambda.name; |
| | |
| | | }; |
| | | } |
| | | |
| | | Compressor.prototype = new TreeTransformer; |
| | | merge(Compressor.prototype, { |
| | | option: function(key) { return this.options[key] }, |
| | | exposed: function(def) { |
| | | if (def.exported) return true; |
| | | if (def.undeclared) return true; |
| | | if (!(def.global || def.scope.resolve() instanceof AST_Toplevel)) return false; |
| | | var toplevel = this.toplevel; |
| | | return !all(def.orig, function(sym) { |
| | | return toplevel[sym instanceof AST_SymbolDefun ? "funcs" : "vars"]; |
| | | }); |
| | | }, |
| | | compress: function(node) { |
| | | node = node.resolve_defines(this); |
| | | node.hoist_exports(this); |
| | | if (this.option("expression")) { |
| | | node.process_expression(true); |
| | | } |
| | | var passes = +this.options.passes || 1; |
| | | var min_count = 1 / 0; |
| | | var stopping = false; |
| | | var mangle = { ie: this.option("ie") }; |
| | | for (var pass = 0; pass < passes; pass++) { |
| | | node.figure_out_scope(mangle); |
| | | if (pass > 0 || this.option("reduce_vars")) |
| | | node.reset_opt_flags(this); |
| | | node = node.transform(this); |
| | | if (passes > 1) { |
| | | var count = 0; |
| | | node.walk(new TreeWalker(function() { |
| | | count++; |
| | | })); |
| | | AST_Node.info("pass {pass}: last_count: {min_count}, count: {count}", { |
| | | pass: pass, |
| | | min_count: min_count, |
| | | count: count, |
| | | }); |
| | | if (count < min_count) { |
| | | min_count = count; |
| | | stopping = false; |
| | | } else if (stopping) { |
| | | break; |
| | | } else { |
| | | stopping = true; |
| | | } |
| | | Compressor.prototype = new TreeTransformer(function(node, descend, in_list) { |
| | | if (node._squeezed) return node; |
| | | var is_scope = node instanceof AST_Scope; |
| | | if (is_scope) { |
| | | node.hoist_properties(this); |
| | | node.hoist_declarations(this); |
| | | node.process_returns(this); |
| | | } |
| | | // Before https://github.com/mishoo/UglifyJS/pull/1602 AST_Node.optimize() |
| | | // would call AST_Node.transform() if a different instance of AST_Node is |
| | | // produced after OPT(). |
| | | // 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 (is_scope && opt === node && !this.has_directive("use asm") && !opt.pinned()) { |
| | | opt.drop_unused(this); |
| | | if (opt.merge_variables(this)) opt.drop_unused(this); |
| | | descend(opt, this); |
| | | } |
| | | if (opt === node) opt._squeezed = true; |
| | | return opt; |
| | | }); |
| | | Compressor.prototype.option = function(key) { |
| | | return this.options[key]; |
| | | }; |
| | | Compressor.prototype.exposed = function(def) { |
| | | if (def.exported) return true; |
| | | if (def.undeclared) return true; |
| | | if (!(def.global || def.scope.resolve() instanceof AST_Toplevel)) return false; |
| | | var toplevel = this.toplevel; |
| | | return !all(def.orig, function(sym) { |
| | | return toplevel[sym instanceof AST_SymbolDefun ? "funcs" : "vars"]; |
| | | }); |
| | | }; |
| | | Compressor.prototype.compress = function(node) { |
| | | node = node.resolve_defines(this); |
| | | node.hoist_exports(this); |
| | | if (this.option("expression")) node.process_expression(true); |
| | | var merge_vars = this.options.merge_vars; |
| | | var passes = +this.options.passes || 1; |
| | | var min_count = 1 / 0; |
| | | var stopping = false; |
| | | var mangle = { ie: this.option("ie") }; |
| | | for (var pass = 0; pass < passes; pass++) { |
| | | node.figure_out_scope(mangle); |
| | | if (pass > 0 || this.option("reduce_vars")) |
| | | node.reset_opt_flags(this); |
| | | this.options.merge_vars = merge_vars && (stopping || pass == passes - 1); |
| | | node = node.transform(this); |
| | | if (passes > 1) { |
| | | var count = 0; |
| | | node.walk(new TreeWalker(function() { |
| | | count++; |
| | | })); |
| | | AST_Node.info("pass {pass}: last_count: {min_count}, count: {count}", { |
| | | pass: pass, |
| | | min_count: min_count, |
| | | count: count, |
| | | }); |
| | | if (count < min_count) { |
| | | min_count = count; |
| | | stopping = false; |
| | | } else if (stopping) { |
| | | break; |
| | | } else { |
| | | stopping = true; |
| | | } |
| | | } |
| | | if (this.option("expression")) { |
| | | node.process_expression(false); |
| | | } |
| | | return node; |
| | | }, |
| | | before: function(node, descend, in_list) { |
| | | if (node._squeezed) return node; |
| | | var is_scope = node instanceof AST_Scope; |
| | | if (is_scope) { |
| | | node.hoist_properties(this); |
| | | node.hoist_declarations(this); |
| | | node.process_boolean_returns(this); |
| | | } |
| | | // Before https://github.com/mishoo/UglifyJS/pull/1602 AST_Node.optimize() |
| | | // would call AST_Node.transform() if a different instance of AST_Node is |
| | | // produced after OPT(). |
| | | // 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 (is_scope && opt === node && !this.has_directive("use asm") && !opt.pinned()) { |
| | | opt.merge_variables(this); |
| | | opt.drop_unused(this); |
| | | descend(opt, this); |
| | | } |
| | | if (opt === node) opt._squeezed = true; |
| | | return opt; |
| | | } |
| | | }); |
| | | if (this.option("expression")) node.process_expression(false); |
| | | return node; |
| | | }; |
| | | |
| | | (function(OPT) { |
| | | OPT(AST_Node, function(self, compressor) { |
| | |
| | | AST_Scope.DEFMETHOD("process_expression", function(insert, transform) { |
| | | var self = this; |
| | | var tt = new TreeTransformer(function(node) { |
| | | if (insert && node instanceof AST_SimpleStatement) { |
| | | return transform ? transform(node) : make_node(AST_Return, node, { value: node.body }); |
| | | } |
| | | if (!insert && node instanceof AST_Return) { |
| | | return transform ? transform(node) : make_node(AST_SimpleStatement, node, { |
| | | body: node.value || make_node(AST_UnaryPrefix, node, { |
| | | if (insert) { |
| | | if (node instanceof AST_Directive) node = make_node(AST_SimpleStatement, node, { |
| | | body: make_node(AST_String, node, node), |
| | | }); |
| | | if (node instanceof AST_SimpleStatement) { |
| | | return transform ? transform(node) : make_node(AST_Return, node, { value: node.body }); |
| | | } |
| | | } else if (node instanceof AST_Return) { |
| | | if (transform) return transform(node); |
| | | var value = node.value; |
| | | if (value instanceof AST_String) return make_node(AST_Directive, node, value); |
| | | return make_node(AST_SimpleStatement, node, { |
| | | body: value || make_node(AST_UnaryPrefix, node, { |
| | | operator: "void", |
| | | expression: make_node(AST_Number, node, { value: 0 }), |
| | | }), |
| | |
| | | } |
| | | } else if (node instanceof AST_If) { |
| | | node.body = node.body.transform(tt); |
| | | if (node.alternative) { |
| | | node.alternative = node.alternative.transform(tt); |
| | | } |
| | | if (node.alternative) node.alternative = node.alternative.transform(tt); |
| | | } else if (node instanceof AST_With) { |
| | | node.body = node.body.transform(tt); |
| | | } |
| | |
| | | } |
| | | var lhs = is_lhs(node, parent); |
| | | if (lhs) return lhs; |
| | | if (level == 0 && value && value.is_constant()) return; |
| | | if (parent instanceof AST_Array) return is_modified(compressor, tw, parent, parent, level + 1); |
| | | if (parent instanceof AST_Assign) switch (parent.operator) { |
| | | case "=": |
| | | return is_modified(compressor, tw, parent, value, level + 1, immutable, recursive); |
| | | case "&&=": |
| | | case "||=": |
| | | case "??=": |
| | | return is_modified(compressor, tw, parent, parent, level + 1); |
| | | default: |
| | | return; |
| | | } |
| | | if (parent instanceof AST_Binary) { |
| | | if (!lazy_op[parent.operator]) return; |
| | | return is_modified(compressor, tw, parent, parent, level + 1); |
| | |
| | | return def.name == "arguments" && def.scope.uses_arguments; |
| | | } |
| | | |
| | | function is_funarg(def) { |
| | | return def.orig[0] instanceof AST_SymbolFunarg || def.orig[1] instanceof AST_SymbolFunarg; |
| | | } |
| | | |
| | | function cross_scope(def, sym) { |
| | | do { |
| | | if (def === sym) return false; |
| | |
| | | } |
| | | |
| | | function can_drop_symbol(ref, compressor, keep_lambda) { |
| | | var def = ref.definition(); |
| | | var def = ref.redef || ref.definition(); |
| | | if (ref.in_arg && is_funarg(def)) return false; |
| | | return all(def.orig, function(sym) { |
| | | if (sym instanceof AST_SymbolConst || sym instanceof AST_SymbolLet) { |
| | | if (sym instanceof AST_SymbolImport) return true; |
| | | return compressor && can_varify(compressor, sym); |
| | | } |
| | | return !(keep_lambda && sym instanceof AST_SymbolLambda); |
| | |
| | | |
| | | function reset_def(tw, compressor, def) { |
| | | def.assignments = 0; |
| | | def.bool_fn = 0; |
| | | def.bool_return = 0; |
| | | def.drop_return = 0; |
| | | def.cross_loop = false; |
| | | def.direct_access = false; |
| | | def.escaped = []; |
| | |
| | | def.reassigned = 0; |
| | | def.recursive_refs = 0; |
| | | def.references = []; |
| | | def.should_replace = undefined; |
| | | def.single_use = undefined; |
| | | } |
| | | |
| | |
| | | }); |
| | | } |
| | | |
| | | function safe_to_visit(tw, fn) { |
| | | var marker = fn.safe_ids; |
| | | return marker === undefined || marker === tw.safe_ids; |
| | | } |
| | | |
| | | function walk_fn_def(tw, fn) { |
| | | var was_scanning = tw.fn_scanning; |
| | | tw.fn_scanning = fn; |
| | |
| | | d.single_use = false; |
| | | var fixed = d.fixed; |
| | | if (typeof fixed == "function") fixed = fixed(); |
| | | if (fixed instanceof AST_Lambda && HOP(fixed, "safe_ids")) return; |
| | | if (fixed instanceof AST_Lambda && fixed.safe_ids !== undefined) return; |
| | | d.fixed = false; |
| | | }); |
| | | } |
| | | |
| | | function mark_fn_def(tw, def, fn) { |
| | | if (!HOP(fn, "safe_ids")) return; |
| | | var marker = fn.safe_ids; |
| | | if (marker === undefined) return; |
| | | if (marker === false) return; |
| | | if (fn.parent_scope.resolve().may_call_this === return_true) { |
| | | if (member(fn, tw.fn_visited)) revisit_fn_def(tw, fn); |
| | |
| | | walk_fn_def(tw, fn); |
| | | }); |
| | | fn_defs.forEach(function(fn) { |
| | | delete fn.safe_ids; |
| | | fn.safe_ids = undefined; |
| | | }); |
| | | delete scope.fn_defs; |
| | | delete scope.may_call_this; |
| | | scope.fn_defs = undefined; |
| | | scope.may_call_this = undefined; |
| | | } |
| | | |
| | | function push(tw) { |
| | |
| | | if (def.single_use == "m") return false; |
| | | var safe = tw.safe_ids[def.id]; |
| | | if (safe) { |
| | | if (!HOP(tw.safe_ids, def.id)) safe.read = safe.read && safe.read !== tw.safe_ids ? true : tw.safe_ids; |
| | | var in_order = HOP(tw.safe_ids, def.id); |
| | | if (!in_order) safe.read = safe.read && safe.read !== tw.safe_ids ? true : tw.safe_ids; |
| | | if (def.fixed == null) { |
| | | if (is_arguments(def)) return false; |
| | | if (def.global && def.name == "arguments") return false; |
| | | tw.loop_ids[def.id] = null; |
| | | def.fixed = make_node(AST_Undefined, def.orig[0]); |
| | | if (in_order) def.safe_ids = undefined; |
| | | return true; |
| | | } |
| | | return !safe.assign || safe.assign === tw.safe_ids; |
| | |
| | | if (def.fixed === undefined) return declare || all(def.orig, function(sym) { |
| | | return !(sym instanceof AST_SymbolLet); |
| | | }); |
| | | if (def.fixed === false) return false; |
| | | if (def.fixed === false || def.fixed === 0) return false; |
| | | var safe = tw.safe_ids[def.id]; |
| | | if (def.safe_ids) { |
| | | def.safe_ids[def.id] = false; |
| | | delete def.safe_ids; |
| | | def.safe_ids = undefined; |
| | | return def.fixed === null || HOP(tw.safe_ids, def.id) && !safe.read; |
| | | } |
| | | if (!HOP(tw.safe_ids, def.id)) { |
| | | if (!safe) return false; |
| | | if (safe.read) { |
| | | if (safe.read || tw.in_loop) { |
| | | var scope = tw.find_parent(AST_BlockScope); |
| | | if (scope instanceof AST_Class) return false; |
| | | if (def.scope.resolve() !== scope.resolve()) return false; |
| | |
| | | var node = make_node(AST_SymbolRef, ref, ref); |
| | | node.fixed = fixed || make_node(AST_Undefined, ref); |
| | | return node; |
| | | } |
| | | |
| | | function replace_ref(ref, fixed) { |
| | | return function() { |
| | | var node = make_ref(ref, fixed); |
| | | var def = ref.definition(); |
| | | def.references.push(node); |
| | | def.replaced++; |
| | | return node; |
| | | }; |
| | | } |
| | | |
| | | function ref_once(compressor, def) { |
| | |
| | | if (depth > 1 && !(value && value.is_constant_expression(scope))) depth = 1; |
| | | if (!d.escaped.depth || d.escaped.depth > depth) d.escaped.depth = depth; |
| | | if (d.scope.resolve() !== scope.resolve()) d.escaped.cross_scope = true; |
| | | if (d.fixed) d.fixed.escaped = d.escaped; |
| | | return; |
| | | } else if (value_in_use(node, parent)) { |
| | | mark_escaped(tw, d, scope, parent, parent, level + 1, depth); |
| | |
| | | if (parent instanceof AST_SimpleStatement) return; |
| | | if (parent instanceof AST_Unary && !unary_side_effects[parent.operator]) return; |
| | | d.direct_access = true; |
| | | if (d.fixed) d.fixed.direct_access = true; |
| | | } |
| | | |
| | | function mark_assignment_to_arguments(node) { |
| | |
| | | }); |
| | | } |
| | | |
| | | function make_fixed(save, fn) { |
| | | var prev_save, prev_value; |
| | | return function() { |
| | | var current = save(); |
| | | if (prev_save !== current) { |
| | | prev_save = current; |
| | | prev_value = fn(current); |
| | | } |
| | | return prev_value; |
| | | }; |
| | | } |
| | | |
| | | function make_fixed_default(compressor, node, save) { |
| | | var prev_save, prev_seq; |
| | | return function() { |
| | | var current = save(); |
| | | var ev; |
| | | if (!is_undefined(current, compressor) && (ev = fuzzy_eval(compressor, current, true)) !== undefined) { |
| | | return ev instanceof AST_Node ? node : current; |
| | | } |
| | | if (prev_save !== current) { |
| | | prev_save = current; |
| | | prev_seq = make_sequence(node, [ current, node.value ]); |
| | | } |
| | | return prev_seq; |
| | | }; |
| | | } |
| | | |
| | | function scan_declaration(tw, compressor, lhs, fixed, visit) { |
| | | var scanner = new TreeWalker(function(node) { |
| | | if (node instanceof AST_DefaultValue) { |
| | |
| | | node.value.walk(tw); |
| | | pop(tw); |
| | | var save = fixed; |
| | | if (save) fixed = function() { |
| | | var value = save(); |
| | | var ev; |
| | | if (is_undefined(value, compressor) |
| | | || (ev = fuzzy_eval(compressor, value, true)) === undefined) { |
| | | return make_sequence(node, [ value, node.value ]); |
| | | } |
| | | return ev instanceof AST_Node ? node : value; |
| | | }; |
| | | if (save) fixed = make_fixed_default(compressor, node, save); |
| | | node.name.walk(scanner); |
| | | fixed = save; |
| | | return true; |
| | |
| | | var save = fixed; |
| | | node.elements.forEach(function(node, index) { |
| | | if (node instanceof AST_Hole) return reset_flags(node); |
| | | if (save) fixed = function() { |
| | | if (save) fixed = make_fixed(save, function(value) { |
| | | return make_node(AST_Sub, node, { |
| | | expression: save(), |
| | | expression: value, |
| | | property: make_node(AST_Number, node, { value: index }), |
| | | }); |
| | | }; |
| | | }); |
| | | node.walk(scanner); |
| | | }); |
| | | if (node.rest) { |
| | | if (save) fixed = compressor.option("rests") && function() { |
| | | var value = save(); |
| | | return value instanceof AST_Array ? make_node(AST_Array, node, { |
| | | elements: value.elements.slice(node.elements.length), |
| | | }) : node; |
| | | }; |
| | | var fixed_node; |
| | | if (save) fixed = compressor.option("rests") && make_fixed(save, function(value) { |
| | | if (!(value instanceof AST_Array)) return node; |
| | | for (var i = 0, len = node.elements.length; i < len; i++) { |
| | | if (value.elements[i] instanceof AST_Spread) return node; |
| | | } |
| | | if (!fixed_node) fixed_node = make_node(AST_Array, node); |
| | | fixed_node.elements = value.elements.slice(len); |
| | | return fixed_node; |
| | | }); |
| | | node.rest.walk(scanner); |
| | | } |
| | | fixed = save; |
| | |
| | | node.key.walk(tw); |
| | | pop(tw); |
| | | } |
| | | if (save) fixed = function() { |
| | | if (save) fixed = make_fixed(save, function(value) { |
| | | var key = node.key; |
| | | var type = AST_Sub; |
| | | if (typeof key == "string") { |
| | |
| | | } |
| | | } |
| | | return make_node(type, node, { |
| | | expression: save(), |
| | | property: key |
| | | expression: value, |
| | | property: key, |
| | | }); |
| | | }; |
| | | }); |
| | | node.value.walk(scanner); |
| | | }); |
| | | if (node.rest) { |
| | |
| | | return arg || make_node(AST_Undefined, iife); |
| | | }, visit); |
| | | }); |
| | | var rest = fn.rest; |
| | | var rest = fn.rest, fixed_node; |
| | | if (rest) scan_declaration(tw, compressor, rest, compressor.option("rests") && function() { |
| | | return fn.rest === rest ? make_node(AST_Array, fn, { |
| | | elements: iife.args.slice(fn.argnames.length), |
| | | }) : rest; |
| | | if (fn.rest !== rest) return rest; |
| | | if (!fixed_node) fixed_node = make_node(AST_Array, fn); |
| | | fixed_node.elements = iife.args.slice(fn.argnames.length); |
| | | return fixed_node; |
| | | }, visit); |
| | | walk_lambda(fn, tw); |
| | | var safe_ids = tw.safe_ids; |
| | |
| | | if (left.equivalent_to(right) && !left.has_side_effects(compressor)) { |
| | | right.walk(tw); |
| | | walk_prop(left); |
| | | node.__drop = true; |
| | | node.redundant = true; |
| | | return true; |
| | | } |
| | | if (ld && right instanceof AST_LambdaExpression) { |
| | |
| | | case "&&=": |
| | | case "||=": |
| | | case "??=": |
| | | left.walk(tw); |
| | | push(tw); |
| | | if (scan) { |
| | | right.walk(tw); |
| | | walk_assign(); |
| | | } else { |
| | | mark_assignment_to_arguments(left); |
| | | right.walk(tw); |
| | | } |
| | | pop(tw); |
| | | return true; |
| | | var lazy = true; |
| | | default: |
| | | if (!scan) { |
| | | mark_assignment_to_arguments(left); |
| | | return; |
| | | return walk_lazy(); |
| | | } |
| | | ld.assignments++; |
| | | var fixed = ld.fixed; |
| | | if (is_modified(compressor, tw, node, node, 0)) { |
| | | ld.fixed = false; |
| | | return; |
| | | return walk_lazy(); |
| | | } |
| | | var safe = safe_to_read(tw, ld); |
| | | if (lazy) push(tw); |
| | | right.walk(tw); |
| | | if (lazy) pop(tw); |
| | | if (safe && !left.in_arg && safe_to_assign(tw, ld)) { |
| | | push_ref(ld, left); |
| | | mark(tw, ld); |
| | |
| | | right: node.right, |
| | | }); |
| | | }; |
| | | left.fixed.assigns = !fixed || !fixed.assigns ? [] : fixed.assigns.slice(); |
| | | left.fixed.assigns = !fixed || !fixed.assigns ? [ ld.orig[0] ] : fixed.assigns.slice(); |
| | | left.fixed.assigns.push(node); |
| | | left.fixed.to_binary = replace_ref(left, fixed); |
| | | } else { |
| | | left.walk(tw); |
| | | ld.fixed = false; |
| | |
| | | } |
| | | var d = sym.definition(); |
| | | d.assignments++; |
| | | if (fixed && !modified && !sym.in_arg && safe_to_assign(tw, d)) { |
| | | if (!fixed || sym.in_arg || !safe_to_assign(tw, d)) { |
| | | walk(); |
| | | d.fixed = false; |
| | | } else if (modified) { |
| | | walk(); |
| | | d.fixed = 0; |
| | | } else { |
| | | push_ref(d, sym); |
| | | mark(tw, d); |
| | | if (left instanceof AST_Destructured |
| | |
| | | d.single_use = false; |
| | | } |
| | | tw.loop_ids[d.id] = tw.in_loop; |
| | | mark_escaped(tw, d, sym.scope, node, right, 0, 1); |
| | | sym.fixed = d.fixed = fixed; |
| | | sym.fixed.assigns = [ node ]; |
| | | } else { |
| | | walk(); |
| | | d.fixed = false; |
| | | mark_escaped(tw, d, sym.scope, node, right, 0, 1); |
| | | } |
| | | }); |
| | | } |
| | | |
| | | function walk_lazy() { |
| | | if (!lazy) return; |
| | | left.walk(tw); |
| | | push(tw); |
| | | right.walk(tw); |
| | | pop(tw); |
| | | return true; |
| | | } |
| | | }); |
| | | def(AST_Binary, function(tw) { |
| | |
| | | if (iife) delete exp.reduce_vars; |
| | | return true; |
| | | } |
| | | if (node.TYPE == "Call" && tw.in_boolean_context()) { |
| | | if (exp instanceof AST_SymbolRef) { |
| | | exp.definition().bool_fn++; |
| | | } else if (exp instanceof AST_Assign && exp.operator == "=" && exp.left instanceof AST_SymbolRef) { |
| | | exp.left.definition().bool_fn++; |
| | | } |
| | | if (node.TYPE == "Call") switch (tw.in_boolean_context()) { |
| | | case "d": |
| | | var drop = true; |
| | | case true: |
| | | mark_refs(exp, drop); |
| | | } |
| | | exp.walk(tw); |
| | | var optional = node.optional; |
| | |
| | | tw.find_parent(AST_Scope).may_call_this(); |
| | | } |
| | | return true; |
| | | |
| | | function mark_refs(node, drop) { |
| | | if (node instanceof AST_Assign) { |
| | | if (node.operator != "=") return; |
| | | mark_refs(node.left, drop); |
| | | mark_refs(node.right, drop); |
| | | } else if (node instanceof AST_Binary) { |
| | | if (!lazy_op[node.operator]) return; |
| | | mark_refs(node.left, drop); |
| | | mark_refs(node.right, drop); |
| | | } else if (node instanceof AST_Conditional) { |
| | | mark_refs(node.consequent, drop); |
| | | mark_refs(node.alternative, drop); |
| | | } else if (node instanceof AST_SymbolRef) { |
| | | var def = node.definition(); |
| | | def.bool_return++; |
| | | if (drop) def.drop_return++; |
| | | } |
| | | } |
| | | }); |
| | | def(AST_Class, function(tw, descend, compressor) { |
| | | var node = this; |
| | |
| | | }); |
| | | def(AST_Lambda, function(tw, descend, compressor) { |
| | | var fn = this; |
| | | if (HOP(fn, "safe_ids") && fn.safe_ids !== tw.safe_ids) return true; |
| | | if (!safe_to_visit(tw, fn)) return true; |
| | | if (!push_uniq(tw.fn_visited, fn)) return true; |
| | | fn.inlined = false; |
| | | push(tw); |
| | |
| | | var def = fn.name.definition(); |
| | | var parent = tw.parent(); |
| | | if (parent instanceof AST_ExportDeclaration || parent instanceof AST_ExportDefault) def.single_use = false; |
| | | if (HOP(fn, "safe_ids") && fn.safe_ids !== tw.safe_ids) return true; |
| | | if (!safe_to_visit(tw, fn)) return true; |
| | | if (!push_uniq(tw.fn_visited, fn)) return true; |
| | | fn.inlined = false; |
| | | push(tw); |
| | |
| | | if (!safe) return; |
| | | safe.assign = true; |
| | | }); |
| | | if (d.fixed === false) { |
| | | if (d.fixed === false || d.fixed === 0) { |
| | | var redef = d.redefined(); |
| | | if (redef && cross_scope(d.scope, this.scope)) redef.single_use = false; |
| | | } else if (d.fixed === undefined || !safe_to_read(tw, d)) { |
| | |
| | | if (d.single_use) { |
| | | d.single_use = "m"; |
| | | } else { |
| | | d.fixed = false; |
| | | d.fixed = 0; |
| | | } |
| | | } |
| | | if (d.fixed && tw.loop_ids[d.id] !== tw.in_loop) d.cross_loop = true; |
| | |
| | | operator: node.operator.slice(0, -1), |
| | | left: make_node(AST_UnaryPrefix, node, { |
| | | operator: "+", |
| | | expression: make_ref(exp, fixed) |
| | | expression: make_ref(exp, fixed), |
| | | }), |
| | | right: make_node(AST_Number, node, { |
| | | value: 1 |
| | | }) |
| | | right: make_node(AST_Number, node, { value: 1 }), |
| | | }); |
| | | }; |
| | | d.fixed.assigns = fixed && fixed.assigns ? fixed.assigns.slice() : []; |
| | |
| | | exp.fixed = function() { |
| | | return make_node(AST_UnaryPrefix, node, { |
| | | operator: "+", |
| | | expression: make_ref(exp, fixed) |
| | | expression: make_ref(exp, fixed), |
| | | }); |
| | | }; |
| | | exp.fixed.assigns = fixed && fixed.assigns; |
| | | exp.fixed.to_prefix = replace_ref(exp, d.fixed); |
| | | } |
| | | } else { |
| | | exp.walk(tw); |
| | |
| | | function reset_flags(node) { |
| | | node._squeezed = false; |
| | | node._optimized = false; |
| | | delete node.fixed; |
| | | if (node instanceof AST_Scope) delete node._var_names; |
| | | if (node instanceof AST_BlockScope) node._var_names = undefined; |
| | | if (node instanceof AST_SymbolRef) node.fixed = undefined; |
| | | } |
| | | |
| | | AST_Toplevel.DEFMETHOD("reset_opt_flags", function(compressor) { |
| | |
| | | |
| | | AST_Symbol.DEFMETHOD("fixed_value", function() { |
| | | var fixed = this.definition().fixed; |
| | | if (fixed) { |
| | | if (this.fixed) fixed = this.fixed; |
| | | return (fixed instanceof AST_Node ? fixed : fixed()).tail_node(); |
| | | } |
| | | fixed = fixed === 0 && this.fixed; |
| | | if (!fixed) return fixed; |
| | | if (this.fixed) fixed = this.fixed; |
| | | return fixed instanceof AST_Node ? fixed : fixed(); |
| | | var value = (fixed instanceof AST_Node ? fixed : fixed()).tail_node(); |
| | | return value.is_constant() && value; |
| | | }); |
| | | |
| | | AST_SymbolRef.DEFMETHOD("is_immutable", function() { |
| | | var def = this.redef || this.definition(); |
| | | return def.orig.length == 1 && def.orig[0] instanceof AST_SymbolLambda; |
| | | return (this.in_arg || def.orig.length == 1) && def.orig[0] instanceof AST_SymbolLambda; |
| | | }); |
| | | |
| | | AST_Node.DEFMETHOD("convert_symbol", noop); |
| | |
| | | } |
| | | AST_SymbolDeclaration.DEFMETHOD("convert_symbol", convert_symbol); |
| | | AST_SymbolRef.DEFMETHOD("convert_symbol", convert_symbol); |
| | | |
| | | function process_to_assign(ref) { |
| | | var def = ref.definition(); |
| | | def.assignments++; |
| | | def.references.push(ref); |
| | | } |
| | | |
| | | function mark_destructured(process, tw) { |
| | | var marker = new TreeWalker(function(node) { |
| | |
| | | function make_node_from_constant(val, orig) { |
| | | switch (typeof val) { |
| | | case "string": |
| | | return make_node(AST_String, orig, { |
| | | value: val |
| | | }); |
| | | return make_node(AST_String, orig, { value: val }); |
| | | case "number": |
| | | if (isNaN(val)) return make_node(AST_NaN, orig); |
| | | if (isFinite(val)) { |
| | |
| | | } |
| | | return val < 0 ? make_node(AST_UnaryPrefix, orig, { |
| | | operator: "-", |
| | | expression: make_node(AST_Infinity, orig) |
| | | expression: make_node(AST_Infinity, orig), |
| | | }) : make_node(AST_Infinity, orig); |
| | | case "boolean": |
| | | return make_node(val ? AST_True : AST_False, orig); |
| | |
| | | return make_node(AST_Undefined, orig); |
| | | default: |
| | | if (val === null) { |
| | | return make_node(AST_Null, orig, { value: null }); |
| | | return make_node(AST_Null, orig); |
| | | } |
| | | if (val instanceof RegExp) { |
| | | return make_node(AST_RegExp, orig, { value: val }); |
| | |
| | | |
| | | function merge_sequence(array, node) { |
| | | if (node instanceof AST_Sequence) { |
| | | array.push.apply(array, node.expressions); |
| | | [].push.apply(array, node.expressions); |
| | | } else { |
| | | array.push(node); |
| | | } |
| | |
| | | return true; |
| | | } |
| | | |
| | | // Certain combination of unused name + side effect leads to invalid AST: |
| | | // https://github.com/mishoo/UglifyJS/issues/44 |
| | | // https://github.com/mishoo/UglifyJS/issues/1838 |
| | | // https://github.com/mishoo/UglifyJS/issues/3371 |
| | | // We fix it at this stage by moving the `var` outside the `for`. |
| | | function patch_for_init(node, in_list) { |
| | | var block; |
| | | if (node.init instanceof AST_BlockStatement) { |
| | | block = node.init; |
| | | node.init = block.body.pop(); |
| | | block.body.push(node); |
| | | } |
| | | if (node.init instanceof AST_Defun) { |
| | | if (!block) block = make_node(AST_BlockStatement, node, { body: [ node ] }); |
| | | block.body.splice(-1, 0, node.init); |
| | | node.init = null; |
| | | } else if (node.init instanceof AST_SimpleStatement) { |
| | | node.init = node.init.body; |
| | | } else if (is_empty(node.init)) { |
| | | node.init = null; |
| | | } |
| | | if (!block) return; |
| | | return in_list ? List.splice(block.body) : block; |
| | | } |
| | | |
| | | function tighten_body(statements, compressor) { |
| | | var in_loop, in_try, scope; |
| | | var in_lambda = last_of(compressor, function(node) { |
| | | return node instanceof AST_Lambda; |
| | | }); |
| | | var block_scope, in_loop, in_try, scope; |
| | | find_loop_scope_try(); |
| | | var CHANGED, max_iter = 10; |
| | | var changed, last_changed, max_iter = 10; |
| | | do { |
| | | CHANGED = false; |
| | | eliminate_spurious_blocks(statements); |
| | | last_changed = changed; |
| | | changed = 0; |
| | | if (eliminate_spurious_blocks(statements)) changed = 1; |
| | | if (!changed && last_changed == 1) break; |
| | | if (compressor.option("dead_code")) { |
| | | eliminate_dead_code(statements, compressor); |
| | | if (eliminate_dead_code(statements, compressor)) changed = 2; |
| | | if (!changed && last_changed == 2) break; |
| | | } |
| | | if (compressor.option("if_return")) { |
| | | handle_if_return(statements, compressor); |
| | | if (handle_if_return(statements, compressor)) changed = 3; |
| | | if (!changed && last_changed == 3) break; |
| | | } |
| | | if (compressor.option("awaits") && compressor.option("side_effects")) { |
| | | if (trim_awaits(statements, compressor)) changed = 4; |
| | | if (!changed && last_changed == 4) break; |
| | | } |
| | | if (compressor.option("inline") >= 4) { |
| | | if (inline_iife(statements, compressor)) changed = 5; |
| | | if (!changed && last_changed == 5) break; |
| | | } |
| | | if (compressor.sequences_limit > 0) { |
| | | sequencesize(statements, compressor); |
| | | sequencesize_2(statements, compressor); |
| | | if (sequencesize(statements, compressor)) changed = 6; |
| | | if (!changed && last_changed == 6) break; |
| | | if (sequencesize_2(statements, compressor)) changed = 7; |
| | | if (!changed && last_changed == 7) break; |
| | | } |
| | | if (compressor.option("join_vars")) { |
| | | join_consecutive_vars(statements); |
| | | if (join_consecutive_vars(statements)) changed = 8; |
| | | if (!changed && last_changed == 8) break; |
| | | } |
| | | if (compressor.option("collapse_vars")) { |
| | | collapse(statements, compressor); |
| | | if (collapse(statements, compressor)) changed = 9; |
| | | } |
| | | } while (CHANGED && max_iter-- > 0); |
| | | } while (changed && max_iter-- > 0); |
| | | return statements; |
| | | |
| | | function last_of(compressor, predicate) { |
| | | var block = compressor.self(), stat, level = 0; |
| | | do { |
| | | do { |
| | | if (predicate(block)) return true; |
| | | block = compressor.parent(level++); |
| | | } while (block instanceof AST_If && (stat = block)); |
| | | } while ((block instanceof AST_BlockStatement || block instanceof AST_Scope) |
| | | && is_last_statement(block.body, stat)); |
| | | } |
| | | |
| | | function find_loop_scope_try() { |
| | | var node = compressor.self(), level = 0; |
| | | do { |
| | | if (!block_scope && node.variables) block_scope = node; |
| | | if (node instanceof AST_Catch) { |
| | | if (compressor.parent(level).bfinally) { |
| | | if (!in_try) in_try = {}; |
| | |
| | | // Will not attempt to collapse assignments into or past code blocks |
| | | // which are not sequentially executed, e.g. loops and conditionals. |
| | | function collapse(statements, compressor) { |
| | | if (scope.pinned()) return statements; |
| | | if (scope.pinned()) return; |
| | | var args; |
| | | var assignments = Object.create(null); |
| | | var assignments = new Dictionary(); |
| | | var candidates = []; |
| | | var declare_only = Object.create(null); |
| | | var changed = false; |
| | | var declare_only = new Dictionary(); |
| | | var force_single; |
| | | var stat_index = statements.length; |
| | | var scanner = new TreeTransformer(function(node, descend) { |
| | |
| | | if (stop_after === node) abort = true; |
| | | return node; |
| | | } |
| | | // Stop immediately if these node types are encountered |
| | | var parent = scanner.parent(); |
| | | if (should_stop(node, parent)) { |
| | | abort = true; |
| | | return node; |
| | | } |
| | | // Stop only if candidate is found within conditional branches |
| | | if (!stop_if_hit && in_conditional(node, parent)) { |
| | | stop_if_hit = parent; |
| | | } |
| | | // Cascade compound assignments |
| | | if (compound && scan_lhs && can_replace && !stop_if_hit |
| | | && node instanceof AST_Assign && node.operator != "=" && node.left.equivalent_to(lhs)) { |
| | | replaced++; |
| | | changed = true; |
| | | AST_Node.info("Cascading {node} [{file}:{line},{col}]", { |
| | | node: node, |
| | | file: node.start.file, |
| | | line: node.start.line, |
| | | col: node.start.col, |
| | | }); |
| | | can_replace = false; |
| | | lvalues = get_lvalues(lhs); |
| | | node.right.transform(scanner); |
| | | clear_write_only(candidate); |
| | | var folded; |
| | | if (abort) { |
| | | folded = candidate; |
| | | } else { |
| | | abort = true; |
| | | folded = make_node(AST_Binary, candidate, { |
| | | operator: compound, |
| | | left: lhs.fixed && lhs.definition().fixed ? lhs.fixed.to_binary() : lhs, |
| | | right: rvalue, |
| | | }); |
| | | } |
| | | return make_node(AST_Assign, node, { |
| | | operator: "=", |
| | | left: node.left, |
| | | right: make_node(AST_Binary, node, { |
| | | operator: node.operator.slice(0, -1), |
| | | left: folded, |
| | | right: node.right, |
| | | }), |
| | | }); |
| | | } |
| | | // Stop immediately if these node types are encountered |
| | | if (should_stop(node, parent)) { |
| | | abort = true; |
| | | return node; |
| | | } |
| | | // Skip transient nodes caused by single-use variable replacement |
| | | if (node.single_use && parent instanceof AST_VarDef && parent.value === node) return node; |
| | |
| | | if (is_lhs(node, parent)) { |
| | | if (value_def && !hit_rhs) assign_used = true; |
| | | return node; |
| | | } else if (value_def) { |
| | | } |
| | | if (!hit_rhs && verify_ref && node.fixed !== lhs.fixed) { |
| | | abort = true; |
| | | return node; |
| | | } |
| | | if (value_def) { |
| | | if (stop_if_hit && assign_pos == 0) assign_pos = remaining - replaced; |
| | | if (!hit_rhs) replaced++; |
| | | return node; |
| | | } else { |
| | | replaced++; |
| | | } |
| | | CHANGED = abort = true; |
| | | replaced++; |
| | | changed = abort = true; |
| | | AST_Node.info("Collapsing {node} [{file}:{line},{col}]", { |
| | | node: node, |
| | | file: node.start.file, |
| | | line: node.start.line, |
| | | col: node.start.col, |
| | | }); |
| | | if (candidate.TYPE == "Binary") return make_node(AST_Assign, candidate, { |
| | | operator: "=", |
| | | left: candidate.right.left, |
| | | right: make_node(AST_Conditional, candidate, { |
| | | condition: candidate.operator == "&&" ? candidate.left : candidate.left.negate(compressor), |
| | | consequent: candidate.right.right, |
| | | alternative: node, |
| | | }), |
| | | }); |
| | | if (candidate instanceof AST_UnaryPostfix) { |
| | | if (lhs instanceof AST_SymbolRef) lhs.definition().fixed = false; |
| | | return make_node(AST_UnaryPrefix, candidate, candidate); |
| | | if (candidate.TYPE == "Binary") { |
| | | update_symbols(candidate, node); |
| | | return make_node(AST_Assign, candidate, { |
| | | operator: "=", |
| | | left: candidate.right.left, |
| | | right: candidate.operator == "&&" ? make_node(AST_Conditional, candidate, { |
| | | condition: candidate.left, |
| | | consequent: candidate.right.right, |
| | | alternative: node, |
| | | }) : make_node(AST_Conditional, candidate, { |
| | | condition: candidate.left, |
| | | consequent: node, |
| | | alternative: candidate.right.right, |
| | | }), |
| | | }); |
| | | } |
| | | if (candidate instanceof AST_UnaryPostfix) return make_node(AST_UnaryPrefix, candidate, { |
| | | operator: candidate.operator, |
| | | expression: lhs.fixed && lhs.definition().fixed ? lhs.fixed.to_prefix() : lhs, |
| | | }); |
| | | if (candidate instanceof AST_UnaryPrefix) { |
| | | clear_write_only(candidate); |
| | | return candidate; |
| | | } |
| | | update_symbols(rvalue, node); |
| | | if (candidate instanceof AST_VarDef) { |
| | | var def = candidate.name.definition(); |
| | | if (def.references.length - def.replaced == 1 && !compressor.exposed(def)) { |
| | | def.replaced++; |
| | | return maintain_this_binding(compressor, parent, node, candidate.value); |
| | | return maintain_this_binding(compressor, parent, node, rvalue); |
| | | } |
| | | return make_node(AST_Assign, candidate, { |
| | | operator: "=", |
| | | left: make_node(AST_SymbolRef, candidate.name, candidate.name), |
| | | right: candidate.value, |
| | | left: node, |
| | | right: rvalue, |
| | | }); |
| | | } |
| | | var assign = candidate; |
| | | while (assign.write_only) { |
| | | assign.write_only = false; |
| | | if (!(assign instanceof AST_Assign)) break; |
| | | assign = assign.right; |
| | | } |
| | | return candidate; |
| | | clear_write_only(rvalue); |
| | | var assign = candidate.clone(); |
| | | assign.right = rvalue; |
| | | return assign; |
| | | } |
| | | // Stop signals related to AST_SymbolRef |
| | | if (should_stop_ref(node, parent)) { |
| | | abort = true; |
| | | return node; |
| | | } |
| | | // These node types have child nodes that execute sequentially, |
| | | // but are otherwise not safe to scan into or beyond them. |
| | |
| | | if (node instanceof AST_BlockScope |
| | | && !(node instanceof AST_Scope) |
| | | && !(node.variables && node.variables.all(function(def) { |
| | | return !lvalues.has(def.name); |
| | | return !enclosed.has(def.name) && !lvalues.has(def.name); |
| | | }))) { |
| | | var replace = can_replace; |
| | | can_replace = false; |
| | |
| | | var parent = multi_replacer.parent(); |
| | | if (parent instanceof AST_Sequence && parent.tail_node() !== node) { |
| | | value_def.replaced++; |
| | | return List.skip; |
| | | if (rvalue === rhs_value) return List.skip; |
| | | return make_sequence(rhs_value, rhs_value.expressions.slice(0, -1)); |
| | | } |
| | | return rvalue; |
| | | case 1: |
| | |
| | | if (node instanceof AST_SymbolRef && node.definition() === def) { |
| | | if (is_lhs(node, multi_replacer.parent())) return node; |
| | | if (!--replaced) abort = true; |
| | | AST_Node.info("Replacing {node} [{file}:{line},{col}]", { |
| | | node: node, |
| | | file: node.start.file, |
| | | line: node.start.line, |
| | | col: node.start.col, |
| | | }); |
| | | var ref = rvalue.clone(); |
| | | ref.scope = node.scope; |
| | | ref.reference(); |
| | |
| | | } |
| | | // Skip (non-executed) functions and (leading) default case in switch statements |
| | | if (node instanceof AST_Default || node instanceof AST_Scope) return node; |
| | | }, patch_sequence); |
| | | }, function(node) { |
| | | return patch_sequence(node, multi_replacer); |
| | | }); |
| | | while (--stat_index >= 0) { |
| | | // Treat parameters as collapsible in IIFE, i.e. |
| | | // function(a, b){ ... }(x()); |
| | |
| | | var candidate = hit_stack[hit_stack.length - 1]; |
| | | var assign_pos = -1; |
| | | var assign_used = false; |
| | | var verify_ref = false; |
| | | var remaining; |
| | | var value_def = null; |
| | | var stop_after = null; |
| | | var stop_if_hit = null; |
| | | var lhs = get_lhs(candidate); |
| | | var side_effects = lhs && lhs.has_side_effects(compressor); |
| | | var scan_lhs = lhs && !side_effects && !is_lhs_read_only(lhs, compressor); |
| | | var scan_lhs = lhs && (!side_effects || lhs instanceof AST_SymbolRef) |
| | | && !is_lhs_read_only(lhs, compressor); |
| | | var scan_rhs = foldable(candidate); |
| | | if (!scan_lhs && !scan_rhs) continue; |
| | | var compound = candidate instanceof AST_Assign && candidate.operator.slice(0, -1); |
| | | var funarg = candidate.name instanceof AST_SymbolFunarg; |
| | | var may_throw = return_false; |
| | | if (candidate.may_throw(compressor)) { |
| | |
| | | var read_toplevel = false; |
| | | var modify_toplevel = false; |
| | | // Locate symbols which may execute code outside of scanning range |
| | | var enclosed = new Dictionary(); |
| | | var well_defined = true; |
| | | var lvalues = get_lvalues(candidate); |
| | | var lhs_local = is_lhs_local(lhs); |
| | | var rvalue = get_rvalue(candidate); |
| | | if (!side_effects) side_effects = value_has_side_effects(); |
| | | var rhs_value = get_rvalue(candidate); |
| | | var rvalue = rhs_value; |
| | | if (!side_effects) { |
| | | if (!compound && rvalue instanceof AST_Sequence) rvalue = rvalue.tail_node(); |
| | | side_effects = value_has_side_effects(); |
| | | } |
| | | var check_destructured = in_try || !lhs_local ? function(node) { |
| | | return node instanceof AST_Destructured; |
| | | } : return_false; |
| | |
| | | && !compressor.exposed(def); |
| | | value_def.last_ref = false; |
| | | value_def.single_use = false; |
| | | CHANGED = true; |
| | | changed = true; |
| | | } |
| | | if (replaced && !remove_candidate(candidate)) statements.splice(stat_index, 1); |
| | | if (replaced) remove_candidate(candidate); |
| | | } |
| | | } |
| | | return changed; |
| | | |
| | | function signal_abort(node) { |
| | | if (abort) return node; |
| | |
| | | if (node instanceof AST_DestructuredKeyVal) return node.key instanceof AST_Node; |
| | | if (node instanceof AST_DWLoop) return true; |
| | | if (node instanceof AST_LoopControl) return true; |
| | | if (node instanceof AST_SymbolRef) { |
| | | if (node.is_declared(compressor)) { |
| | | if (node.fixed_value()) return false; |
| | | if (can_drop_symbol(node)) { |
| | | return !(parent instanceof AST_PropAccess && parent.expression === node) |
| | | && is_arguments(node.definition()); |
| | | } |
| | | } else if (is_direct_assignment(node, parent)) { |
| | | return false; |
| | | } |
| | | if (!replace_all) return true; |
| | | scan_rhs = false; |
| | | return false; |
| | | } |
| | | if (node instanceof AST_Try) return true; |
| | | if (node instanceof AST_With) return true; |
| | | return false; |
| | | } |
| | | |
| | | function should_stop_ref(node, parent) { |
| | | if (!(node instanceof AST_SymbolRef)) return false; |
| | | if (node.is_declared(compressor)) { |
| | | if (node.fixed_value()) return false; |
| | | if (can_drop_symbol(node)) { |
| | | return !(parent instanceof AST_PropAccess && parent.expression === node) |
| | | && is_arguments(node.definition()); |
| | | } |
| | | } else if (is_direct_assignment(node, parent)) { |
| | | return false; |
| | | } |
| | | if (!replace_all) return true; |
| | | scan_rhs = false; |
| | | return false; |
| | | } |
| | | |
| | |
| | | stop_if_hit = if_hit; |
| | | stop_after = after; |
| | | can_replace = replace; |
| | | delete fn.collapse_scanning; |
| | | fn.collapse_scanning = false; |
| | | if (!abort) return false; |
| | | abort = false; |
| | | return true; |
| | |
| | | }); |
| | | args = iife.args.slice(); |
| | | var len = args.length; |
| | | var names = Object.create(null); |
| | | var names = new Dictionary(); |
| | | for (var i = fn.argnames.length; --i >= 0;) { |
| | | var sym = fn.argnames[i]; |
| | | var arg = args[i]; |
| | |
| | | candidates.length = 0; |
| | | break; |
| | | } |
| | | if (sym.name in names) continue; |
| | | names[sym.name] = true; |
| | | if (value) arg = !arg || is_undefined(arg) ? value : null; |
| | | if (names.has(sym.name)) continue; |
| | | names.set(sym.name, true); |
| | | if (value) arg = is_undefined(arg) ? value : null; |
| | | if (!arg && !value) { |
| | | arg = make_node(AST_Undefined, sym).transform(compressor); |
| | | } else if (arg instanceof AST_Lambda && arg.pinned()) { |
| | |
| | | extract_candidates(lhs); |
| | | extract_candidates(expr.right); |
| | | if (lhs instanceof AST_SymbolRef && expr.operator == "=") { |
| | | assignments[lhs.name] = (assignments[lhs.name] || 0) + 1; |
| | | assignments.set(lhs.name, (assignments.get(lhs.name) || 0) + 1); |
| | | } |
| | | } else if (expr instanceof AST_Await) { |
| | | extract_candidates(expr.expression, unused); |
| | |
| | | candidates.push(hit_stack.slice()); |
| | | } |
| | | } else { |
| | | declare_only[expr.name.name] = (declare_only[expr.name.name] || 0) + 1; |
| | | declare_only.set(expr.name.name, (declare_only.get(expr.name.name) || 0) + 1); |
| | | } |
| | | } |
| | | if (expr.value) extract_candidates(expr.value); |
| | |
| | | return; |
| | | } |
| | | if (remaining < 1) return; |
| | | rhs = rhs.tail_node(); |
| | | var value = rhs instanceof AST_Assign && rhs.operator == "=" ? rhs.left : rhs; |
| | | if (!(value instanceof AST_SymbolRef)) return; |
| | | var def = value.definition(); |
| | |
| | | } |
| | | |
| | | function remaining_refs(def) { |
| | | return def.references.length - def.replaced - (assignments[def.name] || 0); |
| | | return def.references.length - def.replaced - (assignments.get(def.name) || 0); |
| | | } |
| | | |
| | | function get_lhs(expr) { |
| | | if (expr instanceof AST_Assign) { |
| | | var lhs = expr.left; |
| | | if (expr.operator != "=") return lhs; |
| | | if (!(lhs instanceof AST_SymbolRef)) return lhs; |
| | | var def = lhs.definition(); |
| | | if (scope.uses_arguments && is_funarg(def)) return lhs; |
| | |
| | | if (matches < remaining) { |
| | | remaining = matches; |
| | | assign_pos = 0; |
| | | verify_ref = true; |
| | | } |
| | | } |
| | | mangleable_var(expr.right); |
| | | if (expr.operator == "=") mangleable_var(expr.right); |
| | | return lhs; |
| | | } |
| | | if (expr instanceof AST_Binary) return expr.right.left; |
| | |
| | | if (def.const_redefs) return; |
| | | if (!member(lhs, def.orig)) return; |
| | | if (scope.uses_arguments && is_funarg(def)) return; |
| | | var declared = def.orig.length - def.eliminated - (declare_only[def.name] || 0); |
| | | var declared = def.orig.length - def.eliminated - (declare_only.get(def.name) || 0); |
| | | remaining = remaining_refs(def); |
| | | if (def.fixed) remaining = Math.min(remaining, def.references.filter(function(ref) { |
| | | if (!ref.fixed) return true; |
| | |
| | | }; |
| | | } |
| | | |
| | | function clear_write_only(assign) { |
| | | while (assign.write_only) { |
| | | assign.write_only = false; |
| | | if (!(assign instanceof AST_Assign)) break; |
| | | assign = assign.right; |
| | | } |
| | | } |
| | | |
| | | function update_symbols(value, node) { |
| | | var scope = node.scope || find_scope(scanner) || block_scope; |
| | | value.walk(new TreeWalker(function(node) { |
| | | if (node instanceof AST_BlockScope) return true; |
| | | if (node instanceof AST_Symbol) node.scope = scope; |
| | | })); |
| | | } |
| | | |
| | | function may_be_global(node) { |
| | | if (node instanceof AST_SymbolRef) { |
| | | node = node.fixed_value(); |
| | |
| | | if (!value) { |
| | | value = node; |
| | | var def = node.definition(); |
| | | var escaped = node.fixed && node.fixed.escaped || def.escaped; |
| | | if (!def.undeclared |
| | | && (def.assignments || !def.escaped || def.escaped.cross_scope) |
| | | && (def.assignments || !escaped || escaped.cross_scope) |
| | | && (has_escaped(def, node.scope, node, tw.parent()) || !same_scope(def))) { |
| | | well_defined = false; |
| | | } |
| | |
| | | } else if (node instanceof AST_ObjectIdentity) { |
| | | value = node; |
| | | } |
| | | if (value) lvalues.add(node.name, is_modified(compressor, tw, node, value, 0)); |
| | | if (find_arguments && node instanceof AST_Sub) { |
| | | if (value) { |
| | | lvalues.add(node.name, is_modified(compressor, tw, node, value, 0)); |
| | | } else if (node instanceof AST_Lambda) { |
| | | for (var level = 0, parent, child = node; parent = tw.parent(level++); child = parent) { |
| | | if (parent instanceof AST_Assign) { |
| | | if (parent.left === child) break; |
| | | if (parent.operator == "=") continue; |
| | | if (lazy_op[parent.operator.slice(0, -1)]) continue; |
| | | break; |
| | | } |
| | | if (parent instanceof AST_Binary) { |
| | | if (lazy_op[parent.operator]) continue; |
| | | break; |
| | | } |
| | | if (parent instanceof AST_Call) return; |
| | | if (parent instanceof AST_Scope) return; |
| | | if (parent instanceof AST_Sequence) { |
| | | if (parent.tail_node() === child) continue; |
| | | break; |
| | | } |
| | | if (parent instanceof AST_Template) { |
| | | if (parent.tag) return; |
| | | break; |
| | | } |
| | | } |
| | | node.enclosed.forEach(function(def) { |
| | | if (def.scope !== node) enclosed.set(def.name, true); |
| | | }); |
| | | return true; |
| | | } else if (find_arguments && node instanceof AST_Sub) { |
| | | scope.each_argname(function(argname) { |
| | | if (!compressor.option("reduce_vars") || argname.definition().assignments) { |
| | | if (!argname.definition().fixed) well_defined = false; |
| | |
| | | } |
| | | |
| | | function remove_candidate(expr) { |
| | | var value = rvalue === rhs_value ? null : make_sequence(rhs_value, rhs_value.expressions.slice(0, -1)); |
| | | var index = expr.name_index; |
| | | if (index >= 0) { |
| | | var argname = scope.argnames[index]; |
| | | var args, argname = scope.argnames[index]; |
| | | if (argname instanceof AST_DefaultValue) { |
| | | argname.value = make_node(AST_Number, argname, { |
| | | value: 0 |
| | | }); |
| | | argname.name.definition().fixed = false; |
| | | } else { |
| | | var args = compressor.parent().args; |
| | | if (args[index]) { |
| | | args[index] = make_node(AST_Number, args[index], { |
| | | value: 0 |
| | | }); |
| | | argname.definition().fixed = false; |
| | | } |
| | | scope.argnames[index] = argname = argname.clone(); |
| | | argname.value = value || make_node(AST_Number, argname, { value: 0 }); |
| | | } else if ((args = compressor.parent().args)[index]) { |
| | | scope.argnames[index] = argname.clone(); |
| | | args[index] = value || make_node(AST_Number, args[index], { value: 0 }); |
| | | } |
| | | return true; |
| | | return; |
| | | } |
| | | var end = hit_stack.length - 1; |
| | | if (hit_stack[end - 1].body === hit_stack[end]) end--; |
| | | var last = hit_stack[end]; |
| | | if (last instanceof AST_VarDef || hit_stack[end - 1].body === last) end--; |
| | | var tt = new TreeTransformer(function(node, descend, in_list) { |
| | | if (hit) return node; |
| | | if (node !== hit_stack[hit_index]) return node; |
| | | hit_index++; |
| | | if (hit_index <= end) return handle_custom_scan_order(node, tt); |
| | | hit = true; |
| | | if (node instanceof AST_VarDef) { |
| | | declare_only[node.name.name] = (declare_only[node.name.name] || 0) + 1; |
| | | if (node instanceof AST_Definitions) { |
| | | declare_only.set(last.name.name, (declare_only.get(last.name.name) || 0) + 1); |
| | | if (value_def) value_def.replaced++; |
| | | node = node.clone(); |
| | | node.value = null; |
| | | return node; |
| | | var defns = node.definitions; |
| | | var index = defns.indexOf(last); |
| | | var defn = last.clone(); |
| | | defn.value = null; |
| | | if (!value) { |
| | | node.definitions[index] = defn; |
| | | return node; |
| | | } |
| | | var body = [ make_node(AST_SimpleStatement, value, { body: value }) ]; |
| | | if (index > 0) { |
| | | var head = node.clone(); |
| | | head.definitions = defns.slice(0, index); |
| | | body.unshift(head); |
| | | node = node.clone(); |
| | | node.definitions = defns.slice(index); |
| | | } |
| | | body.push(node); |
| | | node.definitions[0] = defn; |
| | | return in_list ? List.splice(body) : make_node(AST_BlockStatement, node, { body: body }); |
| | | } |
| | | return in_list ? List.skip : null; |
| | | }, patch_sequence); |
| | | if (!value) return in_list ? List.skip : null; |
| | | return is_statement(node) ? make_node(AST_SimpleStatement, value, { body: value }) : value; |
| | | }, function(node, in_list) { |
| | | if (node instanceof AST_For) return patch_for_init(node, in_list); |
| | | return patch_sequence(node, tt); |
| | | }); |
| | | abort = false; |
| | | hit = false; |
| | | hit_index = 0; |
| | | return statements[stat_index].transform(tt); |
| | | if (!(statements[stat_index] = statements[stat_index].transform(tt))) statements.splice(stat_index, 1); |
| | | } |
| | | |
| | | function patch_sequence(node) { |
| | | function patch_sequence(node, tt) { |
| | | if (node instanceof AST_Sequence) switch (node.expressions.length) { |
| | | case 0: return null; |
| | | case 1: return maintain_this_binding(compressor, this.parent(), node, node.expressions[0]); |
| | | case 1: return maintain_this_binding(compressor, tt.parent(), node, node.expressions[0]); |
| | | } |
| | | } |
| | | |
| | | function is_lhs_local(lhs) { |
| | | var sym = root_expr(lhs); |
| | | return sym instanceof AST_SymbolRef |
| | | && sym.definition().scope.resolve() === scope |
| | | && !(in_loop |
| | | && (lvalues.has(sym.name) && lvalues.get(sym.name)[0] !== lhs |
| | | || candidate instanceof AST_Unary |
| | | || candidate instanceof AST_Assign && candidate.operator != "=")); |
| | | if (!(sym instanceof AST_SymbolRef)) return false; |
| | | if (sym.definition().scope.resolve() !== scope) return false; |
| | | if (!in_loop) return true; |
| | | if (compound) return false; |
| | | if (candidate instanceof AST_Unary) return false; |
| | | var lvalue = lvalues.get(sym.name); |
| | | return !lvalue || lvalue[0] === lhs; |
| | | } |
| | | |
| | | function value_has_side_effects() { |
| | |
| | | return false; |
| | | } |
| | | var def = lhs.definition(); |
| | | return def.references.length - def.replaced == referenced; |
| | | if (def.references.length - def.replaced == referenced) return true; |
| | | if (!def.fixed) return false; |
| | | if (!lhs.fixed) return false; |
| | | if (def.references.filter(function(ref) { |
| | | return ref.fixed === lhs.fixed; |
| | | }).length != referenced) return false; |
| | | verify_ref = true; |
| | | return true; |
| | | } |
| | | |
| | | function symbol_in_lvalues(sym, parent) { |
| | |
| | | if (def.scope.resolve() !== scope) return true; |
| | | if (modify_toplevel && compressor.exposed(def)) return true; |
| | | return !all(def.references, function(ref) { |
| | | return ref.scope.resolve() === scope; |
| | | return ref.scope.resolve(true) === scope; |
| | | }); |
| | | } |
| | | |
| | |
| | | } |
| | | |
| | | function eliminate_spurious_blocks(statements) { |
| | | var seen_dirs = []; |
| | | var changed = false, seen_dirs = []; |
| | | for (var i = 0; i < statements.length;) { |
| | | var stat = statements[i]; |
| | | if (stat instanceof AST_BlockStatement) { |
| | | if (all(stat.body, safe_to_trim)) { |
| | | CHANGED = true; |
| | | changed = true; |
| | | eliminate_spurious_blocks(stat.body); |
| | | [].splice.apply(statements, [i, 1].concat(stat.body)); |
| | | i += stat.body.length; |
| | |
| | | } |
| | | if (stat instanceof AST_Directive) { |
| | | if (member(stat.value, seen_dirs)) { |
| | | CHANGED = true; |
| | | changed = true; |
| | | statements.splice(i, 1); |
| | | continue; |
| | | } |
| | | seen_dirs.push(stat.value); |
| | | } |
| | | if (stat instanceof AST_EmptyStatement) { |
| | | CHANGED = true; |
| | | changed = true; |
| | | statements.splice(i, 1); |
| | | continue; |
| | | } |
| | | i++; |
| | | } |
| | | return changed; |
| | | } |
| | | |
| | | function handle_if_return(statements, compressor) { |
| | | var self = compressor.self(); |
| | | var changed = false; |
| | | var parent = compressor.parent(); |
| | | var in_lambda = last_of(function(node) { |
| | | return node instanceof AST_Lambda; |
| | | }); |
| | | var in_iife = in_lambda && parent && parent.TYPE == "Call"; |
| | | var self = compressor.self(); |
| | | var in_iife = in_lambda && parent && parent.TYPE == "Call" && parent.expression === self; |
| | | var chain_if_returns = in_lambda && compressor.option("conditionals") && compressor.option("sequences"); |
| | | var multiple_if_returns = has_multiple_if_returns(statements); |
| | | for (var i = statements.length; --i >= 0;) { |
| | | var stat = statements[i]; |
| | |
| | | |
| | | if (in_lambda && !next && stat instanceof AST_Return) { |
| | | if (!stat.value) { |
| | | CHANGED = true; |
| | | changed = true; |
| | | statements.splice(i, 1); |
| | | continue; |
| | | } |
| | | var tail = stat.value.tail_node(); |
| | | if (tail instanceof AST_UnaryPrefix && tail.operator == "void") { |
| | | CHANGED = true; |
| | | changed = true; |
| | | var body; |
| | | if (tail === stat.value) { |
| | | body = tail.expression; |
| | |
| | | body = stat.value.clone(); |
| | | body.expressions[body.length - 1] = tail.expression; |
| | | } |
| | | statements[i] = make_node(AST_SimpleStatement, stat, { |
| | | body: body, |
| | | }); |
| | | statements[i] = make_node(AST_SimpleStatement, stat, { body: body }); |
| | | continue; |
| | | } |
| | | } |
| | |
| | | var ab = aborts(stat.body); |
| | | if (can_merge_flow(ab)) { |
| | | if (ab.label) remove(ab.label.thedef.references, ab); |
| | | CHANGED = true; |
| | | changed = true; |
| | | stat = stat.clone(); |
| | | stat.condition = stat.condition.negate(compressor); |
| | | var body = as_statement_array_with_return(stat.body, ab); |
| | | stat.body = make_node(AST_BlockStatement, stat, { |
| | | body: as_statement_array(stat.alternative).concat(extract_functions()) |
| | | body: as_statement_array_with_return(stat.body, ab), |
| | | }); |
| | | stat.alternative = make_node(AST_BlockStatement, stat, { |
| | | body: body |
| | | body: as_statement_array(stat.alternative).concat(extract_functions()), |
| | | }); |
| | | statements[i] = stat; |
| | | statements[i] = stat.transform(compressor); |
| | | continue; |
| | | } |
| | | |
| | | if (ab && !stat.alternative && stat.body instanceof AST_BlockStatement && next instanceof AST_Jump) { |
| | | var negated = stat.condition.negate(compressor); |
| | | if (negated.print_to_string().length <= stat.condition.print_to_string().length) { |
| | | CHANGED = true; |
| | | if (ab && !stat.alternative && next instanceof AST_Jump) { |
| | | var cond = stat.condition; |
| | | cond = best_of_expression(cond, cond.negate(compressor), stat.body instanceof AST_BlockStatement); |
| | | if (cond !== stat.condition) { |
| | | changed = true; |
| | | stat = stat.clone(); |
| | | stat.condition = negated; |
| | | stat.condition = cond; |
| | | statements[j] = stat.body; |
| | | stat.body = next; |
| | | statements[i] = stat; |
| | |
| | | var alt = aborts(stat.alternative); |
| | | if (can_merge_flow(alt)) { |
| | | if (alt.label) remove(alt.label.thedef.references, alt); |
| | | CHANGED = true; |
| | | changed = true; |
| | | stat = stat.clone(); |
| | | stat.body = make_node(AST_BlockStatement, stat.body, { |
| | | body: as_statement_array(stat.body).concat(extract_functions()) |
| | | body: as_statement_array(stat.body).concat(extract_functions()), |
| | | }); |
| | | var body = as_statement_array_with_return(stat.alternative, alt); |
| | | stat.alternative = make_node(AST_BlockStatement, stat.alternative, { |
| | | body: body |
| | | body: as_statement_array_with_return(stat.alternative, alt), |
| | | }); |
| | | statements[i] = stat; |
| | | statements[i] = stat.transform(compressor); |
| | |
| | | |
| | | if (compressor.option("typeofs")) { |
| | | if (ab && !alt) { |
| | | mark_locally_defined(stat.condition, null, make_node(AST_BlockStatement, self, { |
| | | body: statements.slice(i + 1) |
| | | })); |
| | | var stats = make_node(AST_BlockStatement, self, { body: statements.slice(i + 1) }); |
| | | mark_locally_defined(stat.condition, null, stats); |
| | | } |
| | | if (!ab && alt) { |
| | | mark_locally_defined(stat.condition, make_node(AST_BlockStatement, self, { |
| | | body: statements.slice(i + 1) |
| | | })); |
| | | var stats = make_node(AST_BlockStatement, self, { body: statements.slice(i + 1) }); |
| | | mark_locally_defined(stat.condition, stats); |
| | | } |
| | | } |
| | | } |
| | |
| | | var value = stat.body.value; |
| | | var in_bool = stat.body.in_bool || next instanceof AST_Return && next.in_bool; |
| | | //--- |
| | | // pretty silly case, but: |
| | | // if (foo()) return; return; ---> foo(); return; |
| | | if (!value && !stat.alternative |
| | | && (in_lambda && !next || next instanceof AST_Return && !next.value)) { |
| | | CHANGED = true; |
| | | statements[i] = make_node(AST_SimpleStatement, stat.condition, { |
| | | body: stat.condition |
| | | }); |
| | | continue; |
| | | } |
| | | //--- |
| | | // if (foo()) return x; return y; ---> return foo() ? x : y; |
| | | if (!stat.alternative && next instanceof AST_Return) { |
| | | CHANGED = true; |
| | | changed = true; |
| | | stat = stat.clone(); |
| | | stat.alternative = next; |
| | | statements.splice(i, 1, stat.transform(compressor)); |
| | |
| | | //--- |
| | | // if (foo()) return x; [ return ; ] ---> return foo() ? x : undefined; |
| | | if (!stat.alternative && !next && in_lambda && (in_bool || value && multiple_if_returns)) { |
| | | CHANGED = true; |
| | | changed = true; |
| | | stat = stat.clone(); |
| | | stat.alternative = make_node(AST_Return, stat, { |
| | | value: null |
| | | }); |
| | | stat.alternative = make_node(AST_Return, stat, { value: null }); |
| | | statements.splice(i, 1, stat.transform(compressor)); |
| | | continue; |
| | | } |
| | |
| | | // if sequences is not enabled, this can lead to an endless loop (issue #866). |
| | | // however, with sequences on this helps producing slightly better output for |
| | | // the example code. |
| | | var prev = statements[prev_index(i)]; |
| | | if (compressor.option("sequences") && in_lambda && !stat.alternative |
| | | && (!prev && in_iife || prev instanceof AST_If && prev.body instanceof AST_Return) |
| | | var prev, prev_stat; |
| | | if (chain_if_returns && !stat.alternative |
| | | && (!(prev_stat = statements[prev = prev_index(i)]) && in_iife |
| | | || prev_stat instanceof AST_If && prev_stat.body instanceof AST_Return) |
| | | && next_index(j) == statements.length && next instanceof AST_SimpleStatement) { |
| | | CHANGED = true; |
| | | changed = true; |
| | | var exprs = []; |
| | | var args = prev_stat ? trim_defns(prev, exprs) : [ i, 1 ]; |
| | | stat = stat.clone(); |
| | | exprs.push(stat.condition); |
| | | stat.condition = make_sequence(stat, exprs); |
| | | stat.alternative = make_node(AST_BlockStatement, next, { |
| | | body: [ |
| | | next, |
| | | make_node(AST_Return, next, { |
| | | value: null |
| | | }) |
| | | ] |
| | | make_node(AST_Return, next, { value: null }), |
| | | ], |
| | | }); |
| | | statements.splice(i, 1, stat.transform(compressor)); |
| | | args.push(stat.transform(compressor)); |
| | | statements.splice(j, 1); |
| | | [].splice.apply(statements, args); |
| | | i = prev + 1; |
| | | continue; |
| | | } |
| | | } |
| | | } |
| | | return changed; |
| | | |
| | | function has_multiple_if_returns(statements) { |
| | | var n = 0; |
| | |
| | | return false; |
| | | } |
| | | |
| | | function is_return_void(value) { |
| | | return !value || value instanceof AST_UnaryPrefix && value.operator == "void"; |
| | | } |
| | | |
| | | function last_of(predicate) { |
| | | var block = self, stat, level = 0; |
| | | do { |
| | | do { |
| | | if (predicate(block)) return true; |
| | | block = compressor.parent(level++); |
| | | } while (block instanceof AST_If && (stat = block)); |
| | | } while ((block instanceof AST_BlockStatement || block instanceof AST_Scope) |
| | | && is_last_statement(block.body, stat)); |
| | | } |
| | | |
| | | function match_target(target) { |
| | | return last_of(function(node) { |
| | | return last_of(compressor, function(node) { |
| | | return node === target; |
| | | }); |
| | | } |
| | | |
| | | function can_drop_abort(ab) { |
| | | if (ab instanceof AST_Return) return in_lambda && is_return_void(ab.value); |
| | | if (ab instanceof AST_Return) return in_lambda && is_undefined(ab.value); |
| | | if (!(ab instanceof AST_LoopControl)) return false; |
| | | var lct = compressor.loopcontrol_target(ab); |
| | | if (ab instanceof AST_Continue) return match_target(loop_body(lct)); |
| | |
| | | block = last.body; |
| | | } |
| | | block.pop(); |
| | | if (ab.value) block.push(make_node(AST_SimpleStatement, ab.value, { |
| | | body: ab.value.expression |
| | | })); |
| | | if (ab.value) block.push(make_node(AST_SimpleStatement, ab.value, { body: ab.value })); |
| | | return body; |
| | | } |
| | | |
| | |
| | | |
| | | function prev_index(i) { |
| | | for (var j = i; --j >= 0;) { |
| | | if (!is_declaration(statements[j])) break; |
| | | if (!(statements[j] instanceof AST_Var)) break; |
| | | } |
| | | return j; |
| | | } |
| | | |
| | | function trim_defns(j, exprs) { |
| | | var args = [ j + 1, i - j ]; |
| | | var var_defs = []; |
| | | while (++j < i) { |
| | | var stat = statements[j]; |
| | | stat.remove_initializers(compressor, var_defs); |
| | | stat.definitions.forEach(function(var_def) { |
| | | if (!var_def.value) return; |
| | | exprs.push(make_node(AST_Assign, var_def, { |
| | | operator: "=", |
| | | left: var_def.name.convert_symbol(AST_SymbolRef, process_to_assign), |
| | | right: var_def.value, |
| | | })); |
| | | }); |
| | | } |
| | | if (var_defs.length > 0) args.push(make_node(AST_Var, stat, { definitions: var_defs })); |
| | | return args; |
| | | } |
| | | } |
| | | |
| | |
| | | var stat = statements[i]; |
| | | if (stat instanceof AST_LoopControl) { |
| | | var lct = compressor.loopcontrol_target(stat); |
| | | if (stat instanceof AST_Break |
| | | && !(lct instanceof AST_IterationStatement) |
| | | && loop_body(lct) === self |
| | | || stat instanceof AST_Continue |
| | | && loop_body(lct) === self) { |
| | | if (stat.label) remove(stat.label.thedef.references, stat); |
| | | } else { |
| | | if (loop_body(lct) !== self |
| | | || stat instanceof AST_Break && lct instanceof AST_IterationStatement) { |
| | | statements[n++] = stat; |
| | | } else if (stat.label) { |
| | | remove(stat.label.thedef.references, stat); |
| | | } |
| | | } else { |
| | | statements[n++] = stat; |
| | |
| | | } |
| | | } |
| | | statements.length = n; |
| | | CHANGED = n != len; |
| | | if (has_quit) has_quit.forEach(function(stat) { |
| | | extract_declarations_from_unreachable_code(compressor, stat, statements); |
| | | }); |
| | | return statements.length != len; |
| | | } |
| | | |
| | | function trim_awaits(statements, compressor) { |
| | | if (!in_lambda || in_try && in_try.bfinally) return; |
| | | var changed = false; |
| | | for (var index = statements.length; --index >= 0;) { |
| | | var stat = statements[index]; |
| | | if (!(stat instanceof AST_SimpleStatement)) break; |
| | | var node = stat.body; |
| | | if (!(node instanceof AST_Await)) break; |
| | | var exp = node.expression; |
| | | if (!is_primitive(compressor, exp)) break; |
| | | changed = true; |
| | | exp = exp.drop_side_effect_free(compressor, true); |
| | | if (exp) { |
| | | stat.body = exp; |
| | | break; |
| | | } |
| | | } |
| | | statements.length = index + 1; |
| | | return changed; |
| | | } |
| | | |
| | | function inline_iife(statements, compressor) { |
| | | var changed = false; |
| | | var index = statements.length - 1; |
| | | if (in_lambda && index >= 0) { |
| | | var inlined = statements[index].try_inline(compressor, block_scope); |
| | | if (inlined) { |
| | | statements[index--] = inlined; |
| | | changed = true; |
| | | } |
| | | } |
| | | var loop = in_loop && in_try && in_try.bfinally ? "try" : in_loop; |
| | | for (; index >= 0; index--) { |
| | | var inlined = statements[index].try_inline(compressor, block_scope, true, loop); |
| | | if (!inlined) continue; |
| | | statements[index] = inlined; |
| | | changed = true; |
| | | } |
| | | return changed; |
| | | } |
| | | |
| | | function sequencesize(statements, compressor) { |
| | |
| | | var stat = statements[i]; |
| | | if (stat instanceof AST_SimpleStatement) { |
| | | if (seq.length >= compressor.sequences_limit) push_seq(); |
| | | var body = stat.body; |
| | | if (seq.length > 0) body = body.drop_side_effect_free(compressor); |
| | | if (body) merge_sequence(seq, body); |
| | | merge_sequence(seq, stat.body); |
| | | } else if (is_declaration(stat)) { |
| | | statements[n++] = stat; |
| | | } else { |
| | |
| | | } |
| | | push_seq(); |
| | | statements.length = n; |
| | | if (n != len) CHANGED = true; |
| | | return n != len; |
| | | } |
| | | |
| | | function to_simple_statement(block, decls) { |
| | |
| | | } |
| | | |
| | | function sequencesize_2(statements, compressor) { |
| | | function cons_seq(right) { |
| | | n--; |
| | | CHANGED = true; |
| | | var left = prev.body; |
| | | return make_sequence(left, [ left, right ]); |
| | | } |
| | | var n = 0, prev; |
| | | var changed = false, n = 0, prev; |
| | | for (var i = 0; i < statements.length; i++) { |
| | | var stat = statements[i]; |
| | | if (prev) { |
| | |
| | | else { |
| | | stat.init = prev.body; |
| | | n--; |
| | | CHANGED = true; |
| | | changed = true; |
| | | } |
| | | } |
| | | } |
| | |
| | | i += len; |
| | | n += len + 1; |
| | | prev = null; |
| | | CHANGED = true; |
| | | changed = true; |
| | | continue; |
| | | } |
| | | } |
| | |
| | | prev = stat instanceof AST_SimpleStatement ? stat : null; |
| | | } |
| | | statements.length = n; |
| | | return changed; |
| | | |
| | | function cons_seq(right) { |
| | | n--; |
| | | changed = true; |
| | | var left = prev.body; |
| | | return make_sequence(left, [ left, right ]); |
| | | } |
| | | } |
| | | |
| | | function extract_exprs(body) { |
| | |
| | | function join_assigns(defn, body, keep) { |
| | | var exprs = extract_exprs(body); |
| | | if (!exprs) return; |
| | | keep = keep || 0; |
| | | var trimmed = false; |
| | | for (var i = exprs.length - 1; --i >= 0;) { |
| | | for (var i = exprs.length - keep; --i >= 0;) { |
| | | var expr = exprs[i]; |
| | | if (!(expr instanceof AST_Assign)) continue; |
| | | if (expr.operator != "=") continue; |
| | | if (!(expr.left instanceof AST_SymbolRef)) continue; |
| | | var tail = exprs.slice(i + 1); |
| | | if (!can_trim(expr)) continue; |
| | | var tail; |
| | | if (expr.left instanceof AST_SymbolRef) { |
| | | tail = exprs.slice(i + 1); |
| | | } else if (expr.left instanceof AST_PropAccess && can_trim(expr.left.expression)) { |
| | | tail = exprs.slice(i + 1); |
| | | var flattened = expr.clone(); |
| | | expr = expr.left.expression; |
| | | flattened.left = flattened.left.clone(); |
| | | flattened.left.expression = expr.left.clone(); |
| | | tail.unshift(flattened); |
| | | } else { |
| | | continue; |
| | | } |
| | | if (tail.length == 0) continue; |
| | | if (!trim_assigns(expr.left, expr.right, tail)) continue; |
| | | trimmed = true; |
| | | exprs = exprs.slice(0, i + 1).concat(tail); |
| | | exprs = exprs.slice(0, i).concat(expr, tail); |
| | | } |
| | | if (defn instanceof AST_Definitions) { |
| | | keep = keep || 0; |
| | | for (var i = defn.definitions.length; --i >= 0;) { |
| | | var def = defn.definitions[i]; |
| | | if (!def.value) continue; |
| | |
| | | if (defn instanceof AST_Var && join_var_assign(defn.definitions, exprs, keep)) trimmed = true; |
| | | } |
| | | return trimmed && exprs; |
| | | |
| | | function can_trim(node) { |
| | | return node instanceof AST_Assign && node.operator == "="; |
| | | } |
| | | } |
| | | |
| | | function merge_assigns(prev, defn) { |
| | |
| | | } |
| | | |
| | | function trim_assigns(name, value, exprs) { |
| | | var names = new Dictionary(); |
| | | names.set(name.name, true); |
| | | while (value instanceof AST_Assign && value.operator == "=") { |
| | | if (value.left instanceof AST_SymbolRef) names.set(value.left.name, true); |
| | | value = value.right; |
| | | } |
| | | if (!(value instanceof AST_Object)) return; |
| | | var trimmed = false; |
| | | do { |
| | | var node = exprs[0]; |
| | | if (!(node instanceof AST_Assign)) break; |
| | | if (node.operator != "=") break; |
| | | if (!(node.left instanceof AST_PropAccess)) break; |
| | | if (!try_join(exprs[0])) break; |
| | | exprs.shift(); |
| | | trimmed = true; |
| | | } while (exprs.length); |
| | | return trimmed; |
| | | |
| | | function try_join(node) { |
| | | if (!(node instanceof AST_Assign)) return; |
| | | if (node.operator != "=") return; |
| | | if (!(node.left instanceof AST_PropAccess)) return; |
| | | var sym = node.left.expression; |
| | | if (!(sym instanceof AST_SymbolRef)) break; |
| | | if (name.name != sym.name) break; |
| | | if (!node.right.is_constant_expression(scope)) break; |
| | | if (!(sym instanceof AST_SymbolRef)) return; |
| | | if (!names.has(sym.name)) return; |
| | | if (!node.right.is_constant_expression(scope)) return; |
| | | var prop = node.left.property; |
| | | if (prop instanceof AST_Node) { |
| | | if (try_join(prop)) prop = node.left.property = prop.right.clone(); |
| | | prop = prop.evaluate(compressor); |
| | | } |
| | | if (prop instanceof AST_Node) break; |
| | | if (prop instanceof AST_Node) return; |
| | | prop = "" + prop; |
| | | var diff = prop == "__proto__" || compressor.has_directive("use strict") ? function(node) { |
| | | var key = node.key; |
| | |
| | | } |
| | | return key !== "__proto__"; |
| | | }; |
| | | if (!all(value.properties, diff)) break; |
| | | if (!all(value.properties, diff)) return; |
| | | value.properties.push(make_node(AST_ObjectKeyVal, node, { |
| | | key: prop, |
| | | value: node.right |
| | | value: node.right, |
| | | })); |
| | | exprs.shift(); |
| | | trimmed = true; |
| | | } while (exprs.length); |
| | | return trimmed; |
| | | return true; |
| | | } |
| | | } |
| | | |
| | | function join_consecutive_vars(statements) { |
| | | var defs; |
| | | var changed = false, defs; |
| | | for (var i = 0, j = -1; i < statements.length; i++) { |
| | | var stat = statements[i]; |
| | | var prev = statements[j]; |
| | | if (stat instanceof AST_Definitions) { |
| | | if (prev && prev.TYPE == stat.TYPE) { |
| | | prev.definitions = prev.definitions.concat(stat.definitions); |
| | | CHANGED = true; |
| | | changed = true; |
| | | } else if (defs && defs.TYPE == stat.TYPE && declarations_only(stat)) { |
| | | defs.definitions = defs.definitions.concat(stat.definitions); |
| | | CHANGED = true; |
| | | changed = true; |
| | | } else if (stat instanceof AST_Var) { |
| | | var exprs = merge_assigns(prev, stat); |
| | | if (exprs) { |
| | |
| | | prev.body = make_sequence(prev, exprs); |
| | | j++; |
| | | } |
| | | CHANGED = true; |
| | | changed = true; |
| | | } else { |
| | | j++; |
| | | } |
| | |
| | | } else if (stat instanceof AST_For) { |
| | | var exprs = join_assigns(prev, stat.init); |
| | | if (exprs) { |
| | | CHANGED = true; |
| | | changed = true; |
| | | stat.init = exprs.length ? make_sequence(stat.init, exprs) : null; |
| | | } else if (prev instanceof AST_Var && (!stat.init || stat.init.TYPE == prev.TYPE)) { |
| | | if (stat.init) { |
| | | prev.definitions = prev.definitions.concat(stat.init.definitions); |
| | | } |
| | | stat = stat.clone(); |
| | | defs = stat.init = prev; |
| | | statements[j] = merge_defns(stat); |
| | | CHANGED = true; |
| | | changed = true; |
| | | continue; |
| | | } else if (defs && stat.init && defs.TYPE == stat.init.TYPE && declarations_only(stat.init)) { |
| | | defs.definitions = defs.definitions.concat(stat.init.definitions); |
| | | stat.init = null; |
| | | CHANGED = true; |
| | | changed = true; |
| | | } else if (stat.init instanceof AST_Var) { |
| | | defs = stat.init; |
| | | exprs = merge_assigns(prev, stat.init); |
| | | if (exprs) { |
| | | CHANGED = true; |
| | | changed = true; |
| | | if (exprs.length == 0) { |
| | | statements[j] = merge_defns(stat); |
| | | continue; |
| | |
| | | name.definition().references.push(ref); |
| | | }); |
| | | defs.definitions = defns; |
| | | CHANGED = true; |
| | | changed = true; |
| | | } |
| | | stat.object = join_assigns_expr(stat.object); |
| | | } else if (stat instanceof AST_If) { |
| | | stat.condition = join_assigns_expr(stat.condition); |
| | | } else if (stat instanceof AST_SimpleStatement) { |
| | | var exprs = join_assigns(prev, stat.body); |
| | | var exprs = join_assigns(prev, stat.body), next; |
| | | if (exprs) { |
| | | CHANGED = true; |
| | | changed = true; |
| | | if (!exprs.length) continue; |
| | | stat.body = make_sequence(stat.body, exprs); |
| | | } else if (prev instanceof AST_Definitions |
| | | && (next = statements[i + 1]) |
| | | && prev.TYPE == next.TYPE |
| | | && (next = next.definitions[0]).value) { |
| | | changed = true; |
| | | next.value = make_sequence(stat, [ stat.body, next.value ]); |
| | | continue; |
| | | } |
| | | } else if (stat instanceof AST_Switch) { |
| | | stat.expression = join_assigns_expr(stat.expression); |
| | |
| | | statements[++j] = defs ? merge_defns(stat) : stat; |
| | | } |
| | | statements.length = j + 1; |
| | | return changed; |
| | | |
| | | function join_assigns_expr(value) { |
| | | var exprs = join_assigns(prev, value, 1); |
| | | if (!exprs) return value; |
| | | CHANGED = true; |
| | | changed = true; |
| | | var tail = value.tail_node(); |
| | | if (exprs[exprs.length - 1] !== tail) exprs.push(tail.left); |
| | | return make_sequence(value, exprs); |
| | |
| | | if (parent instanceof AST_ForEnumeration && parent.init === node) return node; |
| | | if (!declarations_only(node)) return node; |
| | | defs.definitions = defs.definitions.concat(node.definitions); |
| | | CHANGED = true; |
| | | changed = true; |
| | | if (parent instanceof AST_For && parent.init === node) return null; |
| | | return in_list ? List.skip : make_node(AST_EmptyStatement, node); |
| | | } |
| | |
| | | } |
| | | |
| | | function is_undefined(node, compressor) { |
| | | return node.is_undefined |
| | | return node == null |
| | | || node.is_undefined |
| | | || node instanceof AST_Undefined |
| | | || node instanceof AST_UnaryPrefix |
| | | && node.operator == "void" |
| | |
| | | "setUTCMonth", |
| | | "setUTCSeconds", |
| | | "setYear", |
| | | "toExponential", |
| | | "toFixed", |
| | | "toPrecision", |
| | | ]); |
| | | def(AST_Call, function(compressor) { |
| | | if (!compressor.option("unsafe")) return false; |
| | |
| | | def(AST_Sequence, function(compressor) { |
| | | return this.tail_node().is_number(compressor); |
| | | }); |
| | | def(AST_SymbolRef, function(compressor) { |
| | | def(AST_SymbolRef, function(compressor, keep_unary) { |
| | | var fixed = this.fixed_value(); |
| | | if (!fixed) return false; |
| | | if (keep_unary |
| | | && fixed instanceof AST_UnaryPrefix |
| | | && fixed.operator == "+" |
| | | && fixed.expression.equivalent_to(this)) { |
| | | return false; |
| | | } |
| | | this.is_number = return_false; |
| | | var result = fixed.is_number(compressor); |
| | | delete this.is_number; |
| | |
| | | "charAt", |
| | | "substr", |
| | | "substring", |
| | | "toExponential", |
| | | "toFixed", |
| | | "toLowerCase", |
| | | "toPrecision", |
| | | "toString", |
| | | "toUpperCase", |
| | | "trim", |
| | |
| | | }); |
| | | var scan_modified = new TreeWalker(function(node) { |
| | | if (node instanceof AST_Assign) modified(node.left); |
| | | if (node instanceof AST_ForEnumeration) modified(node.init); |
| | | if (node instanceof AST_Unary && UNARY_POSTFIX[node.operator]) modified(node.expression); |
| | | }); |
| | | function modified(node) { |
| | |
| | | } |
| | | return this; |
| | | }); |
| | | var nonsafe_props = makePredicate("__proto__ toString valueOf"); |
| | | def(AST_Object, function(compressor, ignore_side_effects, cached, depth) { |
| | | if (compressor.option("unsafe")) { |
| | | var val = {}; |
| | |
| | | key = key._eval(compressor, ignore_side_effects, cached, depth); |
| | | if (key === prop.key) return this; |
| | | } |
| | | if (nonsafe_props[key]) return this; |
| | | switch (key) { |
| | | case "__proto__": |
| | | case "toString": |
| | | case "valueOf": |
| | | return this; |
| | | } |
| | | val[key] = prop.value._eval(compressor, ignore_side_effects, cached, depth); |
| | | if (val[key] === prop.value) return this; |
| | | } |
| | |
| | | return result; |
| | | |
| | | function decimals(operand) { |
| | | var match = /(\.[0-9]*)?(e.+)?$/.exec(+operand); |
| | | var match = /(\.[0-9]*)?(e[^e]+)?$/.exec(+operand); |
| | | return (match[1] || ".").length - 1 - (match[2] || "").slice(1); |
| | | } |
| | | }); |
| | |
| | | if (!all(fn.argnames, function(sym, index) { |
| | | if (sym instanceof AST_DefaultValue) { |
| | | if (!args) return false; |
| | | if (args[index] !== undefined) return false; |
| | | var value = sym.value._eval(compressor, ignore_side_effects, cached, depth); |
| | | if (value === sym.value) return false; |
| | | args[index] = value; |
| | | if (args[index] === undefined) { |
| | | var value = sym.value._eval(compressor, ignore_side_effects, cached, depth); |
| | | if (value === sym.value) return false; |
| | | args[index] = value; |
| | | } |
| | | sym = sym.name; |
| | | } |
| | | return !(sym instanceof AST_Destructured); |
| | |
| | | } |
| | | if (node instanceof AST_Scope && node !== fn) return true; |
| | | })); |
| | | delete fn.evaluating; |
| | | fn.evaluating = false; |
| | | if (!found) return; |
| | | } |
| | | return this; |
| | |
| | | if (!args || all(fn.argnames, function(sym, i) { |
| | | return assign(sym, args[i]); |
| | | }) && !(fn.rest && !assign(fn.rest, args.slice(fn.argnames.length))) || ignore_side_effects) { |
| | | if (ignore_side_effects) fn.argnames.forEach(function(sym) { |
| | | if (sym instanceof AST_DefaultValue) sym.value.walk(scan_modified); |
| | | }); |
| | | fn.evaluating = true; |
| | | val = val._eval(compressor, ignore_side_effects, cached, depth); |
| | | delete fn.evaluating; |
| | | fn.evaluating = false; |
| | | } |
| | | cached_args.forEach(function(node) { |
| | | delete node._eval; |
| | |
| | | return this; |
| | | |
| | | function decode(str) { |
| | | return str.replace(/\\(u\{[^}]*\}?|u[\s\S]{0,4}|x[\s\S]{0,2}|[0-9]+|[\s\S])/g, function(match, seq) { |
| | | var s = decode_escape_sequence(seq); |
| | | if (typeof s != "string") malformed = true; |
| | | return s; |
| | | }); |
| | | str = decode_template(str); |
| | | if (typeof str != "string") malformed = true; |
| | | return str; |
| | | } |
| | | }); |
| | | })(function(node, func) { |
| | |
| | | return map && map[prop]; |
| | | }); |
| | | |
| | | function spread_side_effects(exp) { |
| | | while ((exp = exp.tail_node()) instanceof AST_SymbolRef) { |
| | | exp = exp.fixed_value(); |
| | | if (!exp) return true; |
| | | } |
| | | return !(exp instanceof AST_Array |
| | | || exp.TYPE == "Binary" && !lazy_op[exp.operator] |
| | | || exp instanceof AST_Constant |
| | | || exp instanceof AST_Lambda |
| | | || exp instanceof AST_Object && all(exp.properties, function(prop) { |
| | | // determine if object spread syntax may cause runtime exception |
| | | (function(def) { |
| | | def(AST_Node, return_false); |
| | | def(AST_Array, return_true); |
| | | def(AST_Assign, function() { |
| | | switch (this.operator) { |
| | | case "=": |
| | | return this.right.safe_to_spread(); |
| | | case "&&=": |
| | | case "||=": |
| | | case "??=": |
| | | return this.left.safe_to_spread() && this.right.safe_to_spread(); |
| | | } |
| | | return true; |
| | | }); |
| | | def(AST_Binary, function() { |
| | | return !lazy_op[this.operator] || this.left.safe_to_spread() && this.right.safe_to_spread(); |
| | | }); |
| | | def(AST_Constant, return_true); |
| | | def(AST_Lambda, return_true); |
| | | def(AST_Object, function() { |
| | | return all(this.properties, function(prop) { |
| | | return !(prop instanceof AST_ObjectGetter || prop instanceof AST_Spread); |
| | | }) |
| | | || exp instanceof AST_ObjectIdentity |
| | | || exp instanceof AST_Unary); |
| | | } |
| | | }); |
| | | }); |
| | | def(AST_Sequence, function() { |
| | | return this.tail_node().safe_to_spread(); |
| | | }); |
| | | def(AST_SymbolRef, function() { |
| | | var fixed = this.fixed_value(); |
| | | return fixed && fixed.safe_to_spread(); |
| | | }); |
| | | def(AST_Unary, return_true); |
| | | })(function(node, func) { |
| | | node.DEFMETHOD("safe_to_spread", func); |
| | | }); |
| | | |
| | | // determine if expression has side effects |
| | | (function(def) { |
| | |
| | | }); |
| | | } |
| | | function array_spread(node, compressor) { |
| | | return !node.expression.is_string(compressor) || node.expression.has_side_effects(compressor); |
| | | var exp = node.expression; |
| | | return !exp.is_string(compressor) || exp.has_side_effects(compressor); |
| | | } |
| | | def(AST_Node, return_true); |
| | | def(AST_Array, function(compressor) { |
| | |
| | | return any(this.properties, compressor); |
| | | }); |
| | | def(AST_Dot, function(compressor) { |
| | | return !this.optional && this.expression.may_throw_on_access(compressor) |
| | | return this.expression.may_throw_on_access(compressor) |
| | | || this.expression.has_side_effects(compressor); |
| | | }); |
| | | def(AST_EmptyStatement, return_false); |
| | |
| | | def(AST_Object, function(compressor) { |
| | | return any(this.properties, compressor, function(node, compressor) { |
| | | var exp = node.expression; |
| | | return spread_side_effects(exp) || exp.has_side_effects(compressor); |
| | | return !exp.safe_to_spread() || exp.has_side_effects(compressor); |
| | | }); |
| | | }); |
| | | def(AST_ObjectIdentity, return_false); |
| | |
| | | return this.body.has_side_effects(compressor); |
| | | }); |
| | | def(AST_Sub, function(compressor) { |
| | | return !this.optional && this.expression.may_throw_on_access(compressor) |
| | | return this.expression.may_throw_on_access(compressor) |
| | | || this.expression.has_side_effects(compressor) |
| | | || this.property.has_side_effects(compressor); |
| | | }); |
| | |
| | | def(AST_Node, return_true); |
| | | |
| | | def(AST_Constant, return_false); |
| | | def(AST_Destructured, return_true); |
| | | def(AST_EmptyStatement, return_false); |
| | | def(AST_Lambda, return_false); |
| | | def(AST_ObjectIdentity, return_false); |
| | |
| | | return false; |
| | | } |
| | | return this.left.may_throw(compressor); |
| | | }); |
| | | def(AST_Await, function(compressor) { |
| | | return this.expression.may_throw(compressor); |
| | | }); |
| | | def(AST_Binary, function(compressor) { |
| | | return this.left.may_throw(compressor) |
| | |
| | | def(AST_Dot, function(compressor) { |
| | | return !this.optional && this.expression.may_throw_on_access(compressor) |
| | | || this.expression.may_throw(compressor); |
| | | }); |
| | | def(AST_ForEnumeration, function(compressor) { |
| | | if (this.init.may_throw(compressor)) return true; |
| | | var obj = this.object; |
| | | if (obj.may_throw(compressor)) return true; |
| | | obj = obj.tail_node(); |
| | | if (!(obj instanceof AST_Array || obj.is_string(compressor))) return true; |
| | | return this.body.may_throw(compressor); |
| | | }); |
| | | def(AST_If, function(compressor) { |
| | | return this.condition.may_throw(compressor) |
| | |
| | | return compressor.option("unused") && self.label.references.length == 0 ? self.body : self; |
| | | }); |
| | | |
| | | OPT(AST_LoopControl, function(self, compressor) { |
| | | if (!compressor.option("dead_code")) return self; |
| | | var label = self.label; |
| | | if (label) { |
| | | var lct = compressor.loopcontrol_target(self); |
| | | self.label = null; |
| | | if (compressor.loopcontrol_target(self) === lct) { |
| | | remove(label.thedef.references, self); |
| | | } else { |
| | | self.label = label; |
| | | } |
| | | } |
| | | return self; |
| | | }); |
| | | |
| | | OPT(AST_Block, function(self, compressor) { |
| | | self.body = tighten_body(self.body, compressor); |
| | | return self; |
| | |
| | | fn = fn.fixed_value(); |
| | | } |
| | | if (!(fn instanceof AST_Defun || fn instanceof AST_Function)) break; |
| | | if (fn.rest) break; |
| | | if (fn.uses_arguments) break; |
| | | if (fn === call.expression) { |
| | | if (fn.parent_scope !== self) break; |
| | |
| | | } |
| | | if (node instanceof AST_Binary) { |
| | | if (!lazy_op[node.operator]) return; |
| | | node.left.walk(tw); |
| | | push(); |
| | | node.right.walk(tw); |
| | | pop(); |
| | | walk_cond(node); |
| | | return true; |
| | | } |
| | | if (node instanceof AST_Break) { |
| | |
| | | } |
| | | if (node instanceof AST_Call) { |
| | | var exp = node.expression; |
| | | var tail = exp.tail_node(); |
| | | if (!(tail instanceof AST_LambdaExpression)) { |
| | | if (exp instanceof AST_LambdaExpression) { |
| | | node.args.forEach(function(arg) { |
| | | arg.walk(tw); |
| | | }); |
| | | exp.walk(tw); |
| | | } else { |
| | | descend(); |
| | | return mark_expression(exp); |
| | | mark_expression(exp); |
| | | } |
| | | if (exp !== tail) exp.expressions.slice(0, -1).forEach(function(node) { |
| | | node.walk(tw); |
| | | return true; |
| | | } |
| | | if (node instanceof AST_Class) { |
| | | if (node.name) node.name.walk(tw); |
| | | if (node.extends) node.extends.walk(tw); |
| | | node.properties.filter(function(prop) { |
| | | if (prop.key instanceof AST_Node) prop.key.walk(tw); |
| | | return prop.value; |
| | | }).forEach(function(prop) { |
| | | if (prop.static) { |
| | | prop.value.walk(tw); |
| | | } else { |
| | | push(); |
| | | segment.block = node; |
| | | prop.value.walk(tw); |
| | | pop(); |
| | | } |
| | | }); |
| | | node.args.forEach(function(arg) { |
| | | arg.walk(tw); |
| | | }); |
| | | tail.walk(tw); |
| | | return true; |
| | | } |
| | | if (node instanceof AST_Conditional) { |
| | | node.condition.walk(tw); |
| | | push(); |
| | | node.consequent.walk(tw); |
| | | pop(); |
| | | push(); |
| | | node.alternative.walk(tw); |
| | | pop(); |
| | | walk_cond(node.condition, node.consequent, node.alternative); |
| | | return true; |
| | | } |
| | | if (node instanceof AST_Continue) { |
| | |
| | | return true; |
| | | } |
| | | if (node instanceof AST_If) { |
| | | node.condition.walk(tw); |
| | | push(); |
| | | node.body.walk(tw); |
| | | pop(); |
| | | if (node.alternative) { |
| | | push(); |
| | | node.alternative.walk(tw); |
| | | pop(); |
| | | } |
| | | walk_cond(node.condition, node.body, node.alternative); |
| | | return true; |
| | | } |
| | | if (node instanceof AST_LabeledStatement) { |
| | |
| | | if (node === self) root = segment; |
| | | if (node instanceof AST_Lambda) { |
| | | if (node.name) references[node.name.definition().id] = false; |
| | | var parent = tw.parent(); |
| | | var in_iife = parent && parent.TYPE == "Call" && parent.expression === node; |
| | | var marker = node.uses_arguments && !tw.has_directive("use strict") ? function(node) { |
| | | if (node instanceof AST_SymbolFunarg) references[node.definition().id] = false; |
| | | } : function(node) { |
| | |
| | | || node.parent_scope.find_variable(ref.name) === def)) { |
| | | references[def.id] = false; |
| | | references[ldef.id] = false; |
| | | } else { |
| | | } else if (in_iife) { |
| | | var save = segment; |
| | | pop(); |
| | | mark(ref, true); |
| | | segment = save; |
| | | } else { |
| | | mark(ref, true); |
| | | } |
| | | return true; |
| | | }); |
| | |
| | | } else { |
| | | descend(); |
| | | } |
| | | return mark_expression(exp); |
| | | mark_expression(exp); |
| | | return true; |
| | | } |
| | | if (node instanceof AST_Switch) { |
| | | node.expression.walk(tw); |
| | |
| | | if (node instanceof AST_Try) { |
| | | var save_try = in_try; |
| | | in_try = node; |
| | | var save = segment; |
| | | walk_body(node, tw); |
| | | segment = save; |
| | | if (node.bcatch) { |
| | | if (node.bcatch.argname) node.bcatch.argname.mark_symbol(function(node) { |
| | | if (node instanceof AST_SymbolCatch) { |
| | |
| | | } |
| | | } |
| | | in_try = save_try; |
| | | segment = save; |
| | | if (node.bfinally) node.bfinally.walk(tw); |
| | | return true; |
| | | } |
| | |
| | | } |
| | | |
| | | function mark_expression(exp) { |
| | | if (compressor.option("ie")) { |
| | | var sym = root_expr(exp); |
| | | if (sym instanceof AST_SymbolRef) sym.walk(tw); |
| | | if (!compressor.option("ie")) return; |
| | | var sym = root_expr(exp); |
| | | if (sym instanceof AST_SymbolRef) sym.walk(tw); |
| | | } |
| | | |
| | | function walk_cond(condition, consequent, alternative) { |
| | | var save = segment; |
| | | var segments = [ save, save ]; |
| | | if (condition instanceof AST_Binary) switch (condition.operator) { |
| | | case "&&": |
| | | segments[0] = walk_cond(condition.left, condition.right)[0]; |
| | | break; |
| | | case "||": |
| | | case "??": |
| | | segments[1] = walk_cond(condition.left, null, condition.right)[1]; |
| | | break; |
| | | default: |
| | | condition.walk(tw); |
| | | break; |
| | | } else if (condition instanceof AST_Conditional) { |
| | | walk_cond(condition.condition, condition.consequent, condition.alternative); |
| | | } else { |
| | | condition.walk(tw); |
| | | } |
| | | return true; |
| | | segment = segments[0]; |
| | | if (consequent) { |
| | | push(); |
| | | consequent.walk(tw); |
| | | } |
| | | segments[0] = segment; |
| | | segment = segments[1]; |
| | | if (alternative) { |
| | | push(); |
| | | alternative.walk(tw); |
| | | } |
| | | segments[1] = segment; |
| | | segment = save; |
| | | return segments; |
| | | } |
| | | }); |
| | | tw.directives = Object.create(compressor.directives); |
| | | self.walk(tw); |
| | | var changed = false; |
| | | var merged = Object.create(null); |
| | | while (first.length && last.length) { |
| | | var head = first.pop(); |
| | | var def = head.definition; |
| | | if (!(def.id in prev)) continue; |
| | | if (!references[def.id]) continue; |
| | | var head_refs = { |
| | | start: references[def.id].start, |
| | | }; |
| | | var tail = last.shift(); |
| | | if (!tail) continue; |
| | | var def = tail.definition; |
| | | var tail_refs = references[def.id]; |
| | | if (!tail_refs) continue; |
| | | tail_refs = { end: tail_refs.end }; |
| | | while (def.id in merged) def = merged[def.id]; |
| | | head_refs.end = references[def.id].end; |
| | | tail_refs.start = references[def.id].start; |
| | | var skipped = []; |
| | | do { |
| | | var tail = last.pop(); |
| | | if (!tail) continue; |
| | | var head = first.shift(); |
| | | if (tail.index > head.index) continue; |
| | | var id = tail.definition.id; |
| | | var tail_refs = references[id]; |
| | | if (!tail_refs) continue; |
| | | var id = head.definition.id; |
| | | if (!(id in prev)) continue; |
| | | var head_refs = references[id]; |
| | | if (!head_refs) continue; |
| | | if (head_refs.start.block !== tail_refs.start.block |
| | | || !mergeable(head_refs, tail_refs) |
| | | || (head_refs.start.loop || !same_scope(def)) && !mergeable(tail_refs, head_refs) |
| | | || compressor.option("webkit") && is_funarg(def) !== is_funarg(tail.definition) |
| | | || !all(tail_refs, function(sym) { |
| | | || compressor.option("webkit") && is_funarg(def) !== is_funarg(head.definition) |
| | | || head.definition.const_redefs |
| | | || !all(head_refs, function(sym) { |
| | | return sym.scope.find_variable(def.name) === def; |
| | | })) { |
| | | skipped.unshift(tail); |
| | | skipped.push(head); |
| | | continue; |
| | | } |
| | | var orig = [], refs = []; |
| | | tail_refs.forEach(function(sym) { |
| | | head_refs.forEach(function(sym) { |
| | | sym.thedef = def; |
| | | sym.name = def.name; |
| | | if (sym instanceof AST_SymbolRef) { |
| | | refs.push(sym); |
| | | def.references.push(sym); |
| | | } else { |
| | | orig.push(sym); |
| | | def.orig.push(sym); |
| | | } |
| | | }); |
| | | def.orig = orig.concat(def.orig); |
| | | def.references = refs.concat(def.references); |
| | | def.fixed = tail.definition.fixed && def.fixed; |
| | | if (!head.definition.fixed) def.fixed = false; |
| | | merged[id] = def; |
| | | changed = true; |
| | | break; |
| | | } while (last.length); |
| | | if (skipped.length) last = last.concat(skipped); |
| | | } while (first.length); |
| | | if (skipped.length) first = skipped.concat(first); |
| | | } |
| | | return changed; |
| | | |
| | | function push() { |
| | | segment = Object.create(segment); |
| | |
| | | |
| | | function to_class_expr(defcl, drop_name) { |
| | | var cl = make_node(AST_ClassExpression, defcl, defcl); |
| | | cl.name = drop_name ? null : make_node(AST_SymbolClass, defcl.name, defcl.name); |
| | | if (cl.name) cl.name = drop_name ? null : make_node(AST_SymbolClass, cl.name, cl.name); |
| | | return cl; |
| | | } |
| | | |
| | |
| | | if (scope === self) { |
| | | if (node instanceof AST_DefClass) { |
| | | var def = node.name.definition(); |
| | | if ((!drop_funcs || def.exported) && !(def.id in in_use_ids)) { |
| | | var drop = drop_funcs && !def.exported; |
| | | if (!drop && !(def.id in in_use_ids)) { |
| | | in_use_ids[def.id] = true; |
| | | in_use.push(def); |
| | | } |
| | | if (node.extends) node.extends.walk(tw); |
| | | var is_export = false; |
| | | if (tw.parent() instanceof AST_ExportDefault) { |
| | | is_export = true; |
| | | export_defaults[def.id] = true; |
| | | } |
| | | var used = tw.parent() instanceof AST_ExportDefault; |
| | | if (used) export_defaults[def.id] = true; |
| | | var values = []; |
| | | node.properties.forEach(function(prop) { |
| | | if (prop.key instanceof AST_Node) prop.key.walk(tw); |
| | | if (!prop.value) return; |
| | | if (is_export || prop instanceof AST_ClassField && prop.static) { |
| | | var save_scope = scope; |
| | | scope = node; |
| | | prop.value.walk(tw); |
| | | scope = save_scope; |
| | | var value = prop.value; |
| | | if (!value) return; |
| | | if (prop instanceof AST_ClassField && prop.static) { |
| | | if (!used && value.contains_this()) used = true; |
| | | walk_class_prop(value); |
| | | } else { |
| | | initializations.add(def.id, prop.value); |
| | | values.push(value); |
| | | } |
| | | }); |
| | | values.forEach(drop && used ? walk_class_prop : function(value) { |
| | | initializations.add(def.id, value); |
| | | }); |
| | | return true; |
| | | } |
| | |
| | | } |
| | | } |
| | | return scan_ref_scoped(node, descend, true); |
| | | |
| | | function walk_class_prop(value) { |
| | | var save_scope = scope; |
| | | scope = node; |
| | | value.walk(tw); |
| | | scope = save_scope; |
| | | } |
| | | }); |
| | | tw.directives = Object.create(compressor.directives); |
| | | self.walk(tw); |
| | |
| | | } |
| | | }); |
| | | // pass 3: we should drop declarations not in_use |
| | | var trim_defns = []; |
| | | var unused_fn_names = []; |
| | | var calls_to_drop_args = []; |
| | | var fns_with_marked_args = []; |
| | |
| | | && indexOf_assign(node.expression.definition(), node) < 0) { |
| | | return make_node(AST_UnaryPrefix, node, { |
| | | operator: "+", |
| | | expression: node.expression |
| | | expression: node.expression, |
| | | }); |
| | | } |
| | | } |
| | | if (node instanceof AST_Call) calls_to_drop_args.push(node); |
| | | if (node instanceof AST_Call) { |
| | | calls_to_drop_args.push(node); |
| | | node.args = node.args.map(function(arg) { |
| | | return arg.transform(tt); |
| | | }); |
| | | node.expression = node.expression.transform(tt); |
| | | return node; |
| | | } |
| | | if (scope !== self) return; |
| | | if (drop_funcs && node !== self && node instanceof AST_DefClass) { |
| | | var def = node.name.definition(); |
| | |
| | | log(node.name, "Dropping unused class {name}"); |
| | | def.eliminated++; |
| | | descend(node, tt); |
| | | if (parent instanceof AST_ExportDefault) return to_class_expr(node, true); |
| | | var trimmed = node.drop_side_effect_free(compressor, true); |
| | | if (trimmed === node) trimmed = to_class_expr(node, true); |
| | | var trimmed = to_class_expr(node, true); |
| | | if (parent instanceof AST_ExportDefault) return trimmed; |
| | | trimmed = trimmed.drop_side_effect_free(compressor, true); |
| | | if (trimmed) return make_node(AST_SimpleStatement, node, { body: trimmed }); |
| | | return in_list ? List.skip : make_node(AST_EmptyStatement, node); |
| | | } |
| | |
| | | unused_fn_names.push(node); |
| | | } |
| | | if (!(node instanceof AST_Accessor)) { |
| | | if (node.rest) { |
| | | var rest = node.rest.transform(trimmer); |
| | | var args, spread, trim = compressor.drop_fargs(node, parent); |
| | | if (trim && parent instanceof AST_Call && parent.expression === node) { |
| | | args = parent.args; |
| | | for (spread = 0; spread < args.length; spread++) { |
| | | if (args[spread] instanceof AST_Spread) break; |
| | | } |
| | | } |
| | | var argnames = node.argnames; |
| | | var rest = node.rest; |
| | | if (rest) { |
| | | if (!args || spread < argnames.length || rest instanceof AST_SymbolFunarg) { |
| | | rest = rest.transform(trimmer); |
| | | } else { |
| | | var trimmed = trim_destructured(rest, make_node(AST_Array, parent, { |
| | | elements: args.slice(argnames.length), |
| | | }), function(node) { |
| | | return node.definition().id in in_use_ids ? node : null; |
| | | }, !node.uses_arguments, rest); |
| | | rest = trimmed.name; |
| | | args.length = argnames.length; |
| | | if (trimmed.value.elements.length) [].push.apply(args, trimmed.value.elements); |
| | | } |
| | | if (rest instanceof AST_Destructured && !rest.rest |
| | | && (!node.uses_arguments || tt.has_directive("use strict"))) { |
| | | if (rest instanceof AST_DestructuredArray) { |
| | |
| | | } |
| | | } |
| | | node.rest = rest; |
| | | if (rest) trim = false; |
| | | } |
| | | var argnames = node.argnames; |
| | | var trim = compressor.drop_fargs(node, parent) && !node.rest; |
| | | var default_length = trim ? -1 : node.length(); |
| | | for (var i = argnames.length; --i >= 0;) { |
| | | var sym = argnames[i]; |
| | | if (!(sym instanceof AST_SymbolFunarg)) { |
| | | var arg = sym.transform(trimmer); |
| | | if (arg) { |
| | | if (sym instanceof AST_SymbolFunarg) { |
| | | var def = sym.definition(); |
| | | if (def.id in in_use_ids) { |
| | | trim = false; |
| | | if (indexOf_assign(def, sym) < 0) sym.unused = null; |
| | | } else if (trim) { |
| | | log(sym, "Dropping unused function argument {name}"); |
| | | argnames.pop(); |
| | | } else { |
| | | sym.unused = true; |
| | | } |
| | | } else { |
| | | var funarg; |
| | | if (!args || spread < i) { |
| | | funarg = sym.transform(trimmer); |
| | | } else { |
| | | funarg = trim_destructured(sym, args[i], function(node) { |
| | | return node.definition().id in in_use_ids ? node : null; |
| | | }, !node.uses_arguments, sym).name; |
| | | } |
| | | if (funarg) { |
| | | trim = false; |
| | | } else if (trim) { |
| | | log(sym.name, "Dropping unused default argument {name}"); |
| | | log_default(sym, "Dropping unused default argument {name}"); |
| | | argnames.pop(); |
| | | } else if (i > default_length) { |
| | | log(sym.name, "Dropping unused default argument assignment {name}"); |
| | | sym.name.__unused = true; |
| | | log_default(sym, "Dropping unused default argument assignment {name}"); |
| | | if (sym.name instanceof AST_SymbolFunarg) sym.name.unused = true; |
| | | argnames[i] = sym.name; |
| | | } else { |
| | | log(sym.name, "Dropping unused default argument value {name}"); |
| | | log_default(sym, "Dropping unused default argument value {name}"); |
| | | sym.value = make_node(AST_Number, sym, { value: 0 }); |
| | | } |
| | | continue; |
| | | } |
| | | var def = sym.definition(); |
| | | if (def.id in in_use_ids) { |
| | | trim = false; |
| | | if (indexOf_assign(def, sym) < 0) sym.__unused = null; |
| | | } else if (trim) { |
| | | log(sym, "Dropping unused function argument {name}"); |
| | | argnames.pop(); |
| | | } else { |
| | | sym.__unused = true; |
| | | } |
| | | } |
| | | fns_with_marked_args.push(node); |
| | |
| | | var sym = def.name.definition(); |
| | | var drop_sym = is_var ? can_drop_symbol(def.name) : is_safe_lexical(sym); |
| | | if (!drop_sym || !drop_vars || sym.id in in_use_ids) { |
| | | if (value && indexOf_assign(sym, def) < 0) { |
| | | var index; |
| | | if (value && ((index = indexOf_assign(sym, def)) < 0 || self_assign(value.tail_node()))) { |
| | | def = def.clone(); |
| | | value = value.drop_side_effect_free(compressor); |
| | | if (value) { |
| | | AST_Node.warn("Side effects in last use of variable {name} [{file}:{line},{col}]", template(def.name)); |
| | | side_effects.push(value); |
| | | if (value) AST_Node.warn("Side effects in definition of variable {name} [{file}:{line},{col}]", template(def.name)); |
| | | if (node instanceof AST_Const) { |
| | | def.value = value || make_node(AST_Number, def, { value: 0 }); |
| | | } else { |
| | | def.value = null; |
| | | if (value) side_effects.push(value); |
| | | } |
| | | value = null; |
| | | trim_defns.push(def); |
| | | if (index >= 0) assign_in_use[sym.id][index] = def; |
| | | } |
| | | var old_def; |
| | | var old_def, fn; |
| | | if (!value && !(node instanceof AST_Let)) { |
| | | if (parent instanceof AST_ExportDeclaration) { |
| | | flush(); |
| | |
| | | } else if (compressor.option("functions") |
| | | && !compressor.option("ie") |
| | | && drop_sym |
| | | && value |
| | | && var_defs[sym.id] == 1 |
| | | && sym.assignments == 0 |
| | | && value instanceof AST_LambdaExpression |
| | | && (fn = value.tail_node()) instanceof AST_LambdaExpression |
| | | && !is_arguments(sym) |
| | | && !is_arrow(value) |
| | | && assigned_once(value, sym.references) |
| | | && can_declare_defun(value) |
| | | && (old_def = rename_def(value, def.name.name)) !== false) { |
| | | && !is_arrow(fn) |
| | | && assigned_once(fn, sym.references) |
| | | && can_declare_defun(fn) |
| | | && (old_def = rename_def(fn, def.name.name)) !== false) { |
| | | AST_Node.warn("Declaring {name} as function [{file}:{line},{col}]", template(def.name)); |
| | | var ctor; |
| | | switch (value.CTOR) { |
| | | switch (fn.CTOR) { |
| | | case AST_AsyncFunction: |
| | | ctor = AST_AsyncDefun; |
| | | break; |
| | |
| | | ctor = AST_GeneratorDefun; |
| | | break; |
| | | } |
| | | var defun = make_node(ctor, def, value); |
| | | var defun = make_node(ctor, def, fn); |
| | | defun.name = make_node(AST_SymbolDefun, def.name, def.name); |
| | | var name_def = def.name.scope.resolve().def_function(defun.name); |
| | | if (old_def) old_def.forEach(function(node) { |
| | |
| | | node.reference(); |
| | | }); |
| | | body.push(defun); |
| | | if (value !== fn) [].push.apply(side_effects, value.expressions.slice(0, -1)); |
| | | } else { |
| | | if (drop_sym |
| | | && var_defs[sym.id] > 1 |
| | |
| | | head.push(def); |
| | | } |
| | | } else { |
| | | value = value && !value.single_use && value.drop_side_effect_free(compressor); |
| | | value = value && value.drop_side_effect_free(compressor); |
| | | if (value) { |
| | | AST_Node.warn("Side effects in initialization of unused variable {name} [{file}:{line},{col}]", template(def.name)); |
| | | side_effects.push(value); |
| | |
| | | log(def.name, "Dropping unused variable {name}"); |
| | | } |
| | | sym.eliminated++; |
| | | } |
| | | |
| | | function self_assign(ref) { |
| | | return ref instanceof AST_SymbolRef && ref.definition() === sym; |
| | | } |
| | | |
| | | function assigned_once(fn, refs) { |
| | |
| | | if (def.orig.length > 1) return null; |
| | | if (def.assignments > 0) return false; |
| | | if (def.name == name) return def; |
| | | if (compressor.option("keep_fnames")) return false; |
| | | var forbidden; |
| | | switch (name) { |
| | | case "await": |
| | |
| | | var assign = make_node(AST_Assign, def, { |
| | | operator: "=", |
| | | left: ref, |
| | | right: def.value |
| | | right: def.value, |
| | | }); |
| | | var index = indexOf_assign(sym, def); |
| | | if (index >= 0) assign_in_use[sym.id][index] = assign; |
| | |
| | | } |
| | | } |
| | | default: |
| | | var seq; |
| | | if (tail.length > 0 && (seq = tail[0].value) instanceof AST_Sequence) { |
| | | tail[0].value = seq.tail_node(); |
| | | body.push(make_node(AST_SimpleStatement, node, { |
| | | body: make_sequence(seq, seq.expressions.slice(0, -1)), |
| | | })); |
| | | } |
| | | node.definitions = head.concat(tail); |
| | | body.push(node); |
| | | } |
| | | if (side_effects.length > 0) { |
| | | body.push(make_node(AST_SimpleStatement, node, { |
| | | body: make_sequence(node, side_effects) |
| | | })); |
| | | body.push(make_node(AST_SimpleStatement, node, { body: make_sequence(node, side_effects) })); |
| | | } |
| | | return insert_statements(body, node, in_list); |
| | | } |
| | |
| | | } |
| | | }, function(node, in_list) { |
| | | if (node instanceof AST_BlockStatement) return trim_block(node, tt.parent(), in_list); |
| | | // Certain combination of unused name + side effect leads to invalid AST: |
| | | // https://github.com/mishoo/UglifyJS/issues/44 |
| | | // https://github.com/mishoo/UglifyJS/issues/1838 |
| | | // https://github.com/mishoo/UglifyJS/issues/3371 |
| | | // We fix it at this stage by moving the `var` outside the `for`. |
| | | if (node instanceof AST_For) { |
| | | var block; |
| | | if (node.init instanceof AST_BlockStatement) { |
| | | block = node.init; |
| | | node.init = block.body.pop(); |
| | | block.body.push(node); |
| | | } |
| | | if (node.init instanceof AST_Defun) { |
| | | if (!block) { |
| | | block = make_node(AST_BlockStatement, node, { |
| | | body: [ node ] |
| | | }); |
| | | } |
| | | block.body.splice(-1, 0, node.init); |
| | | node.init = null; |
| | | } else if (node.init instanceof AST_SimpleStatement) { |
| | | node.init = node.init.body; |
| | | } else if (is_empty(node.init)) { |
| | | node.init = null; |
| | | } |
| | | return !block ? node : in_list ? List.splice(block.body) : block; |
| | | } |
| | | if (node instanceof AST_For) return patch_for_init(node, in_list); |
| | | if (node instanceof AST_ForIn) { |
| | | if (!drop_vars || !compressor.option("loops")) return; |
| | | if (!is_empty(node.body)) return; |
| | |
| | | && self.body[0].value == "use strict") { |
| | | self.body.length = 0; |
| | | } |
| | | trim_defns.forEach(function(def) { |
| | | def.value = null; |
| | | }); |
| | | unused_fn_names.forEach(function(fn) { |
| | | fn.name = null; |
| | | }); |
| | |
| | | |
| | | function log(sym, text) { |
| | | AST_Node[sym.definition().references.length > 0 ? "info" : "warn"](text + " [{file}:{line},{col}]", template(sym)); |
| | | } |
| | | |
| | | function log_default(node, text) { |
| | | if (node.name instanceof AST_SymbolFunarg) { |
| | | log(node.name, text); |
| | | } else { |
| | | AST_Node.info(text + " [{file}:{line},{col}]", { |
| | | name: node, |
| | | file: node.start.file, |
| | | line: node.start.line, |
| | | col : node.start.col, |
| | | }); |
| | | } |
| | | } |
| | | |
| | | function template(sym) { |
| | |
| | | var value = node.value.drop_side_effect_free(compressor); |
| | | if (!value) return null; |
| | | log(node.name, "Side effects in default value of unused variable {name}"); |
| | | node.name.__unused = null; |
| | | node.name.unused = null; |
| | | node.value = value; |
| | | } |
| | | return node; |
| | | } |
| | | |
| | | function trim_destructured(node, value, process, drop) { |
| | | function trim_destructured(node, value, process, drop, root) { |
| | | var trimmer = new TreeTransformer(function(node) { |
| | | if (node instanceof AST_DefaultValue) { |
| | | if (compressor.option("default_values") && value && value.is_defined(compressor)) { |
| | | node = node.name; |
| | | } else { |
| | | if (!(compressor.option("default_values") && value && value.is_defined(compressor))) { |
| | | var save_drop = drop; |
| | | drop = false; |
| | | var trimmed = trim_default(trimmer, node); |
| | | drop = save_drop; |
| | | if (!trimmed && drop && value) value = value.drop_side_effect_free(compressor); |
| | | return trimmed; |
| | | } else if (node === root) { |
| | | root = node = node.name; |
| | | } else { |
| | | node = node.name; |
| | | } |
| | | } |
| | | if (node instanceof AST_DestructuredArray) { |
| | |
| | | if (!node.rest && (value instanceof AST_Array |
| | | || value && value.is_string(compressor))) switch (elements.length) { |
| | | case 0: |
| | | if (node === root) break; |
| | | if (drop) value = value.drop_side_effect_free(compressor); |
| | | return null; |
| | | case 1: |
| | | if (!drop) break; |
| | | if (node === root) break; |
| | | var sym = elements[0]; |
| | | if (!(sym instanceof AST_Symbol)) break; |
| | | if (sym.has_side_effects(compressor)) break; |
| | | if (value.has_side_effects(compressor) && sym.match_symbol(function(node) { |
| | | return node instanceof AST_PropAccess; |
| | | })) break; |
| | | value = make_node(AST_Sub, node, { |
| | | expression: value, |
| | | property: make_node(AST_Number, node, { value: 0 }), |
| | |
| | | var prop_keys, prop_map; |
| | | if (value instanceof AST_Object) { |
| | | prop_keys = []; |
| | | prop_map = Object.create(null); |
| | | prop_map = new Dictionary(); |
| | | value.properties.forEach(function(prop, index) { |
| | | if (prop instanceof AST_Spread) return prop_map = false; |
| | | var key = prop.key; |
| | |
| | | if (key instanceof AST_Node) { |
| | | prop_map = false; |
| | | } else if (prop_map && !(prop instanceof AST_ObjectSetter)) { |
| | | prop_map[key] = prop; |
| | | prop_map.set(key, prop); |
| | | } |
| | | prop_keys[index] = key; |
| | | }); |
| | |
| | | value = false; |
| | | node.rest = node.rest.transform(compressor.option("rests") ? trimmer : tt); |
| | | } |
| | | var can_drop = Object.create(null); |
| | | var drop_keys = drop && Object.create(null); |
| | | var can_drop = new Dictionary(); |
| | | var drop_keys = drop && new Dictionary(); |
| | | var properties = []; |
| | | node.properties.map(function(prop) { |
| | | var key = prop.key; |
| | |
| | | if (key instanceof AST_Node) { |
| | | drop_keys = false; |
| | | } else { |
| | | can_drop[key] = !(key in can_drop); |
| | | can_drop.set(key, !can_drop.has(key)); |
| | | } |
| | | return key; |
| | | }).forEach(function(key, index) { |
| | |
| | | value = false; |
| | | trimmed = prop.value.transform(trimmer) || retain_lhs(prop.value); |
| | | } else { |
| | | drop = drop_keys && can_drop[key]; |
| | | var mapped = prop_map && prop_map[key]; |
| | | drop = drop_keys && can_drop.get(key); |
| | | var mapped = prop_map && prop_map.get(key); |
| | | if (mapped) { |
| | | value = mapped.value; |
| | | if (value instanceof AST_Accessor) value = false; |
| | |
| | | trimmed = prop.value.transform(trimmer); |
| | | if (!trimmed) { |
| | | if (node.rest || retain_key(prop)) trimmed = retain_lhs(prop.value); |
| | | if (drop_keys && !(key in drop_keys)) { |
| | | if (drop_keys && !drop_keys.has(key)) { |
| | | if (mapped) { |
| | | drop_keys[key] = mapped; |
| | | drop_keys.set(key, mapped); |
| | | if (value === null) { |
| | | prop_map[key] = retain_key(mapped) && make_node(AST_ObjectKeyVal, mapped, { |
| | | prop_map.set(key, retain_key(mapped) && make_node(AST_ObjectKeyVal, mapped, { |
| | | key: mapped.key, |
| | | value: make_node(AST_Number, mapped, { value: 0 }), |
| | | }); |
| | | })); |
| | | } |
| | | } else { |
| | | drop_keys[key] = true; |
| | | drop_keys.set(key, true); |
| | | } |
| | | } |
| | | } else if (drop_keys) { |
| | | drop_keys[key] = false; |
| | | drop_keys.set(key, false); |
| | | } |
| | | if (value) mapped.value = value; |
| | | } |
| | |
| | | if (prop instanceof AST_Spread) return prop; |
| | | var key = prop_keys[index]; |
| | | if (key instanceof AST_Node) return prop; |
| | | if (key in drop_keys) { |
| | | var mapped = drop_keys[key]; |
| | | if (drop_keys.has(key)) { |
| | | var mapped = drop_keys.get(key); |
| | | if (!mapped) return prop; |
| | | if (mapped === prop) return prop_map[key] || List.skip; |
| | | if (mapped === prop) return prop_map.get(key) || List.skip; |
| | | } else if (node.rest) { |
| | | return prop; |
| | | } |
| | |
| | | }); |
| | | if (value && !node.rest) switch (properties.length) { |
| | | case 0: |
| | | if (node === root) break; |
| | | if (value.may_throw_on_access(compressor, true)) break; |
| | | if (drop) value = value.drop_side_effect_free(compressor); |
| | | return null; |
| | | case 1: |
| | | if (!drop) break; |
| | | if (node === root) break; |
| | | var prop = properties[0]; |
| | | if (prop.key instanceof AST_Node) break; |
| | | if (!(prop.value instanceof AST_Symbol)) break; |
| | | if (prop.value.has_side_effects(compressor)) break; |
| | | if (value.has_side_effects(compressor) && prop.value.match_symbol(function(node) { |
| | | return node instanceof AST_PropAccess; |
| | | })) break; |
| | | value = make_node(AST_Sub, node, { |
| | | expression: value, |
| | | property: make_node_from_constant(prop.key, prop), |
| | |
| | | return prop.key instanceof AST_Node && prop.key.has_side_effects(compressor); |
| | | } |
| | | |
| | | function clear_write_only(node) { |
| | | if (node instanceof AST_Assign) { |
| | | node.write_only = false; |
| | | clear_write_only(node.right); |
| | | } else if (node instanceof AST_Binary) { |
| | | if (!lazy_op[node.operator]) return; |
| | | clear_write_only(node.left); |
| | | clear_write_only(node.right); |
| | | } else if (node instanceof AST_Conditional) { |
| | | clear_write_only(node.consequent); |
| | | clear_write_only(node.alternative); |
| | | } else if (node instanceof AST_Sequence) { |
| | | clear_write_only(node.tail_node()); |
| | | } else if (node instanceof AST_Unary) { |
| | | node.write_only = false; |
| | | } |
| | | } |
| | | |
| | | function retain_lhs(node) { |
| | | if (node instanceof AST_DefaultValue) return retain_lhs(node.name); |
| | | if (node instanceof AST_Destructured) { |
| | | if (value === null) { |
| | | value = make_node(AST_Number, node, { value: 0 }); |
| | | } else if (value && (value.tail_node().write_only === true |
| | | || value.may_throw_on_access(compressor, true))) { |
| | | value = make_node(AST_Array, node, { |
| | | elements: value instanceof AST_Sequence ? value.expressions : [ value ], |
| | | }); |
| | | } else if (value) { |
| | | if (value.may_throw_on_access(compressor, true)) { |
| | | value = make_node(AST_Array, node, { |
| | | elements: value instanceof AST_Sequence ? value.expressions : [ value ], |
| | | }); |
| | | } else { |
| | | clear_write_only(value); |
| | | } |
| | | } |
| | | return make_node(AST_DestructuredObject, node, { properties: [] }); |
| | | } |
| | | node.__unused = null; |
| | | node.unused = null; |
| | | return node; |
| | | } |
| | | } |
| | |
| | | if (var_decl <= 1) hoist_vars = false; |
| | | } |
| | | if (!hoist_funs && !hoist_vars) return; |
| | | var consts = Object.create(null); |
| | | var consts = new Dictionary(); |
| | | var dirs = []; |
| | | var hoisted = []; |
| | | var vars = new Dictionary(), vars_found = 0; |
| | | var vars = new Dictionary(); |
| | | var tt = new TreeTransformer(function(node, descend, in_list) { |
| | | if (node === self) return; |
| | | if (node instanceof AST_Directive) { |
| | |
| | | if (!all(node.definitions, function(defn) { |
| | | var sym = defn.name; |
| | | return sym instanceof AST_SymbolVar |
| | | && !consts[sym.name] |
| | | && !consts.has(sym.name) |
| | | && self.find_variable(sym.name) === sym.definition(); |
| | | })) return node; |
| | | node.definitions.forEach(function(def) { |
| | | vars.set(def.name.name, def); |
| | | ++vars_found; |
| | | node.definitions.forEach(function(defn) { |
| | | vars.set(defn.name.name, defn); |
| | | }); |
| | | var seq = node.to_assignments(); |
| | | if (p instanceof AST_ForEnumeration && p.init === node) { |
| | | if (seq) return seq; |
| | | var def = node.definitions[0].name; |
| | | return make_node(AST_SymbolRef, def, def); |
| | | var sym = node.definitions[0].name; |
| | | return make_node(AST_SymbolRef, sym, sym); |
| | | } |
| | | if (p instanceof AST_For && p.init === node) return seq; |
| | | if (!seq) return in_list ? List.skip : make_node(AST_EmptyStatement, node); |
| | |
| | | } |
| | | if (node instanceof AST_Scope) return node; |
| | | if (node instanceof AST_SymbolConst) { |
| | | consts[node.name] = true; |
| | | consts.set(node.name, true); |
| | | return node; |
| | | } |
| | | }); |
| | | self.transform(tt); |
| | | if (vars_found > 0) { |
| | | if (vars.size() > 0) { |
| | | // collect only vars which don't show up in self's arguments list |
| | | var defs = []; |
| | | var defns = []; |
| | | if (self instanceof AST_Lambda) self.each_argname(function(argname) { |
| | | vars.del(argname.name); |
| | | }); |
| | | vars.each(function(def, name) { |
| | | def = def.clone(); |
| | | def.value = null; |
| | | defs.push(def); |
| | | vars.set(name, def); |
| | | vars.each(function(defn, name) { |
| | | defn = defn.clone(); |
| | | defn.name = defn.name.clone(); |
| | | defn.value = null; |
| | | defns.push(defn); |
| | | vars.set(name, defn); |
| | | defn.name.definition().orig.unshift(defn.name); |
| | | }); |
| | | if (defs.length > 0) { |
| | | if (defns.length > 0) { |
| | | // try to merge in assignments |
| | | insert_vars(self.body); |
| | | defs = make_node(AST_Var, self, { definitions: defs }); |
| | | hoisted.push(defs); |
| | | hoisted.push(make_node(AST_Var, self, { definitions: defns })); |
| | | } |
| | | } |
| | | self.body = dirs.concat(hoisted, self.body); |
| | |
| | | && expr.operator == "=" |
| | | && (sym = expr.left) instanceof AST_Symbol |
| | | && vars.has(sym.name)) { |
| | | var def = vars.get(sym.name); |
| | | if (def.value) break; |
| | | var defn = vars.get(sym.name); |
| | | if (defn.value) break; |
| | | var value = expr.right; |
| | | if (value instanceof AST_Sequence) value = value.clone(); |
| | | def.value = value; |
| | | remove(defs, def); |
| | | defs.push(def); |
| | | defn.value = value; |
| | | remove(defns, defn); |
| | | defns.push(defn); |
| | | body.shift(); |
| | | continue; |
| | | } |
| | |
| | | && 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); |
| | | var defn = vars.get(sym.name); |
| | | if (defn.value) break; |
| | | defn.value = assign.right; |
| | | remove(defns, defn); |
| | | defns.push(defn); |
| | | stat.body = make_sequence(expr, expr.expressions.slice(1)); |
| | | continue; |
| | | } |
| | |
| | | })); |
| | | } |
| | | |
| | | function map_bool_returns(fn) { |
| | | function map_self_returns(fn) { |
| | | var map = Object.create(null); |
| | | scan_local_returns(fn, function(node) { |
| | | var value = node.value; |
| | |
| | | return map; |
| | | } |
| | | |
| | | function all_bool(def, bool_returns, compressor) { |
| | | return def.bool_fn + (bool_returns[def.id] || 0) === def.references.length - def.replaced |
| | | && !compressor.exposed(def); |
| | | function can_trim_returns(def, self_returns, compressor) { |
| | | if (compressor.exposed(def)) return false; |
| | | switch (def.references.length - def.replaced - (self_returns[def.id] || 0)) { |
| | | case def.drop_return: |
| | | return "d"; |
| | | case def.bool_return: |
| | | return true; |
| | | } |
| | | } |
| | | |
| | | function process_boolean_returns(fn, compressor) { |
| | |
| | | }); |
| | | } |
| | | |
| | | AST_Scope.DEFMETHOD("process_boolean_returns", noop); |
| | | AST_Defun.DEFMETHOD("process_boolean_returns", function(compressor) { |
| | | AST_Scope.DEFMETHOD("process_returns", noop); |
| | | AST_Defun.DEFMETHOD("process_returns", function(compressor) { |
| | | if (!compressor.option("booleans")) return; |
| | | var bool_returns = map_bool_returns(this); |
| | | if (!all_bool(this.name.definition(), bool_returns, compressor)) return; |
| | | if (compressor.parent() instanceof AST_ExportDefault) return; |
| | | process_boolean_returns(this, compressor); |
| | | switch (can_trim_returns(this.name.definition(), map_self_returns(this), compressor)) { |
| | | case "d": |
| | | drop_returns(compressor, this, true); |
| | | break; |
| | | case true: |
| | | process_boolean_returns(this, compressor); |
| | | break; |
| | | } |
| | | }); |
| | | AST_Function.DEFMETHOD("process_boolean_returns", function(compressor) { |
| | | AST_Function.DEFMETHOD("process_returns", function(compressor) { |
| | | if (!compressor.option("booleans")) return; |
| | | var bool_returns = map_bool_returns(this); |
| | | if (this.name && !all_bool(this.name.definition(), bool_returns, compressor)) return; |
| | | var drop = true; |
| | | var self_returns = map_self_returns(this); |
| | | if (this.name && !can_trim(this.name.definition())) return; |
| | | var parent = compressor.parent(); |
| | | if (parent instanceof AST_Assign) { |
| | | if (parent.operator != "=") return; |
| | | var sym = parent.left; |
| | | if (!(sym instanceof AST_SymbolRef)) return; |
| | | if (!all_bool(sym.definition(), bool_returns, compressor)) return; |
| | | if (!can_trim(sym.definition())) return; |
| | | } else if (parent instanceof AST_Call && parent.expression !== this) { |
| | | var exp = parent.expression; |
| | | if (exp instanceof AST_SymbolRef) exp = exp.fixed_value(); |
| | | if (!(exp instanceof AST_Lambda)) return; |
| | | if (exp.uses_arguments || exp.pinned()) return; |
| | | var sym = exp.argnames[parent.args.indexOf(this)]; |
| | | var args = parent.args, sym; |
| | | for (var i = 0; i < args.length; i++) { |
| | | var arg = args[i]; |
| | | if (arg === this) { |
| | | sym = exp.argnames[i]; |
| | | if (!sym && exp.rest) return; |
| | | break; |
| | | } |
| | | if (arg instanceof AST_Spread) return; |
| | | } |
| | | if (sym instanceof AST_DefaultValue) sym = sym.name; |
| | | if (sym instanceof AST_SymbolFunarg && !all_bool(sym.definition(), bool_returns, compressor)) return; |
| | | if (sym instanceof AST_SymbolFunarg && !can_trim(sym.definition())) return; |
| | | } else if (parent.TYPE == "Call") { |
| | | compressor.pop(); |
| | | var in_bool = compressor.in_boolean_context(); |
| | | compressor.push(this); |
| | | if (!in_bool) return; |
| | | switch (in_bool) { |
| | | case true: |
| | | drop = false; |
| | | case "d": |
| | | break; |
| | | default: |
| | | return; |
| | | } |
| | | } else return; |
| | | process_boolean_returns(this, compressor); |
| | | if (drop) { |
| | | drop_returns(compressor, this, true); |
| | | } else { |
| | | process_boolean_returns(this, compressor); |
| | | } |
| | | |
| | | function can_trim(def) { |
| | | switch (can_trim_returns(def, self_returns, compressor)) { |
| | | case true: |
| | | drop = false; |
| | | case "d": |
| | | return true; |
| | | } |
| | | } |
| | | }); |
| | | |
| | | AST_BlockScope.DEFMETHOD("var_names", function() { |
| | | var var_names = this._var_names; |
| | | if (!var_names) { |
| | | this._var_names = var_names = Object.create(null); |
| | | this._var_names = var_names = new Dictionary(); |
| | | this.enclosed.forEach(function(def) { |
| | | var_names[def.name] = true; |
| | | var_names.set(def.name, true); |
| | | }); |
| | | this.variables.each(function(def, name) { |
| | | var_names[name] = true; |
| | | var_names.set(name, true); |
| | | }); |
| | | } |
| | | return var_names; |
| | |
| | | s = s.parent_scope; |
| | | } while (s && s !== this); |
| | | }); |
| | | prefix = prefix.replace(/(?:^[^a-z_$]|[^a-z0-9_$])/ig, "_"); |
| | | prefix = prefix.replace(/^[^a-z_$]|[^a-z0-9_$]/gi, "_"); |
| | | var name = prefix; |
| | | for (var i = 0; !all(scopes, function(scope) { |
| | | return !scope.var_names()[name]; |
| | | return !scope.var_names().has(name); |
| | | }); i++) name = prefix + "$" + i; |
| | | var sym = make_node(type, orig, { |
| | | name: name, |
| | |
| | | var def = this.def_variable(sym); |
| | | scopes.forEach(function(scope) { |
| | | scope.enclosed.push(def); |
| | | scope.var_names()[name] = true; |
| | | scope.var_names().set(name, true); |
| | | }); |
| | | return sym; |
| | | }); |
| | |
| | | right: prop.value, |
| | | })); |
| | | }); |
| | | defs.value = node.right; |
| | | defs_by_id[node.left.definition().id] = defs; |
| | | self.body.splice(self.body.indexOf(this.stack[1]) + 1, 0, make_node(AST_Var, node, { |
| | | definitions: decls, |
| | |
| | | descend(node, this); |
| | | var defs = new Dictionary(); |
| | | var var_defs = []; |
| | | var decl = node.clone(); |
| | | decl.value = node.name instanceof AST_SymbolConst ? make_node(AST_Number, node, { value: 0 }) : null; |
| | | var_defs.push(decl); |
| | | node.value.properties.forEach(function(prop) { |
| | | var_defs.push(make_node(AST_VarDef, node, { |
| | | name: make_sym(node.name.CTOR, node.name, prop.key), |
| | | value: prop.value, |
| | | })); |
| | | }); |
| | | defs.value = node.value; |
| | | defs_by_id[node.name.definition().id] = defs; |
| | | return List.splice(var_defs); |
| | | } |
| | |
| | | if (!(node.expression instanceof AST_SymbolRef)) return; |
| | | var defs = defs_by_id[node.expression.definition().id]; |
| | | if (!defs) return; |
| | | if (node.expression.fixed_value() !== defs.value) return; |
| | | var def = defs.get(node.get_property()); |
| | | var sym = make_node(AST_SymbolRef, node, { |
| | | name: def.name, |
| | |
| | | return sym; |
| | | } |
| | | if (node instanceof AST_SymbolRef) { |
| | | if (!(node.definition().id in defs_by_id)) return; |
| | | var defs = defs_by_id[node.definition().id]; |
| | | if (!defs) return; |
| | | if (node.fixed_value() !== defs.value) return; |
| | | return make_node(AST_Object, node, { properties: [] }); |
| | | } |
| | | })); |
| | |
| | | if (!(sym instanceof AST_Symbol)) return; |
| | | var def = sym.definition(); |
| | | if (def.assignments != count) return; |
| | | if (def.direct_access) return; |
| | | if (def.escaped.depth == 1) return; |
| | | if (def.references.length - def.replaced == count) return; |
| | | if (def.single_use) return; |
| | | if (top_retain(def)) return; |
| | | if (sym.fixed_value() !== right) return; |
| | | var fixed = sym.fixed || def.fixed; |
| | | if (fixed.direct_access) return; |
| | | if (fixed.escaped && fixed.escaped.depth == 1) return; |
| | | return right instanceof AST_Object |
| | | && right.properties.length > 0 |
| | | && all(right.properties, can_hoist_property) |
| | | && all(def.references, function(ref) { |
| | | return ref.fixed_value() === right; |
| | | }) |
| | | && can_drop_symbol(sym, compressor); |
| | | } |
| | | }); |
| | |
| | | return all(def.references, function(sym) { |
| | | return !(sym instanceof AST_SymbolRef); |
| | | }); |
| | | } |
| | | |
| | | function drop_returns(compressor, exp, ignore_name) { |
| | | if (!(exp instanceof AST_Lambda)) return; |
| | | var arrow = is_arrow(exp); |
| | | var async = is_async(exp); |
| | | var changed = false; |
| | | var drop_body = false; |
| | | if (arrow && compressor.option("arrows")) { |
| | | if (!exp.value) { |
| | | drop_body = true; |
| | | } else if (!async || is_primitive(compressor, exp.value)) { |
| | | var dropped = exp.value.drop_side_effect_free(compressor); |
| | | if (dropped !== exp.value) { |
| | | changed = true; |
| | | exp.value = dropped; |
| | | } |
| | | } |
| | | } else if (!is_generator(exp)) { |
| | | if (!ignore_name && exp.name) { |
| | | var def = exp.name.definition(); |
| | | drop_body = def.references.length == def.replaced; |
| | | } else { |
| | | drop_body = true; |
| | | } |
| | | } |
| | | if (drop_body) { |
| | | exp.process_expression(false, function(node) { |
| | | var value = node.value; |
| | | if (value) { |
| | | if (async && !is_primitive(compressor, value)) return node; |
| | | value = value.drop_side_effect_free(compressor, true); |
| | | } |
| | | changed = true; |
| | | if (!value) return make_node(AST_EmptyStatement, node); |
| | | return make_node(AST_SimpleStatement, node, { body: value }); |
| | | }); |
| | | scan_local_returns(exp, function(node) { |
| | | var value = node.value; |
| | | if (value) { |
| | | if (async && !is_primitive(compressor, value)) return; |
| | | var dropped = value.drop_side_effect_free(compressor); |
| | | if (dropped !== value) { |
| | | changed = true; |
| | | node.value = dropped; |
| | | } |
| | | } |
| | | }); |
| | | } |
| | | if (async && compressor.option("awaits")) { |
| | | if (drop_body) exp.process_expression("awaits", function(node) { |
| | | var body = node.body; |
| | | if (body instanceof AST_Await) { |
| | | if (is_primitive(compressor, body.expression)) { |
| | | changed = true; |
| | | body = body.expression.drop_side_effect_free(compressor, true); |
| | | if (!body) return make_node(AST_EmptyStatement, node); |
| | | node.body = body; |
| | | } |
| | | } else if (body instanceof AST_Sequence) { |
| | | var exprs = body.expressions; |
| | | for (var i = exprs.length; --i >= 0;) { |
| | | var tail = exprs[i]; |
| | | if (!(tail instanceof AST_Await)) break; |
| | | var value = tail.expression; |
| | | if (!is_primitive(compressor, value)) break; |
| | | changed = true; |
| | | if (exprs[i] = value.drop_side_effect_free(compressor)) break; |
| | | } |
| | | switch (i) { |
| | | case -1: |
| | | return make_node(AST_EmptyStatement, node); |
| | | case 0: |
| | | node.body = exprs[0]; |
| | | break; |
| | | default: |
| | | exprs.length = i + 1; |
| | | break; |
| | | } |
| | | } |
| | | return node; |
| | | }); |
| | | var abort = !drop_body && exp.name || arrow && exp.value && !is_primitive(compressor, exp.value); |
| | | var tw = new TreeWalker(function(node) { |
| | | if (abort) return true; |
| | | if (tw.parent() === exp && node.may_throw(compressor)) return abort = true; |
| | | if (node instanceof AST_Await) return abort = true; |
| | | if (node instanceof AST_ForAwaitOf) return abort = true; |
| | | if (node instanceof AST_Return) { |
| | | if (node.value && !is_primitive(compressor, node.value)) return abort = true; |
| | | return; |
| | | } |
| | | if (node instanceof AST_Scope && node !== exp) return true; |
| | | }); |
| | | exp.walk(tw); |
| | | if (!abort) { |
| | | var ctor; |
| | | switch (exp.CTOR) { |
| | | case AST_AsyncArrow: |
| | | ctor = AST_Arrow; |
| | | break; |
| | | case AST_AsyncFunction: |
| | | ctor = AST_Function; |
| | | break; |
| | | case AST_AsyncGeneratorFunction: |
| | | ctor = AST_GeneratorFunction; |
| | | break; |
| | | } |
| | | return make_node(ctor, exp, exp); |
| | | } |
| | | } |
| | | return changed && exp.clone(); |
| | | } |
| | | |
| | | // drop_side_effect_free() |
| | |
| | | return exp.drop_side_effect_free(compressor, first_in_statement); |
| | | } |
| | | function convert_spread(node) { |
| | | return node instanceof AST_Spread ? make_node(AST_Array, node, { |
| | | elements: [ node ] |
| | | }) : node; |
| | | return node instanceof AST_Spread ? make_node(AST_Array, node, { elements: [ node ] }) : node; |
| | | } |
| | | def(AST_Node, return_this); |
| | | def(AST_Accessor, return_null); |
| | |
| | | if (compressor.has_directive("use strict") && expr.is_constant()) return this; |
| | | } |
| | | if (left.has_side_effects(compressor)) return this; |
| | | var right = this.right; |
| | | if (!lazy_op[this.operator.slice(0, -1)]) { |
| | | this.write_only = true; |
| | | if (root_expr(left).is_constant_expression(compressor.find_parent(AST_Scope))) { |
| | | return right.drop_side_effect_free(compressor); |
| | | } |
| | | } |
| | | return this; |
| | | if (lazy_op[this.operator.slice(0, -1)]) return this; |
| | | this.write_only = true; |
| | | if (!root_expr(left).is_constant_expression(compressor.find_parent(AST_Scope))) return this; |
| | | return this.right.drop_side_effect_free(compressor); |
| | | }); |
| | | def(AST_Await, function(compressor) { |
| | | if (!compressor.option("awaits")) return this; |
| | | var exp = this.expression; |
| | | if (!is_primitive(compressor, exp)) return this; |
| | | if (exp instanceof AST_UnaryPrefix && exp.operator == "!") exp = exp.expression; |
| | | var dropped = exp.drop_side_effect_free(compressor); |
| | | if (dropped === exp) return this; |
| | | if (!dropped) { |
| | | dropped = make_node(AST_Number, exp, { value: 0 }); |
| | | } else if (!is_primitive(compressor, dropped)) { |
| | | dropped = dropped.negate(compressor); |
| | | } |
| | | var node = this.clone(); |
| | | node.expression = exp.drop_side_effect_free(compressor) || make_node(AST_Number, this, { value: 0 }); |
| | | node.expression = dropped; |
| | | return node; |
| | | }); |
| | | def(AST_Binary, function(compressor, first_in_statement) { |
| | |
| | | node.right = rhs.drop_side_effect_free(compressor); |
| | | } |
| | | if (op == "??") return node; |
| | | var negated = make_node(AST_Binary, this, { |
| | | operator: op == "&&" ? "||" : "&&", |
| | | left: left.negate(compressor, first_in_statement), |
| | | right: node.right, |
| | | }); |
| | | return first_in_statement ? best_of_statement(node, negated) : best_of_expression(node, negated); |
| | | var negated = node.clone(); |
| | | negated.operator = op == "&&" ? "||" : "&&"; |
| | | negated.left = left.negate(compressor, first_in_statement); |
| | | if (negated.operator == negated.right.operator) swap_chain(negated); |
| | | var best = first_in_statement ? best_of_statement : best_of_expression; |
| | | return op == "&&" ? best(node, negated) : best(negated, node); |
| | | } |
| | | var lhs = left.drop_side_effect_free(compressor, first_in_statement); |
| | | if (!lhs) return rhs; |
| | |
| | | if (!rhs) return lhs; |
| | | return make_sequence(this, [ lhs, rhs ]); |
| | | }); |
| | | function drop_returns(compressor, exp) { |
| | | var arrow = is_arrow(exp); |
| | | var async = is_async(exp); |
| | | var drop_body = false; |
| | | if (arrow && compressor.option("arrows")) { |
| | | if (!exp.value) { |
| | | drop_body = true; |
| | | } else if (!async || is_primitive(compressor, exp.value)) { |
| | | exp.value = exp.value.drop_side_effect_free(compressor); |
| | | } |
| | | } else if (exp instanceof AST_AsyncFunction || exp instanceof AST_Function) { |
| | | if (exp.name) { |
| | | var def = exp.name.definition(); |
| | | drop_body = def.references.length == def.replaced; |
| | | } else { |
| | | drop_body = true; |
| | | } |
| | | } |
| | | if (drop_body) { |
| | | exp.process_expression(false, function(node) { |
| | | var value = node.value; |
| | | if (value) { |
| | | if (async && !is_primitive(compressor, value)) return node; |
| | | value = value.drop_side_effect_free(compressor, true); |
| | | } |
| | | if (!value) return make_node(AST_EmptyStatement, node); |
| | | return make_node(AST_SimpleStatement, node, { body: value }); |
| | | }); |
| | | scan_local_returns(exp, function(node) { |
| | | var value = node.value; |
| | | if (value) { |
| | | if (async && !is_primitive(compressor, value)) return; |
| | | node.value = value.drop_side_effect_free(compressor); |
| | | } |
| | | }); |
| | | } |
| | | if (async && compressor.option("awaits")) { |
| | | if (drop_body) exp.process_expression("awaits", function(node) { |
| | | var body = node.body; |
| | | if (body instanceof AST_Await) { |
| | | if (is_primitive(compressor, body.expression)) { |
| | | body = body.expression.drop_side_effect_free(compressor, true); |
| | | if (!body) return make_node(AST_EmptyStatement, node); |
| | | node.body = body; |
| | | } |
| | | } else if (body instanceof AST_Sequence) { |
| | | var exprs = body.expressions; |
| | | for (var i = exprs.length; --i >= 0;) { |
| | | var tail = exprs[i]; |
| | | if (!(tail instanceof AST_Await)) break; |
| | | if (!is_primitive(compressor, tail.expression)) break; |
| | | if (exprs[i] = tail.expression.drop_side_effect_free(compressor)) break; |
| | | } |
| | | switch (i) { |
| | | case -1: |
| | | return make_node(AST_EmptyStatement, node); |
| | | case 0: |
| | | node.body = exprs[0]; |
| | | break; |
| | | default: |
| | | exprs.length = i + 1; |
| | | break; |
| | | } |
| | | } |
| | | return node; |
| | | }); |
| | | var abort = !drop_body && exp.name || arrow && exp.value && !is_primitive(compressor, exp.value); |
| | | var tw = new TreeWalker(function(node) { |
| | | if (abort) return true; |
| | | if (tw.parent() === exp && node.may_throw(compressor)) return abort = true; |
| | | if (node instanceof AST_Await) return abort = true; |
| | | if (node instanceof AST_ForAwaitOf) return abort = true; |
| | | if (node instanceof AST_Return) { |
| | | if (node.value && !is_primitive(compressor, node.value)) return abort = true; |
| | | return; |
| | | } |
| | | if (node instanceof AST_Scope && node !== exp) return true; |
| | | }); |
| | | exp.walk(tw); |
| | | if (!abort) { |
| | | var ctor; |
| | | switch (exp.CTOR) { |
| | | case AST_AsyncArrow: |
| | | ctor = AST_Arrow; |
| | | break; |
| | | case AST_AsyncFunction: |
| | | ctor = AST_Function; |
| | | break; |
| | | case AST_AsyncGeneratorFunction: |
| | | ctor = AST_GeneratorFunction; |
| | | break; |
| | | } |
| | | return make_node(ctor, exp, exp); |
| | | } |
| | | } |
| | | return drop_body && exp.clone(); |
| | | function assign_this_only(fn, compressor) { |
| | | fn.new = true; |
| | | var result = all(fn.body, function(stat) { |
| | | return !stat.has_side_effects(compressor); |
| | | }) && all(fn.argnames, function(argname) { |
| | | return !argname.match_symbol(return_false); |
| | | }) && !(fn.rest && fn.rest.match_symbol(return_false)); |
| | | fn.new = false; |
| | | return result; |
| | | } |
| | | def(AST_Call, function(compressor, first_in_statement) { |
| | | var self = this; |
| | |
| | | exprs = trim(exprs, compressor, first_in_statement, array_spread); |
| | | return exprs && make_sequence(self, exprs.map(convert_spread)); |
| | | } |
| | | if (!fn.contains_this()) self = make_node(AST_Call, self, self); |
| | | if (!fn.contains_this()) { |
| | | self = make_node(AST_Call, self, self); |
| | | self.expression = self.expression.clone(); |
| | | self.args = self.args.slice(); |
| | | } |
| | | } |
| | | } |
| | | self.call_only = true; |
| | | return self; |
| | | }); |
| | | function assign_this_only(fn, compressor) { |
| | | fn.new = true; |
| | | var result = all(fn.body, function(stat) { |
| | | return !stat.has_side_effects(compressor); |
| | | }) && all(fn.argnames, function(argname) { |
| | | return !argname.match_symbol(return_false); |
| | | }) && !(fn.rest && fn.rest.match_symbol(return_false)); |
| | | delete fn.new; |
| | | return result; |
| | | } |
| | | function drop_class(self, compressor, first_in_statement) { |
| | | def(AST_ClassExpression, function(compressor, first_in_statement) { |
| | | var self = this; |
| | | var exprs = [], values = []; |
| | | var props = self.properties; |
| | | for (var i = 0; i < props.length; i++) { |
| | |
| | | if (exprs) first_in_statement = false; |
| | | values = trim(values, compressor, first_in_statement); |
| | | if (!exprs) { |
| | | if (!base && !values) return null; |
| | | if (!base && !values && !self.name) return null; |
| | | exprs = []; |
| | | } |
| | | if (base) { |
| | | var node = to_class_expr(self, true); |
| | | if (base || self.name || !compressor.has_directive("use strict")) { |
| | | var node = to_class_expr(self); |
| | | if (!base) node.extends = null; |
| | | node.properties = []; |
| | | if (exprs.length) node.properties.push(make_node(AST_ClassMethod, self, { |
| | | key: make_sequence(self, exprs), |
| | | value: make_node(AST_Function, self, { |
| | | argnames: [], |
| | | body: [], |
| | | }).init_vars(node), |
| | | })); |
| | | exprs = [ node ]; |
| | | if (values) { |
| | | node.properties.push(make_node(AST_ClassField, self, { |
| | | static: true, |
| | | key: exprs.length ? make_sequence(self, exprs) : "c", |
| | | value: make_sequence(self, values), |
| | | })); |
| | | } else if (exprs.length) { |
| | | node.properties.push(make_node(AST_ClassMethod, self, { |
| | | key: make_sequence(self, exprs), |
| | | value: make_node(AST_Function, self, { |
| | | argnames: [], |
| | | body: [], |
| | | }).init_vars(node), |
| | | })); |
| | | } |
| | | return node; |
| | | } |
| | | if (values) exprs.push(make_node(AST_Call, self, { |
| | | expression: make_node(AST_Arrow, self, { |
| | |
| | | args: [], |
| | | })); |
| | | return make_sequence(self, exprs); |
| | | } |
| | | def(AST_ClassExpression, function(compressor, first_in_statement) { |
| | | var self = this; |
| | | var name = self.name; |
| | | if (name && name.fixed_value() !== self && name.definition().references.length > 0) return self; |
| | | return drop_class(self, compressor, first_in_statement); |
| | | }); |
| | | def(AST_Conditional, function(compressor) { |
| | | var consequent = this.consequent.drop_side_effect_free(compressor); |
| | |
| | | node = alternative ? make_node(AST_Binary, this, { |
| | | operator: "||", |
| | | left: this.condition, |
| | | right: alternative |
| | | right: alternative, |
| | | }) : this.condition.drop_side_effect_free(compressor); |
| | | } else if (!alternative) { |
| | | node = make_node(AST_Binary, this, { |
| | | operator: "&&", |
| | | left: this.condition, |
| | | right: consequent |
| | | right: consequent, |
| | | }); |
| | | } else { |
| | | node = this.clone(); |
| | | node.consequent = consequent; |
| | | node.alternative = alternative; |
| | | } |
| | | if (!compressor.option("ie")) return node; |
| | | if (!exprs) return node; |
| | | if (node) exprs.push(node); |
| | | return exprs.length == 0 ? null : make_sequence(this, exprs); |
| | | }); |
| | | def(AST_Constant, return_null); |
| | | def(AST_DefClass, function(compressor, first_in_statement) { |
| | | return drop_class(this, compressor, first_in_statement); |
| | | }); |
| | | def(AST_Dot, function(compressor, first_in_statement) { |
| | | var expr = this.expression; |
| | | if (!this.optional && expr.may_throw_on_access(compressor)) return this; |
| | | if (expr.may_throw_on_access(compressor)) return this; |
| | | return expr.drop_side_effect_free(compressor, first_in_statement); |
| | | }); |
| | | def(AST_Function, function(compressor) { |
| | |
| | | }); |
| | | var values = trim(exprs, compressor, first_in_statement, function(node, compressor, first_in_statement) { |
| | | var exp = node.expression; |
| | | return spread_side_effects(exp) ? node : exp.drop_side_effect_free(compressor, first_in_statement); |
| | | return exp.safe_to_spread() ? exp.drop_side_effect_free(compressor, first_in_statement) : node; |
| | | }); |
| | | if (!values) return null; |
| | | if (values === exprs && !all(values, function(node) { |
| | | return !(node instanceof AST_Spread); |
| | | })) return this; |
| | | return make_sequence(this, values.map(function(node) { |
| | | return node instanceof AST_Spread ? make_node(AST_Object, node, { |
| | | properties: [ node ], |
| | | }) : node; |
| | | return node instanceof AST_Spread ? make_node(AST_Object, node, { properties: [ node ] }) : node; |
| | | })); |
| | | }); |
| | | def(AST_ObjectIdentity, return_null); |
| | |
| | | if (compressor.option("awaits") && end > 0 && last instanceof AST_Await && last.expression.is_constant()) { |
| | | expressions = expressions.slice(0, -1); |
| | | end--; |
| | | last.expression = expressions[end]; |
| | | var expr = expressions[end]; |
| | | last.expression = is_primitive(compressor, expr) ? expr : expr.negate(compressor); |
| | | expressions[end] = last; |
| | | } |
| | | var assign, cond, lhs; |
| | |
| | | }); |
| | | def(AST_Sub, function(compressor, first_in_statement) { |
| | | var expr = this.expression; |
| | | if (expr.may_throw_on_access(compressor)) return this; |
| | | var prop = this.property; |
| | | if (expr.may_throw_on_access(compressor)) { |
| | | if (!this.optional) return this; |
| | | if (prop.has_side_effects(compressor)) { |
| | | prop = prop.drop_side_effect_free(compressor); |
| | | if (!prop) return expr.drop_side_effect_free(compressor, first_in_statement); |
| | | var node = this.clone(); |
| | | node.property = prop; |
| | | return node; |
| | | } |
| | | } |
| | | expr = expr.drop_side_effect_free(compressor, first_in_statement); |
| | | if (!expr) return prop.drop_side_effect_free(compressor, first_in_statement); |
| | | prop = prop.drop_side_effect_free(compressor); |
| | |
| | | }); |
| | | |
| | | function mark_locally_defined(condition, consequent, alternative) { |
| | | if (condition instanceof AST_Sequence) condition = condition.tail_node(); |
| | | if (!(condition instanceof AST_Binary)) return; |
| | | if (!(condition.left instanceof AST_String)) { |
| | | switch (condition.operator) { |
| | |
| | | return; |
| | | } |
| | | if (!body) return; |
| | | var abort = false; |
| | | var def = sym.definition(); |
| | | var tw = new TreeWalker(function(node) { |
| | | if (node instanceof AST_Scope) { |
| | | var parent = tw.parent(); |
| | | if (parent instanceof AST_Call && parent.expression === node) return; |
| | | var fn; |
| | | var refs = []; |
| | | var scanned = []; |
| | | var tw = new TreeWalker(function(node, descend) { |
| | | if (abort) return true; |
| | | if (node instanceof AST_Assign) { |
| | | var ref = node.left; |
| | | if (!(ref instanceof AST_SymbolRef && ref.definition() === def)) return; |
| | | node.right.walk(tw); |
| | | switch (node.operator) { |
| | | case "=": |
| | | case "&&=": |
| | | abort = true; |
| | | } |
| | | return true; |
| | | } |
| | | if (node instanceof AST_SymbolRef && node.definition() === def) node.defined = true; |
| | | if (node instanceof AST_Call) { |
| | | descend(); |
| | | fn = node.expression.tail_node(); |
| | | var save; |
| | | if (fn instanceof AST_SymbolRef) { |
| | | fn = fn.fixed_value(); |
| | | save = refs.length; |
| | | } |
| | | if (!(fn instanceof AST_Lambda)) { |
| | | abort = true; |
| | | } else if (push_uniq(scanned, fn)) { |
| | | fn.walk(tw); |
| | | } |
| | | if (save >= 0) refs.length = save; |
| | | return true; |
| | | } |
| | | if (node instanceof AST_DWLoop) { |
| | | var save = refs.length; |
| | | descend(); |
| | | if (abort) refs.length = save; |
| | | return true; |
| | | } |
| | | if (node instanceof AST_For) { |
| | | if (node.init) node.init.walk(tw); |
| | | var save = refs.length; |
| | | if (node.condition) node.condition.walk(tw); |
| | | node.body.walk(tw); |
| | | if (node.step) node.step.walk(tw); |
| | | if (abort) refs.length = save; |
| | | return true; |
| | | } |
| | | if (node instanceof AST_ForEnumeration) { |
| | | node.object.walk(tw); |
| | | var save = refs.length; |
| | | node.init.walk(tw); |
| | | node.body.walk(tw); |
| | | if (abort) refs.length = save; |
| | | return true; |
| | | } |
| | | if (node instanceof AST_Scope) { |
| | | if (node === fn) return; |
| | | return true; |
| | | } |
| | | if (node instanceof AST_SymbolRef) { |
| | | if (node.definition() === def) refs.push(node); |
| | | return true; |
| | | } |
| | | }); |
| | | body.walk(tw); |
| | | refs.forEach(function(ref) { |
| | | ref.defined = true; |
| | | }); |
| | | |
| | | function negate(node) { |
| | | if (!(node instanceof AST_Binary)) return; |
| | |
| | | var cond = fuzzy_eval(compressor, self.condition); |
| | | if (!cond) { |
| | | AST_Node.warn("Condition always false [{file}:{line},{col}]", self.condition.start); |
| | | var body = [ make_node(AST_SimpleStatement, self.condition, { body: self.condition }) ]; |
| | | var body = [ |
| | | make_node(AST_SimpleStatement, self.condition, { body: self.condition }).transform(compressor), |
| | | ]; |
| | | extract_declarations_from_unreachable_code(compressor, self.body, body); |
| | | if (self.alternative) body.push(self.alternative); |
| | | return make_node(AST_BlockStatement, self, { body: body }).optimize(compressor); |
| | | } else if (!(cond instanceof AST_Node)) { |
| | | AST_Node.warn("Condition always true [{file}:{line},{col}]", self.condition.start); |
| | | var body = [ |
| | | make_node(AST_SimpleStatement, self.condition, { body: self.condition }), |
| | | make_node(AST_SimpleStatement, self.condition, { body: self.condition }).transform(compressor), |
| | | self.body, |
| | | ]; |
| | | if (self.alternative) extract_declarations_from_unreachable_code(compressor, self.alternative, body); |
| | |
| | | // 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; |
| | | self.body = self.alternative; |
| | | self.alternative = is_empty(tmp) ? null : tmp; |
| | | } |
| | | var body = [], var_defs = [], refs = []; |
| | | var body_exprs = sequencesize(self.body, body, var_defs, refs); |
| | | var alt_exprs = sequencesize(self.alternative, body, var_defs, refs); |
| | | if (body_exprs && alt_exprs) { |
| | | var body_defuns = []; |
| | | var body_var_defs = []; |
| | | var body_refs = []; |
| | | var body_exprs = sequencesize(self.body, body_defuns, body_var_defs, body_refs); |
| | | var alt_defuns = []; |
| | | var alt_var_defs = []; |
| | | var alt_refs = []; |
| | | var alt_exprs = sequencesize(self.alternative, alt_defuns, alt_var_defs, alt_refs); |
| | | if (body_exprs instanceof AST_BlockStatement || alt_exprs instanceof AST_BlockStatement) { |
| | | var body = [], var_defs = []; |
| | | if (body_exprs) { |
| | | [].push.apply(body, body_defuns); |
| | | [].push.apply(var_defs, body_var_defs); |
| | | if (body_exprs instanceof AST_BlockStatement) { |
| | | self.body = body_exprs; |
| | | } else if (body_exprs.length == 0) { |
| | | self.body = make_node(AST_EmptyStatement, self.body); |
| | | } else { |
| | | self.body = make_node(AST_SimpleStatement, self.body, { |
| | | body: make_sequence(self.body, body_exprs), |
| | | }); |
| | | } |
| | | body_refs.forEach(process_to_assign); |
| | | } |
| | | if (alt_exprs) { |
| | | [].push.apply(body, alt_defuns); |
| | | [].push.apply(var_defs, alt_var_defs); |
| | | if (alt_exprs instanceof AST_BlockStatement) { |
| | | self.alternative = alt_exprs; |
| | | } else if (alt_exprs.length == 0) { |
| | | self.alternative = null; |
| | | } else { |
| | | self.alternative = make_node(AST_SimpleStatement, self.alternative, { |
| | | body: make_sequence(self.alternative, alt_exprs), |
| | | }); |
| | | } |
| | | alt_refs.forEach(process_to_assign); |
| | | } |
| | | if (var_defs.length > 0) body.push(make_node(AST_Var, self, { definitions: var_defs })); |
| | | if (body.length > 0) { |
| | | body.push(self); |
| | | return make_node(AST_BlockStatement, self, { body: body }).optimize(compressor); |
| | | } |
| | | } else if (body_exprs && alt_exprs) { |
| | | var body = body_defuns.concat(alt_defuns); |
| | | if (body_var_defs.length > 0 || alt_var_defs.length > 0) body.push(make_node(AST_Var, self, { |
| | | definitions: body_var_defs.concat(alt_var_defs), |
| | | })); |
| | | if (body_exprs.length == 0) { |
| | | body.push(make_node(AST_SimpleStatement, self.condition, { |
| | | body: alt_exprs.length > 0 ? make_node(AST_Binary, self, { |
| | | operator : "||", |
| | | left : self.condition, |
| | | right : make_sequence(self.alternative, alt_exprs) |
| | | }).transform(compressor) : self.condition.clone() |
| | | operator: "||", |
| | | left: self.condition, |
| | | right: make_sequence(self.alternative, alt_exprs), |
| | | }).transform(compressor) : self.condition.clone(), |
| | | }).optimize(compressor)); |
| | | } else if (alt_exprs.length == 0) { |
| | | if (self_condition_length === negated_length && !negated_is_best |
| | |
| | | } |
| | | body.push(make_node(AST_SimpleStatement, self, { |
| | | body: make_node(AST_Binary, self, { |
| | | operator : negated_is_best ? "||" : "&&", |
| | | left : negated_is_best ? negated : self.condition, |
| | | right : make_sequence(self.body, body_exprs) |
| | | }).transform(compressor) |
| | | operator: negated_is_best ? "||" : "&&", |
| | | left: negated_is_best ? negated : self.condition, |
| | | right: make_sequence(self.body, body_exprs), |
| | | }).transform(compressor), |
| | | }).optimize(compressor)); |
| | | } else { |
| | | body.push(make_node(AST_SimpleStatement, self, { |
| | | body: make_node(AST_Conditional, self, { |
| | | condition : self.condition, |
| | | consequent : make_sequence(self.body, body_exprs), |
| | | alternative : make_sequence(self.alternative, alt_exprs) |
| | | }) |
| | | condition: self.condition, |
| | | consequent: make_sequence(self.body, body_exprs), |
| | | alternative: make_sequence(self.alternative, alt_exprs), |
| | | }), |
| | | }).optimize(compressor)); |
| | | } |
| | | refs.forEach(function(ref) { |
| | | ref.definition().references.push(ref); |
| | | }); |
| | | return make_node(AST_BlockStatement, self, { |
| | | body: body |
| | | }).optimize(compressor); |
| | | body_refs.forEach(process_to_assign); |
| | | alt_refs.forEach(process_to_assign); |
| | | return make_node(AST_BlockStatement, self, { body: body }).optimize(compressor); |
| | | } |
| | | if (is_empty(self.body)) { |
| | | self = make_node(AST_If, self, { |
| | | condition: negated, |
| | | body: self.alternative, |
| | | alternative: null |
| | | }); |
| | | } |
| | | if (self.body instanceof AST_Exit |
| | | && self.alternative instanceof AST_Exit |
| | | && self.body.TYPE == self.alternative.TYPE) { |
| | | if (is_empty(self.body)) self = make_node(AST_If, self, { |
| | | condition: negated, |
| | | body: self.alternative, |
| | | alternative: null, |
| | | }); |
| | | if (self.alternative instanceof AST_Exit && self.body.TYPE == self.alternative.TYPE) { |
| | | var exit = 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).transform(compressor), |
| | | alternative : self.alternative.value || make_node(AST_Undefined, self.alternative).transform(compressor) |
| | | }) |
| | | condition: self.condition, |
| | | consequent: self.body.value || make_node(AST_Undefined, self.body).transform(compressor), |
| | | alternative: self.alternative.value |
| | | || make_node(AST_Undefined, self.alternative).transform(compressor), |
| | | }), |
| | | }); |
| | | if (exit instanceof AST_Return) { |
| | | exit.in_bool = self.body.in_bool || self.alternative.in_bool; |
| | | } |
| | | if (exit instanceof AST_Return) exit.in_bool = self.body.in_bool || self.alternative.in_bool; |
| | | return exit; |
| | | } |
| | | if (self.body instanceof AST_If |
| | | && !self.body.alternative |
| | | && !self.alternative) { |
| | | 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 |
| | | right: self.body.condition, |
| | | }), |
| | | body: self.body.body, |
| | | alternative: null |
| | | 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.body) && 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 make_node(AST_BlockStatement, self, { body: [ self, body ] }).optimize(compressor); |
| | | } |
| | | if (compressor.option("typeofs")) mark_locally_defined(self.condition, self.body, self.alternative); |
| | | return self; |
| | |
| | | var exprs = []; |
| | | for (var i = 0; i < stat.body.length; i++) { |
| | | var line = stat.body[i]; |
| | | if (line instanceof AST_EmptyStatement) continue; |
| | | if (line instanceof AST_Exit) { |
| | | if (i == 0) return; |
| | | if (exprs.length > 0) { |
| | | line = line.clone(); |
| | | exprs.push(line.value || make_node(AST_Undefined, line).transform(compressor)); |
| | | line.value = make_sequence(stat, exprs); |
| | | } |
| | | var block = stat.clone(); |
| | | block.body = block.body.slice(i + 1); |
| | | block.body.unshift(line); |
| | | return block; |
| | | } |
| | | if (line instanceof AST_LambdaDefinition) { |
| | | defuns.push(line); |
| | | } else if (line instanceof AST_EmptyStatement) { |
| | | continue; |
| | | } else if (line instanceof AST_SimpleStatement) { |
| | | if (!compressor.option("sequences") && exprs.length > 0) return; |
| | | exprs.push(line.body); |
| | |
| | | left: var_def.name.convert_symbol(AST_SymbolRef, function(ref) { |
| | | refs.push(ref); |
| | | }), |
| | | right: var_def.value |
| | | right: var_def.value, |
| | | })); |
| | | } |
| | | } |
| | |
| | | left: self.expression, |
| | | right: exp, |
| | | }), |
| | | body: make_node(AST_BlockStatement, self, { |
| | | body: statements, |
| | | }), |
| | | body: make_node(AST_BlockStatement, self, { body: statements }), |
| | | alternative: null, |
| | | }).optimize(compressor); |
| | | if (exp) statements.unshift(make_node(AST_SimpleStatement, exp, { |
| | | body: exp, |
| | | })); |
| | | statements.unshift(make_node(AST_SimpleStatement, self.expression, { |
| | | body:self.expression, |
| | | })); |
| | | return make_node(AST_BlockStatement, self, { |
| | | body: statements, |
| | | }).optimize(compressor); |
| | | if (exp) statements.unshift(make_node(AST_SimpleStatement, exp, { body: exp })); |
| | | statements.unshift(make_node(AST_SimpleStatement, self.expression, { body: self.expression })); |
| | | return make_node(AST_BlockStatement, self, { body: statements }).optimize(compressor); |
| | | case 2: |
| | | if (!member(default_branch, body) || !no_break(body[1])) break; |
| | | var statements = body[0].body.slice(); |
| | |
| | | left: self.expression, |
| | | right: body[0].expression, |
| | | }), |
| | | body: make_node(AST_BlockStatement, body[0], { |
| | | body: statements, |
| | | }), |
| | | body: make_node(AST_BlockStatement, body[0], { body: statements }), |
| | | alternative: exclusive && alternative || null, |
| | | }); |
| | | if (!exclusive && alternative) node = make_node(AST_BlockStatement, self, { |
| | | body: [ node, alternative ], |
| | | }); |
| | | if (!exclusive && alternative) node = make_node(AST_BlockStatement, self, { body: [ node, alternative ] }); |
| | | return node.optimize(compressor); |
| | | } |
| | | return self; |
| | |
| | | if (self.bfinally) { |
| | | body.push(make_node(AST_BlockStatement, self.bfinally, self.bfinally).optimize(compressor)); |
| | | } |
| | | return make_node(AST_BlockStatement, self, { |
| | | body: body |
| | | }).optimize(compressor); |
| | | return make_node(AST_BlockStatement, self, { body: body }).optimize(compressor); |
| | | } |
| | | if (self.bfinally && has_declarations_only(self.bfinally)) { |
| | | var body = make_node(AST_BlockStatement, self.bfinally, self.bfinally).optimize(compressor); |
| | | body = self.body.concat(body); |
| | | if (!self.bcatch) return make_node(AST_BlockStatement, self, { |
| | | body: body |
| | | }).optimize(compressor); |
| | | if (!self.bcatch) return make_node(AST_BlockStatement, self, { body: body }).optimize(compressor); |
| | | self.body = body; |
| | | self.bfinally = null; |
| | | } |
| | |
| | | right: value, |
| | | }); |
| | | a.push(assign); |
| | | name.fixed = function() { |
| | | var fixed = function() { |
| | | return assign.right; |
| | | }; |
| | | name.fixed.assigns = [ assign ]; |
| | | fixed.assigns = [ assign ]; |
| | | fixed.direct_access = def.direct_access; |
| | | fixed.escaped = def.escaped; |
| | | name.fixed = fixed; |
| | | def.references.forEach(function(ref) { |
| | | var assigns = ref.fixed && ref.fixed.assigns; |
| | | if (assigns && assigns[0] === defn) assigns[0] = assign; |
| | | if (!ref.fixed) return; |
| | | var assigns = ref.fixed.assigns; |
| | | if (!assigns) return; |
| | | if (assigns[0] !== defn) return; |
| | | if (assigns.length > 1 || ref.fixed.to_binary || ref.fixed.to_prefix) { |
| | | assigns[0] = assign; |
| | | } else { |
| | | ref.fixed = fixed; |
| | | if (def.fixed === ref.fixed) def.fixed = fixed; |
| | | } |
| | | }); |
| | | def.references.push(name); |
| | | } |
| | | def.assignments++; |
| | | def.eliminated++; |
| | | def.single_use = false; |
| | | return a; |
| | |
| | | var scope = def.scope.resolve(); |
| | | for (var s = def.scope; s !== scope;) { |
| | | s = s.parent_scope; |
| | | if (s.var_names()[def.name]) return true; |
| | | if (s.var_names().has(def.name)) return true; |
| | | } |
| | | } |
| | | |
| | |
| | | def.scope = scope; |
| | | scope.variables.set(def.name, def); |
| | | scope.enclosed.push(def); |
| | | scope.var_names()[def.name] = true; |
| | | scope.var_names().set(def.name, true); |
| | | }), |
| | | value: defn.value, |
| | | }); |
| | |
| | | if (argname instanceof AST_DestructuredObject) { |
| | | return argname.properties.length == 0 && !argname.rest && arg && !arg.may_throw_on_access(compressor); |
| | | } |
| | | return argname.__unused; |
| | | return argname.unused; |
| | | } : return_false; |
| | | var side_effects = []; |
| | | for (var i = 0; i < args.length; i++) { |
| | |
| | | if (drop_defaults && argname instanceof AST_DefaultValue && args[i].is_defined(compressor)) { |
| | | argnames[i] = argname = argname.name; |
| | | } |
| | | if (!argname || "__unused" in argname) { |
| | | if (!argname || argname.unused !== undefined) { |
| | | var node = args[i].drop_side_effect_free(compressor); |
| | | if (drop_fargs(argname)) { |
| | | if (argname) argnames.splice(i, 1); |
| | |
| | | args[pos++] = make_sequence(call, side_effects); |
| | | side_effects = []; |
| | | } else { |
| | | args[pos++] = make_node(AST_Number, args[i], { |
| | | value: 0 |
| | | }); |
| | | args[pos++] = make_node(AST_Number, args[i], { value: 0 }); |
| | | continue; |
| | | } |
| | | } |
| | |
| | | operator: "void", |
| | | expression: arg, |
| | | }) : arg); |
| | | } |
| | | |
| | | function avoid_await_yield(parent_scope) { |
| | | var avoid = []; |
| | | if (is_async(parent_scope)) avoid.push("await"); |
| | | if (is_generator(parent_scope)) avoid.push("yield"); |
| | | return avoid.length && makePredicate(avoid); |
| | | } |
| | | |
| | | function safe_from_await_yield(fn, avoid) { |
| | | if (!avoid) return true; |
| | | var safe = true; |
| | | var tw = new TreeWalker(function(node) { |
| | | if (!safe) return true; |
| | | if (node instanceof AST_Scope) { |
| | | if (node === fn) return; |
| | | if (is_arrow(node)) { |
| | | for (var i = 0; safe && i < node.argnames.length; i++) node.argnames[i].walk(tw); |
| | | } else if (node instanceof AST_LambdaDefinition && avoid[node.name.name]) { |
| | | safe = false; |
| | | } |
| | | return true; |
| | | } |
| | | if (node instanceof AST_Symbol && avoid[node.name] && node !== fn.name) safe = false; |
| | | }); |
| | | fn.walk(tw); |
| | | return safe; |
| | | } |
| | | |
| | | OPT(AST_Call, function(self, compressor) { |
| | |
| | | expression: exp.expression, |
| | | property: "call", |
| | | }), |
| | | args: args |
| | | args: args, |
| | | }).optimize(compressor); |
| | | } |
| | | break; |
| | |
| | | self.args[0], |
| | | make_node(AST_Call, self, { |
| | | expression: exp.expression, |
| | | args: self.args.slice(1) |
| | | }) |
| | | args: self.args.slice(1), |
| | | }), |
| | | ]) : make_node(AST_Call, self, { |
| | | expression: exp.expression, |
| | | args: [] |
| | | args: [], |
| | | })).optimize(compressor); |
| | | } |
| | | break; |
| | |
| | | if (argname instanceof AST_DefaultValue) { |
| | | if (!has_default) has_default = 1; |
| | | var arg = has_default == 1 && self.args[index]; |
| | | if (arg && !is_undefined(arg)) has_default = 2; |
| | | if (has_arg_refs(argname.value)) return false; |
| | | if (!is_undefined(arg)) has_default = 2; |
| | | if (has_arg_refs(fn, argname.value)) return false; |
| | | argname = argname.name; |
| | | } |
| | | if (argname instanceof AST_Destructured) { |
| | | has_destructured = true; |
| | | if (has_arg_refs(argname)) return false; |
| | | if (has_arg_refs(fn, argname)) return false; |
| | | } |
| | | return true; |
| | | }) && !(fn.rest instanceof AST_Destructured && has_arg_refs(fn.rest)); |
| | | }) && !(fn.rest instanceof AST_Destructured && has_arg_refs(fn, fn.rest)); |
| | | var can_inline = can_drop && compressor.option("inline") && !self.is_expr_pure(compressor); |
| | | if (can_inline && stat instanceof AST_Return) { |
| | | var value = stat.value; |
| | | if (exp === fn && !fn.name && (!value || value.is_constant_expression()) && safe_from_await_yield(fn)) { |
| | | if (exp === fn |
| | | && !fn.name |
| | | && (!value || value.is_constant_expression()) |
| | | && safe_from_await_yield(fn, avoid_await_yield(compressor.find_parent(AST_Scope)))) { |
| | | return make_sequence(self, convert_args(value)).optimize(compressor); |
| | | } |
| | | } |
| | | if (is_func) { |
| | | if (is_func && !fn.contains_this()) { |
| | | var def, value, var_assigned = false; |
| | | if (can_inline |
| | | && !fn.uses_arguments |
| | |
| | | && !(fn.name && fn instanceof AST_LambdaExpression) |
| | | && (exp === fn || !recursive_ref(compressor, def = exp.definition(), fn) |
| | | && fn.is_constant_expression(find_scope(compressor))) |
| | | && !has_spread |
| | | && (value = can_flatten_body(stat)) |
| | | && !fn.contains_this()) { |
| | | && (value = can_flatten_body(stat))) { |
| | | var replacing = exp === fn || def.single_use && def.references.length - def.replaced == 1; |
| | | if (can_substitute_directly()) { |
| | | var args = self.args.slice(); |
| | | var refs = []; |
| | | args.push(value.clone(true).transform(new TreeTransformer(function(node) { |
| | | var retValue = value.clone(true).transform(new TreeTransformer(function(node) { |
| | | if (node instanceof AST_SymbolRef) { |
| | | var def = node.definition(); |
| | | if (fn.variables.get(node.name) !== def) { |
| | |
| | | var parent = this.parent(); |
| | | return parent ? maintain_this_binding(compressor, parent, node, arg) : arg; |
| | | } |
| | | }))); |
| | | })); |
| | | var save_inlined = fn.inlined; |
| | | if (exp !== fn) fn.inlined = true; |
| | | var node = make_sequence(self, args.filter(function(arg) { |
| | | return arg; |
| | | })).optimize(compressor); |
| | | var exprs = []; |
| | | args.forEach(function(arg) { |
| | | if (!arg) return; |
| | | arg = arg.clone(true); |
| | | arg.walk(new TreeWalker(function(node) { |
| | | if (node instanceof AST_SymbolRef) refs.push(node); |
| | | })); |
| | | exprs.push(arg); |
| | | }, []); |
| | | exprs.push(retValue); |
| | | var node = make_sequence(self, exprs).optimize(compressor); |
| | | fn.inlined = save_inlined; |
| | | node = maintain_this_binding(compressor, parent, current, node); |
| | | if (replacing || best_of_expression(node, self) === node) { |
| | |
| | | }); |
| | | return node; |
| | | } else if (!node.has_side_effects(compressor)) { |
| | | self.drop_side_effect_free = return_null; |
| | | self.drop_side_effect_free = function(compressor, first_in_statement) { |
| | | var self = this; |
| | | var exprs = self.args.slice(); |
| | | exprs.unshift(self.expression); |
| | | return make_sequence(self, exprs).drop_side_effect_free(compressor, first_in_statement); |
| | | }; |
| | | } |
| | | } |
| | | var arg_used, insert, in_loop, scope; |
| | |
| | | && all(fn.body, is_empty) |
| | | && (fn === exp ? fn_name_unused(fn, compressor) : !has_default && !has_destructured && !fn.rest) |
| | | && !(is_arrow(fn) && fn.value) |
| | | && safe_from_await_yield(fn)) { |
| | | && safe_from_await_yield(fn, avoid_await_yield(compressor.find_parent(AST_Scope)))) { |
| | | return make_sequence(self, convert_args()).optimize(compressor); |
| | | } |
| | | } |
| | |
| | | return self.negate(compressor, true); |
| | | } |
| | | return try_evaluate(compressor, self); |
| | | |
| | | function has_arg_refs(node) { |
| | | var found = false; |
| | | node.walk(new TreeWalker(function(node) { |
| | | if (found) return true; |
| | | if (node instanceof AST_SymbolRef && fn.variables.get(node.name) === node.definition()) { |
| | | return found = true; |
| | | } |
| | | })); |
| | | return found; |
| | | } |
| | | |
| | | function make_void_lhs(orig) { |
| | | return make_node(AST_Dot, orig, { |
| | |
| | | return args; |
| | | } |
| | | |
| | | function avoid_await_yield() { |
| | | var avoid = []; |
| | | var parent_scope = scope || compressor.find_parent(AST_Scope); |
| | | if (is_async(parent_scope)) avoid.push("await"); |
| | | if (is_generator(parent_scope)) avoid.push("yield"); |
| | | return avoid.length && makePredicate(avoid); |
| | | } |
| | | |
| | | function safe_from_await_yield(node) { |
| | | var avoid = avoid_await_yield(); |
| | | if (!avoid) return true; |
| | | var safe = true; |
| | | var tw = new TreeWalker(function(node) { |
| | | if (!safe) return true; |
| | | if (node instanceof AST_Scope) { |
| | | if (node === fn) return; |
| | | if (is_arrow(node)) { |
| | | for (var i = 0; safe && i < node.argnames.length; i++) node.argnames[i].walk(tw); |
| | | } else if (node instanceof AST_LambdaDefinition && avoid[node.name.name]) { |
| | | safe = false; |
| | | } |
| | | return true; |
| | | } |
| | | if (node instanceof AST_Symbol && avoid[node.name] && node !== fn.name) safe = false; |
| | | }); |
| | | node.walk(tw); |
| | | return safe; |
| | | } |
| | | |
| | | function noop_value() { |
| | | return self.call_only ? make_node(AST_Number, self, { value: 0 }) : make_node(AST_Undefined, self); |
| | | } |
| | |
| | | for (var i = 0; i < len; i++) { |
| | | var line = fn.body[i]; |
| | | if (line instanceof AST_Var) { |
| | | var assigned = var_assigned || !declarations_only(line); |
| | | if (assigned) { |
| | | if (var_assigned) { |
| | | if (!stat) continue; |
| | | if (!(stat instanceof AST_SimpleStatement)) return false; |
| | | if (!declarations_only(line)) stat = null; |
| | | } else if (!declarations_only(line)) { |
| | | if (stat && !(stat instanceof AST_SimpleStatement)) return false; |
| | | stat = null; |
| | | var_assigned = true; |
| | | if (stat) return false; |
| | | } |
| | | } else if (line instanceof AST_AsyncDefun |
| | | || line instanceof AST_Defun |
| | |
| | | } |
| | | |
| | | function can_substitute_directly() { |
| | | if (has_default || has_destructured || var_assigned || fn.rest) return; |
| | | if (has_default || has_destructured || has_spread || var_assigned || fn.rest) return; |
| | | if (compressor.option("inline") < 2 && fn.argnames.length) return; |
| | | if (!fn.variables.all(function(def) { |
| | | return def.references.length - def.replaced < 2 && def.orig[0] instanceof AST_SymbolFunarg; |
| | | })) return; |
| | | var scope = compressor.find_parent(AST_Scope); |
| | | var abort = false; |
| | | var avoid = avoid_await_yield(); |
| | | var avoid = avoid_await_yield(scope); |
| | | var begin; |
| | | var in_order = []; |
| | | var side_effects = false; |
| | |
| | | while (end-- > begin && fn.argnames[end] === in_order.pop()); |
| | | end++; |
| | | } |
| | | var scope = side_effects && !in_order && compressor.find_parent(AST_Scope); |
| | | return end <= begin || all(self.args.slice(begin, end), scope ? function(funarg) { |
| | | return end <= begin || all(self.args.slice(begin, end), side_effects && !in_order ? function(funarg) { |
| | | return funarg.is_constant_expression(scope); |
| | | } : function(funarg) { |
| | | return !funarg.has_side_effects(compressor); |
| | |
| | | } |
| | | |
| | | function var_exists(defined, name) { |
| | | return defined[name] || identifier_atom[name] || scope.var_names()[name]; |
| | | return defined.has(name) || identifier_atom[name] || scope.var_names().has(name); |
| | | } |
| | | |
| | | function can_inject_args(defined, used, safe_to_inject) { |
| | | function can_inject_args(defined, safe_to_inject) { |
| | | var abort = false; |
| | | fn.each_argname(function(arg) { |
| | | if (abort) return; |
| | | if (arg.__unused) return; |
| | | if (arg.unused) return; |
| | | if (!safe_to_inject || var_exists(defined, arg.name)) return abort = true; |
| | | used[arg.name] = true; |
| | | arg_used.set(arg.name, true); |
| | | if (in_loop) in_loop.push(arg.definition()); |
| | | }); |
| | | return !abort; |
| | | } |
| | | |
| | | function can_inject_vars(defined, used, safe_to_inject) { |
| | | function can_inject_vars(defined, safe_to_inject) { |
| | | for (var i = 0; i < fn.body.length; i++) { |
| | | var stat = fn.body[i]; |
| | | if (stat instanceof AST_LambdaDefinition) { |
| | | if (!safe_to_inject || var_exists(used, stat.name.name)) return false; |
| | | var name = stat.name; |
| | | if (!safe_to_inject) return false; |
| | | if (arg_used.has(name.name)) return false; |
| | | if (var_exists(defined, name.name)) return false; |
| | | if (!all(stat.enclosed, function(def) { |
| | | return def.scope === stat || !defined[def.name]; |
| | | return def.scope === scope || def.scope === stat || !defined.has(def.name); |
| | | })) return false; |
| | | if (in_loop) in_loop.push(stat.name.definition()); |
| | | if (in_loop) in_loop.push(name.definition()); |
| | | continue; |
| | | } |
| | | if (!(stat instanceof AST_Var)) continue; |
| | |
| | | } |
| | | |
| | | function can_inject_symbols() { |
| | | var defined = Object.create(null); |
| | | var defined = new Dictionary(); |
| | | var level = 0, child; |
| | | scope = current; |
| | | do { |
| | | if (scope.variables) scope.variables.each(function(def) { |
| | | defined[def.name] = true; |
| | | defined.set(def.name, true); |
| | | }); |
| | | child = scope; |
| | | scope = compressor.parent(level++); |
| | |
| | | if (scope.init === child) continue; |
| | | if (scope.object === child) continue; |
| | | in_loop = []; |
| | | } else if (scope instanceof AST_SymbolRef) { |
| | | if (scope.fixed_value() instanceof AST_Scope) return false; |
| | | } |
| | | } while (!(scope instanceof AST_Scope)); |
| | | insert = scope.body.indexOf(child) + 1; |
| | | if (!insert) return false; |
| | | if (!safe_from_await_yield(fn)) return false; |
| | | var safe_to_inject = exp !== fn || fn.parent_scope.resolve() === scope; |
| | | if (!safe_from_await_yield(fn, avoid_await_yield(scope))) return false; |
| | | var safe_to_inject = (exp !== fn || fn.parent_scope.resolve() === scope) && !scope.pinned(); |
| | | if (scope instanceof AST_Toplevel) { |
| | | if (compressor.toplevel.vars) { |
| | | defined["arguments"] = true; |
| | | defined.set("arguments", true); |
| | | } else { |
| | | safe_to_inject = false; |
| | | } |
| | | } |
| | | arg_used = new Dictionary(); |
| | | var inline = compressor.option("inline"); |
| | | arg_used = Object.create(defined); |
| | | if (!can_inject_args(defined, arg_used, inline >= 2 && safe_to_inject)) return false; |
| | | var used = Object.create(arg_used); |
| | | if (!can_inject_vars(defined, used, inline >= 3 && safe_to_inject)) return false; |
| | | if (!can_inject_args(defined, inline >= 2 && safe_to_inject)) return false; |
| | | if (!can_inject_vars(defined, inline >= 3 && safe_to_inject)) return false; |
| | | return !in_loop || in_loop.length == 0 || !is_reachable(fn, in_loop); |
| | | } |
| | | |
| | | function append_var(decls, expressions, name, value) { |
| | | var def = name.definition(); |
| | | if (!scope.var_names()[name.name]) { |
| | | scope.var_names()[name.name] = true; |
| | | if (!scope.var_names().has(name.name)) { |
| | | scope.var_names().set(name.name, true); |
| | | decls.push(make_node(AST_VarDef, name, { |
| | | name: name, |
| | | value: null, |
| | |
| | | scope.enclosed.push(def); |
| | | if (!value) return; |
| | | var sym = make_node(AST_SymbolRef, name, name); |
| | | def.assignments++; |
| | | def.references.push(sym); |
| | | expressions.push(make_node(AST_Assign, self, { |
| | | operator: "=", |
| | |
| | | name = argname; |
| | | } |
| | | var value = self.args[i]; |
| | | if (name.__unused || scope.var_names()[name.name]) { |
| | | if (name.unused || scope.var_names().has(name.name)) { |
| | | if (value) expressions.push(value); |
| | | } else { |
| | | var symbol = make_node(AST_SymbolVar, name, name); |
| | | name.definition().orig.push(symbol); |
| | | if ("__unused" in name) { |
| | | var def = name.definition(); |
| | | def.orig.push(symbol); |
| | | def.eliminated++; |
| | | if (name.unused !== undefined) { |
| | | append_var(decls, expressions, symbol); |
| | | if (value) expressions.push(value); |
| | | } else { |
| | |
| | | expressions.reverse(); |
| | | for (i = default_args.length; --i >= 0;) { |
| | | var node = default_args[i]; |
| | | if ("__unused" in node.name) { |
| | | if (node.name.unused !== undefined) { |
| | | expressions.push(node.value); |
| | | } else { |
| | | var sym = make_node(AST_SymbolRef, node.name, node.name); |
| | |
| | | operator: "=", |
| | | left: make_node(AST_DestructuredArray, self, { |
| | | elements: fn.argnames.map(function(argname) { |
| | | if (argname.__unused) return make_node(AST_Hole, argname); |
| | | if (argname.unused) return make_node(AST_Hole, argname); |
| | | return argname.convert_symbol(AST_SymbolRef, process); |
| | | }), |
| | | rest: fn.rest && fn.rest.convert_symbol(AST_SymbolRef, process), |
| | |
| | | |
| | | function process(ref, name) { |
| | | var def = name.definition(); |
| | | def.assignments++; |
| | | def.references.push(ref); |
| | | var symbol = make_node(AST_SymbolVar, name, name); |
| | | def.orig.push(symbol); |
| | | def.eliminated++; |
| | | append_var(decls, expressions, symbol); |
| | | } |
| | | } |
| | | |
| | | function flatten_var(name) { |
| | | var redef = name.definition().redefined(); |
| | | if (redef) { |
| | | name = name.clone(); |
| | | name.thedef = redef; |
| | | } |
| | | return name; |
| | | } |
| | | |
| | | function flatten_vars(decls, expressions) { |
| | | var args = [ insert, 0 ]; |
| | | var decl_var = [], expr_var = [], expr_loop = []; |
| | | for (var i = 0; i < fn.body.length; i++) { |
| | | var stat = fn.body[i]; |
| | | if (stat instanceof AST_LambdaDefinition) { |
| | | if (in_loop) { |
| | | var name = make_node(AST_SymbolVar, stat.name, flatten_var(stat.name)); |
| | | name.definition().orig.push(name); |
| | | append_var(decls, expressions, name, to_func_expr(stat, true)); |
| | | } else { |
| | | var def = stat.name.definition(); |
| | | scope.functions.set(def.name, def); |
| | | scope.variables.set(def.name, def); |
| | | scope.enclosed.push(def); |
| | | scope.var_names()[def.name] = true; |
| | | args.push(stat); |
| | | } |
| | | continue; |
| | | var decl_var = [], expr_fn = [], expr_var = [], expr_loop = [], exprs = []; |
| | | fn.body.filter(in_loop ? function(stat) { |
| | | if (!(stat instanceof AST_LambdaDefinition)) return true; |
| | | var name = make_node(AST_SymbolVar, stat.name, flatten_var(stat.name)); |
| | | var def = name.definition(); |
| | | def.fixed = false; |
| | | def.orig.push(name); |
| | | def.eliminated++; |
| | | append_var(decls, expr_fn, name, to_func_expr(stat, true)); |
| | | return false; |
| | | } : function(stat) { |
| | | if (!(stat instanceof AST_LambdaDefinition)) return true; |
| | | var def = stat.name.definition(); |
| | | scope.functions.set(def.name, def); |
| | | scope.variables.set(def.name, def); |
| | | scope.enclosed.push(def); |
| | | scope.var_names().set(def.name, true); |
| | | args.push(stat); |
| | | return false; |
| | | }).forEach(function(stat) { |
| | | if (!(stat instanceof AST_Var)) { |
| | | if (stat instanceof AST_SimpleStatement) exprs.push(stat.body); |
| | | return; |
| | | } |
| | | if (!(stat instanceof AST_Var)) continue; |
| | | for (var j = 0; j < stat.definitions.length; j++) { |
| | | var var_def = stat.definitions[j]; |
| | | var name = flatten_var(var_def.name); |
| | | append_var(decl_var, expr_var, name, var_def.value); |
| | | if (in_loop && !HOP(arg_used, name.name)) { |
| | | var def = fn.variables.get(name.name); |
| | | var sym = make_node(AST_SymbolRef, name, name); |
| | | def.references.push(sym); |
| | | expr_loop.push(make_node(AST_Assign, var_def, { |
| | | operator: "=", |
| | | left: sym, |
| | | right: make_node(AST_Undefined, name), |
| | | })); |
| | | var value = var_def.value; |
| | | if (value && exprs.length > 0) { |
| | | exprs.push(value); |
| | | value = make_sequence(var_def, exprs); |
| | | exprs = []; |
| | | } |
| | | append_var(decl_var, expr_var, name, value); |
| | | if (!in_loop) continue; |
| | | if (arg_used.has(name.name)) continue; |
| | | if (name.definition().orig.length == 1 && fn.functions.has(name.name)) continue; |
| | | expr_loop.push(init_ref(compressor, name)); |
| | | } |
| | | } |
| | | }); |
| | | [].push.apply(decls, decl_var); |
| | | [].push.apply(expressions, expr_loop); |
| | | [].push.apply(expressions, expr_fn); |
| | | [].push.apply(expressions, expr_var); |
| | | return args; |
| | | } |
| | |
| | | function flatten_fn() { |
| | | var decls = []; |
| | | var expressions = []; |
| | | if (has_default > 1 || has_destructured || fn.rest) { |
| | | if (has_default > 1 || has_destructured || has_spread || fn.rest) { |
| | | flatten_destructured(decls, expressions); |
| | | } else { |
| | | flatten_args(decls, expressions); |
| | | } |
| | | var args = flatten_vars(decls, expressions); |
| | | expressions.push(value); |
| | | if (decls.length) args.push(make_node(AST_Var, fn, { |
| | | definitions: decls |
| | | })); |
| | | if (decls.length) args.push(make_node(AST_Var, fn, { definitions: decls })); |
| | | [].splice.apply(scope.body, args); |
| | | fn.enclosed.forEach(function(def) { |
| | | if (scope.var_names()[def.name]) return; |
| | | if (scope.var_names().has(def.name)) return; |
| | | scope.enclosed.push(def); |
| | | scope.var_names()[def.name] = true; |
| | | scope.var_names().set(def.name, true); |
| | | }); |
| | | return expressions; |
| | | } |
| | |
| | | OPT(AST_UnaryPrefix, function(self, compressor) { |
| | | var op = self.operator; |
| | | var exp = self.expression; |
| | | if (compressor.option("evaluate") && op == "delete" && !may_not_delete(exp)) { |
| | | return make_sequence(self, [ exp, make_node(AST_True, self) ]).optimize(compressor); |
| | | } |
| | | if (compressor.option("sequences") && can_lift()) { |
| | | var seq = lift_sequence_in_expression(self, compressor); |
| | | if (seq !== self) return seq.optimize(compressor); |
| | | } |
| | | if (compressor.option("side_effects") && op == "void") { |
| | | switch (op) { |
| | | case "+": |
| | | if (!compressor.option("evaluate")) break; |
| | | if (!exp.is_number(compressor, true)) break; |
| | | var parent = compressor.parent(); |
| | | if (parent instanceof AST_UnaryPrefix && parent.operator == "delete") break; |
| | | return exp; |
| | | case "-": |
| | | if (exp instanceof AST_Infinity) exp = exp.transform(compressor); |
| | | // avoids infinite recursion of numerals |
| | | if (exp instanceof AST_Number || exp instanceof AST_Infinity) return self; |
| | | break; |
| | | case "!": |
| | | if (!compressor.option("booleans")) break; |
| | | if (exp.is_truthy()) return make_sequence(self, [ exp, make_node(AST_False, self) ]).optimize(compressor); |
| | | if (compressor.in_boolean_context()) { |
| | | // !!foo ---> foo, if we're in boolean context |
| | | if (exp instanceof AST_UnaryPrefix && exp.operator == "!") return exp.expression; |
| | | if (exp instanceof AST_Binary) { |
| | | self = best_of(compressor, self, exp.negate(compressor, first_in_statement(compressor))); |
| | | } |
| | | } |
| | | break; |
| | | case "delete": |
| | | if (!compressor.option("evaluate")) break; |
| | | if (may_not_delete(exp)) break; |
| | | return make_sequence(self, [ exp, make_node(AST_True, self) ]).optimize(compressor); |
| | | case "typeof": |
| | | if (!compressor.option("booleans")) break; |
| | | if (!compressor.in_boolean_context()) break; |
| | | // typeof always returns a non-empty string, thus always truthy |
| | | AST_Node.warn("Boolean expression always true [{file}:{line},{col}]", self.start); |
| | | var exprs = [ make_node(AST_True, self) ]; |
| | | if (!(exp instanceof AST_SymbolRef && can_drop_symbol(exp, compressor))) exprs.unshift(exp); |
| | | return make_sequence(self, exprs).optimize(compressor); |
| | | case "void": |
| | | if (!compressor.option("side_effects")) break; |
| | | exp = exp.drop_side_effect_free(compressor); |
| | | if (!exp) return make_node(AST_Undefined, self).optimize(compressor); |
| | | self.expression = exp; |
| | | return self; |
| | | } |
| | | if (compressor.option("booleans")) { |
| | | if (op == "!" && exp.is_truthy()) { |
| | | return make_sequence(self, [ exp, make_node(AST_False, self) ]).optimize(compressor); |
| | | } else if (compressor.in_boolean_context()) switch (op) { |
| | | case "!": |
| | | if (exp instanceof AST_UnaryPrefix && exp.operator == "!") { |
| | | // !!foo ---> foo, if we're in boolean context |
| | | return exp.expression; |
| | | } |
| | | if (exp instanceof AST_Binary) { |
| | | self = best_of(compressor, self, exp.negate(compressor, first_in_statement(compressor))); |
| | | } |
| | | break; |
| | | case "typeof": |
| | | // typeof always returns a non-empty string, thus it's |
| | | // always true in booleans |
| | | AST_Node.warn("Boolean expression always true [{file}:{line},{col}]", self.start); |
| | | var exprs = [ make_node(AST_True, self) ]; |
| | | if (!(exp instanceof AST_SymbolRef && can_drop_symbol(exp, compressor))) exprs.unshift(exp); |
| | | return make_sequence(self, exprs).optimize(compressor); |
| | | } |
| | | } |
| | | if (op == "-" && exp instanceof AST_Infinity) exp = exp.transform(compressor); |
| | | if (compressor.option("evaluate") |
| | | && exp instanceof AST_Binary |
| | | && SIGN_OPS[op] |
| | |
| | | operator: exp.operator, |
| | | left: make_node(AST_UnaryPrefix, exp.left, { |
| | | operator: op, |
| | | expression: exp.left |
| | | expression: exp.left, |
| | | }), |
| | | right: exp.right |
| | | right: exp.right, |
| | | }); |
| | | } |
| | | // avoids infinite recursion of numerals |
| | | return op == "-" && (exp instanceof AST_Number || exp instanceof AST_Infinity) |
| | | ? self : try_evaluate(compressor, self); |
| | | return try_evaluate(compressor, self); |
| | | |
| | | function may_not_delete(node) { |
| | | return node instanceof AST_Infinity |
| | |
| | | if (compressor.option("side_effects")) { |
| | | var exp = self.expression; |
| | | if (exp instanceof AST_Await) return exp.optimize(compressor); |
| | | if (exp instanceof AST_UnaryPrefix) { |
| | | if (exp.expression instanceof AST_Await) return exp.optimize(compressor); |
| | | if (exp.operator == "void") return make_node(AST_UnaryPrefix, self, { |
| | | operator: "void", |
| | | expression: make_node(AST_Await, self, { expression: exp.expression }), |
| | | }).optimize(compressor); |
| | | } |
| | | if (exp instanceof AST_UnaryPrefix && exp.expression instanceof AST_Await) return exp.optimize(compressor); |
| | | for (var level = 0, node = self, parent; parent = compressor.parent(level++); node = parent) { |
| | | if (is_arrow(parent)) { |
| | | if (parent.value === node) return exp.optimize(compressor); |
| | |
| | | do { |
| | | node = parent; |
| | | parent = compressor.parent(level++); |
| | | if (parent instanceof AST_Try && parent.bfinally && parent.bfinally !== node) { |
| | | if (parent instanceof AST_Try && (parent.bfinally || parent.bcatch) !== node) { |
| | | drop = false; |
| | | break; |
| | | } |
| | |
| | | return !node.has_side_effects(compressor); |
| | | } |
| | | |
| | | OPT(AST_Binary, function(self, compressor) { |
| | | function reversible() { |
| | | return self.left.is_constant() |
| | | || self.right.is_constant() |
| | | || !self.left.has_side_effects(compressor) |
| | | && !self.right.has_side_effects(compressor); |
| | | } |
| | | function reverse(op) { |
| | | if (reversible()) { |
| | | if (op) self.operator = op; |
| | | var tmp = self.left; |
| | | self.left = self.right; |
| | | self.right = tmp; |
| | | } |
| | | } |
| | | function swap_chain() { |
| | | var rhs = self.right; |
| | | self.left = make_node(AST_Binary, self, { |
| | | operator: self.operator, |
| | | left: self.left, |
| | | right: rhs.left, |
| | | start: self.left.start, |
| | | end: rhs.left.end |
| | | }); |
| | | self.right = rhs.right; |
| | | function swap_chain(self, compressor) { |
| | | var rhs = self.right; |
| | | self.left = make_node(AST_Binary, self, { |
| | | operator: self.operator, |
| | | left: self.left, |
| | | right: rhs.left, |
| | | start: self.left.start, |
| | | end: rhs.left.end |
| | | }); |
| | | self.right = rhs.right; |
| | | if (compressor) { |
| | | self.left = self.left.transform(compressor); |
| | | } else if (self.operator == rhs.left.operator) { |
| | | swap_chain(self.left); |
| | | } |
| | | } |
| | | |
| | | OPT(AST_Binary, function(self, compressor) { |
| | | if (commutativeOperators[self.operator] |
| | | && self.right.is_constant() |
| | | && !self.left.is_constant() |
| | |
| | | && assign instanceof AST_Assign |
| | | && assign.operator == "=" |
| | | && self.left.equivalent_to(assign.left)) { |
| | | self.right = assign.right; |
| | | assign.right = self; |
| | | return assign; |
| | | return make_node(AST_Assign, self, { |
| | | operator: "=", |
| | | left: assign.left, |
| | | right: make_node(AST_Binary, self, { |
| | | operator: self.operator, |
| | | left: self.left, |
| | | right: assign.right, |
| | | }), |
| | | }).optimize(compressor); |
| | | } |
| | | } |
| | | if (compressor.option("comparisons")) switch (self.operator) { |
| | |
| | | // void 0 !== x && null !== x ---> null != x |
| | | // void 0 === x || null === x ---> null == x |
| | | var lhs = self.left; |
| | | if (lhs.operator == self.operator) { |
| | | lhs = lhs.right; |
| | | } |
| | | if (lhs.operator == self.operator) lhs = lhs.right; |
| | | var expr = lhs.right; |
| | | if (expr instanceof AST_Assign && expr.operator == "=") expr = expr.left; |
| | | if (lhs instanceof AST_Binary |
| | | && lhs.operator == (self.operator == "&&" ? "!==" : "===") |
| | | && self.right instanceof AST_Binary |
| | | && lhs.operator == self.right.operator |
| | | && (is_undefined(lhs.left, compressor) && self.right.left instanceof AST_Null |
| | | || lhs.left instanceof AST_Null && is_undefined(self.right.left, compressor)) |
| | | && !lhs.right.has_side_effects(compressor) |
| | | && lhs.right.equivalent_to(self.right.right)) { |
| | | var combined = make_node(AST_Binary, self, { |
| | | operator: lhs.operator.slice(0, -1), |
| | | left: make_node(AST_Null, self), |
| | | right: lhs.right |
| | | }); |
| | | if (lhs !== self.left) { |
| | | combined = make_node(AST_Binary, self, { |
| | | operator: self.operator, |
| | | left: self.left.left, |
| | | right: combined |
| | | }); |
| | | } |
| | | return combined; |
| | | && !expr.has_side_effects(compressor) |
| | | && expr.equivalent_to(self.right.right)) { |
| | | lhs.operator = lhs.operator.slice(0, -1); |
| | | lhs.left = make_node(AST_Null, self); |
| | | return self.left; |
| | | } |
| | | break; |
| | | } |
| | |
| | | } |
| | | if (in_bool) switch (self.operator) { |
| | | case "+": |
| | | var ll = self.left.evaluate(compressor); |
| | | var rr = self.right.evaluate(compressor); |
| | | if (ll && typeof ll == "string") { |
| | | var ev = self.left.evaluate(compressor, true); |
| | | if (ev && typeof ev == "string" || (ev = self.right.evaluate(compressor, true)) && typeof ev == "string") { |
| | | AST_Node.warn("+ in boolean context always true [{file}:{line},{col}]", self.start); |
| | | return make_sequence(self, [ |
| | | self.right, |
| | | make_node(AST_True, self) |
| | | ]).optimize(compressor); |
| | | } |
| | | if (rr && typeof rr == "string") { |
| | | AST_Node.warn("+ in boolean context always true [{file}:{line},{col}]", self.start); |
| | | return make_sequence(self, [ |
| | | self.left, |
| | | make_node(AST_True, self) |
| | | ]).optimize(compressor); |
| | | var exprs = []; |
| | | if (self.left.evaluate(compressor) instanceof AST_Node) exprs.push(self.left); |
| | | if (self.right.evaluate(compressor) instanceof AST_Node) exprs.push(self.right); |
| | | if (exprs.length < 2) { |
| | | exprs.push(make_node(AST_True, self)); |
| | | return make_sequence(self, exprs).optimize(compressor); |
| | | } |
| | | self.truthy = true; |
| | | } |
| | | break; |
| | | case "==": |
| | |
| | | && lazy_op[self.operator] |
| | | && self.right instanceof AST_Binary |
| | | && self.operator == self.right.operator) { |
| | | swap_chain(); |
| | | swap_chain(self, compressor); |
| | | } |
| | | if (compressor.option("strings") && self.operator == "+") { |
| | | // "foo" + 42 + "" ---> "foo" + 42 |
| | |
| | | && self.left.operator == "+" |
| | | && self.left.left instanceof AST_String |
| | | && self.left.left.value == "" |
| | | && self.right.is_string(compressor)) { |
| | | && self.right.is_string(compressor) |
| | | && (self.left.right.is_constant() || !self.right.has_side_effects(compressor))) { |
| | | self.left = self.left.right; |
| | | return self.optimize(compressor); |
| | | } |
| | |
| | | && (self.left.is_string(compressor) && self.right.is_string(compressor) |
| | | || self.right.left.is_string(compressor) |
| | | && (self.left.is_constant() || !self.right.right.has_side_effects(compressor)))) { |
| | | swap_chain(); |
| | | swap_chain(self, compressor); |
| | | } |
| | | } |
| | | if (compressor.option("evaluate")) { |
| | |
| | | AST_Node.warn("Condition left of && always true [{file}:{line},{col}]", self.start); |
| | | return make_sequence(self, [ self.left, self.right ]).optimize(compressor); |
| | | } |
| | | var rr = self.right.evaluate(compressor); |
| | | if (!rr) { |
| | | if (in_bool) { |
| | | if (!self.right.evaluate(compressor, true)) { |
| | | if (in_bool && !(self.right.evaluate(compressor) instanceof AST_Node)) { |
| | | AST_Node.warn("Boolean && always false [{file}:{line},{col}]", self.start); |
| | | return make_sequence(self, [ |
| | | self.left, |
| | | make_node(AST_False, self) |
| | | ]).optimize(compressor); |
| | | return make_sequence(self, [ self.left, make_node(AST_False, self) ]).optimize(compressor); |
| | | } else self.falsy = true; |
| | | } else if (!(rr instanceof AST_Node)) { |
| | | if (in_bool || parent.operator == "&&" && parent.left === compressor.self()) { |
| | | AST_Node.warn("Dropping side-effect-free && [{file}:{line},{col}]", self.start); |
| | | return self.left.optimize(compressor); |
| | | } |
| | | } else if ((in_bool || parent.operator == "&&" && parent.left === compressor.self()) |
| | | && !(self.right.evaluate(compressor) instanceof AST_Node)) { |
| | | AST_Node.warn("Dropping side-effect-free && [{file}:{line},{col}]", self.start); |
| | | return self.left.optimize(compressor); |
| | | } |
| | | // (x || false) && y ---> x ? y : false |
| | | if (self.left.operator == "||") { |
| | |
| | | }); |
| | | return maintain_this_binding(compressor, parent, compressor.self(), self.left).optimize(compressor); |
| | | } |
| | | var rr = self.right.evaluate(compressor); |
| | | if (!rr) { |
| | | if (in_bool || parent.operator == "||" && parent.left === compressor.self()) { |
| | | AST_Node.warn("Dropping side-effect-free {operator} [{file}:{line},{col}]", { |
| | | operator: self.operator, |
| | | file: self.start.file, |
| | | line: self.start.line, |
| | | col: self.start.col, |
| | | }); |
| | | return self.left.optimize(compressor); |
| | | } |
| | | } else if (!nullish && !(rr instanceof AST_Node)) { |
| | | if (in_bool) { |
| | | var rr; |
| | | if (!nullish && (rr = self.right.evaluate(compressor, true)) && !(rr instanceof AST_Node)) { |
| | | if (in_bool && !(self.right.evaluate(compressor) instanceof AST_Node)) { |
| | | AST_Node.warn("Boolean || always true [{file}:{line},{col}]", self.start); |
| | | return make_sequence(self, [ |
| | | self.left, |
| | | make_node(AST_True, self) |
| | | ]).optimize(compressor); |
| | | return make_sequence(self, [ self.left, make_node(AST_True, self) ]).optimize(compressor); |
| | | } else self.truthy = true; |
| | | } else if ((in_bool || parent.operator == "||" && parent.left === compressor.self()) |
| | | && !self.right.evaluate(compressor)) { |
| | | AST_Node.warn("Dropping side-effect-free {operator} [{file}:{line},{col}]", { |
| | | operator: self.operator, |
| | | file: self.start.file, |
| | | line: self.start.line, |
| | | col: self.start.col, |
| | | }); |
| | | return self.left.optimize(compressor); |
| | | } |
| | | // x && true || y ---> x ? true : y |
| | | if (!nullish && self.left.operator == "&&") { |
| | |
| | | if (self.left.value == 0) { |
| | | if (self.right.is_boolean(compressor)) return make_node(AST_UnaryPrefix, self, { |
| | | operator: "+", |
| | | expression: self.right |
| | | expression: self.right, |
| | | }).optimize(compressor); |
| | | if (self.right.is_number(compressor) && !self.right.is_negative_zero()) return self.right; |
| | | } |
| | | break; |
| | | // 1 * n ---> n |
| | | case "*": |
| | | if (self.left.value == 1) { |
| | | return self.right.is_number(compressor) ? self.right : make_node(AST_UnaryPrefix, self, { |
| | | operator: "+", |
| | | expression: self.right |
| | | }).optimize(compressor); |
| | | } |
| | | if (self.left.value == 1) return make_node(AST_UnaryPrefix, self, { |
| | | operator: "+", |
| | | expression: self.right, |
| | | }).optimize(compressor); |
| | | break; |
| | | } |
| | | if (self.right instanceof AST_Number && !self.left.is_constant()) switch (self.operator) { |
| | |
| | | if (self.right.value == 0) { |
| | | if (self.left.is_boolean(compressor)) return make_node(AST_UnaryPrefix, self, { |
| | | operator: "+", |
| | | expression: self.left |
| | | expression: self.left, |
| | | }).optimize(compressor); |
| | | if (self.left.is_number(compressor) && !self.left.is_negative_zero()) return self.left; |
| | | } |
| | | break; |
| | | // n - 0 ---> n |
| | | case "-": |
| | | if (self.right.value == 0) { |
| | | return self.left.is_number(compressor) ? self.left : make_node(AST_UnaryPrefix, self, { |
| | | operator: "+", |
| | | expression: self.left |
| | | }).optimize(compressor); |
| | | } |
| | | if (self.right.value == 0) return make_node(AST_UnaryPrefix, self, { |
| | | operator: "+", |
| | | expression: self.left, |
| | | }).optimize(compressor); |
| | | break; |
| | | // n / 1 ---> n |
| | | case "/": |
| | | if (self.right.value == 1) { |
| | | return self.left.is_number(compressor) ? self.left : make_node(AST_UnaryPrefix, self, { |
| | | operator: "+", |
| | | expression: self.left |
| | | }).optimize(compressor); |
| | | } |
| | | if (self.right.value == 1) return make_node(AST_UnaryPrefix, self, { |
| | | operator: "+", |
| | | expression: self.left, |
| | | }).optimize(compressor); |
| | | break; |
| | | } |
| | | } |
| | |
| | | && self.left.expression instanceof AST_Number && self.left.expression.value == 1; |
| | | } |
| | | } |
| | | |
| | | function reversible() { |
| | | return self.left.is_constant() |
| | | || self.right.is_constant() |
| | | || !self.left.has_side_effects(compressor) |
| | | && !self.right.has_side_effects(compressor); |
| | | } |
| | | |
| | | function reverse(op) { |
| | | if (reversible()) { |
| | | if (op) self.operator = op; |
| | | var tmp = self.left; |
| | | self.left = self.right; |
| | | self.right = tmp; |
| | | } |
| | | } |
| | | }); |
| | | |
| | | OPT(AST_SymbolExport, function(self) { |
| | |
| | | var scope = self.scope.resolve(); |
| | | fixed.enclosed.forEach(function(def) { |
| | | if (fixed.variables.has(def.name)) return; |
| | | if (scope.var_names()[def.name]) return; |
| | | if (scope.var_names().has(def.name)) return; |
| | | scope.enclosed.push(def); |
| | | scope.var_names()[def.name] = true; |
| | | scope.var_names().set(def.name, true); |
| | | }); |
| | | } |
| | | var value; |
| | |
| | | def.replaced++; |
| | | return value; |
| | | } |
| | | var local = self.fixed !== def.fixed; |
| | | if (fixed && (local || def.should_replace !== false)) { |
| | | var state; |
| | | if (fixed && (state = self.fixed || def.fixed).should_replace !== false) { |
| | | var ev, init; |
| | | if (fixed instanceof AST_This) { |
| | | if (!is_funarg(def) && same_scope(def)) init = fixed; |
| | | if (!is_funarg(def) && same_scope(def) && !cross_class(def)) init = fixed; |
| | | } else if ((ev = fixed.evaluate(compressor, true)) !== fixed |
| | | && typeof ev != "function" |
| | | && (ev === null |
| | |
| | | init = make_node_from_constant(ev, fixed); |
| | | } |
| | | if (init) { |
| | | if (!local && def.should_replace === undefined) { |
| | | if (state.should_replace === undefined) { |
| | | var value_length = init.optimize(compressor).print_to_string().length; |
| | | if (!has_symbol_ref(fixed)) { |
| | | value_length = Math.min(value_length, fixed.print_to_string().length); |
| | | } |
| | | var name_length = def.name.length; |
| | | if (compressor.option("unused") && !compressor.exposed(def)) { |
| | | var referenced = def.references.length - def.replaced; |
| | | name_length += (name_length + 2 + value_length) / (referenced - def.assignments); |
| | | var refs = def.references.length - def.replaced - def.assignments; |
| | | refs = Math.min(refs, def.references.filter(function(ref) { |
| | | return ref.fixed === state; |
| | | }).length); |
| | | name_length += (name_length + 2 + value_length) / Math.max(1, refs); |
| | | } |
| | | var delta = value_length - Math.floor(name_length); |
| | | def.should_replace = delta < compressor.eval_threshold; |
| | | state.should_replace = value_length - Math.floor(name_length) < compressor.eval_threshold; |
| | | } |
| | | if (local || def.should_replace) { |
| | | if (state.should_replace) { |
| | | var value; |
| | | if (has_symbol_ref(fixed)) { |
| | | value = init.optimize(compressor); |
| | |
| | | } |
| | | } |
| | | return self; |
| | | |
| | | function cross_class(def) { |
| | | var scope = self.scope; |
| | | while (scope !== def.scope) { |
| | | if (scope instanceof AST_Class) return true; |
| | | scope = scope.parent_scope; |
| | | } |
| | | } |
| | | |
| | | function has_symbol_ref(value) { |
| | | var found; |
| | |
| | | && tag.expression.name == "String"; |
| | | } |
| | | |
| | | function decode_template(str) { |
| | | var malformed = false; |
| | | str = str.replace(/\\(u\{[^{}]*\}?|u[\s\S]{0,4}|x[\s\S]{0,2}|[0-9]+|[\s\S])/g, function(match, seq) { |
| | | var ch = decode_escape_sequence(seq); |
| | | if (typeof ch == "string") return ch; |
| | | malformed = true; |
| | | }); |
| | | if (!malformed) return str; |
| | | } |
| | | |
| | | OPT(AST_Template, function(self, compressor) { |
| | | if (!compressor.option("templates")) return self; |
| | | var tag = self.tag; |
| | | if (!tag || is_raw_tag(compressor, tag)) { |
| | | var exprs = self.expressions.slice(); |
| | | var strs = self.strings.slice(); |
| | | var CHANGED = false; |
| | | for (var i = exprs.length; --i >= 0;) { |
| | | var node = exprs[i]; |
| | | var ev = node.evaluate(compressor); |
| | | if (ev === node) continue; |
| | | if (tag && /\r|\\|`/.test(ev)) continue; |
| | | ev = ("" + ev).replace(/\r|\\|`/g, function(s) { |
| | | return "\\" + (s == "\r" ? "r" : s); |
| | | }); |
| | | if (ev.length > node.print_to_string().length + 3) continue; |
| | | var combined = strs[i] + ev + strs[i + 1]; |
| | | if (typeof make_node(AST_Template, self, { |
| | | expressions: [], |
| | | strings: [ combined ], |
| | | tag: tag, |
| | | }).evaluate(compressor) != typeof make_node(AST_Template, self, { |
| | | expressions: [ node ], |
| | | strings: strs.slice(i, i + 2), |
| | | tag: tag, |
| | | }).evaluate(compressor)) continue; |
| | | exprs.splice(i, 1); |
| | | strs.splice(i, 2, combined); |
| | | CHANGED = true; |
| | | var exprs = []; |
| | | var strs = []; |
| | | for (var i = 0, status; i < self.strings.length; i++) { |
| | | var str = self.strings[i]; |
| | | if (!tag) { |
| | | var trimmed = decode_template(str); |
| | | if (trimmed) str = escape_literal(trimmed); |
| | | } |
| | | if (i > 0) { |
| | | var node = self.expressions[i - 1]; |
| | | var value = should_join(node); |
| | | if (value) { |
| | | var prev = strs[strs.length - 1]; |
| | | var joined = prev + value + str; |
| | | var decoded; |
| | | if (tag || typeof (decoded = decode_template(joined)) == status) { |
| | | strs[strs.length - 1] = decoded ? escape_literal(decoded) : joined; |
| | | continue; |
| | | } |
| | | } |
| | | exprs.push(node); |
| | | } |
| | | strs.push(str); |
| | | if (!tag) status = typeof trimmed; |
| | | } |
| | | if (CHANGED) { |
| | | self.expressions = exprs; |
| | | self.strings = strs; |
| | | if (!tag && strs.length > 1) { |
| | | if (strs[strs.length - 1] == "") return make_node(AST_Binary, self, { |
| | | operator: "+", |
| | | left: make_node(AST_Template, self, { |
| | | expressions: exprs.slice(0, -1), |
| | | strings: strs.slice(0, -1), |
| | | tag: tag, |
| | | }).transform(compressor), |
| | | right: exprs[exprs.length - 1], |
| | | }).optimize(compressor); |
| | | if (strs[0] == "") { |
| | | var left = make_node(AST_Binary, self, { |
| | | operator: "+", |
| | | left: make_node(AST_String, self, { value: "" }), |
| | | right: exprs[0], |
| | | }); |
| | | for (var i = 1; strs[i] == "" && i < exprs.length; i++) { |
| | | left = make_node(AST_Binary, self, { |
| | | operator: "+", |
| | | left: left, |
| | | right: exprs[i], |
| | | }); |
| | | } |
| | | return best_of(compressor, self, make_node(AST_Binary, self, { |
| | | operator: "+", |
| | | left: left.transform(compressor), |
| | | right: make_node(AST_Template, self, { |
| | | expressions: exprs.slice(i), |
| | | strings: strs.slice(i), |
| | | tag: tag, |
| | | }).transform(compressor), |
| | | }).optimize(compressor)); |
| | | } |
| | | } |
| | | self.expressions = exprs; |
| | | self.strings = strs; |
| | | } |
| | | return try_evaluate(compressor, self); |
| | | |
| | | function escape_literal(str) { |
| | | return str.replace(/\r|\\|`|\${/g, function(s) { |
| | | return "\\" + (s == "\r" ? "r" : s); |
| | | }); |
| | | } |
| | | |
| | | function should_join(node) { |
| | | var ev = node.evaluate(compressor); |
| | | if (ev === node) return; |
| | | if (tag && /\r|\\|`/.test(ev)) return; |
| | | ev = escape_literal("" + ev); |
| | | if (ev.length > node.print_to_string().length + "${}".length) return; |
| | | return ev; |
| | | } |
| | | }); |
| | | |
| | | function is_atomic(lhs, self) { |
| | |
| | | if (lhs && is_atomic(lhs, self)) return self; |
| | | return make_node(AST_UnaryPrefix, self, { |
| | | operator: "void", |
| | | expression: make_node(AST_Number, self, { |
| | | value: 0 |
| | | }) |
| | | expression: make_node(AST_Number, self, { value: 0 }), |
| | | }); |
| | | }); |
| | | |
| | |
| | | } |
| | | return make_node(AST_Binary, self, { |
| | | operator: "/", |
| | | left: make_node(AST_Number, self, { |
| | | value: 1 |
| | | }), |
| | | right: make_node(AST_Number, self, { |
| | | value: 0 |
| | | }) |
| | | left: make_node(AST_Number, self, { value: 1 }), |
| | | right: make_node(AST_Number, self, { value: 0 }), |
| | | }); |
| | | }); |
| | | |
| | |
| | | if (!lhs && !find_scope(compressor).find_variable("NaN")) return self; |
| | | return make_node(AST_Binary, self, { |
| | | operator: "/", |
| | | left: make_node(AST_Number, self, { |
| | | value: 0 |
| | | }), |
| | | right: make_node(AST_Number, self, { |
| | | value: 0 |
| | | }) |
| | | left: make_node(AST_Number, self, { value: 0 }), |
| | | right: make_node(AST_Number, self, { value: 0 }), |
| | | }); |
| | | }); |
| | | |
| | |
| | | var reachable = false; |
| | | var find_ref = new TreeWalker(function(node) { |
| | | if (reachable) return true; |
| | | if (node instanceof AST_SymbolRef && member(node.definition(), defs)) { |
| | | return reachable = true; |
| | | } |
| | | if (node instanceof AST_SymbolRef && member(node.definition(), defs)) return reachable = true; |
| | | }); |
| | | var scan_scope = new TreeWalker(function(node) { |
| | | if (reachable) return true; |
| | |
| | | if (compressor.option("dead_code")) { |
| | | if (self.left instanceof AST_PropAccess) { |
| | | if (self.operator == "=") { |
| | | if (self.__drop) { |
| | | if (self.redundant) { |
| | | var exprs = [ self.left.expression ]; |
| | | if (self.left instanceof AST_Sub) exprs.push(self.left.property); |
| | | exprs.push(self.right); |
| | |
| | | node = parent; |
| | | parent = compressor.parent(level++); |
| | | if (parent instanceof AST_Assign) { |
| | | var found = false; |
| | | if (parent.left instanceof AST_SymbolRef && parent.left.definition() === def) { |
| | | if (in_try(level, parent)) break; |
| | | return strip_assignment(def); |
| | | } |
| | | if (parent.left.match_symbol(function(node) { |
| | | if (node instanceof AST_PropAccess) return true; |
| | | if (!found && node instanceof AST_SymbolRef && node.definition() === def) { |
| | | if (in_try(level, parent)) return true; |
| | | found = true; |
| | | } |
| | | })) break; |
| | | if (!found) continue; |
| | | return strip_assignment(def); |
| | | continue; |
| | | } |
| | | if (parent instanceof AST_Exit) { |
| | | if (!local) break; |
| | |
| | | } |
| | | if (compressor.option("assignments")) { |
| | | if (self.operator == "=" && self.left instanceof AST_SymbolRef && self.right instanceof AST_Binary) { |
| | | var ref; |
| | | // x = expr1 OP expr2 |
| | | if (self.right.left instanceof AST_SymbolRef |
| | | && self.right.left.name == self.left.name |
| | | if ((ref = self.right.left) instanceof AST_SymbolRef |
| | | && ref.name == self.left.name |
| | | && ASSIGN_OPS[self.right.operator]) { |
| | | // x = x - 2 ---> x -= 2 |
| | | if (self.left.fixed) self.left.fixed.to_binary = function() { |
| | | return ref; |
| | | }; |
| | | return make_node(AST_Assign, self, { |
| | | operator: self.right.operator + "=", |
| | | left: self.left, |
| | | right: self.right.right, |
| | | }); |
| | | } |
| | | if (self.right.right instanceof AST_SymbolRef |
| | | && self.right.right.name == self.left.name |
| | | if ((ref = self.right.right) instanceof AST_SymbolRef |
| | | && ref.name == self.left.name |
| | | && ASSIGN_OPS_COMMUTATIVE[self.right.operator] |
| | | && !self.right.left.has_side_effects(compressor)) { |
| | | // x = 2 & x ---> x &= 2 |
| | | if (self.left.fixed) self.left.fixed.to_binary = function() { |
| | | return ref; |
| | | }; |
| | | return make_node(AST_Assign, self, { |
| | | operator: self.right.operator + "=", |
| | | left: self.left, |
| | |
| | | } |
| | | |
| | | function pop_seq(node) { |
| | | if (!(node instanceof AST_Sequence)) return make_node(AST_Number, node, { |
| | | value: 0 |
| | | }); |
| | | if (!(node instanceof AST_Sequence)) return make_node(AST_Number, node, { value: 0 }); |
| | | return make_sequence(node, node.expressions.slice(0, -1)); |
| | | } |
| | | }); |
| | | |
| | | OPT(AST_Boolean, function(self, compressor) { |
| | | if (!compressor.option("booleans")) return self; |
| | | if (compressor.in_boolean_context()) return make_node(AST_Number, self, { |
| | | value: +self.value |
| | | }); |
| | | if (compressor.in_boolean_context()) return make_node(AST_Number, self, { value: +self.value }); |
| | | var p = compressor.parent(); |
| | | if (p instanceof AST_Binary && (p.operator == "==" || p.operator == "!=")) { |
| | | AST_Node.warn("Non-strict equality against boolean: {operator} {value} [{file}:{line},{col}]", { |
| | |
| | | line : p.start.line, |
| | | col : p.start.col, |
| | | }); |
| | | return make_node(AST_Number, self, { |
| | | value: +self.value |
| | | }); |
| | | return make_node(AST_Number, self, { value: +self.value }); |
| | | } |
| | | return make_node(AST_UnaryPrefix, self, { |
| | | operator: "!", |
| | | expression: make_node(AST_Number, self, { |
| | | value: 1 - self.value |
| | | }) |
| | | expression: make_node(AST_Number, self, { value: 1 - self.value }), |
| | | }); |
| | | }); |
| | | |
| | |
| | | if (assigned) def.reassigned--; |
| | | var sym = make_node(AST_SymbolRef, self, argname); |
| | | sym.reference(); |
| | | delete argname.__unused; |
| | | argname.unused = undefined; |
| | | return sym; |
| | | } |
| | | } |
| | |
| | | if (flatten) { |
| | | values.push(retValue); |
| | | return make_sequence(self, values).optimize(compressor); |
| | | } else return make_node(AST_Sub, self, { |
| | | } |
| | | return make_node(AST_Sub, self, { |
| | | expression: make_node(AST_Array, expr, { elements: values }), |
| | | property: make_node(AST_Number, prop, { value: index }), |
| | | }); |
| | |
| | | } |
| | | }); |
| | | |
| | | AST_Arrow.DEFMETHOD("contains_super", return_false); |
| | | AST_AsyncArrow.DEFMETHOD("contains_super", return_false); |
| | | AST_Lambda.DEFMETHOD("contains_super", function() { |
| | | var result; |
| | | AST_LambdaExpression.DEFMETHOD("contains_super", function() { |
| | | var result = false; |
| | | var self = this; |
| | | self.walk(new TreeWalker(function(node) { |
| | | if (result) return true; |
| | |
| | | })); |
| | | return result; |
| | | }); |
| | | AST_LambdaDefinition.DEFMETHOD("contains_super", return_false); |
| | | AST_Scope.DEFMETHOD("contains_super", return_false); |
| | | |
| | | AST_Arrow.DEFMETHOD("contains_this", return_false); |
| | | AST_AsyncArrow.DEFMETHOD("contains_this", return_false); |
| | | AST_Node.DEFMETHOD("contains_this", function() { |
| | | var result; |
| | | var self = this; |
| | | self.walk(new TreeWalker(function(node) { |
| | | if (result) return true; |
| | | if (node instanceof AST_This) return result = true; |
| | | if (node !== self && node instanceof AST_Scope && !is_arrow(node)) return true; |
| | | })); |
| | | return result; |
| | | // contains_this() |
| | | // returns false only if context bound by the specified scope (or scope |
| | | // containing the specified expression) is not referenced by `this` |
| | | (function(def) { |
| | | // scope of arrow function cannot bind to any context |
| | | def(AST_Arrow, return_false); |
| | | def(AST_AsyncArrow, return_false); |
| | | def(AST_Node, function() { |
| | | var result = false; |
| | | var self = this; |
| | | self.walk(new TreeWalker(function(node) { |
| | | if (result) return true; |
| | | if (node instanceof AST_This) return result = true; |
| | | if (node !== self && node instanceof AST_Scope && !is_arrow(node)) return true; |
| | | })); |
| | | return result; |
| | | }); |
| | | })(function(node, func) { |
| | | node.DEFMETHOD("contains_this", func); |
| | | }); |
| | | |
| | | function can_hoist_property(prop) { |
| | |
| | | AST_PropAccess.DEFMETHOD("flatten_object", function(key, compressor) { |
| | | if (!compressor.option("properties")) return; |
| | | if (key === "__proto__") return; |
| | | var expr = this.expression; |
| | | if (expr instanceof AST_Object) { |
| | | var props = expr.properties; |
| | | for (var i = props.length; --i >= 0;) { |
| | | var prop = props[i]; |
| | | if (prop.key !== key) continue; |
| | | if (!all(props, can_hoist_property)) break; |
| | | if (!safe_to_flatten(prop.value, compressor)) break; |
| | | props = props.map(function(prop) { |
| | | return prop.value; |
| | | }); |
| | | if (prop instanceof AST_ObjectMethod |
| | | && prop.value instanceof AST_Function |
| | | && !(compressor.parent() instanceof AST_Call)) { |
| | | if (prop.value.uses_arguments) break; |
| | | props[i] = make_node(AST_Arrow, prop.value, prop.value); |
| | | var self = this; |
| | | var expr = self.expression; |
| | | if (!(expr instanceof AST_Object)) return; |
| | | var props = expr.properties; |
| | | for (var i = props.length; --i >= 0;) { |
| | | var prop = props[i]; |
| | | if (prop.key !== key) continue; |
| | | if (!all(props, can_hoist_property)) return; |
| | | if (!safe_to_flatten(prop.value, compressor)) return; |
| | | var call, scope, values = []; |
| | | for (var j = 0; j < props.length; j++) { |
| | | var value = props[j].value; |
| | | if (props[j] instanceof AST_ObjectMethod) { |
| | | var arrow = !(value.uses_arguments || is_generator(value) || value.contains_this()); |
| | | if (arrow) { |
| | | if (!scope) scope = compressor.find_parent(AST_Scope); |
| | | var avoid = avoid_await_yield(scope); |
| | | value.each_argname(function(argname) { |
| | | if (avoid[argname.name]) arrow = false; |
| | | }); |
| | | } |
| | | var ctor; |
| | | if (arrow) { |
| | | ctor = is_async(value) ? AST_AsyncArrow : AST_Arrow; |
| | | } else if (i != j |
| | | || (call = compressor.parent()) instanceof AST_Call && call.expression === self) { |
| | | ctor = value.CTOR; |
| | | } else { |
| | | return; |
| | | } |
| | | value = make_node(ctor, value, value); |
| | | } |
| | | return make_node(AST_Sub, this, { |
| | | expression: make_node(AST_Array, expr, { elements: props }), |
| | | property: make_node(AST_Number, this, { value: i }), |
| | | }); |
| | | values.push(value); |
| | | } |
| | | return make_node(AST_Sub, self, { |
| | | expression: make_node(AST_Array, expr, { elements: values }), |
| | | property: make_node(AST_Number, self, { value: i }), |
| | | }); |
| | | } |
| | | }); |
| | | |
| | |
| | | var exp = self.expression.expression; |
| | | if (is_undeclared_ref(exp)) switch (exp.name) { |
| | | case "Array": |
| | | self.expression = make_node(AST_Array, self.expression, { |
| | | elements: [] |
| | | }); |
| | | self.expression = make_node(AST_Array, self.expression, { elements: [] }); |
| | | break; |
| | | case "Function": |
| | | self.expression = make_node(AST_Function, self.expression, { |
| | | argnames: [], |
| | | body: [] |
| | | body: [], |
| | | }).init_vars(exp.scope); |
| | | break; |
| | | case "Number": |
| | | self.expression = make_node(AST_Number, self.expression, { |
| | | value: 0 |
| | | }); |
| | | self.expression = make_node(AST_Number, self.expression, { value: 0 }); |
| | | break; |
| | | case "Object": |
| | | self.expression = make_node(AST_Object, self.expression, { |
| | | properties: [] |
| | | }); |
| | | self.expression = make_node(AST_Object, self.expression, { properties: [] }); |
| | | break; |
| | | case "RegExp": |
| | | self.expression = make_node(AST_RegExp, self.expression, { |
| | | value: /t/ |
| | | }); |
| | | self.expression = make_node(AST_RegExp, self.expression, { value: /t/ }); |
| | | break; |
| | | case "String": |
| | | self.expression = make_node(AST_String, self.expression, { |
| | | value: "" |
| | | }); |
| | | self.expression = make_node(AST_String, self.expression, { value: "" }); |
| | | break; |
| | | } |
| | | } |
| | |
| | | var found = false; |
| | | var generated = false; |
| | | var keep_duplicate = compressor.has_directive("use strict"); |
| | | var keys = new Dictionary(); |
| | | var keys = []; |
| | | var map = new Dictionary(); |
| | | var values = []; |
| | | self.properties.forEach(function(prop) { |
| | | if (!(prop instanceof AST_Spread)) return process(prop); |
| | |
| | | return make_node(AST_Object, self, { properties: values }); |
| | | |
| | | function flush() { |
| | | keys.each(function(props) { |
| | | if (props.length == 1) return values.push(props[0]); |
| | | keys.forEach(function(key) { |
| | | var props = map.get(key); |
| | | switch (props.length) { |
| | | case 0: |
| | | return; |
| | | case 1: |
| | | return values.push(props[0]); |
| | | } |
| | | changed = true; |
| | | var tail = keep_duplicate && !generated && props.pop(); |
| | | values.push(props.length == 1 ? props[0] : make_node(AST_ObjectKeyVal, self, { |
| | | key: props[0].key, |
| | | value: make_sequence(self, props.map(function(prop) { |
| | | return prop.value; |
| | | })) |
| | | })), |
| | | })); |
| | | if (tail) values.push(tail); |
| | | props.length = 0; |
| | | }); |
| | | keys = new Dictionary(); |
| | | keys = []; |
| | | map = new Dictionary(); |
| | | } |
| | | |
| | | function process(prop) { |
| | |
| | | } |
| | | if (can_hoist_property(prop)) { |
| | | if (prop.value.has_side_effects(compressor)) flush(); |
| | | keys.add(key, prop); |
| | | keys.push(key); |
| | | map.add(key, prop); |
| | | } else { |
| | | flush(); |
| | | values.push(prop); |
| | | } |
| | | if (found && !generated && typeof key == "string" && RE_POSITIVE_INTEGER.test(key)) { |
| | | generated = true; |
| | | if (keys.has(key)) prop = keys.get(key)[0]; |
| | | if (map.has(key)) prop = map.get(key)[0]; |
| | | prop.key = make_node(AST_Number, prop, { value: +key }); |
| | | } |
| | | } |
| | | }); |
| | | |
| | | function flatten_var(name) { |
| | | var redef = name.definition().redefined(); |
| | | if (redef) { |
| | | name = name.clone(); |
| | | name.thedef = redef; |
| | | } |
| | | return name; |
| | | } |
| | | |
| | | function has_arg_refs(fn, node) { |
| | | var found = false; |
| | | node.walk(new TreeWalker(function(node) { |
| | | if (found) return true; |
| | | if (node instanceof AST_SymbolRef && fn.variables.get(node.name) === node.definition()) { |
| | | return found = true; |
| | | } |
| | | })); |
| | | return found; |
| | | } |
| | | |
| | | function insert_assign(def, assign) { |
| | | var visited = []; |
| | | def.references.forEach(function(ref) { |
| | | var fixed = ref.fixed; |
| | | if (!fixed || !push_uniq(visited, fixed)) return; |
| | | if (fixed.assigns) { |
| | | fixed.assigns.unshift(assign); |
| | | } else { |
| | | fixed.assigns = [ assign ]; |
| | | } |
| | | }); |
| | | } |
| | | |
| | | function init_ref(compressor, name) { |
| | | var sym = make_node(AST_SymbolRef, name, name); |
| | | var assign = make_node(AST_Assign, name, { |
| | | operator: "=", |
| | | left: sym, |
| | | right: make_node(AST_Undefined, name).transform(compressor), |
| | | }); |
| | | var def = name.definition(); |
| | | if (def.fixed) { |
| | | sym.fixed = function() { |
| | | return assign.right; |
| | | }; |
| | | sym.fixed.assigns = [ assign ]; |
| | | insert_assign(def, assign); |
| | | } |
| | | def.assignments++; |
| | | def.references.push(sym); |
| | | return assign; |
| | | } |
| | | |
| | | (function(def) { |
| | | def(AST_Node, noop); |
| | | def(AST_Assign, noop); |
| | | def(AST_Await, function(compressor, scope, no_return, in_loop) { |
| | | var self = this; |
| | | var inlined = sync(self.expression).try_inline(compressor, scope, no_return, in_loop); |
| | | if (!inlined) return; |
| | | if (!no_return) scan_local_returns(inlined, function(node) { |
| | | node.in_bool = false; |
| | | var value = node.value; |
| | | if (value instanceof AST_Await) return; |
| | | node.value = make_node(AST_Await, self, { |
| | | expression: value || make_node(AST_Undefined, node).transform(compressor), |
| | | }); |
| | | }); |
| | | return aborts(inlined) ? inlined : make_node(AST_BlockStatement, self, { |
| | | body: [ inlined, make_node(AST_SimpleStatement, self, { |
| | | body: make_node(AST_Await, self, { expression: make_node(AST_Number, self, { value: 0 })}), |
| | | }) ], |
| | | }); |
| | | |
| | | function sync(node) { |
| | | if (!no_return) return node; |
| | | if (node.TYPE != "Call") return node; |
| | | var fn = node.expression; |
| | | switch (fn.CTOR) { |
| | | case AST_AsyncArrow: |
| | | fn = make_node(AST_Arrow, fn, fn); |
| | | break; |
| | | case AST_AsyncFunction: |
| | | fn = make_node(AST_Function, fn, fn); |
| | | break; |
| | | case AST_AsyncGeneratorFunction: |
| | | fn = make_node(AST_GeneratorFunction, fn, fn); |
| | | break; |
| | | default: |
| | | return node; |
| | | } |
| | | node = node.clone(); |
| | | node.expression = fn; |
| | | return node; |
| | | } |
| | | }); |
| | | def(AST_Binary, function(compressor, scope, no_return, in_loop) { |
| | | if (no_return === undefined) return; |
| | | var self = this; |
| | | var op = self.operator; |
| | | if (!lazy_op[op]) return; |
| | | var inlined = self.right.try_inline(compressor, scope, no_return, in_loop); |
| | | if (!inlined) return; |
| | | return make_node(AST_If, self, { |
| | | condition: make_condition(self.left), |
| | | body: inlined, |
| | | alternative: no_return ? null : make_node(AST_Return, self, { value: null }), |
| | | }); |
| | | |
| | | function make_condition(cond) { |
| | | switch (op) { |
| | | case "&&": |
| | | return cond; |
| | | case "||": |
| | | return cond.negate(compressor); |
| | | case "??": |
| | | return make_node(AST_Binary, self, { |
| | | operator: "==", |
| | | left: make_node(AST_Null, self), |
| | | right: cond, |
| | | }); |
| | | } |
| | | } |
| | | }); |
| | | def(AST_BlockStatement, function(compressor, scope, no_return, in_loop) { |
| | | if (no_return) return; |
| | | if (!this.variables) return; |
| | | var body = this.body; |
| | | var last = body.length - 1; |
| | | if (last < 0) return; |
| | | var inlined = body[last].try_inline(compressor, this, no_return, in_loop); |
| | | if (!inlined) return; |
| | | body[last] = inlined; |
| | | return this; |
| | | }); |
| | | def(AST_Call, function(compressor, scope, no_return, in_loop) { |
| | | if (compressor.option("inline") < 4) return; |
| | | var call = this; |
| | | if (call.is_expr_pure(compressor)) return; |
| | | var fn = call.expression; |
| | | if (!(fn instanceof AST_LambdaExpression)) return; |
| | | if (fn.name) return; |
| | | if (fn.uses_arguments) return; |
| | | if (fn.pinned()) return; |
| | | if (is_generator(fn)) return; |
| | | var arrow = is_arrow(fn); |
| | | if (arrow && fn.value) return; |
| | | if (fn.body[0] instanceof AST_Directive) return; |
| | | if (fn.contains_this()) return; |
| | | if (!scope) scope = find_scope(compressor); |
| | | if (in_async_generator(scope)) return; |
| | | var defined = new Dictionary(); |
| | | defined.set("NaN", true); |
| | | while (!(scope instanceof AST_Scope)) { |
| | | scope.variables.each(function(def) { |
| | | defined.set(def.name, true); |
| | | }); |
| | | scope = scope.parent_scope; |
| | | } |
| | | if (!member(scope, compressor.stack)) return; |
| | | if (scope.pinned() && fn.variables.size() > (arrow ? 0 : 1)) return; |
| | | if (scope instanceof AST_Toplevel) { |
| | | if (fn.variables.size() > (arrow ? 0 : 1)) { |
| | | if (!compressor.toplevel.vars) return; |
| | | if (fn.functions.size() > 0 && !compressor.toplevel.funcs) return; |
| | | } |
| | | defined.set("arguments", true); |
| | | } |
| | | var async = is_async(fn); |
| | | if (async) { |
| | | if (!compressor.option("awaits")) return; |
| | | if (!is_async(scope)) return; |
| | | if (call.may_throw(compressor)) return; |
| | | } |
| | | var names = scope.var_names(); |
| | | if (in_loop) in_loop = []; |
| | | if (!fn.variables.all(function(def, name) { |
| | | if (in_loop) in_loop.push(def); |
| | | if (!defined.has(name) && !names.has(name)) return true; |
| | | return !arrow && name == "arguments" && def.orig.length == 1; |
| | | })) return; |
| | | if (in_loop && in_loop.length > 0 && is_reachable(fn, in_loop)) return; |
| | | var simple_argnames = true; |
| | | if (!all(fn.argnames, function(argname) { |
| | | var abort = false; |
| | | var tw = new TreeWalker(function(node) { |
| | | if (abort) return true; |
| | | if (node instanceof AST_DefaultValue) { |
| | | if (has_arg_refs(fn, node.value)) return abort = true; |
| | | node.name.walk(tw); |
| | | return true; |
| | | } |
| | | if (node instanceof AST_DestructuredKeyVal) { |
| | | if (node.key instanceof AST_Node && has_arg_refs(fn, node.key)) return abort = true; |
| | | node.value.walk(tw); |
| | | return true; |
| | | } |
| | | if (node instanceof AST_SymbolFunarg && !all(node.definition().orig, function(sym) { |
| | | return !(sym instanceof AST_SymbolDefun); |
| | | })) return abort = true; |
| | | }); |
| | | argname.walk(tw); |
| | | if (abort) return false; |
| | | if (!(argname instanceof AST_SymbolFunarg)) simple_argnames = false; |
| | | return true; |
| | | })) return; |
| | | if (fn.rest) { |
| | | if (has_arg_refs(fn, fn.rest)) return; |
| | | simple_argnames = false; |
| | | } |
| | | if (no_return && !all(fn.body, function(stat) { |
| | | var abort = false; |
| | | stat.walk(new TreeWalker(function(node) { |
| | | if (abort) return true; |
| | | if (async && node instanceof AST_Await || node instanceof AST_Return) return abort = true; |
| | | if (node instanceof AST_Scope && node !== fn) return true; |
| | | })); |
| | | return !abort; |
| | | })) return; |
| | | if (!safe_from_await_yield(fn, avoid_await_yield(scope))) return; |
| | | fn.functions.each(function(def, name) { |
| | | scope.functions.set(name, def); |
| | | }); |
| | | var body = []; |
| | | fn.variables.each(function(def, name) { |
| | | if (!arrow && name == "arguments" && def.orig.length == 1) return; |
| | | names.set(name, true); |
| | | scope.enclosed.push(def); |
| | | scope.variables.set(name, def); |
| | | def.single_use = false; |
| | | if (!in_loop) return; |
| | | if (def.references.length == def.replaced) return; |
| | | if (def.orig.length == def.eliminated) return; |
| | | if (def.orig.length == 1 && fn.functions.has(name)) return; |
| | | if (!all(def.orig, function(sym) { |
| | | if (sym instanceof AST_SymbolConst) return false; |
| | | if (sym instanceof AST_SymbolFunarg) return def.scope.resolve() !== fn; |
| | | if (sym instanceof AST_SymbolLet) return false; |
| | | return true; |
| | | })) return; |
| | | var sym = def.orig[0]; |
| | | if (sym instanceof AST_SymbolCatch) return; |
| | | body.push(make_node(AST_SimpleStatement, sym, { body: init_ref(compressor, flatten_var(sym)) })); |
| | | }); |
| | | var defs = Object.create(null), syms = new Dictionary(); |
| | | if (simple_argnames && all(call.args, function(arg) { |
| | | return !(arg instanceof AST_Spread); |
| | | })) { |
| | | var values = call.args.slice(); |
| | | fn.argnames.forEach(function(argname) { |
| | | var value = values.shift(); |
| | | if (argname.unused) { |
| | | if (value) body.push(make_node(AST_SimpleStatement, call, { body: value })); |
| | | return; |
| | | } |
| | | var defn = make_node(AST_VarDef, call, { |
| | | name: argname.convert_symbol(AST_SymbolVar, process), |
| | | value: value || make_node(AST_Undefined, call).transform(compressor), |
| | | }); |
| | | if (argname instanceof AST_SymbolFunarg) insert_assign(argname.definition(), defn); |
| | | body.push(make_node(AST_Var, call, { definitions: [ defn ] })); |
| | | }); |
| | | if (values.length) body.push(make_node(AST_SimpleStatement, call, { |
| | | body: make_sequence(call, values), |
| | | })); |
| | | } else { |
| | | body.push(make_node(AST_Var, call, { |
| | | definitions: [ make_node(AST_VarDef, call, { |
| | | name: make_node(AST_DestructuredArray, call, { |
| | | elements: fn.argnames.map(function(argname) { |
| | | if (argname.unused) return make_node(AST_Hole, argname); |
| | | return argname.convert_symbol(AST_SymbolVar, process); |
| | | }), |
| | | rest: fn.rest && fn.rest.convert_symbol(AST_SymbolVar, process), |
| | | }), |
| | | value: make_node(AST_Array, call, { elements: call.args.slice() }), |
| | | }) ], |
| | | })); |
| | | } |
| | | syms.each(function(orig, id) { |
| | | var def = defs[id]; |
| | | [].unshift.apply(def.orig, orig); |
| | | def.eliminated += orig.length; |
| | | }); |
| | | [].push.apply(body, in_loop ? fn.body.filter(function(stat) { |
| | | if (!(stat instanceof AST_LambdaDefinition)) return true; |
| | | var name = make_node(AST_SymbolVar, stat.name, flatten_var(stat.name)); |
| | | var def = name.definition(); |
| | | def.fixed = false; |
| | | def.orig.push(name); |
| | | def.eliminated++; |
| | | body.push(make_node(AST_Var, stat, { |
| | | definitions: [ make_node(AST_VarDef, stat, { |
| | | name: name, |
| | | value: to_func_expr(stat, true), |
| | | }) ], |
| | | })); |
| | | return false; |
| | | }) : fn.body); |
| | | var inlined = make_node(AST_BlockStatement, call, { body: body }); |
| | | if (!no_return) { |
| | | if (async) scan_local_returns(inlined, function(node) { |
| | | var value = node.value; |
| | | if (is_undefined(value)) return; |
| | | node.value = make_node(AST_Await, call, { expression: value }); |
| | | }); |
| | | body.push(make_node(AST_Return, call, { value: null })); |
| | | } |
| | | return inlined; |
| | | |
| | | function process(sym, argname) { |
| | | var def = argname.definition(); |
| | | defs[def.id] = def; |
| | | syms.add(def.id, sym); |
| | | } |
| | | }); |
| | | def(AST_Conditional, function(compressor, scope, no_return, in_loop) { |
| | | var self = this; |
| | | var body = self.consequent.try_inline(compressor, scope, no_return, in_loop); |
| | | var alt = self.alternative.try_inline(compressor, scope, no_return, in_loop); |
| | | if (!body && !alt) return; |
| | | return make_node(AST_If, self, { |
| | | condition: self.condition, |
| | | body: body || make_body(self.consequent), |
| | | alternative: alt || make_body(self.alternative), |
| | | }); |
| | | |
| | | function make_body(value) { |
| | | if (no_return) return make_node(AST_SimpleStatement, value, { body: value }); |
| | | return make_node(AST_Return, value, { value: value }); |
| | | } |
| | | }); |
| | | def(AST_For, function(compressor, scope, no_return, in_loop) { |
| | | var body = this.body.try_inline(compressor, scope, true, true); |
| | | if (body) this.body = body; |
| | | var inlined = this.init; |
| | | if (inlined) { |
| | | inlined = inlined.try_inline(compressor, scope, true, in_loop); |
| | | if (inlined) { |
| | | this.init = null; |
| | | if (inlined instanceof AST_BlockStatement) { |
| | | inlined.body.push(this); |
| | | return inlined; |
| | | } |
| | | return make_node(AST_BlockStatement, inlined, { body: [ inlined, this ] }); |
| | | } |
| | | } |
| | | return body && this; |
| | | }); |
| | | def(AST_ForEnumeration, function(compressor, scope, no_return, in_loop) { |
| | | var body = this.body.try_inline(compressor, scope, true, true); |
| | | if (body) this.body = body; |
| | | var obj = this.object; |
| | | if (obj instanceof AST_Sequence) { |
| | | var inlined = inline_sequence(compressor, scope, true, in_loop, obj, 1); |
| | | if (inlined) { |
| | | this.object = obj.tail_node(); |
| | | inlined.body.push(this); |
| | | return inlined; |
| | | } |
| | | } |
| | | return body && this; |
| | | }); |
| | | def(AST_If, function(compressor, scope, no_return, in_loop) { |
| | | var body = this.body.try_inline(compressor, scope, no_return, in_loop); |
| | | if (body) this.body = body; |
| | | var alt = this.alternative; |
| | | if (alt) { |
| | | alt = alt.try_inline(compressor, scope, no_return, in_loop); |
| | | if (alt) this.alternative = alt; |
| | | } |
| | | var cond = this.condition; |
| | | if (cond instanceof AST_Sequence) { |
| | | var inlined = inline_sequence(compressor, scope, true, in_loop, cond, 1); |
| | | if (inlined) { |
| | | this.condition = cond.tail_node(); |
| | | inlined.body.push(this); |
| | | return inlined; |
| | | } |
| | | } |
| | | return (body || alt) && this; |
| | | }); |
| | | def(AST_IterationStatement, function(compressor, scope, no_return, in_loop) { |
| | | var body = this.body.try_inline(compressor, scope, true, true); |
| | | if (!body) return; |
| | | this.body = body; |
| | | return this; |
| | | }); |
| | | def(AST_LabeledStatement, function(compressor, scope, no_return, in_loop) { |
| | | var body = this.body.try_inline(compressor, scope, no_return, in_loop); |
| | | if (!body) return; |
| | | if (this.body instanceof AST_IterationStatement && body instanceof AST_BlockStatement) { |
| | | var loop = body.body.pop(); |
| | | this.body = loop; |
| | | body.body.push(this); |
| | | return body; |
| | | } |
| | | this.body = body; |
| | | return this; |
| | | }); |
| | | def(AST_New, noop); |
| | | def(AST_Return, function(compressor, scope, no_return, in_loop) { |
| | | var value = this.value; |
| | | return value && value.try_inline(compressor, scope, undefined, in_loop === "try"); |
| | | }); |
| | | function inline_sequence(compressor, scope, no_return, in_loop, node, skip) { |
| | | var body = [], exprs = node.expressions, no_ret = no_return; |
| | | for (var i = exprs.length - (skip || 0), j = i; --i >= 0; no_ret = true) { |
| | | var inlined = exprs[i].try_inline(compressor, scope, no_ret, in_loop); |
| | | if (!inlined) continue; |
| | | flush(); |
| | | body.push(inlined); |
| | | } |
| | | if (body.length == 0) return; |
| | | flush(); |
| | | if (!no_return && body[0] instanceof AST_SimpleStatement) { |
| | | body[0] = make_node(AST_Return, node, { value: body[0].body }); |
| | | } |
| | | return make_node(AST_BlockStatement, node, { body: body.reverse() }); |
| | | |
| | | function flush() { |
| | | if (j > i + 1) body.push(make_node(AST_SimpleStatement, node, { |
| | | body: make_sequence(node, exprs.slice(i + 1, j)), |
| | | })); |
| | | j = i; |
| | | } |
| | | } |
| | | def(AST_Sequence, function(compressor, scope, no_return, in_loop) { |
| | | return inline_sequence(compressor, scope, no_return, in_loop, this); |
| | | }); |
| | | def(AST_SimpleStatement, function(compressor, scope, no_return, in_loop) { |
| | | var body = this.body; |
| | | while (body instanceof AST_UnaryPrefix) { |
| | | var op = body.operator; |
| | | if (unary_side_effects[op]) break; |
| | | if (op == "void") break; |
| | | body = body.expression; |
| | | } |
| | | if (!no_return && !is_undefined(body)) body = make_node(AST_UnaryPrefix, this, { |
| | | operator: "void", |
| | | expression: body, |
| | | }); |
| | | return body.try_inline(compressor, scope, no_return || false, in_loop); |
| | | }); |
| | | def(AST_UnaryPrefix, function(compressor, scope, no_return, in_loop) { |
| | | var self = this; |
| | | var op = self.operator; |
| | | if (unary_side_effects[op]) return; |
| | | if (!no_return && op == "void") no_return = false; |
| | | var inlined = self.expression.try_inline(compressor, scope, no_return, in_loop); |
| | | if (!inlined) return; |
| | | if (!no_return) scan_local_returns(inlined, function(node) { |
| | | node.in_bool = false; |
| | | var value = node.value; |
| | | if (op == "void" && is_undefined(value)) return; |
| | | node.value = make_node(AST_UnaryPrefix, self, { |
| | | operator: op, |
| | | expression: value || make_node(AST_Undefined, node).transform(compressor), |
| | | }); |
| | | }); |
| | | return inlined; |
| | | }); |
| | | def(AST_With, function(compressor, scope, no_return, in_loop) { |
| | | var body = this.body.try_inline(compressor, scope, no_return, in_loop); |
| | | if (body) this.body = body; |
| | | var exp = this.expression; |
| | | if (exp instanceof AST_Sequence) { |
| | | var inlined = inline_sequence(compressor, scope, true, in_loop, exp, 1); |
| | | if (inlined) { |
| | | this.expression = exp.tail_node(); |
| | | inlined.body.push(this); |
| | | return inlined; |
| | | } |
| | | } |
| | | return body && this; |
| | | }); |
| | | def(AST_Yield, function(compressor, scope, no_return, in_loop) { |
| | | if (!compressor.option("yields")) return; |
| | | if (!this.nested) return; |
| | | var call = this.expression; |
| | | if (call.TYPE != "Call") return; |
| | | var fn = call.expression; |
| | | switch (fn.CTOR) { |
| | | case AST_AsyncGeneratorFunction: |
| | | fn = make_node(AST_AsyncFunction, fn, fn); |
| | | break; |
| | | case AST_GeneratorFunction: |
| | | fn = make_node(AST_Function, fn, fn); |
| | | break; |
| | | default: |
| | | return; |
| | | } |
| | | call = call.clone(); |
| | | call.expression = fn; |
| | | return call.try_inline(compressor, scope, no_return, in_loop); |
| | | }); |
| | | })(function(node, func) { |
| | | node.DEFMETHOD("try_inline", func); |
| | | }); |
| | | |
| | | OPT(AST_Return, function(self, compressor) { |
| | | if (compressor.option("side_effects") |
| | | && self.value |
| | | && is_undefined(self.value, compressor) |
| | | var value = self.value; |
| | | if (value && compressor.option("side_effects") |
| | | && is_undefined(value, compressor) |
| | | && !in_async_generator(compressor.find_parent(AST_Scope))) { |
| | | self.value = null; |
| | | } |