From 7bafacd236fefbc39d59df2677ecc692dea92148 Mon Sep 17 00:00:00 2001 From: Shrey Banga Date: Thu, 5 May 2016 13:44:59 -0700 Subject: [PATCH 1/9] Respect quote style in object literals The option added in fbbaa42ee55a7f753f7cab9b1a905ccf73cf26d5 wasn't being respected inside object literals, so quoted property names would still be stripped out with this option. This is mostly a corner-case, but useful when the output is passed to something like the Closure compiler, where quoted property names can be used to prevent mangling. --- lib/output.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/output.js b/lib/output.js index f8787582..431e6a01 100644 --- a/lib/output.js +++ b/lib/output.js @@ -1142,7 +1142,11 @@ function OutputStream(options) { && parseFloat(key) >= 0) { output.print(make_num(key)); } else if (RESERVED_WORDS(key) ? output.option("screw_ie8") : is_identifier_string(key)) { - output.print_name(key); + if (quote && output.option("quote_style")) { + output.print_string(key, quote); + } else { + output.print_name(key); + } } else { output.print_string(key, quote); } From a84003d156d727a40809a3d42b1c92634418447f Mon Sep 17 00:00:00 2001 From: Shrey Banga Date: Mon, 9 May 2016 21:48:36 -0700 Subject: [PATCH 2/9] Add new option to preserve quoted props Also added a couple of tests. --- lib/output.js | 5 +++-- test/mocha/minify.js | 32 +++++++++++++++++++++++++++++++- 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/lib/output.js b/lib/output.js index 431e6a01..1ac5ea8e 100644 --- a/lib/output.js +++ b/lib/output.js @@ -64,7 +64,8 @@ function OutputStream(options) { preserve_line : false, screw_ie8 : false, preamble : null, - quote_style : 0 + quote_style : 0, + keep_quoted_props: false }, true); var indentation = 0; @@ -1142,7 +1143,7 @@ function OutputStream(options) { && parseFloat(key) >= 0) { output.print(make_num(key)); } else if (RESERVED_WORDS(key) ? output.option("screw_ie8") : is_identifier_string(key)) { - if (quote && output.option("quote_style")) { + if (quote && output.option("keep_quoted_props")) { output.print_string(key, quote); } else { output.print_name(key); diff --git a/test/mocha/minify.js b/test/mocha/minify.js index bbf188c4..66fd8885 100644 --- a/test/mocha/minify.js +++ b/test/mocha/minify.js @@ -7,5 +7,35 @@ describe("minify", function() { var result = Uglify.minify(js, {fromString: true}); assert.strictEqual(result.code, 'function foo(n){return n?3:7}'); }); -}); + describe("keep_quoted_props", function() { + it("Should preserve quotes in object literals", function() { + var js = 'var foo = {"x": 1, y: 2, \'z\': 3};'; + var result = Uglify.minify(js, { + fromString: true, output: { + keep_quoted_props: true + }}); + assert.strictEqual(result.code, 'var foo={"x":1,y:2,"z":3};'); + }); + + it("Should preserve quote styles when quote_style is 3", function() { + var js = 'var foo = {"x": 1, y: 2, \'z\': 3};'; + var result = Uglify.minify(js, { + fromString: true, output: { + keep_quoted_props: true, + quote_style: 3 + }}); + assert.strictEqual(result.code, 'var foo={"x":1,y:2,\'z\':3};'); + }); + + it("Should not preserve quotes in object literals when disabled", function() { + var js = 'var foo = {"x": 1, y: 2, \'z\': 3};'; + var result = Uglify.minify(js, { + fromString: true, output: { + keep_quoted_props: false, + quote_style: 3 + }}); + assert.strictEqual(result.code, 'var foo={x:1,y:2,z:3};'); + }); + }); +}); From 2e22da589cd1023c0382252b02cb2cb5a9bde622 Mon Sep 17 00:00:00 2001 From: Shrey Banga Date: Fri, 10 Jun 2016 10:43:11 -0700 Subject: [PATCH 3/9] Add keep_quoted_props to readme --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 041a0709..a19f7ef4 100644 --- a/README.md +++ b/README.md @@ -472,6 +472,8 @@ can pass additional arguments that control the code output: - `1` -- always use single quotes - `2` -- always use double quotes - `3` -- always use the original quotes +- `keep_quoted_props` (default `false`) -- when turned on, prevents stripping + quotes from property names in object literals. ### Keeping copyright notices or other comments From 6f3fea60d236081ee6b4ae5b59f7ebc0c0c951af Mon Sep 17 00:00:00 2001 From: Shrey Banga Date: Fri, 10 Jun 2016 11:55:52 -0700 Subject: [PATCH 4/9] Add a mode to only mangle unquoted property names --- README.md | 6 +++++- bin/uglifyjs | 16 ++++++++++------ lib/propmangle.js | 13 +++++++++---- test/compress/properties.js | 32 ++++++++++++++++++++++++++++++++ 4 files changed, 56 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index a19f7ef4..9c63d727 100644 --- a/README.md +++ b/README.md @@ -133,7 +133,11 @@ The available options are: --reserved-file File containing reserved names --reserve-domprops Make (most?) DOM properties reserved for --mangle-props - --mangle-props Mangle property names + --mangle-props Mangle property names (default `0`). Set to + `true` or `1` to mangle all property names. Set + to `1` to only mangle unquoted property names. + Use the `keep_quoted_props` beautifier option to + preserve the quotes around property names. --mangle-regex Only mangle property names matching the regex --name-cache File to hold mangled names mappings --pure-funcs List of functions that can be safely removed if diff --git a/bin/uglifyjs b/bin/uglifyjs index 45c92b50..ec791b07 100755 --- a/bin/uglifyjs +++ b/bin/uglifyjs @@ -69,7 +69,7 @@ You need to pass an argument to this option to specify the name that your module .describe("quotes", "Quote style (0 - auto, 1 - single, 2 - double, 3 - original)") .describe("reserved-file", "File containing reserved names") .describe("reserve-domprops", "Make (most?) DOM properties reserved for --mangle-props") - .describe("mangle-props", "Mangle property names") + .describe("mangle-props", "Mangle property names (0 - disabled, 1 - mangle all properties, 2 - mangle unquoted properies)") .describe("mangle-regex", "Only mangle property names matching the regex") .describe("name-cache", "File to hold mangled names mappings") .describe("pure-funcs", "List of functions that can be safely removed if their return value is not used") @@ -125,7 +125,6 @@ You need to pass an argument to this option to specify the name that your module .boolean("noerr") .boolean("bare-returns") .boolean("keep-fnames") - .boolean("mangle-props") .boolean("reserve-domprops") .wrap(80) @@ -388,6 +387,10 @@ async.eachLimit(files, 1, function (file, cb) { TOPLEVEL = TOPLEVEL.wrap_enclose(arg_parameter_list); } + if (ARGS.mangle_props === true) { + ARGS.mangle_props = 1; + } + if (ARGS.mangle_props || ARGS.name_cache) (function(){ var reserved = RESERVED ? RESERVED.props : null; var cache = readNameCache("props"); @@ -401,10 +404,11 @@ async.eachLimit(files, 1, function (file, cb) { } TOPLEVEL = UglifyJS.mangle_properties(TOPLEVEL, { - reserved : reserved, - cache : cache, - only_cache : !ARGS.mangle_props, - regex : regex + reserved : reserved, + cache : cache, + only_cache : !ARGS.mangle_props, + regex : regex, + ignore_quoted : ARGS.mangle_props == 2 }); writeNameCache("props", cache); })(); diff --git a/lib/propmangle.js b/lib/propmangle.js index 840bda91..c905e85e 100644 --- a/lib/propmangle.js +++ b/lib/propmangle.js @@ -65,7 +65,8 @@ function mangle_properties(ast, options) { reserved : null, cache : null, only_cache : false, - regex : null + regex : null, + ignore_quoted : false }); var reserved = options.reserved; @@ -81,6 +82,7 @@ function mangle_properties(ast, options) { } var regex = options.regex; + var ignore_quoted = options.ignore_quoted; var names_to_mangle = []; var unmangleable = []; @@ -88,11 +90,13 @@ function mangle_properties(ast, options) { // step 1: find candidates to mangle ast.walk(new TreeWalker(function(node){ if (node instanceof AST_ObjectKeyVal) { - add(node.key); + if (!(ignore_quoted && node.quote)) + add(node.key); } else if (node instanceof AST_ObjectProperty) { // setter or getter, since KeyVal is handled above - add(node.key.name); + if (!(ignore_quoted && node.key.quote)) + add(node.key.name); } else if (node instanceof AST_Dot) { if (this.parent() instanceof AST_Assign) { @@ -101,7 +105,8 @@ function mangle_properties(ast, options) { } else if (node instanceof AST_Sub) { if (this.parent() instanceof AST_Assign) { - addStrings(node.property); + if (!(ignore_quoted && node.property.quote)) + addStrings(node.property); } } })); diff --git a/test/compress/properties.js b/test/compress/properties.js index 39470738..c903d315 100644 --- a/test/compress/properties.js +++ b/test/compress/properties.js @@ -72,3 +72,35 @@ evaluate_length: { a = ("foo" + b).length; } } + +mangle_properties: { + mangle_props = { + ignore_quoted: false + }; + input: { + a["foo"] = "bar"; + a.color = "red"; + x = {"bar": 10}; + } + expect: { + a["a"] = "bar"; + a.b = "red"; + x = {c: 10}; + } +} + +mangle_unquoted_properties: { + mangle_props = { + ignore_quoted: true + }; + input: { + a["foo"] = "bar"; + a.color = "red"; + x = {"bar": 10}; + } + expect: { + a["foo"] = "bar"; + a.a = "red"; + x = {"bar": 10}; + } +} From 51ed9b80c9853cc79f9641175f57a89ea5007d29 Mon Sep 17 00:00:00 2001 From: Shrey Banga Date: Sun, 12 Jun 2016 12:00:57 -0700 Subject: [PATCH 5/9] Address feedback - Fix mangle implementation to work for all cases - Update and add tests - Document usage via the API --- README.md | 10 +++++++--- lib/propmangle.js | 11 ++++++----- test/compress/properties.js | 33 ++++++++++++++++++++++++++------- test/mocha/minify.js | 21 +++++++++++++++++++++ test/run-tests.js | 6 +++++- 5 files changed, 65 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 9c63d727..796d37b9 100644 --- a/README.md +++ b/README.md @@ -135,9 +135,12 @@ The available options are: --mangle-props --mangle-props Mangle property names (default `0`). Set to `true` or `1` to mangle all property names. Set - to `1` to only mangle unquoted property names. + to `2` to only mangle unquoted property names. Use the `keep_quoted_props` beautifier option to - preserve the quotes around property names. + preserve the quotes around property names and set + the `properties` compressor option to `false` to + prevent rewriting quoted properties with dot + notation. --mangle-regex Only mangle property names matching the regex --name-cache File to hold mangled names mappings --pure-funcs List of functions that can be safely removed if @@ -666,7 +669,8 @@ Other options: ##### mangleProperties options - - `regex` — Pass a RegExp to only mangle certain names (maps to the `--mange-regex` CLI arguments option) + - `regex` — Pass a RegExp to only mangle certain names (maps to the `--mangle-regex` CLI arguments option) + - `ignore_quoted` – Only mangle unquoted property names (maps to the `--mangle-props 2` CLI arguments option) We could add more options to `UglifyJS.minify` — if you need additional functionality please suggest! diff --git a/lib/propmangle.js b/lib/propmangle.js index c905e85e..08043d73 100644 --- a/lib/propmangle.js +++ b/lib/propmangle.js @@ -95,8 +95,7 @@ function mangle_properties(ast, options) { } else if (node instanceof AST_ObjectProperty) { // setter or getter, since KeyVal is handled above - if (!(ignore_quoted && node.key.quote)) - add(node.key.name); + add(node.key.name); } else if (node instanceof AST_Dot) { if (this.parent() instanceof AST_Assign) { @@ -105,7 +104,7 @@ function mangle_properties(ast, options) { } else if (node instanceof AST_Sub) { if (this.parent() instanceof AST_Assign) { - if (!(ignore_quoted && node.property.quote)) + if (!ignore_quoted) addStrings(node.property); } } @@ -114,7 +113,8 @@ function mangle_properties(ast, options) { // step 2: transform the tree, renaming properties return ast.transform(new TreeTransformer(function(node){ if (node instanceof AST_ObjectKeyVal) { - node.key = mangle(node.key); + if (!(ignore_quoted && node.quote)) + node.key = mangle(node.key); } else if (node instanceof AST_ObjectProperty) { // setter or getter @@ -124,7 +124,8 @@ function mangle_properties(ast, options) { node.property = mangle(node.property); } else if (node instanceof AST_Sub) { - node.property = mangleStrings(node.property); + if (!ignore_quoted) + node.property = mangleStrings(node.property); } // else if (node instanceof AST_String) { // if (should_mangle(node.value)) { diff --git a/test/compress/properties.js b/test/compress/properties.js index c903d315..574c5142 100644 --- a/test/compress/properties.js +++ b/test/compress/properties.js @@ -92,15 +92,34 @@ mangle_properties: { mangle_unquoted_properties: { mangle_props = { ignore_quoted: true - }; + } + beautify = { + beautify: false, + quote_style: 3, + keep_quoted_props: true, + } input: { - a["foo"] = "bar"; - a.color = "red"; - x = {"bar": 10}; + function f1() { + a["foo"] = "bar"; + a.color = "red"; + x = {"bar": 10}; + } + function f2() { + a.foo = "bar"; + a['color'] = "red"; + x = {bar: 10}; + } } expect: { - a["foo"] = "bar"; - a.a = "red"; - x = {"bar": 10}; + function f1() { + a["foo"] = "bar"; + a.a = "red"; + x = {"bar": 10}; + } + function f2() { + a.b = "bar"; + a['color'] = "red"; + x = {c: 10}; + } } } diff --git a/test/mocha/minify.js b/test/mocha/minify.js index 66fd8885..02d31558 100644 --- a/test/mocha/minify.js +++ b/test/mocha/minify.js @@ -38,4 +38,25 @@ describe("minify", function() { assert.strictEqual(result.code, 'var foo={x:1,y:2,z:3};'); }); }); + + describe("mangleProperties", function() { + it("Shouldn't mangle quoted properties", function() { + var js = 'a["foo"] = "bar"; a.color = "red"; x = {"bar": 10};'; + var result = Uglify.minify(js, { + fromString: true, + compress: { + properties: false + }, + mangleProperties: { + ignore_quoted: true + }, + output: { + keep_quoted_props: true, + quote_style: 3 + } + }); + assert.strictEqual(result.code, + 'a["foo"]="bar",a.a="red",x={"bar":10};'); + }); + }); }); diff --git a/test/run-tests.js b/test/run-tests.js index 0a249b9f..01948630 100755 --- a/test/run-tests.js +++ b/test/run-tests.js @@ -106,7 +106,11 @@ function run_compress_tests() { expect = test.expect_exact; } var input = as_toplevel(test.input); - var input_code = make_code(test.input, { beautify: true }); + var input_code = make_code(test.input, { + beautify: true, + quote_style: 3, + keep_quoted_props: true + }); if (test.mangle_props) { input = U.mangle_properties(input, test.mangle_props); } From af42bbe9760324463f4335c26d507528f1d11c35 Mon Sep 17 00:00:00 2001 From: Shrey Banga Date: Mon, 13 Jun 2016 15:17:28 -0700 Subject: [PATCH 6/9] Set sane defaults when mangle_props=2 --- README.md | 12 +++++++----- bin/uglifyjs | 24 +++++++++++++++++------- 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 796d37b9..56f980a2 100644 --- a/README.md +++ b/README.md @@ -136,11 +136,13 @@ The available options are: --mangle-props Mangle property names (default `0`). Set to `true` or `1` to mangle all property names. Set to `2` to only mangle unquoted property names. - Use the `keep_quoted_props` beautifier option to - preserve the quotes around property names and set - the `properties` compressor option to `false` to - prevent rewriting quoted properties with dot - notation. + This also enables the `keep_quoted_props` + beautifier option to preserve the quotes around + property names and disables the `properties` + compressor option to prevent rewriting quoted + properties with dot notation. You can override + these by setting them explicitly on the command + line. --mangle-regex Only mangle property names matching the regex --name-cache File to hold mangled names mappings --pure-funcs List of functions that can be safely removed if diff --git a/bin/uglifyjs b/bin/uglifyjs index ec791b07..844c139c 100755 --- a/bin/uglifyjs +++ b/bin/uglifyjs @@ -212,12 +212,26 @@ if (ARGS.quotes === true) { ARGS.quotes = 3; } +if (ARGS.mangle_props === true) { + ARGS.mangle_props = 1; +} + +if (ARGS.mangle_props === "unquoted") { + ARGS.mangle_props = 2; +} + var OUTPUT_OPTIONS = { - beautify : BEAUTIFY ? true : false, - preamble : ARGS.preamble || null, - quote_style : ARGS.quotes != null ? ARGS.quotes : 0 + beautify : BEAUTIFY ? true : false, + preamble : ARGS.preamble || null, + quote_style : ARGS.quotes != null ? ARGS.quotes : 0, }; +if (ARGS.mangle_props == 2) { + OUTPUT_OPTIONS.keep_quoted_props = true; + if (COMPRESS && !("properties" in COMPRESS)) + COMPRESS.properties = false; +} + if (ARGS.screw_ie8) { if (COMPRESS) COMPRESS.screw_ie8 = true; if (MANGLE) MANGLE.screw_ie8 = true; @@ -387,10 +401,6 @@ async.eachLimit(files, 1, function (file, cb) { TOPLEVEL = TOPLEVEL.wrap_enclose(arg_parameter_list); } - if (ARGS.mangle_props === true) { - ARGS.mangle_props = 1; - } - if (ARGS.mangle_props || ARGS.name_cache) (function(){ var reserved = RESERVED ? RESERVED.props : null; var cache = readNameCache("props"); From 3eed8181d9164447870ecd6d1ccf4ca3ee68073c Mon Sep 17 00:00:00 2001 From: Shrey Banga Date: Mon, 13 Jun 2016 15:21:39 -0700 Subject: [PATCH 7/9] Explain mangle-props better in readme --- README.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 56f980a2..0a6e1b92 100644 --- a/README.md +++ b/README.md @@ -135,14 +135,14 @@ The available options are: --mangle-props --mangle-props Mangle property names (default `0`). Set to `true` or `1` to mangle all property names. Set - to `2` to only mangle unquoted property names. - This also enables the `keep_quoted_props` - beautifier option to preserve the quotes around - property names and disables the `properties` - compressor option to prevent rewriting quoted - properties with dot notation. You can override - these by setting them explicitly on the command - line. + to `unquoted` or `2` to only mangle unquoted + property names. Mode `2` also enables the + `keep_quoted_props` beautifier option to + preserve the quotes around property names and + disables the `properties` compressor option to + prevent rewriting quoted properties with dot + notation. You can override these by setting + them explicitly on the command line. --mangle-regex Only mangle property names matching the regex --name-cache File to hold mangled names mappings --pure-funcs List of functions that can be safely removed if From b8aed9878a93cee1193ca5e8b03f37b76ce0f4dc Mon Sep 17 00:00:00 2001 From: Shrey Banga Date: Mon, 13 Jun 2016 17:08:41 -0700 Subject: [PATCH 8/9] Address feedback --- bin/uglifyjs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/bin/uglifyjs b/bin/uglifyjs index 844c139c..9d139149 100755 --- a/bin/uglifyjs +++ b/bin/uglifyjs @@ -214,16 +214,14 @@ if (ARGS.quotes === true) { if (ARGS.mangle_props === true) { ARGS.mangle_props = 1; -} - -if (ARGS.mangle_props === "unquoted") { +} else if (ARGS.mangle_props === "unquoted") { ARGS.mangle_props = 2; } var OUTPUT_OPTIONS = { - beautify : BEAUTIFY ? true : false, - preamble : ARGS.preamble || null, - quote_style : ARGS.quotes != null ? ARGS.quotes : 0, + beautify : BEAUTIFY ? true : false, + preamble : ARGS.preamble || null, + quote_style : ARGS.quotes != null ? ARGS.quotes : 0, }; if (ARGS.mangle_props == 2) { From 91719ff0527257b46bbe14d8601fc59018e00bad Mon Sep 17 00:00:00 2001 From: Shrey Banga Date: Mon, 13 Jun 2016 17:09:27 -0700 Subject: [PATCH 9/9] remove trailing comma --- bin/uglifyjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/uglifyjs b/bin/uglifyjs index 9d139149..b7009426 100755 --- a/bin/uglifyjs +++ b/bin/uglifyjs @@ -221,7 +221,7 @@ if (ARGS.mangle_props === true) { var OUTPUT_OPTIONS = { beautify : BEAUTIFY ? true : false, preamble : ARGS.preamble || null, - quote_style : ARGS.quotes != null ? ARGS.quotes : 0, + quote_style : ARGS.quotes != null ? ARGS.quotes : 0 }; if (ARGS.mangle_props == 2) {