diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 6d710c6a175..c5343f3f1b9 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -8,7 +8,14 @@ **Uglify version (`uglifyjs -V`)** -**JavaScript input** +**JavaScript input** + + **The `uglifyjs` CLI command executed or `minify()` options used.** diff --git a/lib/minify.js b/lib/minify.js index f7daaacff39..4150ee8e59b 100644 --- a/lib/minify.js +++ b/lib/minify.js @@ -70,6 +70,7 @@ function minify(files, options) { set_shorthand("keep_fnames", options, [ "compress", "mangle" ]); set_shorthand("toplevel", options, [ "compress", "mangle" ]); set_shorthand("warnings", options, [ "compress" ]); + var quoted_props; if (options.mangle) { options.mangle = defaults(options.mangle, { cache: options.nameCache && (options.nameCache.vars || {}), @@ -82,11 +83,16 @@ function minify(files, options) { safari10: false, toplevel: false, }, true); - if (options.nameCache && options.mangle.properties) { + if (options.mangle.properties) { if (typeof options.mangle.properties != "object") { options.mangle.properties = {}; } - if (!("cache" in options.mangle.properties)) { + if (options.mangle.properties.keep_quoted) { + quoted_props = options.mangle.properties.reserved; + if (!Array.isArray(quoted_props)) quoted_props = []; + options.mangle.properties.reserved = quoted_props; + } + if (options.nameCache && !("cache" in options.mangle.properties)) { options.mangle.properties.cache = options.nameCache.props || {}; } } @@ -129,6 +135,9 @@ function minify(files, options) { } toplevel = options.parse.toplevel; } + if (quoted_props) { + reserve_quoted_keys(toplevel, quoted_props); + } if (options.wrap) { toplevel = toplevel.wrap_commonjs(options.wrap); } diff --git a/lib/propmangle.js b/lib/propmangle.js index 3c848c62bd1..6ca6a491571 100644 --- a/lib/propmangle.js +++ b/lib/propmangle.js @@ -85,6 +85,36 @@ function find_builtins(reserved) { } } +function reserve_quoted_keys(ast, reserved) { + function add(name) { + push_uniq(reserved, name); + } + + ast.walk(new TreeWalker(function(node) { + if (node instanceof AST_ObjectKeyVal && node.quote) { + add(node.key); + } else if (node instanceof AST_ObjectProperty && node.quote) { + add(node.key.name); + } else if (node instanceof AST_Sub) { + addStrings(node.property, add); + } + })); +} + +function addStrings(node, add) { + node.walk(new TreeWalker(function(node) { + if (node instanceof AST_Sequence) { + addStrings(node.expressions[node.expressions.length - 1], add); + } else if (node instanceof AST_String) { + add(node.value); + } else if (node instanceof AST_Conditional) { + addStrings(node.consequent, add); + addStrings(node.alternative, add); + } + return true; + })); +} + function mangle_properties(ast, options) { options = defaults(options, { builtins: false, @@ -109,7 +139,6 @@ function mangle_properties(ast, options) { } var regex = options.regex; - var keep_quoted = options.keep_quoted; // note debug is either false (disabled), or a string of the debug suffix to use (enabled). // note debug may be enabled as an empty string, which is falsey. Also treat passing 'true' @@ -122,15 +151,11 @@ function mangle_properties(ast, options) { var names_to_mangle = []; var unmangleable = []; - var to_keep = {}; // step 1: find candidates to mangle ast.walk(new TreeWalker(function(node){ if (node instanceof AST_ObjectKeyVal) { - add(node.key, keep_quoted && node.quote); - } - else if (node instanceof AST_ConciseMethod && node.key && node.key.name) { - add(node.key.name, keep_quoted && node.quote); + add(node.key); } else if (node instanceof AST_ObjectProperty) { // setter or getter, since KeyVal is handled above @@ -140,20 +165,14 @@ function mangle_properties(ast, options) { add(node.property); } else if (node instanceof AST_Sub) { - addStrings(node.property, keep_quoted); + addStrings(node.property, add); } })); // step 2: transform the tree, renaming properties return ast.transform(new TreeTransformer(function(node){ if (node instanceof AST_ObjectKeyVal) { - if (!(keep_quoted && node.quote)) - node.key = mangle(node.key); - } - else if (node instanceof AST_ConciseMethod && node.name && node.key.name) { - if (!(keep_quoted && node.quote) && should_mangle(node.key.name)) { - node.key.name = mangle(node.key.name); - } + node.key = mangle(node.key); } else if (node instanceof AST_ObjectProperty) { // setter or getter @@ -162,22 +181,9 @@ function mangle_properties(ast, options) { else if (node instanceof AST_Dot) { node.property = mangle(node.property); } - else if (node instanceof AST_Sub) { - if (!keep_quoted) - node.property = mangleStrings(node.property); + else if (!options.keep_quoted && node instanceof AST_Sub) { + node.property = mangleStrings(node.property); } - // else if (node instanceof AST_String) { - // if (should_mangle(node.value)) { - // AST_Node.warn( - // "Found \"{prop}\" property candidate for mangling in an arbitrary string [{file}:{line},{col}]", { - // file : node.start.file, - // line : node.start.line, - // col : node.start.col, - // prop : node.value - // } - // ); - // } - // } })); // only function declarations after this line @@ -193,19 +199,13 @@ function mangle_properties(ast, options) { } function should_mangle(name) { - if (keep_quoted && name in to_keep) return false; if (regex && !regex.test(name)) return false; if (reserved.indexOf(name) >= 0) return false; return cache.props.has(name) || names_to_mangle.indexOf(name) >= 0; } - function add(name, keep) { - if (keep) { - to_keep[name] = true; - return; - } - + function add(name) { if (can_mangle(name)) push_uniq(names_to_mangle, name); @@ -225,19 +225,16 @@ function mangle_properties(ast, options) { // debug mode: use a prefix and suffix to preserve readability, e.g. o.foo -> o._$foo$NNN_. var debug_mangled = "_$" + name + "$" + debug_name_suffix + "_"; - if (can_mangle(debug_mangled) && !(keep_quoted && debug_mangled in to_keep)) { + if (can_mangle(debug_mangled)) { mangled = debug_mangled; } } // either debug mode is off, or it is on and we could not use the mangled name if (!mangled) { - // Note: `can_mangle()` does not check if the name collides with the `to_keep` set - // (filled with quoted properties when `keep_quoted` is set). Make sure we add this - // check so we don't collide with a quoted name. do { mangled = base54(++cache.cname); - } while (!can_mangle(mangled) || keep_quoted && mangled in to_keep); + } while (!can_mangle(mangled)); } cache.props.set(name, mangled); @@ -245,32 +242,6 @@ function mangle_properties(ast, options) { return mangled; } - function addStrings(node, keep) { - var out = {}; - try { - (function walk(node){ - node.walk(new TreeWalker(function(node){ - if (node instanceof AST_Sequence) { - walk(node.expressions[node.expressions.length - 1]); - return true; - } - if (node instanceof AST_String) { - add(node.value, keep); - return true; - } - if (node instanceof AST_Conditional) { - walk(node.consequent); - walk(node.alternative); - return true; - } - throw out; - })); - })(node); - } catch(ex) { - if (ex !== out) throw ex; - } - } - function mangleStrings(node) { return node.transform(new TreeTransformer(function(node){ if (node instanceof AST_Sequence) { diff --git a/package.json b/package.json index 4e53e07fb32..2e35d753f29 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "homepage": "https://github.com/mishoo/UglifyJS2/tree/harmony", "author": "Mihai Bazon (http://lisperator.net/)", "license": "BSD-2-Clause", - "version": "3.0.26", + "version": "3.0.27", "engines": { "node": ">=0.8.0" }, diff --git a/test/compress/issue-1321.js b/test/compress/issue-1321.js index 6b92291dfc0..e55696d012c 100644 --- a/test/compress/issue-1321.js +++ b/test/compress/issue-1321.js @@ -1,6 +1,8 @@ issue_1321_no_debug: { - mangle_props = { - keep_quoted: true + mangle = { + properties: { + keep_quoted: true, + }, } input: { var x = {}; @@ -10,17 +12,19 @@ issue_1321_no_debug: { } expect: { var x = {}; - x.o = 1; - x["a"] = 2 * x.o; - console.log(x.o, x["a"]); + x.x = 1; + x["a"] = 2 * x.x; + console.log(x.x, x["a"]); } expect_stdout: true } issue_1321_debug: { - mangle_props = { - keep_quoted: true, - debug: "" + mangle = { + properties: { + debug: "", + keep_quoted: true, + }, } input: { var x = {}; @@ -30,16 +34,18 @@ issue_1321_debug: { } expect: { var x = {}; - x.o = 1; - x["_$foo$_"] = 2 * x.o; - console.log(x.o, x["_$foo$_"]); + x.x = 1; + x["_$foo$_"] = 2 * x.x; + console.log(x.x, x["_$foo$_"]); } expect_stdout: true } issue_1321_with_quoted: { - mangle_props = { - keep_quoted: false + mangle = { + properties: { + keep_quoted: false, + }, } input: { var x = {}; @@ -49,9 +55,9 @@ issue_1321_with_quoted: { } expect: { var x = {}; - x.o = 1; - x["x"] = 2 * x.o; - console.log(x.o, x["x"]); + x.x = 1; + x["o"] = 2 * x.x; + console.log(x.x, x["o"]); } expect_stdout: true } diff --git a/test/compress/issue-1770.js b/test/compress/issue-1770.js index 34e4dc75b76..953e6fcd480 100644 --- a/test/compress/issue-1770.js +++ b/test/compress/issue-1770.js @@ -1,5 +1,7 @@ mangle_props: { - mangle_props = {} + mangle = { + properties: true, + } input: { var obj = { undefined: 1, @@ -54,10 +56,12 @@ mangle_props: { } numeric_literal: { + mangle = { + properties: true, + } beautify = { beautify: true, } - mangle_props = {} input: { var obj = { 0: 0, diff --git a/test/compress/issue-747.js b/test/compress/issue-747.js index 1f7079c239a..e074305a715 100644 --- a/test/compress/issue-747.js +++ b/test/compress/issue-747.js @@ -1,6 +1,8 @@ dont_reuse_prop: { - mangle_props = { - regex: /asd/ + mangle = { + properties: { + regex: /asd/, + }, } input: { "aaaaaaaaaabbbbb"; @@ -20,8 +22,10 @@ dont_reuse_prop: { } unmangleable_props_should_always_be_reserved: { - mangle_props = { - regex: /asd/ + mangle = { + properties: { + regex: /asd/, + }, } input: { "aaaaaaaaaabbbbb"; diff --git a/test/compress/object.js b/test/compress/object.js index f7ce136a488..4ad17324b3c 100644 --- a/test/compress/object.js +++ b/test/compress/object.js @@ -284,9 +284,11 @@ concise_methods_with_various_property_names: { } concise_methods_and_mangle_props: { - mangle_props = { - regex: /_/ - }; + mangle = { + properties: { + regex: /_/, + }, + } input: { function x() { obj = { diff --git a/test/compress/properties.js b/test/compress/properties.js index 07cf7883759..8f2a66aad86 100644 --- a/test/compress/properties.js +++ b/test/compress/properties.js @@ -128,9 +128,11 @@ evaluate_string_length: { } mangle_properties: { - mangle_props = { - keep_quoted: false - }; + mangle = { + properties: { + keep_quoted: false, + }, + } input: { a["foo"] = "bar"; a.color = "red"; @@ -139,11 +141,11 @@ mangle_properties: { a['run']({color: "blue", foo: "baz"}); } expect: { - a["o"] = "bar"; - a.a = "red"; - x = {r: 10}; - a.b(x.r, a.o); - a['b']({a: "blue", o: "baz"}); + a["a"] = "bar"; + a.b = "red"; + x = {o: 10}; + a.r(x.o, a.a); + a['r']({b: "blue", a: "baz"}); } } @@ -151,9 +153,11 @@ mangle_unquoted_properties: { options = { properties: false } - mangle_props = { - builtins: true, - keep_quoted: true + mangle = { + properties: { + builtins: true, + keep_quoted: true, + }, } beautify = { beautify: false, @@ -182,24 +186,26 @@ mangle_unquoted_properties: { function f1() { a["foo"] = "bar"; a.color = "red"; - a.o = 2; - x = {"bar": 10, f: 7}; - a.f = 9; + a.r = 2; + x = {"bar": 10, b: 7}; + a.b = 9; } function f2() { a.foo = "bar"; a['color'] = "red"; - x = {bar: 10, f: 7}; - a.f = 9; - a.o = 3; + x = {bar: 10, b: 7}; + a.b = 9; + a.r = 3; } } } mangle_debug: { - mangle_props = { - debug: "" - }; + mangle = { + properties: { + debug: "", + }, + } input: { a.foo = "bar"; x = { baz: "ban" }; @@ -211,9 +217,11 @@ mangle_debug: { } mangle_debug_true: { - mangle_props = { - debug: true - }; + mangle = { + properties: { + debug: true, + }, + } input: { a.foo = "bar"; x = { baz: "ban" }; @@ -225,9 +233,11 @@ mangle_debug_true: { } mangle_debug_suffix: { - mangle_props = { - debug: "XYZ" - }; + mangle = { + properties: { + debug: "XYZ", + }, + } input: { a.foo = "bar"; x = { baz: "ban" }; @@ -242,11 +252,13 @@ mangle_debug_suffix_keep_quoted: { options = { properties: false } - mangle_props = { - builtins: true, - keep_quoted: true, - debug: "XYZ", - reserved: [] + mangle = { + properties: { + builtins: true, + debug: "XYZ", + keep_quoted: true, + reserved: [], + }, } beautify = { beautify: false, @@ -876,9 +888,11 @@ methods_keep_quoted_true: { arrows: true, ecma: 6, } - mangle_props = { - keep_quoted: true, - }; + mangle = { + properties: { + keep_quoted: true, + }, + } input: { class C { "Quoted"(){} Unquoted(){} } f1({ "Quoted"(){}, Unquoted(){}, "Prop": 3 }); @@ -893,9 +907,11 @@ methods_keep_quoted_false: { arrows: true, ecma: 6, } - mangle_props = { - keep_quoted: false, - }; + mangle = { + properties: { + keep_quoted: false, + }, + } input: { class C { "Quoted"(){} Unquoted(){} } f1({ "Quoted"(){}, Unquoted(){}, "Prop": 3 }); @@ -904,3 +920,47 @@ methods_keep_quoted_false: { } expect_exact: "class C{o(){}d(){}}f1({o(){},d(){},e:3});f2({o(){}});f3({o(){}});" } + +methods_keep_quoted_from_dead_code: { + options = { + arrows: true, + booleans: true, + conditionals: true, + dead_code: true, + ecma: 6, + evaluate: true, + reduce_vars: true, + side_effects: true, + } + mangle = { + properties: { + keep_quoted: true, + }, + } + input: { + class C { Quoted(){} Unquoted(){} } + f1({ Quoted(){}, Unquoted(){}, "Prop": 3 }); + f2({ Quoted: function(){} }); + f3({ Quoted: ()=>{} }); + 0 && obj["Quoted"]; + } + expect_exact: "class C{Quoted(){}o(){}}f1({Quoted(){},o(){},Prop:3});f2({Quoted(){}});f3({Quoted(){}});" +} + +issue_2256: { + options = { + side_effects: true, + } + mangle = { + properties: { + keep_quoted: true, + }, + } + input: { + ({ "keep": 1 }); + g.keep = g.change; + } + expect: { + g.keep = g.g; + } +} diff --git a/test/exports.js b/test/exports.js index f50b772c516..997ad513c90 100644 --- a/test/exports.js +++ b/test/exports.js @@ -8,6 +8,7 @@ exports["defaults"] = defaults; exports["mangle_properties"] = mangle_properties; exports["minify"] = minify; exports["parse"] = parse; +exports["reserve_quoted_keys"] = reserve_quoted_keys; exports["string_template"] = string_template; exports["tokenizer"] = tokenizer; exports["is_identifier"] = is_identifier; diff --git a/test/mocha/minify.js b/test/mocha/minify.js index 88e9c4eb3d7..fc7332fb54c 100644 --- a/test/mocha/minify.js +++ b/test/mocha/minify.js @@ -124,6 +124,17 @@ describe("minify", function() { assert.strictEqual(result.code, 'a["foo"]="bar",a.a="red",x={"bar":10};'); }); + it("Should not mangle quoted property within dead code", function() { + var result = Uglify.minify('({ "keep": 1 }); g.keep = g.change;', { + mangle: { + properties: { + keep_quoted: true + } + } + }); + if (result.error) throw result.error; + assert.strictEqual(result.code, "g.keep=g.g;"); + }); }); describe("inSourceMap", function() { diff --git a/test/run-tests.js b/test/run-tests.js index 6b8c9ddf63a..0051c6c2f59 100755 --- a/test/run-tests.js +++ b/test/run-tests.js @@ -111,18 +111,22 @@ function run_compress_tests() { }; if (!options.warnings) options.warnings = true; } + if (test.mangle && test.mangle.properties && test.mangle.properties.keep_quoted) { + var quoted_props = test.mangle.properties.reserved; + if (!Array.isArray(quoted_props)) quoted_props = []; + test.mangle.properties.reserved = quoted_props; + U.reserve_quoted_keys(input, quoted_props); + } var cmp = new U.Compressor(options, true); var output = cmp.compress(input); output.figure_out_scope(test.mangle); - if (test.mangle || test.mangle_props) { + if (test.mangle) { U.base54.reset(); output.compute_char_frequency(test.mangle); - } - if (test.mangle) { output.mangle_names(test.mangle); - } - if (test.mangle_props) { - output = U.mangle_properties(output, test.mangle_props); + if (test.mangle.properties) { + output = U.mangle_properties(output, test.mangle.properties); + } } output = make_code(output, output_options); if (expect != output) {