From 335e349314963e1761f7531656a64efaff548ceb Mon Sep 17 00:00:00 2001 From: Richard van Velzen Date: Wed, 28 Oct 2015 20:50:01 +0100 Subject: [PATCH 01/25] Allow specification beautify options in tests Caught an error in #847 as well - `output` wasn't passed anywhere which led to an exception. `options` was available though. --- lib/output.js | 2 +- test/compress/screw-ie8.js | 18 ++++++++++++++++++ test/run-tests.js | 17 +++++++++-------- 3 files changed, 28 insertions(+), 9 deletions(-) create mode 100644 test/compress/screw-ie8.js diff --git a/lib/output.js b/lib/output.js index 5d75e703..b7f69717 100644 --- a/lib/output.js +++ b/lib/output.js @@ -95,7 +95,7 @@ function OutputStream(options) { case "\f": return "\\f"; case "\n": return "\\n"; case "\r": return "\\r"; - case "\x0B": return output.option("screw_ie8") ? "\\v" : "\\x0B"; + case "\x0B": return options.screw_ie8 ? "\\v" : "\\x0B"; case "\u2028": return "\\u2028"; case "\u2029": return "\\u2029"; case '"': ++dq; return '"'; diff --git a/test/compress/screw-ie8.js b/test/compress/screw-ie8.js new file mode 100644 index 00000000..527aea04 --- /dev/null +++ b/test/compress/screw-ie8.js @@ -0,0 +1,18 @@ +do_screw: { + options = { screw_ie8: true }; + beautify = { + screw_ie8: true, + ascii_only: true + }; + + input: f("\v"); + expect_exact: 'f("\\v");'; +} + +dont_screw: { + options = { screw_ie8: false }; + beautify = { screw_ie8: false, ascii_only: true }; + + input: f("\v"); + expect_exact: 'f("\\x0B");'; +} \ No newline at end of file diff --git a/test/run-tests.js b/test/run-tests.js index 1e9eb3cb..7f6166fe 100755 --- a/test/run-tests.js +++ b/test/run-tests.js @@ -87,20 +87,21 @@ function run_compress_tests() { warnings: false }); var cmp = new U.Compressor(options, true); + var output_options = test.beautify || {}; var expect; if (test.expect) { - expect = make_code(as_toplevel(test.expect), false); + expect = make_code(as_toplevel(test.expect), output_options); } else { expect = test.expect_exact; } var input = as_toplevel(test.input); - var input_code = make_code(test.input); + var input_code = make_code(test.input, { beautify: true }); if (test.mangle_props) { input = U.mangle_properties(input, test.mangle_props); } var output = input.transform(cmp); output.figure_out_scope(); - output = make_code(output, false); + output = make_code(output, output_options); if (expect != output) { log("!!! failed\n---INPUT---\n{input}\n---OUTPUT---\n{output}\n---EXPECTED---\n{expected}\n\n", { input: input_code, @@ -144,7 +145,7 @@ function parse_test(file) { file: file, line: node.start.line, col: node.start.col, - code: make_code(node, false) + code: make_code(node, { beautify: false }) })); } @@ -191,15 +192,15 @@ function parse_test(file) { }; } -function make_code(ast, beautify) { - if (arguments.length == 1) beautify = true; - var stream = U.OutputStream({ beautify: beautify, inline_script: true }); +function make_code(ast, options) { + options.inline_script = true; + var stream = U.OutputStream(options); ast.print(stream); return stream.get(); } function evaluate(code) { if (code instanceof U.AST_Node) - code = make_code(code); + code = make_code(code, { beautify: true }); return new Function("return(" + code + ")")(); } From 7491d07666822fe943bd5f5768a5761b562c634a Mon Sep 17 00:00:00 2001 From: kzc Date: Wed, 28 Oct 2015 13:25:27 -0400 Subject: [PATCH 02/25] optimize `return undefined` and `return void 0` --- lib/compress.js | 10 ++ test/compress/return_undefined.js | 229 ++++++++++++++++++++++++++++++ 2 files changed, 239 insertions(+) create mode 100644 test/compress/return_undefined.js diff --git a/lib/compress.js b/lib/compress.js index 216aade9..ebe7e957 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -65,6 +65,7 @@ function Compressor(options, false_by_default) { keep_fnames : false, hoist_vars : false, if_return : !false_by_default, + return_void_0 : !false_by_default, join_vars : !false_by_default, cascade : !false_by_default, side_effects : !false_by_default, @@ -2519,4 +2520,13 @@ merge(Compressor.prototype, { OPT(AST_Object, literals_in_boolean_context); OPT(AST_RegExp, literals_in_boolean_context); + OPT(AST_Return, function(self, compressor){ + if (compressor.option("return_void_0")) { + if (self.value instanceof AST_Undefined) { + self.value = null; + } + } + return self; + }); + })(); diff --git a/test/compress/return_undefined.js b/test/compress/return_undefined.js new file mode 100644 index 00000000..be79d89d --- /dev/null +++ b/test/compress/return_undefined.js @@ -0,0 +1,229 @@ +return_void_0_true: { + options = { + return_void_0 : true, + sequences : false, + if_return : true, + evaluate : true, + dead_code : true, + conditionals : true, + comparisons : true, + booleans : true, + unused : true, + side_effects : true, + properties : true, + drop_debugger : true, + loops : true, + hoist_funs : true, + keep_fargs : true, + keep_fnames : false, + hoist_vars : true, + join_vars : true, + cascade : true, + negate_iife : true + }; + input: { + function f0() { + } + function f1() { + return undefined; + } + function f2() { + return void 0; + } + function f3() { + return void 123; + } + function f4() { + return; + } + function f5(a, b) { + console.log(a, b); + baz(a); + return; + } + function f6(a, b) { + console.log(a, b); + if (a) { + foo(b); + baz(a); + return a + b; + } + return undefined; + } + function f7(a, b) { + console.log(a, b); + if (a) { + foo(b); + baz(a); + return void 0; + } + return a + b; + } + function f8(a, b) { + foo(a); + bar(b); + return void 0; + } + function f9(a, b) { + foo(a); + bar(b); + return undefined; + } + } + expect: { + function f0() {} + function f1() {} + function f2() {} + function f3() {} + function f4() {} + function f5(a, b) { + console.log(a, b); + baz(a); + } + function f6(a, b) { + console.log(a, b); + if (a) { + foo(b); + baz(a); + return a + b; + } + } + function f7(a, b) { + console.log(a, b); + if (!a) + return a + b; + foo(b); + baz(a); + } + function f8(a, b) { + foo(a); + bar(b); + } + function f9(a, b) { + foo(a); + bar(b); + } + } +} + +return_void_0_false: { + options = { + return_void_0 : false, + sequences : false, + if_return : true, + evaluate : true, + dead_code : true, + conditionals : true, + comparisons : true, + booleans : true, + unused : true, + side_effects : true, + properties : true, + drop_debugger : true, + loops : true, + hoist_funs : true, + keep_fargs : true, + keep_fnames : false, + hoist_vars : true, + join_vars : true, + cascade : true, + negate_iife : true + }; + input: { + function f0() { + } + function f1() { + return undefined; + } + function f2() { + return void 0; + } + function f3() { + return void 123; + } + function f4() { + return; + } + function f5(a, b) { + console.log(a, b); + baz(a); + return; + } + function f6(a, b) { + console.log(a, b); + if (a) { + foo(b); + baz(a); + return a + b; + } + return undefined; + } + function f7(a, b) { + console.log(a, b); + if (a) { + foo(b); + baz(a); + return void 0; + } + return a + b; + } + function f8(a, b) { + foo(a); + bar(b); + return void 0; + } + function f9(a, b) { + foo(a); + bar(b); + return undefined; + } + } + expect: { + function f0() { + } + function f1() { + return void 0; + } + function f2() { + return void 0; + } + function f3() { + return void 0; + } + function f4() { + } + function f5(a, b) { + console.log(a, b); + baz(a); + } + function f6(a, b) { + console.log(a, b); + if (a) { + foo(b); + baz(a); + return a + b; + } + return void 0; + } + function f7(a, b) { + console.log(a, b); + if (a) { + foo(b); + baz(a); + return void 0; + } + return a + b; + } + function f8(a, b) { + foo(a); + bar(b); + return void 0; + } + function f9(a, b) { + foo(a); + bar(b); + return void 0; + } + } +} + From 841a661071a70d1b1c222e7c0f35fb05c6b7b041 Mon Sep 17 00:00:00 2001 From: kzc Date: Wed, 28 Oct 2015 13:41:41 -0400 Subject: [PATCH 03/25] more tests for `return undefined` optimization --- test/compress/return_undefined.js | 36 +++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/test/compress/return_undefined.js b/test/compress/return_undefined.js index be79d89d..b1195a72 100644 --- a/test/compress/return_undefined.js +++ b/test/compress/return_undefined.js @@ -69,6 +69,15 @@ return_void_0_true: { bar(b); return undefined; } + function f10() { + return false; + } + function f11() { + return null; + } + function f12() { + return 0; + } } expect: { function f0() {} @@ -103,6 +112,15 @@ return_void_0_true: { foo(a); bar(b); } + function f10() { + return !1; + } + function f11() { + return null; + } + function f12() { + return 0; + } } } @@ -177,6 +195,15 @@ return_void_0_false: { bar(b); return undefined; } + function f10() { + return false; + } + function f11() { + return null; + } + function f12() { + return 0; + } } expect: { function f0() { @@ -224,6 +251,15 @@ return_void_0_false: { bar(b); return void 0; } + function f10() { + return !1; + } + function f11() { + return null; + } + function f12() { + return 0; + } } } From bd0ae6569fd81f9f53d66b1c696e5394a0fa2bc2 Mon Sep 17 00:00:00 2001 From: kzc Date: Wed, 28 Oct 2015 17:51:46 -0400 Subject: [PATCH 04/25] `return undefined` optimization no longer uses `return_void_0` option --- lib/compress.js | 7 +- test/compress/return_undefined.js | 143 +----------------------------- 2 files changed, 3 insertions(+), 147 deletions(-) diff --git a/lib/compress.js b/lib/compress.js index ebe7e957..50353fe0 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -65,7 +65,6 @@ function Compressor(options, false_by_default) { keep_fnames : false, hoist_vars : false, if_return : !false_by_default, - return_void_0 : !false_by_default, join_vars : !false_by_default, cascade : !false_by_default, side_effects : !false_by_default, @@ -2521,10 +2520,8 @@ merge(Compressor.prototype, { OPT(AST_RegExp, literals_in_boolean_context); OPT(AST_Return, function(self, compressor){ - if (compressor.option("return_void_0")) { - if (self.value instanceof AST_Undefined) { - self.value = null; - } + if (self.value instanceof AST_Undefined) { + self.value = null; } return self; }); diff --git a/test/compress/return_undefined.js b/test/compress/return_undefined.js index b1195a72..9662aa51 100644 --- a/test/compress/return_undefined.js +++ b/test/compress/return_undefined.js @@ -1,6 +1,5 @@ -return_void_0_true: { +return_undefined: { options = { - return_void_0 : true, sequences : false, if_return : true, evaluate : true, @@ -123,143 +122,3 @@ return_void_0_true: { } } } - -return_void_0_false: { - options = { - return_void_0 : false, - sequences : false, - if_return : true, - evaluate : true, - dead_code : true, - conditionals : true, - comparisons : true, - booleans : true, - unused : true, - side_effects : true, - properties : true, - drop_debugger : true, - loops : true, - hoist_funs : true, - keep_fargs : true, - keep_fnames : false, - hoist_vars : true, - join_vars : true, - cascade : true, - negate_iife : true - }; - input: { - function f0() { - } - function f1() { - return undefined; - } - function f2() { - return void 0; - } - function f3() { - return void 123; - } - function f4() { - return; - } - function f5(a, b) { - console.log(a, b); - baz(a); - return; - } - function f6(a, b) { - console.log(a, b); - if (a) { - foo(b); - baz(a); - return a + b; - } - return undefined; - } - function f7(a, b) { - console.log(a, b); - if (a) { - foo(b); - baz(a); - return void 0; - } - return a + b; - } - function f8(a, b) { - foo(a); - bar(b); - return void 0; - } - function f9(a, b) { - foo(a); - bar(b); - return undefined; - } - function f10() { - return false; - } - function f11() { - return null; - } - function f12() { - return 0; - } - } - expect: { - function f0() { - } - function f1() { - return void 0; - } - function f2() { - return void 0; - } - function f3() { - return void 0; - } - function f4() { - } - function f5(a, b) { - console.log(a, b); - baz(a); - } - function f6(a, b) { - console.log(a, b); - if (a) { - foo(b); - baz(a); - return a + b; - } - return void 0; - } - function f7(a, b) { - console.log(a, b); - if (a) { - foo(b); - baz(a); - return void 0; - } - return a + b; - } - function f8(a, b) { - foo(a); - bar(b); - return void 0; - } - function f9(a, b) { - foo(a); - bar(b); - return void 0; - } - function f10() { - return !1; - } - function f11() { - return null; - } - function f12() { - return 0; - } - } -} - From 83db98ad3bc92797df7a1cde009893af9e3dbaf3 Mon Sep 17 00:00:00 2001 From: kzc Date: Sun, 1 Nov 2015 01:02:52 -0400 Subject: [PATCH 05/25] Fixed RegExp literal in mozilla AST generation/output and added a --dump-spidermonkey-ast flag --- bin/uglifyjs | 44 +++++++++++++++++++++++++------------------- lib/mozilla-ast.js | 15 ++++++++++++++- 2 files changed, 39 insertions(+), 20 deletions(-) diff --git a/bin/uglifyjs b/bin/uglifyjs index ca75f159..8d4fe4d9 100755 --- a/bin/uglifyjs +++ b/bin/uglifyjs @@ -73,6 +73,7 @@ You need to pass an argument to this option to specify the name that your module .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") + .describe("dump-spidermonkey-ast", "Dump SpiderMonkey AST to stdout.") .alias("p", "prefix") .alias("o", "output") @@ -117,6 +118,7 @@ You need to pass an argument to this option to specify the name that your module .boolean("stats") .boolean("acorn") .boolean("spidermonkey") + .boolean("dump-spidermonkey-ast") .boolean("lint") .boolean("V") .boolean("version") @@ -444,26 +446,30 @@ async.eachLimit(files, 1, function (file, cb) { } } - time_it("generate", function(){ - TOPLEVEL.print(output); - }); - - output = output.get(); - - if (SOURCE_MAP) { - fs.writeFileSync(ARGS.source_map, SOURCE_MAP, "utf8"); - var source_map_url = ARGS.source_map_url || ( - P_RELATIVE - ? path.relative(path.dirname(OUTPUT_FILE), ARGS.source_map) - : ARGS.source_map - ); - output += "\n//# sourceMappingURL=" + source_map_url; - } - - if (OUTPUT_FILE) { - fs.writeFileSync(OUTPUT_FILE, output, "utf8"); + if (ARGS.dump_spidermonkey_ast) { + print(JSON.stringify(TOPLEVEL.to_mozilla_ast(), null, 2)); } else { - print(output); + time_it("generate", function(){ + TOPLEVEL.print(output); + }); + + output = output.get(); + + if (SOURCE_MAP) { + fs.writeFileSync(ARGS.source_map, SOURCE_MAP, "utf8"); + var source_map_url = ARGS.source_map_url || ( + P_RELATIVE + ? path.relative(path.dirname(OUTPUT_FILE), ARGS.source_map) + : ARGS.source_map + ); + output += "\n//# sourceMappingURL=" + source_map_url; + } + + if (OUTPUT_FILE) { + fs.writeFileSync(OUTPUT_FILE, output, "utf8"); + } else { + print(output); + } } if (ARGS.stats) { diff --git a/lib/mozilla-ast.js b/lib/mozilla-ast.js index ac53ca27..0a439a59 100644 --- a/lib/mozilla-ast.js +++ b/lib/mozilla-ast.js @@ -146,7 +146,7 @@ case "boolean": return new (val ? AST_True : AST_False)(args); default: - args.value = val; + args.value = M.regex && M.raw ? M.raw : val; return new AST_RegExp(args); } }, @@ -334,6 +334,19 @@ }; }); + def_to_moz(AST_RegExp, function To_Moz_Literal(M) { + var value = M.value; + return { + type: "Literal", + value: value, + raw: value.toString(), + regex: { + pattern: value.source, + flags: value.toString().match(/[gimuy]*$/)[0] + } + }; + }); + def_to_moz(AST_Constant, function To_Moz_Literal(M) { var value = M.value; if (typeof value === 'number' && (value < 0 || (value === 0 && 1 / value < 0))) { From 37ee9de9021f1f34ed7d2f453f58348f2e74764f Mon Sep 17 00:00:00 2001 From: kzc Date: Sun, 1 Nov 2015 10:20:42 -0500 Subject: [PATCH 06/25] rename To_Moz_Literal to To_Moz_RegExp --- lib/mozilla-ast.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mozilla-ast.js b/lib/mozilla-ast.js index 0a439a59..15be4581 100644 --- a/lib/mozilla-ast.js +++ b/lib/mozilla-ast.js @@ -334,7 +334,7 @@ }; }); - def_to_moz(AST_RegExp, function To_Moz_Literal(M) { + def_to_moz(AST_RegExp, function To_Moz_RegExp(M) { var value = M.value; return { type: "Literal", From 94c4daaf9ea67875ab1b720d1d7bceef7c690552 Mon Sep 17 00:00:00 2001 From: kzc Date: Mon, 2 Nov 2015 12:24:09 -0500 Subject: [PATCH 07/25] Have mozilla AST RegExpLiteral parser use regex.pattern and regex.flags rather than non-standard `raw` property. --- lib/mozilla-ast.js | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/lib/mozilla-ast.js b/lib/mozilla-ast.js index 15be4581..7ce6e78d 100644 --- a/lib/mozilla-ast.js +++ b/lib/mozilla-ast.js @@ -146,7 +146,17 @@ case "boolean": return new (val ? AST_True : AST_False)(args); default: - args.value = M.regex && M.raw ? M.raw : val; + var rx = M.regex; + if (rx && rx.pattern) { + // RegExpLiteral as per ESTree AST spec + args.value = "/" + rx.pattern + "/"; + if (rx.flags) { + args.value += rx.flags; + } + } else { + // support legacy RegExp + args.value = M.regex && M.raw ? M.raw : val; + } return new AST_RegExp(args); } }, @@ -334,7 +344,7 @@ }; }); - def_to_moz(AST_RegExp, function To_Moz_RegExp(M) { + def_to_moz(AST_RegExp, function To_Moz_RegExpLiteral(M) { var value = M.value; return { type: "Literal", From 7dbe961b2d49533d54dfe5263f94de27de043316 Mon Sep 17 00:00:00 2001 From: kzc Date: Mon, 2 Nov 2015 13:10:37 -0500 Subject: [PATCH 08/25] simplify mozilla AST RegExpLiteral token parse and handle corner cases of regex.pattern better --- lib/mozilla-ast.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/lib/mozilla-ast.js b/lib/mozilla-ast.js index 7ce6e78d..2bb469fb 100644 --- a/lib/mozilla-ast.js +++ b/lib/mozilla-ast.js @@ -149,10 +149,7 @@ var rx = M.regex; if (rx && rx.pattern) { // RegExpLiteral as per ESTree AST spec - args.value = "/" + rx.pattern + "/"; - if (rx.flags) { - args.value += rx.flags; - } + args.value = new RegExp(rx.pattern, rx.flags).toString(); } else { // support legacy RegExp args.value = M.regex && M.raw ? M.raw : val; From 63d35f8f6db6d90d6142132d2d5f0bd5d3d698aa Mon Sep 17 00:00:00 2001 From: Richard van Velzen Date: Mon, 9 Nov 2015 11:28:27 +0100 Subject: [PATCH 09/25] Prevent ReDoS by not using a regexp to verify floating point numbers `parseFloat` will return `NaN` for invalid numbers anyway, which is the check used to throw the parse error. Fixes #857 --- lib/parse.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/parse.js b/lib/parse.js index 1ab03589..4c548a26 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -59,7 +59,6 @@ var OPERATOR_CHARS = makePredicate(characters("+-*&%=<>!?|~^")); var RE_HEX_NUMBER = /^0x[0-9a-f]+$/i; var RE_OCT_NUMBER = /^0[0-7]+$/; -var RE_DEC_NUMBER = /^\d*\.?\d*(?:e[+-]?\d*(?:\d\.?|\.?\d)\d*)?$/i; var OPERATORS = makePredicate([ "in", @@ -182,7 +181,7 @@ function parse_js_number(num) { return parseInt(num.substr(2), 16); } else if (RE_OCT_NUMBER.test(num)) { return parseInt(num.substr(1), 8); - } else if (RE_DEC_NUMBER.test(num)) { + } else { return parseFloat(num); } }; From 18d37ac761d8f9bb3904d447ddbb0ac471fc161c Mon Sep 17 00:00:00 2001 From: Mihai Bazon Date: Mon, 9 Nov 2015 13:15:20 +0200 Subject: [PATCH 10/25] Fix parsing invalid input MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit i.e. `x = 1.xe` — because parseFloat("1.xe") returns 1, this parsed as `x = 1`. Ref #857 --- lib/parse.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/parse.js b/lib/parse.js index 4c548a26..cb35118a 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -182,7 +182,8 @@ function parse_js_number(num) { } else if (RE_OCT_NUMBER.test(num)) { return parseInt(num.substr(1), 8); } else { - return parseFloat(num); + var val = parseFloat(num); + if (val == num) return val; } }; From 7691bebea525e96cb74d52e0bb8f294cf778c966 Mon Sep 17 00:00:00 2001 From: Mihai Bazon Date: Wed, 11 Nov 2015 22:15:25 +0200 Subject: [PATCH 11/25] Rework has_directive It's now available during tree walking, i.e. walker.has_directive("use asm"), rather than as part of the scope. It's thus no longer necessary to call `figure_out_scope` before codegen. Added special bits in the code generator to overcome the fact that it doesn't inherit from TreeWalker. Fix #861 --- bin/uglifyjs | 29 +++++++++++++++++------------ lib/ast.js | 30 ++++++++++++++++++++++++------ lib/compress.js | 2 +- lib/output.js | 16 +++++++++++----- lib/parse.js | 11 +++++------ lib/scope.js | 19 ------------------- lib/transform.js | 2 +- tools/node.js | 10 +--------- 8 files changed, 60 insertions(+), 59 deletions(-) diff --git a/bin/uglifyjs b/bin/uglifyjs index 8d4fe4d9..f7f22215 100755 --- a/bin/uglifyjs +++ b/bin/uglifyjs @@ -409,14 +409,17 @@ async.eachLimit(files, 1, function (file, cb) { writeNameCache("props", cache); })(); + var SCOPE_IS_NEEDED = COMPRESS || MANGLE || ARGS.lint var TL_CACHE = readNameCache("vars"); - time_it("scope", function(){ - TOPLEVEL.figure_out_scope({ screw_ie8: ARGS.screw_ie8, cache: TL_CACHE }); - if (ARGS.lint) { - TOPLEVEL.scope_warnings(); - } - }); + if (SCOPE_IS_NEEDED) { + time_it("scope", function(){ + TOPLEVEL.figure_out_scope({ screw_ie8: ARGS.screw_ie8, cache: TL_CACHE }); + if (ARGS.lint) { + TOPLEVEL.scope_warnings(); + } + }); + } if (COMPRESS) { time_it("squeeze", function(){ @@ -424,12 +427,14 @@ async.eachLimit(files, 1, function (file, cb) { }); } - time_it("scope", function(){ - TOPLEVEL.figure_out_scope({ screw_ie8: ARGS.screw_ie8, cache: TL_CACHE }); - if (MANGLE && !TL_CACHE) { - TOPLEVEL.compute_char_frequency(MANGLE); - } - }); + if (SCOPE_IS_NEEDED) { + time_it("scope", function(){ + TOPLEVEL.figure_out_scope({ screw_ie8: ARGS.screw_ie8, cache: TL_CACHE }); + if (MANGLE && !TL_CACHE) { + TOPLEVEL.compute_char_frequency(MANGLE); + } + }); + } if (MANGLE) time_it("mangle", function(){ MANGLE.cache = TL_CACHE; diff --git a/lib/ast.js b/lib/ast.js index e7952847..f5225d78 100644 --- a/lib/ast.js +++ b/lib/ast.js @@ -85,7 +85,7 @@ function DEFNODE(type, props, methods, base) { return ctor; }; -var AST_Token = DEFNODE("Token", "type value line col pos endline endcol endpos nlb comments_before file", { +var AST_Token = DEFNODE("Token", "type value line col pos endline endcol endpos nlb comments_before file literal", { }, null); var AST_Node = DEFNODE("Node", "start end", { @@ -927,27 +927,36 @@ var AST_True = DEFNODE("True", null, { function TreeWalker(callback) { this.visit = callback; this.stack = []; + this.directives = Object.create(null); }; TreeWalker.prototype = { _visit: function(node, descend) { - this.stack.push(node); + this.push(node); var ret = this.visit(node, descend ? function(){ descend.call(node); } : noop); if (!ret && descend) { descend.call(node); } - this.stack.pop(); + this.pop(node); return ret; }, parent: function(n) { return this.stack[this.stack.length - 2 - (n || 0)]; }, push: function (node) { + if (node instanceof AST_Lambda) { + this.directives = Object.create(this.directives); + } else if (node instanceof AST_Directive) { + this.directives[node.value] = this.directives[node.value] ? "up" : true; + } this.stack.push(node); }, - pop: function() { - return this.stack.pop(); + pop: function(node) { + this.stack.pop(); + if (node instanceof AST_Lambda) { + this.directives = Object.getPrototypeOf(this.directives); + } }, self: function() { return this.stack[this.stack.length - 1]; @@ -960,7 +969,16 @@ TreeWalker.prototype = { } }, has_directive: function(type) { - return this.find_parent(AST_Scope).has_directive(type); + var dir = this.directives[type]; + if (dir) return dir; + var node = this.stack[this.stack.length - 1]; + if (node instanceof AST_Scope) { + for (var i = 0; i < node.body.length; ++i) { + var st = node.body[i]; + if (!(st instanceof AST_Directive)) break; + if (st.value == type) return true; + } + } }, in_boolean_context: function() { var stack = this.stack; diff --git a/lib/compress.js b/lib/compress.js index 50353fe0..3efca411 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -991,7 +991,7 @@ merge(Compressor.prototype, { /* -----[ optimizers ]----- */ OPT(AST_Directive, function(self, compressor){ - if (self.scope.has_directive(self.value) !== self.scope) { + if (compressor.has_directive(self.value) === "up") { return make_node(AST_EmptyStatement, self); } return self; diff --git a/lib/output.js b/lib/output.js index b7f69717..9dadf0e5 100644 --- a/lib/output.js +++ b/lib/output.js @@ -382,8 +382,13 @@ function OutputStream(options) { nodetype.DEFMETHOD("_codegen", generator); }; + var use_asm = false; + AST_Node.DEFMETHOD("print", function(stream, force_parens){ - var self = this, generator = self._codegen; + var self = this, generator = self._codegen, prev_use_asm = use_asm; + if (self instanceof AST_Directive && self.value == "use asm") { + use_asm = true; + } function doit() { self.add_comments(stream); self.add_source_map(stream); @@ -396,6 +401,9 @@ function OutputStream(options) { doit(); } stream.pop_node(); + if (self instanceof AST_Lambda) { + use_asm = prev_use_asm; + } }); AST_Node.DEFMETHOD("print_to_string", function(options){ @@ -1170,10 +1178,8 @@ function OutputStream(options) { output.print_string(self.getValue(), self.quote); }); DEFPRINT(AST_Number, function(self, output){ - if (self.literal !== undefined - && +self.literal === self.value /* paranoid check */ - && self.scope && self.scope.has_directive('use asm')) { - output.print(self.literal); + if (use_asm) { + output.print(self.start.literal); } else { output.print(make_num(self.getValue())); } diff --git a/lib/parse.js b/lib/parse.js index cb35118a..901d10a3 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -285,6 +285,9 @@ function tokenizer($TEXT, filename, html5_comments, shebang) { nlb : S.newline_before, file : filename }; + if (/^(?:num|string|regexp)$/i.test(type)) { + ret.literal = $TEXT.substring(ret.pos, ret.endpos); + } if (!is_comment) { ret.comments_before = S.comments_before; S.comments_before = []; @@ -335,11 +338,7 @@ function tokenizer($TEXT, filename, html5_comments, shebang) { if (prefix) num = prefix + num; var valid = parse_js_number(num); if (!isNaN(valid)) { - var tok = token("num", valid); - if (num.indexOf('.') >= 0) { - tok.literal = num; - } - return tok; + return token("num", valid); } else { parse_error("Invalid syntax: " + num); } @@ -1152,7 +1151,7 @@ function parse($TEXT, options) { ret = _make_symbol(AST_SymbolRef); break; case "num": - ret = new AST_Number({ start: tok, end: tok, value: tok.value, literal: tok.literal }); + ret = new AST_Number({ start: tok, end: tok, value: tok.value }); break; case "string": ret = new AST_String({ diff --git a/lib/scope.js b/lib/scope.js index 06bd65ae..4a3739c4 100644 --- a/lib/scope.js +++ b/lib/scope.js @@ -114,15 +114,6 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){ defun = save_defun; return true; // don't descend again in TreeWalker } - if (node instanceof AST_Directive) { - node.scope = scope; - push_uniq(scope.directives, node.value); - return true; - } - if (node instanceof AST_Number) { - node.scope = scope; - return true; - } if (node instanceof AST_With) { for (var s = scope; s; s = s.parent_scope) s.uses_with = true; @@ -202,7 +193,6 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){ }); AST_Scope.DEFMETHOD("init_scope_vars", function(nesting){ - this.directives = []; // contains the directives defined in this scope, i.e. "use strict" this.variables = new Dictionary(); // map name to AST_SymbolVar (variables defined in this scope; includes functions) this.functions = new Dictionary(); // map name to AST_SymbolDefun (functions defined in this scope) this.uses_with = false; // will be set to true if this or some nested scope uses the `with` statement @@ -213,10 +203,6 @@ AST_Scope.DEFMETHOD("init_scope_vars", function(nesting){ this.nesting = nesting; // the nesting level of this scope (0 means toplevel) }); -AST_Scope.DEFMETHOD("strict", function(){ - return this.has_directive("use strict"); -}); - AST_Lambda.DEFMETHOD("init_scope_vars", function(){ AST_Scope.prototype.init_scope_vars.apply(this, arguments); this.uses_arguments = false; @@ -240,11 +226,6 @@ AST_Scope.DEFMETHOD("find_variable", function(name){ || (this.parent_scope && this.parent_scope.find_variable(name)); }); -AST_Scope.DEFMETHOD("has_directive", function(value){ - return this.parent_scope && this.parent_scope.has_directive(value) - || (this.directives.indexOf(value) >= 0 ? this : null); -}); - AST_Scope.DEFMETHOD("def_function", function(symbol){ this.functions.set(symbol.name, this.def_variable(symbol)); }); diff --git a/lib/transform.js b/lib/transform.js index c3c34f58..62e6e02b 100644 --- a/lib/transform.js +++ b/lib/transform.js @@ -70,7 +70,7 @@ TreeTransformer.prototype = new TreeWalker; if (y !== undefined) x = y; } } - tw.pop(); + tw.pop(this); return x; }); }; diff --git a/tools/node.js b/tools/node.js index 7e61d2a1..f6048661 100644 --- a/tools/node.js +++ b/tools/node.js @@ -45,7 +45,6 @@ exports.minify = function(files, options) { UglifyJS.base54.reset(); // 1. parse - var haveScope = false; var toplevel = null, sourcesContent = {}; @@ -74,7 +73,6 @@ exports.minify = function(files, options) { var compress = { warnings: options.warnings }; UglifyJS.merge(compress, options.compress); toplevel.figure_out_scope(); - haveScope = true; var sq = UglifyJS.Compressor(compress); toplevel = toplevel.transform(sq); } @@ -82,17 +80,11 @@ exports.minify = function(files, options) { // 3. mangle if (options.mangle) { toplevel.figure_out_scope(options.mangle); - haveScope = true; toplevel.compute_char_frequency(options.mangle); toplevel.mangle_names(options.mangle); } - // 4. scope (if needed) - if (!haveScope) { - toplevel.figure_out_scope(); - } - - // 5. output + // 4. output var inMap = options.inSourceMap; var output = {}; if (typeof options.inSourceMap == "string") { From 619adb0308dbc794e9c3b77185e886443e03df9e Mon Sep 17 00:00:00 2001 From: Mihai Bazon Date: Thu, 12 Nov 2015 11:47:37 +0200 Subject: [PATCH 12/25] Replace util.error with console.log --- test/run-tests.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/test/run-tests.js b/test/run-tests.js index 7f6166fe..3ec04fda 100755 --- a/test/run-tests.js +++ b/test/run-tests.js @@ -4,7 +4,6 @@ var U = require("../tools/node"); var path = require("path"); var fs = require("fs"); var assert = require("assert"); -var sys = require("util"); var tests_dir = path.dirname(module.filename); var failures = 0; @@ -12,8 +11,8 @@ var failed_files = {}; run_compress_tests(); if (failures) { - sys.error("\n!!! Failed " + failures + " test cases."); - sys.error("!!! " + Object.keys(failed_files).join(", ")); + console.error("\n!!! Failed " + failures + " test cases."); + console.error("!!! " + Object.keys(failed_files).join(", ")); process.exit(1); } From c898a26117c2687d5707a4e80d6058d5c8601165 Mon Sep 17 00:00:00 2001 From: Mihai Bazon Date: Thu, 12 Nov 2015 11:48:06 +0200 Subject: [PATCH 13/25] Build label def/refs info when figuring out scope Fix #862 --- lib/scope.js | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/lib/scope.js b/lib/scope.js index 4a3739c4..1f0986c4 100644 --- a/lib/scope.js +++ b/lib/scope.js @@ -92,6 +92,7 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){ // pass 1: setup scope chaining and handle definitions var self = this; var scope = self.parent_scope = null; + var labels = new Dictionary(); var defun = null; var nesting = 0; var tw = new TreeWalker(function(node, descend){ @@ -108,12 +109,25 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){ node.init_scope_vars(nesting); var save_scope = node.parent_scope = scope; var save_defun = defun; + var save_labels = labels; defun = scope = node; + labels = new Dictionary(); ++nesting; descend(); --nesting; scope = save_scope; defun = save_defun; + labels = save_labels; return true; // don't descend again in TreeWalker } + if (node instanceof AST_LabeledStatement) { + var l = node.label; + if (labels.has(l.name)) { + throw new Error(string_template("Label {name} defined twice", l)); + } + labels.set(l.name, l); + descend(); + labels.del(l.name); + return true; // no descend again + } if (node instanceof AST_With) { for (var s = scope; s; s = s.parent_scope) s.uses_with = true; @@ -122,6 +136,10 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){ if (node instanceof AST_Symbol) { node.scope = scope; } + if (node instanceof AST_Label) { + node.thedef = node; + node.references = []; + } if (node instanceof AST_SymbolLambda) { defun.def_function(node); } @@ -143,6 +161,15 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){ (options.screw_ie8 ? scope : defun) .def_variable(node); } + else if (node instanceof AST_LabelRef) { + var sym = labels.get(node.name); + if (!sym) throw new Error(string_template("Undefined label {name} [{line},{col}]", { + name: node.name, + line: node.start.line, + col: node.start.col + })); + node.thedef = sym; + } }); self.walk(tw); @@ -157,6 +184,10 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){ func = prev_func; return true; } + if (node instanceof AST_LoopControl && node.label) { + node.label.thedef.references.push(node); + return true; + } if (node instanceof AST_SymbolRef) { var name = node.name; var sym = node.scope.find_variable(name); From 08623aa6a700f62e6b096917d31b0b7a2705d281 Mon Sep 17 00:00:00 2001 From: Mihai Bazon Date: Thu, 12 Nov 2015 12:18:25 +0200 Subject: [PATCH 14/25] Fix output for "use asm" code from SpiderMonkey AST (will only work properly if the SM tree contains "raw" properties for Literal number nodes) --- lib/ast.js | 2 +- lib/mozilla-ast.js | 18 ++++++++++++++---- lib/output.js | 4 ++-- lib/parse.js | 2 +- 4 files changed, 18 insertions(+), 8 deletions(-) diff --git a/lib/ast.js b/lib/ast.js index f5225d78..0ac14dc9 100644 --- a/lib/ast.js +++ b/lib/ast.js @@ -85,7 +85,7 @@ function DEFNODE(type, props, methods, base) { return ctor; }; -var AST_Token = DEFNODE("Token", "type value line col pos endline endcol endpos nlb comments_before file literal", { +var AST_Token = DEFNODE("Token", "type value line col pos endline endcol endpos nlb comments_before file raw", { }, null); var AST_Node = DEFNODE("Node", "start end", { diff --git a/lib/mozilla-ast.js b/lib/mozilla-ast.js index 2bb469fb..c1b2b683 100644 --- a/lib/mozilla-ast.js +++ b/lib/mozilla-ast.js @@ -363,13 +363,15 @@ prefix: true, argument: { type: "Literal", - value: -value + value: -value, + raw: M.start.raw } }; } return { type: "Literal", - value: value + value: value, + raw: M.start.raw }; }); @@ -389,6 +391,12 @@ /* -----[ tools ]----- */ + function raw_token(moznode) { + if (moznode.type == "Literal") { + return moznode.raw != null ? moznode.raw : moznode.value + ""; + } + } + function my_start_token(moznode) { var loc = moznode.loc, start = loc && loc.start; var range = moznode.range; @@ -399,7 +407,8 @@ pos : range ? range[0] : moznode.start, endline : start && start.line, endcol : start && start.column, - endpos : range ? range[0] : moznode.start + endpos : range ? range[0] : moznode.start, + raw : raw_token(moznode), }); }; @@ -413,7 +422,8 @@ pos : range ? range[1] : moznode.end, endline : end && end.line, endcol : end && end.column, - endpos : range ? range[1] : moznode.end + endpos : range ? range[1] : moznode.end, + raw : raw_token(moznode), }); }; diff --git a/lib/output.js b/lib/output.js index 9dadf0e5..f10c918a 100644 --- a/lib/output.js +++ b/lib/output.js @@ -1178,8 +1178,8 @@ function OutputStream(options) { output.print_string(self.getValue(), self.quote); }); DEFPRINT(AST_Number, function(self, output){ - if (use_asm) { - output.print(self.start.literal); + if (use_asm && self.start.raw != null) { + output.print(self.start.raw); } else { output.print(make_num(self.getValue())); } diff --git a/lib/parse.js b/lib/parse.js index 901d10a3..de27d987 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -286,7 +286,7 @@ function tokenizer($TEXT, filename, html5_comments, shebang) { file : filename }; if (/^(?:num|string|regexp)$/i.test(type)) { - ret.literal = $TEXT.substring(ret.pos, ret.endpos); + ret.raw = $TEXT.substring(ret.pos, ret.endpos); } if (!is_comment) { ret.comments_before = S.comments_before; From d895c09c70e66a20fe4fd3a923e6b672437030b9 Mon Sep 17 00:00:00 2001 From: Mihai Bazon Date: Thu, 12 Nov 2015 12:46:28 +0200 Subject: [PATCH 15/25] v2.6.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 953ab1a5..1f74fc29 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.5.0", + "version": "2.6.0", "engines": { "node": ">=0.8.0" }, From 645626ebe8bcf578d4c34911b077360f2481040b Mon Sep 17 00:00:00 2001 From: plievone Date: Sat, 14 Nov 2015 11:38:00 +0200 Subject: [PATCH 16/25] Fix docs for keep_fargs Compression options `keep_fargs` and `unsafe` were decoupled in v.2.5.0 (commit 5fd1245), so document actual keep_fargs default. --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index c2434547..bd0d7c48 100644 --- a/README.md +++ b/README.md @@ -348,7 +348,7 @@ to set `true`; it's effectively a shortcut for `foo=true`). - `drop_console` -- default `false`. Pass `true` to discard calls to `console.*` functions. -- `keep_fargs` -- default `false`. Pass `true` to prevent the +- `keep_fargs` -- default `true`. Prevents the compressor from discarding unused function arguments. You need this for code which relies on `Function.length`. @@ -372,7 +372,6 @@ when this flag is on: - `void 0` → `undefined` (if there is a variable named "undefined" in scope; we do it because the variable name will be mangled, typically reduced to a single character) -- discards unused function arguments (affects `function.length`) ### Conditional compilation From b6968b6bd2a740281e03e804ae68b3f5e63ce5ed Mon Sep 17 00:00:00 2001 From: Mihai Bazon Date: Mon, 16 Nov 2015 12:06:12 +0200 Subject: [PATCH 17/25] Limit max iterations for tighten_body Ref #866 --- lib/compress.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/compress.js b/lib/compress.js index 3efca411..c384e997 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -199,7 +199,7 @@ merge(Compressor.prototype, { }; function tighten_body(statements, compressor) { - var CHANGED; + var CHANGED, max_iter = 10; do { CHANGED = false; if (compressor.option("angular")) { @@ -218,7 +218,7 @@ merge(Compressor.prototype, { if (compressor.option("join_vars")) { statements = join_consecutive_vars(statements, compressor); } - } while (CHANGED); + } while (CHANGED && max_iter-- > 0); if (compressor.option("negate_iife")) { negate_iifes(statements, compressor); From 7f48d5b33cc2a130afead79cd09482cc833d17bb Mon Sep 17 00:00:00 2001 From: Mihai Bazon Date: Mon, 16 Nov 2015 12:07:15 +0200 Subject: [PATCH 18/25] Fix endless loop Close #866 --- lib/compress.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/compress.js b/lib/compress.js index c384e997..32833ebf 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -384,7 +384,12 @@ merge(Compressor.prototype, { continue loop; } //--- - if (ret.length == 1 && in_lambda && ret[0] instanceof AST_SimpleStatement + // XXX: what was the intention of this case? + // if sequences is not enabled, this can lead to an endless loop (issue #866). + // however, with sequences on this helps producing slightly better output for + // the example code. + if (compressor.option("sequences") + && ret.length == 1 && in_lambda && ret[0] instanceof AST_SimpleStatement && (!stat.alternative || stat.alternative instanceof AST_SimpleStatement)) { CHANGED = true; ret.push(make_node(AST_Return, ret[0], { From 15b5f70338695c435cab05b7ac2de29cad230360 Mon Sep 17 00:00:00 2001 From: Mihai Bazon Date: Mon, 16 Nov 2015 12:10:47 +0200 Subject: [PATCH 19/25] v2.6.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 1f74fc29..6b0d2f40 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.0", + "version": "2.6.1", "engines": { "node": ">=0.8.0" }, From 774bda13cdf8de39de7f236ed95ae3da4fc7d822 Mon Sep 17 00:00:00 2001 From: kzc Date: Tue, 24 Nov 2015 13:27:50 -0500 Subject: [PATCH 20/25] #873 Fix `conditionals` optimizations with default compress options --- lib/compress.js | 66 +++++++++-- test/compress/conditionals.js | 210 ++++++++++++++++++++++++++++++++-- 2 files changed, 256 insertions(+), 20 deletions(-) diff --git a/lib/compress.js b/lib/compress.js index 32833ebf..44e19799 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -726,6 +726,32 @@ merge(Compressor.prototype, { return [ this ]; } }); + AST_Node.DEFMETHOD("is_constant", function(compressor){ + // Accomodate when compress option evaluate=false + // as well as the common constant expressions !0 and !1 + return this instanceof AST_Constant + || (this instanceof AST_UnaryPrefix && this.operator == "!" + && this.expression instanceof AST_Constant) + || this.evaluate(compressor).length > 1; + }); + // Obtain the constant value of an expression already known to be constant. + // Result only valid iff this.is_constant(compressor) is true. + AST_Node.DEFMETHOD("constant_value", function(compressor){ + // Accomodate when option evaluate=false. + if (this instanceof AST_Constant) return this.value; + // Accomodate the common constant expressions !0 and !1 when option evaluate=false. + if (this instanceof AST_UnaryPrefix + && this.operator == "!" + && this.expression instanceof AST_Constant) { + return !this.expression.value; + } + var result = this.evaluate(compressor) + if (result.length > 1) { + return result[1]; + } + // should never be reached + return undefined; + }); def(AST_Statement, function(){ throw new Error(string_template("Cannot evaluate a statement [{file}:{line},{col}]", this.start)); }); @@ -2427,32 +2453,48 @@ merge(Compressor.prototype, { alternative: alternative }); } - // x=y?1:1 --> x=1 - if (consequent instanceof AST_Constant - && alternative instanceof AST_Constant + // y?1:1 --> 1 + if (consequent.is_constant(compressor) + && alternative.is_constant(compressor) && consequent.equivalent_to(alternative)) { + var consequent_value = consequent.constant_value(); if (self.condition.has_side_effects(compressor)) { - return AST_Seq.from_array([self.condition, make_node_from_constant(compressor, consequent.value, self)]); + return AST_Seq.from_array([self.condition, make_node_from_constant(compressor, consequent_value, self)]); } else { - return make_node_from_constant(compressor, consequent.value, self); - + return make_node_from_constant(compressor, consequent_value, self); } } - // x=y?true:false --> x=!!y - if (consequent instanceof AST_True - && alternative instanceof AST_False) { + + // y?true:false --> !!y + if (is_true(consequent) && is_false(alternative)) { self.condition = self.condition.negate(compressor); return make_node(AST_UnaryPrefix, self.condition, { operator: "!", expression: self.condition }); } - // x=y?false:true --> x=!y - if (consequent instanceof AST_False - && alternative instanceof AST_True) { + // y?false:true --> !y + if (is_false(consequent) && is_true(alternative)) { return self.condition.negate(compressor) } return self; + + // AST_True or !0 + function is_true(node) { + return node instanceof AST_True + || (node instanceof AST_UnaryPrefix + && node.operator == "!" + && node.expression instanceof AST_Constant + && !node.expression.value); + } + // AST_False or !1 + function is_false(node) { + return node instanceof AST_False + || (node instanceof AST_UnaryPrefix + && node.operator == "!" + && node.expression instanceof AST_Constant + && !!node.expression.value); + } }); OPT(AST_Boolean, function(self, compressor){ diff --git a/test/compress/conditionals.js b/test/compress/conditionals.js index 16ef6d66..65cfea64 100644 --- a/test/compress/conditionals.js +++ b/test/compress/conditionals.js @@ -332,53 +332,247 @@ cond_7_1: { cond_8: { options = { conditionals: true, - evaluate : true + evaluate : true, + booleans : false }; input: { var a; // compress these a = condition ? true : false; - a = !condition ? true : false; - a = condition() ? true : false; + a = condition ? !0 : !1; + a = !condition ? !null : !2; + a = condition() ? !0 : !-3.5; + if (condition) { a = true; } else { a = false; } + if (condition) { + a = !0; + } else { + a = !1; + } + a = condition ? false : true; - a = !condition ? false : true; - a = condition() ? false : true; + a = condition ? !3 : !0; + a = !condition ? !2 : !0; + a = condition() ? !1 : !0; + if (condition) { a = false; } else { a = true; } + if (condition) { + a = !1; + } else { + a = !0; + } + // don't compress these a = condition ? 1 : false; - a = !condition ? true : 0; - a = condition ? 1 : 0; - } expect: { var a; a = !!condition; a = !condition; a = !!condition(); + a = !!condition; + a = !condition; + a = !!condition(); + + a = !!condition; + a = !!condition; + a = !condition; a = !!condition; a = !condition(); + a = !condition; + a = !!condition; + a = !condition(); + + a = !condition; + a = !condition; + + a = condition ? 1 : false; + a = condition ? 0 : true; + a = condition ? 1 : 0; + } +} + +cond_8b: { + options = { + conditionals: true, + evaluate : true, + booleans : true + }; + input: { + var a; + // compress these + a = condition ? true : false; + a = !condition ? true : false; + a = condition() ? true : false; + + a = condition ? !0 : !1; + a = !condition ? !null : !2; + a = condition() ? !0 : !-3.5; + + if (condition) { + a = true; + } else { + a = false; + } + + if (condition) { + a = !0; + } else { + a = !1; + } + + a = condition ? false : true; + a = !condition ? false : true; + a = condition() ? false : true; + + a = condition ? !3 : !0; + a = !condition ? !2 : !0; + a = condition() ? !1 : !0; + + if (condition) { + a = false; + } else { + a = true; + } + + if (condition) { + a = !1; + } else { + a = !0; + } + + a = condition ? 1 : false; + a = !condition ? true : 0; + a = condition ? 1 : 0; + } + expect: { + var a; + a = !!condition; + a = !condition; + a = !!condition(); + + a = !!condition; + a = !condition; + a = !!condition(); + + a = !!condition; + a = !!condition; + + a = !condition; + a = !!condition; + a = !condition(); + + a = !condition; + a = !!condition; + a = !condition(); + + a = !condition; + a = !condition; + + a = condition ? 1 : !1; + a = condition ? 0 : !0; + a = condition ? 1 : 0; + } +} + +cond_8c: { + options = { + conditionals: true, + evaluate : false, + booleans : false + }; + input: { + var a; + // compress these + a = condition ? true : false; + a = !condition ? true : false; + a = condition() ? true : false; + + a = condition ? !0 : !1; + a = !condition ? !null : !2; + a = condition() ? !0 : !-3.5; + + if (condition) { + a = true; + } else { + a = false; + } + + if (condition) { + a = !0; + } else { + a = !1; + } + + a = condition ? false : true; + a = !condition ? false : true; + a = condition() ? false : true; + + a = condition ? !3 : !0; + a = !condition ? !2 : !0; + a = condition() ? !1 : !0; + + if (condition) { + a = false; + } else { + a = true; + } + + if (condition) { + a = !1; + } else { + a = !0; + } + + a = condition ? 1 : false; + a = !condition ? true : 0; + a = condition ? 1 : 0; + } + expect: { + var a; + a = !!condition; + a = !condition; + a = !!condition(); + + a = !!condition; + a = !condition; + a = condition() ? !0 : !-3.5; + + a = !!condition; + a = !!condition; + + a = !condition; + a = !!condition; + a = !condition(); + + a = !condition; + a = !!condition; + a = !condition(); + + a = !condition; + a = !condition; + a = condition ? 1 : false; a = condition ? 0 : true; a = condition ? 1 : 0; From e87c77ed4144ff52e8f48c1c7ecac09148eca34b Mon Sep 17 00:00:00 2001 From: ReadmeCritic Date: Fri, 27 Nov 2015 08:46:55 -0800 Subject: [PATCH 21/25] Update README URLs based on HTTP redirects --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index bd0d7c48..67324dbd 100644 --- a/README.md +++ b/README.md @@ -783,7 +783,7 @@ The `source_map_options` (optional) can contain the following properties: came from. It can be simply a string in JSON, or a JSON object containing the original source map. - [acorn]: https://github.com/marijnh/acorn + [acorn]: https://github.com/ternjs/acorn [source-map]: https://github.com/mozilla/source-map [sm-spec]: https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit [codegen]: http://lisperator.net/uglifyjs/codegen From bd99b004137961b3b604fcd55602835bf6ffc522 Mon Sep 17 00:00:00 2001 From: Anthony Van de Gejuchte Date: Thu, 17 Dec 2015 23:02:35 +0100 Subject: [PATCH 22/25] Semicolon after do...while statement is optional --- lib/parse.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/parse.js b/lib/parse.js index de27d987..4d06ae05 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -724,9 +724,9 @@ function parse($TEXT, options) { ); }; - function semicolon() { + function semicolon(optional) { if (is("punc", ";")) next(); - else if (!can_insert_semicolon()) unexpected(); + else if (!optional && !can_insert_semicolon()) unexpected(); }; function parenthesised() { @@ -814,7 +814,7 @@ function parse($TEXT, options) { case "do": return new AST_Do({ body : in_loop(statement), - condition : (expect_token("keyword", "while"), tmp = parenthesised(), semicolon(), tmp) + condition : (expect_token("keyword", "while"), tmp = parenthesised(), semicolon(true), tmp) }); case "while": From 5cd26c005b3b171f4da8b6732de224089e55ae8a Mon Sep 17 00:00:00 2001 From: Anthony Van de Gejuchte Date: Fri, 18 Dec 2015 14:39:48 +0100 Subject: [PATCH 23/25] Add tests --- test/compress/loops.js | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/test/compress/loops.js b/test/compress/loops.js index cdf1f045..b7194fed 100644 --- a/test/compress/loops.js +++ b/test/compress/loops.js @@ -121,3 +121,25 @@ drop_if_else_break_4: { for (; bar() && (x(), y(), foo());) baz(), z(), k(); } } + +parse_do_while_with_semicolon: { + input: { + do { + x(); + } while (false);y() + } + expect: { + do x(); while (false);y(); + } +} + +parse_do_while_without_semicolon: { + input: { + do { + x(); + } while (false)y() + } + expect: { + do x(); while (false);y(); + } +} \ No newline at end of file From 0cabedc52617cef9623627b390703c011ab6c0b6 Mon Sep 17 00:00:00 2001 From: Anthony Van de Gejuchte Date: Fri, 18 Dec 2015 15:25:24 +0100 Subject: [PATCH 24/25] Disable loop optimization for parse-only tests --- test/compress/loops.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/compress/loops.js b/test/compress/loops.js index b7194fed..91aa1c5f 100644 --- a/test/compress/loops.js +++ b/test/compress/loops.js @@ -123,6 +123,7 @@ drop_if_else_break_4: { } parse_do_while_with_semicolon: { + options = { loops: false }; input: { do { x(); @@ -134,6 +135,7 @@ parse_do_while_with_semicolon: { } parse_do_while_without_semicolon: { + options = { loops: false }; input: { do { x(); From 174404c0f37bf14aff23c41889e6bab1fd87c1d7 Mon Sep 17 00:00:00 2001 From: Anthony Van de Gejuchte Date: Sat, 26 Dec 2015 15:08:37 +0100 Subject: [PATCH 25/25] Do not allow newlines in string literals --- lib/parse.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/parse.js b/lib/parse.js index 4d06ae05..7e7b2272 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -399,6 +399,7 @@ function tokenizer($TEXT, filename, html5_comments, shebang) { if (octal_len > 0) ch = String.fromCharCode(parseInt(ch, 8)); else ch = read_escaped_char(true); } + else if (ch == "\n") parse_error("Unterminated string constant"); else if (ch == quote) break; ret += ch; }