Compare commits

..

5 Commits

Author SHA1 Message Date
Alex Lam S.L
f72d3029dd v3.3.25 2018-05-12 23:50:40 +00:00
Alex Lam S.L
1a0d6edc81 remove colors dependency (#3133) 2018-05-13 07:50:02 +08:00
Alex Lam S.L
7b59b2f5b2 replace mocha dependency (#3131) 2018-05-11 20:15:34 +08:00
Alex Lam S.L
7bc7704edf fix corner case in reduce_vars (#3129) 2018-05-10 18:45:20 +08:00
Alex Lam S.L
14e712ee80 fix corner case in call binding (#3128)
fixes #3127
2018-05-10 06:16:35 +08:00
7 changed files with 273 additions and 59 deletions

View File

@@ -361,7 +361,7 @@ merge(Compressor.prototype, {
if (def.scope === scope) return true; if (def.scope === scope) return true;
} while (scope instanceof AST_Function && (scope = scope.parent_scope)); } while (scope instanceof AST_Function && (scope = scope.parent_scope));
})) { })) {
tw.defun_ids[def.id] = undefined; tw.defun_ids[def.id] = false;
} }
def.recursive_refs = 0; def.recursive_refs = 0;
def.references = []; def.references = [];
@@ -386,23 +386,29 @@ merge(Compressor.prototype, {
if (def.id in tw.defun_ids) { if (def.id in tw.defun_ids) {
var marker = tw.defun_ids[def.id]; var marker = tw.defun_ids[def.id];
if (!marker) return; if (!marker) return;
if (marker !== tw.safe_ids) { var visited = tw.defun_visited[def.id];
tw.defun_ids[def.id] = undefined; if (marker === tw.safe_ids) {
return; if (!visited) return def.fixed;
} else if (visited) {
def.init.enclosed.forEach(function(d) {
if (def.init.variables.get(d.name) === d) return;
if (!safe_to_read(tw, d)) d.fixed = false;
});
} else {
tw.defun_ids[def.id] = false;
} }
return def.fixed; } else {
} if (!tw.in_loop) {
if (!tw.in_loop) { tw.defun_ids[def.id] = tw.safe_ids;
tw.defun_ids[def.id] = tw.safe_ids; return def.fixed;
return def.fixed; }
} else if (tw.defun_ids[def.id] !== false) { tw.defun_ids[def.id] = false;
tw.defun_ids[def.id] = undefined;
} }
} }
function walk_defuns(tw, scope) { function walk_defuns(tw, scope) {
scope.functions.each(function(def) { scope.functions.each(function(def) {
if (def.init instanceof AST_Defun && tw.defun_ids[def.id] === undefined) { if (def.init instanceof AST_Defun && !tw.defun_visited[def.id]) {
tw.defun_ids[def.id] = tw.safe_ids; tw.defun_ids[def.id] = tw.safe_ids;
def.init.walk(tw); def.init.walk(tw);
} }
@@ -587,8 +593,9 @@ merge(Compressor.prototype, {
}); });
def(AST_Defun, function(tw, descend, compressor) { def(AST_Defun, function(tw, descend, compressor) {
var id = this.name.definition().id; var id = this.name.definition().id;
if (tw.defun_visited[id]) return true;
if (tw.defun_ids[id] !== tw.safe_ids) return true; if (tw.defun_ids[id] !== tw.safe_ids) return true;
tw.defun_ids[id] = false; tw.defun_visited[id] = true;
this.inlined = false; this.inlined = false;
push(tw); push(tw);
reset_variables(tw, compressor, this); reset_variables(tw, compressor, this);
@@ -820,6 +827,7 @@ merge(Compressor.prototype, {
}); });
// Flow control for visiting `AST_Defun`s // Flow control for visiting `AST_Defun`s
tw.defun_ids = Object.create(null); tw.defun_ids = Object.create(null);
tw.defun_visited = Object.create(null);
// Record the loop body in which `AST_SymbolDeclaration` is first encountered // Record the loop body in which `AST_SymbolDeclaration` is first encountered
tw.in_loop = null; tw.in_loop = null;
tw.loop_ids = Object.create(null); tw.loop_ids = Object.create(null);
@@ -919,15 +927,21 @@ merge(Compressor.prototype, {
type: typeof val type: typeof val
})); }));
} }
}; }
function needs_unbinding(compressor, val) {
return val instanceof AST_PropAccess
|| compressor.has_directive("use strict")
&& is_undeclared_ref(val)
&& val.name == "eval";
}
// we shouldn't compress (1,func)(something) to // we shouldn't compress (1,func)(something) to
// func(something) because that changes the meaning of // func(something) because that changes the meaning of
// the func (becomes lexical instead of global). // the func (becomes lexical instead of global).
function maintain_this_binding(parent, orig, val) { function maintain_this_binding(compressor, parent, orig, val) {
if (parent instanceof AST_UnaryPrefix && parent.operator == "delete" if (parent instanceof AST_UnaryPrefix && parent.operator == "delete"
|| parent.TYPE == "Call" && parent.expression === orig || parent.TYPE == "Call" && parent.expression === orig && needs_unbinding(compressor, val)) {
&& (val instanceof AST_PropAccess || val instanceof AST_SymbolRef && val.name == "eval")) {
return make_sequence(orig, [ make_node(AST_Number, orig, { value: 0 }), val ]); return make_sequence(orig, [ make_node(AST_Number, orig, { value: 0 }), val ]);
} }
return val; return val;
@@ -1098,7 +1112,7 @@ merge(Compressor.prototype, {
var def = candidate.name.definition(); var def = candidate.name.definition();
if (def.references.length - def.replaced == 1 && !compressor.exposed(def)) { if (def.references.length - def.replaced == 1 && !compressor.exposed(def)) {
def.replaced++; def.replaced++;
return maintain_this_binding(parent, node, candidate.value); return maintain_this_binding(compressor, parent, node, candidate.value);
} }
return make_node(AST_Assign, candidate, { return make_node(AST_Assign, candidate, {
operator: "=", operator: "=",
@@ -3346,7 +3360,7 @@ merge(Compressor.prototype, {
} }
if (value) { if (value) {
props.push(value); props.push(value);
return maintain_this_binding(parent, node, make_sequence(node, props.map(function(prop) { return maintain_this_binding(compressor, parent, node, make_sequence(node, props.map(function(prop) {
return prop.transform(tt); return prop.transform(tt);
}))); })));
} }
@@ -4477,7 +4491,7 @@ merge(Compressor.prototype, {
var exp = this.expression; var exp = this.expression;
if (!(exp instanceof AST_Sequence)) return this; if (!(exp instanceof AST_Sequence)) return this;
var tail = exp.tail_node(); var tail = exp.tail_node();
if (tail instanceof AST_PropAccess && !(this instanceof AST_New)) return this; if (needs_unbinding(compressor, tail) && !(this instanceof AST_New)) return this;
var expressions = exp.expressions.slice(0, -1); var expressions = exp.expressions.slice(0, -1);
var node = this.clone(); var node = this.clone();
node.expression = tail; node.expression = tail;
@@ -5028,7 +5042,7 @@ merge(Compressor.prototype, {
var end = expressions.length - 1; var end = expressions.length - 1;
trim_right_for_undefined(); trim_right_for_undefined();
if (end == 0) { if (end == 0) {
self = maintain_this_binding(compressor.parent(), compressor.self(), expressions[0]); self = maintain_this_binding(compressor, compressor.parent(), compressor.self(), expressions[0]);
if (!(self instanceof AST_Sequence)) self = self.optimize(compressor); if (!(self instanceof AST_Sequence)) self = self.optimize(compressor);
return self; return self;
} }
@@ -5347,7 +5361,7 @@ merge(Compressor.prototype, {
var ll = fuzzy_eval(self.left); var ll = fuzzy_eval(self.left);
if (!ll) { if (!ll) {
compressor.warn("Condition left of && always false [{file}:{line},{col}]", self.start); compressor.warn("Condition left of && always false [{file}:{line},{col}]", self.start);
return maintain_this_binding(compressor.parent(), compressor.self(), self.left).optimize(compressor); return maintain_this_binding(compressor, compressor.parent(), compressor.self(), self.left).optimize(compressor);
} else if (!(ll instanceof AST_Node)) { } else if (!(ll instanceof AST_Node)) {
compressor.warn("Condition left of && always true [{file}:{line},{col}]", self.start); compressor.warn("Condition left of && always true [{file}:{line},{col}]", self.start);
return make_sequence(self, [ self.left, self.right ]).optimize(compressor); return make_sequence(self, [ self.left, self.right ]).optimize(compressor);
@@ -5386,7 +5400,7 @@ merge(Compressor.prototype, {
return make_sequence(self, [ self.left, self.right ]).optimize(compressor); return make_sequence(self, [ self.left, self.right ]).optimize(compressor);
} else if (!(ll instanceof AST_Node)) { } else if (!(ll instanceof AST_Node)) {
compressor.warn("Condition left of || always true [{file}:{line},{col}]", self.start); compressor.warn("Condition left of || always true [{file}:{line},{col}]", self.start);
return maintain_this_binding(compressor.parent(), compressor.self(), self.left).optimize(compressor); return maintain_this_binding(compressor, compressor.parent(), compressor.self(), self.left).optimize(compressor);
} }
var rr = self.right.evaluate(compressor); var rr = self.right.evaluate(compressor);
if (!rr) { if (!rr) {

View File

@@ -3,7 +3,7 @@
"description": "JavaScript parser, mangler/compressor and beautifier toolkit", "description": "JavaScript parser, mangler/compressor and beautifier toolkit",
"author": "Mihai Bazon <mihai.bazon@gmail.com> (http://lisperator.net/)", "author": "Mihai Bazon <mihai.bazon@gmail.com> (http://lisperator.net/)",
"license": "BSD-2-Clause", "license": "BSD-2-Clause",
"version": "3.3.24", "version": "3.3.25",
"engines": { "engines": {
"node": ">=0.8.0" "node": ">=0.8.0"
}, },
@@ -28,7 +28,6 @@
}, },
"devDependencies": { "devDependencies": {
"acorn": "~5.5.3", "acorn": "~5.5.3",
"mocha": "~3.5.1",
"semver": "~5.5.0" "semver": "~5.5.0"
}, },
"scripts": { "scripts": {

View File

@@ -1,11 +1,31 @@
remove_redundant_sequence_items: { remove_sequence: {
options = { side_effects: true }; options = {
side_effects: true,
}
input: { input: {
(0, 1, eval)(); (0, 1, eval)();
(0, 1, logThis)(); (0, 1, logThis)();
(0, 1, _decorators.logThis)(); (0, 1, _decorators.logThis)();
} }
expect: { expect: {
eval();
logThis();
(0, _decorators.logThis)();
}
}
remove_redundant_sequence_items: {
options = {
side_effects: true,
}
input: {
"use strict";
(0, 1, eval)();
(0, 1, logThis)();
(0, 1, _decorators.logThis)();
}
expect: {
"use strict";
(0, eval)(); (0, eval)();
logThis(); logThis();
(0, _decorators.logThis)(); (0, _decorators.logThis)();
@@ -13,13 +33,17 @@ remove_redundant_sequence_items: {
} }
dont_remove_this_binding_sequence: { dont_remove_this_binding_sequence: {
options = { side_effects: true }; options = {
side_effects: true,
}
input: { input: {
"use strict";
(0, eval)(); (0, eval)();
(0, logThis)(); (0, logThis)();
(0, _decorators.logThis)(); (0, _decorators.logThis)();
} }
expect: { expect: {
"use strict";
(0, eval)(); (0, eval)();
logThis(); logThis();
(0, _decorators.logThis)(); (0, _decorators.logThis)();

View File

@@ -3,8 +3,9 @@ this_binding_conditionals: {
conditionals: true, conditionals: true,
evaluate: true, evaluate: true,
side_effects: true, side_effects: true,
}; }
input: { input: {
"use strict";
(1 && a)(); (1 && a)();
(0 || a)(); (0 || a)();
(0 || 1 && a)(); (0 || 1 && a)();
@@ -26,6 +27,7 @@ this_binding_conditionals: {
(1 ? eval : 0)(); (1 ? eval : 0)();
} }
expect: { expect: {
"use strict";
a(); a();
a(); a();
a(); a();
@@ -53,13 +55,15 @@ this_binding_collapse_vars: {
collapse_vars: true, collapse_vars: true,
toplevel: true, toplevel: true,
unused: true, unused: true,
}; }
input: { input: {
"use strict";
var c = a; c(); var c = a; c();
var d = a.b; d(); var d = a.b; d();
var e = eval; e(); var e = eval; e();
} }
expect: { expect: {
"use strict";
a(); a();
(0, a.b)(); (0, a.b)();
(0, eval)(); (0, eval)();
@@ -69,31 +73,88 @@ this_binding_collapse_vars: {
this_binding_side_effects: { this_binding_side_effects: {
options = { options = {
side_effects : true side_effects : true
}; }
input: { input: {
(function (foo) { (function(foo) {
(0, foo)(); (0, foo)();
(0, foo.bar)(); (0, foo.bar)();
(0, eval)('console.log(foo);'); (0, eval)("console.log(foo);");
}()); }());
(function (foo) { (function(foo) {
"use strict";
(0, foo)();
(0, foo.bar)();
(0, eval)("console.log(foo);");
}());
(function(foo) {
var eval = console; var eval = console;
(0, foo)(); (0, foo)();
(0, foo.bar)(); (0, foo.bar)();
(0, eval)('console.log(foo);'); (0, eval)("console.log(foo);");
}()); }());
} }
expect: { expect: {
(function (foo) { (function(foo) {
foo(); foo();
(0, foo.bar)(); (0, foo.bar)();
(0, eval)('console.log(foo);'); eval("console.log(foo);");
}()); }());
(function (foo) { (function(foo) {
"use strict";
foo();
(0, foo.bar)();
(0, eval)("console.log(foo);");
}());
(function(foo) {
var eval = console; var eval = console;
foo(); foo();
(0, foo.bar)(); (0, foo.bar)();
(0, eval)('console.log(foo);'); eval("console.log(foo);");
}()); }());
} }
} }
this_binding_sequences: {
options = {
sequences: true,
side_effects: true,
}
input: {
console.log(typeof function() {
return eval("this");
}());
console.log(typeof function() {
"use strict";
return eval("this");
}());
console.log(typeof function() {
return (0, eval)("this");
}());
console.log(typeof function() {
"use strict";
return (0, eval)("this");
}());
}
expect: {
console.log(typeof function() {
return eval("this");
}()),
console.log(typeof function() {
"use strict";
return eval("this");
}()),
console.log(typeof function() {
return eval("this");
}()),
console.log(typeof function() {
"use strict";
return (0, eval)("this");
}());
}
expect_stdout: [
"object",
"undefined",
"object",
"object",
]
}

View File

@@ -6059,6 +6059,38 @@ conditional_nested_2: {
expect_stdout: "1" expect_stdout: "1"
} }
conditional_nested_3: {
options = {
evaluate: true,
reduce_vars: true,
}
input: {
var n = 2, c = 0;
(function f(a) {
0 < n-- && g(a = 1);
function g() {
a && c++;
}
g();
0 < n-- && f();
})();
console.log(c);
}
expect: {
var n = 2, c = 0;
(function f(a) {
0 < n-- && g(a = 1);
function g() {
a && c++;
}
g();
0 < n-- && f();
})();
console.log(c);
}
expect_stdout: "2"
}
issue_2436: { issue_2436: {
options = { options = {
evaluate: true, evaluate: true,

View File

@@ -1,24 +1,109 @@
var fs = require("fs"); var fs = require("fs");
var Mocha = require("mocha");
var path = require("path");
// Instantiate a Mocha instance var config = {
var mocha = new Mocha({ limit: 5000,
timeout: 5000 timeout: function(limit) {
}); this.limit = limit;
var testDir = __dirname + "/mocha/"; }
};
var tasks = [];
var titles = [];
describe = function(title, fn) {
config = Object.create(config);
titles.push(title);
fn.call(config);
titles.pop();
config = Object.getPrototypeOf(config);
};
it = function(title, fn) {
fn.limit = config.limit;
fn.titles = titles.slice();
fn.titles.push(title);
tasks.push(fn);
};
// Add each .js file to the Mocha instance fs.readdirSync("test/mocha").filter(function(file) {
fs.readdirSync(testDir).filter(function(file) {
return /\.js$/.test(file); return /\.js$/.test(file);
}).forEach(function(file) { }).forEach(function(file) {
mocha.addFile(path.join(testDir, file)); require("./mocha/" + file);
}); });
module.exports = function() { function log_titles(log, current, marker) {
mocha.run(function(failures) { var indent = "";
if (failures) process.on("exit", function() { var writing = false;
process.exit(failures); for (var i = 0; i < current.length; i++, indent += " ") {
if (titles[i] != current[i]) writing = true;
if (writing) log(indent + (i == current.length - 1 && marker || "") + current[i]);
}
titles = current;
}
function red(text) {
return "\u001B[31m" + text + "\u001B[39m";
}
function green(text) {
return "\u001B[32m" + text + "\u001B[39m";
}
var errors = [];
var total = tasks.length;
titles = [];
process.nextTick(function run() {
var task = tasks.shift();
if (task) try {
var elapsed = Date.now();
var timer;
var done = function() {
clearTimeout(timer);
done = function() {};
elapsed = Date.now() - elapsed;
if (elapsed > task.limit) {
throw new Error("Timed out: " + elapsed + "ms > " + task.limit + "ms");
}
log_titles(console.log, task.titles, green('\u221A '));
process.nextTick(run);
};
if (task.length) {
task.timeout = function(limit) {
clearTimeout(timer);
task.limit = limit;
timer = setTimeout(function() {
raise(new Error("Timed out: exceeds " + limit + "ms"));
}, limit);
};
task.timeout(task.limit);
task.call(task, done);
} else {
task.timeout = config.timeout;
task.call(task);
done();
}
} catch (err) {
raise(err);
} else if (errors.length) {
console.error();
console.log(red(errors.length + " test(s) failed!"));
titles = [];
errors.forEach(function(titles, index) {
console.error();
log_titles(console.error, titles, (index + 1) + ") ");
var lines = titles.error.stack.split('\n');
console.error(red(lines[0]));
console.error(lines.slice(1).join("\n"));
}); });
}); process.exit(1);
}; } else {
console.log();
console.log(green(total + " test(s) passed."));
}
function raise(err) {
clearTimeout(timer);
done = function() {};
task.titles.error = err;
errors.push(task.titles);
log_titles(console.log, task.titles, red('\u00D7 '));
process.nextTick(run);
}
});

View File

@@ -18,9 +18,8 @@ if (failures) {
console.error("!!! " + Object.keys(failed_files).join(", ")); console.error("!!! " + Object.keys(failed_files).join(", "));
process.exit(1); process.exit(1);
} }
console.log();
var mocha_tests = require("./mocha.js"); require("./mocha.js");
mocha_tests();
/* -----[ utils ]----- */ /* -----[ utils ]----- */