diff --git a/README.md b/README.md index 67324dbd..92559019 100644 --- a/README.md +++ b/README.md @@ -125,7 +125,9 @@ The available options are: --noerr Don't throw an error for unknown options in -c, -b or -m. --bare-returns Allow return outside of functions. Useful when - minifying CommonJS modules. + minifying CommonJS modules and Userscripts that + may be anonymous function wrapped (IIFE) by the + .user.js engine `caller`. --keep-fnames Do not mangle/drop function names. Useful for code relying on Function.prototype.name. --reserved-file File containing reserved names @@ -190,11 +192,6 @@ input files from the command line. To enable the mangler you need to pass `--mangle` (`-m`). The following (comma-separated) options are supported: -- `sort` — to assign shorter names to most frequently used variables. This - saves a few hundred bytes on jQuery before gzip, but the output is - _bigger_ after gzip (and seems to happen for other libraries I tried it - on) therefore it's not enabled by default. - - `toplevel` — mangle names declared in the toplevel scope (disabled by default). @@ -323,6 +320,9 @@ to set `true`; it's effectively a shortcut for `foo=true`). - `cascade` -- small optimization for sequences, transform `x, x` into `x` and `x = something(), x` into `x = something()` +- `collapse_vars` -- default `false`. Collapse single-use `var` and `const` + definitions when possible. + - `warnings` -- display warnings when dropping unreachable code or unused declarations etc. @@ -395,6 +395,8 @@ separate file and include it into the build. For example you can have a ```javascript const DEBUG = false; const PRODUCTION = true; +// Alternative for environments that don't support `const` +/** @const */ var STAGING = false; // etc. ``` @@ -404,8 +406,8 @@ and build your code like this: UglifyJS will notice the constants and, since they cannot be altered, it will evaluate references to them to the value itself and drop unreachable -code as usual. The possible downside of this approach is that the build -will contain the `const` declarations. +code as usual. The build will contain the `const` declarations if you use +them. If you are targeting < ES6 environments, use `/** @const */ var`. ## Beautifier options @@ -624,6 +626,9 @@ Other options: - `mangle` — pass `false` to skip mangling names. +- `mangleProperties` (default `false`) — pass an object to specify custom + mangle property options. + - `output` (default `null`) — pass an object if you wish to specify additional [output options][codegen]. The defaults are optimized for best compression. @@ -631,6 +636,13 @@ Other options: - `compress` (default `{}`) — pass `false` to skip compressing entirely. Pass an object to specify custom [compressor options][compressor]. +- `parse` (default {}) — pass an object if you wish to specify some + additional [parser options][parser]. (not all options available... see below) + +##### mangleProperties options + + - `regex` — Pass a RegExp to only mangle certain names (maps to the `--mange-regex` CLI arguments option) + We could add more options to `UglifyJS.minify` — if you need additional functionality please suggest! @@ -649,6 +661,9 @@ properties are available: - `strict` — disable automatic semicolon insertion and support for trailing comma in arrays and objects +- `bare_returns` — Allow return outside of functions. (maps to the + `--bare-returns` CLI arguments option and available to `minify` `parse` + other options object) - `filename` — the name of the file where this code is coming from - `toplevel` — a `toplevel` node (as returned by a previous invocation of `parse`) @@ -788,3 +803,4 @@ The `source_map_options` (optional) can contain the following properties: [sm-spec]: https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit [codegen]: http://lisperator.net/uglifyjs/codegen [compressor]: http://lisperator.net/uglifyjs/compress + [parser]: http://lisperator.net/uglifyjs/parser diff --git a/bin/uglifyjs b/bin/uglifyjs index f7f22215..90197cc4 100755 --- a/bin/uglifyjs +++ b/bin/uglifyjs @@ -499,17 +499,19 @@ function normalize(o) { } } -function getOptions(x, constants) { - x = ARGS[x]; - if (x == null) return null; +function getOptions(flag, constants) { + var x = ARGS[flag]; + if (x == null || x === false) return null; var ret = {}; if (x !== "") { + if (Array.isArray(x)) x = x.map(function (v) { return "(" + v + ")"; }).join(", "); + var ast; try { ast = UglifyJS.parse(x, { expression: true }); } catch(ex) { if (ex instanceof UglifyJS.JS_Parse_Error) { - print_error("Error parsing arguments in: " + x); + print_error("Error parsing arguments for flag `" + flag + "': " + x); process.exit(1); } } @@ -529,7 +531,7 @@ function getOptions(x, constants) { return true; // no descend } print_error(node.TYPE) - print_error("Error parsing arguments in: " + x); + print_error("Error parsing arguments for flag `" + flag + "': " + x); process.exit(1); })); } diff --git a/lib/compress.js b/lib/compress.js index f96fb04a..9d613797 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -66,6 +66,7 @@ function Compressor(options, false_by_default) { hoist_vars : false, if_return : !false_by_default, join_vars : !false_by_default, + collapse_vars : false, cascade : !false_by_default, side_effects : !false_by_default, pure_getters : false, @@ -175,6 +176,23 @@ merge(Compressor.prototype, { } }; + // we shouldn't compress (1,func)(something) to + // func(something) because that changes the meaning of + // the func (becomes lexical instead of global). + function maintain_this_binding(parent, orig, val) { + if (parent instanceof AST_Call && parent.expression === orig) { + if (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 val; + } + function as_statement_array(thing) { if (thing === null) return []; if (thing instanceof AST_BlockStatement) return thing.body; @@ -218,6 +236,9 @@ merge(Compressor.prototype, { if (compressor.option("join_vars")) { statements = join_consecutive_vars(statements, compressor); } + if (compressor.option("collapse_vars")) { + statements = collapse_single_use_vars(statements, compressor); + } } while (CHANGED && max_iter-- > 0); if (compressor.option("negate_iife")) { @@ -226,6 +247,163 @@ merge(Compressor.prototype, { 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; + 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") { + 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(compressor)) { + var ctt = new TreeTransformer(function(node) { + if (node === ref) + return replace_var(node, ctt.parent(), true); + }); + stat.transform(ctt); + continue; + } + + // Restrict var replacement to constants if side effects encountered. + if (side_effects_encountered |= lvalues_encountered) continue; + + // Non-constant single use vars can only be replaced in same scope. + if (ref.scope !== self) { + side_effects_encountered |= var_decl.value.has_side_effects(compressor); + 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; + } + }); + 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) + || (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; + } + }, + 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; + } + } + ); + stat.transform(tt); + } + } + + // 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); + } + + return statements; + + function is_lvalue(node, parent) { + return node instanceof AST_SymbolRef && ( + (parent instanceof AST_Assign && node === parent.left) + || (parent instanceof AST_Unary && parent.expression === node + && (parent.operator == "++" || parent.operator == "--"))); + } + 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; + } + // Further optimize statement after substitution. + stat.walk(new TreeWalker(function(node){ + delete node._squeezed; + delete node._optimized; + })); + + compressor.warn("Replacing " + (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); @@ -1276,7 +1454,8 @@ merge(Compressor.prototype, { var hoist_funs = compressor.option("hoist_funs"); var hoist_vars = compressor.option("hoist_vars"); - + var self = this; + if (!(self.body instanceof Array)) { return self; } // Hoisting makes no sense in an arrow func if (hoist_funs || hoist_vars) { var dirs = []; var hoisted = []; @@ -1312,7 +1491,10 @@ merge(Compressor.prototype, { var seq = node.to_assignments(); var p = tt.parent(); if (p instanceof AST_ForIn && p.init === node) { - if (seq == null) return node.definitions[0].name; + if (seq == null) { + var def = node.definitions[0].name; + return make_node(AST_SymbolRef, def, def); + } return seq; } if (p instanceof AST_For && p.init === node) { @@ -1543,9 +1725,13 @@ merge(Compressor.prototype, { } if (is_empty(self.alternative)) self.alternative = null; var negated = self.condition.negate(compressor); - var negated_is_best = best_of(self.condition, negated) === negated; + var self_condition_length = self.condition.print_to_string().length; + var negated_length = negated.print_to_string().length; + var negated_is_best = negated_length < self_condition_length; if (self.alternative && negated_is_best) { negated_is_best = false; // because we already do the switch here. + // no need to swap values of self_condition_length and negated_length + // here because they are only used in an equality comparison later on. self.condition = negated; var tmp = self.body; self.body = self.alternative || make_node(AST_EmptyStatement); @@ -1567,6 +1753,13 @@ merge(Compressor.prototype, { }).transform(compressor); } if (is_empty(self.alternative) && self.body instanceof AST_SimpleStatement) { + if (self_condition_length === negated_length && !negated_is_best + && self.condition instanceof AST_Binary && self.condition.operator == "||") { + // although the code length of self.condition and negated are the same, + // negated does not require additional surrounding parentheses. + // see https://github.com/mishoo/UglifyJS2/issues/979 + negated_is_best = true; + } if (negated_is_best) return make_node(AST_SimpleStatement, self, { body: make_node(AST_Binary, self, { operator : "||", @@ -1999,13 +2192,7 @@ merge(Compressor.prototype, { if (!compressor.option("side_effects")) return self; if (!self.car.has_side_effects(compressor)) { - // we shouldn't compress (1,func)(something) to - // func(something) because that changes the meaning of - // the func (becomes lexical instead of global). - var p = compressor.parent(); - if (!(p instanceof AST_Call && p.expression === self)) { - return self.cdr; - } + return maintain_this_binding(compressor.parent(), self, self.cdr); } if (compressor.option("cascade")) { if (self.car instanceof AST_Assign @@ -2195,11 +2382,10 @@ merge(Compressor.prototype, { if (ll.length > 1) { if (ll[1]) { compressor.warn("Condition left of && always true [{file}:{line},{col}]", self.start); - var rr = self.right.evaluate(compressor); - return rr[0]; + return maintain_this_binding(compressor.parent(), self, self.right.evaluate(compressor)[0]); } else { compressor.warn("Condition left of && always false [{file}:{line},{col}]", self.start); - return ll[0]; + return maintain_this_binding(compressor.parent(), self, ll[0]); } } } @@ -2208,11 +2394,10 @@ merge(Compressor.prototype, { if (ll.length > 1) { if (ll[1]) { compressor.warn("Condition left of || always true [{file}:{line},{col}]", self.start); - return ll[0]; + return maintain_this_binding(compressor.parent(), self, ll[0]); } else { compressor.warn("Condition left of || always false [{file}:{line},{col}]", self.start); - var rr = self.right.evaluate(compressor); - return rr[0]; + return maintain_this_binding(compressor.parent(), self, self.right.evaluate(compressor)[0]); } } } @@ -2437,10 +2622,10 @@ merge(Compressor.prototype, { if (cond.length > 1) { if (cond[1]) { compressor.warn("Condition always true [{file}:{line},{col}]", self.start); - return self.consequent; + return maintain_this_binding(compressor.parent(), self, self.consequent); } else { compressor.warn("Condition always false [{file}:{line},{col}]", self.start); - return self.alternative; + return maintain_this_binding(compressor.parent(), self, self.alternative); } } var negated = cond[0].negate(compressor); @@ -2522,6 +2707,10 @@ merge(Compressor.prototype, { // y?true:false --> !!y if (is_true(consequent) && is_false(alternative)) { + if (self.condition.is_boolean()) { + // boolean_expression ? true : false --> boolean_expression + return self.condition; + } self.condition = self.condition.negate(compressor); return make_node(AST_UnaryPrefix, self.condition, { operator: "!", diff --git a/lib/output.js b/lib/output.js index cbd13893..53a3a8d1 100644 --- a/lib/output.js +++ b/lib/output.js @@ -444,11 +444,11 @@ function OutputStream(options) { }); } else if (c.test) { comments = comments.filter(function(comment){ - return c.test(comment.value) || comment.type == "comment5"; + return comment.type == "comment5" || c.test(comment.value); }); } else if (typeof c == "function") { comments = comments.filter(function(comment){ - return c(self, comment) || comment.type == "comment5"; + return comment.type == "comment5" || c(self, comment); }); } @@ -596,8 +596,12 @@ function OutputStream(options) { PARENS(AST_Number, function(output){ var p = output.parent(); - if (this.getValue() < 0 && p instanceof AST_PropAccess && p.expression === this) - return true; + if (p instanceof AST_PropAccess && p.expression === this) { + var value = this.getValue(); + if (value < 0 || /^0/.test(make_num(value))) { + return true; + } + } }); PARENS([ AST_Assign, AST_Conditional ], function (output){ @@ -1172,7 +1176,7 @@ function OutputStream(options) { var expr = self.expression; expr.print(output); if (expr instanceof AST_Number && expr.getValue() >= 0) { - if (!/[xa-f.]/i.test(output.last())) { + if (!/[xa-f.)]/i.test(output.last())) { output.print("."); } } diff --git a/lib/parse.js b/lib/parse.js index 6bc79215..2230f4e0 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -408,7 +408,7 @@ function tokenizer($TEXT, filename, html5_comments, shebang) { if (octal_len > 0) ch = String.fromCharCode(parseInt(ch, 8)); else ch = read_escaped_char(true); } - else if (ch == "\n") parse_error("Unterminated string constant"); + else if ("\r\n\u2028\u2029".indexOf(ch) >= 0) parse_error("Unterminated string constant"); else if (ch == quote) break; ret += ch; } @@ -1367,6 +1367,13 @@ function parse($TEXT, options) { break; } break; + case "operator": + if (!is_identifier_string(tok.value)) { + throw new JS_Parse_Error("Invalid getter/setter name: " + tok.value, + tok.file, tok.line, tok.col, tok.pos); + } + ret = _make_symbol(AST_SymbolRef); + break; } next(); return ret; diff --git a/lib/scope.js b/lib/scope.js index f9241f2d..7c3f0e02 100644 --- a/lib/scope.js +++ b/lib/scope.js @@ -102,6 +102,7 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){ var scope = self.parent_scope = null; var labels = new Dictionary(); var defun = null; + var last_var_had_const_pragma = false; var nesting = 0; var in_destructuring = null; var in_export; @@ -165,6 +166,10 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){ node.thedef = node; node.references = []; } + if (node instanceof AST_SymbolFunarg) { + node.object_destructuring_arg = !!in_destructuring; + defun.def_variable(node, in_export); + } if (node instanceof AST_SymbolLambda) { defun.def_function(node, in_export); } @@ -235,6 +240,11 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){ } if (node instanceof AST_SymbolRef) { var name = node.name; + if (name == "eval" && tw.parent() instanceof AST_Call) { + for (var s = node.scope; s && !s.uses_eval; s = s.parent_scope) { + s.uses_eval = true; + } + } var sym = node.scope.find_variable(name); if (!sym) { var g; @@ -269,6 +279,7 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){ }); AST_Scope.DEFMETHOD("init_scope_vars", function(nesting){ + this.directives = []; // contains the directives defined in this scope, i.e. "use strict" this.variables = new Dictionary(); // map name to AST_SymbolVar (variables defined in this scope; includes functions) this.functions = new Dictionary(); // map name to AST_SymbolDefun (functions defined in this scope) this.uses_with = false; // will be set to true if this or some nested scope uses the `with` statement @@ -279,9 +290,17 @@ AST_Scope.DEFMETHOD("init_scope_vars", function(nesting){ this.nesting = nesting; // the nesting level of this scope (0 means toplevel) }); +AST_Scope.DEFMETHOD("strict", function(){ + return this.has_directive("use strict"); +}); + AST_Lambda.DEFMETHOD("init_scope_vars", function(){ AST_Scope.prototype.init_scope_vars.apply(this, arguments); this.uses_arguments = false; + + var symbol = new AST_VarDef({ name: "arguments", start: this.start, end: this.end }); + var def = new SymbolDef(this, this.variables.size(), symbol); + this.variables.set(symbol.name, def); }); AST_SymbolRef.DEFMETHOD("reference", function() { @@ -410,11 +429,17 @@ AST_Symbol.DEFMETHOD("global", function(){ return this.definition().global; }); +AST_Var.DEFMETHOD("has_const_pragma", function() { + var comments_before = this.start && this.start.comments_before; + var lastComment = comments_before && comments_before[comments_before.length - 1]; + return lastComment && /@const\b/.test(lastComment.value); +}); + AST_Toplevel.DEFMETHOD("_default_mangler_options", function(options){ return defaults(options, { except : [], eval : false, - sort : false, + sort : false, // Ignored. Flag retained for backwards compatibility. toplevel : false, screw_ie8 : false, keep_fnames : false, @@ -424,6 +449,10 @@ AST_Toplevel.DEFMETHOD("_default_mangler_options", function(options){ AST_Toplevel.DEFMETHOD("mangle_names", function(options){ options = this._default_mangler_options(options); + + // Never mangle arguments + options.except.push('arguments'); + // We only need to mangle declaration nodes. Special logic wired // into the code generator will display the mangled name if it's // present (and for AST_SymbolRef-s it'll use the mangled name of @@ -454,9 +483,6 @@ AST_Toplevel.DEFMETHOD("mangle_names", function(options){ a.push(symbol); } }); - if (options.sort) a.sort(function(a, b){ - return b.references.length - a.references.length; - }); to_mangle.push.apply(to_mangle, a); return; } diff --git a/lib/sourcemap.js b/lib/sourcemap.js index a67011f0..e5d7df60 100644 --- a/lib/sourcemap.js +++ b/lib/sourcemap.js @@ -53,16 +53,11 @@ function SourceMap(options) { orig_line_diff : 0, dest_line_diff : 0, }); + var generator = new MOZ_SourceMap.SourceMapGenerator({ + file : options.file, + sourceRoot : options.root + }); var orig_map = options.orig && new MOZ_SourceMap.SourceMapConsumer(options.orig); - var generator; - if (orig_map) { - generator = MOZ_SourceMap.SourceMapGenerator.fromSourceMap(orig_map); - } else { - generator = new MOZ_SourceMap.SourceMapGenerator({ - file : options.file, - sourceRoot : options.root - }); - } function add(source, gen_line, gen_col, orig_line, orig_col, name) { if (orig_map) { var info = orig_map.originalPositionFor({ @@ -83,7 +78,7 @@ function SourceMap(options) { source : source, name : name }); - } + }; return { add : add, get : function() { return generator }, diff --git a/lib/transform.js b/lib/transform.js index dc3a068f..34663351 100644 --- a/lib/transform.js +++ b/lib/transform.js @@ -64,7 +64,7 @@ TreeTransformer.prototype = new TreeWalker; x = this; descend(x, tw); } else { - tw.stack[tw.stack.length - 1] = x = this.clone(); + tw.stack[tw.stack.length - 1] = x = this; descend(x, tw); y = tw.after(x, in_list); if (y !== undefined) x = y; diff --git a/package.json b/package.json index 6b0d2f40..748e04fb 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "homepage": "http://lisperator.net/uglifyjs", "author": "Mihai Bazon (http://lisperator.net/)", "license": "BSD-2-Clause", - "version": "2.6.1", + "version": "2.6.2", "engines": { "node": ">=0.8.0" }, @@ -38,7 +38,8 @@ "acorn": "~0.6.0", "escodegen": "~1.3.3", "esfuzz": "~0.3.1", - "estraverse": "~1.5.1" + "estraverse": "~1.5.1", + "mocha": "~2.3.4" }, "browserify": { "transform": [ @@ -48,5 +49,6 @@ "scripts": { "shrinkwrap": "rm ./npm-shrinkwrap.json; rm -rf ./node_modules; npm i && npm shrinkwrap && npm outdated", "test": "node test/run-tests.js" - } + }, + "keywords": ["uglify", "uglify-js", "minify", "minifier"] } diff --git a/test/compress/collapse_vars.js b/test/compress/collapse_vars.js new file mode 100644 index 00000000..934a5c73 --- /dev/null +++ b/test/compress/collapse_vars.js @@ -0,0 +1,1155 @@ +collapse_vars_side_effects_1: { + options = { + collapse_vars:true, sequences:true, properties:true, dead_code:true, conditionals:true, + comparisons:true, evaluate:true, booleans:true, loops:true, unused:true, hoist_funs:true, + keep_fargs:true, if_return:true, join_vars:true, cascade:true, side_effects:true + } + input: { + function f1() { + var e = 7; + var s = "abcdef"; + var i = 2; + var log = console.log.bind(console); + var x = s.charAt(i++); + var y = s.charAt(i++); + var z = s.charAt(i++); + log(x, y, z, e); + } + function f2() { + var e = 7; + var log = console.log.bind(console); + var s = "abcdef"; + var i = 2; + var x = s.charAt(i++); + var y = s.charAt(i++); + var z = s.charAt(i++); + log(x, i, y, z, e); + } + function f3() { + var e = 7; + var s = "abcdef"; + var i = 2; + var log = console.log.bind(console); + var x = s.charAt(i++); + var y = s.charAt(i++); + var z = s.charAt(i++); + log(x, z, y, e); + } + function f4() { + var log = console.log.bind(console), + i = 10, + x = i += 2, + y = i += 3, + z = i += 4; + log(x, z, y, i); + } + } + expect: { + function f1() { + var s = "abcdef", i = 2; + console.log.bind(console)(s.charAt(i++), s.charAt(i++), s.charAt(i++), 7); + } + function f2() { + var log = console.log.bind(console), + s = "abcdef", + i = 2, + x = s.charAt(i++), + y = s.charAt(i++), + z = s.charAt(i++); + log(x, i, y, z, 7); + } + function f3() { + var s = "abcdef", + i = 2, + log = console.log.bind(console), + x = s.charAt(i++), + y = s.charAt(i++); + log(x, s.charAt(i++), y, 7); + } + function f4() { + var log = console.log.bind(console), + i = 10, + x = i += 2, + y = i += 3; + log(x, i += 4, y, i); + } + } +} + +collapse_vars_side_effects_2: { + options = { + collapse_vars:true, sequences:true, properties:true, dead_code:true, conditionals:true, + comparisons:true, evaluate:true, booleans:true, loops:true, unused:true, hoist_funs:true, + keep_fargs:true, if_return:true, join_vars:true, cascade:true, side_effects:true + } + input: { + function fn(x) { return console.log(x), x; } + + function p1() { var a = foo(), b = bar(), c = baz(); return a + b + c; } + function p2() { var a = foo(), c = bar(), b = baz(); return a + b + c; } + function p3() { var b = foo(), a = bar(), c = baz(); return a + b + c; } + function p4() { var b = foo(), c = bar(), a = baz(); return a + b + c; } + function p5() { var c = foo(), a = bar(), b = baz(); return a + b + c; } + function p6() { var c = foo(), b = bar(), a = baz(); return a + b + c; } + + function q1() { var a = foo(), b = bar(), c = baz(); return fn(a + b + c); } + function q2() { var a = foo(), c = bar(), b = baz(); return fn(a + b + c); } + function q3() { var b = foo(), a = bar(), c = baz(); return fn(a + b + c); } + function q4() { var b = foo(), c = bar(), a = baz(); return fn(a + b + c); } + function q5() { var c = foo(), a = bar(), b = baz(); return fn(a + b + c); } + function q6() { var c = foo(), b = bar(), a = baz(); return fn(a + b + c); } + + function r1() { var a = foo(), b = bar(), c = baz(); return fn(a) + fn(b) + fn(c); } + function r2() { var a = foo(), c = bar(), b = baz(); return fn(a) + fn(b) + fn(c); } + function r3() { var b = foo(), a = bar(), c = baz(); return fn(a) + fn(b) + fn(c); } + function r4() { var b = foo(), c = bar(), a = baz(); return fn(a) + fn(b) + fn(c); } + function r5() { var c = foo(), a = bar(), b = baz(); return fn(a) + fn(b) + fn(c); } + function r6() { var c = foo(), b = bar(), a = baz(); return fn(a) + fn(b) + fn(c); } + + function s1() { var a = foo(), b = bar(), c = baz(); return g(a + b + c); } + function s6() { var c = foo(), b = bar(), a = baz(); return g(a + b + c); } + + function t1() { var a = foo(), b = bar(), c = baz(); return g(a) + g(b) + g(c); } + function t6() { var c = foo(), b = bar(), a = baz(); return g(a) + g(b) + g(c); } + } + expect: { + function fn(x) { return console.log(x), x; } + + function p1() { return foo() + bar() + baz(); } + function p2() { var a = foo(), c = bar(); return a + baz() + c; } + function p3() { var b = foo(); return bar() + b + baz(); } + function p4() { var b = foo(), c = bar(); return baz() + b + c; } + function p5() { var c = foo(); return bar() + baz() + c; } + function p6() { var c = foo(), b = bar(); return baz() + b + c; } + + function q1() { return fn(foo() + bar() + baz()); } + function q2() { var a = foo(), c = bar(); return fn(a + baz() + c); } + function q3() { var b = foo(); return fn(bar() + b + baz()); } + function q4() { var b = foo(), c = bar(); return fn(baz() + b + c); } + function q5() { var c = foo(); return fn(bar() + baz() + c); } + function q6() { var c = foo(), b = bar(); return fn(baz() + b + c); } + + function r1() { var a = foo(), b = bar(), c = baz(); return fn(a) + fn(b) + fn(c); } + function r2() { var a = foo(), c = bar(), b = baz(); return fn(a) + fn(b) + fn(c); } + function r3() { var b = foo(), a = bar(), c = baz(); return fn(a) + fn(b) + fn(c); } + function r4() { var b = foo(), c = bar(); return fn(baz()) + fn(b) + fn(c); } + function r5() { var c = foo(), a = bar(), b = baz(); return fn(a) + fn(b) + fn(c); } + function r6() { var c = foo(), b = bar(); return fn(baz()) + fn(b) + fn(c); } + + function s1() { var a = foo(), b = bar(), c = baz(); return g(a + b + c); } + function s6() { var c = foo(), b = bar(), a = baz(); return g(a + b + c); } + + function t1() { var a = foo(), b = bar(), c = baz(); return g(a) + g(b) + g(c); } + function t6() { var c = foo(), b = bar(), a = baz(); return g(a) + g(b) + g(c); } + } +} + +collapse_vars_issue_721: { + options = { + collapse_vars:true, sequences:true, properties:true, dead_code:true, conditionals:true, + comparisons:true, evaluate:true, booleans:true, loops:true, unused:true, hoist_funs:true, + keep_fargs:true, if_return:true, join_vars:true, cascade:true, side_effects:true + } + input: { + define(["require", "exports", 'handlebars'], function (require, exports, hb) { + var win = window; + var _hb = win.Handlebars = hb; + return _hb; + }); + def(function (hb) { + var win = window; + var prop = 'Handlebars'; + var _hb = win[prop] = hb; + return _hb; + }); + def(function (hb) { + var prop = 'Handlebars'; + var win = window; + var _hb = win[prop] = hb; + return _hb; + }); + def(function (hb) { + var prop = 'Handlebars'; + var win = g(); + var _hb = win[prop] = hb; + return _hb; + }); + def(function (hb) { + var prop = g1(); + var win = g2(); + var _hb = win[prop] = hb; + return _hb; + }); + def(function (hb) { + var win = g2(); + var prop = g1(); + var _hb = win[prop] = hb; + return _hb; + }); + } + expect: { + define([ "require", "exports", "handlebars" ], function(require, exports, hb) { + return window.Handlebars = hb; + }), + def(function(hb) { + return window.Handlebars = hb; + }), + def(function(hb) { + return window.Handlebars = hb; + }), + def(function (hb) { + return g().Handlebars = hb; + }), + def(function (hb) { + var prop = g1(); + return g2()[prop] = hb; + }), + def(function (hb) { + return g2()[g1()] = hb; + }); + } +} + +collapse_vars_properties: { + options = { + collapse_vars:true, sequences:true, properties:true, dead_code:true, conditionals:true, + comparisons:true, evaluate:true, booleans:true, loops:true, unused:true, hoist_funs:true, + keep_fargs:true, if_return:true, join_vars:true, cascade:true, side_effects:true + } + input: { + function f1(obj) { + var prop = 'LiteralProperty'; + return !!-+obj[prop]; + } + function f2(obj) { + var prop1 = 'One'; + var prop2 = 'Two'; + return ~!!-+obj[prop1 + prop2]; + } + } + expect: { + function f1(obj) { + return !!-+obj.LiteralProperty; + } + function f2(obj) { + return ~!!-+obj.OneTwo; + } + } +} + +collapse_vars_if: { + options = { + collapse_vars:true, sequences:true, properties:true, dead_code:true, conditionals:true, + comparisons:true, evaluate:true, booleans:true, loops:true, unused:true, hoist_funs:true, + keep_fargs:true, if_return:true, join_vars:true, cascade:true, side_effects:true + } + input: { + function f1() { + var not_used = sideeffect(), x = g1 + g2; + var y = x / 4, z = 'Bar' + y; + if ('x' != z) { return g9; } + else return g5; + } + function f2() { + var x = g1 + g2, not_used = sideeffect(); + var y = x / 4 + var z = 'Bar' + y; + if ('x' != z) { return g9; } + else return g5; + } + function f3(x) { + if (x) { + var a = 1; + return a; + } + else { + var b = 2; + return b; + } + } + } + expect: { + function f1() { + sideeffect(); + return "x" != "Bar" + (g1 + g2) / 4 ? g9 : g5; + } + function f2() { + var x = g1 + g2; + sideeffect(); + return "x" != "Bar" + x / 4 ? g9 : g5; + } + function f3(x) { + if (x) { + return 1; + } + return 2; + } + } +} + +collapse_vars_while: { + options = { + collapse_vars:true, sequences:true, properties:true, dead_code:true, conditionals:true, + comparisons:true, evaluate:true, booleans:true, loops:false, unused:true, hoist_funs:true, + keep_fargs:true, if_return:true, join_vars:true, cascade:true, side_effects:true + } + input: { + function f1(y) { + // Neither the non-constant while condition `c` will be + // replaced, nor the non-constant `x` in the body. + var x = y, c = 3 - y; + while (c) { return x; } + var z = y * y; + return z; + } + function f2(y) { + // The constant `x` will be replaced in the while body. + var x = 7; + while (y) { return x; } + var z = y * y; + return z; + } + function f3(y) { + // The non-constant `n` will not be replaced in the while body. + var n = 5 - y; + while (y) { return n; } + var z = y * y; + return z; + } + } + expect: { + function f1(y) { + var x = y, c = 3 - y; + while (c) return x; + return y * y; + } + function f2(y) { + while (y) return 7; + return y * y + } + function f3(y) { + var n = 5 - y; + while (y) return n; + return y * y; + } + } +} + +collapse_vars_do_while: { + options = { + collapse_vars:true, sequences:true, properties:true, dead_code:true, conditionals:true, + comparisons:true, evaluate:true, booleans:false, loops:false, unused:true, hoist_funs:true, + keep_fargs:true, if_return:true, join_vars:true, cascade:true, side_effects:true + } + input: { + function f1(y) { + // The constant do-while condition `c` will be replaced. + var c = 9; + do { } while (c === 77); + } + function f2(y) { + // The non-constant do-while condition `c` will not be replaced. + var c = 5 - y; + do { } while (c); + } + function f3(y) { + // The constant `x` will be replaced in the do loop body. + function fn(n) { console.log(n); } + var a = 2, x = 7; + do { + fn(a = x); + break; + } while (y); + } + function f4(y) { + // The non-constant `a` will not be replaced in the do loop body. + var a = y / 4; + do { + return a; + } while (y); + } + function f5(y) { + function p(x) { console.log(x); } + do { + // The non-constant `a` will be replaced in p(a) + // because it is declared in same block. + var a = y - 3; + p(a); + } while (--y); + } + } + expect: { + function f1(y) { + do ; while (false); + } + function f2(y) { + var c = 5 - y; + do ; while (c); + } + function f3(y) { + function fn(n) { console.log(n); } + var a = 2; + do { + fn(a = 7); + break; + } while (y); + } + function f4(y) { + var a = y / 4; + do + return a; + while (y); + } + function f5(y) { + function p(x) { console.log(x); } + do { + p(y - 3); + } while (--y); + } + } +} + +collapse_vars_seq: { + options = { + collapse_vars:true, sequences:true, properties:true, dead_code:true, conditionals:true, + comparisons:true, evaluate:true, booleans:true, loops:true, unused:true, hoist_funs:true, + keep_fargs:true, if_return:true, join_vars:true, cascade:true, side_effects:true + } + input: { + var f1 = function(x, y) { + var a, b, r = x + y, q = r * r, z = q - r; + a = z, b = 7; + return a + b; + }; + } + expect: { + var f1 = function(x, y) { + var a, b, r = x + y; + return a = r * r - r, b = 7, a + b + }; + } +} + +collapse_vars_throw: { + options = { + collapse_vars:true, sequences:true, properties:true, dead_code:true, conditionals:true, + comparisons:true, evaluate:true, booleans:true, loops:true, unused:true, hoist_funs:true, + keep_fargs:true, if_return:true, join_vars:true, cascade:true, side_effects:true + } + input: { + var f1 = function(x, y) { + var a, b, r = x + y, q = r * r, z = q - r; + a = z, b = 7; + throw a + b; + }; + } + expect: { + var f1 = function(x, y) { + var a, b, r = x + y; + throw a = r * r - r, b = 7, a + b + }; + } +} + +collapse_vars_switch: { + options = { + collapse_vars:true, sequences:true, properties:true, dead_code:true, conditionals:true, + comparisons:true, evaluate:true, booleans:true, loops:true, unused:true, hoist_funs:true, + keep_fargs:true, if_return:true, join_vars:true, cascade:true, side_effects:true + } + input: { + function f1() { + var not_used = sideeffect(), x = g1 + g2; + var y = x / 4, z = 'Bar' + y; + switch (z) { case 0: return g9; } + } + function f2() { + var x = g1 + g2, not_used = sideeffect(); + var y = x / 4 + var z = 'Bar' + y; + switch (z) { case 0: return g9; } + } + function f3(x) { + switch(x) { case 1: var a = 3 - x; return a; } + } + } + expect: { + function f1() { + sideeffect(); + switch ("Bar" + (g1 + g2) / 4) { case 0: return g9 } + } + function f2() { + var x = g1 + g2; + sideeffect(); + switch ("Bar" + x / 4) { case 0: return g9 } + } + function f3(x) { + // verify no extraneous semicolon in case block before return + // when the var definition was eliminated + switch(x) { case 1: return 3 - x; } + } + } +} + +collapse_vars_assignment: { + options = { + collapse_vars:true, sequences:true, properties:true, dead_code:true, conditionals:true, + comparisons:true, evaluate:true, booleans:true, loops:true, unused:true, hoist_funs:true, + keep_fargs:true, if_return:true, join_vars:true, cascade:true, side_effects:true + } + input: { + function log(x) { return console.log(x), x; } + function f0(c) { + var a = 3 / c; + return a = a; + } + function f1(c) { + const a = 3 / c; + const b = 1 - a; + return b; + } + function f2(c) { + var a = 3 / c; + var b = a - 7; + return log(c = b); + } + function f3(c) { + var a = 3 / c; + var b = a - 7; + return log(c |= b); + } + function f4(c) { + var a = 3 / c; + var b = 2; + return log(b += a); + } + function f5(c) { + var b = 2; + var a = 3 / c; + return log(b += a); + } + function f6(c) { + var b = g(); + var a = 3 / c; + return log(b += a); + } + } + expect: { + function log(x) { return console.log(x), x; } + function f0(c) { + var a = 3 / c; + return a = a; + } + function f1(c) { + return 1 - 3 / c + } + function f2(c) { + return log(c = 3 / c - 7); + } + function f3(c) { + return log(c |= 3 / c - 7); + } + function f4(c) { + var b = 2; + return log(b += 3 / c); + } + function f5(c) { + var b = 2; + return log(b += 3 / c); + } + function f6(c) { + var b = g(); + return log(b += 3 / c); + } + } +} + +collapse_vars_lvalues: { + options = { + collapse_vars:true, sequences:true, properties:true, dead_code:true, conditionals:true, + comparisons:true, evaluate:true, booleans:true, loops:true, unused:true, hoist_funs:true, + keep_fargs:true, if_return:true, join_vars:true, cascade:true, side_effects:true + } + input: { + function f0(x) { var i = ++x; return x += i; } + function f1(x) { var a = (x -= 3); return x += a; } + function f2(x) { var z = x, a = ++z; return z += a; } + function f3(x) { var a = (x -= 3), b = x + a; return b; } + function f4(x) { var a = (x -= 3); return x + a; } + function f5(x) { var w = e1(), v = e2(), c = v = --x, b = w = x; return b - c; } + function f6(x) { var w = e1(), v = e2(), c = v = --x, b = w = x; return c - b; } + function f7(x) { var w = e1(), v = e2(), c = v - x, b = w = x; return b - c; } + function f8(x) { var w = e1(), v = e2(), b = w = x, c = v - x; return b - c; } + function f9(x) { var w = e1(), v = e2(), b = w = x, c = v - x; return c - b; } + } + expect: { + function f0(x) { var i = ++x; return x += i; } + function f1(x) { var a = (x -= 3); return x += a; } + function f2(x) { var z = x, a = ++z; return z += a; } + function f3(x) { var a = (x -= 3); return x + a; } + function f4(x) { var a = (x -= 3); return x + a; } + function f5(x) { var w = e1(), v = e2(), c = v = --x; return (w = x) - c; } + function f6(x) { var w = e1(), v = e2(); return (v = --x) - (w = x); } + function f7(x) { var w = e1(), v = e2(), c = v - x; return (w = x) - c; } + function f8(x) { var w = e1(), v = e2(); return (w = x) - (v - x); } + function f9(x) { var w = e1(); return e2() - x - (w = x); } + + } +} + +collapse_vars_misc1: { + options = { + collapse_vars:true, sequences:true, properties:true, dead_code:true, conditionals:true, + comparisons:true, evaluate:true, booleans:true, loops:true, unused:true, hoist_funs:true, + keep_fargs:true, if_return:true, join_vars:true, cascade:true, side_effects:true + } + input: { + function f0(o, a, h) { + var b = 3 - a; + var obj = o; + var seven = 7; + var prop = 'run'; + var t = obj[prop](b)[seven] = h; + return t; + } + function f1(x) { var y = 5 - x; return y; } + function f2(x) { const z = foo(), y = z / (5 - x); return y; } + function f3(x) { var z = foo(), y = (5 - x) / z; return y; } + function f4(x) { var z = foo(), y = (5 - u) / z; return y; } + function f5(x) { const z = foo(), y = (5 - window.x) / z; return y; } + function f6() { var b = window.a * window.z; return b && zap(); } + function f7() { var b = window.a * window.z; return b + b; } + function f8() { var b = window.a * window.z; var c = b + 5; return b + c; } + function f9() { var b = window.a * window.z; return bar() || b; } + function f10(x) { var a = 5, b = 3; return a += b; } + function f11(x) { var a = 5, b = 3; return a += --b; } + } + expect: { + function f0(o, a, h) { + var b = 3 - a; + return o.run(b)[7] = h; + } + function f1(x) { return 5 - x } + function f2(x) { return foo() / (5 - x) } + function f3(x) { return (5 - x) / foo() } + function f4(x) { var z = foo(); return (5 - u) / z } + function f5(x) { const z = foo(); return (5 - window.x) / z } + function f6() { return window.a * window.z && zap() } + function f7() { var b = window.a * window.z; return b + b } + function f8() { var b = window.a * window.z; return b + (b + 5) } + function f9() { var b = window.a * window.z; return bar() || b } + function f10(x) { var a = 5; return a += 3; } + function f11(x) { var a = 5, b = 3; return a += --b; } + } +} + +collapse_vars_self_reference: { + options = { + collapse_vars:true, unused:false, + sequences:true, properties:true, dead_code:true, conditionals:true, + comparisons:true, evaluate:true, booleans:true, loops:true, hoist_funs:true, + keep_fargs:true, if_return:true, join_vars:true, cascade:true, side_effects:true + } + input: { + // avoid bug in self-referential declaration. + function f1() { + var self = { + inner: function() { return self; } + }; + } + function f2() { + var self = { inner: self }; + } + } + expect: { + // note: `unused` option is false + function f1() { + var self = { + inner: function() { return self } + }; + } + function f2() { + var self = { inner: self }; + } + } +} + +collapse_vars_repeated: { + options = { + collapse_vars:true, sequences:true, properties:true, dead_code:true, conditionals:true, + comparisons:true, evaluate:true, booleans:true, loops:true, unused:true, hoist_funs:true, + keep_fargs:true, if_return:true, join_vars:true, cascade:true, side_effects:true + } + input: { + function f1() { + var dummy = 3, a = 5, unused = 2, a = 1, a = 3; + return -a; + } + function f2(x) { + var a = 3, a = x; + return a; + } + (function(x){ + var a = "GOOD" + x, e = "BAD", k = "!", e = a; + console.log(e + k); + })("!"), + + (function(x){ + var a = "GOOD" + x, e = "BAD" + x, k = "!", e = a; + console.log(e + k); + })("!"); + } + expect: { + function f1() { + return -3 + } + function f2(x) { + return x + } + (function(x){ + var a = "GOOD" + x, e = "BAD", e = a; + console.log(e + "!"); + })("!"), + (function(x){ + var a = "GOOD" + x, e = "BAD" + x, e = a; + console.log(e + "!"); + })("!"); + } +} + +collapse_vars_closures: { + options = { + collapse_vars:true, sequences:true, properties:true, dead_code:true, conditionals:true, + comparisons:true, evaluate:true, booleans:true, loops:true, unused:true, hoist_funs:true, + keep_fargs:true, if_return:true, join_vars:true, cascade:true, side_effects:true + } + input: { + function constant_vars_can_be_replaced_in_any_scope() { + var outer = 3; + return function() { return outer; } + } + function non_constant_vars_can_only_be_replace_in_same_scope(x) { + var outer = x; + return function() { return outer; } + } + } + expect: { + function constant_vars_can_be_replaced_in_any_scope() { + return function() { return 3 } + } + function non_constant_vars_can_only_be_replace_in_same_scope(x) { + var outer = x + return function() { return outer } + } + } +} + +collapse_vars_unary: { + options = { + collapse_vars:true, sequences:true, properties:true, dead_code:true, conditionals:true, + comparisons:true, evaluate:true, booleans:true, loops:true, unused:true, hoist_funs:true, + keep_fargs:true, if_return:true, join_vars:true, cascade:true, side_effects:true + } + input: { + function f0(o, p) { + var x = o[p]; + delete x; + } + function f1(n) { + var k = !!n; + return n > +k; + } + function f2(n) { + // test unary with constant + var k = 7; + return k--; + } + function f3(n) { + // test unary with constant + var k = 7; + return ++k; + } + function f4(n) { + // test unary with non-constant + var k = 8 - n; + return k--; + } + function f5(n) { + // test unary with non-constant + var k = 9 - n; + return ++k; + } + } + expect: { + function f0(o, p) { + delete o[p]; + } + function f1(n) { + return n > +!!n + } + function f2(n) { + var k = 7; + return k-- + } + function f3(n) { + var k = 7; + return ++k + } + function f4(n) { + var k = 8 - n; + return k--; + } + function f5(n) { + var k = 9 - n; + return ++k; + } + } +} + +collapse_vars_try: { + options = { + collapse_vars:true, sequences:true, properties:true, dead_code:true, conditionals:true, + comparisons:true, evaluate:true, booleans:true, loops:true, unused:true, hoist_funs:true, + keep_fargs:true, if_return:true, join_vars:true, cascade:true, side_effects:true + } + input: { + function f1() { + try { + var a = 1; + return a; + } + catch (ex) { + var b = 2; + return b; + } + finally { + var c = 3; + return c; + } + } + function f2() { + var t = could_throw(); // shouldn't be replaced in try block + try { + return t + might_throw(); + } + catch (ex) { + return 3; + } + } + } + expect: { + function f1() { + try { + return 1; + } + catch (ex) { + return 2; + } + finally { + return 3; + } + } + function f2() { + var t = could_throw(); + try { + return t + might_throw(); + } + catch (ex) { + return 3; + } + } + } +} + +collapse_vars_array: { + options = { + collapse_vars:true, sequences:true, properties:true, dead_code:true, conditionals:true, + comparisons:true, evaluate:true, booleans:true, loops:true, unused:true, hoist_funs:true, + keep_fargs:true, if_return:true, join_vars:true, cascade:true, side_effects:true + } + input: { + function f1(x, y) { + var z = x + y; + return [z]; + } + function f2(x, y) { + var z = x + y; + return [x, side_effect(), z]; + } + function f3(x, y) { + var z = f(x + y); + return [ [3], [z, x, y], [g()] ]; + } + } + expect: { + function f1(x, y) { + return [x + y] + } + function f2(x, y) { + var z = x + y + return [x, side_effect(), z] + } + function f3(x, y) { + return [ [3], [f(x + y), x, y], [g()] ] + } + } +} + +collapse_vars_object: { + options = { + collapse_vars:true, sequences:true, properties:true, dead_code:true, conditionals:true, + comparisons:true, evaluate:true, booleans:true, loops:true, unused:true, hoist_funs:true, + keep_fargs:true, if_return:true, join_vars:true, cascade:true, side_effects:true + } + input: { + function f0(x, y) { + var z = x + y; + return { + get b() { return 7; }, + r: z + }; + } + function f1(x, y) { + var z = x + y; + return { + r: z, + get b() { return 7; } + }; + } + function f2(x, y) { + var z = x + y; + var k = x - y; + return { + q: k, + r: g(x), + s: z + }; + } + function f3(x, y) { + var z = f(x + y); + return [{ + a: {q: x, r: y, s: z}, + b: g() + }]; + } + } + expect: { + function f0(x, y) { + var z = x + y; + return { + get b() { return 7; }, + r: z + }; + } + function f1(x, y) { + return { + r: x + y, + get b() { return 7; } + }; + } + function f2(x, y) { + var z = x + y; + return { + q: x - y, + r: g(x), + s: z + }; + } + function f3(x, y) { + return [{ + a: {q: x, r: y, s: f(x + y)}, + b: g() + }]; + } + } +} + +collapse_vars_eval_and_with: { + options = { + collapse_vars:true, sequences:false, properties:true, dead_code:true, conditionals:true, + comparisons:true, evaluate:true, booleans:true, loops:true, unused:true, hoist_funs:true, + keep_fargs:true, if_return:true, join_vars:true, cascade:true, side_effects:true + } + input: { + // Don't attempt to collapse vars in presence of eval() or with statement. + (function f0() { + var a = 2; + console.log(a - 5); + eval("console.log(a);"); + })(); + (function f1() { + var o = {a: 1}, a = 2; + with (o) console.log(a); + })(); + (function f2() { + var o = {a: 1}, a = 2; + return function() { with (o) console.log(a) }; + })()(); + } + expect: { + (function f0() { + var a = 2; + console.log(a - 5); + eval("console.log(a);"); + })(); + (function f1() { + var o = {a: 1}, a = 2; + with(o) console.log(a); + })(); + (function f2() { + var o = {a: 1}, a = 2; + return function() { with (o) console.log(a) }; + })()(); + } +} + +collapse_vars_constants: { + options = { + collapse_vars:true, sequences:true, properties:true, dead_code:true, conditionals:true, + comparisons:true, evaluate:true, booleans:true, loops:true, unused:true, hoist_funs:true, + keep_fargs:true, if_return:true, join_vars:true, cascade:true, side_effects:true + } + input: { + function f1(x) { + var a = 4, b = x.prop, c = 5, d = sideeffect1(), e = sideeffect2(); + return b + (function() { return d - a * e - c; })(); + } + function f2(x) { + var a = 4, b = x.prop, c = 5, not_used = sideeffect1(), e = sideeffect2(); + return b + (function() { return -a * e - c; })(); + } + function f3(x) { + var a = 4, b = x.prop, c = 5, not_used = sideeffect1(); + return b + (function() { return -a - c; })(); + } + } + expect: { + function f1(x) { + var b = x.prop, d = sideeffect1(), e = sideeffect2(); + return b + (function() { return d - 4 * e - 5; })(); + } + function f2(x) { + var b = x.prop, e = (sideeffect1(), sideeffect2()); + return b + (function() { return -4 * e - 5; })(); + } + function f3(x) { + var b = x.prop; + sideeffect1(); + return b + (function() { return -9; })(); + } + } +} + +collapse_vars_arguments: { + options = { + collapse_vars:true, sequences:true, properties:true, dead_code:true, conditionals:true, + comparisons:true, evaluate:true, booleans:true, loops:true, unused:true, hoist_funs:true, + keep_fargs:true, if_return:true, join_vars:true, cascade:true, side_effects:true + } + input: { + var outer = function() { + // Do not replace `arguments` but do replace the constant `k` before it. + var k = 7, arguments = 5, inner = function() { console.log(arguments); } + inner(k, 1); + } + outer(); + } + expect: { + (function() { + (function(){console.log(arguments);})(7, 1); + })(); + } +} + +collapse_vars_short_circuit: { + options = { + collapse_vars:true, sequences:true, properties:true, dead_code:true, conditionals:true, + comparisons:true, evaluate:true, booleans:true, loops:true, unused:true, hoist_funs:true, + keep_fargs:true, if_return:true, join_vars:true, cascade:true, side_effects:true + } + input: { + function f0(x) { var a = foo(), b = bar(); return b || x; } + function f1(x) { var a = foo(), b = bar(); return b && x; } + function f2(x) { var a = foo(), b = bar(); return x && a && b; } + function f3(x) { var a = foo(), b = bar(); return a && x; } + function f4(x) { var a = foo(), b = bar(); return a && x && b; } + function f5(x) { var a = foo(), b = bar(); return x || a || b; } + function f6(x) { var a = foo(), b = bar(); return a || x || b; } + function f7(x) { var a = foo(), b = bar(); return a && b && x; } + function f8(x,y) { var a = foo(), b = bar(); return (x || a) && (y || b); } + function f9(x,y) { var a = foo(), b = bar(); return (x && a) || (y && b); } + function f10(x,y) { var a = foo(), b = bar(); return (x - a) || (y - b); } + function f11(x,y) { var a = foo(), b = bar(); return (x - b) || (y - a); } + function f12(x,y) { var a = foo(), b = bar(); return (x - y) || (b - a); } + function f13(x,y) { var a = foo(), b = bar(); return (a - b) || (x - y); } + function f14(x,y) { var a = foo(), b = bar(); return (b - a) || (x - y); } + } + expect: { + function f0(x) { foo(); return bar() || x; } + function f1(x) { foo(); return bar() && x; } + function f2(x) { var a = foo(), b = bar(); return x && a && b; } + function f3(x) { var a = foo(); bar(); return a && x; } + function f4(x) { var a = foo(), b = bar(); return a && x && b; } + function f5(x) { var a = foo(), b = bar(); return x || a || b; } + function f6(x) { var a = foo(), b = bar(); return a || x || b; } + function f7(x) { var a = foo(), b = bar(); return a && b && x; } + function f8(x,y) { var a = foo(), b = bar(); return (x || a) && (y || b); } + function f9(x,y) { var a = foo(), b = bar(); return (x && a) || (y && b); } + function f10(x,y) { var a = foo(), b = bar(); return (x - a) || (y - b); } + function f11(x,y) { var a = foo(); return (x - bar()) || (y - a); } + function f12(x,y) { var a = foo(), b = bar(); return (x - y) || (b - a); } + function f13(x,y) { return (foo() - bar()) || (x - y); } + function f14(x,y) { var a = foo(); return (bar() - a) || (x - y); } + } +} + +collapse_vars_short_circuited_conditions: { + options = { + collapse_vars: true, + sequences: false, + dead_code: true, + conditionals: false, + comparisons: false, + evaluate: true, + booleans: true, + loops: true, + unused: true, + hoist_funs: true, + keep_fargs: true, + if_return: false, + join_vars: true, + cascade: true, + side_effects: true, + } + input: { + function c1(x) { var a = foo(), b = bar(), c = baz(); return a ? b : c; } + function c2(x) { var a = foo(), b = bar(), c = baz(); return a ? c : b; } + function c3(x) { var a = foo(), b = bar(), c = baz(); return b ? a : c; } + function c4(x) { var a = foo(), b = bar(), c = baz(); return b ? c : a; } + function c5(x) { var a = foo(), b = bar(), c = baz(); return c ? a : b; } + function c6(x) { var a = foo(), b = bar(), c = baz(); return c ? b : a; } + + function i1(x) { var a = foo(), b = bar(), c = baz(); if (a) return b; else return c; } + function i2(x) { var a = foo(), b = bar(), c = baz(); if (a) return c; else return b; } + function i3(x) { var a = foo(), b = bar(), c = baz(); if (b) return a; else return c; } + function i4(x) { var a = foo(), b = bar(), c = baz(); if (b) return c; else return a; } + function i5(x) { var a = foo(), b = bar(), c = baz(); if (c) return a; else return b; } + function i6(x) { var a = foo(), b = bar(), c = baz(); if (c) return b; else return a; } + } + expect: { + function c1(x) { var a = foo(), b = bar(), c = baz(); return a ? b : c; } + function c2(x) { var a = foo(), b = bar(), c = baz(); return a ? c : b; } + function c3(x) { var a = foo(), b = bar(), c = baz(); return b ? a : c; } + function c4(x) { var a = foo(), b = bar(), c = baz(); return b ? c : a; } + function c5(x) { var a = foo(), b = bar(); return baz() ? a : b; } + function c6(x) { var a = foo(), b = bar(); return baz() ? b : a; } + + function i1(x) { var a = foo(), b = bar(), c = baz(); if (a) return b; else return c; } + function i2(x) { var a = foo(), b = bar(), c = baz(); if (a) return c; else return b; } + function i3(x) { var a = foo(), b = bar(), c = baz(); if (b) return a; else return c; } + function i4(x) { var a = foo(), b = bar(), c = baz(); if (b) return c; else return a; } + function i5(x) { var a = foo(), b = bar(); if (baz()) return a; else return b; } + function i6(x) { var a = foo(), b = bar(); if (baz()) return b; else return a; } + } +} + diff --git a/test/compress/conditionals.js b/test/compress/conditionals.js index 65cfea64..db0d8000 100644 --- a/test/compress/conditionals.js +++ b/test/compress/conditionals.js @@ -738,3 +738,77 @@ conditional_or: { a = condition + 3 || null; } } + +trivial_boolean_ternary_expressions : { + options = { + conditionals: true, + evaluate : true, + booleans : true + }; + input: { + f('foo' in m ? true : false); + f('foo' in m ? false : true); + + f(g ? true : false); + f(foo() ? true : false); + f("bar" ? true : false); + f(5 ? true : false); + f(5.7 ? true : false); + f(x - y ? true : false); + + f(x == y ? true : false); + f(x === y ? !0 : !1); + f(x < y ? !0 : false); + f(x <= y ? true : false); + f(x > y ? true : !1); + f(x >= y ? !0 : !1); + + f(g ? false : true); + f(foo() ? false : true); + f("bar" ? false : true); + f(5 ? false : true); + f(5.7 ? false : true); + f(x - y ? false : true); + + f(x == y ? !1 : !0); + f(x === y ? false : true); + + f(x < y ? false : true); + f(x <= y ? false : !0); + f(x > y ? !1 : true); + f(x >= y ? !1 : !0); + } + expect: { + f('foo' in m); + f(!('foo' in m)); + + f(!!g); + f(!!foo()); + f(!0); + f(!0); + f(!0); + f(!!(x - y)); + + f(x == y); + f(x === y); + f(x < y); + f(x <= y); + f(x > y); + f(x >= y); + + f(!g); + f(!foo()); + f(!1); + f(!1); + f(!1); + f(!(x - y)); + + f(x != y); + f(x !== y); + + f(!(x < y)); + f(!(x <= y)); + f(!(x > y)); + f(!(x >= y)); + } +} diff --git a/test/compress/dead-code.js b/test/compress/dead-code.js index 5009ae1e..fa4b37d6 100644 --- a/test/compress/dead-code.js +++ b/test/compress/dead-code.js @@ -87,3 +87,120 @@ dead_code_constant_boolean_should_warn_more: { var moo; } } + +dead_code_const_declaration: { + options = { + dead_code : true, + loops : true, + booleans : true, + conditionals : true, + evaluate : true + }; + input: { + var unused; + const CONST_FOO = false; + if (CONST_FOO) { + console.log("unreachable"); + var moo; + function bar() {} + } + } + expect: { + var unused; + const CONST_FOO = !1; + var moo; + function bar() {} + } +} + +dead_code_const_annotation: { + options = { + dead_code : true, + loops : true, + booleans : true, + conditionals : true, + evaluate : true + }; + input: { + var unused; + /** @const */ var CONST_FOO_ANN = false; + if (CONST_FOO_ANN) { + console.log("unreachable"); + var moo; + function bar() {} + } + } + expect: { + var unused; + var CONST_FOO_ANN = !1; + var moo; + function bar() {} + } +} + +dead_code_const_annotation_regex: { + options = { + dead_code : true, + loops : true, + booleans : true, + conditionals : true, + evaluate : true + }; + input: { + var unused; + // @constraint this shouldn't be a constant + var CONST_FOO_ANN = false; + if (CONST_FOO_ANN) { + console.log("reachable"); + } + } + expect: { + var unused; + var CONST_FOO_ANN = !1; + CONST_FOO_ANN && console.log('reachable'); + } +} + +dead_code_const_annotation_complex_scope: { + options = { + dead_code : true, + loops : true, + booleans : true, + conditionals : true, + evaluate : true + }; + input: { + var unused_var; + /** @const */ var test = 'test'; + // @const + var CONST_FOO_ANN = false; + var unused_var_2; + if (CONST_FOO_ANN) { + console.log("unreachable"); + var moo; + function bar() {} + } + if (test === 'test') { + var beef = 'good'; + /** @const */ var meat = 'beef'; + var pork = 'bad'; + if (meat === 'pork') { + console.log('also unreachable'); + } else if (pork === 'good') { + console.log('reached, not const'); + } + } + } + expect: { + var unused_var; + var test = 'test'; + var CONST_FOO_ANN = !1; + var unused_var_2; + var moo; + function bar() {} + var beef = 'good'; + var meat = 'beef'; + var pork = 'bad'; + 'good' === pork && console.log('reached, not const'); + } +} diff --git a/test/compress/issue-12.js b/test/compress/issue-12.js index bf87d5c0..e2d8bda7 100644 --- a/test/compress/issue-12.js +++ b/test/compress/issue-12.js @@ -9,3 +9,50 @@ keep_name_of_setter: { input: { a = { set foo () {} } } expect: { a = { set foo () {} } } } + +setter_with_operator_keys: { + input: { + var tokenCodes = { + get instanceof(){ + return test0; + }, + set instanceof(value){ + test0 = value; + }, + set typeof(value){ + test1 = value; + }, + get typeof(){ + return test1; + }, + set else(value){ + test2 = value; + }, + get else(){ + return test2; + } + }; + } + expect: { + var tokenCodes = { + get instanceof(){ + return test0; + }, + set instanceof(value){ + test0 = value; + }, + set typeof(value){ + test1 = value; + }, + get typeof(){ + return test1; + }, + set else(value){ + test2 = value; + }, + get else(){ + return test2; + } + }; + } +} \ No newline at end of file diff --git a/test/compress/issue-782.js b/test/compress/issue-782.js index cce15fd1..2f72d1ab 100644 --- a/test/compress/issue-782.js +++ b/test/compress/issue-782.js @@ -1,23 +1,27 @@ remove_redundant_sequence_items: { options = { side_effects: true }; input: { + (0, 1, eval)(); (0, 1, logThis)(); (0, 1, _decorators.logThis)(); } expect: { - (0, logThis)(); + (0, eval)(); + logThis(); (0, _decorators.logThis)(); } } -dont_remove_lexical_binding_sequence: { +dont_remove_this_binding_sequence: { options = { side_effects: true }; input: { + (0, eval)(); (0, logThis)(); (0, _decorators.logThis)(); } expect: { - (0, logThis)(); + (0, eval)(); + logThis(); (0, _decorators.logThis)(); } } diff --git a/test/compress/issue-892.js b/test/compress/issue-892.js new file mode 100644 index 00000000..2dab420f --- /dev/null +++ b/test/compress/issue-892.js @@ -0,0 +1,32 @@ +dont_mangle_arguments: { + mangle = { + }; + options = { + sequences : true, + properties : true, + dead_code : true, + drop_debugger : true, + conditionals : true, + comparisons : true, + evaluate : true, + booleans : true, + loops : true, + unused : true, + hoist_funs : true, + keep_fargs : true, + keep_fnames : false, + hoist_vars : true, + if_return : true, + join_vars : true, + cascade : true, + side_effects : true, + negate_iife : false + }; + input: { + (function(){ + var arguments = arguments, not_arguments = 9; + console.log(not_arguments, arguments); + })(5,6,7); + } + expect_exact: "(function(){var arguments=arguments,o=9;console.log(o,arguments)})(5,6,7);" +} diff --git a/test/compress/issue-913.js b/test/compress/issue-913.js new file mode 100644 index 00000000..9d34d9d9 --- /dev/null +++ b/test/compress/issue-913.js @@ -0,0 +1,20 @@ +keep_var_for_in: { + options = { + hoist_vars: true, + unused: true + }; + input: { + (function(obj){ + var foo = 5; + for (var i in obj) + return foo; + })(); + } + expect: { + (function(obj){ + var i, foo = 5; + for (i in obj) + return foo; + })(); + } +} diff --git a/test/compress/issue-973.js b/test/compress/issue-973.js new file mode 100644 index 00000000..0e040922 --- /dev/null +++ b/test/compress/issue-973.js @@ -0,0 +1,96 @@ +this_binding_conditionals: { + options = { + conditionals: true, + evaluate : true + }; + input: { + (1 && a)(); + (0 || a)(); + (0 || 1 && a)(); + (1 ? a : 0)(); + + (1 && a.b)(); + (0 || a.b)(); + (0 || 1 && a.b)(); + (1 ? a.b : 0)(); + + (1 && a[b])(); + (0 || a[b])(); + (0 || 1 && a[b])(); + (1 ? a[b] : 0)(); + + (1 && eval)(); + (0 || eval)(); + (0 || 1 && eval)(); + (1 ? eval : 0)(); + } + expect: { + a(); + a(); + a(); + a(); + + (0, a.b)(); + (0, a.b)(); + (0, a.b)(); + (0, a.b)(); + + (0, a[b])(); + (0, a[b])(); + (0, a[b])(); + (0, a[b])(); + + (0, eval)(); + (0, eval)(); + (0, eval)(); + (0, eval)(); + } +} + +this_binding_collapse_vars: { + options = { + collapse_vars: true, + }; + input: { + var c = a; c(); + var d = a.b; d(); + var e = eval; e(); + } + expect: { + a(); + (0, a.b)(); + (0, eval)(); + } +} + +this_binding_side_effects: { + options = { + side_effects : true + }; + input: { + (function (foo) { + (0, foo)(); + (0, foo.bar)(); + (0, eval)('console.log(foo);'); + }()); + (function (foo) { + var eval = console; + (0, foo)(); + (0, foo.bar)(); + (0, eval)('console.log(foo);'); + }()); + } + expect: { + (function (foo) { + foo(); + (0, foo.bar)(); + (0, eval)('console.log(foo);'); + }()); + (function (foo) { + var eval = console; + foo(); + (0, foo.bar)(); + (0, eval)('console.log(foo);'); + }()); + } +} \ No newline at end of file diff --git a/test/compress/issue-976.js b/test/compress/issue-976.js new file mode 100644 index 00000000..dea30705 --- /dev/null +++ b/test/compress/issue-976.js @@ -0,0 +1,88 @@ +eval_collapse_vars: { + options = { + collapse_vars:true, sequences:false, properties:true, dead_code:true, conditionals:true, + comparisons:true, evaluate:true, booleans:true, loops:true, unused:true, hoist_funs:true, + keep_fargs:true, if_return:true, join_vars:true, cascade:true, side_effects:true + }; + input: { + function f1() { + var e = 7; + var s = "abcdef"; + var i = 2; + var eval = console.log.bind(console); + var x = s.charAt(i++); + var y = s.charAt(i++); + var z = s.charAt(i++); + eval(x, y, z, e); + } + function p1() { var a = foo(), b = bar(), eval = baz(); return a + b + eval; } + function p2() { var a = foo(), b = bar(), eval = baz; return a + b + eval(); } + (function f2(eval) { + var a = 2; + console.log(a - 5); + eval("console.log(a);"); + })(eval); + } + expect: { + function f1() { + var e = 7, + s = "abcdef", + i = 2, + eval = console.log.bind(console), + x = s.charAt(i++), + y = s.charAt(i++), + z = s.charAt(i++); + eval(x, y, z, e); + } + function p1() { return foo() + bar() + baz(); } + function p2() { var a = foo(), b = bar(), eval = baz; return a + b + eval(); } + (function f2(eval) { + var a = 2; + console.log(a - 5); + eval("console.log(a);"); + })(eval); + } +} + +eval_unused: { + options = { unused: true, keep_fargs: false }; + input: { + function f1(a, eval, c, d, e) { + return a('c') + eval; + } + function f2(a, b, c, d, e) { + return a + eval('c'); + } + function f3(a, eval, c, d, e) { + return a + eval('c'); + } + } + expect: { + function f1(a, eval) { + return a('c') + eval; + } + function f2(a, b, c, d, e) { + return a + eval('c'); + } + function f3(a, eval, c, d, e) { + return a + eval('c'); + } + } +} + +eval_mangle: { + mangle = { + }; + input: { + function f1(a, eval, c, d, e) { + return a('c') + eval; + } + function f2(a, b, c, d, e) { + return a + eval('c'); + } + function f3(a, eval, c, d, e) { + return a + eval('c'); + } + } + expect_exact: 'function f1(n,c,e,a,o){return n("c")+c}function f2(a,b,c,d,e){return a+eval("c")}function f3(a,eval,c,d,e){return a+eval("c")}' +} diff --git a/test/compress/issue-979.js b/test/compress/issue-979.js new file mode 100644 index 00000000..bae15db8 --- /dev/null +++ b/test/compress/issue-979.js @@ -0,0 +1,89 @@ +issue979_reported: { + options = { + sequences:true, properties:true, dead_code:true, conditionals:true, + comparisons:true, evaluate:true, booleans:true, loops:true, unused:true, hoist_funs:true, + keep_fargs:true, if_return:true, join_vars:true, cascade:true, side_effects:true + } + input: { + function f1() { + if (a == 1 || b == 2) { + foo(); + } + } + function f2() { + if (!(a == 1 || b == 2)) { + } + else { + foo(); + } + } + } + expect: { + function f1() { + 1!=a&&2!=b||foo(); + } + function f2() { + 1!=a&&2!=b||foo(); + } + } +} + +issue979_test_negated_is_best: { + options = { + sequences:true, properties:true, dead_code:true, conditionals:true, + comparisons:true, evaluate:true, booleans:true, loops:true, unused:true, hoist_funs:true, + keep_fargs:true, if_return:true, join_vars:true, cascade:true, side_effects:true + } + input: { + function f3() { + if (a == 1 | b == 2) { + foo(); + } + } + function f4() { + if (!(a == 1 | b == 2)) { + } + else { + foo(); + } + } + function f5() { + if (a == 1 && b == 2) { + foo(); + } + } + function f6() { + if (!(a == 1 && b == 2)) { + } + else { + foo(); + } + } + function f7() { + if (a == 1 || b == 2) { + foo(); + } + else { + return bar(); + } + } + } + expect: { + function f3() { + 1==a|2==b&&foo(); + } + function f4() { + 1==a|2==b&&foo(); + } + function f5() { + 1==a&&2==b&&foo(); + } + function f6() { + 1!=a||2!=b||foo(); + } + function f7() { + return 1!=a&&2!=b?bar():void foo(); + } + } +} + diff --git a/test/compress/numbers.js b/test/compress/numbers.js new file mode 100644 index 00000000..8e32ad02 --- /dev/null +++ b/test/compress/numbers.js @@ -0,0 +1,19 @@ +hex_numbers_in_parentheses_for_prototype_functions: { + input: { + (-2); + (-2).toFixed(0); + + (2); + (2).toFixed(0); + + (0.2); + (0.2).toFixed(0); + + (0.00000002); + (0.00000002).toFixed(0); + + (1000000000000000128); + (1000000000000000128).toFixed(0); + } + expect_exact: "-2;(-2).toFixed(0);2;2..toFixed(0);.2;.2.toFixed(0);2e-8;2e-8.toFixed(0);0xde0b6b3a7640080;(0xde0b6b3a7640080).toFixed(0);" +} diff --git a/test/mocha.js b/test/mocha.js new file mode 100644 index 00000000..411f52c5 --- /dev/null +++ b/test/mocha.js @@ -0,0 +1,29 @@ +var Mocha = require('mocha'), + fs = require('fs'), + path = require('path'); + +// Instantiate a Mocha instance. +var mocha = new Mocha({}); + +var testDir = __dirname + '/mocha/'; + +// Add each .js file to the mocha instance +fs.readdirSync(testDir).filter(function(file){ + // Only keep the .js files + return file.substr(-3) === '.js'; + +}).forEach(function(file){ + mocha.addFile( + path.join(testDir, file) + ); +}); + +module.exports = function() { + mocha.run(function(failures) { + if (failures !== 0) { + process.on('exit', function () { + process.exit(failures); + }); + } + }); +}; \ No newline at end of file diff --git a/test/mocha/arguments.js b/test/mocha/arguments.js new file mode 100644 index 00000000..089826fc --- /dev/null +++ b/test/mocha/arguments.js @@ -0,0 +1,22 @@ +var UglifyJS = require('../../'); +var assert = require("assert"); + +describe("arguments", function() { + it("Should known that arguments in functions are local scoped", function() { + var ast = UglifyJS.parse("var arguments; var f = function() {arguments.length}"); + ast.figure_out_scope(); + + // Test scope of `var arguments` + assert.strictEqual(ast.find_variable("arguments").global, true); + + // Select arguments symbol in function + var symbol = ast.body[1].definitions[0].value.find_variable("arguments"); + + assert.strictEqual(symbol.global, false); + assert.strictEqual(symbol.scope, ast. // From ast + body[1]. // Select 2nd statement (equals to `var f ...`) + definitions[0]. // First definition of selected statement + value // Select function as scope + ); + }); +}); \ No newline at end of file diff --git a/test/mocha/comment-filter.js b/test/mocha/comment-filter.js new file mode 100644 index 00000000..ea2ec2eb --- /dev/null +++ b/test/mocha/comment-filter.js @@ -0,0 +1,45 @@ +var UglifyJS = require('../../'); +var assert = require("assert"); + +describe("comment filters", function() { + it("Should be able to filter comments by passing regex", function() { + var ast = UglifyJS.parse("/*!test1*/\n/*test2*/\n//!test3\n//test4\ntest7\n-->!test8"); + assert.strictEqual(ast.print_to_string({comments: /^!/}), "/*!test1*/\n//!test3\n//!test6\n//!test8\n"); + }); + + it("Should be able to filter comments by passing a function", function() { + var ast = UglifyJS.parse("/*TEST 123*/\n//An other comment\n//8 chars."); + var f = function(node, comment) { + return comment.value.length === 8; + }; + + assert.strictEqual(ast.print_to_string({comments: f}), "/*TEST 123*/\n//8 chars.\n"); + }); + + it("Should be able to get the comment and comment type when using a function", function() { + var ast = UglifyJS.parse("/*!test1*/\n/*test2*/\n//!test3\n//test4\ntest7\n-->!test8"); + var f = function(node, comment) { + return comment.type == "comment1" || comment.type == "comment3"; + }; + + assert.strictEqual(ast.print_to_string({comments: f}), "//!test3\n//test4\n//test5\n//!test6\n"); + }); + + it("Should be able to filter comments by passing a boolean", function() { + var ast = UglifyJS.parse("/*!test1*/\n/*test2*/\n//!test3\n//test4\ntest7\n-->!test8"); + + assert.strictEqual(ast.print_to_string({comments: true}), "/*!test1*/\n/*test2*/\n//!test3\n//test4\n//test5\n//!test6\n//test7\n//!test8\n"); + assert.strictEqual(ast.print_to_string({comments: false}), ""); + }); + + it("Should never be able to filter comment5 (shebangs)", function() { + var ast = UglifyJS.parse("#!Random comment\n//test1\n/*test2*/"); + var f = function(node, comment) { + assert.strictEqual(comment.type === "comment5", false); + + return true; + }; + + assert.strictEqual(ast.print_to_string({comments: f}), "#!Random comment\n//test1\n/*test2*/\n"); + }); +}); diff --git a/test/mocha/getter-setter.js b/test/mocha/getter-setter.js new file mode 100644 index 00000000..641a2026 --- /dev/null +++ b/test/mocha/getter-setter.js @@ -0,0 +1,89 @@ +var UglifyJS = require('../../'); +var assert = require("assert"); + +describe("Getters and setters", function() { + it("Should not accept operator symbols as getter/setter name", function() { + var illegalOperators = [ + "++", + "--", + "+", + "-", + "!", + "~", + "&", + "|", + "^", + "*", + "/", + "%", + ">>", + "<<", + ">>>", + "<", + ">", + "<=", + ">=", + "==", + "===", + "!=", + "!==", + "?", + "=", + "+=", + "-=", + "/=", + "*=", + "%=", + ">>=", + "<<=", + ">>>=", + "|=", + "^=", + "&=", + "&&", + "||" + ]; + var generator = function() { + var results = []; + + for (var i in illegalOperators) { + results.push({ + code: "var obj = { get " + illegalOperators[i] + "() { return test; }};", + operator: illegalOperators[i], + method: "get" + }); + results.push({ + code: "var obj = { set " + illegalOperators[i] + "(value) { test = value}};", + operator: illegalOperators[i], + method: "set" + }); + } + + return results; + }; + + var testCase = function(data) { + return function() { + UglifyJS.parse(data.code); + }; + }; + + var fail = function(data) { + return function (e) { + return e instanceof UglifyJS.JS_Parse_Error && + e.message === "Invalid getter/setter name: " + data.operator; + }; + }; + + var errorMessage = function(data) { + return "Expected but didn't get a syntax error while parsing following line:\n" + data.code; + }; + + var tests = generator(); + for (var i = 0; i < tests.length; i++) { + var test = tests[i]; + assert.throws(testCase(test), fail(test), errorMessage(test)); + } + }); + +}); diff --git a/test/mocha/string-literal.js b/test/mocha/string-literal.js new file mode 100644 index 00000000..84aaad7e --- /dev/null +++ b/test/mocha/string-literal.js @@ -0,0 +1,34 @@ +var UglifyJS = require('../../'); +var assert = require("assert"); + +describe("String literals", function() { + it("Should throw syntax error if a string literal contains a newline", function() { + var inputs = [ + "'\n'", + "'\r'", + '"\r\n"', + "'\u2028'", + '"\u2029"' + ]; + + var test = function(input) { + return function() { + var ast = UglifyJS.parse(input); + }; + }; + + var error = function(e) { + return e instanceof UglifyJS.JS_Parse_Error && + e.message === "Unterminated string constant"; + }; + + for (var input in inputs) { + assert.throws(test(inputs[input]), error); + } + }); + + it("Should not throw syntax error if a string has a line continuation", function() { + var output = UglifyJS.parse('var a = "a\\\nb";').print_to_string(); + assert.equal(output, 'var a="ab";'); + }); +}); \ No newline at end of file diff --git a/test/run-tests.js b/test/run-tests.js index c1530109..9a31d148 100755 --- a/test/run-tests.js +++ b/test/run-tests.js @@ -16,6 +16,9 @@ if (failures) { process.exit(1); } +var mocha_tests = require("./mocha.js"); +mocha_tests(); + var run_sourcemaps_tests = require('./sourcemaps'); run_sourcemaps_tests(); @@ -105,6 +108,7 @@ function run_compress_tests() { var output = input.transform(cmp); output.figure_out_scope(); if (test.mangle) { + output.compute_char_frequency(test.mangle); output.mangle_names(test.mangle); } output = make_code(output, output_options); diff --git a/tools/exports.js b/tools/exports.js index 5007e03b..110b5c4e 100644 --- a/tools/exports.js +++ b/tools/exports.js @@ -15,3 +15,4 @@ exports["parse"] = parse; exports["push_uniq"] = push_uniq; exports["string_template"] = string_template; exports["is_identifier"] = is_identifier; +exports["SymbolDef"] = SymbolDef; diff --git a/tools/node.js b/tools/node.js index f6048661..fa8c19dc 100644 --- a/tools/node.js +++ b/tools/node.js @@ -32,15 +32,18 @@ UglifyJS.AST_Node.warn_function = function(txt) { exports.minify = function(files, options) { options = UglifyJS.defaults(options, { - spidermonkey : false, - outSourceMap : null, - sourceRoot : null, - inSourceMap : null, - fromString : false, - warnings : false, - mangle : {}, - output : null, - compress : {} + spidermonkey : false, + outSourceMap : null, + sourceRoot : null, + inSourceMap : null, + fromString : false, + warnings : false, + mangle : {}, + mangleProperties : false, + nameCache : null, + output : null, + compress : {}, + parse : {} }); UglifyJS.base54.reset(); @@ -60,7 +63,8 @@ exports.minify = function(files, options) { sourcesContent[file] = code; toplevel = UglifyJS.parse(code, { filename: options.fromString ? i : file, - toplevel: toplevel + toplevel: toplevel, + bare_returns: options.parse ? options.parse.bare_returns : undefined }); }); } @@ -77,14 +81,21 @@ exports.minify = function(files, options) { toplevel = toplevel.transform(sq); } - // 3. mangle + // 3. mangle properties + if (options.mangleProperties || options.nameCache) { + options.mangleProperties.cache = UglifyJS.readNameCache(options.nameCache, "props"); + toplevel = UglifyJS.mangle_properties(toplevel, options.mangleProperties); + UglifyJS.writeNameCache(options.nameCache, "props", options.mangleProperties.cache); + } + + // 4. mangle if (options.mangle) { toplevel.figure_out_scope(options.mangle); toplevel.compute_char_frequency(options.mangle); toplevel.mangle_names(options.mangle); } - // 4. output + // 5. output var inMap = options.inSourceMap; var output = {}; if (typeof options.inSourceMap == "string") {