Compare commits

..

5 Commits

Author SHA1 Message Date
Alex Lam S.L
5843494ee2 v3.3.28 2018-05-29 12:25:42 +00:00
Alex Lam S.L
efa21ae3e6 fix corner case in reduce_vars (#3151) 2018-05-26 05:45:44 +08:00
Alex Lam S.L
24d9633a35 fix corner cases with eval() (#3147)
fixes #3146
2018-05-24 14:29:30 +08:00
Alex Lam S.L
7963b96681 augment tests for inline source maps (#3145) 2018-05-24 02:37:51 +08:00
Alex Lam S.L
8c62d854ce augment tests for RegExp (#3144) 2018-05-23 17:24:13 +08:00
9 changed files with 268 additions and 42 deletions

View File

@@ -314,6 +314,9 @@ var AST_Scope = DEFNODE("Scope", "variables functions uses_with uses_eval parent
if (this.functions) node.functions = this.functions.clone(); if (this.functions) node.functions = this.functions.clone();
if (this.enclosed) node.enclosed = this.enclosed.slice(); if (this.enclosed) node.enclosed = this.enclosed.slice();
return node; return node;
},
pinned: function() {
return this.uses_eval || this.uses_with;
} }
}, AST_Block); }, AST_Block);

View File

@@ -348,14 +348,14 @@ merge(Compressor.prototype, {
def.chained = false; def.chained = false;
def.direct_access = false; def.direct_access = false;
def.escaped = false; def.escaped = false;
if (def.scope.uses_eval || def.scope.uses_with) { if (def.scope.pinned()) {
def.fixed = false; def.fixed = false;
} else if (!compressor.exposed(def)) { } else if (!compressor.exposed(def)) {
def.fixed = def.init; def.fixed = def.init;
} else { } else {
def.fixed = false; def.fixed = false;
} }
if (def.init instanceof AST_Defun && !all(def.references, function(ref) { if (def.fixed instanceof AST_Defun && !all(def.references, function(ref) {
var scope = ref.scope; var scope = ref.scope;
do { do {
if (def.scope === scope) return true; if (def.scope === scope) return true;
@@ -388,7 +388,6 @@ merge(Compressor.prototype, {
tw.defun_ids[def.id] = false; tw.defun_ids[def.id] = false;
} }
}); });
return true;
}; };
} }
@@ -471,8 +470,7 @@ merge(Compressor.prototype, {
function ref_once(tw, compressor, def) { function ref_once(tw, compressor, def) {
return compressor.option("unused") return compressor.option("unused")
&& !def.scope.uses_eval && !def.scope.pinned()
&& !def.scope.uses_with
&& def.references.length - def.recursive_refs == 1 && def.references.length - def.recursive_refs == 1
&& tw.loop_ids[def.id] === tw.in_loop; && tw.loop_ids[def.id] === tw.in_loop;
} }
@@ -566,7 +564,7 @@ merge(Compressor.prototype, {
return true; return true;
}); });
def(AST_Call, function(tw, descend) { def(AST_Call, function(tw, descend) {
if (tw.find_parent(AST_Scope).may_call_this()) return; tw.find_parent(AST_Scope).may_call_this();
var exp = this.expression; var exp = this.expression;
if (!(exp instanceof AST_SymbolRef)) return; if (!(exp instanceof AST_SymbolRef)) return;
var def = exp.definition(); var def = exp.definition();
@@ -725,7 +723,7 @@ merge(Compressor.prototype, {
if (value instanceof AST_Lambda && recursive_ref(tw, d)) { if (value instanceof AST_Lambda && recursive_ref(tw, d)) {
d.recursive_refs++; d.recursive_refs++;
} else if (value && ref_once(tw, compressor, d)) { } else if (value && ref_once(tw, compressor, d)) {
d.single_use = value instanceof AST_Lambda d.single_use = value instanceof AST_Lambda && !value.pinned()
|| d.scope === this.scope && value.is_constant_expression(); || d.scope === this.scope && value.is_constant_expression();
} else { } else {
d.single_use = false; d.single_use = false;
@@ -1066,7 +1064,7 @@ merge(Compressor.prototype, {
// Will not attempt to collapse assignments into or past code blocks // Will not attempt to collapse assignments into or past code blocks
// which are not sequentially executed, e.g. loops and conditionals. // which are not sequentially executed, e.g. loops and conditionals.
function collapse(statements, compressor) { function collapse(statements, compressor) {
if (scope.uses_eval || scope.uses_with) return statements; if (scope.pinned()) return statements;
var args; var args;
var candidates = []; var candidates = [];
var stat_index = statements.length; var stat_index = statements.length;
@@ -1302,7 +1300,7 @@ merge(Compressor.prototype, {
if (fn instanceof AST_Function if (fn instanceof AST_Function
&& !fn.name && !fn.name
&& !fn.uses_arguments && !fn.uses_arguments
&& !fn.uses_eval && !fn.pinned()
&& (iife = compressor.parent()) instanceof AST_Call && (iife = compressor.parent()) instanceof AST_Call
&& iife.expression === fn) { && iife.expression === fn) {
var fn_strict = compressor.has_directive("use strict"); var fn_strict = compressor.has_directive("use strict");
@@ -1319,9 +1317,12 @@ merge(Compressor.prototype, {
})); }));
if (sym.name in names) continue; if (sym.name in names) continue;
names[sym.name] = true; names[sym.name] = true;
if (!arg) arg = make_node(AST_Undefined, sym).transform(compressor); if (!arg) {
else { arg = make_node(AST_Undefined, sym).transform(compressor);
var tw = new TreeWalker(function(node) { } else if (arg instanceof AST_Lambda && arg.pinned()) {
arg = null;
} else {
arg.walk(new TreeWalker(function(node) {
if (!arg) return true; if (!arg) return true;
if (node instanceof AST_SymbolRef && fn.variables.has(node.name)) { if (node instanceof AST_SymbolRef && fn.variables.has(node.name)) {
var s = node.definition().scope; var s = node.definition().scope;
@@ -1330,12 +1331,11 @@ merge(Compressor.prototype, {
} }
arg = null; arg = null;
} }
if (node instanceof AST_This && (fn_strict || !tw.find_parent(AST_Scope))) { if (node instanceof AST_This && (fn_strict || !this.find_parent(AST_Scope))) {
arg = null; arg = null;
return true; return true;
} }
}); }));
arg.walk(tw);
} }
if (arg) candidates.unshift([ make_node(AST_VarDef, sym, { if (arg) candidates.unshift([ make_node(AST_VarDef, sym, {
name: sym, name: sym,
@@ -3263,7 +3263,7 @@ merge(Compressor.prototype, {
if (!compressor.option("unused")) return; if (!compressor.option("unused")) return;
if (compressor.has_directive("use asm")) return; if (compressor.has_directive("use asm")) return;
var self = this; var self = this;
if (self.uses_eval || self.uses_with) return; if (self.pinned()) return;
var drop_funcs = !(self instanceof AST_Toplevel) || compressor.toplevel.funcs; var drop_funcs = !(self instanceof AST_Toplevel) || compressor.toplevel.funcs;
var drop_vars = !(self instanceof AST_Toplevel) || compressor.toplevel.vars; var drop_vars = !(self instanceof AST_Toplevel) || compressor.toplevel.vars;
var assign_as_unused = /keep_assign/.test(compressor.option("unused")) ? return_false : function(node, props) { var assign_as_unused = /keep_assign/.test(compressor.option("unused")) ? return_false : function(node, props) {
@@ -4526,7 +4526,7 @@ merge(Compressor.prototype, {
if (compressor.option("unused") if (compressor.option("unused")
&& is_func && is_func
&& !fn.uses_arguments && !fn.uses_arguments
&& !fn.uses_eval) { && !fn.pinned()) {
var pos = 0, last = 0; var pos = 0, last = 0;
for (var i = 0, len = self.args.length; i < len; i++) { for (var i = 0, len = self.args.length; i < len; i++) {
var trim = i >= fn.argnames.length; var trim = i >= fn.argnames.length;
@@ -4806,7 +4806,7 @@ merge(Compressor.prototype, {
var def, value, scope, in_loop, level = -1; var def, value, scope, in_loop, level = -1;
if (can_inline if (can_inline
&& !fn.uses_arguments && !fn.uses_arguments
&& !fn.uses_eval && !fn.pinned()
&& !(fn.name && fn instanceof AST_Function) && !(fn.name && fn instanceof AST_Function)
&& (value = can_flatten_body(stat)) && (value = can_flatten_body(stat))
&& (exp === fn && (exp === fn

View File

@@ -63,12 +63,12 @@ SymbolDef.prototype = {
unmangleable: function(options) { unmangleable: function(options) {
if (!options) options = {}; if (!options) options = {};
return (this.global && !options.toplevel) return this.global && !options.toplevel
|| this.undeclared || this.undeclared
|| (!options.eval && (this.scope.uses_eval || this.scope.uses_with)) || !options.eval && this.scope.pinned()
|| (options.keep_fnames || options.keep_fnames
&& (this.orig[0] instanceof AST_SymbolLambda && (this.orig[0] instanceof AST_SymbolLambda
|| this.orig[0] instanceof AST_SymbolDefun)); || this.orig[0] instanceof AST_SymbolDefun);
}, },
mangle: function(options) { mangle: function(options) {
var cache = options.cache && options.cache.props; var cache = options.cache && options.cache.props;
@@ -355,7 +355,7 @@ function next_mangled_name(scope, options, def) {
return name; return name;
} }
AST_Symbol.DEFMETHOD("unmangleable", function(options){ AST_Symbol.DEFMETHOD("unmangleable", function(options) {
var def = this.definition(); var def = this.definition();
return !def || def.unmangleable(options); return !def || def.unmangleable(options);
}); });
@@ -363,16 +363,15 @@ AST_Symbol.DEFMETHOD("unmangleable", function(options){
// labels are always mangleable // labels are always mangleable
AST_Label.DEFMETHOD("unmangleable", return_false); AST_Label.DEFMETHOD("unmangleable", return_false);
AST_Symbol.DEFMETHOD("unreferenced", function(){ AST_Symbol.DEFMETHOD("unreferenced", function() {
return this.definition().references.length == 0 return !this.definition().references.length && !this.scope.pinned();
&& !(this.scope.uses_eval || this.scope.uses_with);
}); });
AST_Symbol.DEFMETHOD("definition", function(){ AST_Symbol.DEFMETHOD("definition", function() {
return this.thedef; return this.thedef;
}); });
AST_Symbol.DEFMETHOD("global", function(){ AST_Symbol.DEFMETHOD("global", function() {
return this.definition().global; return this.definition().global;
}); });

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.27", "version": "3.3.28",
"engines": { "engines": {
"node": ">=0.8.0" "node": ">=0.8.0"
}, },

View File

@@ -1814,3 +1814,115 @@ issue_2995: {
} }
expect_stdout: "PASS" expect_stdout: "PASS"
} }
issue_3146_1: {
options = {
collapse_vars: true,
unused: true,
}
input: {
(function(f) {
f("g()");
})(function(a) {
eval(a);
function g(b) {
if (!b) b = "PASS";
console.log(b);
}
});
}
expect: {
(function(f) {
f("g()");
})(function(a) {
eval(a);
function g(b) {
if (!b) b = "PASS";
console.log(b);
}
});
}
expect_stdout: "PASS"
}
issue_3146_2: {
options = {
reduce_vars: true,
unused: true,
}
input: {
(function(f) {
f("g()");
})(function(a) {
eval(a);
function g(b) {
if (!b) b = "PASS";
console.log(b);
}
});
}
expect: {
(function(f) {
f("g()");
})(function(a) {
eval(a);
function g(b) {
if (!b) b = "PASS";
console.log(b);
}
});
}
expect_stdout: "PASS"
}
issue_3146_3: {
options = {
collapse_vars: true,
unused: true,
}
input: {
var g = "PASS";
(function(f) {
var g = "FAIL";
f("console.log(g)", g[g]);
})(function(a) {
eval(a);
});
}
expect: {
var g = "PASS";
(function(f) {
var g = "FAIL";
f("console.log(g)", g[g]);
})(function(a) {
eval(a);
});
}
expect_stdout: "PASS"
}
issue_3146_4: {
options = {
reduce_vars: true,
unused: true,
}
input: {
var g = "PASS";
(function(f) {
var g = "FAIL";
f("console.log(g)", g[g]);
})(function(a) {
eval(a);
});
}
expect: {
var g = "PASS";
(function(f) {
var g = "FAIL";
f("console.log(g)", g[g]);
})(function(a) {
eval(a);
});
}
expect_stdout: "PASS"
}

View File

@@ -6367,3 +6367,39 @@ issue_3140_4: {
} }
expect_stdout: "PASS" expect_stdout: "PASS"
} }
issue_3140_5: {
options = {
evaluate: true,
reduce_vars: true,
}
input: {
var n = 1, c = 0;
(function(a) {
var b = function() {
this;
n-- && h();
}();
function h() {
b && c++;
}
h(b = 1);
})();
console.log(c);
}
expect: {
var n = 1, c = 0;
(function(a) {
var b = function() {
this;
n-- && h();
}();
function h() {
b && c++;
}
h(b = 1);
})();
console.log(c);
}
expect_stdout: "1"
}

37
test/compress/regexp.js Normal file
View File

@@ -0,0 +1,37 @@
regexp_simple: {
input: {
/rx/ig
}
expect_exact: "/rx/gi;"
}
regexp_slashes: {
input: {
/\\\/rx\/\\/ig
}
expect_exact: "/\\\\\\/rx\\/\\\\/gi;"
}
regexp_1: {
input: {
console.log(JSON.stringify("COMPASS? Overpass.".match(/([Sap]+)/ig)));
}
expect: {
console.log(JSON.stringify("COMPASS? Overpass.".match(/([Sap]+)/gi)));
}
expect_stdout: '["PASS","pass"]'
}
regexp_2: {
options = {
evaluate: true,
unsafe: true,
}
input: {
console.log(JSON.stringify("COMPASS? Overpass.".match(new RegExp("([Sap]+)", "ig"))));
}
expect: {
console.log(JSON.stringify("COMPASS? Overpass.".match(/([Sap]+)/gi)));
}
expect_stdout: '["PASS","pass"]'
}

View File

@@ -14,6 +14,13 @@ issue_1929: {
function f(s) { function f(s) {
return s.split(/[\\/]/); return s.split(/[\\/]/);
} }
console.log(JSON.stringify(f("A/B\\C\\D/E\\F")));
} }
expect_exact: "function f(s){return s.split(/[\\\\/]/)}" expect: {
function f(s) {
return s.split(/[\\/]/);
}
console.log(JSON.stringify(f("A/B\\C\\D/E\\F")));
}
expect_stdout: '["A","B","C","D","E","F"]'
} }

View File

@@ -57,24 +57,23 @@ describe("sourcemaps", function() {
includeSources: true includeSources: true
} }
}); });
if (result.error) throw result.error;
var map = JSON.parse(result.map); var map = JSON.parse(result.map);
assert.equal(map.file, "simple.min.js");
assert.equal(map.file, 'simple.min.js');
assert.equal(map.sourcesContent.length, 1); assert.equal(map.sourcesContent.length, 1);
assert.equal(map.sourcesContent[0], assert.equal(map.sourcesContent[0], 'let foo = x => "foo " + x;\nconsole.log(foo("bar"));');
'let foo = x => "foo " + x;\nconsole.log(foo("bar"));');
}); });
it("Should process inline source map", function() { it("Should process inline source map", function() {
var code = Uglify.minify(read("./test/input/issue-520/input.js"), { var result = Uglify.minify(read("./test/input/issue-520/input.js"), {
compress: { toplevel: true }, compress: { toplevel: true },
sourceMap: { sourceMap: {
content: "inline", content: "inline",
includeSources: true, includeSources: true,
url: "inline" url: "inline"
} }
}).code + "\n"; });
assert.strictEqual(code, readFileSync("test/input/issue-520/output.js", "utf8")); if (result.error) throw result.error;
assert.strictEqual(result.code + "\n", readFileSync("test/input/issue-520/output.js", "utf8"));
}); });
it("Should warn for missing inline source map", function() { it("Should warn for missing inline source map", function() {
var warn_function = Uglify.AST_Node.warn_function; var warn_function = Uglify.AST_Node.warn_function;
@@ -149,22 +148,24 @@ describe("sourcemaps", function() {
}); });
describe("sourceMapInline", function() { describe("sourceMapInline", function() {
it("should append source map to output js when sourceMapInline is enabled", function() { it("Should append source map to output js when sourceMapInline is enabled", function() {
var result = Uglify.minify('var a = function(foo) { return foo; };', { var result = Uglify.minify('var a = function(foo) { return foo; };', {
sourceMap: { sourceMap: {
url: "inline" url: "inline"
} }
}); });
if (result.error) throw result.error;
var code = result.code; var code = result.code;
assert.strictEqual(code, "var a=function(n){return n};\n" + assert.strictEqual(code, "var a=function(n){return n};\n" +
"//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIjAiXSwibmFtZXMiOlsiYSIsImZvbyJdLCJtYXBwaW5ncyI6IkFBQUEsSUFBSUEsRUFBSSxTQUFTQyxHQUFPLE9BQU9BIn0="); "//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIjAiXSwibmFtZXMiOlsiYSIsImZvbyJdLCJtYXBwaW5ncyI6IkFBQUEsSUFBSUEsRUFBSSxTQUFTQyxHQUFPLE9BQU9BIn0=");
}); });
it("should not append source map to output js when sourceMapInline is not enabled", function() { it("Should not append source map to output js when sourceMapInline is not enabled", function() {
var result = Uglify.minify('var a = function(foo) { return foo; };'); var result = Uglify.minify('var a = function(foo) { return foo; };');
if (result.error) throw result.error;
var code = result.code; var code = result.code;
assert.strictEqual(code, "var a=function(n){return n};"); assert.strictEqual(code, "var a=function(n){return n};");
}); });
it("should work with max_line_len", function() { it("Should work with max_line_len", function() {
var result = Uglify.minify(read("./test/input/issue-505/input.js"), { var result = Uglify.minify(read("./test/input/issue-505/input.js"), {
output: { output: {
max_line_len: 20 max_line_len: 20
@@ -173,8 +174,39 @@ describe("sourcemaps", function() {
url: "inline" url: "inline"
} }
}); });
assert.strictEqual(result.error, undefined); if (result.error) throw result.error;
assert.strictEqual(result.code, read("./test/input/issue-505/output.js")); assert.strictEqual(result.code, read("./test/input/issue-505/output.js"));
}); });
it("Should work with unicode characters", function() {
var code = [
"var tëst = '→unicøde←';",
"alert(tëst);",
].join("\n");
var result = Uglify.minify(code, {
sourceMap: {
includeSources: true,
url: "inline",
}
});
if (result.error) throw result.error;
var map = JSON.parse(result.map);
assert.strictEqual(map.sourcesContent.length, 1);
assert.strictEqual(map.sourcesContent[0], code);
var encoded = result.code.slice(result.code.lastIndexOf(",") + 1);
map = JSON.parse(new Buffer(encoded, "base64").toString());
assert.strictEqual(map.sourcesContent.length, 1);
assert.strictEqual(map.sourcesContent[0], code);
result = Uglify.minify(result.code, {
sourceMap: {
content: "inline",
includeSources: true,
}
});
if (result.error) throw result.error;
map = JSON.parse(result.map);
assert.strictEqual(map.names.length, 2);
assert.strictEqual(map.names[0], "tëst");
assert.strictEqual(map.names[1], "alert");
});
}); });
}); });