diff options
author | Alex Lam S.L <alexlamsl@gmail.com> | 2019-04-15 22:23:11 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-04-15 22:23:11 +0800 |
commit | 5172ba5f2ade22716e65ff2333218fc08996bfcc (patch) | |
tree | f5d5084980b543c64b7fedd1c233eb8b4414ddb2 | |
parent | a57b069409d84e3768dfd8e786532e458f68e553 (diff) | |
download | tracifyjs-5172ba5f2ade22716e65ff2333218fc08996bfcc.tar.gz tracifyjs-5172ba5f2ade22716e65ff2333218fc08996bfcc.zip |
introduce `functions` (#3360)
`var f = function() {};` => `function f() {}`
-rw-r--r-- | README.md | 3 | ||||
-rw-r--r-- | lib/compress.js | 84 | ||||
-rw-r--r-- | lib/output.js | 2 | ||||
-rw-r--r-- | lib/parse.js | 6 | ||||
-rw-r--r-- | lib/scope.js | 4 | ||||
-rw-r--r-- | test/compress/functions.js | 213 | ||||
-rw-r--r-- | test/jetstream.js | 2 |
7 files changed, 268 insertions, 46 deletions
@@ -636,6 +636,9 @@ If you're using the `X-SourceMap` header instead, you can just omit `sourceMap.u - `expression` (default: `false`) -- Pass `true` to preserve completion values from terminal statements without `return`, e.g. in bookmarklets. +- `functions` (default: `true`) -- convert declarations from `var`to `function` + whenever possible. + - `global_defs` (default: `{}`) -- see [conditional compilation](#conditional-compilation) - `hoist_funs` (default: `false`) -- hoist function declarations diff --git a/lib/compress.js b/lib/compress.js index 6c12d2bd..2fdfc89e 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -60,6 +60,7 @@ function Compressor(options, false_by_default) { drop_debugger : !false_by_default, evaluate : !false_by_default, expression : false, + functions : !false_by_default, global_defs : false, hoist_funs : false, hoist_props : !false_by_default, @@ -150,7 +151,7 @@ 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 (def.global) for (var i = 0; i < def.orig.length; i++) if (!this.toplevel[def.orig[i] instanceof AST_SymbolDefun ? "funcs" : "vars"]) return true; return false; @@ -1241,7 +1242,7 @@ merge(Compressor.prototype, { // Scan case expressions first in a switch statement if (node instanceof AST_Switch) { node.expression = node.expression.transform(scanner); - for (var i = 0, len = node.body.length; !abort && i < len; i++) { + for (var i = 0; !abort && i < node.body.length; i++) { var branch = node.body[i]; if (branch instanceof AST_Case) { if (!hit) { @@ -1887,7 +1888,7 @@ merge(Compressor.prototype, { } function next_index(i) { - for (var j = i + 1, len = statements.length; j < len; j++) { + for (var j = i + 1; j < statements.length; j++) { var stat = statements[j]; if (!(stat instanceof AST_Var && declarations_only(stat))) { break; @@ -1978,7 +1979,7 @@ merge(Compressor.prototype, { function to_simple_statement(block, decls) { if (!(block instanceof AST_BlockStatement)) return block; var stat = null; - for (var i = 0, len = block.body.length; i < len; i++) { + for (var i = 0; i < block.body.length; i++) { var line = block.body[i]; if (line instanceof AST_Var && declarations_only(line)) { decls.push(line); @@ -2118,7 +2119,7 @@ merge(Compressor.prototype, { function join_consecutive_vars(statements) { var defs; - for (var i = 0, j = -1, len = statements.length; i < len; i++) { + for (var i = 0, j = -1; i < statements.length; i++) { var stat = statements[i]; var prev = statements[j]; if (stat instanceof AST_Definitions) { @@ -2719,7 +2720,7 @@ merge(Compressor.prototype, { def(AST_Array, function(compressor, cached, depth) { if (compressor.option("unsafe")) { var elements = []; - for (var i = 0, len = this.elements.length; i < len; i++) { + for (var i = 0; i < this.elements.length; i++) { var element = this.elements[i]; var value = element._eval(compressor, cached, depth); if (element === value) return this; @@ -2732,7 +2733,7 @@ merge(Compressor.prototype, { def(AST_Object, function(compressor, cached, depth) { if (compressor.option("unsafe")) { var val = {}; - for (var i = 0, len = this.properties.length; i < len; i++) { + for (var i = 0; i < this.properties.length; i++) { var prop = this.properties[i]; var key = prop.key; if (key instanceof AST_Symbol) { @@ -2927,7 +2928,7 @@ merge(Compressor.prototype, { if (!native_fn || !native_fn[key]) return this; } var args = []; - for (var i = 0, len = this.args.length; i < len; i++) { + for (var i = 0; i < this.args.length; i++) { var arg = this.args[i]; var value = arg._eval(compressor, cached, depth); if (arg === value) return this; @@ -3463,9 +3464,7 @@ merge(Compressor.prototype, { 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); - } + 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; @@ -3572,29 +3571,40 @@ merge(Compressor.prototype, { if (def.value && sym.id in fixed_ids && fixed_ids[sym.id] !== def) { def.value = def.value.drop_side_effect_free(compressor); } - if (def.name instanceof AST_SymbolVar) { - var var_defs = var_defs_by_id.get(sym.id); - if (var_defs.length > 1 && (!def.value || sym.orig.indexOf(def.name) > sym.eliminated)) { - compressor.warn("Dropping duplicated definition of variable {name} [{file}:{line},{col}]", template(def.name)); - if (def.value) { - var ref = make_node(AST_SymbolRef, def.name, def.name); - sym.references.push(ref); - var assign = make_node(AST_Assign, def, { - operator: "=", - left: ref, - right: def.value - }); - if (fixed_ids[sym.id] === def) { - fixed_ids[sym.id] = assign; - } - side_effects.push(assign.transform(tt)); + var var_defs = var_defs_by_id.get(sym.id); + if (var_defs.length > 1 && (!def.value || sym.orig.indexOf(def.name) > sym.eliminated)) { + compressor.warn("Dropping duplicated definition of variable {name} [{file}:{line},{col}]", template(def.name)); + if (def.value) { + var ref = make_node(AST_SymbolRef, def.name, def.name); + sym.references.push(ref); + var assign = make_node(AST_Assign, def, { + operator: "=", + left: ref, + right: def.value + }); + if (fixed_ids[sym.id] === def) { + fixed_ids[sym.id] = assign; } - remove(var_defs, def); - sym.eliminated++; - return; + side_effects.push(assign.transform(tt)); } + remove(var_defs, def); + sym.eliminated++; + return; } - if (def.value) { + if (!def.value) { + head.push(def); + } else if (compressor.option("functions") + && def.value === def.name.fixed_value() + && def.value instanceof AST_Function + && !def.value.name + && !def.value.variables.get(def.name.name) + && (!compressor.has_directive("use strict") || parent instanceof AST_Scope)) { + compressor.warn("Declaring {name} as function [{file}:{line},{col}]", template(def.name)); + var defun = make_node(AST_Defun, def, def.value); + defun.name = make_node(AST_SymbolDefun, def.name, def.name); + self.def_function(defun.name); + body.push(defun); + } else { if (side_effects.length > 0) { if (tail.length > 0) { side_effects.push(def.value); @@ -3607,8 +3617,6 @@ merge(Compressor.prototype, { side_effects = []; } tail.push(def); - } else { - head.push(def); } } else if (sym.orig[0] instanceof AST_SymbolCatch) { var value = def.value && def.value.drop_side_effect_free(compressor); @@ -4669,7 +4677,7 @@ merge(Compressor.prototype, { && !fn.uses_arguments && !fn.pinned()) { var pos = 0, last = 0; - for (var i = 0, len = self.args.length; i < len; i++) { + for (var i = 0; i < self.args.length; i++) { var trim = i >= fn.argnames.length; if (trim || fn.argnames[i].__unused) { var node = self.args[i].drop_side_effect_free(compressor); @@ -5047,7 +5055,7 @@ merge(Compressor.prototype, { } function can_inject_args(catches, safe_to_inject) { - for (var i = 0, len = fn.argnames.length; i < len; i++) { + for (var i = 0; i < fn.argnames.length; i++) { var arg = fn.argnames[i]; if (arg.__unused) continue; if (!safe_to_inject @@ -5062,7 +5070,7 @@ merge(Compressor.prototype, { } function can_inject_vars(catches, safe_to_inject) { - for (var i = 0, len = fn.body.length; i < len; i++) { + for (var i = 0; i < fn.body.length; i++) { var stat = fn.body[i]; if (!(stat instanceof AST_Var)) continue; if (!safe_to_inject) return false; @@ -5142,10 +5150,10 @@ merge(Compressor.prototype, { function flatten_vars(decls, expressions) { var pos = expressions.length; - for (var i = 0, lines = fn.body.length; i < lines; i++) { + for (var i = 0; i < fn.body.length; i++) { var stat = fn.body[i]; if (!(stat instanceof AST_Var)) continue; - for (var j = 0, defs = stat.definitions.length; j < defs; j++) { + for (var j = 0; j < stat.definitions.length; j++) { var var_def = stat.definitions[j]; var name = var_def.name; var redef = name.definition().redefined(); diff --git a/lib/output.js b/lib/output.js index 7f7e74df..4b1ae949 100644 --- a/lib/output.js +++ b/lib/output.js @@ -123,7 +123,7 @@ function OutputStream(options) { }); } : function(str) { var s = ""; - for (var i = 0, len = str.length; i < len; i++) { + for (var i = 0; i < str.length; i++) { if (is_surrogate_pair_head(str[i]) && !is_surrogate_pair_tail(str[i + 1]) || is_surrogate_pair_tail(str[i]) && !is_surrogate_pair_head(str[i - 1])) { s += "\\u" + str.charCodeAt(i).toString(16); diff --git a/lib/parse.js b/lib/parse.js index a58557dd..f7c5d11b 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -272,10 +272,8 @@ function tokenizer($TEXT, filename, html5_comments, shebang) { function find_eol() { var text = S.text; - for (var i = S.pos, n = S.text.length; i < n; ++i) { - var ch = text[i]; - if (NEWLINE_CHARS[ch]) - return i; + for (var i = S.pos; i < S.text.length; ++i) { + if (NEWLINE_CHARS[text[i]]) return i; } return -1; } diff --git a/lib/scope.js b/lib/scope.js index f78b4c5e..b6bfe1a8 100644 --- a/lib/scope.js +++ b/lib/scope.js @@ -333,7 +333,7 @@ function next_mangled_name(scope, options, def) { } while (scope = scope.parent_scope); }); var name; - for (var i = 0, len = holes.length; i < len; i++) { + for (var i = 0; i < holes.length; i++) { name = base54(holes[i]); if (names[name]) continue; holes.splice(i, 1); @@ -555,7 +555,7 @@ var base54 = (function() { var freq = Object.create(null); function init(chars) { var array = []; - for (var i = 0, len = chars.length; i < len; i++) { + for (var i = 0; i < chars.length; i++) { var ch = chars[i]; array.push(ch); freq[ch] = -1e-2 * i; diff --git a/test/compress/functions.js b/test/compress/functions.js index cb7a1b86..2d00c594 100644 --- a/test/compress/functions.js +++ b/test/compress/functions.js @@ -2703,3 +2703,216 @@ loop_inline: { } expect_stdout: "undefined" } + +functions: { + options = { + functions: true, + reduce_vars: true, + unused: true, + } + input: { + !function() { + var a = function() { + return "a"; + }; + var b = function x() { + return !!x; + }; + var c = function(c) { + return c; + }; + if (c(b(a()))) { + var d = function() {}; + var e = function y() { + return typeof y; + }; + var f = function(f) { + return f; + }; + console.log(a(d()), b(e()), c(f(42)), typeof d, e(), typeof f); + } + }(); + } + expect: { + !function() { + function a() { + return "a"; + } + var b = function x() { + return !!x; + }; + var c = function(c) { + return c; + }; + if (c(b(a()))) { + function d() {} + var e = function y() { + return typeof y; + }; + var f = function(f) { + return f; + }; + console.log(a(d()), b(e()), c(f(42)), typeof d, e(), typeof f); + } + }(); + } + expect_stdout: "a true 42 function function function" +} + +functions_use_strict: { + options = { + functions: true, + reduce_vars: true, + unused: true, + } + input: { + "use strict"; + !function() { + var a = function() { + return "a"; + }; + var b = function x() { + return !!x; + }; + var c = function(c) { + return c; + }; + if (c(b(a()))) { + var d = function() {}; + var e = function y() { + return typeof y; + }; + var f = function(f) { + return f; + }; + console.log(a(d()), b(e()), c(f(42)), typeof d, e(), typeof f); + } + }(); + } + expect: { + "use strict"; + !function() { + function a() { + return "a"; + } + var b = function x() { + return !!x; + }; + var c = function(c) { + return c; + }; + if (c(b(a()))) { + var d = function() {}; + var e = function y() { + return typeof y; + }; + var f = function(f) { + return f; + }; + console.log(a(d()), b(e()), c(f(42)), typeof d, e(), typeof f); + } + }(); + } + expect_stdout: "a true 42 function function function" +} + +issue_2437: { + options = { + collapse_vars: true, + conditionals: true, + functions: true, + inline: true, + join_vars: true, + passes: 2, + reduce_funcs: true, + reduce_vars: true, + sequences: true, + side_effects: true, + toplevel: true, + unused: true, + } + input: { + function foo() { + return bar(); + } + function bar() { + if (xhrDesc) { + var req = new XMLHttpRequest(); + var result = !!req.onreadystatechange; + Object.defineProperty(XMLHttpRequest.prototype, 'onreadystatechange', xhrDesc || {}); + return result; + } else { + var req = new XMLHttpRequest(); + var detectFunc = function(){}; + req.onreadystatechange = detectFunc; + var result = req[SYMBOL_FAKE_ONREADYSTATECHANGE_1] === detectFunc; + req.onreadystatechange = null; + return result; + } + } + console.log(foo()); + } + expect: { + console.log(function() { + if (xhrDesc) { + var result = !!(req = new XMLHttpRequest()).onreadystatechange; + return Object.defineProperty(XMLHttpRequest.prototype, "onreadystatechange", xhrDesc || {}), + result; + } + function detectFunc() {} + var req; + (req = new XMLHttpRequest()).onreadystatechange = detectFunc; + result = req[SYMBOL_FAKE_ONREADYSTATECHANGE_1] === detectFunc; + return req.onreadystatechange = null, result; + }()); + } +} + +issue_2485: { + options = { + functions: true, + reduce_funcs: true, + reduce_vars: true, + unused: true, + } + input: { + var foo = function(bar) { + var n = function(a, b) { + return a + b; + }; + var sumAll = function(arg) { + return arg.reduce(n, 0); + }; + var runSumAll = function(arg) { + return sumAll(arg); + }; + bar.baz = function(arg) { + var n = runSumAll(arg); + return (n.get = 1), n; + }; + return bar; + }; + var bar = foo({}); + console.log(bar.baz([1, 2, 3])); + } + expect: { + var foo = function(bar) { + function n(a, b) { + return a + b; + } + function runSumAll(arg) { + return function(arg) { + return arg.reduce(n, 0); + }(arg); + } + bar.baz = function(arg) { + var n = runSumAll(arg); + return (n.get = 1), n; + }; + return bar; + }; + var bar = foo({}); + console.log(bar.baz([1, 2, 3])); + } + expect_stdout: "6" +} diff --git a/test/jetstream.js b/test/jetstream.js index 9545784b..2d85dfdd 100644 --- a/test/jetstream.js +++ b/test/jetstream.js @@ -3,7 +3,7 @@ "use strict"; -var site = "https://browserbench.org/JetStream"; +var site = "https://browserbench.org/JetStream1.1"; if (typeof phantom == "undefined") { require("../tools/exit"); var args = process.argv.slice(2); |