diff options
Diffstat (limited to 'node_modules/uglify-js/lib')
-rw-r--r-- | node_modules/uglify-js/lib/ast.js | 160 | ||||
-rw-r--r-- | node_modules/uglify-js/lib/compress.js | 2552 | ||||
-rw-r--r-- | node_modules/uglify-js/lib/minify.js | 220 | ||||
-rw-r--r-- | node_modules/uglify-js/lib/mozilla-ast.js | 16 | ||||
-rw-r--r-- | node_modules/uglify-js/lib/output.js | 267 | ||||
-rw-r--r-- | node_modules/uglify-js/lib/parse.js | 152 | ||||
-rw-r--r-- | node_modules/uglify-js/lib/propmangle.js | 121 | ||||
-rw-r--r-- | node_modules/uglify-js/lib/scope.js | 289 | ||||
-rw-r--r-- | node_modules/uglify-js/lib/transform.js | 7 | ||||
-rw-r--r-- | node_modules/uglify-js/lib/utils.js | 9 |
10 files changed, 2081 insertions, 1712 deletions
diff --git a/node_modules/uglify-js/lib/ast.js b/node_modules/uglify-js/lib/ast.js index 028772f3f..0918574d1 100644 --- a/node_modules/uglify-js/lib/ast.js +++ b/node_modules/uglify-js/lib/ast.js @@ -182,21 +182,13 @@ var AST_BlockStatement = DEFNODE("BlockStatement", null, { }, AST_Block); var AST_EmptyStatement = DEFNODE("EmptyStatement", null, { - $documentation: "The empty statement (empty block or simply a semicolon)", - _walk: function(visitor) { - return visitor._visit(this); - } + $documentation: "The empty statement (empty block or simply a semicolon)" }, AST_Statement); var AST_StatementWithBody = DEFNODE("StatementWithBody", "body", { $documentation: "Base class for all statements that contain one nested body: `For`, `ForIn`, `Do`, `While`, `With`", $propdoc: { body: "[AST_Statement] the body; this should always be present, even if it's an AST_EmptyStatement" - }, - _walk: function(visitor) { - return visitor._visit(this, function(){ - this.body._walk(visitor); - }); } }, AST_Statement); @@ -326,62 +318,13 @@ var AST_Toplevel = DEFNODE("Toplevel", "globals", { $propdoc: { globals: "[Object/S] a map of name -> SymbolDef for all undeclared names", }, - wrap_enclose: function(arg_parameter_pairs) { - var self = this; - var args = []; - var parameters = []; - - arg_parameter_pairs.forEach(function(pair) { - var splitAt = pair.lastIndexOf(":"); - - args.push(pair.substr(0, splitAt)); - parameters.push(pair.substr(splitAt + 1)); - }); - - var wrapped_tl = "(function(" + parameters.join(",") + "){ '$ORIG'; })(" + args.join(",") + ")"; + wrap_commonjs: function(name) { + var body = this.body; + var wrapped_tl = "(function(exports){'$ORIG';})(typeof " + name + "=='undefined'?(" + name + "={}):" + name + ");"; wrapped_tl = parse(wrapped_tl); wrapped_tl = wrapped_tl.transform(new TreeTransformer(function before(node){ if (node instanceof AST_Directive && node.value == "$ORIG") { - return MAP.splice(self.body); - } - })); - return wrapped_tl; - }, - wrap_commonjs: function(name, export_all) { - var self = this; - var to_export = []; - if (export_all) { - self.figure_out_scope(); - self.walk(new TreeWalker(function(node){ - if (node instanceof AST_SymbolDeclaration && node.definition().global) { - if (!find_if(function(n){ return n.name == node.name }, to_export)) - to_export.push(node); - } - })); - } - var wrapped_tl = "(function(exports, global){ '$ORIG'; '$EXPORTS'; global['" + name + "'] = exports; }({}, (function(){return this}())))"; - wrapped_tl = parse(wrapped_tl); - wrapped_tl = wrapped_tl.transform(new TreeTransformer(function before(node){ - if (node instanceof AST_Directive) { - switch (node.value) { - case "$ORIG": - return MAP.splice(self.body); - case "$EXPORTS": - var body = []; - to_export.forEach(function(sym){ - body.push(new AST_SimpleStatement({ - body: new AST_Assign({ - left: new AST_Sub({ - expression: new AST_SymbolRef({ name: "exports" }), - property: new AST_String({ value: sym.name }), - }), - operator: "=", - right: new AST_SymbolRef(sym), - }), - })); - }); - return MAP.splice(body); - } + return MAP.splice(body); } })); return wrapped_tl; @@ -552,10 +495,10 @@ var AST_Finally = DEFNODE("Finally", null, { $documentation: "A `finally` node; only makes sense as part of a `try` statement" }, AST_Block); -/* -----[ VAR/CONST ]----- */ +/* -----[ VAR ]----- */ var AST_Definitions = DEFNODE("Definitions", "definitions", { - $documentation: "Base class for `var` or `const` nodes (variable declarations/initializations)", + $documentation: "Base class for `var` nodes (variable declarations/initializations)", $propdoc: { definitions: "[AST_VarDef*] array of variable definitions" }, @@ -573,14 +516,10 @@ var AST_Var = DEFNODE("Var", null, { $documentation: "A `var` statement" }, AST_Definitions); -var AST_Const = DEFNODE("Const", null, { - $documentation: "A `const` statement" -}, AST_Definitions); - var AST_VarDef = DEFNODE("VarDef", "name value", { $documentation: "A variable declaration; only appears in a AST_Definitions node", $propdoc: { - name: "[AST_SymbolVar|AST_SymbolConst] name of the variable", + name: "[AST_SymbolVar] name of the variable", value: "[AST_Node?] initializer, or null of there's no initializer" }, _walk: function(visitor) { @@ -601,11 +540,11 @@ var AST_Call = DEFNODE("Call", "expression args", { }, _walk: function(visitor) { return visitor._visit(this, function(){ - this.expression._walk(visitor); var args = this.args; for (var i = 0, len = args.length; i < len; i++) { args[i]._walk(visitor); } + this.expression._walk(visitor); }); } }); @@ -614,68 +553,16 @@ var AST_New = DEFNODE("New", null, { $documentation: "An object instantiation. Derives from a function call since it has exactly the same properties" }, AST_Call); -var AST_Seq = DEFNODE("Seq", "car cdr", { - $documentation: "A sequence expression (two comma-separated expressions)", +var AST_Sequence = DEFNODE("Sequence", "expressions", { + $documentation: "A sequence expression (comma-separated expressions)", $propdoc: { - car: "[AST_Node] first element in sequence", - cdr: "[AST_Node] second element in sequence" - }, - $cons: function(x, y) { - var seq = new AST_Seq(x); - seq.car = x; - seq.cdr = y; - return seq; - }, - $from_array: function(array) { - if (array.length == 0) return null; - if (array.length == 1) return array[0].clone(); - var list = null; - for (var i = array.length; --i >= 0;) { - list = AST_Seq.cons(array[i], list); - } - var p = list; - while (p) { - if (p.cdr && !p.cdr.cdr) { - p.cdr = p.cdr.car; - break; - } - p = p.cdr; - } - return list; - }, - to_array: function() { - var p = this, a = []; - while (p) { - a.push(p.car); - if (p.cdr && !(p.cdr instanceof AST_Seq)) { - a.push(p.cdr); - break; - } - p = p.cdr; - } - return a; - }, - add: function(node) { - var p = this; - while (p) { - if (!(p.cdr instanceof AST_Seq)) { - var cell = AST_Seq.cons(p.cdr, node); - return p.cdr = cell; - } - p = p.cdr; - } - }, - len: function() { - if (this.cdr instanceof AST_Seq) { - return this.cdr.len() + 1; - } else { - return 2; - } + expressions: "[AST_Node*] array of expressions (at least two)" }, _walk: function(visitor) { return visitor._visit(this, function(){ - this.car._walk(visitor); - if (this.cdr) this.cdr._walk(visitor); + this.expressions.forEach(function(node) { + node._walk(visitor); + }); }); } }); @@ -728,7 +615,7 @@ var AST_UnaryPostfix = DEFNODE("UnaryPostfix", null, { $documentation: "Unary postfix expression, i.e. `i++`" }, AST_Unary); -var AST_Binary = DEFNODE("Binary", "left operator right", { +var AST_Binary = DEFNODE("Binary", "operator left right", { $documentation: "Binary expression, i.e. `a + b`", $propdoc: { left: "[AST_Node] left-hand side expression", @@ -837,17 +724,13 @@ var AST_SymbolAccessor = DEFNODE("SymbolAccessor", null, { }, AST_Symbol); var AST_SymbolDeclaration = DEFNODE("SymbolDeclaration", "init", { - $documentation: "A declaration symbol (symbol in var/const, function name or argument, symbol in catch)", + $documentation: "A declaration symbol (symbol in var, function name or argument, symbol in catch)", }, AST_Symbol); var AST_SymbolVar = DEFNODE("SymbolVar", null, { $documentation: "Symbol defining a variable", }, AST_SymbolDeclaration); -var AST_SymbolConst = DEFNODE("SymbolConst", null, { - $documentation: "A constant declaration" -}, AST_SymbolDeclaration); - var AST_SymbolFunarg = DEFNODE("SymbolFunarg", null, { $documentation: "Symbol naming a function argument", }, AST_SymbolVar); @@ -976,13 +859,13 @@ TreeWalker.prototype = { if (!ret && descend) { descend.call(node); } - this.pop(node); + this.pop(); return ret; }, parent: function(n) { return this.stack[this.stack.length - 2 - (n || 0)]; }, - push: function (node) { + push: function(node) { if (node instanceof AST_Lambda) { this.directives = Object.create(this.directives); } else if (node instanceof AST_Directive && !this.directives[node.value]) { @@ -990,9 +873,8 @@ TreeWalker.prototype = { } this.stack.push(node); }, - pop: function(node) { - this.stack.pop(); - if (node instanceof AST_Lambda) { + pop: function() { + if (this.stack.pop() instanceof AST_Lambda) { this.directives = Object.getPrototypeOf(this.directives); } }, diff --git a/node_modules/uglify-js/lib/compress.js b/node_modules/uglify-js/lib/compress.js index d8a491ebc..7a16ba86b 100644 --- a/node_modules/uglify-js/lib/compress.js +++ b/node_modules/uglify-js/lib/compress.js @@ -48,7 +48,6 @@ function Compressor(options, false_by_default) { return new Compressor(options, false_by_default); TreeTransformer.call(this, this.before, this.after); this.options = defaults(options, { - angular : false, booleans : !false_by_default, cascade : !false_by_default, collapse_vars : !false_by_default, @@ -62,7 +61,9 @@ function Compressor(options, false_by_default) { global_defs : {}, hoist_funs : !false_by_default, hoist_vars : false, + ie8 : false, if_return : !false_by_default, + inline : !false_by_default, join_vars : !false_by_default, keep_fargs : true, keep_fnames : false, @@ -74,20 +75,29 @@ function Compressor(options, false_by_default) { pure_getters : !false_by_default && "strict", pure_funcs : null, reduce_vars : !false_by_default, - screw_ie8 : true, sequences : !false_by_default, side_effects : !false_by_default, switches : !false_by_default, top_retain : null, toplevel : !!(options && options["top_retain"]), + typeofs : !false_by_default, unsafe : false, unsafe_comps : false, + unsafe_Func : false, unsafe_math : false, unsafe_proto : false, unsafe_regexp : false, unused : !false_by_default, - warnings : true, + warnings : false, }, true); + 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 + }); + } + } var pure_funcs = this.options["pure_funcs"]; if (typeof pure_funcs == "function") { this.pure_funcs = pure_funcs; @@ -111,26 +121,50 @@ function Compressor(options, false_by_default) { return top_retain.indexOf(def.name) >= 0; }; } + var toplevel = this.options["toplevel"]; + this.toplevel = typeof toplevel == "string" ? { + funcs: /funcs/.test(toplevel), + vars: /vars/.test(toplevel) + } : { + funcs: toplevel, + vars: toplevel + }; var sequences = this.options["sequences"]; - this.sequences_limit = sequences == 1 ? 200 : sequences | 0; + this.sequences_limit = sequences == 1 ? 800 : sequences | 0; this.warnings_produced = {}; }; Compressor.prototype = new TreeTransformer; merge(Compressor.prototype, { option: function(key) { return this.options[key] }, + exposed: function(def) { + if (def.global) for (var i = 0, len = def.orig.length; i < len; i++) + if (!this.toplevel[def.orig[i] instanceof AST_SymbolDefun ? "funcs" : "vars"]) + return true; + return false; + }, compress: function(node) { if (this.option("expression")) { - node = node.process_expression(true); + node.process_expression(true); } var passes = +this.options.passes || 1; - for (var pass = 0; pass < passes && pass < 3; ++pass) { + var last_count = 1 / 0; + for (var pass = 0; pass < passes; pass++) { if (pass > 0 || this.option("reduce_vars")) node.reset_opt_flags(this, true); node = node.transform(this); + if (passes > 1) { + var count = 0; + node.walk(new TreeWalker(function() { + count++; + })); + this.info("pass " + pass + ": last_count: " + last_count + ", count: " + count); + if (count >= last_count) break; + last_count = count; + } } if (this.option("expression")) { - node = node.process_expression(false); + node.process_expression(false); } return node; }, @@ -202,7 +236,7 @@ merge(Compressor.prototype, { return this.TYPE == node.TYPE && this.print_to_string() == node.print_to_string(); }); - AST_Node.DEFMETHOD("process_expression", function(insert, compressor) { + AST_Scope.DEFMETHOD("process_expression", function(insert, compressor) { var self = this; var tt = new TreeTransformer(function(node) { if (insert && node instanceof AST_SimpleStatement) { @@ -246,21 +280,20 @@ merge(Compressor.prototype, { } return node; }); - return self.transform(tt); + self.transform(tt); }); - AST_Node.DEFMETHOD("reset_opt_flags", function(compressor, rescan){ + AST_Node.DEFMETHOD("reset_opt_flags", function(compressor, rescan) { var reduce_vars = rescan && compressor.option("reduce_vars"); - var toplevel = compressor.option("toplevel"); var safe_ids = Object.create(null); var suppressor = new TreeWalker(function(node) { - if (node instanceof AST_Symbol) { - var d = node.definition(); - if (node instanceof AST_SymbolRef) d.references.push(node); - d.fixed = false; - } + if (!(node instanceof AST_Symbol)) return; + var d = node.definition(); + if (!d) return; + if (node instanceof AST_SymbolRef) d.references.push(node); + d.fixed = false; }); - var tw = new TreeWalker(function(node, descend){ + var tw = new TreeWalker(function(node, descend) { node._squeezed = false; node._optimized = false; if (reduce_vars) { @@ -269,8 +302,8 @@ merge(Compressor.prototype, { if (node instanceof AST_SymbolRef) { var d = node.definition(); d.references.push(node); - if (d.fixed === undefined || !is_safe(d) - || is_modified(node, 0, node.fixed_value() instanceof AST_Lambda)) { + if (d.fixed === undefined || !safe_to_read(d) + || is_modified(node, 0, is_immutable(node.fixed_value()))) { d.fixed = false; } else { var parent = tw.parent(); @@ -287,7 +320,7 @@ merge(Compressor.prototype, { } if (node instanceof AST_VarDef) { var d = node.name.definition(); - if (d.fixed == null) { + if (d.fixed === undefined || safe_to_assign(d, node.value)) { if (node.value) { d.fixed = function() { return node.value; @@ -303,9 +336,24 @@ merge(Compressor.prototype, { d.fixed = false; } } + if (node instanceof AST_Assign + && node.operator == "=" + && node.left instanceof AST_SymbolRef) { + var d = node.left.definition(); + if (safe_to_assign(d, node.right)) { + d.references.push(node.left); + d.fixed = function() { + return node.right; + }; + mark(d, false); + node.right.walk(tw); + mark(d, true); + return true; + } + } if (node instanceof AST_Defun) { var d = node.name.definition(); - if (!toplevel && d.global || is_safe(d)) { + if (compressor.exposed(d) || safe_to_read(d)) { d.fixed = false; } else { d.fixed = node; @@ -367,7 +415,7 @@ merge(Compressor.prototype, { pop(); return true; } - if (node instanceof AST_If || node instanceof AST_DWLoop) { + if (node instanceof AST_If) { node.condition.walk(tw); push(); node.body.walk(tw); @@ -379,6 +427,13 @@ merge(Compressor.prototype, { } return true; } + if (node instanceof AST_DWLoop) { + push(); + node.condition.walk(tw); + node.body.walk(tw); + pop(); + return true; + } if (node instanceof AST_LabeledStatement) { push(); node.body.walk(tw); @@ -387,11 +442,19 @@ merge(Compressor.prototype, { } if (node instanceof AST_For) { if (node.init) node.init.walk(tw); + if (node.condition) { + push(); + node.condition.walk(tw); + pop(); + } push(); - if (node.condition) node.condition.walk(tw); node.body.walk(tw); - if (node.step) node.step.walk(tw); pop(); + if (node.step) { + push(); + node.step.walk(tw); + pop(); + } return true; } if (node instanceof AST_ForIn) { @@ -428,7 +491,7 @@ merge(Compressor.prototype, { safe_ids[def.id] = safe; } - function is_safe(def) { + function safe_to_read(def) { if (safe_ids[def.id]) { if (def.fixed == null) { var orig = def.orig[0]; @@ -439,6 +502,17 @@ merge(Compressor.prototype, { } } + function safe_to_assign(def, value) { + if (!HOP(safe_ids, def.id)) return false; + if (!safe_to_read(def)) return false; + if (def.fixed === false) return false; + if (def.fixed != null && (!value || def.references.length > 0)) return false; + return !def.orig.some(function(sym) { + return sym instanceof AST_SymbolDefun + || sym instanceof AST_SymbolLambda; + }); + } + function push() { safe_ids = Object.create(safe_ids); } @@ -451,7 +525,7 @@ merge(Compressor.prototype, { def.escaped = false; if (def.scope.uses_eval) { def.fixed = false; - } else if (toplevel || !def.global || def.orig[0] instanceof AST_SymbolConst) { + } else if (!compressor.exposed(def)) { def.fixed = undefined; } else { def.fixed = false; @@ -460,13 +534,17 @@ merge(Compressor.prototype, { def.should_replace = undefined; } - function is_modified(node, level, func) { + function is_immutable(value) { + return value && value.is_constant() || value instanceof AST_Lambda; + } + + function is_modified(node, level, immutable) { var parent = tw.parent(level); if (is_lhs(node, parent) - || !func && parent instanceof AST_Call && parent.expression === node) { + || !immutable && parent instanceof AST_Call && parent.expression === node) { return true; } else if (parent instanceof AST_PropAccess && parent.expression === node) { - return !func && is_modified(parent, level + 1); + return !immutable && is_modified(parent, level + 1); } } }); @@ -477,12 +555,25 @@ merge(Compressor.prototype, { return fixed(); }); - function is_reference_const(ref) { - if (!(ref instanceof AST_SymbolRef)) return false; - var orig = ref.definition().orig; - for (var i = orig.length; --i >= 0;) { - if (orig[i] instanceof AST_SymbolConst) return true; + AST_SymbolRef.DEFMETHOD("is_immutable", function() { + var orig = this.definition().orig; + return orig.length == 1 && orig[0] instanceof AST_SymbolLambda; + }); + + function is_lhs_read_only(lhs) { + if (lhs instanceof AST_SymbolRef) return lhs.definition().orig[0] instanceof AST_SymbolLambda; + if (lhs instanceof AST_PropAccess) { + lhs = lhs.expression; + if (lhs instanceof AST_SymbolRef) { + if (lhs.is_immutable()) return false; + lhs = lhs.fixed_value(); + } + if (!lhs) return true; + if (lhs instanceof AST_RegExp) return false; + if (lhs instanceof AST_Constant) return true; + return is_lhs_read_only(lhs); } + return false; } function find_variable(compressor, name) { @@ -506,6 +597,13 @@ merge(Compressor.prototype, { return new ctor(props); }; + function make_sequence(orig, expressions) { + if (expressions.length == 1) return expressions[0]; + return make_node(AST_Sequence, orig, { + expressions: expressions + }); + } + function make_node_from_constant(val, orig) { switch (typeof val) { case "string": @@ -548,16 +646,19 @@ merge(Compressor.prototype, { if (parent instanceof AST_UnaryPrefix && parent.operator == "delete" || parent instanceof AST_Call && parent.expression === orig && (val instanceof AST_PropAccess || val instanceof AST_SymbolRef && val.name == "eval")) { - return make_node(AST_Seq, orig, { - car: make_node(AST_Number, orig, { - value: 0 - }), - cdr: val - }); + return make_sequence(orig, [ make_node(AST_Number, orig, { value: 0 }), val ]); } return val; } + function merge_sequence(array, node) { + if (node instanceof AST_Sequence) { + array.push.apply(array, node.expressions); + } else { + array.push(node); + } + } + function as_statement_array(thing) { if (thing === null) return []; if (thing instanceof AST_BlockStatement) return thing.body; @@ -588,436 +689,425 @@ merge(Compressor.prototype, { return false; } + function is_undeclared_ref(node) { + return node instanceof AST_SymbolRef && node.definition().undeclared; + } + + var global_names = makePredicate("Array Boolean console Error Function Math Number RegExp Object String"); + AST_SymbolRef.DEFMETHOD("is_declared", function(compressor) { + return !this.definition().undeclared + || compressor.option("unsafe") && global_names(this.name); + }); + function tighten_body(statements, compressor) { var CHANGED, max_iter = 10; do { CHANGED = false; - if (compressor.option("angular")) { - statements = process_for_angular(statements); - } - statements = eliminate_spurious_blocks(statements); + eliminate_spurious_blocks(statements); if (compressor.option("dead_code")) { - statements = eliminate_dead_code(statements, compressor); + eliminate_dead_code(statements, compressor); } if (compressor.option("if_return")) { - statements = handle_if_return(statements, compressor); + handle_if_return(statements, compressor); } if (compressor.sequences_limit > 0) { - statements = sequencesize(statements, compressor); + sequencesize(statements, compressor); } if (compressor.option("join_vars")) { - statements = join_consecutive_vars(statements, compressor); + join_consecutive_vars(statements, compressor); } if (compressor.option("collapse_vars")) { - statements = collapse_single_use_vars(statements, compressor); + collapse(statements, compressor); } } while (CHANGED && max_iter-- > 0); - return statements; - - function collapse_single_use_vars(statements, compressor) { - // Iterate statements backwards looking for a statement with a var/const - // declaration immediately preceding it. Grab the rightmost var definition - // and if it has exactly one reference then attempt to replace its reference - // in the statement with the var value and then erase the var definition. - - var self = compressor.self(); - var var_defs_removed = false; - var toplevel = compressor.option("toplevel"); - for (var stat_index = statements.length; --stat_index >= 0;) { - var stat = statements[stat_index]; - if (stat instanceof AST_Definitions) continue; - - // Process child blocks of statement if present. - [stat, stat.body, stat.alternative, stat.bcatch, stat.bfinally].forEach(function(node) { - node && node.body && collapse_single_use_vars(node.body, compressor); - }); - - // The variable definition must precede a statement. - if (stat_index <= 0) break; - var prev_stat_index = stat_index - 1; - var prev_stat = statements[prev_stat_index]; - if (!(prev_stat instanceof AST_Definitions)) continue; - var var_defs = prev_stat.definitions; - if (var_defs == null) continue; - - var var_names_seen = {}; - var side_effects_encountered = false; - var lvalues_encountered = false; - var lvalues = {}; - - // Scan variable definitions from right to left. - for (var var_defs_index = var_defs.length; --var_defs_index >= 0;) { - - // Obtain var declaration and var name with basic sanity check. - var var_decl = var_defs[var_defs_index]; - if (var_decl.value == null) break; - var var_name = var_decl.name.name; - if (!var_name || !var_name.length) break; - - // Bail if we've seen a var definition of same name before. - if (var_name in var_names_seen) break; - var_names_seen[var_name] = true; - - // Only interested in cases with just one reference to the variable. - var def = self.find_variable && self.find_variable(var_name); - if (!def || !def.references || def.references.length !== 1 - || var_name == "arguments" || (!toplevel && def.global)) { - side_effects_encountered = true; - continue; - } - var ref = def.references[0]; - - // Don't replace ref if eval() or with statement in scope. - if (ref.scope.uses_eval || ref.scope.uses_with) break; - - // Constant single use vars can be replaced in any scope. - if (var_decl.value.is_constant()) { - var ctt = new TreeTransformer(function(node) { - var parent = ctt.parent(); - if (parent instanceof AST_IterationStatement - && (parent.condition === node || parent.init === node)) { + // Search from right to left for assignment-like expressions: + // - `var a = x;` + // - `a = x;` + // - `++a` + // For each candidate, scan from left to right for first usage, then try + // to fold assignment into the site for compression. + // 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) { + var scope = compressor.find_parent(AST_Scope); + if (scope.uses_eval || scope.uses_with) return statements; + var candidates = []; + var stat_index = statements.length; + while (--stat_index >= 0) { + // Treat parameters as collapsible in IIFE, i.e. + // function(a, b){ ... }(x()); + // would be translated into equivalent assignments: + // var a = x(), b = undefined; + if (stat_index == 0 && compressor.option("unused")) extract_args(); + // Find collapsible assignments + extract_candidates(statements[stat_index]); + while (candidates.length > 0) { + var candidate = candidates.pop(); + var lhs = get_lhs(candidate); + if (!lhs || is_lhs_read_only(lhs)) continue; + // Locate symbols which may execute code outside of scanning range + var lvalues = get_lvalues(candidate); + if (lhs instanceof AST_SymbolRef) lvalues[lhs.name] = false; + var side_effects = value_has_side_effects(candidate); + var hit = candidate.name instanceof AST_SymbolFunarg; + var abort = false, replaced = false; + var tt = new TreeTransformer(function(node, descend) { + if (abort) return node; + // Skip nodes before `candidate` as quickly as possible + if (!hit) { + if (node === candidate) { + hit = true; return node; } - if (node === ref) - return replace_var(node, parent, true); - }); - stat.transform(ctt); - continue; - } - - // Restrict var replacement to constants if side effects encountered. - if (side_effects_encountered |= lvalues_encountered) continue; - - var value_has_side_effects = var_decl.value.has_side_effects(compressor); - // Non-constant single use vars can only be replaced in same scope. - if (ref.scope !== self) { - side_effects_encountered |= value_has_side_effects; - continue; - } - - // Detect lvalues in var value. - var tw = new TreeWalker(function(node){ - if (node instanceof AST_SymbolRef && is_lvalue(node, tw.parent())) { - lvalues[node.name] = lvalues_encountered = true; + return; } - }); - var_decl.value.walk(tw); - - // Replace the non-constant single use var in statement if side effect free. - var unwind = false; - var tt = new TreeTransformer( - function preorder(node) { - if (unwind) return node; - var parent = tt.parent(); - if (node instanceof AST_Lambda - || node instanceof AST_Try - || node instanceof AST_With - || node instanceof AST_Case - || node instanceof AST_IterationStatement - || (parent instanceof AST_If && node !== parent.condition) - || (parent instanceof AST_Conditional && node !== parent.condition) - || (node instanceof AST_SymbolRef - && value_has_side_effects - && !are_references_in_scope(node.definition(), self)) - || (parent instanceof AST_Binary - && (parent.operator == "&&" || parent.operator == "||") - && node === parent.right) - || (parent instanceof AST_Switch && node !== parent.expression)) { - return side_effects_encountered = unwind = true, node; + // Stop immediately if these node types are encountered + var parent = tt.parent(); + if (node instanceof AST_Assign && node.operator != "=" && lhs.equivalent_to(node.left) + || node instanceof AST_Call && lhs instanceof AST_PropAccess && lhs.equivalent_to(node.expression) + || node instanceof AST_Debugger + || node instanceof AST_IterationStatement && !(node instanceof AST_For) + || node instanceof AST_SymbolRef && !node.is_declared(compressor) + || node instanceof AST_Try + || node instanceof AST_With + || parent instanceof AST_For && node !== parent.init) { + abort = true; + return node; + } + // Replace variable with assignment when found + if (!(node instanceof AST_SymbolDeclaration) + && !is_lhs(node, parent) + && lhs.equivalent_to(node)) { + CHANGED = replaced = abort = true; + compressor.info("Collapsing {name} [{file}:{line},{col}]", { + name: node.print_to_string(), + file: node.start.file, + line: node.start.line, + col: node.start.col + }); + if (candidate instanceof AST_UnaryPostfix) { + return make_node(AST_UnaryPrefix, candidate, candidate); } - function are_references_in_scope(def, scope) { - if (def.orig.length === 1 - && def.orig[0] instanceof AST_SymbolDefun) return true; - if (def.scope !== scope) return false; - var refs = def.references; - for (var i = 0, len = refs.length; i < len; i++) { - if (refs[i].scope !== scope) return false; + if (candidate instanceof AST_VarDef) { + var def = candidate.name.definition(); + if (def.references.length == 1 && !compressor.exposed(def)) { + return maintain_this_binding(parent, node, candidate.value); } - return true; - } - }, - function postorder(node) { - if (unwind) return node; - if (node === ref) - return unwind = true, replace_var(node, tt.parent(), false); - if (side_effects_encountered |= node.has_side_effects(compressor)) - return unwind = true, node; - if (lvalues_encountered && node instanceof AST_SymbolRef && node.name in lvalues) { - side_effects_encountered = true; - return unwind = true, node; + return make_node(AST_Assign, candidate, { + operator: "=", + left: make_node(AST_SymbolRef, candidate.name, candidate.name), + right: candidate.value + }); } + candidate.write_only = false; + return candidate; } - ); - stat.transform(tt); + // These node types have child nodes that execute sequentially, + // but are otherwise not safe to scan into or beyond them. + var sym; + if (node instanceof AST_Call + || node instanceof AST_Exit + || node instanceof AST_PropAccess + || node instanceof AST_SymbolRef + && (lvalues[node.name] + || side_effects && !references_in_scope(node.definition())) + || (sym = lhs_or_def(node)) && get_symbol(sym).name in lvalues + || parent instanceof AST_Binary + && (parent.operator == "&&" || parent.operator == "||") + || parent instanceof AST_Case + || parent instanceof AST_Conditional + || parent instanceof AST_For + || parent instanceof AST_If) { + if (!(node instanceof AST_Scope)) descend(node, tt); + abort = true; + return node; + } + // Skip (non-executed) functions and (leading) default case in switch statements + if (node instanceof AST_Default || node instanceof AST_Scope) return node; + }); + for (var i = stat_index; !abort && i < statements.length; i++) { + statements[i].transform(tt); + } + if (replaced && !remove_candidate(candidate)) statements.splice(stat_index, 1); } } - // Remove extraneous empty statments in block after removing var definitions. - // Leave at least one statement in `statements`. - if (var_defs_removed) for (var i = statements.length; --i >= 0;) { - if (statements.length > 1 && statements[i] instanceof AST_EmptyStatement) - statements.splice(i, 1); + function extract_args() { + var iife, fn = compressor.self(); + if (fn instanceof AST_Function + && !fn.name + && !fn.uses_arguments + && !fn.uses_eval + && (iife = compressor.parent()) instanceof AST_Call + && iife.expression === fn) { + var names = Object.create(null); + for (var i = fn.argnames.length; --i >= 0;) { + var sym = fn.argnames[i]; + if (sym.name in names) continue; + names[sym.name] = true; + var arg = iife.args[i]; + if (!arg) arg = make_node(AST_Undefined, sym); + else { + var tw = new TreeWalker(function(node) { + if (!arg) return true; + if (node instanceof AST_SymbolRef && fn.variables.has(node.name)) { + var s = node.definition().scope; + if (s !== scope) while (s = s.parent_scope) { + if (s === scope) return true; + } + arg = null; + } + if (node instanceof AST_This && !tw.find_parent(AST_Scope)) { + arg = null; + return true; + } + }); + arg.walk(tw); + } + if (arg) candidates.unshift(make_node(AST_VarDef, sym, { + name: sym, + value: arg + })); + } + } } - return statements; - - function is_lvalue(node, parent) { - return node instanceof AST_SymbolRef && is_lhs(node, parent); + function extract_candidates(expr) { + if (expr instanceof AST_Assign && !expr.left.has_side_effects(compressor) + || expr instanceof AST_Unary && (expr.operator == "++" || expr.operator == "--")) { + candidates.push(expr); + } else if (expr instanceof AST_Sequence) { + expr.expressions.forEach(extract_candidates); + } else if (expr instanceof AST_Definitions) { + expr.definitions.forEach(function(var_def) { + if (var_def.value) candidates.push(var_def); + }); + } else if (expr instanceof AST_SimpleStatement) { + extract_candidates(expr.body); + } else if (expr instanceof AST_For && expr.init) { + extract_candidates(expr.init); + } } - function replace_var(node, parent, is_constant) { - if (is_lvalue(node, parent)) return node; - // Remove var definition and return its value to the TreeTransformer to replace. - var value = maintain_this_binding(parent, node, var_decl.value); - var_decl.value = null; - - var_defs.splice(var_defs_index, 1); - if (var_defs.length === 0) { - statements[prev_stat_index] = make_node(AST_EmptyStatement, self); - var_defs_removed = true; + function get_lhs(expr) { + if (expr instanceof AST_VarDef) { + var def = expr.name.definition(); + if (def.orig.length > 1 && !(expr.name instanceof AST_SymbolFunarg) + || def.references.length == 1 && !compressor.exposed(def)) { + return make_node(AST_SymbolRef, expr.name, expr.name); + } + } else { + return expr[expr instanceof AST_Assign ? "left" : "expression"]; } - // Further optimize statement after substitution. - stat.reset_opt_flags(compressor); - - compressor.info("Collapsing " + (is_constant ? "constant" : "variable") + - " " + var_name + " [{file}:{line},{col}]", node.start); - CHANGED = true; - return value; } - } - function process_for_angular(statements) { - function has_inject(comment) { - return /@ngInject/.test(comment.value); + function get_symbol(node) { + while (node instanceof AST_PropAccess) node = node.expression; + return node; } - function make_arguments_names_list(func) { - return func.argnames.map(function(sym){ - return make_node(AST_String, sym, { value: sym.name }); + + function get_lvalues(expr) { + var lvalues = Object.create(null); + if (expr instanceof AST_Unary) return lvalues; + var scope; + var tw = new TreeWalker(function(node, descend) { + if (node instanceof AST_Scope) { + var save_scope = scope; + descend(); + scope = save_scope; + return true; + } + if (node instanceof AST_SymbolRef || node instanceof AST_PropAccess) { + var sym = get_symbol(node); + if (sym instanceof AST_SymbolRef) { + lvalues[sym.name] = lvalues[sym.name] || is_lhs(node, tw.parent()); + } + } }); + expr[expr instanceof AST_Assign ? "right" : "value"].walk(tw); + return lvalues; } - function make_array(orig, elements) { - return make_node(AST_Array, orig, { elements: elements }); - } - function make_injector(func, name) { - return make_node(AST_SimpleStatement, func, { - body: make_node(AST_Assign, func, { - operator: "=", - left: make_node(AST_Dot, name, { - expression: make_node(AST_SymbolRef, name, name), - property: "$inject" - }), - right: make_array(func, make_arguments_names_list(func)) - }) - }); + + function lhs_or_def(node) { + if (node instanceof AST_VarDef) return node.value && node.name; + return is_lhs(node.left, node); } - function check_expression(body) { - if (body && body.args) { - // if this is a function call check all of arguments passed - body.args.forEach(function(argument, index, array) { - var comments = argument.start.comments_before; - // if the argument is function preceded by @ngInject - if (argument instanceof AST_Lambda && comments.length && has_inject(comments[0])) { - // replace the function with an array of names of its parameters and function at the end - array[index] = make_array(argument, make_arguments_names_list(argument).concat(argument)); - } + + function remove_candidate(expr) { + if (expr.name instanceof AST_SymbolFunarg) { + var index = compressor.self().argnames.indexOf(expr.name); + var args = compressor.parent().args; + if (args[index]) args[index] = make_node(AST_Number, args[index], { + value: 0 }); - // if this is chained call check previous one recursively - if (body.expression && body.expression.expression) { - check_expression(body.expression.expression); - } + return true; } - } - return statements.reduce(function(a, stat){ - a.push(stat); - - if (stat.body && stat.body.args) { - check_expression(stat.body); - } else { - var token = stat.start; - var comments = token.comments_before; - if (comments && comments.length > 0) { - var last = comments.pop(); - if (has_inject(last)) { - // case 1: defun - if (stat instanceof AST_Defun) { - a.push(make_injector(stat, stat.name)); - } - else if (stat instanceof AST_Definitions) { - stat.definitions.forEach(function(def) { - if (def.value && def.value instanceof AST_Lambda) { - a.push(make_injector(def.value, def.name)); - } - }); - } - else { - compressor.warn("Unknown statement marked with @ngInject [{file}:{line},{col}]", token); - } + var found = false; + return statements[stat_index].transform(new TreeTransformer(function(node, descend, in_list) { + if (found) return node; + if (node === expr) { + found = true; + if (node instanceof AST_VarDef) { + remove(node.name.definition().orig, node.name); } + return in_list ? MAP.skip : null; } - } + }, function(node) { + if (node instanceof AST_Sequence) switch (node.expressions.length) { + case 0: return null; + case 1: return node.expressions[0]; + } + if (node instanceof AST_Definitions && node.definitions.length == 0 + || node instanceof AST_SimpleStatement && !node.body) { + return null; + } + })); + } + + function value_has_side_effects(expr) { + if (expr instanceof AST_Unary) return false; + return expr[expr instanceof AST_Assign ? "right" : "value"].has_side_effects(compressor); + } - return a; - }, []); + function references_in_scope(def) { + if (def.orig.length == 1 && def.orig[0] instanceof AST_SymbolDefun) return true; + if (def.scope !== scope) return false; + return def.references.every(function(ref) { + return ref.scope === scope; + }); + } } function eliminate_spurious_blocks(statements) { var seen_dirs = []; - return statements.reduce(function(a, stat){ + for (var i = 0; i < statements.length;) { + var stat = statements[i]; if (stat instanceof AST_BlockStatement) { CHANGED = true; - a.push.apply(a, eliminate_spurious_blocks(stat.body)); + eliminate_spurious_blocks(stat.body); + [].splice.apply(statements, [i, 1].concat(stat.body)); + i += stat.body.length; } else if (stat instanceof AST_EmptyStatement) { CHANGED = true; + statements.splice(i, 1); } else if (stat instanceof AST_Directive) { if (seen_dirs.indexOf(stat.value) < 0) { - a.push(stat); + i++; seen_dirs.push(stat.value); } else { CHANGED = true; + statements.splice(i, 1); } - } else { - a.push(stat); - } - return a; - }, []); - }; + } else i++; + } + } function handle_if_return(statements, compressor) { var self = compressor.self(); var multiple_if_returns = has_multiple_if_returns(statements); var in_lambda = self instanceof AST_Lambda; - var ret = []; // Optimized statements, build from tail to front - loop: for (var i = statements.length; --i >= 0;) { + for (var i = statements.length; --i >= 0;) { var stat = statements[i]; - switch (true) { - case (in_lambda && stat instanceof AST_Return && !stat.value && ret.length == 0): - CHANGED = true; - // note, ret.length is probably always zero - // because we drop unreachable code before this - // step. nevertheless, it's good to check. - continue loop; - case stat instanceof AST_If: - if (stat.body instanceof AST_Return) { - //--- - // pretty silly case, but: - // if (foo()) return; return; ==> foo(); return; - if (((in_lambda && ret.length == 0) - || (ret[0] instanceof AST_Return && !ret[0].value)) - && !stat.body.value && !stat.alternative) { - CHANGED = true; - var cond = make_node(AST_SimpleStatement, stat.condition, { - body: stat.condition - }); - ret.unshift(cond); - continue loop; - } - //--- - // if (foo()) return x; return y; ==> return foo() ? x : y; - if (ret[0] instanceof AST_Return && stat.body.value && ret[0].value && !stat.alternative) { - CHANGED = true; - stat = stat.clone(); - stat.alternative = ret[0]; - ret[0] = stat.transform(compressor); - continue loop; - } - //--- - // if (foo()) return x; [ return ; ] ==> return foo() ? x : undefined; - if (multiple_if_returns && (ret.length == 0 || ret[0] instanceof AST_Return) - && stat.body.value && !stat.alternative && in_lambda) { - CHANGED = true; - stat = stat.clone(); - stat.alternative = ret[0] || make_node(AST_Return, stat, { - value: null - }); - ret[0] = stat.transform(compressor); - continue loop; - } - //--- - // if (foo()) return; [ else x... ]; y... ==> if (!foo()) { x...; y... } - if (!stat.body.value && in_lambda) { - CHANGED = true; - stat = stat.clone(); - stat.condition = stat.condition.negate(compressor); - var body = as_statement_array(stat.alternative).concat(ret); - var funs = extract_functions_from_statement_array(body); - stat.body = make_node(AST_BlockStatement, stat, { - body: body - }); - stat.alternative = null; - ret = funs.concat([ stat.transform(compressor) ]); - continue loop; - } + var next = statements[i + 1]; - //--- - // if (a) return b; if (c) return d; e; ==> return a ? b : c ? d : void e; - // - // 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. - if (compressor.option("sequences") - && i > 0 && statements[i - 1] instanceof AST_If && statements[i - 1].body instanceof AST_Return - && ret.length == 1 && in_lambda && ret[0] instanceof AST_SimpleStatement - && !stat.alternative) { - CHANGED = true; - ret.push(make_node(AST_Return, ret[0], { - value: null - }).transform(compressor)); - ret.unshift(stat); - continue loop; - } - } + if (in_lambda && stat instanceof AST_Return && !stat.value && !next) { + CHANGED = true; + statements.length--; + continue; + } + if (stat instanceof AST_If) { var ab = aborts(stat.body); - var lct = ab instanceof AST_LoopControl ? compressor.loopcontrol_target(ab) : null; - if (ab && ((ab instanceof AST_Return && !ab.value && in_lambda) - || (ab instanceof AST_Continue && self === loop_body(lct)) - || (ab instanceof AST_Break && lct instanceof AST_BlockStatement && self === lct))) { + if (can_merge_flow(ab)) { if (ab.label) { remove(ab.label.thedef.references, ab); } CHANGED = true; - var body = as_statement_array(stat.body).slice(0, -1); 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(ret) + body: as_statement_array(stat.alternative).concat(extract_functions()) }); stat.alternative = make_node(AST_BlockStatement, stat, { body: body }); - ret = [ stat.transform(compressor) ]; - continue loop; + statements[i] = stat.transform(compressor); + continue; } var ab = aborts(stat.alternative); - var lct = ab instanceof AST_LoopControl ? compressor.loopcontrol_target(ab) : null; - if (ab && ((ab instanceof AST_Return && !ab.value && in_lambda) - || (ab instanceof AST_Continue && self === loop_body(lct)) - || (ab instanceof AST_Break && lct instanceof AST_BlockStatement && self === lct))) { + if (can_merge_flow(ab)) { if (ab.label) { remove(ab.label.thedef.references, ab); } CHANGED = true; stat = stat.clone(); stat.body = make_node(AST_BlockStatement, stat.body, { - body: as_statement_array(stat.body).concat(ret) + body: as_statement_array(stat.body).concat(extract_functions()) }); + var body = as_statement_array_with_return(stat.alternative, ab); stat.alternative = make_node(AST_BlockStatement, stat.alternative, { - body: as_statement_array(stat.alternative).slice(0, -1) + body: body }); - ret = [ stat.transform(compressor) ]; - continue loop; + statements[i] = stat.transform(compressor); + continue; } + } - ret.unshift(stat); - break; - default: - ret.unshift(stat); - break; + if (stat instanceof AST_If && stat.body instanceof AST_Return) { + var value = stat.body.value; + //--- + // 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 (value && !stat.alternative && next instanceof AST_Return && next.value) { + CHANGED = true; + stat = stat.clone(); + stat.alternative = next; + statements.splice(i, 2, stat.transform(compressor)); + continue; + } + //--- + // if (foo()) return x; [ return ; ] ==> return foo() ? x : undefined; + if (multiple_if_returns && in_lambda && value && !stat.alternative + && (!next || next instanceof AST_Return)) { + CHANGED = true; + stat = stat.clone(); + stat.alternative = next || make_node(AST_Return, stat, { + value: null + }); + statements.splice(i, next ? 2 : 1, stat.transform(compressor)); + continue; + } + //--- + // if (a) return b; if (c) return d; e; ==> return a ? b : c ? d : void e; + // + // 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[i - 1]; + if (compressor.option("sequences") && in_lambda && !stat.alternative + && prev instanceof AST_If && prev.body instanceof AST_Return + && i + 2 == statements.length && next instanceof AST_SimpleStatement) { + CHANGED = true; + statements.push(make_node(AST_Return, next, { + value: null + }).transform(compressor)); + continue; + } } } - return ret; function has_multiple_if_returns(statements) { var n = 0; @@ -1029,108 +1119,133 @@ merge(Compressor.prototype, { } return false; } - }; + + function is_return_void(value) { + return !value || value instanceof AST_UnaryPrefix && value.operator == "void"; + } + + function can_merge_flow(ab) { + if (!ab) return false; + var lct = ab instanceof AST_LoopControl ? compressor.loopcontrol_target(ab) : null; + return ab instanceof AST_Return && in_lambda && is_return_void(ab.value) + || ab instanceof AST_Continue && self === loop_body(lct) + || ab instanceof AST_Break && lct instanceof AST_BlockStatement && self === lct; + } + + function extract_functions() { + var tail = statements.slice(i + 1); + statements.length = i + 1; + return tail.filter(function(stat) { + if (stat instanceof AST_Defun) { + statements.push(stat); + return false; + } + return true; + }); + } + + function as_statement_array_with_return(node, ab) { + var body = as_statement_array(node).slice(0, -1); + if (ab.value) { + body.push(make_node(AST_SimpleStatement, ab.value, { + body: ab.value.expression + })); + } + return body; + } + } function eliminate_dead_code(statements, compressor) { - var has_quit = false; - var orig = statements.length; + var has_quit; var self = compressor.self(); - statements = statements.reduce(function(a, stat){ - if (has_quit) { - extract_declarations_from_unreachable_code(compressor, stat, a); - } else { - 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 { - a.push(stat); + for (var i = 0, n = 0, len = statements.length; i < len; i++) { + 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 { - a.push(stat); + statements[n++] = stat; } - if (aborts(stat)) has_quit = true; + } else { + statements[n++] = stat; } - return a; - }, []); - CHANGED = statements.length != orig; - return statements; - }; + if (aborts(stat)) { + has_quit = statements.slice(i + 1); + break; + } + } + statements.length = n; + CHANGED = n != len; + if (has_quit) has_quit.forEach(function(stat) { + extract_declarations_from_unreachable_code(compressor, stat, statements); + }); + } function sequencesize(statements, compressor) { - if (statements.length < 2) return statements; - var seq = [], ret = []; + if (statements.length < 2) return; + var seq = [], n = 0; function push_seq() { - seq = AST_Seq.from_array(seq); - if (seq) ret.push(make_node(AST_SimpleStatement, seq, { - body: seq - })); + if (!seq.length) return; + var body = make_sequence(seq[0], seq); + statements[n++] = make_node(AST_SimpleStatement, body, { body: body }); seq = []; - }; - statements.forEach(function(stat){ + } + for (var i = 0, len = statements.length; i < len; i++) { + var stat = statements[i]; if (stat instanceof AST_SimpleStatement) { - if (seqLength(seq) >= compressor.sequences_limit) push_seq(); + 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) seq.push(body); + if (body) merge_sequence(seq, body); } else { push_seq(); - ret.push(stat); - } - }); - push_seq(); - ret = sequencesize_2(ret, compressor); - CHANGED = ret.length != statements.length; - return ret; - }; - - function seqLength(a) { - for (var len = 0, i = 0; i < a.length; ++i) { - var stat = a[i]; - if (stat instanceof AST_Seq) { - len += stat.len(); - } else { - len++; + statements[n++] = stat; } } - return len; - }; + push_seq(); + statements.length = n; + sequencesize_2(statements, compressor); + CHANGED = statements.length != len; + } function sequencesize_2(statements, compressor) { function cons_seq(right) { - ret.pop(); + n--; var left = prev.body; - if (left instanceof AST_Seq) { - left.add(right); - } else { - left = AST_Seq.cons(left, right); + if (!(left instanceof AST_Sequence)) { + left = make_node(AST_Sequence, left, { + expressions: [ left ] + }); } + merge_sequence(left.expressions, right); return left.transform(compressor); }; - var ret = [], prev = null; - statements.forEach(function(stat){ + var n = 0, prev; + for (var i = 0, len = statements.length; i < len; i++) { + var stat = statements[i]; if (prev) { - if (stat instanceof AST_For) { - var opera = {}; - try { - prev.body.walk(new TreeWalker(function(node){ - if (node instanceof AST_Binary && node.operator == "in") - throw opera; - })); - if (stat.init && !(stat.init instanceof AST_Definitions)) { - stat.init = cons_seq(stat.init); + if (stat instanceof AST_For && !(stat.init instanceof AST_Definitions)) { + var abort = false; + prev.body.walk(new TreeWalker(function(node) { + if (abort || node instanceof AST_Scope) return true; + if (node instanceof AST_Binary && node.operator == "in") { + abort = true; + return true; } - else if (!stat.init) { + })); + if (!abort) { + if (stat.init) stat.init = cons_seq(stat.init); + else { stat.init = prev.body.drop_side_effect_free(compressor); - ret.pop(); + n--; } - } catch(ex) { - if (ex !== opera) throw ex; } } else if (stat instanceof AST_If) { @@ -1149,15 +1264,16 @@ merge(Compressor.prototype, { stat.expression = cons_seq(stat.expression); } } - ret.push(stat); + statements[n++] = stat; prev = stat instanceof AST_SimpleStatement ? stat : null; - }); - return ret; - }; + } + statements.length = n; + } function join_consecutive_vars(statements, compressor) { - var prev = null; - return statements.reduce(function(a, stat){ + for (var i = 0, j = -1, len = statements.length; i < len; i++) { + var stat = statements[i]; + var prev = statements[j]; if (stat instanceof AST_Definitions && prev && prev.TYPE == stat.TYPE) { prev.definitions = prev.definitions.concat(stat.definitions); CHANGED = true; @@ -1166,35 +1282,19 @@ merge(Compressor.prototype, { && prev instanceof AST_Var && (!stat.init || stat.init.TYPE == prev.TYPE)) { CHANGED = true; - a.pop(); if (stat.init) { stat.init.definitions = prev.definitions.concat(stat.init.definitions); } else { stat.init = prev; } - a.push(stat); - prev = stat; + statements[j] = stat; } else { - prev = stat; - a.push(stat); + statements[++j] = stat; } - return a; - }, []); - }; - - }; - - function extract_functions_from_statement_array(statements) { - var funs = []; - for (var i = statements.length - 1; i >= 0; --i) { - var stat = statements[i]; - if (stat instanceof AST_Defun) { - statements.splice(i, 1); - funs.unshift(stat); } - } - return funs; + statements.length = j + 1; + }; } function extract_declarations_from_unreachable_code(compressor, stat, target) { @@ -1208,7 +1308,7 @@ merge(Compressor.prototype, { target.push(node); return true; } - if (node instanceof AST_Defun) { + if (node instanceof AST_Defun && (node === stat || !compressor.has_directive("use strict"))) { target.push(node); return true; } @@ -1230,12 +1330,12 @@ merge(Compressor.prototype, { // returns true if this node may be null, undefined or contain `AST_Accessor` (function(def) { AST_Node.DEFMETHOD("may_throw_on_access", function(compressor) { - var pure_getters = compressor.option("pure_getters"); - return !pure_getters || this._throw_on_access(pure_getters); + return !compressor.option("pure_getters") + || this._dot_throw(compressor); }); - function is_strict(pure_getters) { - return /strict/.test(pure_getters); + function is_strict(compressor) { + return /strict/.test(compressor.option("pure_getters")); } def(AST_Node, is_strict); @@ -1243,8 +1343,8 @@ merge(Compressor.prototype, { def(AST_Undefined, return_true); def(AST_Constant, return_false); def(AST_Array, return_false); - def(AST_Object, function(pure_getters) { - if (!is_strict(pure_getters)) return false; + def(AST_Object, function(compressor) { + if (!is_strict(compressor)) return false; for (var i = this.properties.length; --i >=0;) if (this.properties[i].value instanceof AST_Accessor) return true; return false; @@ -1254,42 +1354,44 @@ merge(Compressor.prototype, { def(AST_UnaryPrefix, function() { return this.operator == "void"; }); - def(AST_Binary, function(pure_getters) { + def(AST_Binary, function(compressor) { switch (this.operator) { case "&&": - return this.left._throw_on_access(pure_getters); + return this.left._dot_throw(compressor); case "||": - return this.left._throw_on_access(pure_getters) - && this.right._throw_on_access(pure_getters); + return this.left._dot_throw(compressor) + && this.right._dot_throw(compressor); default: return false; } }) - def(AST_Assign, function(pure_getters) { + def(AST_Assign, function(compressor) { return this.operator == "=" - && this.right._throw_on_access(pure_getters); + && this.right._dot_throw(compressor); }) - def(AST_Conditional, function(pure_getters) { - return this.consequent._throw_on_access(pure_getters) - || this.alternative._throw_on_access(pure_getters); + def(AST_Conditional, function(compressor) { + return this.consequent._dot_throw(compressor) + || this.alternative._dot_throw(compressor); }) - def(AST_Seq, function(pure_getters) { - return this.cdr._throw_on_access(pure_getters); + def(AST_Sequence, function(compressor) { + return this.expressions[this.expressions.length - 1]._dot_throw(compressor); }); - def(AST_SymbolRef, function(pure_getters) { + def(AST_SymbolRef, function(compressor) { if (this.is_undefined) return true; - if (!is_strict(pure_getters)) return false; + if (!is_strict(compressor)) return false; + if (is_undeclared_ref(this) && this.is_declared(compressor)) return false; + if (this.is_immutable()) return false; var fixed = this.fixed_value(); - return !fixed || fixed._throw_on_access(pure_getters); + return !fixed || fixed._dot_throw(compressor); }); })(function(node, func) { - node.DEFMETHOD("_throw_on_access", func); + node.DEFMETHOD("_dot_throw", func); }); /* -----[ boolean/negation helpers ]----- */ // methods to determine whether an expression has a boolean result type - (function (def){ + (function(def){ var unary_bool = [ "!", "delete" ]; var binary_bool = [ "in", "instanceof", "==", "!=", "===", "!==", "<", "<=", ">=", ">" ]; def(AST_Node, return_false); @@ -1307,8 +1409,8 @@ merge(Compressor.prototype, { def(AST_Assign, function(){ return this.operator == "=" && this.right.is_boolean(); }); - def(AST_Seq, function(){ - return this.cdr.is_boolean(); + def(AST_Sequence, function(){ + return this.expressions[this.expressions.length - 1].is_boolean(); }); def(AST_True, return_true); def(AST_False, return_true); @@ -1317,7 +1419,7 @@ merge(Compressor.prototype, { }); // methods to determine if an expression has a numeric result type - (function (def){ + (function(def){ def(AST_Node, return_false); def(AST_Number, return_true); var unary = makePredicate("+ - ~ ++ --"); @@ -1334,8 +1436,8 @@ merge(Compressor.prototype, { return binary(this.operator.slice(0, -1)) || this.operator == "=" && this.right.is_number(compressor); }); - def(AST_Seq, function(compressor){ - return this.cdr.is_number(compressor); + def(AST_Sequence, function(compressor){ + return this.expressions[this.expressions.length - 1].is_number(compressor); }); def(AST_Conditional, function(compressor){ return this.consequent.is_number(compressor) && this.alternative.is_number(compressor); @@ -1345,7 +1447,7 @@ merge(Compressor.prototype, { }); // methods to determine if an expression has a string result type - (function (def){ + (function(def){ def(AST_Node, return_false); def(AST_String, return_true); def(AST_UnaryPrefix, function(){ @@ -1358,8 +1460,8 @@ merge(Compressor.prototype, { def(AST_Assign, function(compressor){ return (this.operator == "=" || this.operator == "+=") && this.right.is_string(compressor); }); - def(AST_Seq, function(compressor){ - return this.cdr.is_string(compressor); + def(AST_Sequence, function(compressor){ + return this.expressions[this.expressions.length - 1].is_string(compressor); }); def(AST_Conditional, function(compressor){ return this.consequent.is_string(compressor) && this.alternative.is_string(compressor); @@ -1375,7 +1477,7 @@ merge(Compressor.prototype, { if (parent instanceof AST_Assign && parent.left === node) return node; } - (function (def){ + (function(def){ AST_Node.DEFMETHOD("resolve_defines", function(compressor) { if (!compressor.option("global_defs")) return; var def = this._find_defs(compressor, ""); @@ -1401,7 +1503,7 @@ merge(Compressor.prototype, { }); if (value && typeof value == "object") { var props = []; - for (var key in value) { + for (var key in value) if (HOP(value, key)) { props.push(make_node(AST_ObjectKeyVal, orig, { key: key, value: to_node(value[key], orig) @@ -1456,7 +1558,7 @@ merge(Compressor.prototype, { } // methods to evaluate a constant expression - (function (def){ + (function(def){ // If the node has been successfully reduced to a constant, // then its value is returned; otherwise the element itself // is returned. @@ -1464,13 +1566,8 @@ merge(Compressor.prototype, { // descendant of AST_Node. AST_Node.DEFMETHOD("evaluate", function(compressor){ if (!compressor.option("evaluate")) return this; - try { - var val = this._eval(compressor); - return !val || val instanceof RegExp || typeof val != "object" ? val : this; - } catch(ex) { - if (ex !== def) throw ex; - return this; - } + var val = this._eval(compressor); + return !val || val instanceof RegExp || typeof val != "object" ? val : this; }); var unaryPrefix = makePredicate("! ~ - + void"); AST_Node.DEFMETHOD("is_constant", function(){ @@ -1516,27 +1613,28 @@ merge(Compressor.prototype, { def(AST_Statement, function(){ throw new Error(string_template("Cannot evaluate a statement [{file}:{line},{col}]", this.start)); }); - def(AST_Lambda, function(){ - throw def; - }); + def(AST_Lambda, return_this); function ev(node, compressor) { if (!compressor) throw new Error("Compressor must be passed"); return node._eval(compressor); }; - def(AST_Node, function(){ - throw def; // not constant - }); + def(AST_Node, return_this); def(AST_Constant, function(){ return this.getValue(); }); def(AST_Array, function(compressor){ if (compressor.option("unsafe")) { - return this.elements.map(function(element) { - return ev(element, compressor); - }); + var elements = []; + for (var i = 0, len = this.elements.length; i < len; i++) { + var element = this.elements[i]; + var value = ev(element, compressor); + if (element === value) return this; + elements.push(value); + } + return elements; } - throw def; + return this; }); def(AST_Object, function(compressor){ if (compressor.option("unsafe")) { @@ -1548,106 +1646,255 @@ merge(Compressor.prototype, { key = key.name; } else if (key instanceof AST_Node) { key = ev(key, compressor); + if (key === prop.key) return this; } if (typeof Object.prototype[key] === 'function') { - throw def; + return this; } val[key] = ev(prop.value, compressor); + if (val[key] === prop.value) return this; } return val; } - throw def; + return this; }); def(AST_UnaryPrefix, function(compressor){ - var e = this.expression; + // Function would be evaluated to an array and so typeof would + // incorrectly return 'object'. Hence making is a special case. + if (this.operator == "typeof" && this.expression instanceof AST_Function) { + return typeof function(){}; + } + var e = ev(this.expression, compressor); + if (e === this.expression) return this; switch (this.operator) { - case "!": return !ev(e, compressor); + case "!": return !e; case "typeof": - // Function would be evaluated to an array and so typeof would - // incorrectly return 'object'. Hence making is a special case. - if (e instanceof AST_Function) return typeof function(){}; - - e = ev(e, compressor); - // typeof <RegExp> returns "object" or "function" on different platforms // so cannot evaluate reliably - if (e instanceof RegExp) throw def; - + if (e instanceof RegExp) return this; return typeof e; - case "void": return void ev(e, compressor); - case "~": return ~ev(e, compressor); - case "-": return -ev(e, compressor); - case "+": return +ev(e, compressor); + case "void": return void e; + case "~": return ~e; + case "-": return -e; + case "+": return +e; } - throw def; + return this; }); - def(AST_Binary, function(c){ - var left = this.left, right = this.right, result; + def(AST_Binary, function(compressor){ + var left = ev(this.left, compressor); + if (left === this.left) return this; + var right = ev(this.right, compressor); + if (right === this.right) return this; + var result; switch (this.operator) { - case "&&" : result = ev(left, c) && ev(right, c); break; - case "||" : result = ev(left, c) || ev(right, c); break; - case "|" : result = ev(left, c) | ev(right, c); break; - case "&" : result = ev(left, c) & ev(right, c); break; - case "^" : result = ev(left, c) ^ ev(right, c); break; - case "+" : result = ev(left, c) + ev(right, c); break; - case "*" : result = ev(left, c) * ev(right, c); break; - case "/" : result = ev(left, c) / ev(right, c); break; - case "%" : result = ev(left, c) % ev(right, c); break; - case "-" : result = ev(left, c) - ev(right, c); break; - case "<<" : result = ev(left, c) << ev(right, c); break; - case ">>" : result = ev(left, c) >> ev(right, c); break; - case ">>>" : result = ev(left, c) >>> ev(right, c); break; - case "==" : result = ev(left, c) == ev(right, c); break; - case "===" : result = ev(left, c) === ev(right, c); break; - case "!=" : result = ev(left, c) != ev(right, c); break; - case "!==" : result = ev(left, c) !== ev(right, c); break; - case "<" : result = ev(left, c) < ev(right, c); break; - case "<=" : result = ev(left, c) <= ev(right, c); break; - case ">" : result = ev(left, c) > ev(right, c); break; - case ">=" : result = ev(left, c) >= ev(right, c); break; + case "&&" : result = left && right; break; + case "||" : result = left || right; break; + case "|" : result = left | right; break; + case "&" : result = left & right; break; + case "^" : result = left ^ right; break; + case "+" : result = left + right; break; + case "*" : result = left * right; break; + case "/" : result = left / right; break; + case "%" : result = left % right; break; + case "-" : result = left - right; break; + case "<<" : result = left << right; break; + case ">>" : result = left >> right; break; + case ">>>" : result = left >>> right; break; + case "==" : result = left == right; break; + case "===" : result = left === right; break; + case "!=" : result = left != right; break; + case "!==" : result = left !== right; break; + case "<" : result = left < right; break; + case "<=" : result = left <= right; break; + case ">" : result = left > right; break; + case ">=" : result = left >= right; break; default: - throw def; + return this; } - if (isNaN(result) && c.find_parent(AST_With)) { + if (isNaN(result) && compressor.find_parent(AST_With)) { // leave original expression as is - throw def; + return this; } return result; }); def(AST_Conditional, function(compressor){ - return ev(this.condition, compressor) - ? ev(this.consequent, compressor) - : ev(this.alternative, compressor); + var condition = ev(this.condition, compressor); + if (condition === this.condition) return this; + var node = condition ? this.consequent : this.alternative; + var value = ev(node, compressor); + return value === node ? this : value; }); def(AST_SymbolRef, function(compressor){ - if (!compressor.option("reduce_vars") || this._evaluating) throw def; - this._evaluating = true; - try { - var fixed = this.fixed_value(); - if (!fixed) throw def; - var value = ev(fixed, compressor); - if (!HOP(fixed, "_eval")) fixed._eval = function() { - return value; - }; - if (value && typeof value == "object" && this.definition().escaped) throw def; + if (!compressor.option("reduce_vars")) return this; + var fixed = this.fixed_value(); + if (!fixed) return this; + this._eval = return_this; + var value = ev(fixed, compressor); + if (value === fixed) { + delete this._eval; + return this; + } + if (!HOP(fixed, "_eval")) fixed._eval = function() { return value; - } finally { - this._evaluating = false; + }; + if (value && typeof value == "object" && this.definition().escaped) { + delete this._eval; + return this; } + this._eval = fixed._eval; + return value; }); + var global_objs = { + Array: Array, + Math: Math, + Number: Number, + String: String, + }; + function convert_to_predicate(obj) { + for (var key in obj) { + obj[key] = makePredicate(obj[key]); + } + } + var static_values = { + Math: [ + "E", + "LN10", + "LN2", + "LOG2E", + "LOG10E", + "PI", + "SQRT1_2", + "SQRT2", + ], + Number: [ + "MAX_VALUE", + "MIN_VALUE", + "NaN", + "NEGATIVE_INFINITY", + "POSITIVE_INFINITY", + ], + }; + convert_to_predicate(static_values); def(AST_PropAccess, function(compressor){ if (compressor.option("unsafe")) { var key = this.property; if (key instanceof AST_Node) { key = ev(key, compressor); + if (key === this.property) return this; + } + var exp = this.expression; + var val; + if (is_undeclared_ref(exp)) { + if (!(static_values[exp.name] || return_false)(key)) return this; + val = global_objs[exp.name]; + } else { + val = ev(exp, compressor); + if (!val || val === exp || !HOP(val, key)) return this; + } + return val[key]; + } + return this; + }); + var object_fns = [ + "constructor", + "toString", + "valueOf", + ]; + var native_fns = { + Array: [ + "indexOf", + "join", + "lastIndexOf", + "slice", + ].concat(object_fns), + Boolean: object_fns, + Number: [ + "toExponential", + "toFixed", + "toPrecision", + ].concat(object_fns), + RegExp: [ + "test", + ].concat(object_fns), + String: [ + "charAt", + "charCodeAt", + "concat", + "indexOf", + "italics", + "lastIndexOf", + "match", + "replace", + "search", + "slice", + "split", + "substr", + "substring", + "trim", + ].concat(object_fns), + }; + convert_to_predicate(native_fns); + var static_fns = { + Array: [ + "isArray", + ], + Math: [ + "abs", + "acos", + "asin", + "atan", + "ceil", + "cos", + "exp", + "floor", + "log", + "round", + "sin", + "sqrt", + "tan", + "atan2", + "pow", + "max", + "min" + ], + Number: [ + "isFinite", + "isNaN", + ], + String: [ + "fromCharCode", + ], + }; + convert_to_predicate(static_fns); + def(AST_Call, function(compressor){ + var exp = this.expression; + if (compressor.option("unsafe") && exp instanceof AST_PropAccess) { + var key = exp.property; + if (key instanceof AST_Node) { + key = ev(key, compressor); + if (key === exp.property) return this; + } + var val; + var e = exp.expression; + if (is_undeclared_ref(e)) { + if (!(static_fns[e.name] || return_false)(key)) return this; + val = global_objs[e.name]; + } else { + val = ev(e, compressor); + if (val === e || !(val && native_fns[val.constructor.name] || return_false)(key)) return this; } - var val = ev(this.expression, compressor); - if (val && HOP(val, key)) { - return val[key]; + var args = []; + for (var i = 0, len = this.args.length; i < len; i++) { + var arg = this.args[i]; + var value = ev(arg, compressor); + if (arg === value) return this; + args.push(value); } + return val[key].apply(val, args); } - throw def; + return this; }); + def(AST_New, return_this); })(function(node, func){ node.DEFMETHOD("_eval", func); }); @@ -1684,10 +1931,10 @@ merge(Compressor.prototype, { return this.expression; return basic_negation(this); }); - def(AST_Seq, function(compressor){ - var self = this.clone(); - self.cdr = self.cdr.negate(compressor); - return self; + def(AST_Sequence, function(compressor){ + var expressions = this.expressions.slice(); + expressions.push(expressions.pop().negate(compressor)); + return make_sequence(this, expressions); }); def(AST_Conditional, function(compressor, first_in_statement){ var self = this.clone(); @@ -1811,8 +2058,9 @@ merge(Compressor.prototype, { || this.expression.has_side_effects(compressor); }); def(AST_SymbolRef, function(compressor){ - return this.undeclared(); + return !this.is_declared(compressor); }); + def(AST_SymbolDeclaration, return_false); def(AST_Object, function(compressor){ return any(this.properties, compressor); }); @@ -1831,14 +2079,44 @@ merge(Compressor.prototype, { || this.expression.has_side_effects(compressor) || this.property.has_side_effects(compressor); }); - def(AST_Seq, function(compressor){ - return this.car.has_side_effects(compressor) - || this.cdr.has_side_effects(compressor); + def(AST_Sequence, function(compressor){ + return this.expressions.some(function(expression, index) { + return expression.has_side_effects(compressor); + }); }); })(function(node, func){ node.DEFMETHOD("has_side_effects", func); }); + // determine if expression is constant + (function(def){ + function all(list) { + for (var i = list.length; --i >= 0;) + if (!list[i].is_constant_expression()) + return false; + return true; + } + def(AST_Node, return_false); + def(AST_Constant, return_true); + def(AST_Unary, function(){ + return this.expression.is_constant_expression(); + }); + def(AST_Binary, function(){ + return this.left.is_constant_expression() && this.right.is_constant_expression(); + }); + def(AST_Array, function(){ + return all(this.elements); + }); + def(AST_Object, function(){ + return all(this.properties); + }); + def(AST_ObjectProperty, function(){ + return this.value.is_constant_expression(); + }); + })(function(node, func){ + node.DEFMETHOD("is_constant_expression", func); + }); + // tell me if a statement aborts function aborts(thing) { return thing && thing.aborts(); @@ -1883,12 +2161,12 @@ merge(Compressor.prototype, { }); OPT(AST_Block, function(self, compressor){ - self.body = tighten_body(self.body, compressor); + tighten_body(self.body, compressor); return self; }); OPT(AST_BlockStatement, function(self, compressor){ - self.body = tighten_body(self.body, compressor); + tighten_body(self.body, compressor); switch (self.body.length) { case 1: return self.body[0]; case 0: return make_node(AST_EmptyStatement, self); @@ -1897,269 +2175,290 @@ merge(Compressor.prototype, { }); AST_Scope.DEFMETHOD("drop_unused", function(compressor){ + if (!compressor.option("unused")) return; + if (compressor.has_directive("use asm")) return; var self = this; - if (compressor.has_directive("use asm")) return self; - var toplevel = compressor.option("toplevel"); - if (compressor.option("unused") - && (!(self instanceof AST_Toplevel) || toplevel) - && !self.uses_eval - && !self.uses_with) { - var assign_as_unused = !/keep_assign/.test(compressor.option("unused")); - var drop_funcs = /funcs/.test(toplevel); - var drop_vars = /vars/.test(toplevel); - if (!(self instanceof AST_Toplevel) || toplevel == true) { - drop_funcs = drop_vars = true; - } - var in_use = []; - var in_use_ids = Object.create(null); // avoid expensive linear scans of in_use - if (self instanceof AST_Toplevel && compressor.top_retain) { - self.variables.each(function(def) { - if (compressor.top_retain(def) && !(def.id in in_use_ids)) { - in_use_ids[def.id] = true; - in_use.push(def); + if (self.uses_eval || self.uses_with) return; + var drop_funcs = !(self instanceof AST_Toplevel) || compressor.toplevel.funcs; + var drop_vars = !(self instanceof AST_Toplevel) || compressor.toplevel.vars; + if (!drop_funcs && !drop_vars) return; + var assign_as_unused = /keep_assign/.test(compressor.option("unused")) ? return_false : function(node) { + if (node instanceof AST_Assign && (node.write_only || node.operator == "=")) { + return node.left; + } + if (node instanceof AST_Unary && node.write_only) return node.expression; + }; + var in_use = []; + var in_use_ids = Object.create(null); // avoid expensive linear scans of in_use + if (self instanceof AST_Toplevel && compressor.top_retain) { + self.variables.each(function(def) { + if (compressor.top_retain(def) && !(def.id in in_use_ids)) { + in_use_ids[def.id] = true; + in_use.push(def); + } + }); + } + var var_defs_by_id = new Dictionary(); + var initializations = new Dictionary(); + // pass 1: find out which symbols are directly used in + // this scope (not in nested scopes). + var scope = this; + var tw = new TreeWalker(function(node, descend){ + if (node !== self) { + if (node instanceof AST_Defun) { + if (!drop_funcs && scope === self) { + var node_def = node.name.definition(); + if (!(node_def.id in in_use_ids)) { + in_use_ids[node_def.id] = true; + in_use.push(node_def); + } } - }); - } - var initializations = new Dictionary(); - // pass 1: find out which symbols are directly used in - // this scope (not in nested scopes). - var scope = this; - var tw = new TreeWalker(function(node, descend){ - if (node !== self) { - if (node instanceof AST_Defun) { - if (!drop_funcs && scope === self) { - var node_def = node.name.definition(); + initializations.add(node.name.name, node); + return true; // don't go in nested scopes + } + if (node instanceof AST_Definitions && scope === self) { + node.definitions.forEach(function(def){ + var node_def = def.name.definition(); + if (def.name instanceof AST_SymbolVar) { + var_defs_by_id.add(node_def.id, def); + } + if (!drop_vars) { if (!(node_def.id in in_use_ids)) { in_use_ids[node_def.id] = true; in_use.push(node_def); } } - initializations.add(node.name.name, node); - return true; // don't go in nested scopes - } - if (node instanceof AST_Definitions && scope === self) { - node.definitions.forEach(function(def){ - if (!drop_vars) { - var node_def = def.name.definition(); - if (!(node_def.id in in_use_ids)) { - in_use_ids[node_def.id] = true; - in_use.push(node_def); - } + if (def.value) { + initializations.add(def.name.name, def.value); + if (def.value.has_side_effects(compressor)) { + def.value.walk(tw); } - if (def.value) { - initializations.add(def.name.name, def.value); - if (def.value.has_side_effects(compressor)) { - def.value.walk(tw); - } - } - }); - return true; - } - if (assign_as_unused - && node instanceof AST_Assign - && node.operator == "=" - && node.left instanceof AST_SymbolRef - && !is_reference_const(node.left) - && scope === self) { - node.right.walk(tw); - return true; - } - if (node instanceof AST_SymbolRef) { - var node_def = node.definition(); - if (!(node_def.id in in_use_ids)) { - in_use_ids[node_def.id] = true; - in_use.push(node_def); } - return true; - } - if (node instanceof AST_Scope) { - var save_scope = scope; - scope = node; - descend(); - scope = save_scope; - return true; + }); + return true; + } + if (assign_as_unused(node) instanceof AST_SymbolRef && scope === self) { + if (node instanceof AST_Assign) node.right.walk(tw); + return true; + } + if (node instanceof AST_SymbolRef) { + var node_def = node.definition(); + if (!(node_def.id in in_use_ids)) { + in_use_ids[node_def.id] = true; + in_use.push(node_def); } + return true; } - }); - self.walk(tw); - // pass 2: for every used symbol we need to walk its - // initialization code to figure out if it uses other - // symbols (that may not be in_use). - for (var i = 0; i < in_use.length; ++i) { - in_use[i].orig.forEach(function(decl){ - // undeclared globals will be instanceof AST_SymbolRef - var init = initializations.get(decl.name); - if (init) init.forEach(function(init){ - var tw = new TreeWalker(function(node){ - if (node instanceof AST_SymbolRef) { - var node_def = node.definition(); - if (!(node_def.id in in_use_ids)) { - in_use_ids[node_def.id] = true; - in_use.push(node_def); - } + if (node instanceof AST_Scope) { + var save_scope = scope; + scope = node; + descend(); + scope = save_scope; + return true; + } + } + }); + self.walk(tw); + // pass 2: for every used symbol we need to walk its + // initialization code to figure out if it uses other + // symbols (that may not be in_use). + for (var i = 0; i < in_use.length; ++i) { + in_use[i].orig.forEach(function(decl){ + // undeclared globals will be instanceof AST_SymbolRef + var init = initializations.get(decl.name); + if (init) init.forEach(function(init){ + var tw = new TreeWalker(function(node){ + if (node instanceof AST_SymbolRef) { + var node_def = node.definition(); + if (!(node_def.id in in_use_ids)) { + in_use_ids[node_def.id] = true; + in_use.push(node_def); } - }); - init.walk(tw); + } }); + init.walk(tw); }); - } - // pass 3: we should drop declarations not in_use - var tt = new TreeTransformer( - function before(node, descend, in_list) { - if (node instanceof AST_Function - && node.name - && !compressor.option("keep_fnames")) { - var def = node.name.definition(); - // any declarations with same name will overshadow - // name of this anonymous function and can therefore - // never be used anywhere - if (!(def.id in in_use_ids) || def.orig.length > 1) - node.name = null; - } - if (node instanceof AST_Lambda && !(node instanceof AST_Accessor)) { - var trim = !compressor.option("keep_fargs"); - for (var a = node.argnames, i = a.length; --i >= 0;) { - var sym = a[i]; - if (!(sym.definition().id in in_use_ids)) { - sym.__unused = true; - if (trim) { - a.pop(); - compressor[sym.unreferenced() ? "warn" : "info"]("Dropping unused function argument {name} [{file}:{line},{col}]", { - name : sym.name, - file : sym.start.file, - line : sym.start.line, - col : sym.start.col - }); - } - } - else { - trim = false; + }); + } + // pass 3: we should drop declarations not in_use + var tt = new TreeTransformer( + function before(node, descend, in_list) { + if (node instanceof AST_Function + && node.name + && !compressor.option("keep_fnames")) { + var def = node.name.definition(); + // any declarations with same name will overshadow + // name of this anonymous function and can therefore + // never be used anywhere + if (!(def.id in in_use_ids) || def.orig.length > 1) + node.name = null; + } + if (node instanceof AST_Lambda && !(node instanceof AST_Accessor)) { + var trim = !compressor.option("keep_fargs"); + for (var a = node.argnames, i = a.length; --i >= 0;) { + var sym = a[i]; + if (!(sym.definition().id in in_use_ids)) { + sym.__unused = true; + if (trim) { + a.pop(); + compressor[sym.unreferenced() ? "warn" : "info"]("Dropping unused function argument {name} [{file}:{line},{col}]", template(sym)); } } - } - if (drop_funcs && node instanceof AST_Defun && node !== self) { - if (!(node.name.definition().id in in_use_ids)) { - compressor[node.name.unreferenced() ? "warn" : "info"]("Dropping unused function {name} [{file}:{line},{col}]", { - name : node.name.name, - file : node.name.start.file, - line : node.name.start.line, - col : node.name.start.col - }); - return make_node(AST_EmptyStatement, node); + else { + trim = false; } - return node; } - if (drop_vars && node instanceof AST_Definitions && !(tt.parent() instanceof AST_ForIn && tt.parent().init === node)) { - var def = node.definitions.filter(function(def){ - if (def.value) def.value = def.value.transform(tt); - var sym = def.name.definition(); - if (sym.id in in_use_ids) return true; - if (sym.orig[0] instanceof AST_SymbolCatch) { - def.value = def.value && def.value.drop_side_effect_free(compressor); - return true; - } - var w = { - name : def.name.name, - file : def.name.start.file, - line : def.name.start.line, - col : def.name.start.col - }; - if (def.value && (def._unused_side_effects = def.value.drop_side_effect_free(compressor))) { - compressor.warn("Side effects in initialization of unused variable {name} [{file}:{line},{col}]", w); - return true; + } + if (drop_funcs && node instanceof AST_Defun && node !== self) { + if (!(node.name.definition().id in in_use_ids)) { + compressor[node.name.unreferenced() ? "warn" : "info"]("Dropping unused function {name} [{file}:{line},{col}]", template(node.name)); + return make_node(AST_EmptyStatement, node); + } + return node; + } + if (drop_vars && node instanceof AST_Definitions && !(tt.parent() instanceof AST_ForIn && tt.parent().init === node)) { + // place uninitialized names at the start + var body = [], head = [], tail = []; + // for unused names whose initialization has + // side effects, we can cascade the init. code + // into the next one, or next statement. + var side_effects = []; + node.definitions.forEach(function(def) { + if (def.value) def.value = def.value.transform(tt); + var sym = def.name.definition(); + if (sym.id in in_use_ids) { + if (def.name instanceof AST_SymbolVar) { + var var_defs = var_defs_by_id.get(sym.id); + if (var_defs.length > 1 && !def.value) { + compressor.warn("Dropping duplicated definition of variable {name} [{file}:{line},{col}]", template(def.name)); + remove(var_defs, def); + remove(sym.orig, def.name); + return; + } } - compressor[def.name.unreferenced() ? "warn" : "info"]("Dropping unused variable {name} [{file}:{line},{col}]", w); - return false; - }); - // place uninitialized names at the start - def = mergeSort(def, function(a, b){ - if (!a.value && b.value) return -1; - if (!b.value && a.value) return 1; - return 0; - }); - // for unused names whose initialization has - // side effects, we can cascade the init. code - // into the next one, or next statement. - var side_effects = []; - for (var i = 0; i < def.length;) { - var x = def[i]; - if (x._unused_side_effects) { - side_effects.push(x._unused_side_effects); - def.splice(i, 1); - } else { + if (def.value) { if (side_effects.length > 0) { - side_effects.push(x.value); - x.value = AST_Seq.from_array(side_effects); + if (tail.length > 0) { + merge_sequence(side_effects, def.value); + def.value = make_sequence(def.value, side_effects); + } else { + body.push(make_node(AST_SimpleStatement, node, { + body: make_sequence(node, side_effects) + })); + } side_effects = []; } - ++i; + tail.push(def); + } else { + head.push(def); } - } - if (side_effects.length > 0) { - side_effects = make_node(AST_BlockStatement, node, { - body: [ make_node(AST_SimpleStatement, node, { - body: AST_Seq.from_array(side_effects) - }) ] - }); + } else if (sym.orig[0] instanceof AST_SymbolCatch) { + var value = def.value && def.value.drop_side_effect_free(compressor); + if (value) merge_sequence(side_effects, value); + def.value = null; + head.push(def); } else { - side_effects = null; - } - if (def.length == 0 && !side_effects) { - return make_node(AST_EmptyStatement, node); - } - if (def.length == 0) { - return in_list ? MAP.splice(side_effects.body) : side_effects; + var value = def.value && def.value.drop_side_effect_free(compressor); + if (value) { + compressor.warn("Side effects in initialization of unused variable {name} [{file}:{line},{col}]", template(def.name)); + merge_sequence(side_effects, value); + } else { + compressor[def.name.unreferenced() ? "warn" : "info"]("Dropping unused variable {name} [{file}:{line},{col}]", template(def.name)); + } + remove(sym.orig, def.name); } - node.definitions = def; - if (side_effects) { - side_effects.body.unshift(node); - return in_list ? MAP.splice(side_effects.body) : side_effects; + }); + if (head.length == 0 && tail.length == 1 && tail[0].name instanceof AST_SymbolVar) { + var var_defs = var_defs_by_id.get(tail[0].name.definition().id); + if (var_defs.length > 1) { + var def = tail.pop(); + compressor.warn("Converting duplicated definition of variable {name} to assignment [{file}:{line},{col}]", template(def.name)); + remove(var_defs, def); + remove(def.name.definition().orig, def.name); + side_effects.unshift(make_node(AST_Assign, def, { + operator: "=", + left: make_node(AST_SymbolRef, def.name, def.name), + right: def.value + })); } - return node; } - if (drop_vars && assign_as_unused - && node instanceof AST_Assign - && node.operator == "=" - && node.left instanceof AST_SymbolRef) { - var def = node.left.definition(); - if (!(def.id in in_use_ids) - && self.variables.get(def.name) === def) { + if (head.length > 0 || tail.length > 0) { + 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) + })); + } + switch (body.length) { + case 0: + return in_list ? MAP.skip : make_node(AST_EmptyStatement, node); + case 1: + return body[0]; + default: + return in_list ? MAP.splice(body) : make_node(AST_BlockStatement, node, { + body: body + }); + } + } + if (drop_vars) { + var def = assign_as_unused(node); + if (def instanceof AST_SymbolRef + && !((def = def.definition()).id in in_use_ids) + && self.variables.get(def.name) === def) { + if (node instanceof AST_Assign) { return maintain_this_binding(tt.parent(), node, node.right.transform(tt)); } + return make_node(AST_Number, node, { + value: 0 + }); } - // certain combination of unused name + side effect leads to: - // https://github.com/mishoo/UglifyJS2/issues/44 - // https://github.com/mishoo/UglifyJS2/issues/1830 - // that's an invalid AST. - // We fix it at this stage by moving the `var` outside the `for`. - if (node instanceof AST_For) { - descend(node, this); - if (node.init instanceof AST_BlockStatement) { - var block = node.init; - node.init = block.body.pop(); - block.body.push(node); - return in_list ? MAP.splice(block.body) : block; - } else if (is_empty(node.init)) { - node.init = null; - } - return node; + } + // certain combination of unused name + side effect leads to: + // https://github.com/mishoo/UglifyJS2/issues/44 + // https://github.com/mishoo/UglifyJS2/issues/1830 + // https://github.com/mishoo/UglifyJS2/issues/1838 + // that's an invalid AST. + // We fix it at this stage by moving the `var` outside the `for`. + if (node instanceof AST_For) { + descend(node, this); + if (node.init instanceof AST_BlockStatement) { + var block = node.init; + node.init = block.body.pop(); + block.body.push(node); + return in_list ? MAP.splice(block.body) : block; + } else if (node.init instanceof AST_SimpleStatement) { + node.init = node.init.body; + } else if (is_empty(node.init)) { + node.init = null; } - if (node instanceof AST_LabeledStatement && node.body instanceof AST_For) { - descend(node, this); - if (node.body instanceof AST_BlockStatement) { - var block = node.body; - node.body = block.body.pop(); - block.body.push(node); - return in_list ? MAP.splice(block.body) : block; - } - return node; + return node; + } + if (node instanceof AST_LabeledStatement && node.body instanceof AST_For) { + descend(node, this); + if (node.body instanceof AST_BlockStatement) { + var block = node.body; + node.body = block.body.pop(); + block.body.push(node); + return in_list ? MAP.splice(block.body) : block; } - if (node instanceof AST_Scope && node !== self) - return node; + return node; } - ); - self.transform(tt); - } + if (node instanceof AST_Scope && node !== self) + return node; + + function template(sym) { + return { + name : sym.name, + file : sym.start.file, + line : sym.start.line, + col : sym.start.col + }; + } + } + ); + self.transform(tt); }); AST_Scope.DEFMETHOD("hoist_declarations", function(compressor){ @@ -2189,11 +2488,12 @@ merge(Compressor.prototype, { dirs.push(node); return make_node(AST_EmptyStatement, node); } - if (node instanceof AST_Defun && hoist_funs) { + if (hoist_funs && node instanceof AST_Defun + && (tt.parent() === self || !compressor.has_directive("use strict"))) { hoisted.push(node); return make_node(AST_EmptyStatement, node); } - if (node instanceof AST_Var && hoist_vars) { + if (hoist_vars && node instanceof AST_Var) { node.definitions.forEach(function(def){ vars.set(def.name.name, def); ++vars_found; @@ -2254,8 +2554,8 @@ merge(Compressor.prototype, { self.body.splice(i, 1); continue; } - if (expr instanceof AST_Seq - && (assign = expr.car) instanceof AST_Assign + if (expr instanceof AST_Sequence + && (assign = expr.expressions[0]) instanceof AST_Assign && assign.operator == "=" && (sym = assign.left) instanceof AST_Symbol && vars.has(sym.name)) @@ -2265,7 +2565,7 @@ merge(Compressor.prototype, { def.value = assign.right; remove(defs, def); defs.push(def); - self.body[i].body = expr.cdr; + self.body[i].body = make_sequence(expr, expr.expressions.slice(1)); continue; } } @@ -2299,12 +2599,14 @@ merge(Compressor.prototype, { // if all elements were dropped. Note: original array may be // returned if nothing changed. function trim(nodes, compressor, first_in_statement) { + var len = nodes.length; + if (!len) return null; var ret = [], changed = false; - for (var i = 0, len = nodes.length; i < len; i++) { + for (var i = 0; i < len; i++) { var node = nodes[i].drop_side_effect_free(compressor, first_in_statement); changed |= node !== nodes[i]; if (node) { - ret.push(node); + merge_sequence(ret, node); first_in_statement = false; } } @@ -2319,7 +2621,7 @@ merge(Compressor.prototype, { if (this.expression instanceof AST_Function && (!this.expression.name || !this.expression.name.definition().references.length)) { var node = this.clone(); - node.expression = node.expression.process_expression(false, compressor); + node.expression.process_expression(false, compressor); return node; } return this; @@ -2329,7 +2631,7 @@ merge(Compressor.prototype, { this.pure.value = this.pure.value.replace(/[@#]__PURE__/g, ' '); } var args = trim(this.args, compressor, first_in_statement); - return args && AST_Seq.from_array(args); + return args && make_sequence(this, args); }); def(AST_Accessor, return_null); def(AST_Function, return_null); @@ -2346,13 +2648,13 @@ merge(Compressor.prototype, { default: var left = this.left.drop_side_effect_free(compressor, first_in_statement); if (!left) return this.right.drop_side_effect_free(compressor, first_in_statement); - return make_node(AST_Seq, this, { - car: left, - cdr: right - }); + return make_sequence(this, [ left, right ]); } }); - def(AST_Assign, return_this); + def(AST_Assign, function(compressor){ + this.write_only = !this.left.has_side_effects(compressor); + return this; + }); def(AST_Conditional, function(compressor){ var consequent = this.consequent.drop_side_effect_free(compressor); var alternative = this.alternative.drop_side_effect_free(compressor); @@ -2373,7 +2675,10 @@ merge(Compressor.prototype, { return node; }); def(AST_Unary, function(compressor, first_in_statement){ - if (unary_side_effects(this.operator)) return this; + if (unary_side_effects(this.operator)) { + this.write_only = !this.expression.has_side_effects(compressor); + return this; + } if (this.operator == "typeof" && this.expression instanceof AST_SymbolRef) return null; var expression = this.expression.drop_side_effect_free(compressor, first_in_statement); if (first_in_statement @@ -2387,19 +2692,19 @@ merge(Compressor.prototype, { } return expression; }); - def(AST_SymbolRef, function() { - return this.undeclared() ? this : null; + def(AST_SymbolRef, function(compressor) { + return this.is_declared(compressor) ? null : this; }); def(AST_Object, function(compressor, first_in_statement){ var values = trim(this.properties, compressor, first_in_statement); - return values && AST_Seq.from_array(values); + return values && make_sequence(this, values); }); def(AST_ObjectProperty, function(compressor, first_in_statement){ return this.value.drop_side_effect_free(compressor, first_in_statement); }); def(AST_Array, function(compressor, first_in_statement){ var values = trim(this.elements, compressor, first_in_statement); - return values && AST_Seq.from_array(values); + return values && make_sequence(this, values); }); def(AST_Dot, function(compressor, first_in_statement){ if (this.expression.may_throw_on_access(compressor)) return this; @@ -2411,19 +2716,15 @@ merge(Compressor.prototype, { if (!expression) return this.property.drop_side_effect_free(compressor, first_in_statement); var property = this.property.drop_side_effect_free(compressor); if (!property) return expression; - return make_node(AST_Seq, this, { - car: expression, - cdr: property - }); + return make_sequence(this, [ expression, property ]); }); - def(AST_Seq, function(compressor){ - var cdr = this.cdr.drop_side_effect_free(compressor); - if (cdr === this.cdr) return this; - if (!cdr) return this.car; - return make_node(AST_Seq, this, { - car: this.car, - cdr: cdr - }); + def(AST_Sequence, function(compressor){ + var last = this.expressions[this.expressions.length - 1]; + var expr = last.drop_side_effect_free(compressor); + if (expr === last) return this; + var expressions = this.expressions.slice(0, -1); + if (expr) merge_sequence(expressions, expr); + return make_sequence(this, expressions); }); })(function(node, func){ node.DEFMETHOD("drop_side_effect_free", func); @@ -2782,7 +3083,7 @@ merge(Compressor.prototype, { }); OPT(AST_Try, function(self, compressor){ - self.body = tighten_body(self.body, compressor); + tighten_body(self.body, compressor); if (self.bcatch && self.bfinally && all(self.bfinally.body, is_empty)) self.bfinally = null; if (all(self.body, is_empty)) { var body = []; @@ -2814,7 +3115,7 @@ merge(Compressor.prototype, { return a; }, []); if (assignments.length == 0) return null; - return AST_Seq.from_array(assignments); + return make_sequence(this, assignments); }); OPT(AST_Definitions, function(self, compressor){ @@ -2825,33 +3126,18 @@ merge(Compressor.prototype, { OPT(AST_Call, function(self, compressor){ var exp = self.expression; - if (compressor.option("reduce_vars") - && exp instanceof AST_SymbolRef) { - var def = exp.definition(); - var fixed = exp.fixed_value(); - if (fixed instanceof AST_Defun) { - def.fixed = fixed = make_node(AST_Function, fixed, fixed).clone(true); - } - if (fixed instanceof AST_Function) { - exp = fixed; - if (compressor.option("unused") - && def.references.length == 1 - && !(def.scope.uses_arguments - && def.orig[0] instanceof AST_SymbolFunarg) - && !def.scope.uses_eval - && compressor.find_parent(AST_Scope) === def.scope) { - self.expression = exp; - } - } - } + var fn = exp; if (compressor.option("unused") - && exp instanceof AST_Function - && !exp.uses_arguments - && !exp.uses_eval) { + && (fn instanceof AST_Function + || compressor.option("reduce_vars") + && fn instanceof AST_SymbolRef + && (fn = fn.fixed_value()) instanceof AST_Function) + && !fn.uses_arguments + && !fn.uses_eval) { var pos = 0, last = 0; for (var i = 0, len = self.args.length; i < len; i++) { - var trim = i >= exp.argnames.length; - if (trim || exp.argnames[i].__unused) { + var trim = i >= fn.argnames.length; + if (trim || fn.argnames[i].__unused) { var node = self.args[i].drop_side_effect_free(compressor); if (node) { self.args[pos++] = node; @@ -2869,7 +3155,7 @@ merge(Compressor.prototype, { self.args.length = last; } if (compressor.option("unsafe")) { - if (exp instanceof AST_SymbolRef && exp.undeclared()) { + if (is_undeclared_ref(exp)) { switch (exp.name) { case "Array": if (self.args.length != 1) { @@ -2913,62 +3199,6 @@ merge(Compressor.prototype, { operator: "!" }).optimize(compressor); break; - case "Function": - // new Function() => function(){} - if (self.args.length == 0) return make_node(AST_Function, self, { - argnames: [], - body: [] - }); - if (all(self.args, function(x){ return x instanceof AST_String })) { - // quite a corner-case, but we can handle it: - // https://github.com/mishoo/UglifyJS2/issues/203 - // if the code argument is a constant, then we can minify it. - try { - var code = "(function(" + self.args.slice(0, -1).map(function(arg){ - return arg.value; - }).join(",") + "){" + self.args[self.args.length - 1].value + "})()"; - var ast = parse(code); - ast.figure_out_scope({ screw_ie8: compressor.option("screw_ie8") }); - var comp = new Compressor(compressor.options); - ast = ast.transform(comp); - ast.figure_out_scope({ screw_ie8: compressor.option("screw_ie8") }); - ast.mangle_names(); - var fun; - try { - ast.walk(new TreeWalker(function(node){ - if (node instanceof AST_Lambda) { - fun = node; - throw ast; - } - })); - } catch(ex) { - if (ex !== ast) throw ex; - }; - if (!fun) return self; - var args = fun.argnames.map(function(arg, i){ - return make_node(AST_String, self.args[i], { - value: arg.print_to_string() - }); - }); - var code = OutputStream(); - AST_BlockStatement.prototype._codegen.call(fun, fun, code); - code = code.toString().replace(/^\{|\}$/g, ""); - args.push(make_node(AST_String, self.args[self.args.length - 1], { - value: code - })); - self.args = args; - return self; - } catch(ex) { - if (ex instanceof JS_Parse_Error) { - compressor.warn("Error parsing code passed to new Function [{file}:{line},{col}]", self.args[self.args.length - 1].start); - compressor.warn(ex.toString()); - } else { - console.log(ex); - throw ex; - } - } - } - break; } } else if (exp instanceof AST_Dot && exp.property == "toString" && self.args.length == 0) { @@ -3051,17 +3281,116 @@ merge(Compressor.prototype, { } } } + if (compressor.option("unsafe_Func") + && is_undeclared_ref(exp) + && exp.name == "Function") { + // new Function() => function(){} + if (self.args.length == 0) return make_node(AST_Function, self, { + argnames: [], + body: [] + }); + if (all(self.args, function(x) { + return x instanceof AST_String; + })) { + // quite a corner-case, but we can handle it: + // https://github.com/mishoo/UglifyJS2/issues/203 + // if the code argument is a constant, then we can minify it. + try { + var code = "n(function(" + self.args.slice(0, -1).map(function(arg) { + return arg.value; + }).join(",") + "){" + self.args[self.args.length - 1].value + "})"; + var ast = parse(code); + var mangle = { ie8: compressor.option("ie8") }; + ast.figure_out_scope(mangle); + var comp = new Compressor(compressor.options); + ast = ast.transform(comp); + ast.figure_out_scope(mangle); + base54.reset(); + ast.compute_char_frequency(mangle); + ast.mangle_names(mangle); + var fun; + ast.walk(new TreeWalker(function(node) { + if (fun) return true; + if (node instanceof AST_Lambda) { + fun = node; + return true; + } + })); + var code = OutputStream(); + AST_BlockStatement.prototype._codegen.call(fun, fun, code); + self.args = [ + make_node(AST_String, self, { + value: fun.argnames.map(function(arg) { + return arg.print_to_string(); + }).join(",") + }), + make_node(AST_String, self.args[self.args.length - 1], { + value: code.get().replace(/^\{|\}$/g, "") + }) + ]; + return self; + } catch (ex) { + if (ex instanceof JS_Parse_Error) { + compressor.warn("Error parsing code passed to new Function [{file}:{line},{col}]", self.args[self.args.length - 1].start); + compressor.warn(ex.toString()); + } else { + throw ex; + } + } + } + } + var stat = fn instanceof AST_Function && fn.body[0]; + if (compressor.option("inline") && stat instanceof AST_Return) { + var value = stat.value; + if (!value || value.is_constant_expression()) { + var args = self.args.concat(value || make_node(AST_Undefined, self)); + return make_sequence(self, args).optimize(compressor); + } + } if (exp instanceof AST_Function) { - if (exp.body[0] instanceof AST_Return) { - var value = exp.body[0].value; - if (!value || value.is_constant()) { - var args = self.args.concat(value || make_node(AST_Undefined, self)); - return AST_Seq.from_array(args).transform(compressor); + if (compressor.option("inline") + && !exp.name + && !exp.uses_arguments + && !exp.uses_eval + && exp.body.length == 1 + && all(exp.argnames, function(arg) { + return arg.__unused; + }) + && !self.has_pure_annotation(compressor)) { + var value; + if (stat instanceof AST_Return) { + value = stat.value; + } else if (stat instanceof AST_SimpleStatement) { + value = make_node(AST_UnaryPrefix, stat, { + operator: "void", + expression: stat.body + }); + } + if (value) { + var tw = new TreeWalker(function(node) { + if (!value) return true; + if (node instanceof AST_SymbolRef) { + var ref = node.scope.find_variable(node); + if (ref && ref.scope.parent_scope === fn.parent_scope) { + value = null; + return true; + } + } + if (node instanceof AST_This && !tw.find_parent(AST_Scope)) { + value = null; + return true; + } + }); + value.walk(tw); + } + if (value) { + var args = self.args.concat(value); + return make_sequence(self, args).optimize(compressor); } } if (compressor.option("side_effects") && all(exp.body, is_empty)) { var args = self.args.concat(make_node(AST_Undefined, self)); - return AST_Seq.from_array(args).transform(compressor); + return make_sequence(self, args).optimize(compressor); } } if (compressor.option("drop_console")) { @@ -3070,9 +3399,7 @@ merge(Compressor.prototype, { while (name.expression) { name = name.expression; } - if (name instanceof AST_SymbolRef - && name.name == "console" - && name.undeclared()) { + if (is_undeclared_ref(name) && name.name == "console") { return make_node(AST_Undefined, self).optimize(compressor); } } @@ -3082,13 +3409,18 @@ merge(Compressor.prototype, { && is_iife_call(self)) { return self.negate(compressor, true); } + var ev = self.evaluate(compressor); + if (ev !== self) { + ev = make_node_from_constant(ev, self).optimize(compressor); + return best_of(compressor, ev, self); + } return self; }); OPT(AST_New, function(self, compressor){ if (compressor.option("unsafe")) { var exp = self.expression; - if (exp instanceof AST_SymbolRef && exp.undeclared()) { + if (is_undeclared_ref(exp)) { switch (exp.name) { case "Object": case "RegExp": @@ -3102,73 +3434,118 @@ merge(Compressor.prototype, { return self; }); - OPT(AST_Seq, function(self, compressor){ - if (!compressor.option("side_effects")) + OPT(AST_Sequence, function(self, compressor){ + if (!compressor.option("side_effects")) return self; + var expressions = []; + filter_for_side_effects(); + var end = expressions.length - 1; + trim_right_for_undefined(); + if (end > 0 && compressor.option("cascade")) trim_left_for_assignment(); + if (end == 0) { + self = maintain_this_binding(compressor.parent(), self, expressions[0]); + if (!(self instanceof AST_Sequence)) self = self.optimize(compressor); return self; - self.car = self.car.drop_side_effect_free(compressor, first_in_statement(compressor)); - if (!self.car) return maintain_this_binding(compressor.parent(), self, self.cdr); - if (compressor.option("cascade")) { - var left; - if (self.car instanceof AST_Assign - && !self.car.left.has_side_effects(compressor)) { - left = self.car.left; - } else if (self.car instanceof AST_Unary - && (self.car.operator == "++" || self.car.operator == "--")) { - left = self.car.expression; - } - if (left - && !(left instanceof AST_SymbolRef - && (left.definition().orig[0] instanceof AST_SymbolLambda - || is_reference_const(left)))) { - var parent, field; - var cdr = self.cdr; + } + self.expressions = expressions; + return self; + + function filter_for_side_effects() { + var first = first_in_statement(compressor); + var last = self.expressions.length - 1; + self.expressions.forEach(function(expr, index) { + if (index < last) expr = expr.drop_side_effect_free(compressor, first); + if (expr) { + merge_sequence(expressions, expr); + first = false; + } + }); + } + + function trim_right_for_undefined() { + while (end > 0 && is_undefined(expressions[end], compressor)) end--; + if (end < expressions.length - 1) { + expressions[end] = make_node(AST_UnaryPrefix, self, { + operator : "void", + expression : expressions[end] + }); + expressions.length = end + 1; + } + } + + function trim_left_for_assignment() { + for (var i = 0, j = 1; j <= end; j++) { + var left = expressions[i]; + var cdr = expressions[j]; + if (left instanceof AST_Assign + && !left.left.has_side_effects(compressor)) { + left = left.left; + } else if (left instanceof AST_Unary + && (left.operator == "++" || left.operator == "--")) { + left = left.expression; + } else left = null; + if (!left || is_lhs_read_only(left)) { + expressions[++i] = cdr; + continue; + } + var parent = null, field; + expressions[j] = cdr = cdr.clone(); while (true) { if (cdr.equivalent_to(left)) { - var car = self.car instanceof AST_UnaryPostfix ? make_node(AST_UnaryPrefix, self.car, { - operator: self.car.operator, - expression: left - }) : self.car; + var car = expressions[i]; + if (car instanceof AST_UnaryPostfix) { + car = make_node(AST_UnaryPrefix, car, { + operator: car.operator, + expression: left + }); + } else { + car.write_only = false; + } if (parent) { parent[field] = car; - return self.cdr; + expressions[i] = expressions[j]; + } else { + expressions[i] = car; } - return car; + break; } if (cdr instanceof AST_Binary && !(cdr instanceof AST_Assign)) { if (cdr.left.is_constant()) { - if (cdr.operator == "||" || cdr.operator == "&&") break; + if (cdr.operator == "||" || cdr.operator == "&&") { + expressions[++i] = expressions[j]; + break; + } field = "right"; } else { field = "left"; } } else if (cdr instanceof AST_Call + && !(left instanceof AST_PropAccess && cdr.expression.equivalent_to(left)) + || cdr instanceof AST_PropAccess || cdr instanceof AST_Unary && !unary_side_effects(cdr.operator)) { field = "expression"; - } else break; + } else if (cdr instanceof AST_Conditional) { + field = "condition"; + } else { + expressions[++i] = expressions[j]; + break; + } parent = cdr; - cdr = cdr[field]; + cdr = cdr[field] = cdr[field].clone(); } } + end = i; + expressions.length = end + 1; } - if (is_undefined(self.cdr, compressor)) { - return make_node(AST_UnaryPrefix, self, { - operator : "void", - expression : self.car - }); - } - return self; }); AST_Unary.DEFMETHOD("lift_sequences", function(compressor){ if (compressor.option("sequences")) { - if (this.expression instanceof AST_Seq) { - var seq = this.expression; - var x = seq.to_array(); + if (this.expression instanceof AST_Sequence) { + var x = this.expression.expressions.slice(); var e = this.clone(); e.expression = x.pop(); x.push(e); - seq = AST_Seq.from_array(x).transform(compressor); - return seq; + return make_sequence(this, x).optimize(compressor); } } return this; @@ -3186,15 +3563,12 @@ merge(Compressor.prototype, { || e instanceof AST_NaN || e instanceof AST_Infinity || e instanceof AST_Undefined)) { - if (e instanceof AST_Seq) { - e = e.to_array(); + if (e instanceof AST_Sequence) { + e = e.expressions.slice(); e.push(make_node(AST_True, self)); - return AST_Seq.from_array(e).optimize(compressor); + return make_sequence(self, e).optimize(compressor); } - return make_node(AST_Seq, self, { - car: e, - cdr: make_node(AST_True, self) - }).optimize(compressor); + return make_sequence(self, [ e, make_node(AST_True, self) ]).optimize(compressor); } var seq = self.lift_sequences(compressor); if (seq !== self) { @@ -3224,10 +3598,10 @@ merge(Compressor.prototype, { // typeof always returns a non-empty string, thus it's // always true in booleans compressor.warn("Boolean expression always true [{file}:{line},{col}]", self.start); - return (e instanceof AST_SymbolRef ? make_node(AST_True, self) : make_node(AST_Seq, self, { - car: e, - cdr: make_node(AST_True, self) - })).optimize(compressor); + return (e instanceof AST_SymbolRef ? make_node(AST_True, self) : make_sequence(self, [ + e, + make_node(AST_True, self) + ])).optimize(compressor); } } if (self.operator == "-" && e instanceof AST_Infinity) { @@ -3259,29 +3633,32 @@ merge(Compressor.prototype, { AST_Binary.DEFMETHOD("lift_sequences", function(compressor){ if (compressor.option("sequences")) { - if (this.left instanceof AST_Seq) { - var seq = this.left; - var x = seq.to_array(); + if (this.left instanceof AST_Sequence) { + var x = this.left.expressions.slice(); var e = this.clone(); e.left = x.pop(); x.push(e); - return AST_Seq.from_array(x).optimize(compressor); + return make_sequence(this, x).optimize(compressor); } - if (this.right instanceof AST_Seq && !this.left.has_side_effects(compressor)) { + if (this.right instanceof AST_Sequence && !this.left.has_side_effects(compressor)) { var assign = this.operator == "=" && this.left instanceof AST_SymbolRef; - var root = this.right.clone(); - var cursor, seq = root; - while (assign || !seq.car.has_side_effects(compressor)) { - cursor = seq; - if (seq.cdr instanceof AST_Seq) { - seq = seq.cdr = seq.cdr.clone(); - } else break; + var x = this.right.expressions; + var last = x.length - 1; + for (var i = 0; i < last; i++) { + if (!assign && x[i].has_side_effects(compressor)) break; } - if (cursor) { + if (i == last) { + x = x.slice(); var e = this.clone(); - e.right = cursor.cdr; - cursor.cdr = e; - return root.optimize(compressor); + e.right = x.pop(); + x.push(e); + return make_sequence(this, x).optimize(compressor); + } else if (i > 0) { + var e = this.clone(); + e.right = make_sequence(this.right, x.slice(i)); + x = x.slice(0, i); + x.push(e); + return make_sequence(this, x).optimize(compressor); } } } @@ -3331,13 +3708,14 @@ merge(Compressor.prototype, { case "==": case "!=": // "undefined" == typeof x => undefined === x - if (self.left instanceof AST_String + if (compressor.option("typeofs") + && self.left instanceof AST_String && self.left.value == "undefined" && self.right instanceof AST_UnaryPrefix && self.right.operator == "typeof") { var expr = self.right.expression; - if (expr instanceof AST_SymbolRef ? !expr.undeclared() - : !(expr instanceof AST_PropAccess) || compressor.option("screw_ie8")) { + if (expr instanceof AST_SymbolRef ? expr.is_declared(compressor) + : !(expr instanceof AST_PropAccess && compressor.option("ie8"))) { self.right = expr; self.left = make_node(AST_Undefined, self.left).optimize(compressor); if (self.operator.length == 2) self.operator += "="; @@ -3350,17 +3728,17 @@ merge(Compressor.prototype, { var rr = self.right.evaluate(compressor); if (ll && typeof ll == "string") { compressor.warn("+ in boolean context always true [{file}:{line},{col}]", self.start); - return make_node(AST_Seq, self, { - car: self.right, - cdr: make_node(AST_True, self) - }).optimize(compressor); + return make_sequence(self, [ + self.right, + make_node(AST_True, self) + ]).optimize(compressor); } if (rr && typeof rr == "string") { compressor.warn("+ in boolean context always true [{file}:{line},{col}]", self.start); - return make_node(AST_Seq, self, { - car: self.left, - cdr: make_node(AST_True, self) - }).optimize(compressor); + return make_sequence(self, [ + self.left, + make_node(AST_True, self) + ]).optimize(compressor); } } if (compressor.option("comparisons") && self.is_boolean()) { @@ -3414,10 +3792,10 @@ merge(Compressor.prototype, { var rr = self.right.evaluate(compressor); if (!rr) { compressor.warn("Boolean && always false [{file}:{line},{col}]", self.start); - return make_node(AST_Seq, self, { - car: self.left, - cdr: make_node(AST_False, self) - }).optimize(compressor); + return make_sequence(self, [ + self.left, + make_node(AST_False, self) + ]).optimize(compressor); } else if (rr !== self.right) { compressor.warn("Dropping side-effect-free && in boolean context [{file}:{line},{col}]", self.start); return self.left.optimize(compressor); @@ -3440,10 +3818,10 @@ merge(Compressor.prototype, { return self.left.optimize(compressor); } else if (rr !== self.right) { compressor.warn("Boolean || always true [{file}:{line},{col}]", self.start); - return make_node(AST_Seq, self, { - car: self.left, - cdr: make_node(AST_True, self) - }).optimize(compressor); + return make_sequence(self, [ + self.left, + make_node(AST_True, self) + ]).optimize(compressor); } } break; @@ -3661,8 +4039,8 @@ merge(Compressor.prototype, { return def.optimize(compressor); } // testing against !self.scope.uses_with first is an optimization - if (compressor.option("screw_ie8") - && self.undeclared() + if (!compressor.option("ie8") + && is_undeclared_ref(self) && (!self.scope.uses_with || !compressor.find_parent(AST_With))) { switch (self.name) { case "undefined": @@ -3673,17 +4051,27 @@ merge(Compressor.prototype, { return make_node(AST_Infinity, self).optimize(compressor); } } - if (compressor.option("evaluate") - && compressor.option("reduce_vars") + if (compressor.option("reduce_vars") && is_lhs(self, compressor.parent()) !== self) { var d = self.definition(); var fixed = self.fixed_value(); - if (fixed) { + if (fixed instanceof AST_Defun) { + d.fixed = fixed = make_node(AST_Function, fixed, fixed); + } + if (compressor.option("unused") + && fixed instanceof AST_Function + && d.references.length == 1 + && !(d.scope.uses_arguments && d.orig[0] instanceof AST_SymbolFunarg) + && !d.scope.uses_eval + && compressor.find_parent(AST_Scope) === fixed.parent_scope) { + return fixed.clone(true); + } + if (compressor.option("evaluate") && fixed) { if (d.should_replace === undefined) { var init = fixed.evaluate(compressor); if (init !== fixed && (compressor.option("unsafe_regexp") || !(init instanceof RegExp))) { init = make_node_from_constant(init, fixed); - var value = init.optimize(compressor).print_to_string().length; + var value_length = init.optimize(compressor).print_to_string().length; var fn; if (has_symbol_ref(fixed)) { fn = function() { @@ -3691,18 +4079,18 @@ merge(Compressor.prototype, { return result === init ? result.clone(true) : result; }; } else { - value = Math.min(value, fixed.print_to_string().length); + value_length = Math.min(value_length, fixed.print_to_string().length); fn = function() { var result = best_of_expression(init.optimize(compressor), fixed); return result === init || result === fixed ? result.clone(true) : result; }; } - var name = d.name.length; + var name_length = d.name.length; var overhead = 0; - if (compressor.option("unused") && (!d.global || compressor.option("toplevel"))) { - overhead = (name + 2 + value) / d.references.length; + if (compressor.option("unused") && !compressor.exposed(d)) { + overhead = (name_length + 2 + value_length) / d.references.length; } - d.should_replace = value <= name + overhead ? fn : false; + d.should_replace = value_length <= name_length + overhead ? fn : false; } else { d.should_replace = false; } @@ -3813,10 +4201,12 @@ merge(Compressor.prototype, { OPT(AST_Conditional, function(self, compressor){ if (!compressor.option("conditionals")) return self; - if (self.condition instanceof AST_Seq) { - var car = self.condition.car; - self.condition = self.condition.cdr; - return AST_Seq.cons(car, self); + // This looks like lift_sequences(), should probably be under "sequences" + if (self.condition instanceof AST_Sequence) { + var expressions = self.condition.expressions.slice(); + self.condition = expressions.pop(); + expressions.push(self); + return make_sequence(self, expressions); } var cond = self.condition.evaluate(compressor); if (cond !== self.condition) { @@ -3899,10 +4289,10 @@ merge(Compressor.prototype, { } // x ? y : y --> x, y if (consequent.equivalent_to(alternative)) { - return make_node(AST_Seq, self, { - car: self.condition, - cdr: consequent - }).optimize(compressor); + return make_sequence(self, [ + self.condition, + consequent + ]).optimize(compressor); } if (is_true(self.consequent)) { @@ -4005,7 +4395,7 @@ merge(Compressor.prototype, { var prop = self.property; if (prop instanceof AST_String && compressor.option("properties")) { prop = prop.getValue(); - if (RESERVED_WORDS(prop) ? compressor.option("screw_ie8") : is_identifier_string(prop)) { + if (is_identifier_string(prop)) { return make_node(AST_Dot, self, { expression : self.expression, property : prop @@ -4026,25 +4416,41 @@ merge(Compressor.prototype, { return self; }); + AST_Lambda.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) return true; + })); + return result; + }); + OPT(AST_Dot, function(self, compressor){ var def = self.resolve_defines(compressor); if (def) { return def.optimize(compressor); } - var prop = self.property; - if (RESERVED_WORDS(prop) && !compressor.option("screw_ie8")) { - return make_node(AST_Sub, self, { - expression : self.expression, - property : make_node(AST_String, self, { - value: prop - }) - }).optimize(compressor); + if (compressor.option("unsafe") && self.expression instanceof AST_Object) { + var values = self.expression.properties; + for (var i = values.length; --i >= 0;) { + if (values[i].key === self.property) { + var value = values[i].value; + if (value instanceof AST_Function ? !value.contains_this() : !value.has_side_effects(compressor)) { + var obj = self.expression.clone(); + obj.properties = obj.properties.slice(); + obj.properties.splice(i, 1); + return make_sequence(self, [ obj, value ]).optimize(compressor); + } + } + } } if (compressor.option("unsafe_proto") && self.expression instanceof AST_Dot && self.expression.property == "prototype") { var exp = self.expression.expression; - if (exp instanceof AST_SymbolRef && exp.undeclared()) switch (exp.name) { + if (is_undeclared_ref(exp)) switch (exp.name) { case "Array": self.expression = make_node(AST_Array, self.expression, { elements: [] @@ -4072,10 +4478,10 @@ merge(Compressor.prototype, { function literals_in_boolean_context(self, compressor) { if (compressor.option("booleans") && compressor.in_boolean_context()) { - return best_of(compressor, self, make_node(AST_Seq, self, { - car: self, - cdr: make_node(AST_True, self) - }).optimize(compressor)); + return best_of(compressor, self, make_sequence(self, [ + self, + make_node(AST_True, self) + ]).optimize(compressor)); } return self; }; diff --git a/node_modules/uglify-js/lib/minify.js b/node_modules/uglify-js/lib/minify.js new file mode 100644 index 000000000..773e953ae --- /dev/null +++ b/node_modules/uglify-js/lib/minify.js @@ -0,0 +1,220 @@ +"use strict"; + +var to_ascii = typeof atob == "undefined" ? function(b64) { + return new Buffer(b64, "base64").toString(); +} : atob; +var to_base64 = typeof btoa == "undefined" ? function(str) { + return new Buffer(str).toString("base64"); +} : btoa; + +function read_source_map(code) { + var match = /\n\/\/# sourceMappingURL=data:application\/json(;.*?)?;base64,(.*)/.exec(code); + if (!match) { + AST_Node.warn("inline source map not found"); + return null; + } + return to_ascii(match[2]); +} + +function set_shorthand(name, options, keys) { + if (options[name]) { + keys.forEach(function(key) { + if (options[key]) { + if (typeof options[key] != "object") options[key] = {}; + if (!(name in options[key])) options[key][name] = options[name]; + } + }); + } +} + +function init_cache(cache) { + if (!cache) return; + if (!("cname" in cache)) cache.cname = -1; + if (!("props" in cache)) { + cache.props = new Dictionary(); + } else if (!(cache.props instanceof Dictionary)) { + cache.props = Dictionary.fromObject(cache.props); + } +} + +function to_json(cache) { + return { + cname: cache.cname, + props: cache.props.toObject() + }; +} + +function minify(files, options) { + var warn_function = AST_Node.warn_function; + try { + options = defaults(options, { + compress: {}, + ie8: false, + keep_fnames: false, + mangle: {}, + nameCache: null, + output: {}, + parse: {}, + sourceMap: false, + timings: false, + toplevel: false, + warnings: false, + wrap: false, + }, true); + var timings = options.timings && { + start: Date.now() + }; + set_shorthand("ie8", options, [ "compress", "mangle", "output" ]); + set_shorthand("keep_fnames", options, [ "compress", "mangle" ]); + set_shorthand("toplevel", options, [ "compress", "mangle" ]); + set_shorthand("warnings", options, [ "compress" ]); + var quoted_props; + if (options.mangle) { + options.mangle = defaults(options.mangle, { + cache: options.nameCache && (options.nameCache.vars || {}), + eval: false, + ie8: false, + keep_fnames: false, + properties: false, + reserved: [], + toplevel: false, + }, true); + if (options.mangle.properties) { + if (typeof options.mangle.properties != "object") { + options.mangle.properties = {}; + } + if (options.mangle.properties.keep_quoted) { + quoted_props = options.mangle.properties.reserved; + if (!Array.isArray(quoted_props)) quoted_props = []; + options.mangle.properties.reserved = quoted_props; + } + if (options.nameCache && !("cache" in options.mangle.properties)) { + options.mangle.properties.cache = options.nameCache.props || {}; + } + } + init_cache(options.mangle.cache); + init_cache(options.mangle.properties.cache); + } + if (options.sourceMap) { + options.sourceMap = defaults(options.sourceMap, { + content: null, + filename: null, + includeSources: false, + root: null, + url: null, + }, true); + } + var warnings = []; + if (options.warnings && !AST_Node.warn_function) { + AST_Node.warn_function = function(warning) { + warnings.push(warning); + }; + } + if (timings) timings.parse = Date.now(); + var toplevel; + if (files instanceof AST_Toplevel) { + toplevel = files; + } else { + if (typeof files == "string") { + files = [ files ]; + } + options.parse = options.parse || {}; + options.parse.toplevel = null; + for (var name in files) if (HOP(files, name)) { + options.parse.filename = name; + options.parse.toplevel = parse(files[name], options.parse); + if (options.sourceMap && options.sourceMap.content == "inline") { + if (Object.keys(files).length > 1) + throw new Error("inline source map only works with singular input"); + options.sourceMap.content = read_source_map(files[name]); + } + } + toplevel = options.parse.toplevel; + } + if (quoted_props) { + reserve_quoted_keys(toplevel, quoted_props); + } + if (options.wrap) { + toplevel = toplevel.wrap_commonjs(options.wrap); + } + if (timings) timings.scope1 = Date.now(); + if (options.compress) toplevel.figure_out_scope(options.mangle); + if (timings) timings.compress = Date.now(); + if (options.compress) toplevel = new Compressor(options.compress).compress(toplevel); + if (timings) timings.scope2 = Date.now(); + if (options.mangle) toplevel.figure_out_scope(options.mangle); + if (timings) timings.mangle = Date.now(); + if (options.mangle) { + base54.reset(); + toplevel.compute_char_frequency(options.mangle); + toplevel.mangle_names(options.mangle); + } + if (timings) timings.properties = Date.now(); + if (options.mangle && options.mangle.properties) { + toplevel = mangle_properties(toplevel, options.mangle.properties); + } + if (timings) timings.output = Date.now(); + var result = {}; + if (options.output.ast) { + result.ast = toplevel; + } + if (!HOP(options.output, "code") || options.output.code) { + if (options.sourceMap) { + if (typeof options.sourceMap.content == "string") { + options.sourceMap.content = JSON.parse(options.sourceMap.content); + } + options.output.source_map = SourceMap({ + file: options.sourceMap.filename, + orig: options.sourceMap.content, + root: options.sourceMap.root + }); + if (options.sourceMap.includeSources) { + if (files instanceof AST_Toplevel) { + throw new Error("original source content unavailable"); + } else for (var name in files) if (HOP(files, name)) { + options.output.source_map.get().setSourceContent(name, files[name]); + } + } + } + delete options.output.ast; + delete options.output.code; + var stream = OutputStream(options.output); + toplevel.print(stream); + result.code = stream.get(); + if (options.sourceMap) { + result.map = options.output.source_map.toString(); + if (options.sourceMap.url == "inline") { + result.code += "\n//# sourceMappingURL=data:application/json;charset=utf-8;base64," + to_base64(result.map); + } else if (options.sourceMap.url) { + result.code += "\n//# sourceMappingURL=" + options.sourceMap.url; + } + } + } + if (options.nameCache && options.mangle) { + if (options.mangle.cache) options.nameCache.vars = to_json(options.mangle.cache); + if (options.mangle.properties && options.mangle.properties.cache) { + options.nameCache.props = to_json(options.mangle.properties.cache); + } + } + if (timings) { + timings.end = Date.now(); + result.timings = { + parse: 1e-3 * (timings.scope1 - timings.parse), + scope: 1e-3 * (timings.compress - timings.scope1 + timings.mangle - timings.scope2), + compress: 1e-3 * (timings.scope2 - timings.compress), + mangle: 1e-3 * (timings.properties - timings.mangle), + properties: 1e-3 * (timings.output - timings.properties), + output: 1e-3 * (timings.end - timings.output), + total: 1e-3 * (timings.end - timings.start) + } + } + if (warnings.length) { + result.warnings = warnings; + } + return result; + } catch (ex) { + return { error: ex }; + } finally { + AST_Node.warn_function = warn_function; + } +} diff --git a/node_modules/uglify-js/lib/mozilla-ast.js b/node_modules/uglify-js/lib/mozilla-ast.js index 88a2eb59f..8d7ee4b85 100644 --- a/node_modules/uglify-js/lib/mozilla-ast.js +++ b/node_modules/uglify-js/lib/mozilla-ast.js @@ -145,7 +145,11 @@ }); }, SequenceExpression: function(M) { - return AST_Seq.from_array(M.expressions.map(from_moz)); + return new AST_Sequence({ + start : my_start_token(M), + end : my_end_token(M), + expressions: M.expressions.map(from_moz) + }); }, MemberExpression: function(M) { return new (M.computed ? AST_Sub : AST_Dot)({ @@ -164,7 +168,7 @@ }); }, VariableDeclaration: function(M) { - return new (M.kind === "const" ? AST_Const : AST_Var)({ + return new AST_Var({ start : my_start_token(M), end : my_end_token(M), definitions : M.declarations.map(from_moz) @@ -200,7 +204,7 @@ Identifier: function(M) { var p = FROM_MOZ_STACK[FROM_MOZ_STACK.length - 2]; return new ( p.type == "LabeledStatement" ? AST_Label - : p.type == "VariableDeclarator" && p.id === M ? (p.kind == "const" ? AST_SymbolConst : AST_SymbolVar) + : p.type == "VariableDeclarator" && p.id === M ? AST_SymbolVar : p.type == "FunctionExpression" ? (p.id === M ? AST_SymbolLambda : AST_SymbolFunarg) : p.type == "FunctionDeclaration" ? (p.id === M ? AST_SymbolDefun : AST_SymbolFunarg) : p.type == "CatchClause" ? AST_SymbolCatch @@ -320,15 +324,15 @@ def_to_moz(AST_Definitions, function To_Moz_VariableDeclaration(M) { return { type: "VariableDeclaration", - kind: M instanceof AST_Const ? "const" : "var", + kind: "var", declarations: M.definitions.map(to_moz) }; }); - def_to_moz(AST_Seq, function To_Moz_SequenceExpression(M) { + def_to_moz(AST_Sequence, function To_Moz_SequenceExpression(M) { return { type: "SequenceExpression", - expressions: M.to_array().map(to_moz) + expressions: M.expressions.map(to_moz) }; }); diff --git a/node_modules/uglify-js/lib/output.js b/node_modules/uglify-js/lib/output.js index 0731fb492..4c873f10d 100644 --- a/node_modules/uglify-js/lib/output.js +++ b/node_modules/uglify-js/lib/output.js @@ -57,6 +57,7 @@ function OutputStream(options) { beautify : false, bracketize : false, comments : false, + ie8 : false, indent_level : 4, indent_start : 0, inline_script : true, @@ -66,12 +67,10 @@ function OutputStream(options) { preserve_line : false, quote_keys : false, quote_style : 0, - screw_ie8 : true, semicolons : true, shebang : true, source_map : null, - space_colon : true, - unescape_regexps : false, + webkit : false, width : 80, wrap_iife : false, }, true); @@ -110,7 +109,7 @@ function OutputStream(options) { var current_pos = 0; var OUTPUT = ""; - function to_ascii(str, identifier) { + var to_utf8 = options.ascii_only ? function(str, identifier) { return str.replace(/[\u0000-\u001f\u007f-\uffff]/g, function(ch) { var code = ch.charCodeAt(0).toString(16); if (code.length <= 2 && !identifier) { @@ -121,6 +120,12 @@ function OutputStream(options) { return "\\u" + code; } }); + } : function(str) { + return str.replace(/[\ud800-\udbff](?![\udc00-\udfff])/g, function(ch) { + return "\\u" + ch.charCodeAt(0).toString(16); + }).replace(/(^|[^\ud800-\udbff])([\udc00-\udfff])/g, function(match, prefix, ch) { + return prefix + "\\u" + ch.charCodeAt(0).toString(16); + }); }; function make_string(str, quote) { @@ -136,7 +141,7 @@ function OutputStream(options) { case "\t": return "\\t"; case "\b": return "\\b"; case "\f": return "\\f"; - case "\x0B": return options.screw_ie8 ? "\\v" : "\\x0B"; + case "\x0B": return options.ie8 ? "\\x0B" : "\\v"; case "\u2028": return "\\u2028"; case "\u2029": return "\\u2029"; case "\ufeff": return "\\ufeff"; @@ -151,7 +156,7 @@ function OutputStream(options) { function quote_double() { return '"' + str.replace(/\x22/g, '\\"') + '"'; } - if (options.ascii_only) str = to_ascii(str); + str = to_utf8(str); switch (options.quote_style) { case 1: return quote_single(); @@ -176,8 +181,7 @@ function OutputStream(options) { function make_name(name) { name = name.toString(); - if (options.ascii_only) - name = to_ascii(name, true); + name = to_utf8(name, true); return name; }; @@ -191,12 +195,43 @@ function OutputStream(options) { var might_need_semicolon = false; var might_add_newline = 0; var last = ""; + var mapping_token, mapping_name, mappings = options.source_map && []; + + var do_add_mapping = mappings ? function() { + mappings.forEach(function(mapping) { + try { + options.source_map.add( + mapping.token.file, + mapping.line, mapping.col, + mapping.token.line, mapping.token.col, + !mapping.name && mapping.token.type == "name" ? mapping.token.value : mapping.name + ); + } catch(ex) { + AST_Node.warn("Couldn't figure out mapping for {file}:{line},{col} → {cline},{ccol} [{name}]", { + file: mapping.token.file, + line: mapping.token.line, + col: mapping.token.col, + cline: mapping.line, + ccol: mapping.col, + name: mapping.name || "" + }) + } + }); + mappings = []; + } : noop; var ensure_line_len = options.max_line_len ? function() { if (current_col > options.max_line_len) { if (might_add_newline) { var left = OUTPUT.slice(0, might_add_newline); var right = OUTPUT.slice(might_add_newline); + if (mappings) { + var delta = right.length - current_col; + mappings.forEach(function(mapping) { + mapping.line++; + mapping.col += delta; + }); + } OUTPUT = left + "\n" + right; current_line++; current_pos++; @@ -206,7 +241,10 @@ function OutputStream(options) { AST_Node.warn("Output exceeds {max_line_len} characters", options); } } - might_add_newline = 0; + if (might_add_newline) { + might_add_newline = 0; + do_add_mapping(); + } } : noop; var requireSemicolonChars = makePredicate("( [ + * / - , ."); @@ -266,6 +304,18 @@ function OutputStream(options) { } might_need_space = false; } + + if (mapping_token) { + mappings.push({ + token: mapping_token, + name: mapping_name, + line: current_line, + col: current_col + }); + mapping_token = false; + if (!might_add_newline) do_add_mapping(); + } + OUTPUT += str; current_pos += str.length; var a = str.split(/\r?\n/), n = a.length - 1; @@ -357,27 +407,12 @@ function OutputStream(options) { function colon() { print(":"); - if (options.space_colon) space(); + space(); }; - var add_mapping = options.source_map ? function(token, name) { - try { - if (token) options.source_map.add( - token.file || "?", - current_line, current_col, - token.line, token.col, - (!name && token.type == "name") ? token.value : name - ); - } catch(ex) { - AST_Node.warn("Couldn't figure out mapping for {file}:{line},{col} → {cline},{ccol} [{name}]", { - file: token.file, - line: token.line, - col: token.col, - cline: current_line, - ccol: current_col, - name: name || "" - }) - } + var add_mapping = mappings ? function(token, name) { + mapping_token = token; + mapping_name = name; } : noop; function get() { @@ -403,7 +438,7 @@ function OutputStream(options) { last : function() { return last }, semicolon : semicolon, force_semicolon : force_semicolon, - to_ascii : to_ascii, + to_utf8 : to_utf8, print_name : function(name) { print(make_name(name)) }, print_string : function(str, quote, escape_directive) { var encoded = encode_string(str, quote); @@ -471,6 +506,7 @@ function OutputStream(options) { use_asm = prev_use_asm; } }); + AST_Node.DEFMETHOD("_print", AST_Node.prototype.print); AST_Node.DEFMETHOD("print_to_string", function(options){ var s = OutputStream(options); @@ -568,6 +604,13 @@ function OutputStream(options) { return true; } + if (output.option('webkit')) { + var p = output.parent(); + if (p instanceof AST_PropAccess && p.expression === this) { + return true; + } + } + if (output.option('wrap_iife')) { var p = output.parent(); return p instanceof AST_Call && p.expression === this; @@ -588,7 +631,7 @@ function OutputStream(options) { || p instanceof AST_Call && p.expression === this; }); - PARENS(AST_Seq, function(output){ + PARENS(AST_Sequence, function(output){ var p = output.parent(); return p instanceof AST_Call // (foo, bar)() or foo(1, (2, 3), 4) || p instanceof AST_Unary // !(foo, bar, baz) @@ -634,14 +677,15 @@ function OutputStream(options) { // parens around it too, otherwise the call will be // interpreted as passing the arguments to the upper New // expression. - try { - this.walk(new TreeWalker(function(node){ - if (node instanceof AST_Call) throw p; - })); - } catch(ex) { - if (ex !== p) throw ex; - return true; - } + var parens = false; + this.walk(new TreeWalker(function(node) { + if (parens || node instanceof AST_Scope) return true; + if (node instanceof AST_Call) { + parens = true; + return true; + } + })); + return parens; } }); @@ -677,7 +721,7 @@ function OutputStream(options) { } }); - PARENS([ AST_Assign, AST_Conditional ], function (output){ + PARENS([ AST_Assign, AST_Conditional ], function(output){ var p = output.parent(); // !(a = false) → true if (p instanceof AST_Unary) @@ -902,7 +946,7 @@ function OutputStream(options) { function make_then(self, output) { var b = self.body; if (output.option("bracketize") - || !output.option("screw_ie8") && b instanceof AST_Do) + || output.option("ie8") && b instanceof AST_Do) return make_block(b, output); // The squeezer replaces "block"-s that contain only a single // statement with the statement itself; technically, the AST @@ -1033,24 +1077,19 @@ function OutputStream(options) { DEFPRINT(AST_Var, function(self, output){ self._do_print(output, "var"); }); - DEFPRINT(AST_Const, function(self, output){ - self._do_print(output, "const"); - }); function parenthesize_for_noin(node, output, noin) { - if (!noin) node.print(output); - else try { - // need to take some precautions here: - // https://github.com/mishoo/UglifyJS2/issues/60 - node.walk(new TreeWalker(function(node){ - if (node instanceof AST_Binary && node.operator == "in") - throw output; - })); - node.print(output); - } catch(ex) { - if (ex !== output) throw ex; - node.print(output, true); - } + var parens = false; + // need to take some precautions here: + // https://github.com/mishoo/UglifyJS2/issues/60 + if (noin) node.walk(new TreeWalker(function(node) { + if (parens || node instanceof AST_Scope) return true; + if (node instanceof AST_Binary && node.operator == "in") { + parens = true; + return true; + } + })); + node.print(output, parens); }; DEFPRINT(AST_VarDef, function(self, output){ @@ -1070,6 +1109,9 @@ function OutputStream(options) { self.expression.print(output); if (self instanceof AST_New && !need_constructor_parens(self, output)) return; + if (self.expression instanceof AST_Lambda) { + output.add_mapping(self.start); + } output.with_parens(function(){ self.args.forEach(function(expr, i){ if (i) output.comma(); @@ -1083,18 +1125,19 @@ function OutputStream(options) { AST_Call.prototype._codegen(self, output); }); - AST_Seq.DEFMETHOD("_do_print", function(output){ - this.car.print(output); - if (this.cdr) { - output.comma(); - if (output.should_break()) { - output.newline(); - output.indent(); + AST_Sequence.DEFMETHOD("_do_print", function(output){ + this.expressions.forEach(function(node, index) { + if (index > 0) { + output.comma(); + if (output.should_break()) { + output.newline(); + output.indent(); + } } - this.cdr.print(output); - } + node.print(output); + }); }); - DEFPRINT(AST_Seq, function(self, output){ + DEFPRINT(AST_Sequence, function(self, output){ self._do_print(output); // var p = output.parent(); // if (p instanceof AST_Statement) { @@ -1108,15 +1151,23 @@ function OutputStream(options) { DEFPRINT(AST_Dot, function(self, output){ var expr = self.expression; expr.print(output); - if (expr instanceof AST_Number && expr.getValue() >= 0) { - if (!/[xa-f.)]/i.test(output.last())) { - output.print("."); + var prop = self.property; + if (output.option("ie8") && RESERVED_WORDS(prop)) { + output.print("["); + output.add_mapping(self.end); + output.print_string(prop); + output.print("]"); + } else { + if (expr instanceof AST_Number && expr.getValue() >= 0) { + if (!/[xa-f.)]/i.test(output.last())) { + output.print("."); + } } + output.print("."); + // the name after dot would be mapped about here. + output.add_mapping(self.end); + output.print_name(prop); } - output.print("."); - // the name after dot would be mapped about here. - output.add_mapping(self.end); - output.print_name(self.property); }); DEFPRINT(AST_Sub, function(self, output){ self.expression.print(output); @@ -1207,9 +1258,8 @@ function OutputStream(options) { }); else output.print("{}"); }); - DEFPRINT(AST_ObjectKeyVal, function(self, output){ - var key = self.key; - var quote = self.quote; + + function print_property_name(key, quote, output) { if (output.option("quote_keys")) { output.print_string(key + ""); } else if ((typeof key == "number" @@ -1217,7 +1267,7 @@ function OutputStream(options) { && +key + "" == key) && parseFloat(key) >= 0) { output.print(make_num(key)); - } else if (RESERVED_WORDS(key) ? output.option("screw_ie8") : is_identifier_string(key)) { + } else if (RESERVED_WORDS(key) ? !output.option("ie8") : is_identifier_string(key)) { if (quote && output.option("keep_quoted_props")) { output.print_string(key, quote); } else { @@ -1226,20 +1276,24 @@ function OutputStream(options) { } else { output.print_string(key, quote); } + } + + DEFPRINT(AST_ObjectKeyVal, function(self, output){ + print_property_name(self.key, self.quote, output); output.colon(); self.value.print(output); }); - DEFPRINT(AST_ObjectSetter, function(self, output){ - output.print("set"); + AST_ObjectProperty.DEFMETHOD("_print_getter_setter", function(type, output) { + output.print(type); output.space(); - self.key.print(output); - self.value._do_print(output, true); + print_property_name(this.key.name, this.quote, output); + this.value._do_print(output, true); + }); + DEFPRINT(AST_ObjectSetter, function(self, output){ + self._print_getter_setter("set", output); }); DEFPRINT(AST_ObjectGetter, function(self, output){ - output.print("get"); - output.space(); - self.key.print(output); - self.value._do_print(output, true); + self._print_getter_setter("get", output); }); DEFPRINT(AST_Symbol, function(self, output){ var def = self.definition(); @@ -1263,46 +1317,13 @@ function OutputStream(options) { } }); - function regexp_safe_literal(code) { - return [ - 0x5c , // \ - 0x2f , // / - 0x2e , // . - 0x2b , // + - 0x2a , // * - 0x3f , // ? - 0x28 , // ( - 0x29 , // ) - 0x5b , // [ - 0x5d , // ] - 0x7b , // { - 0x7d , // } - 0x24 , // $ - 0x5e , // ^ - 0x3a , // : - 0x7c , // | - 0x21 , // ! - 0x0a , // \n - 0x0d , // \r - 0x00 , // \0 - 0xfeff , // Unicode BOM - 0x2028 , // unicode "line separator" - 0x2029 , // unicode "paragraph separator" - ].indexOf(code) < 0; - }; - DEFPRINT(AST_RegExp, function(self, output){ - var str = self.getValue().toString(); - if (output.option("ascii_only")) { - str = output.to_ascii(str); - } else if (output.option("unescape_regexps")) { - str = str.split("\\\\").map(function(str){ - return str.replace(/\\u[0-9a-fA-F]{4}|\\x[0-9a-fA-F]{2}/g, function(s){ - var code = parseInt(s.substr(2), 16); - return regexp_safe_literal(code) ? String.fromCharCode(code) : s; - }); - }).join("\\\\"); + var regexp = self.getValue(); + var str = regexp.toString(); + if (regexp.raw_source) { + str = "/" + regexp.raw_source + str.slice(str.lastIndexOf("/")); } + str = output.to_utf8(str); output.print(str); var p = output.parent(); if (p instanceof AST_Binary && /^in/.test(p.operator) && p.left === self) diff --git a/node_modules/uglify-js/lib/parse.js b/node_modules/uglify-js/lib/parse.js index 014822ad9..e2dd04b6c 100644 --- a/node_modules/uglify-js/lib/parse.js +++ b/node_modules/uglify-js/lib/parse.js @@ -115,8 +115,6 @@ var PUNC_BEFORE_EXPRESSION = makePredicate(characters("[{(,;:")); var PUNC_CHARS = makePredicate(characters("[]{}(),;:")); -var REGEXP_MODIFIERS = makePredicate(characters("gmsiy")); - /* -----[ Tokenizer ]----- */ // regexps adapted from http://xregexp.com/plugins/#unicode @@ -477,31 +475,33 @@ function tokenizer($TEXT, filename, html5_comments, shebang) { return name; }; - var read_regexp = with_eof_error("Unterminated regular expression", function(regexp){ + var read_regexp = with_eof_error("Unterminated regular expression", function(source) { var prev_backslash = false, ch, in_class = false; while ((ch = next(true))) if (NEWLINE_CHARS(ch)) { parse_error("Unexpected line terminator"); } else if (prev_backslash) { - regexp += "\\" + ch; + source += "\\" + ch; prev_backslash = false; } else if (ch == "[") { in_class = true; - regexp += ch; + source += ch; } else if (ch == "]" && in_class) { in_class = false; - regexp += ch; + source += ch; } else if (ch == "/" && !in_class) { break; } else if (ch == "\\") { prev_backslash = true; } else { - regexp += ch; + source += ch; } var mods = read_name(); try { - return token("regexp", new RegExp(regexp, mods)); + var regexp = new RegExp(source, mods); + regexp.raw_source = source; + return token("regexp", regexp); } catch(e) { - parse_error(e.message); + parse_error(e.message); } }); @@ -633,8 +633,7 @@ function tokenizer($TEXT, filename, html5_comments, shebang) { } next_token.has_directive = function(directive) { - return S.directives[directive] !== undefined && - S.directives[directive] > 0; + return S.directives[directive] > 0; } return next_token; @@ -683,9 +682,7 @@ var PRECEDENCE = (function(a, ret){ {} ); -var STATEMENTS_WITH_LABELS = array_to_hash([ "for", "do", "while", "switch" ]); - -var ATOMIC_START_TOKEN = array_to_hash([ "atom", "num", "string", "regexp", "name" ]); +var ATOMIC_START_TOKEN = makePredicate([ "atom", "num", "string", "regexp", "name" ]); /* -----[ Parser ]----- */ @@ -693,14 +690,13 @@ function parse($TEXT, options) { options = defaults(options, { bare_returns : false, - cli : false, expression : false, filename : null, html5_comments : true, shebang : true, strict : false, toplevel : null, - }); + }, true); var S = { input : (typeof $TEXT == "string" @@ -941,12 +937,6 @@ function parse($TEXT, options) { semicolon(); return node; - case "const": - next(); - var node = const_(); - semicolon(); - return node; - case "with": if (S.input.has_directive("use strict")) { croak("Strict mode may not include a with statement"); @@ -1020,8 +1010,12 @@ function parse($TEXT, options) { ? (next(), var_(true)) : expression(true, true); if (is("operator", "in")) { - if (init instanceof AST_Var && init.definitions.length > 1) - croak("Only one variable declaration allowed in for..in loop"); + if (init instanceof AST_Var) { + if (init.definitions.length > 1) + croak("Only one variable declaration allowed in for..in loop", init.start.line, init.start.col, init.start.pos); + } else if (!is_assignable(init)) { + croak("Invalid left-hand side in for..in loop", init.start.line, init.start.col, init.start.pos); + } next(); return for_in(init); } @@ -1061,29 +1055,32 @@ function parse($TEXT, options) { if (in_statement && !name) unexpected(); expect("("); + var argnames = []; + for (var first = true; !is("punc", ")");) { + if (first) first = false; else expect(","); + argnames.push(as_symbol(AST_SymbolFunarg)); + } + next(); + var loop = S.in_loop; + var labels = S.labels; + ++S.in_function; + S.in_directives = true; + S.input.push_directives_stack(); + S.in_loop = 0; + S.labels = []; + var body = block_(); + if (S.input.has_directive("use strict")) { + if (name) strict_verify_symbol(name); + argnames.forEach(strict_verify_symbol); + } + S.input.pop_directives_stack(); + --S.in_function; + S.in_loop = loop; + S.labels = labels; return new ctor({ name: name, - argnames: (function(first, a){ - while (!is("punc", ")")) { - if (first) first = false; else expect(","); - a.push(as_symbol(AST_SymbolFunarg)); - } - next(); - return a; - })(true, []), - body: (function(loop, labels){ - ++S.in_function; - S.in_directives = true; - S.input.push_directives_stack(); - S.in_loop = 0; - S.labels = []; - var a = block_(); - S.input.pop_directives_stack(); - --S.in_function; - S.in_loop = loop; - S.labels = labels; - return a; - })(S.in_loop, S.labels) + argnames: argnames, + body: body }); }; @@ -1179,12 +1176,12 @@ function parse($TEXT, options) { }); }; - function vardefs(no_in, in_const) { + function vardefs(no_in) { var a = []; for (;;) { a.push(new AST_VarDef({ start : S.token, - name : as_symbol(in_const ? AST_SymbolConst : AST_SymbolVar), + name : as_symbol(AST_SymbolVar), value : is("operator", "=") ? (next(), expression(false, no_in)) : null, end : prev() })); @@ -1198,15 +1195,7 @@ function parse($TEXT, options) { var var_ = function(no_in) { return new AST_Var({ start : prev(), - definitions : vardefs(no_in, false), - end : prev() - }); - }; - - var const_ = function() { - return new AST_Const({ - start : prev(), - definitions : vardefs(false, true), + definitions : vardefs(no_in), end : prev() }); }; @@ -1233,7 +1222,6 @@ function parse($TEXT, options) { var tok = S.token, ret; switch (tok.type) { case "name": - case "keyword": ret = _make_symbol(AST_SymbolRef); break; case "num": @@ -1263,13 +1251,6 @@ function parse($TEXT, options) { break; } break; - case "operator": - if (!is_identifier_string(tok.value)) { - croak("Invalid getter/setter name: " + tok.value, - tok.line, tok.col, tok.pos); - } - ret = _make_symbol(AST_SymbolRef); - break; } next(); return ret; @@ -1303,7 +1284,7 @@ function parse($TEXT, options) { func.end = prev(); return subscripts(func, allow_calls); } - if (ATOMIC_START_TOKEN[S.token.type]) { + if (ATOMIC_START_TOKEN(S.token.type)) { return subscripts(as_atom_node(), allow_calls); } unexpected(); @@ -1417,12 +1398,20 @@ function parse($TEXT, options) { }); }; + function strict_verify_symbol(sym) { + if (sym.name == "arguments" || sym.name == "eval") + croak("Unexpected " + sym.name + " in strict mode", sym.start.line, sym.start.col, sym.start.pos); + } + function as_symbol(type, noerror) { if (!is("name")) { if (!noerror) croak("Name expected"); return null; } var sym = _make_symbol(type); + if (S.input.has_directive("use strict") && sym instanceof AST_SymbolDeclaration) { + strict_verify_symbol(sym); + } next(); return sym; }; @@ -1483,8 +1472,17 @@ function parse($TEXT, options) { function make_unary(ctor, token, expr) { var op = token.value; - if ((op == "++" || op == "--") && !is_assignable(expr)) - croak("Invalid use of " + op + " operator", token.line, token.col, token.pos); + switch (op) { + case "++": + case "--": + if (!is_assignable(expr)) + croak("Invalid use of " + op + " operator", token.line, token.col, token.pos); + break; + case "delete": + if (expr instanceof AST_SymbolRef && S.input.has_directive("use strict")) + croak("Calling delete on expression not allowed in strict mode", expr.start.line, expr.start.col, expr.start.pos); + break; + } return new ctor({ operator: op, expression: expr }); }; @@ -1529,7 +1527,6 @@ function parse($TEXT, options) { }; function is_assignable(expr) { - if (options.cli) return true; return expr instanceof AST_PropAccess || expr instanceof AST_SymbolRef; }; @@ -1554,17 +1551,18 @@ function parse($TEXT, options) { var expression = function(commas, no_in) { var start = S.token; - var expr = maybe_assign(no_in); - if (commas && is("punc", ",")) { + var exprs = []; + while (true) { + exprs.push(maybe_assign(no_in)); + if (!commas || !is("punc", ",")) break; next(); - return new AST_Seq({ - start : start, - car : expr, - cdr : expression(true, no_in), - end : peek() - }); + commas = true; } - return expr; + return exprs.length == 1 ? exprs[0] : new AST_Sequence({ + start : start, + expressions : exprs, + end : peek() + }); }; function in_loop(cont) { diff --git a/node_modules/uglify-js/lib/propmangle.js b/node_modules/uglify-js/lib/propmangle.js index b62229903..36a67e80a 100644 --- a/node_modules/uglify-js/lib/propmangle.js +++ b/node_modules/uglify-js/lib/propmangle.js @@ -43,16 +43,16 @@ "use strict"; -function find_builtins() { +function find_builtins(reserved) { // NaN will be included due to Number.NaN - var a = [ + [ "null", "true", "false", "Infinity", "-Infinity", "undefined", - ]; + ].forEach(add); [ Object, Array, Function, Number, String, Boolean, Error, Math, Date, RegExp @@ -63,24 +63,52 @@ function find_builtins() { } }); function add(name) { - push_uniq(a, name); + push_uniq(reserved, name); } - return a; +} + +function reserve_quoted_keys(ast, reserved) { + function add(name) { + push_uniq(reserved, name); + } + + ast.walk(new TreeWalker(function(node) { + if (node instanceof AST_ObjectKeyVal && node.quote) { + add(node.key); + } else if (node instanceof AST_Sub) { + addStrings(node.property, add); + } + })); +} + +function addStrings(node, add) { + node.walk(new TreeWalker(function(node) { + if (node instanceof AST_Sequence) { + addStrings(node.expressions[node.expressions.length - 1], add); + } else if (node instanceof AST_String) { + add(node.value); + } else if (node instanceof AST_Conditional) { + addStrings(node.consequent, add); + addStrings(node.alternative, add); + } + return true; + })); } function mangle_properties(ast, options) { options = defaults(options, { + builtins: false, cache: null, debug: false, - ignore_quoted: false, + keep_quoted: false, only_cache: false, regex: null, reserved: null, - }); + }, true); var reserved = options.reserved; - if (reserved == null) - reserved = find_builtins(); + if (!Array.isArray(reserved)) reserved = []; + if (!options.builtins) find_builtins(reserved); var cache = options.cache; if (cache == null) { @@ -91,12 +119,11 @@ function mangle_properties(ast, options) { } var regex = options.regex; - var ignore_quoted = options.ignore_quoted; // note debug is either false (disabled), or a string of the debug suffix to use (enabled). // note debug may be enabled as an empty string, which is falsey. Also treat passing 'true' // the same as passing an empty string. - var debug = (options.debug !== false); + var debug = options.debug !== false; var debug_name_suffix; if (debug) { debug_name_suffix = (options.debug === true ? "" : options.debug); @@ -104,12 +131,11 @@ function mangle_properties(ast, options) { var names_to_mangle = []; var unmangleable = []; - var ignored = {}; // step 1: find candidates to mangle ast.walk(new TreeWalker(function(node){ if (node instanceof AST_ObjectKeyVal) { - add(node.key, ignore_quoted && node.quote); + add(node.key); } else if (node instanceof AST_ObjectProperty) { // setter or getter, since KeyVal is handled above @@ -119,15 +145,14 @@ function mangle_properties(ast, options) { add(node.property); } else if (node instanceof AST_Sub) { - addStrings(node.property, ignore_quoted); + addStrings(node.property, add); } })); // step 2: transform the tree, renaming properties return ast.transform(new TreeTransformer(function(node){ if (node instanceof AST_ObjectKeyVal) { - if (!(ignore_quoted && node.quote)) - node.key = mangle(node.key); + node.key = mangle(node.key); } else if (node instanceof AST_ObjectProperty) { // setter or getter @@ -136,22 +161,9 @@ function mangle_properties(ast, options) { else if (node instanceof AST_Dot) { node.property = mangle(node.property); } - else if (node instanceof AST_Sub) { - if (!ignore_quoted) - node.property = mangleStrings(node.property); + else if (!options.keep_quoted && node instanceof AST_Sub) { + node.property = mangleStrings(node.property); } - // else if (node instanceof AST_String) { - // if (should_mangle(node.value)) { - // AST_Node.warn( - // "Found \"{prop}\" property candidate for mangling in an arbitrary string [{file}:{line},{col}]", { - // file : node.start.file, - // line : node.start.line, - // col : node.start.col, - // prop : node.value - // } - // ); - // } - // } })); // only function declarations after this line @@ -167,19 +179,13 @@ function mangle_properties(ast, options) { } function should_mangle(name) { - if (ignore_quoted && name in ignored) return false; if (regex && !regex.test(name)) return false; if (reserved.indexOf(name) >= 0) return false; return cache.props.has(name) || names_to_mangle.indexOf(name) >= 0; } - function add(name, ignore) { - if (ignore) { - ignored[name] = true; - return; - } - + function add(name) { if (can_mangle(name)) push_uniq(names_to_mangle, name); @@ -199,19 +205,16 @@ function mangle_properties(ast, options) { // debug mode: use a prefix and suffix to preserve readability, e.g. o.foo -> o._$foo$NNN_. var debug_mangled = "_$" + name + "$" + debug_name_suffix + "_"; - if (can_mangle(debug_mangled) && !(ignore_quoted && debug_mangled in ignored)) { + if (can_mangle(debug_mangled)) { mangled = debug_mangled; } } // either debug mode is off, or it is on and we could not use the mangled name if (!mangled) { - // note can_mangle() does not check if the name collides with the 'ignored' set - // (filled with quoted properties when ignore_quoted set). Make sure we add this - // check so we don't collide with a quoted name. do { mangled = base54(++cache.cname); - } while (!can_mangle(mangled) || (ignore_quoted && mangled in ignored)); + } while (!can_mangle(mangled)); } cache.props.set(name, mangled); @@ -219,36 +222,11 @@ function mangle_properties(ast, options) { return mangled; } - function addStrings(node, ignore) { - var out = {}; - try { - (function walk(node){ - node.walk(new TreeWalker(function(node){ - if (node instanceof AST_Seq) { - walk(node.cdr); - return true; - } - if (node instanceof AST_String) { - add(node.value, ignore); - return true; - } - if (node instanceof AST_Conditional) { - walk(node.consequent); - walk(node.alternative); - return true; - } - throw out; - })); - })(node); - } catch(ex) { - if (ex !== out) throw ex; - } - } - function mangleStrings(node) { return node.transform(new TreeTransformer(function(node){ - if (node instanceof AST_Seq) { - node.cdr = mangleStrings(node.cdr); + if (node instanceof AST_Sequence) { + var last = node.expressions.length - 1; + node.expressions[last] = mangleStrings(node.expressions[last]); } else if (node instanceof AST_String) { node.value = mangle(node.value); @@ -260,5 +238,4 @@ function mangle_properties(ast, options) { return node; })); } - } diff --git a/node_modules/uglify-js/lib/scope.js b/node_modules/uglify-js/lib/scope.js index bf6dbcbbf..df7b2076c 100644 --- a/node_modules/uglify-js/lib/scope.js +++ b/node_modules/uglify-js/lib/scope.js @@ -76,10 +76,10 @@ SymbolDef.prototype = { else if (!this.mangled_name && !this.unmangleable(options)) { var s = this.scope; var sym = this.orig[0]; - if (!options.screw_ie8 && sym instanceof AST_SymbolLambda) + if (options.ie8 && sym instanceof AST_SymbolLambda) s = s.parent_scope; var def; - if (this.defun && (def = this.defun.variables.get(this.name))) { + if (def = this.redefined()) { this.mangled_name = def.mangled_name || def.name; } else this.mangled_name = s.next_mangled(options, this); @@ -87,13 +87,16 @@ SymbolDef.prototype = { cache.set(this.name, this.mangled_name); } } + }, + redefined: function() { + return this.defun && this.defun.variables.get(this.name); } }; AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){ options = defaults(options, { cache: null, - screw_ie8: true, + ie8: false, }); // pass 1: setup scope chaining and handle definitions @@ -156,8 +159,7 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){ // later. (node.scope = defun.parent_scope).def_function(node); } - else if (node instanceof AST_SymbolVar - || node instanceof AST_SymbolConst) { + else if (node instanceof AST_SymbolVar) { defun.def_variable(node); if (defun !== scope) { node.mark_enclosed(options); @@ -184,16 +186,8 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){ self.walk(tw); // pass 2: find back references and eval - var func = null; - var globals = self.globals = new Dictionary(); + self.globals = new Dictionary(); var tw = new TreeWalker(function(node, descend){ - if (node instanceof AST_Lambda) { - var prev_func = func; - func = node; - descend(); - func = prev_func; - return true; - } if (node instanceof AST_LoopControl && node.label) { node.label.thedef.references.push(node); return true; @@ -206,21 +200,30 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){ } } var sym = node.scope.find_variable(name); - if (node.scope instanceof AST_Lambda && name == "arguments") { - node.scope.uses_arguments = true; - } if (!sym) { sym = self.def_global(node); + } else if (sym.scope instanceof AST_Lambda && name == "arguments") { + sym.scope.uses_arguments = true; } node.thedef = sym; node.reference(options); return true; } + // ensure mangling works if catch reuses a scope variable + var def; + if (node instanceof AST_SymbolCatch && (def = node.definition().redefined())) { + var s = node.scope; + while (s) { + push_uniq(s.enclosed, def); + if (s === def.scope) break; + s = s.parent_scope; + } + } }); self.walk(tw); // pass 3: fix up any scoping issue with IE8 - if (!options.screw_ie8) { + if (options.ie8) { self.walk(new TreeWalker(function(node, descend) { if (node instanceof AST_SymbolCatch) { var name = node.name; @@ -268,7 +271,7 @@ AST_Scope.DEFMETHOD("init_scope_vars", function(parent_scope){ AST_Lambda.DEFMETHOD("init_scope_vars", function(){ AST_Scope.prototype.init_scope_vars.apply(this, arguments); this.uses_arguments = false; - this.def_variable(new AST_SymbolVar({ + this.def_variable(new AST_SymbolFunarg({ name: "arguments", start: this.start, end: this.end @@ -325,8 +328,8 @@ AST_Scope.DEFMETHOD("next_mangled", function(options){ if (!is_identifier(m)) continue; // skip over "do" // https://github.com/mishoo/UglifyJS2/issues/242 -- do not - // shadow a name excepted from mangling. - if (options.except.indexOf(m) >= 0) continue; + // shadow a name reserved from mangling. + if (options.reserved.indexOf(m) >= 0) continue; // we must ensure that the mangled name does not shadow a name // from some parent scope that is referenced in this or in @@ -358,31 +361,18 @@ AST_Function.DEFMETHOD("next_mangled", function(options, def){ }); AST_Symbol.DEFMETHOD("unmangleable", function(options){ - return this.definition().unmangleable(options); + var def = this.definition(); + return !def || def.unmangleable(options); }); // labels are always mangleable -AST_Label.DEFMETHOD("unmangleable", function(){ - return false; -}); +AST_Label.DEFMETHOD("unmangleable", return_false); AST_Symbol.DEFMETHOD("unreferenced", function(){ return this.definition().references.length == 0 && !(this.scope.uses_eval || this.scope.uses_with); }); -AST_Symbol.DEFMETHOD("undeclared", function(){ - return this.definition().undeclared; -}); - -AST_LabelRef.DEFMETHOD("undeclared", function(){ - return false; -}); - -AST_Label.DEFMETHOD("undeclared", function(){ - return false; -}); - AST_Symbol.DEFMETHOD("definition", function(){ return this.thedef; }); @@ -392,21 +382,22 @@ AST_Symbol.DEFMETHOD("global", function(){ }); AST_Toplevel.DEFMETHOD("_default_mangler_options", function(options){ - return defaults(options, { + options = defaults(options, { eval : false, - except : [], + ie8 : false, keep_fnames : false, - screw_ie8 : true, - sort : false, // Ignored. Flag retained for backwards compatibility. + reserved : [], toplevel : false, }); + if (!Array.isArray(options.reserved)) options.reserved = []; + return options; }); AST_Toplevel.DEFMETHOD("mangle_names", function(options){ options = this._default_mangler_options(options); // Never mangle arguments - options.except.push('arguments'); + options.reserved.push('arguments'); // We only need to mangle declaration nodes. Special logic wired // into the code generator will display the mangled name if it's @@ -417,7 +408,7 @@ AST_Toplevel.DEFMETHOD("mangle_names", function(options){ if (options.cache) { this.globals.each(function(symbol){ - if (options.except.indexOf(symbol.name) < 0) { + if (options.reserved.indexOf(symbol.name) < 0) { to_mangle.push(symbol); } }); @@ -434,7 +425,7 @@ AST_Toplevel.DEFMETHOD("mangle_names", function(options){ if (node instanceof AST_Scope) { var p = tw.parent(), a = []; node.variables.each(function(symbol){ - if (options.except.indexOf(symbol.name) < 0) { + if (options.reserved.indexOf(symbol.name) < 0) { a.push(symbol); } }); @@ -447,7 +438,7 @@ AST_Toplevel.DEFMETHOD("mangle_names", function(options){ node.mangled_name = name; return true; } - if (options.screw_ie8 && node instanceof AST_SymbolCatch) { + if (!options.ie8 && node instanceof AST_SymbolCatch) { to_mangle.push(node.definition()); return; } @@ -462,105 +453,69 @@ AST_Toplevel.DEFMETHOD("mangle_names", function(options){ AST_Toplevel.DEFMETHOD("compute_char_frequency", function(options){ options = this._default_mangler_options(options); - var tw = new TreeWalker(function(node){ - if (node instanceof AST_Constant) - base54.consider(node.print_to_string()); - else if (node instanceof AST_Return) - base54.consider("return"); - else if (node instanceof AST_Throw) - base54.consider("throw"); - else if (node instanceof AST_Continue) - base54.consider("continue"); - else if (node instanceof AST_Break) - base54.consider("break"); - else if (node instanceof AST_Debugger) - base54.consider("debugger"); - else if (node instanceof AST_Directive) - base54.consider(node.value); - else if (node instanceof AST_While) - base54.consider("while"); - else if (node instanceof AST_Do) - base54.consider("do while"); - else if (node instanceof AST_If) { - base54.consider("if"); - if (node.alternative) base54.consider("else"); - } - else if (node instanceof AST_Var) - base54.consider("var"); - else if (node instanceof AST_Const) - base54.consider("const"); - else if (node instanceof AST_Lambda) - base54.consider("function"); - else if (node instanceof AST_For) - base54.consider("for"); - else if (node instanceof AST_ForIn) - base54.consider("for in"); - else if (node instanceof AST_Switch) - base54.consider("switch"); - else if (node instanceof AST_Case) - base54.consider("case"); - else if (node instanceof AST_Default) - base54.consider("default"); - else if (node instanceof AST_With) - base54.consider("with"); - else if (node instanceof AST_ObjectSetter) - base54.consider("set" + node.key); - else if (node instanceof AST_ObjectGetter) - base54.consider("get" + node.key); - else if (node instanceof AST_ObjectKeyVal) - base54.consider(node.key); - else if (node instanceof AST_New) - base54.consider("new"); - else if (node instanceof AST_This) - base54.consider("this"); - else if (node instanceof AST_Try) - base54.consider("try"); - else if (node instanceof AST_Catch) - base54.consider("catch"); - else if (node instanceof AST_Finally) - base54.consider("finally"); - else if (node instanceof AST_Symbol && node.unmangleable(options)) - base54.consider(node.name); - else if (node instanceof AST_Unary || node instanceof AST_Binary) - base54.consider(node.operator); - else if (node instanceof AST_Dot) - base54.consider(node.property); - }); - this.walk(tw); + try { + AST_Node.prototype.print = function(stream, force_parens) { + this._print(stream, force_parens); + if (this instanceof AST_Symbol && !this.unmangleable(options)) { + base54.consider(this.name, -1); + } else if (options.properties) { + if (this instanceof AST_Dot) { + base54.consider(this.property, -1); + } else if (this instanceof AST_Sub) { + skip_string(this.property); + } + } + }; + base54.consider(this.print_to_string(), 1); + } finally { + AST_Node.prototype.print = AST_Node.prototype._print; + } base54.sort(); + + function skip_string(node) { + if (node instanceof AST_String) { + base54.consider(node.value, -1); + } else if (node instanceof AST_Conditional) { + skip_string(node.consequent); + skip_string(node.alternative); + } else if (node instanceof AST_Sequence) { + skip_string(node.expressions[node.expressions.length - 1]); + } + } }); var base54 = (function() { - var string = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ$_0123456789"; + var leading = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ$_".split(""); + var digits = "0123456789".split(""); var chars, frequency; function reset() { frequency = Object.create(null); - chars = string.split("").map(function(ch){ return ch.charCodeAt(0) }); - chars.forEach(function(ch){ frequency[ch] = 0 }); + leading.forEach(function(ch) { + frequency[ch] = 0; + }); + digits.forEach(function(ch) { + frequency[ch] = 0; + }); } - base54.consider = function(str){ + base54.consider = function(str, delta) { for (var i = str.length; --i >= 0;) { - var code = str.charCodeAt(i); - if (code in frequency) ++frequency[code]; + frequency[str[i]] += delta; } }; + function compare(a, b) { + return frequency[b] - frequency[a]; + } base54.sort = function() { - chars = mergeSort(chars, function(a, b){ - if (is_digit(a) && !is_digit(b)) return 1; - if (is_digit(b) && !is_digit(a)) return -1; - return frequency[b] - frequency[a]; - }); + chars = mergeSort(leading, compare).concat(mergeSort(digits, compare)); }; base54.reset = reset; reset(); - base54.get = function(){ return chars }; - base54.freq = function(){ return frequency }; function base54(num) { var ret = "", base = 54; num++; do { num--; - ret += String.fromCharCode(chars[num % base]); + ret += chars[num % base]; num = Math.floor(num / base); base = 64; } while (num > 0); @@ -568,89 +523,3 @@ var base54 = (function() { }; return base54; })(); - -AST_Toplevel.DEFMETHOD("scope_warnings", function(options){ - options = defaults(options, { - assign_to_global : true, - eval : true, - func_arguments : true, - nested_defuns : true, - undeclared : false, // this makes a lot of noise - unreferenced : true, - }); - var tw = new TreeWalker(function(node){ - if (options.undeclared - && node instanceof AST_SymbolRef - && node.undeclared()) - { - // XXX: this also warns about JS standard names, - // i.e. Object, Array, parseInt etc. Should add a list of - // exceptions. - AST_Node.warn("Undeclared symbol: {name} [{file}:{line},{col}]", { - name: node.name, - file: node.start.file, - line: node.start.line, - col: node.start.col - }); - } - if (options.assign_to_global) - { - var sym = null; - if (node instanceof AST_Assign && node.left instanceof AST_SymbolRef) - sym = node.left; - else if (node instanceof AST_ForIn && node.init instanceof AST_SymbolRef) - sym = node.init; - if (sym - && (sym.undeclared() - || (sym.global() && sym.scope !== sym.definition().scope))) { - AST_Node.warn("{msg}: {name} [{file}:{line},{col}]", { - msg: sym.undeclared() ? "Accidental global?" : "Assignment to global", - name: sym.name, - file: sym.start.file, - line: sym.start.line, - col: sym.start.col - }); - } - } - if (options.eval - && node instanceof AST_SymbolRef - && node.undeclared() - && node.name == "eval") { - AST_Node.warn("Eval is used [{file}:{line},{col}]", node.start); - } - if (options.unreferenced - && (node instanceof AST_SymbolDeclaration || node instanceof AST_Label) - && !(node instanceof AST_SymbolCatch) - && node.unreferenced()) { - AST_Node.warn("{type} {name} is declared but not referenced [{file}:{line},{col}]", { - type: node instanceof AST_Label ? "Label" : "Symbol", - name: node.name, - file: node.start.file, - line: node.start.line, - col: node.start.col - }); - } - if (options.func_arguments - && node instanceof AST_Lambda - && node.uses_arguments) { - AST_Node.warn("arguments used in function {name} [{file}:{line},{col}]", { - name: node.name ? node.name.name : "anonymous", - file: node.start.file, - line: node.start.line, - col: node.start.col - }); - } - if (options.nested_defuns - && node instanceof AST_Defun - && !(tw.parent() instanceof AST_Scope)) { - AST_Node.warn("Function {name} declared in nested statement \"{type}\" [{file}:{line},{col}]", { - name: node.name.name, - type: tw.parent().TYPE, - file: node.start.file, - line: node.start.line, - col: node.start.col - }); - } - }); - this.walk(tw); -}); diff --git a/node_modules/uglify-js/lib/transform.js b/node_modules/uglify-js/lib/transform.js index 3018e8ff0..8008e5714 100644 --- a/node_modules/uglify-js/lib/transform.js +++ b/node_modules/uglify-js/lib/transform.js @@ -70,7 +70,7 @@ TreeTransformer.prototype = new TreeWalker; if (y !== undefined) x = y; } } - tw.pop(this); + tw.pop(); return x; }); }; @@ -174,9 +174,8 @@ TreeTransformer.prototype = new TreeWalker; self.args = do_list(self.args, tw); }); - _(AST_Seq, function(self, tw){ - self.car = self.car.transform(tw); - self.cdr = self.cdr.transform(tw); + _(AST_Sequence, function(self, tw){ + self.expressions = do_list(self.expressions, tw); }); _(AST_Dot, function(self, tw){ diff --git a/node_modules/uglify-js/lib/utils.js b/node_modules/uglify-js/lib/utils.js index fdb204719..76306919a 100644 --- a/node_modules/uglify-js/lib/utils.js +++ b/node_modules/uglify-js/lib/utils.js @@ -43,13 +43,6 @@ "use strict"; -function array_to_hash(a) { - var ret = Object.create(null); - for (var i = 0; i < a.length; ++i) - ret[a[i]] = true; - return ret; -}; - function slice(a, start) { return Array.prototype.slice.call(a, start || 0); }; @@ -346,7 +339,7 @@ function first_in_statement(stack) { for (var i = 0, p; p = stack.parent(i); i++) { if (p instanceof AST_Statement && p.body === node) return true; - if ((p instanceof AST_Seq && p.car === node ) || + if ((p instanceof AST_Sequence && p.expressions[0] === node) || (p instanceof AST_Call && p.expression === node && !(p instanceof AST_New) ) || (p instanceof AST_Dot && p.expression === node ) || (p instanceof AST_Sub && p.expression === node ) || |