From 0c003c92a85bd845dec8a10ee567c46b696c1403 Mon Sep 17 00:00:00 2001 From: kzc Date: Sat, 18 Jun 2016 23:06:59 -0400 Subject: [PATCH 01/13] Don't replace undefined, NaN and Infinity within with scope --- lib/compress.js | 69 +++++++++++++++------------ test/compress/issue-1105.js | 95 ++++++++++++++++++++++++++++++++++++- 2 files changed, 133 insertions(+), 31 deletions(-) diff --git a/lib/compress.js b/lib/compress.js index 3200a66f..db6a26d6 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -1056,31 +1056,37 @@ merge(Compressor.prototype, { throw def; }); def(AST_Binary, function(c){ - var left = this.left, right = this.right; + var left = this.left, right = this.right, result; switch (this.operator) { - case "&&" : return ev(left, c) && ev(right, c); - case "||" : return ev(left, c) || ev(right, c); - case "|" : return ev(left, c) | ev(right, c); - case "&" : return ev(left, c) & ev(right, c); - case "^" : return ev(left, c) ^ ev(right, c); - case "+" : return ev(left, c) + ev(right, c); - case "*" : return ev(left, c) * ev(right, c); - case "/" : return ev(left, c) / ev(right, c); - case "%" : return ev(left, c) % ev(right, c); - case "-" : return ev(left, c) - ev(right, c); - case "<<" : return ev(left, c) << ev(right, c); - case ">>" : return ev(left, c) >> ev(right, c); - case ">>>" : return ev(left, c) >>> ev(right, c); - case "==" : return ev(left, c) == ev(right, c); - case "===" : return ev(left, c) === ev(right, c); - case "!=" : return ev(left, c) != ev(right, c); - case "!==" : return ev(left, c) !== ev(right, c); - case "<" : return ev(left, c) < ev(right, c); - case "<=" : return ev(left, c) <= ev(right, c); - case ">" : return ev(left, c) > ev(right, c); - case ">=" : return ev(left, c) >= ev(right, c); + case "&&" : result = ev(left, c) && ev(right, c); break; + case "||" : result = ev(left, c) || ev(right, c); break; + case "|" : result = ev(left, c) | ev(right, c); break; + case "&" : result = ev(left, c) & ev(right, c); break; + case "^" : result = ev(left, c) ^ ev(right, c); break; + case "+" : result = ev(left, c) + ev(right, c); break; + case "*" : result = ev(left, c) * ev(right, c); break; + case "/" : result = ev(left, c) / ev(right, c); break; + case "%" : result = ev(left, c) % ev(right, c); break; + case "-" : result = ev(left, c) - ev(right, c); break; + case "<<" : result = ev(left, c) << ev(right, c); break; + case ">>" : result = ev(left, c) >> ev(right, c); break; + case ">>>" : result = ev(left, c) >>> ev(right, c); break; + case "==" : result = ev(left, c) == ev(right, c); break; + case "===" : result = ev(left, c) === ev(right, c); break; + case "!=" : result = ev(left, c) != ev(right, c); break; + case "!==" : result = ev(left, c) !== ev(right, c); break; + case "<" : result = ev(left, c) < ev(right, c); break; + case "<=" : result = ev(left, c) <= ev(right, c); break; + case ">" : result = ev(left, c) > ev(right, c); break; + case ">=" : result = ev(left, c) >= ev(right, c); break; + default: + throw def; } - throw def; + if (isNaN(result) && c.find_parent(AST_With)) { + // leave original expression as is + throw def; + } + return result; }); def(AST_Conditional, function(compressor){ return ev(this.condition, compressor) @@ -2599,13 +2605,16 @@ merge(Compressor.prototype, { if (defines && HOP(defines, self.name)) { return make_node_from_constant(compressor, defines[self.name], self); } - switch (self.name) { - case "undefined": - return make_node(AST_Undefined, self); - case "NaN": - return make_node(AST_NaN, self).transform(compressor); - case "Infinity": - return make_node(AST_Infinity, self).transform(compressor); + // testing against !self.scope.uses_with first is an optimization + if (!self.scope.uses_with || !compressor.find_parent(AST_With)) { + switch (self.name) { + case "undefined": + return make_node(AST_Undefined, self); + case "NaN": + return make_node(AST_NaN, self).transform(compressor); + case "Infinity": + return make_node(AST_Infinity, self).transform(compressor); + } } } return self; diff --git a/test/compress/issue-1105.js b/test/compress/issue-1105.js index 4205fdf5..28f1557a 100644 --- a/test/compress/issue-1105.js +++ b/test/compress/issue-1105.js @@ -144,4 +144,97 @@ check_drop_unused_in_peer_function: { bar(); } } -} \ No newline at end of file +} + +Infinity_not_in_with_scope: { + options = { + unused: true + } + input: { + var o = { Infinity: 'oInfinity' }; + var vInfinity = "Infinity"; + vInfinity = Infinity; + } + expect: { + var o = { Infinity: 'oInfinity' } + var vInfinity = "Infinity" + vInfinity = 1/0 + } +} + +Infinity_in_with_scope: { + options = { + unused: true + } + input: { + var o = { Infinity: 'oInfinity' }; + var vInfinity = "Infinity"; + with (o) { vInfinity = Infinity; } + } + expect: { + var o = { Infinity: 'oInfinity' } + var vInfinity = "Infinity" + with (o) vInfinity = Infinity + } +} + +assorted_Infinity_NaN_undefined_in_with_scope: { + options = { + unused: true, + evaluate: true, + dead_code: true, + conditionals: true, + comparisons: true, + booleans: true, + hoist_funs: true, + keep_fargs: true, + if_return: true, + join_vars: true, + cascade: true, + side_effects: true, + sequences: false, + } + input: { + var o = { + undefined : 3, + NaN : 4, + Infinity : 5, + } + if (o) { + f(undefined, void 0); + f(NaN, 0/0); + f(Infinity, 1/0); + f(-Infinity, -(1/0)); + f(2 + 7 + undefined, 2 + 7 + void 0); + } + with (o) { + f(undefined, void 0); + f(NaN, 0/0); + f(Infinity, 1/0); + f(-Infinity, -(1/0)); + f(2 + 7 + undefined, 2 + 7 + void 0); + } + } + expect: { + var o = { + undefined : 3, + NaN : 4, + Infinity : 5 + } + if (o) { + f(void 0, void 0); + f(NaN, NaN); + f(1/0, 1/0); + f(-(1/0), -(1/0)); + f(NaN, NaN); + } + with (o) { + f(undefined, void 0); + f(NaN, 0/0); + f(Infinity, 1/0); + f(-Infinity, -(1/0)); + f(9 + undefined, 9 + void 0); + } + } +} + From ace8aaa0f4ad6ecae77e760473353f07b384880e Mon Sep 17 00:00:00 2001 From: kzc Date: Tue, 21 Jun 2016 14:52:13 -0400 Subject: [PATCH 02/13] Fix conditional expressions of form (x ? -1 : -1) Fixes #1154, #1153 --- lib/compress.js | 2 +- test/compress/conditionals.js | 38 +++++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/lib/compress.js b/lib/compress.js index db6a26d6..4152bd06 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -2746,7 +2746,7 @@ merge(Compressor.prototype, { if (consequent.is_constant(compressor) && alternative.is_constant(compressor) && consequent.equivalent_to(alternative)) { - var consequent_value = consequent.constant_value(); + var consequent_value = consequent.constant_value(compressor); if (self.condition.has_side_effects(compressor)) { return AST_Seq.from_array([self.condition, make_node_from_constant(compressor, consequent_value, self)]); } else { diff --git a/test/compress/conditionals.js b/test/compress/conditionals.js index f5eeb6f2..35cb26f7 100644 --- a/test/compress/conditionals.js +++ b/test/compress/conditionals.js @@ -868,3 +868,41 @@ trivial_boolean_ternary_expressions : { f(!(x >= y)); } } + +issue_1154: { + options = { + conditionals: true, + evaluate : true, + booleans : true, + }; + input: { + function f1(x) { return x ? -1 : -1; } + function f2(x) { return x ? +2 : +2; } + function f3(x) { return x ? ~3 : ~3; } + function f4(x) { return x ? !4 : !4; } + function f5(x) { return x ? void 5 : void 5; } + function f6(x) { return x ? typeof 6 : typeof 6; } + + function g1() { return g() ? -1 : -1; } + function g2() { return g() ? +2 : +2; } + function g3() { return g() ? ~3 : ~3; } + function g4() { return g() ? !4 : !4; } + function g5() { return g() ? void 5 : void 5; } + function g6() { return g() ? typeof 6 : typeof 6; } + } + expect: { + function f1(x) { return -1; } + function f2(x) { return 2; } + function f3(x) { return -4; } + function f4(x) { return !1; } + function f5(x) { return; } + function f6(x) { return "number"; } + + function g1() { return g(), -1; } + function g2() { return g(), 2; } + function g3() { return g(), -4; } + function g4() { return g(), !1; } + function g5() { return g(), void 0; } + function g6() { return g(), "number"; } + } +} From 9676167aac715d2dd10918e2beb13b1a4a372616 Mon Sep 17 00:00:00 2001 From: Richard van Velzen Date: Wed, 22 Jun 2016 12:24:31 +0200 Subject: [PATCH 03/13] v2.6.4 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 423a3e5d..3ac7b52b 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "homepage": "http://lisperator.net/uglifyjs", "author": "Mihai Bazon (http://lisperator.net/)", "license": "BSD-2-Clause", - "version": "2.6.3", + "version": "2.6.4", "engines": { "node": ">=0.8.0" }, From 3a7d53f3cfa81fc8df3cd61c9adf0ce6c28ecd30 Mon Sep 17 00:00:00 2001 From: Anthony Van de Gejuchte Date: Thu, 23 Jun 2016 16:53:48 +0200 Subject: [PATCH 04/13] Move OctalEscapeSequence to read_escape_char This should simplify and improve implementation, make it easier to implement template strings, and keep master a bit more in sync with harmony. Previous implementation wasn't broken, though the loop gave me the impression it could read infinite numbers and annoyed me a bit. It was also slightly unnecessary because the lookup involved only 3 characters. --- lib/parse.js | 41 +++++++++++++++------------------ test/compress/string-literal.js | 10 ++++++++ test/mocha/string-literal.js | 2 +- 3 files changed, 30 insertions(+), 23 deletions(-) create mode 100644 test/compress/string-literal.js diff --git a/lib/parse.js b/lib/parse.js index a573234c..c7089b2d 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -365,7 +365,6 @@ function tokenizer($TEXT, filename, html5_comments, shebang) { case 98 : return "\b"; case 118 : return "\u000b"; // \v case 102 : return "\f"; - case 48 : return "\0"; case 120 : return String.fromCharCode(hex_bytes(2)); // \x case 117 : return String.fromCharCode(hex_bytes(4)); // \u case 10 : return ""; // newline @@ -375,9 +374,27 @@ function tokenizer($TEXT, filename, html5_comments, shebang) { return ""; } } + if (ch >= "0" && ch <= "7") + return read_octal_escape_sequence(ch); return ch; }; + function read_octal_escape_sequence(ch) { + // Read + var p = peek(); + if (p >= "0" && p <= "7") { + ch += next(true); + if (ch[0] <= "3" && (p = peek()) >= "0" && p <= "7") + ch += next(true); + } + + // Parse + if (ch === "0") return "\0"; + if (ch.length > 0 && next_token.has_directive("use strict")) + parse_error("SyntaxError: Octal literals are not allowed in strict mode"); + return String.fromCharCode(parseInt(ch, 8)); + } + function hex_bytes(n) { var num = 0; for (; n > 0; --n) { @@ -393,27 +410,7 @@ function tokenizer($TEXT, filename, html5_comments, shebang) { var quote = next(), ret = ""; for (;;) { var ch = next(true, true); - if (ch == "\\") { - var octal_len = 0, first = null; - ch = read_while(function(ch){ - if (ch >= "0" && ch <= "7") { - if (!first) { - first = ch; - return ++octal_len; - } - else if (first <= "3" && octal_len <= 2) return ++octal_len; - else if (first >= "4" && octal_len <= 1) return ++octal_len; - } - return false; - }); - if (octal_len > 0) { - if (ch !== "0" && next_token.has_directive("use strict")) - parse_error("Octal literals are not allowed in strict mode"); - ch = String.fromCharCode(parseInt(ch, 8)); - } else { - ch = read_escaped_char(true); - } - } + if (ch == "\\") ch = read_escaped_char(true); else if ("\r\n\u2028\u2029".indexOf(ch) >= 0) parse_error("Unterminated string constant"); else if (ch == quote) break; ret += ch; diff --git a/test/compress/string-literal.js b/test/compress/string-literal.js new file mode 100644 index 00000000..8b93961c --- /dev/null +++ b/test/compress/string-literal.js @@ -0,0 +1,10 @@ +octal_escape_sequence: { + input: { + var boundaries = "\0\7\00\07\70\77\000\077\300\377"; + var border_check = "\400\700\0000\3000"; + } + expect: { + var boundaries = "\x00\x07\x00\x07\x38\x3f\x00\x3f\xc0\xff"; + var border_check = "\x20\x30\x38\x30\x00\x30\xc0\x30"; + } +} diff --git a/test/mocha/string-literal.js b/test/mocha/string-literal.js index ea984213..d427472f 100644 --- a/test/mocha/string-literal.js +++ b/test/mocha/string-literal.js @@ -49,7 +49,7 @@ describe("String literals", function() { var error = function(e) { return e instanceof UglifyJS.JS_Parse_Error && - e.message === "Octal literals are not allowed in strict mode"; + e.message === "SyntaxError: Octal literals are not allowed in strict mode"; } for (var input in inputs) { From 335b72df03fb6593d4bac8ddcde7d1b128eb3c4e Mon Sep 17 00:00:00 2001 From: kzc Date: Thu, 23 Jun 2016 12:54:38 -0400 Subject: [PATCH 05/13] Fix spidermonkey AST (ESTree) export and import, Array holes Fixes: #1156 #1161 Also add test to exercise Uglify after spidermonkey export/import of itself. --- lib/compress.js | 2 +- lib/mozilla-ast.js | 17 ++++++++++++++++- test/mocha/spidermonkey.js | 33 +++++++++++++++++++++++++++++++++ 3 files changed, 50 insertions(+), 2 deletions(-) create mode 100644 test/mocha/spidermonkey.js diff --git a/lib/compress.js b/lib/compress.js index 4152bd06..527b8e62 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -190,7 +190,7 @@ merge(Compressor.prototype, { if ((1 / val) < 0) { return make_node(AST_UnaryPrefix, orig, { operator: "-", - expression: make_node(AST_Number, null, { value: -val }) + expression: make_node(AST_Number, orig, { value: -val }) }); } diff --git a/lib/mozilla-ast.js b/lib/mozilla-ast.js index c1b2b683..34332215 100644 --- a/lib/mozilla-ast.js +++ b/lib/mozilla-ast.js @@ -94,6 +94,15 @@ return new AST_ObjectGetter(args); } }, + ArrayExpression: function(M) { + return new AST_Array({ + start : my_start_token(M), + end : my_end_token(M), + elements : M.elements.map(function(elem){ + return elem === null ? new AST_Hole() : from_moz(elem); + }) + }); + }, ObjectExpression: function(M) { return new AST_Object({ start : my_start_token(M), @@ -206,7 +215,6 @@ map("CatchClause", AST_Catch, "param>argname, body%body"); map("ThisExpression", AST_This); - map("ArrayExpression", AST_Array, "elements@elements"); map("FunctionExpression", AST_Function, "id>name, params@argnames, body%body"); map("BinaryExpression", AST_Binary, "operator=operator, left>left, right>right"); map("LogicalExpression", AST_Binary, "operator=operator, left>left, right>right"); @@ -302,6 +310,13 @@ }; }); + def_to_moz(AST_Array, function To_Moz_ArrayExpression(M) { + return { + type: "ArrayExpression", + elements: M.elements.map(to_moz) + }; + }); + def_to_moz(AST_Object, function To_Moz_ObjectExpression(M) { return { type: "ObjectExpression", diff --git a/test/mocha/spidermonkey.js b/test/mocha/spidermonkey.js new file mode 100644 index 00000000..f507ad1f --- /dev/null +++ b/test/mocha/spidermonkey.js @@ -0,0 +1,33 @@ +var assert = require("assert"); +var exec = require("child_process").exec; + +describe("spidermonkey export/import sanity test", function() { + it("should produce a functional build when using --self with spidermonkey", function (done) { + this.timeout(20000); + + var uglifyjs = '"' + process.argv[0] + '" bin/uglifyjs'; + var command = uglifyjs + " --self -cm --wrap SpiderUglify --dump-spidermonkey-ast | " + + uglifyjs + " --spidermonkey -cm"; + + exec(command, function (err, stdout) { + if (err) throw err; + + eval(stdout); + assert.strictEqual(typeof SpiderUglify, "object"); + + var ast = SpiderUglify.parse("foo([true,,2+3]);"); + assert.strictEqual(true, ast instanceof SpiderUglify.AST_Node); + + ast.figure_out_scope(); + ast = SpiderUglify.Compressor({}).compress(ast); + assert.strictEqual(true, ast instanceof SpiderUglify.AST_Node); + + var stream = SpiderUglify.OutputStream({}); + ast.print(stream); + var code = stream.toString(); + assert.strictEqual(code, "foo([!0,,5]);"); + + done(); + }); + }); +}); From 030611b729af46d2239bd09c95f88e708660f254 Mon Sep 17 00:00:00 2001 From: iliashk Date: Sat, 25 Jun 2016 11:48:39 +0300 Subject: [PATCH 06/13] Add Node API documentation for mangling options --- README.md | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 2ab9de62..7262ef81 100644 --- a/README.md +++ b/README.md @@ -661,7 +661,8 @@ Other options: - `fromString` (default `false`) — if you pass `true` then you can pass JavaScript source code, rather than file names. -- `mangle` — pass `false` to skip mangling names. +- `mangle` (default `true`) — pass `false` to skip mangling names, or pass + an object to specify mangling options (see below). - `mangleProperties` (default `false`) — pass an object to specify custom mangle property options. @@ -680,6 +681,32 @@ Other options: - `except` - pass an array of identifiers that should be excluded from mangling + - `toplevel` — mangle names declared in the toplevel scope (disabled by + default). + + - `eval` — mangle names visible in scopes where eval or with are used + (disabled by default). + + Examples: + + ```javascript + //tst.js + var globalVar; + function funcName(firstLongName, anotherLongName) + { + var myVariable = firstLongName + anotherLongName; + } + + UglifyJS.minify("tst.js").code; + // 'function funcName(a,n){}var globalVar;' + + UglifyJS.minify("tst.js", { mangle: { except: ['firstLongName'] }}).code; + // 'function funcName(firstLongName,a){}var globalVar;' + + UglifyJS.minify("tst.js", { mangle: toplevel: true }}).code; + // 'function n(n,a){}var a;' + ``` + ##### mangleProperties options - `regex` — Pass a RegExp to only mangle certain names (maps to the `--mangle-regex` CLI arguments option) From 02c638209ee22816b1324ff0c0f47b27db1336af Mon Sep 17 00:00:00 2001 From: kzc Date: Thu, 30 Jun 2016 00:24:34 -0400 Subject: [PATCH 07/13] Enable --screw-ie8 by default. catch identifier is mangled correctly for ES5 standards-compliant JS engines by default. Unconditionally use the ie8 if/do-while workaround whether or not --screw-ie8 is enabled. To support non-standard ie8 javascript use: uglifyjs --support-ie8 --- README.md | 8 +++++--- bin/uglifyjs | 20 +++++++++++++------- lib/compress.js | 2 +- lib/output.js | 6 +++--- lib/scope.js | 4 ++-- test/compress/ascii.js | 6 ++++-- test/compress/properties.js | 3 ++- 7 files changed, 30 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 7262ef81..792767d9 100644 --- a/README.md +++ b/README.md @@ -65,9 +65,11 @@ The available options are: --in-source-map Input source map, useful if you're compressing JS that was generated from some other original code. - --screw-ie8 Pass this flag if you don't care about full - compliance with Internet Explorer 6-8 quirks - (by default UglifyJS will try to be IE-proof). + --screw-ie8 Use this flag if you don't wish to support + Internet Explorer 6-8 quirks. + By default UglifyJS will not try to be IE-proof. + --support-ie8 Use this flag to support Internet Explorer 6-8 quirks. + Note: may break standards compliant `catch` identifiers. --expr Parse a single expression, rather than a program (for parsing JSON) -p, --prefix Skip prefix for original filenames that appear diff --git a/bin/uglifyjs b/bin/uglifyjs index b7009426..30d234fd 100755 --- a/bin/uglifyjs +++ b/bin/uglifyjs @@ -10,6 +10,7 @@ var fs = require("fs"); var path = require("path"); var async = require("async"); var acorn; +var screw_ie8 = true; var ARGS = yargs .usage("$0 input1.js [input2.js ...] [options]\n\ Use a single dash to read input from the standard input.\ @@ -24,7 +25,8 @@ mangling you need to use `-c` and `-m`.\ .describe("source-map-url", "The path to the source map to be added in //# sourceMappingURL. Defaults to the value passed with --source-map.") .describe("source-map-include-sources", "Pass this flag if you want to include the content of source files in the source map as sourcesContent property.") .describe("in-source-map", "Input source map, useful if you're compressing JS that was generated from some other original code.") - .describe("screw-ie8", "Pass this flag if you don't care about full compliance with Internet Explorer 6-8 quirks (by default UglifyJS will try to be IE-proof).") + .describe("screw-ie8", "Do not support Internet Explorer 6-8 quirks. This flag is enabled by default.") + .describe("support-ie8", "Support non-standard Internet Explorer 6-8 javascript. Note: may break standards compliant `catch` identifiers.") .describe("expr", "Parse a single expression, rather than a program (for parsing JSON)") .describe("p", "Skip prefix for original filenames that appear in source maps. \ For example -p 3 will drop 3 directories from file names and ensure they are relative paths. \ @@ -105,12 +107,14 @@ You need to pass an argument to this option to specify the name that your module .string("p") .string("prefix") .string("name-cache") + .array("reserved-file") .array("pure-funcs") .boolean("expr") .boolean("source-map-include-sources") .boolean("screw-ie8") + .boolean("support-ie8") .boolean("export-all") .boolean("self") .boolean("v") @@ -230,12 +234,14 @@ if (ARGS.mangle_props == 2) { COMPRESS.properties = false; } -if (ARGS.screw_ie8) { - if (COMPRESS) COMPRESS.screw_ie8 = true; - if (MANGLE) MANGLE.screw_ie8 = true; - OUTPUT_OPTIONS.screw_ie8 = true; +if (ARGS.support_ie8 === true && ARGS.screw_ie8 !== true) { + screw_ie8 = false; } +if (COMPRESS) COMPRESS.screw_ie8 = screw_ie8; +if (MANGLE) MANGLE.screw_ie8 = screw_ie8; +OUTPUT_OPTIONS.screw_ie8 = screw_ie8; + if (ARGS.keep_fnames) { if (COMPRESS) COMPRESS.keep_fnames = true; if (MANGLE) MANGLE.keep_fnames = true; @@ -426,7 +432,7 @@ async.eachLimit(files, 1, function (file, cb) { if (SCOPE_IS_NEEDED) { time_it("scope", function(){ - TOPLEVEL.figure_out_scope({ screw_ie8: ARGS.screw_ie8, cache: TL_CACHE }); + TOPLEVEL.figure_out_scope({ screw_ie8: screw_ie8, cache: TL_CACHE }); if (ARGS.lint) { TOPLEVEL.scope_warnings(); } @@ -441,7 +447,7 @@ async.eachLimit(files, 1, function (file, cb) { if (SCOPE_IS_NEEDED) { time_it("scope", function(){ - TOPLEVEL.figure_out_scope({ screw_ie8: ARGS.screw_ie8, cache: TL_CACHE }); + TOPLEVEL.figure_out_scope({ screw_ie8: screw_ie8, cache: TL_CACHE }); if (MANGLE && !TL_CACHE) { TOPLEVEL.compute_char_frequency(MANGLE); } diff --git a/lib/compress.js b/lib/compress.js index 527b8e62..22d6bf17 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -72,7 +72,7 @@ function Compressor(options, false_by_default) { pure_getters : false, pure_funcs : null, negate_iife : !false_by_default, - screw_ie8 : false, + screw_ie8 : true, drop_console : false, angular : false, warnings : true, diff --git a/lib/output.js b/lib/output.js index 5db1356c..324f96ed 100644 --- a/lib/output.js +++ b/lib/output.js @@ -64,7 +64,7 @@ function OutputStream(options) { comments : false, shebang : true, preserve_line : false, - screw_ie8 : false, + screw_ie8 : true, preamble : null, quote_style : 0, keep_quoted_props: false @@ -861,8 +861,8 @@ function OutputStream(options) { // adds the block brackets if needed. if (!self.body) return output.force_semicolon(); - if (self.body instanceof AST_Do - && !output.option("screw_ie8")) { + if (self.body instanceof AST_Do) { + // Unconditionally use the if/do-while workaround for all browsers. // https://github.com/mishoo/UglifyJS/issues/#issue/57 IE // croaks with "syntax error" on code like this: if (foo) // do ... while(cond); else ... we need block brackets diff --git a/lib/scope.js b/lib/scope.js index d07eca12..7ae87072 100644 --- a/lib/scope.js +++ b/lib/scope.js @@ -88,7 +88,7 @@ SymbolDef.prototype = { AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){ options = defaults(options, { - screw_ie8: false, + screw_ie8: true, cache: null }); @@ -377,7 +377,7 @@ AST_Toplevel.DEFMETHOD("_default_mangler_options", function(options){ eval : false, sort : false, // Ignored. Flag retained for backwards compatibility. toplevel : false, - screw_ie8 : false, + screw_ie8 : true, keep_fnames : false }); }); diff --git a/test/compress/ascii.js b/test/compress/ascii.js index 7686ac3f..2232d263 100644 --- a/test/compress/ascii.js +++ b/test/compress/ascii.js @@ -2,6 +2,7 @@ ascii_only_true: { options = {} beautify = { ascii_only : true, + screw_ie8 : true, beautify : false, } input: { @@ -12,13 +13,14 @@ ascii_only_true: { "\x20\x21\x22\x23 ... \x7d\x7e\x7f\x80\x81 ... \xfe\xff\u0fff\uffff"; } } - expect_exact: 'function f(){return"\\x000\\x001\\x007\\08\\0"+"\\0\\x01\\x02\\x03\\x04\\x05\\x06\\x07\\b\\t\\n\\x0B\\f\\r\\x0e\\x0f"+"\\x10\\x11\\x12\\x13\\x14\\x15\\x16\\x17\\x18\\x19\\x1a\\x1b\\x1c\\x1d\\x1e\\x1f"+\' !"# ... }~\\x7f\\x80\\x81 ... \\xfe\\xff\\u0fff\\uffff\'}' + expect_exact: 'function f(){return"\\x000\\x001\\x007\\08\\0"+"\\0\\x01\\x02\\x03\\x04\\x05\\x06\\x07\\b\\t\\n\\v\\f\\r\\x0e\\x0f"+"\\x10\\x11\\x12\\x13\\x14\\x15\\x16\\x17\\x18\\x19\\x1a\\x1b\\x1c\\x1d\\x1e\\x1f"+\' !"# ... }~\\x7f\\x80\\x81 ... \\xfe\\xff\\u0fff\\uffff\'}' } ascii_only_false: { options = {} beautify = { ascii_only : false, + screw_ie8 : true, beautify : false, } input: { @@ -29,6 +31,6 @@ ascii_only_false: { "\x20\x21\x22\x23 ... \x7d\x7e\x7f\x80\x81 ... \xfe\xff\u0fff\uffff"; } } - expect_exact: 'function f(){return"\\x000\\x001\\x007\\08\\0"+"\\0\x01\x02\x03\x04\x05\x06\x07\\b\\t\\n\\x0B\\f\\r\x0e\x0f"+"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f"+\' !"# ... }~\x7f\x80\x81 ... \xfe\xff\u0fff\uffff\'}' + expect_exact: 'function f(){return"\\x000\\x001\\x007\\08\\0"+"\\0\x01\x02\x03\x04\x05\x06\x07\\b\\t\\n\\v\\f\\r\x0e\x0f"+"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f"+\' !"# ... }~\x7f\x80\x81 ... \xfe\xff\u0fff\uffff\'}' } diff --git a/test/compress/properties.js b/test/compress/properties.js index 574c5142..cf52e631 100644 --- a/test/compress/properties.js +++ b/test/compress/properties.js @@ -12,7 +12,8 @@ keep_properties: { dot_properties: { options = { - properties: true + properties: true, + screw_ie8: false }; input: { a["foo"] = "bar"; From a97690fc724a7beba77d7fde449ea56676804933 Mon Sep 17 00:00:00 2001 From: Anthony Van de Gejuchte Date: Mon, 13 Jun 2016 12:36:47 +0200 Subject: [PATCH 08/13] Various LineTerminator changes * Escaped newlines should also produce SyntaxError * Fix multiline comment parsing and add tests * Adapt makePredicate to handle \u2028 and \u2029 * Move up nlb check in regex so it's checked before any escape handling * Change error messages to conform ecma standard * Find_eol not recornizing \u2028 and \u2029 as line terminator * Remove \u180e as it is removed in unicode 6.3.0 from the category zs --- lib/parse.js | 46 +++++++++++++++------------------ lib/utils.js | 13 ++++++++-- test/mocha/comment.js | 50 ++++++++++++++++++++++++++++++++++++ test/mocha/line-endings.js | 6 ++++- test/mocha/string-literal.js | 2 +- 5 files changed, 87 insertions(+), 30 deletions(-) create mode 100644 test/mocha/comment.js diff --git a/lib/parse.js b/lib/parse.js index c7089b2d..bfbd14d5 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -107,7 +107,9 @@ var OPERATORS = makePredicate([ "||" ]); -var WHITESPACE_CHARS = makePredicate(characters(" \u00a0\n\r\t\f\u000b\u200b\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\uFEFF")); +var WHITESPACE_CHARS = makePredicate(characters(" \u00a0\n\r\t\f\u000b\u200b\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u2028\u2029\u202f\u205f\u3000\uFEFF")); + +var NEWLINE_CHARS = makePredicate(characters("\n\r\u2028\u2029")); var PUNC_BEFORE_EXPRESSION = makePredicate(characters("[{(,.;:")); @@ -234,7 +236,7 @@ function tokenizer($TEXT, filename, html5_comments, shebang) { var ch = S.text.charAt(S.pos++); if (signal_eof && !ch) throw EX_EOF; - if ("\r\n\u2028\u2029".indexOf(ch) >= 0) { + if (NEWLINE_CHARS(ch)) { S.newline_before = S.newline_before || !in_string; ++S.line; S.col = 0; @@ -261,7 +263,7 @@ function tokenizer($TEXT, filename, html5_comments, shebang) { var text = S.text; for (var i = S.pos, n = S.text.length; i < n; ++i) { var ch = text[i]; - if (ch == '\n' || ch == '\r') + if (NEWLINE_CHARS(ch)) return i; } return -1; @@ -313,8 +315,7 @@ function tokenizer($TEXT, filename, html5_comments, shebang) { }; function skip_whitespace() { - var ch; - while (WHITESPACE_CHARS(ch = peek()) || ch == "\u2028" || ch == "\u2029") + while (WHITESPACE_CHARS(peek())) next(); }; @@ -352,7 +353,7 @@ function tokenizer($TEXT, filename, html5_comments, shebang) { if (!isNaN(valid)) { return token("num", valid); } else { - parse_error("Invalid syntax: " + num); + parse_error("SyntaxError: Invalid syntax: " + num); } }; @@ -400,18 +401,18 @@ function tokenizer($TEXT, filename, html5_comments, shebang) { for (; n > 0; --n) { var digit = parseInt(next(true), 16); if (isNaN(digit)) - parse_error("Invalid hex-character pattern in string"); + parse_error("SyntaxError: Invalid hex-character pattern in string"); num = (num << 4) | digit; } return num; }; - var read_string = with_eof_error("Unterminated string constant", function(quote_char){ + var read_string = with_eof_error("SyntaxError: Unterminated string constant", function(quote_char){ var quote = next(), ret = ""; for (;;) { var ch = next(true, true); if (ch == "\\") ch = read_escaped_char(true); - else if ("\r\n\u2028\u2029".indexOf(ch) >= 0) parse_error("Unterminated string constant"); + else if (NEWLINE_CHARS(ch)) parse_error("SyntaxError: Unterminated string constant"); else if (ch == quote) break; ret += ch; } @@ -436,21 +437,14 @@ function tokenizer($TEXT, filename, html5_comments, shebang) { return next_token; }; - var skip_multiline_comment = with_eof_error("Unterminated multiline comment", function(){ + var skip_multiline_comment = with_eof_error("SyntaxError: Unterminated multiline comment", function(){ var regex_allowed = S.regex_allowed; var i = find("*/", true); - var text = S.text.substring(S.pos, i).replace(/\r\n|\r/g, '\n'); - var a = text.split("\n"), n = a.length; + var text = S.text.substring(S.pos, i).replace(/\r\n|\r|\u2028|\u2029/g, '\n'); // update stream position - S.pos = i + 2; - S.line += n - 1; - if (n > 1) S.col = a[n - 1].length; - else S.col += a[n - 1].length; - S.col += 2; - var nlb = S.newline_before = S.newline_before || text.indexOf("\n") >= 0; + forward(text.length /* doesn't count \r\n as 2 char while S.pos - i does */ + 2); S.comments_before.push(token("comment2", text, true)); S.regex_allowed = regex_allowed; - S.newline_before = nlb; return next_token; }); @@ -463,9 +457,9 @@ function tokenizer($TEXT, filename, html5_comments, shebang) { else break; } else { - if (ch != "u") parse_error("Expecting UnicodeEscapeSequence -- uXXXX"); + if (ch != "u") parse_error("SyntaxError: Expecting UnicodeEscapeSequence -- uXXXX"); ch = read_escaped_char(); - if (!is_identifier_char(ch)) parse_error("Unicode char: " + ch.charCodeAt(0) + " is not valid in identifier"); + if (!is_identifier_char(ch)) parse_error("SyntaxError: Unicode char: " + ch.charCodeAt(0) + " is not valid in identifier"); name += ch; backslash = false; } @@ -477,9 +471,11 @@ function tokenizer($TEXT, filename, html5_comments, shebang) { return name; }; - var read_regexp = with_eof_error("Unterminated regular expression", function(regexp){ + var read_regexp = with_eof_error("SyntaxError: Unterminated regular expression", function(regexp){ var prev_backslash = false, ch, in_class = false; - while ((ch = next(true))) if (prev_backslash) { + while ((ch = next(true))) if (NEWLINE_CHARS(ch)) { + parse_error("SyntaxError: Unexpected line terminator"); + } else if (prev_backslash) { regexp += "\\" + ch; prev_backslash = false; } else if (ch == "[") { @@ -492,8 +488,6 @@ function tokenizer($TEXT, filename, html5_comments, shebang) { break; } else if (ch == "\\") { prev_backslash = true; - } else if ("\r\n\u2028\u2029".indexOf(ch) >= 0) { - parse_error("Unexpected line terminator"); } else { regexp += ch; } @@ -602,7 +596,7 @@ function tokenizer($TEXT, filename, html5_comments, shebang) { } break; } - parse_error("Unexpected character '" + ch + "'"); + parse_error("SyntaxError: Unexpected character '" + ch + "'"); }; next_token.context = function(nc) { diff --git a/lib/utils.js b/lib/utils.js index 78c6dbf7..8ef61936 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -227,10 +227,19 @@ function makePredicate(words) { } cats.push([words[i]]); } + function quote(word) { + return JSON.stringify(word).replace(/[\u2028\u2029]/g, function(s) { + switch (s) { + case "\u2028": return "\\u2028"; + case "\u2029": return "\\u2029"; + } + return s; + }); + } function compareTo(arr) { - if (arr.length == 1) return f += "return str === " + JSON.stringify(arr[0]) + ";"; + if (arr.length == 1) return f += "return str === " + quote(arr[0]) + ";"; f += "switch(str){"; - for (var i = 0; i < arr.length; ++i) f += "case " + JSON.stringify(arr[i]) + ":"; + for (var i = 0; i < arr.length; ++i) f += "case " + quote(arr[i]) + ":"; f += "return true}return false;"; } // When there are more than three length categories, an outer diff --git a/test/mocha/comment.js b/test/mocha/comment.js new file mode 100644 index 00000000..69cdb3d5 --- /dev/null +++ b/test/mocha/comment.js @@ -0,0 +1,50 @@ +var assert = require("assert"); +var uglify = require("../../"); + +describe("Comment", function() { + it("Should recognize eol of single line comments", function() { + var tests = [ + "//Some comment 1\n>", + "//Some comment 2\r>", + "//Some comment 3\r\n>", + "//Some comment 4\u2028>", + "//Some comment 5\u2029>" + ]; + + var fail = function(e) { + return e instanceof uglify.JS_Parse_Error && + e.message === "SyntaxError: Unexpected token: operator (>)" && + e.line === 2 && + e.col === 0; + } + + for (var i = 0; i < tests.length; i++) { + assert.throws(function() { + uglify.parse(tests[i], {fromString: true}) + }, fail, tests[i]); + } + }); + + it("Should update the position of a multiline comment correctly", function() { + var tests = [ + "/*Some comment 1\n\n\n*/\n>\n\n\n\n\n\n", + "/*Some comment 2\r\n\r\n\r\n*/\r\n>\n\n\n\n\n\n", + "/*Some comment 3\r\r\r*/\r>\n\n\n\n\n\n", + "/*Some comment 4\u2028\u2028\u2028*/\u2028>\n\n\n\n\n\n", + "/*Some comment 5\u2029\u2029\u2029*/\u2029>\n\n\n\n\n\n" + ]; + + var fail = function(e) { + return e instanceof uglify.JS_Parse_Error && + e.message === "SyntaxError: Unexpected token: operator (>)" && + e.line === 5 && + e.col === 0; + } + + for (var i = 0; i < tests.length; i++) { + assert.throws(function() { + uglify.parse(tests[i], {fromString: true}) + }, fail, tests[i]); + } + }); +}); diff --git a/test/mocha/line-endings.js b/test/mocha/line-endings.js index 3457dd70..ef46bccd 100644 --- a/test/mocha/line-endings.js +++ b/test/mocha/line-endings.js @@ -37,6 +37,10 @@ describe("line-endings", function() { "/\r/", "/\u2028/", "/\u2029/", + "/\\\n/", + "/\\\r/", + "/\\\u2028/", + "/\\\u2029/", "/someRandomTextLike[]()*AndThen\n/" ] var test = function(input) { @@ -46,7 +50,7 @@ describe("line-endings", function() { } var fail = function(e) { return e instanceof Uglify.JS_Parse_Error && - e.message === "Unexpected line terminator"; + e.message === "SyntaxError: Unexpected line terminator"; } for (var i = 0; i < inputs.length; i++) { assert.throws(test(inputs[i]), fail); diff --git a/test/mocha/string-literal.js b/test/mocha/string-literal.js index d427472f..fc4c4277 100644 --- a/test/mocha/string-literal.js +++ b/test/mocha/string-literal.js @@ -19,7 +19,7 @@ describe("String literals", function() { var error = function(e) { return e instanceof UglifyJS.JS_Parse_Error && - e.message === "Unterminated string constant"; + e.message === "SyntaxError: Unterminated string constant"; }; for (var input in inputs) { From 85924bb32e1c27f555aad1651f4575b45bcbadb1 Mon Sep 17 00:00:00 2001 From: Geraint Date: Mon, 27 Jun 2016 12:01:21 +0100 Subject: [PATCH 09/13] Allow input files to be map (url->filename) --- test/mocha/minify-file-map.js | 40 +++++++++++++++++++++++++++++++++++ tools/node.js | 17 ++++++++++----- 2 files changed, 52 insertions(+), 5 deletions(-) create mode 100644 test/mocha/minify-file-map.js diff --git a/test/mocha/minify-file-map.js b/test/mocha/minify-file-map.js new file mode 100644 index 00000000..aa42d25a --- /dev/null +++ b/test/mocha/minify-file-map.js @@ -0,0 +1,40 @@ +var Uglify = require('../../'); +var assert = require("assert"); + +describe("Input file as map", function() { + it("Should accept object", function() { + var jsMap = { + '/scripts/foo.js': 'var foo = {"x": 1, y: 2, \'z\': 3};' + }; + var result = Uglify.minify(jsMap, {fromString: true, outSourceMap: true}); + + var map = JSON.parse(result.map); + assert.strictEqual(result.code, 'var foo={x:1,y:2,z:3};'); + assert.deepEqual(map.sources, ['/scripts/foo.js']); + }); + + it("Should accept array of objects and strings", function() { + var jsSeq = [ + {'/scripts/foo.js': 'var foo = {"x": 1, y: 2, \'z\': 3};'}, + 'var bar = 15;' + ]; + var result = Uglify.minify(jsSeq, {fromString: true, outSourceMap: true}); + + var map = JSON.parse(result.map); + assert.strictEqual(result.code, 'var foo={x:1,y:2,z:3},bar=15;'); + assert.strictEqual(map.sources[0], '/scripts/foo.js'); + }); + + it("Should correctly include source", function() { + var jsSeq = [ + {'/scripts/foo.js': 'var foo = {"x": 1, y: 2, \'z\': 3};'}, + 'var bar = 15;' + ]; + var result = Uglify.minify(jsSeq, {fromString: true, outSourceMap: true, sourceMapIncludeSources: true}); + + var map = JSON.parse(result.map); + assert.strictEqual(result.code, 'var foo={x:1,y:2,z:3},bar=15;'); + assert.deepEqual(map.sourcesContent, ['var foo = {"x": 1, y: 2, \'z\': 3};', 'var bar = 15;']); + }); + +}); diff --git a/tools/node.js b/tools/node.js index 39976371..2ee7df21 100644 --- a/tools/node.js +++ b/tools/node.js @@ -61,18 +61,25 @@ exports.minify = function(files, options) { if (options.spidermonkey) { toplevel = UglifyJS.AST_Node.from_mozilla_ast(files); } else { - if (typeof files == "string") - files = [ files ]; - files.forEach(function(file, i){ + function addFile(file, fileUrl) { var code = options.fromString ? file : fs.readFileSync(file, "utf8"); - sourcesContent[file] = code; + sourcesContent[fileUrl] = code; toplevel = UglifyJS.parse(code, { - filename: options.fromString ? i : file, + filename: fileUrl, toplevel: toplevel, bare_returns: options.parse ? options.parse.bare_returns : undefined }); + } + [].concat(files).forEach(function (files, i) { + if (typeof files === 'string') { + addFile(files, options.fromString ? i : files); + } else { + for (var fileUrl in files) { + addFile(files[fileUrl], fileUrl); + } + } }); } if (options.wrap) { From b7ef7840f3f0ab2bec09bec4d4b8a8bb8d50dc78 Mon Sep 17 00:00:00 2001 From: kzc Date: Sun, 26 Jun 2016 17:16:02 -0400 Subject: [PATCH 10/13] Allow sequences maximum length to be user configurable. --- README.md | 7 ++++++- lib/compress.js | 6 ++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 792767d9..287c4812 100644 --- a/README.md +++ b/README.md @@ -291,7 +291,12 @@ you can pass a comma-separated list of options. Options are in the form `foo=bar`, or just `foo` (the latter implies a boolean option that you want to set `true`; it's effectively a shortcut for `foo=true`). -- `sequences` -- join consecutive simple statements using the comma operator +- `sequences` (default: true) -- join consecutive simple statements using the + comma operator. May be set to a positive integer to specify the maximum number + of consecutive comma sequences that will be generated. If this option is set to + `true` then the default sequences limit is `2000`. Set option to `false` or `0` + to disable. On rare occasions the default sequences limit leads to very slow + compress times in which case a value of `20` or less is recommended. - `properties` -- rewrite property access using the dot notation, for example `foo["bar"] → foo.bar` diff --git a/lib/compress.js b/lib/compress.js index 22d6bf17..6a534c3a 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -79,6 +79,8 @@ function Compressor(options, false_by_default) { global_defs : {}, passes : 1, }, true); + var sequences = this.options["sequences"]; + this.sequences_limit = sequences == 1 ? 2000 : sequences | 0; this.warnings_produced = {}; }; @@ -266,7 +268,7 @@ merge(Compressor.prototype, { if (compressor.option("if_return")) { statements = handle_if_return(statements, compressor); } - if (compressor.option("sequences")) { + if (compressor.sequences_limit > 0) { statements = sequencesize(statements, compressor); } if (compressor.option("join_vars")) { @@ -721,7 +723,7 @@ merge(Compressor.prototype, { seq = []; }; statements.forEach(function(stat){ - if (stat instanceof AST_SimpleStatement && seqLength(seq) < 2000) { + if (stat instanceof AST_SimpleStatement && seqLength(seq) < compressor.sequences_limit) { seq.push(stat.body); } else { push_seq(); From b40d5de69c61688dad8e9f5c1e99558e0e3bdc30 Mon Sep 17 00:00:00 2001 From: kzc Date: Thu, 30 Jun 2016 14:56:12 -0400 Subject: [PATCH 11/13] Change the default sequences limit to 200 to speed up compress. Has little or no impact on minification size in the majority of cases but can speed up rollup builds significantly. This sequences change also has the beneficial side effect of avoiding "stack size exceeded" errors on very large input files. The user is free to alter the sequences limit if they are so inclined. The previous sequences limit was 2000. 20 is often sufficient. --- README.md | 2 +- lib/compress.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 287c4812..ca1c8e4a 100644 --- a/README.md +++ b/README.md @@ -294,7 +294,7 @@ to set `true`; it's effectively a shortcut for `foo=true`). - `sequences` (default: true) -- join consecutive simple statements using the comma operator. May be set to a positive integer to specify the maximum number of consecutive comma sequences that will be generated. If this option is set to - `true` then the default sequences limit is `2000`. Set option to `false` or `0` + `true` then the default sequences limit is `200`. Set option to `false` or `0` to disable. On rare occasions the default sequences limit leads to very slow compress times in which case a value of `20` or less is recommended. diff --git a/lib/compress.js b/lib/compress.js index 6a534c3a..f0f3d09b 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -80,7 +80,7 @@ function Compressor(options, false_by_default) { passes : 1, }, true); var sequences = this.options["sequences"]; - this.sequences_limit = sequences == 1 ? 2000 : sequences | 0; + this.sequences_limit = sequences == 1 ? 200 : sequences | 0; this.warnings_produced = {}; }; From 5576e2737ad95c12d0a559015ff375c785232ec6 Mon Sep 17 00:00:00 2001 From: kzc Date: Thu, 30 Jun 2016 16:54:52 -0400 Subject: [PATCH 12/13] Document that the smallest sequences optimization length is 2 and a sequences value of 1 is considered to be `true` - which will be set to the default value of 200. --- README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index ca1c8e4a..3245d404 100644 --- a/README.md +++ b/README.md @@ -294,9 +294,11 @@ to set `true`; it's effectively a shortcut for `foo=true`). - `sequences` (default: true) -- join consecutive simple statements using the comma operator. May be set to a positive integer to specify the maximum number of consecutive comma sequences that will be generated. If this option is set to - `true` then the default sequences limit is `200`. Set option to `false` or `0` - to disable. On rare occasions the default sequences limit leads to very slow - compress times in which case a value of `20` or less is recommended. + `true` then the default `sequences` limit is `200`. Set option to `false` or `0` + to disable. The smallest `sequences` length is `2`. A `sequences` value of `1` + is grandfathered to be equivalent to `true` and as such means `200`. On rare + occasions the default sequences limit leads to very slow compress times in which + case a value of `20` or less is recommended. - `properties` -- rewrite property access using the dot notation, for example `foo["bar"] → foo.bar` From debc525fa117438d4971df3790f9f476fed65858 Mon Sep 17 00:00:00 2001 From: Richard van Velzen Date: Mon, 20 Jun 2016 16:57:40 +0200 Subject: [PATCH 13/13] Introduce a test that tests the --self build --- test/mocha/cli.js | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 test/mocha/cli.js diff --git a/test/mocha/cli.js b/test/mocha/cli.js new file mode 100644 index 00000000..38b57cd7 --- /dev/null +++ b/test/mocha/cli.js @@ -0,0 +1,22 @@ +var assert = require("assert"); +var exec = require("child_process").exec; + +describe("bin/uglifyjs", function () { + it("should produce a functional build when using --self", function (done) { + this.timeout(5000); + + var uglifyjs = '"' + process.argv[0] + '" bin/uglifyjs'; + var command = uglifyjs + ' --self -cm --wrap WrappedUglifyJS'; + + exec(command, function (err, stdout) { + if (err) throw err; + + eval(stdout); + + assert.strictEqual(typeof WrappedUglifyJS, 'object'); + assert.strictEqual(true, WrappedUglifyJS.parse('foo;') instanceof WrappedUglifyJS.AST_Node); + + done(); + }); + }); +});