aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlex Lam S.L <alexlamsl@gmail.com>2019-04-15 22:23:11 +0800
committerGitHub <noreply@github.com>2019-04-15 22:23:11 +0800
commit5172ba5f2ade22716e65ff2333218fc08996bfcc (patch)
treef5d5084980b543c64b7fedd1c233eb8b4414ddb2
parenta57b069409d84e3768dfd8e786532e458f68e553 (diff)
downloadtracifyjs-5172ba5f2ade22716e65ff2333218fc08996bfcc.tar.gz
tracifyjs-5172ba5f2ade22716e65ff2333218fc08996bfcc.zip
introduce `functions` (#3360)
`var f = function() {};` => `function f() {}`
-rw-r--r--README.md3
-rw-r--r--lib/compress.js84
-rw-r--r--lib/output.js2
-rw-r--r--lib/parse.js6
-rw-r--r--lib/scope.js4
-rw-r--r--test/compress/functions.js213
-rw-r--r--test/jetstream.js2
7 files changed, 268 insertions, 46 deletions
diff --git a/README.md b/README.md
index 1be2d2e8..bf8fbd23 100644
--- a/README.md
+++ b/README.md
@@ -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);