Compare commits

...

7 Commits

Author SHA1 Message Date
Alex Lam S.L
08514030f4 v3.4.8 2018-08-23 15:27:34 +08:00
Alex Lam S.L
694ca5d045 fix corner case in unused (#3234)
fixes #3233
2018-08-23 06:03:39 +08:00
Alex Lam S.L
57fb58b263 enhance if_return (#3232) 2018-08-21 18:34:16 +08:00
alexlamsl
18c1c9b38a update dependencies
- commander@2.17.1
2018-08-14 17:06:09 +08:00
Alex Lam S.L
5c1ae3662d v3.4.7 2018-08-09 14:47:24 +00:00
Alex Lam S.L
cfebeb2f63 fix corner case in mangle workaround for Safari (#3230)
fixes #3227
2018-08-09 17:34:28 +08:00
Alex Lam S.L
fc78423f1d clean up webkit quirks (#3229) 2018-08-08 16:15:45 +08:00
12 changed files with 420 additions and 168 deletions

View File

@@ -48,51 +48,51 @@ function Compressor(options, false_by_default) {
return new Compressor(options, false_by_default);
TreeTransformer.call(this, this.before, this.after);
this.options = defaults(options, {
arguments : !false_by_default,
booleans : !false_by_default,
collapse_vars : !false_by_default,
comparisons : !false_by_default,
conditionals : !false_by_default,
dead_code : !false_by_default,
directives : !false_by_default,
drop_console : false,
drop_debugger : !false_by_default,
evaluate : !false_by_default,
expression : false,
global_defs : false,
hoist_funs : false,
hoist_props : !false_by_default,
hoist_vars : false,
ie8 : false,
if_return : !false_by_default,
inline : !false_by_default,
join_vars : !false_by_default,
keep_fargs : true,
keep_fnames : false,
keep_infinity : false,
loops : !false_by_default,
negate_iife : !false_by_default,
passes : 1,
properties : !false_by_default,
pure_getters : !false_by_default && "strict",
pure_funcs : null,
reduce_funcs : !false_by_default,
reduce_vars : !false_by_default,
sequences : !false_by_default,
side_effects : !false_by_default,
switches : !false_by_default,
top_retain : null,
toplevel : !!(options && options["top_retain"]),
typeofs : !false_by_default,
unsafe : false,
unsafe_comps : false,
unsafe_Function: false,
unsafe_math : false,
unsafe_proto : false,
unsafe_regexp : false,
arguments : !false_by_default,
booleans : !false_by_default,
collapse_vars : !false_by_default,
comparisons : !false_by_default,
conditionals : !false_by_default,
dead_code : !false_by_default,
directives : !false_by_default,
drop_console : false,
drop_debugger : !false_by_default,
evaluate : !false_by_default,
expression : false,
global_defs : false,
hoist_funs : false,
hoist_props : !false_by_default,
hoist_vars : false,
ie8 : false,
if_return : !false_by_default,
inline : !false_by_default,
join_vars : !false_by_default,
keep_fargs : true,
keep_fnames : false,
keep_infinity : false,
loops : !false_by_default,
negate_iife : !false_by_default,
passes : 1,
properties : !false_by_default,
pure_getters : !false_by_default && "strict",
pure_funcs : null,
reduce_funcs : !false_by_default,
reduce_vars : !false_by_default,
sequences : !false_by_default,
side_effects : !false_by_default,
switches : !false_by_default,
top_retain : null,
toplevel : !!(options && options["top_retain"]),
typeofs : !false_by_default,
unsafe : false,
unsafe_comps : false,
unsafe_Function : false,
unsafe_math : false,
unsafe_proto : false,
unsafe_regexp : false,
unsafe_undefined: false,
unused : !false_by_default,
warnings : false,
unused : !false_by_default,
warnings : false,
}, true);
var global_defs = this.options["global_defs"];
if (typeof global_defs == "object") for (var key in global_defs) {
@@ -450,8 +450,7 @@ merge(Compressor.prototype, {
return value instanceof AST_Node && def.fixed.parent_scope === scope;
}
return all(def.orig, function(sym) {
return !(sym instanceof AST_SymbolDefun
|| sym instanceof AST_SymbolLambda);
return !(sym instanceof AST_SymbolDefun || sym instanceof AST_SymbolLambda);
});
}
@@ -1699,6 +1698,19 @@ merge(Compressor.prototype, {
continue;
}
if (ab && !stat.alternative && stat.body instanceof AST_BlockStatement && next instanceof AST_Jump) {
var negated = stat.condition.negate(compressor);
if (negated.print_to_string().length <= stat.condition.print_to_string().length) {
CHANGED = true;
stat = stat.clone();
stat.condition = negated;
statements[j] = stat.body;
stat.body = next;
statements[i] = stat.transform(compressor);
continue;
}
}
var ab = aborts(stat.alternative);
if (can_merge_flow(ab)) {
if (ab.label) {
@@ -3316,13 +3328,14 @@ merge(Compressor.prototype, {
} else if (node instanceof AST_Unary && node.write_only) {
sym = node.expression;
}
if (/strict/.test(compressor.option("pure_getters"))) {
while (sym instanceof AST_PropAccess && !sym.expression.may_throw_on_access(compressor)) {
if (sym instanceof AST_Sub) props.unshift(sym.property);
sym = sym.expression;
}
if (!/strict/.test(compressor.option("pure_getters"))) return sym instanceof AST_SymbolRef && sym;
while (sym instanceof AST_PropAccess && !sym.expression.may_throw_on_access(compressor)) {
if (sym instanceof AST_Sub) props.unshift(sym.property);
sym = sym.expression;
}
return sym;
return sym instanceof AST_SymbolRef && all(sym.definition().orig, function(sym) {
return !(sym instanceof AST_SymbolLambda);
}) && sym;
};
var in_use = [];
var in_use_ids = Object.create(null); // avoid expensive linear scans of in_use
@@ -3417,7 +3430,7 @@ merge(Compressor.prototype, {
var parent = tt.parent();
if (drop_vars) {
var props = [], sym = assign_as_unused(node, props);
if (sym instanceof AST_SymbolRef) {
if (sym) {
var def = sym.definition();
var in_use = def.id in in_use_ids;
var value = null;
@@ -3616,8 +3629,7 @@ merge(Compressor.prototype, {
function scan_ref_scoped(node, descend) {
var node_def, props = [], sym = assign_as_unused(node, props);
if (sym instanceof AST_SymbolRef
&& self.variables.get(sym.name) === (node_def = sym.definition())) {
if (sym && self.variables.get(sym.name) === (node_def = sym.definition())) {
props.forEach(function(prop) {
prop.walk(tw);
});
@@ -5758,9 +5770,10 @@ merge(Compressor.prototype, {
if (fixed instanceof AST_Defun) {
fixed._squeezed = true;
fixed = make_node(AST_Function, fixed, fixed);
fixed.name = make_node(AST_SymbolLambda, fixed.name, fixed.name);
}
var value;
if (d.recursive_refs > 0 && fixed.name instanceof AST_SymbolDefun) {
if (d.recursive_refs > 0) {
value = fixed.clone(true);
var defun_def = value.name.definition();
var lambda_def = value.variables.get(value.name.name);

View File

@@ -694,23 +694,15 @@ function OutputStream(options) {
// a function expression needs parens around it when it's provably
// the first token to appear in a statement.
PARENS(AST_Function, function(output) {
if (!output.has_parens() && first_in_statement(output)) {
return true;
}
if (!output.has_parens() && first_in_statement(output)) return true;
if (output.option('webkit')) {
var p = output.parent();
if (p instanceof AST_PropAccess && p.expression === this) {
return true;
}
if (p instanceof AST_PropAccess && p.expression === this) return true;
}
if (output.option('wrap_iife')) {
var p = output.parent();
return p instanceof AST_Call && p.expression === this;
if (p instanceof AST_Call && p.expression === this) return true;
}
return false;
});
// same goes for an object literal, because otherwise it would be
@@ -784,17 +776,17 @@ function OutputStream(options) {
});
PARENS(AST_Call, function(output) {
var p = output.parent(), p1;
if (p instanceof AST_New && p.expression === this)
return true;
// workaround for Safari bug.
var p = output.parent();
if (p instanceof AST_New && p.expression === this) return true;
// https://bugs.webkit.org/show_bug.cgi?id=123506
return this.expression instanceof AST_Function
&& p instanceof AST_PropAccess
&& p.expression === this
&& (p1 = output.parent(1)) instanceof AST_Assign
&& p1.left === p;
if (output.option('webkit')) {
var g = output.parent(1);
return this.expression instanceof AST_Function
&& p instanceof AST_PropAccess
&& p.expression === this
&& g instanceof AST_Assign
&& g.left === p;
}
});
PARENS(AST_New, function(output) {

View File

@@ -320,14 +320,6 @@ function next_mangled_name(scope, options, def) {
var in_use = names_in_use(scope, options);
var holes = scope.cname_holes;
var names = Object.create(null);
// #179, #326
// in Safari strict mode, something like (function x(x){...}) is a syntax error;
// a function expression's argument cannot shadow the function expression's name
if (scope instanceof AST_Function && scope.name && def.orig[0] instanceof AST_SymbolFunarg) {
var tricky_def = scope.name.definition();
// the function's mangled_name is null when keep_fnames is true
names[tricky_def.mangled_name || tricky_def.name] = true;
}
var scopes = [ scope ];
def.references.forEach(function(sym) {
var scope = sym.scope;

View File

@@ -3,7 +3,7 @@
"description": "JavaScript parser, mangler/compressor and beautifier toolkit",
"author": "Mihai Bazon <mihai.bazon@gmail.com> (http://lisperator.net/)",
"license": "BSD-2-Clause",
"version": "3.4.6",
"version": "3.4.8",
"engines": {
"node": ">=0.8.0"
},
@@ -23,7 +23,7 @@
"LICENSE"
],
"dependencies": {
"commander": "~2.16.0",
"commander": "~2.17.1",
"source-map": "~0.6.1"
},
"devDependencies": {

View File

@@ -1982,3 +1982,26 @@ issue_3192: {
"foo bar",
]
}
issue_3233: {
options = {
pure_getters: "strict",
side_effects: true,
unused: true,
}
input: {
var a = function b() {
b.c = "PASS";
};
a();
console.log(a.c);
}
expect: {
var a = function b() {
b.c = "PASS";
};
a();
console.log(a.c);
}
expect_stdout: "PASS"
}

View File

@@ -236,32 +236,6 @@ issue_203: {
expect_stdout: "42"
}
no_webkit: {
beautify = {
webkit: false,
}
input: {
console.log(function() {
1 + 1;
}.a = 1);
}
expect_exact: "console.log(function(){1+1}.a=1);"
expect_stdout: "1"
}
webkit: {
beautify = {
webkit: true,
}
input: {
console.log(function() {
1 + 1;
}.a = 1);
}
expect_exact: "console.log((function(){1+1}).a=1);"
expect_stdout: "1"
}
issue_2084: {
options = {
collapse_vars: true,

View File

@@ -396,3 +396,151 @@ if_if_return_return: {
}
}
}
if_body_return_1: {
options = {
if_return: true,
}
input: {
var c = "PASS";
function f(a, b) {
if (a) {
if (b) throw new Error(c);
return 42;
}
return true;
}
console.log(f(0, 0));
console.log(f(0, 1));
console.log(f(1, 0));
try {
f(1, 1);
console.log("FAIL");
} catch (e) {
console.log(e.message);
}
}
expect: {
var c = "PASS";
function f(a, b) {
if (a) {
if (b) throw new Error(c);
return 42;
}
return true;
}
console.log(f(0, 0));
console.log(f(0, 1));
console.log(f(1, 0));
try {
f(1, 1);
console.log("FAIL");
} catch (e) {
console.log(e.message);
}
}
expect_stdout: [
"true",
"true",
"42",
"PASS",
]
}
if_body_return_2: {
options = {
if_return: true,
}
input: {
var c = "PASS";
function f(a, b) {
if (0 + a) {
if (b) throw new Error(c);
return 42;
}
return true;
}
console.log(f(0, 0));
console.log(f(0, 1));
console.log(f(1, 0));
try {
f(1, 1);
console.log("FAIL");
} catch (e) {
console.log(e.message);
}
}
expect: {
var c = "PASS";
function f(a, b) {
if (0 + a) {
if (b) throw new Error(c);
return 42;
}
return true;
}
console.log(f(0, 0));
console.log(f(0, 1));
console.log(f(1, 0));
try {
f(1, 1);
console.log("FAIL");
} catch (e) {
console.log(e.message);
}
}
expect_stdout: [
"true",
"true",
"42",
"PASS",
]
}
if_body_return_3: {
options = {
if_return: true,
}
input: {
var c = "PASS";
function f(a, b) {
if (1 == a) {
if (b) throw new Error(c);
return 42;
}
return true;
}
console.log(f(0, 0));
console.log(f(0, 1));
console.log(f(1, 0));
try {
f(1, 1);
console.log("FAIL");
} catch (e) {
console.log(e.message);
}
}
expect: {
var c = "PASS";
function f(a, b) {
if (1 != a) return true;
if (b) throw new Error(c);
return 42;
}
console.log(f(0, 0));
console.log(f(0, 1));
console.log(f(1, 0));
try {
f(1, 1);
console.log("FAIL");
} catch (e) {
console.log(e.message);
}
}
expect_stdout: [
"true",
"true",
"42",
"PASS",
]
}

109
test/compress/webkit.js Normal file
View File

@@ -0,0 +1,109 @@
lambda_call_dot_assign: {
beautify = {
webkit: false,
}
input: {
console.log(function() {
return {};
}().a = 1);
}
expect_exact: "console.log(function(){return{}}().a=1);"
expect_stdout: "1"
}
lambda_call_dot_assign_webkit: {
beautify = {
webkit: true,
}
input: {
console.log(function() {
return {};
}().a = 1);
}
expect_exact: "console.log((function(){return{}}()).a=1);"
expect_stdout: "1"
}
lambda_dot_assign: {
beautify = {
webkit: false,
}
input: {
console.log(function() {
1 + 1;
}.a = 1);
}
expect_exact: "console.log(function(){1+1}.a=1);"
expect_stdout: "1"
}
lambda_dot_assign_webkit: {
beautify = {
webkit: true,
}
input: {
console.log(function() {
1 + 1;
}.a = 1);
}
expect_exact: "console.log((function(){1+1}).a=1);"
expect_stdout: "1"
}
lambda_name_mangle: {
mangle = {}
input: {
console.log(typeof function foo(bar) {});
}
expect_exact: "console.log(typeof function o(n){});"
expect_stdout: "function"
}
lambda_name_mangle_ie8: {
mangle = {
ie8: true,
toplevel: true,
}
input: {
console.log(typeof function foo(bar) {});
}
expect_exact: "console.log(typeof function n(o){});"
expect_stdout: "function"
}
function_name_mangle: {
options = {
keep_fnames: true,
reduce_vars: true,
unused: true,
}
mangle = {}
input: {
(function() {
function foo(bar) {}
console.log(typeof foo);
})();
}
expect_exact: "(function(){console.log(typeof function o(n){})})();"
expect_stdout: "function"
}
function_name_mangle_ie8: {
options = {
keep_fnames: true,
reduce_vars: true,
unused: true,
}
mangle = {
ie8: true,
toplevel: true,
}
input: {
(function() {
function foo(bar) {}
console.log(typeof foo);
})();
}
expect_exact: "(function(){console.log(typeof function n(o){})})();"
expect_stdout: "function"
}

View File

@@ -23,10 +23,9 @@ module.exports = function(url, callback) {
var options = parse(url);
options.rejectUnauthorized = false;
require(options.protocol.slice(0, -1)).get(options, function(res) {
if (res.statusCode !== 200) return callback(res);
res.pipe(fs.createWriteStream(local(url)).on("close", function() {
callback(null, read(url));
}));
if (res.statusCode !== 200) return callback(res.statusCode);
res.pipe(fs.createWriteStream(local(url)));
callback(null, res);
});
}).on("open", function() {
callback(null, result);

View File

@@ -25,30 +25,35 @@ if (typeof phantom == "undefined") {
request.resume();
var url = site + request.url;
fetch(url, function(err, res) {
if (err) throw err;
response.writeHead(200, {
"Content-Type": {
css: "text/css",
js: "application/javascript",
png: "image/png"
}[url.slice(url.lastIndexOf(".") + 1)] || "text/html; charset=utf-8"
});
if (/\.js$/.test(url)) {
var stderr = "";
var uglifyjs = child_process.fork("bin/uglifyjs", args, {
silent: true
}).on("exit", function(code) {
console.log("uglifyjs", url.slice(site.length + 1), args.join(" "));
console.log(stderr);
if (code) throw new Error("uglifyjs failed with code " + code);
});
uglifyjs.stderr.on("data", function(data) {
stderr += data;
}).setEncoding("utf8");
uglifyjs.stdout.pipe(response);
res.pipe(uglifyjs.stdin);
if (err) {
if (typeof err != "number") throw err;
response.writeHead(err);
response.end();
} else {
res.pipe(response);
response.writeHead(200, {
"Content-Type": {
css: "text/css",
js: "application/javascript",
png: "image/png"
}[url.slice(url.lastIndexOf(".") + 1)] || "text/html; charset=utf-8"
});
if (/\.js$/.test(url)) {
var stderr = "";
var uglifyjs = child_process.fork("bin/uglifyjs", args, {
silent: true
}).on("exit", function(code) {
console.log("uglifyjs", url.slice(site.length + 1), args.join(" "));
console.log(stderr);
if (code) throw new Error("uglifyjs failed with code " + code);
});
uglifyjs.stderr.on("data", function(data) {
stderr += data;
}).setEncoding("utf8");
uglifyjs.stdout.pipe(response);
res.pipe(uglifyjs.stdin);
} else {
res.pipe(response);
}
}
});
}).listen();

View File

@@ -651,7 +651,7 @@ describe("bin/uglifyjs", function() {
});
});
it("Should work with explicit --no-rename", function(done) {
var command = uglifyjscmd + " test/input/rename/input.js -mc --no-rename";
var command = uglifyjscmd + " test/input/rename/input.js -mc passes=2 --no-rename";
exec(command, function(err, stdout, stderr) {
if (err) throw err;
assert.strictEqual(stdout, "function f(n){return function(n){return n}(n)}\n");
@@ -659,7 +659,7 @@ describe("bin/uglifyjs", function() {
});
});
it("Should work with implicit --rename", function(done) {
var command = uglifyjscmd + " test/input/rename/input.js -mc";
var command = uglifyjscmd + " test/input/rename/input.js -mc passes=2";
exec(command, function(err, stdout, stderr) {
if (err) throw err;
assert.strictEqual(stdout, "function f(n){return n}\n");
@@ -667,7 +667,7 @@ describe("bin/uglifyjs", function() {
});
});
it("Should work with implicit --no-rename", function(done) {
var command = uglifyjscmd + " test/input/rename/input.js -c";
var command = uglifyjscmd + " test/input/rename/input.js -c passes=2";
exec(command, function(err, stdout, stderr) {
if (err) throw err;
assert.strictEqual(stdout, "function f(x){return function(x){return x}(x)}\n");

View File

@@ -1,21 +1,6 @@
var semver = require("semver");
var vm = require("vm");
function createContext() {
return vm.createContext(Object.defineProperty({}, "console", {
value: {
log: function(msg) {
if (arguments.length == 1 && typeof msg == "string") {
return console.log("%s", msg);
}
return console.log.apply(console, [].map.call(arguments, function(arg) {
return safe_log(arg, 3);
}));
}
}
}));
}
function safe_log(arg, level) {
if (arg) switch (typeof arg) {
case "function":
@@ -25,21 +10,21 @@ function safe_log(arg, level) {
arg.constructor.toString();
if (level--) for (var key in arg) {
var desc = Object.getOwnPropertyDescriptor(arg, key);
if (!desc || !desc.get) {
arg[key] = safe_log(arg[key], level);
}
if (!desc || !desc.get) arg[key] = safe_log(arg[key], level);
}
}
return arg;
}
function strip_func_ids(text) {
return ("" + text).replace(/F[0-9]{6}N/g, "<F<>N>");
function log(msg) {
if (arguments.length == 1 && typeof msg == "string") return console.log("%s", msg);
return console.log.apply(console, [].map.call(arguments, function(arg) {
return safe_log(arg, 3);
}));
}
var context;
var FUNC_TOSTRING = [
"[ Array, Boolean, Error, Function, Number, Object, RegExp, String].forEach(function(f) {",
var func_toString = new vm.Script([
"[ Array, Boolean, Error, Function, Number, Object, RegExp, String ].forEach(function(f) {",
" f.toString = Function.prototype.toString;",
"});",
"Function.prototype.toString = function() {",
@@ -59,7 +44,15 @@ var FUNC_TOSTRING = [
' return "function(){}";',
" };",
"}();",
]).join("\n");
]).join("\n"));
function createContext() {
var ctx = vm.createContext(Object.defineProperty({}, "console", { value: { log: log } }));
func_toString.runInContext(ctx);
return ctx;
}
var context;
exports.run_code = function(code, reuse) {
var stdout = "";
var original_write = process.stdout.write;
@@ -69,7 +62,6 @@ exports.run_code = function(code, reuse) {
try {
if (!reuse || !context) context = createContext();
vm.runInContext([
FUNC_TOSTRING,
"!function() {",
code,
"}();",
@@ -86,6 +78,11 @@ exports.run_code = function(code, reuse) {
}
}
};
function strip_func_ids(text) {
return ("" + text).replace(/F[0-9]{6}N/g, "<F<>N>");
}
exports.same_stdout = semver.satisfies(process.version, "0.12") ? function(expected, actual) {
if (typeof expected != typeof actual) return false;
if (typeof expected != "string") {