From 8943f57b3171346e4738493bf359d99544836ec8 Mon Sep 17 00:00:00 2001 From: alexlamsl Date: Thu, 12 Jul 2018 23:33:13 +0800 Subject: [PATCH] fix corner cases in `preserve_line` --- README.md | 4 +- bin/uglifyjs | 2 +- lib/output.js | 91 ++++---- lib/utils.js | 3 +- test/compress/evaluate.js | 20 +- test/compress/global_defs.js | 6 +- test/compress/issue-1034.js | 82 ++++--- test/compress/issue-1261.js | 84 +++---- test/compress/issue-208.js | 12 +- test/compress/issue-269.js | 2 +- test/compress/issue-2719.js | 4 +- test/compress/preserve_line.js | 181 +++++++++++++++ test/exports.js | 14 +- test/run-tests.js | 400 +++++++++++++++------------------ tools/exports.js | 6 +- 15 files changed, 533 insertions(+), 378 deletions(-) create mode 100644 test/compress/preserve_line.js diff --git a/README.md b/README.md index b21736de92..a08a36dfa6 100644 --- a/README.md +++ b/README.md @@ -857,8 +857,8 @@ can pass additional arguments that control the code output: adjust for this text. Can be used to insert a comment containing licensing information, for example. -- `preserve_line` (default `false`) -- pass `true` to preserve lines, but it - only works if `beautify` is set to `false`. +- `preserve_line` (default `false`) -- pass `true` to retain line numbering on + a best effort basis. - `quote_keys` (default `false`) -- pass `true` to quote all keys in literal objects diff --git a/bin/uglifyjs b/bin/uglifyjs index fb6ae2fb00..62c7beb34c 100755 --- a/bin/uglifyjs +++ b/bin/uglifyjs @@ -100,7 +100,7 @@ if (program.mangleProps) { if (typeof program.mangleProps != "object") program.mangleProps = {}; if (!Array.isArray(program.mangleProps.reserved)) program.mangleProps.reserved = []; require("../tools/domprops").forEach(function(name) { - UglifyJS._push_uniq(program.mangleProps.reserved, name); + UglifyJS.push_uniq(program.mangleProps.reserved, name); }); } if (typeof options.mangle != "object") options.mangle = {}; diff --git a/lib/output.js b/lib/output.js index 1dbc1f6efc..37536c06b4 100644 --- a/lib/output.js +++ b/lib/output.js @@ -198,16 +198,24 @@ function OutputStream(options) { /* -----[ beautification/minification ]----- */ var has_parens = false; + var line_end = 0; + var line_fixed = true; var might_need_space = false; var might_need_semicolon = false; - var might_add_newline = 0; var need_newline_indented = false; var need_space = false; var newline_insert = -1; var last = ""; var mapping_token, mapping_name, mappings = options.source_map && []; - var do_add_mapping = mappings ? function() { + var adjust_mappings = mappings ? function(line, col) { + mappings.forEach(function(mapping) { + mapping.line += line; + mapping.col += col; + }); + } : noop; + + var flush_mappings = mappings ? function() { mappings.forEach(function(mapping) { try { options.source_map.add( @@ -230,31 +238,30 @@ function OutputStream(options) { mappings = []; } : noop; - var ensure_line_len = options.max_line_len ? function() { - if (current_col > options.max_line_len) { - if (might_add_newline) { - var left = OUTPUT.slice(0, might_add_newline); - var right = OUTPUT.slice(might_add_newline); - if (mappings) { - var delta = right.length - current_col; - mappings.forEach(function(mapping) { - mapping.line++; - mapping.col += delta; - }); - } - OUTPUT = left + "\n" + right; - current_line++; - current_pos++; - current_col = right.length; - } + function insert_newlines(count) { + var index = OUTPUT.lastIndexOf("\n"); + if (line_end < index) line_end = index; + var left = OUTPUT.slice(0, line_end); + var right = OUTPUT.slice(line_end); + adjust_mappings(count, right.length - current_col); + current_line += count; + current_pos += count; + current_col = right.length; + OUTPUT = left; + while (count--) OUTPUT += "\n"; + OUTPUT += right; + } + + var fix_line = options.max_line_len ? function() { + if (line_fixed) { if (current_col > options.max_line_len) { AST_Node.warn("Output exceeds {max_line_len} characters", options); } + return; } - if (might_add_newline) { - might_add_newline = 0; - do_add_mapping(); - } + if (current_col > options.max_line_len) insert_newlines(1); + line_fixed = true; + flush_mappings(); } : noop; var requireSemicolonChars = makePredicate("( [ + * / - , ."); @@ -286,7 +293,7 @@ function OutputStream(options) { current_col++; current_pos++; } else { - ensure_line_len(); + fix_line(); OUTPUT += "\n"; current_pos++; current_line++; @@ -304,18 +311,6 @@ function OutputStream(options) { } } - if (!options.beautify && options.preserve_line && stack[stack.length - 1]) { - var target_line = stack[stack.length - 1].start.line; - while (current_line < target_line) { - ensure_line_len(); - OUTPUT += "\n"; - current_pos++; - current_line++; - current_col = 0; - might_need_space = false; - } - } - if (might_need_space) { if ((is_identifier_char(prev) && (is_identifier_char(ch) || ch == "\\")) @@ -337,7 +332,7 @@ function OutputStream(options) { col: current_col }); mapping_token = false; - if (!might_add_newline) do_add_mapping(); + if (line_fixed) flush_mappings(); } OUTPUT += str; @@ -347,7 +342,7 @@ function OutputStream(options) { current_line += n; current_col += a[0].length; if (n > 0) { - ensure_line_len(); + fix_line(); current_col = a[n].length; } last = str; @@ -374,9 +369,10 @@ function OutputStream(options) { return ret; } : function(col, cont) { return cont() }; - var may_add_newline = options.max_line_len ? function() { - ensure_line_len(); - might_add_newline = OUTPUT.length; + var may_add_newline = options.max_line_len || options.preserve_line ? function() { + fix_line(); + line_end = OUTPUT.length; + line_fixed = false; } : noop; var newline = options.beautify ? function() { @@ -455,9 +451,7 @@ function OutputStream(options) { } : noop; function get() { - if (might_add_newline) { - ensure_line_len(); - } + if (!line_fixed) fix_line(); return OUTPUT; } @@ -622,7 +616,14 @@ function OutputStream(options) { col : function() { return current_col }, pos : function() { return current_pos }, push_node : function(node) { stack.push(node) }, - pop_node : function() { return stack.pop() }, + pop_node : options.preserve_line ? function() { + var node = stack.pop(); + if (node.start && node.start.line > current_line) { + insert_newlines(node.start.line - current_line); + } + } : function() { + stack.pop(); + }, parent : function(n) { return stack[stack.length - 2 - (n || 0)]; } diff --git a/lib/utils.js b/lib/utils.js index 7a51fb8052..4a61623a76 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -162,8 +162,7 @@ var MAP = (function() { })(); function push_uniq(array, el) { - if (array.indexOf(el) < 0) - array.push(el); + if (array.indexOf(el) < 0) return array.push(el); } function string_template(text, props) { diff --git a/test/compress/evaluate.js b/test/compress/evaluate.js index 999a1ca59f..53fe8d96c0 100644 --- a/test/compress/evaluate.js +++ b/test/compress/evaluate.js @@ -1199,7 +1199,7 @@ issue_2231_1: { } expect_stdout: true expect_warnings: [ - "WARN: Error evaluating Object.keys(void 0) [test/compress/evaluate.js:1195,20]", + "WARN: Error evaluating Object.keys(void 0) [test/compress/evaluate.js:1,20]", ] } @@ -1216,7 +1216,7 @@ issue_2231_2: { } expect_stdout: true expect_warnings: [ - "WARN: Error evaluating Object.getOwnPropertyNames(null) [test/compress/evaluate.js:1212,20]", + "WARN: Error evaluating Object.getOwnPropertyNames(null) [test/compress/evaluate.js:1,20]", ] } @@ -1354,14 +1354,14 @@ issue_2535_3: { } expect_stdout: true expect_warnings: [ - "WARN: Dropping side-effect-free && [test/compress/evaluate.js:1340,20]", - "WARN: Dropping side-effect-free && [test/compress/evaluate.js:1341,20]", - "WARN: Dropping side-effect-free && [test/compress/evaluate.js:1342,20]", - "WARN: Condition left of && always false [test/compress/evaluate.js:1342,20]", - "WARN: Dropping side-effect-free || [test/compress/evaluate.js:1343,20]", - "WARN: Dropping side-effect-free || [test/compress/evaluate.js:1344,20]", - "WARN: Dropping side-effect-free || [test/compress/evaluate.js:1345,20]", - "WARN: Condition left of || always true [test/compress/evaluate.js:1345,20]", + "WARN: Dropping side-effect-free && [test/compress/evaluate.js:1,20]", + "WARN: Dropping side-effect-free && [test/compress/evaluate.js:2,20]", + "WARN: Dropping side-effect-free && [test/compress/evaluate.js:3,20]", + "WARN: Condition left of && always false [test/compress/evaluate.js:3,20]", + "WARN: Dropping side-effect-free || [test/compress/evaluate.js:4,20]", + "WARN: Dropping side-effect-free || [test/compress/evaluate.js:5,20]", + "WARN: Dropping side-effect-free || [test/compress/evaluate.js:6,20]", + "WARN: Condition left of || always true [test/compress/evaluate.js:6,20]", ] } diff --git a/test/compress/global_defs.js b/test/compress/global_defs.js index ea8d6cecab..98fa3e9f35 100644 --- a/test/compress/global_defs.js +++ b/test/compress/global_defs.js @@ -141,9 +141,9 @@ mixed: { console.log(CONFIG); } expect_warnings: [ - "WARN: global_defs CONFIG.VALUE redefined [test/compress/global_defs.js:127,22]", - "WARN: global_defs CONFIG.VALUE redefined [test/compress/global_defs.js:128,22]", - "WARN: global_defs CONFIG.VALUE redefined [test/compress/global_defs.js:130,8]", + "WARN: global_defs CONFIG.VALUE redefined [test/compress/global_defs.js:4,22]", + "WARN: global_defs CONFIG.VALUE redefined [test/compress/global_defs.js:5,22]", + "WARN: global_defs CONFIG.VALUE redefined [test/compress/global_defs.js:7,8]", ] } diff --git a/test/compress/issue-1034.js b/test/compress/issue-1034.js index 8f3e0b1a37..f2a89e3348 100644 --- a/test/compress/issue-1034.js +++ b/test/compress/issue-1034.js @@ -36,10 +36,10 @@ non_hoisted_function_after_return: { } } expect_warnings: [ - "WARN: Dropping unreachable code [test/compress/issue-1034.js:20,16]", - "WARN: Dropping unreachable code [test/compress/issue-1034.js:23,16]", - "WARN: Dropping unreachable code [test/compress/issue-1034.js:26,12]", - "WARN: Dropping unused function UnusedFunction [test/compress/issue-1034.js:27,21]" + "WARN: Dropping unreachable code [test/compress/issue-1034.js:4,16]", + "WARN: Dropping unreachable code [test/compress/issue-1034.js:7,16]", + "WARN: Dropping unreachable code [test/compress/issue-1034.js:10,12]", + "WARN: Dropping unused function UnusedFunction [test/compress/issue-1034.js:11,21]" ] } @@ -85,18 +85,18 @@ non_hoisted_function_after_return_2a: { } } expect_warnings: [ - "WARN: Dropping unreachable code [test/compress/issue-1034.js:68,16]", - "WARN: Declarations in unreachable code! [test/compress/issue-1034.js:68,16]", - "WARN: Dropping unreachable code [test/compress/issue-1034.js:71,16]", - "WARN: Declarations in unreachable code! [test/compress/issue-1034.js:71,16]", - "WARN: Dropping unused variable a [test/compress/issue-1034.js:68,20]", - "WARN: Dropping unused function nope [test/compress/issue-1034.js:75,21]", + "WARN: Dropping unreachable code [test/compress/issue-1034.js:4,16]", + "WARN: Declarations in unreachable code! [test/compress/issue-1034.js:4,16]", + "WARN: Dropping unreachable code [test/compress/issue-1034.js:7,16]", + "WARN: Declarations in unreachable code! [test/compress/issue-1034.js:7,16]", + "WARN: Dropping unused variable a [test/compress/issue-1034.js:4,20]", + "WARN: Dropping unused function nope [test/compress/issue-1034.js:11,21]", "WARN: pass 0: last_count: Infinity, count: 37", - "WARN: Dropping unreachable code [test/compress/issue-1034.js:73,12]", - "WARN: Declarations in unreachable code! [test/compress/issue-1034.js:73,12]", - "WARN: Dropping unreachable code [test/compress/issue-1034.js:76,12]", - "WARN: Dropping unused variable b [test/compress/issue-1034.js:71,20]", - "WARN: Dropping unused variable c [test/compress/issue-1034.js:73,16]", + "WARN: Dropping unreachable code [test/compress/issue-1034.js:9,12]", + "WARN: Declarations in unreachable code! [test/compress/issue-1034.js:9,12]", + "WARN: Dropping unreachable code [test/compress/issue-1034.js:12,12]", + "WARN: Dropping unused variable b [test/compress/issue-1034.js:7,20]", + "WARN: Dropping unused variable c [test/compress/issue-1034.js:9,16]", "WARN: pass 1: last_count: 37, count: 18", ] } @@ -139,12 +139,11 @@ non_hoisted_function_after_return_2b: { } } expect_warnings: [ - // duplicate warnings no longer emitted - "WARN: Dropping unreachable code [test/compress/issue-1034.js:126,16]", - "WARN: Declarations in unreachable code! [test/compress/issue-1034.js:126,16]", - "WARN: Dropping unreachable code [test/compress/issue-1034.js:128,12]", - "WARN: Declarations in unreachable code! [test/compress/issue-1034.js:128,12]", - "WARN: Dropping unreachable code [test/compress/issue-1034.js:132,12]", + "WARN: Dropping unreachable code [test/compress/issue-1034.js:6,16]", + "WARN: Declarations in unreachable code! [test/compress/issue-1034.js:6,16]", + "WARN: Dropping unreachable code [test/compress/issue-1034.js:8,12]", + "WARN: Declarations in unreachable code! [test/compress/issue-1034.js:8,12]", + "WARN: Dropping unreachable code [test/compress/issue-1034.js:12,12]", ] } @@ -191,10 +190,10 @@ non_hoisted_function_after_return_strict: { } expect_stdout: "8 7" expect_warnings: [ - "WARN: Dropping unreachable code [test/compress/issue-1034.js:171,16]", - "WARN: Dropping unreachable code [test/compress/issue-1034.js:174,16]", - "WARN: Dropping unreachable code [test/compress/issue-1034.js:177,12]", - "WARN: Dropping unused function UnusedFunction [test/compress/issue-1034.js:178,21]", + "WARN: Dropping unreachable code [test/compress/issue-1034.js:5,16]", + "WARN: Dropping unreachable code [test/compress/issue-1034.js:8,16]", + "WARN: Dropping unreachable code [test/compress/issue-1034.js:11,12]", + "WARN: Dropping unused function UnusedFunction [test/compress/issue-1034.js:12,21]", ] } @@ -245,18 +244,18 @@ non_hoisted_function_after_return_2a_strict: { } expect_stdout: "5 6" expect_warnings: [ - "WARN: Dropping unreachable code [test/compress/issue-1034.js:224,16]", - "WARN: Declarations in unreachable code! [test/compress/issue-1034.js:224,16]", - "WARN: Dropping unreachable code [test/compress/issue-1034.js:227,16]", - "WARN: Declarations in unreachable code! [test/compress/issue-1034.js:227,16]", - "WARN: Dropping unused variable a [test/compress/issue-1034.js:224,20]", - "WARN: Dropping unused function nope [test/compress/issue-1034.js:231,21]", + "WARN: Dropping unreachable code [test/compress/issue-1034.js:5,16]", + "WARN: Declarations in unreachable code! [test/compress/issue-1034.js:5,16]", + "WARN: Dropping unreachable code [test/compress/issue-1034.js:8,16]", + "WARN: Declarations in unreachable code! [test/compress/issue-1034.js:8,16]", + "WARN: Dropping unused variable a [test/compress/issue-1034.js:5,20]", + "WARN: Dropping unused function nope [test/compress/issue-1034.js:12,21]", "WARN: pass 0: last_count: Infinity, count: 48", - "WARN: Dropping unreachable code [test/compress/issue-1034.js:229,12]", - "WARN: Declarations in unreachable code! [test/compress/issue-1034.js:229,12]", - "WARN: Dropping unreachable code [test/compress/issue-1034.js:232,12]", - "WARN: Dropping unused variable b [test/compress/issue-1034.js:227,20]", - "WARN: Dropping unused variable c [test/compress/issue-1034.js:229,16]", + "WARN: Dropping unreachable code [test/compress/issue-1034.js:10,12]", + "WARN: Declarations in unreachable code! [test/compress/issue-1034.js:10,12]", + "WARN: Dropping unreachable code [test/compress/issue-1034.js:13,12]", + "WARN: Dropping unused variable b [test/compress/issue-1034.js:8,20]", + "WARN: Dropping unused variable c [test/compress/issue-1034.js:10,16]", "WARN: pass 1: last_count: 48, count: 29", ] } @@ -304,11 +303,10 @@ non_hoisted_function_after_return_2b_strict: { } expect_stdout: "5 6" expect_warnings: [ - // duplicate warnings no longer emitted - "WARN: Dropping unreachable code [test/compress/issue-1034.js:287,16]", - "WARN: Declarations in unreachable code! [test/compress/issue-1034.js:287,16]", - "WARN: Dropping unreachable code [test/compress/issue-1034.js:289,12]", - "WARN: Declarations in unreachable code! [test/compress/issue-1034.js:289,12]", - "WARN: Dropping unreachable code [test/compress/issue-1034.js:293,12]", + "WARN: Dropping unreachable code [test/compress/issue-1034.js:7,16]", + "WARN: Declarations in unreachable code! [test/compress/issue-1034.js:7,16]", + "WARN: Dropping unreachable code [test/compress/issue-1034.js:9,12]", + "WARN: Declarations in unreachable code! [test/compress/issue-1034.js:9,12]", + "WARN: Dropping unreachable code [test/compress/issue-1034.js:13,12]", ] } diff --git a/test/compress/issue-1261.js b/test/compress/issue-1261.js index e40111c462..284cc28687 100644 --- a/test/compress/issue-1261.js +++ b/test/compress/issue-1261.js @@ -48,13 +48,13 @@ pure_function_calls: { a.b(), f.g(); } expect_warnings: [ - "WARN: Dropping __PURE__ call [test/compress/issue-1261.js:16,8]", - "WARN: Dropping side-effect-free statement [test/compress/issue-1261.js:16,8]", - "WARN: Dropping __PURE__ call [test/compress/issue-1261.js:29,37]", - "WARN: Dropping unused variable iife2 [test/compress/issue-1261.js:29,16]", - "WARN: Dropping side-effect-free statement [test/compress/issue-1261.js:27,8]", - "WARN: Dropping __PURE__ call [test/compress/issue-1261.js:37,8]", - "WARN: Dropping __PURE__ call [test/compress/issue-1261.js:38,31]", + "WARN: Dropping __PURE__ call [test/compress/issue-1261.js:3,8]", + "WARN: Dropping side-effect-free statement [test/compress/issue-1261.js:3,8]", + "WARN: Dropping __PURE__ call [test/compress/issue-1261.js:16,37]", + "WARN: Dropping unused variable iife2 [test/compress/issue-1261.js:16,16]", + "WARN: Dropping side-effect-free statement [test/compress/issue-1261.js:14,8]", + "WARN: Dropping __PURE__ call [test/compress/issue-1261.js:24,8]", + "WARN: Dropping __PURE__ call [test/compress/issue-1261.js:25,31]", ] } @@ -110,17 +110,17 @@ pure_function_calls_toplevel: { a.b(), f.g(); } expect_warnings: [ - "WARN: Dropping __PURE__ call [test/compress/issue-1261.js:77,8]", - "WARN: Dropping side-effect-free statement [test/compress/issue-1261.js:77,8]", - "WARN: Dropping __PURE__ call [test/compress/issue-1261.js:90,37]", - "WARN: Dropping unused variable iife2 [test/compress/issue-1261.js:90,16]", - "WARN: Dropping side-effect-free statement [test/compress/issue-1261.js:88,8]", - "WARN: Dropping __PURE__ call [test/compress/issue-1261.js:105,8]", - "WARN: Dropping __PURE__ call [test/compress/issue-1261.js:106,31]", - "WARN: Dropping __PURE__ call [test/compress/issue-1261.js:82,33]", - "WARN: Dropping unused variable iife1 [test/compress/issue-1261.js:82,12]", - "WARN: Dropping __PURE__ call [test/compress/issue-1261.js:98,45]", - "WARN: Dropping unused variable MyClass [test/compress/issue-1261.js:98,12]", + "WARN: Dropping __PURE__ call [test/compress/issue-1261.js:3,8]", + "WARN: Dropping side-effect-free statement [test/compress/issue-1261.js:3,8]", + "WARN: Dropping __PURE__ call [test/compress/issue-1261.js:16,37]", + "WARN: Dropping unused variable iife2 [test/compress/issue-1261.js:16,16]", + "WARN: Dropping side-effect-free statement [test/compress/issue-1261.js:14,8]", + "WARN: Dropping __PURE__ call [test/compress/issue-1261.js:31,8]", + "WARN: Dropping __PURE__ call [test/compress/issue-1261.js:32,31]", + "WARN: Dropping __PURE__ call [test/compress/issue-1261.js:8,33]", + "WARN: Dropping unused variable iife1 [test/compress/issue-1261.js:8,12]", + "WARN: Dropping __PURE__ call [test/compress/issue-1261.js:24,45]", + "WARN: Dropping unused variable MyClass [test/compress/issue-1261.js:24,12]", ] } @@ -155,29 +155,29 @@ should_warn: { baz(); } expect_warnings: [ - "WARN: Dropping __PURE__ call [test/compress/issue-1261.js:135,61]", - "WARN: Dropping __PURE__ call [test/compress/issue-1261.js:135,23]", - "WARN: Dropping side-effect-free statement [test/compress/issue-1261.js:135,23]", - "WARN: Boolean || always true [test/compress/issue-1261.js:136,23]", - "WARN: Dropping __PURE__ call [test/compress/issue-1261.js:136,23]", - "WARN: Condition always true [test/compress/issue-1261.js:136,23]", - "WARN: Condition left of || always true [test/compress/issue-1261.js:137,8]", - "WARN: Condition always true [test/compress/issue-1261.js:137,8]", - "WARN: Boolean && always false [test/compress/issue-1261.js:138,23]", - "WARN: Dropping __PURE__ call [test/compress/issue-1261.js:138,23]", - "WARN: Condition always false [test/compress/issue-1261.js:138,23]", - "WARN: Condition left of && always false [test/compress/issue-1261.js:139,8]", - "WARN: Condition always false [test/compress/issue-1261.js:139,8]", - "WARN: + in boolean context always true [test/compress/issue-1261.js:140,23]", - "WARN: Dropping __PURE__ call [test/compress/issue-1261.js:140,23]", - "WARN: Condition always true [test/compress/issue-1261.js:140,23]", - "WARN: + in boolean context always true [test/compress/issue-1261.js:141,8]", - "WARN: Dropping __PURE__ call [test/compress/issue-1261.js:141,31]", - "WARN: Condition always true [test/compress/issue-1261.js:141,8]", - "WARN: Dropping __PURE__ call [test/compress/issue-1261.js:142,23]", - "WARN: Condition always true [test/compress/issue-1261.js:143,8]", - "WARN: Dropping __PURE__ call [test/compress/issue-1261.js:143,24]", - "WARN: Dropping __PURE__ call [test/compress/issue-1261.js:144,31]", - "WARN: Condition always false [test/compress/issue-1261.js:144,8]", + "WARN: Dropping __PURE__ call [test/compress/issue-1261.js:1,61]", + "WARN: Dropping __PURE__ call [test/compress/issue-1261.js:1,23]", + "WARN: Dropping side-effect-free statement [test/compress/issue-1261.js:1,23]", + "WARN: Boolean || always true [test/compress/issue-1261.js:2,23]", + "WARN: Dropping __PURE__ call [test/compress/issue-1261.js:2,23]", + "WARN: Condition always true [test/compress/issue-1261.js:2,23]", + "WARN: Condition left of || always true [test/compress/issue-1261.js:3,8]", + "WARN: Condition always true [test/compress/issue-1261.js:3,8]", + "WARN: Boolean && always false [test/compress/issue-1261.js:4,23]", + "WARN: Dropping __PURE__ call [test/compress/issue-1261.js:4,23]", + "WARN: Condition always false [test/compress/issue-1261.js:4,23]", + "WARN: Condition left of && always false [test/compress/issue-1261.js:5,8]", + "WARN: Condition always false [test/compress/issue-1261.js:5,8]", + "WARN: + in boolean context always true [test/compress/issue-1261.js:6,23]", + "WARN: Dropping __PURE__ call [test/compress/issue-1261.js:6,23]", + "WARN: Condition always true [test/compress/issue-1261.js:6,23]", + "WARN: + in boolean context always true [test/compress/issue-1261.js:7,8]", + "WARN: Dropping __PURE__ call [test/compress/issue-1261.js:7,31]", + "WARN: Condition always true [test/compress/issue-1261.js:7,8]", + "WARN: Dropping __PURE__ call [test/compress/issue-1261.js:8,23]", + "WARN: Condition always true [test/compress/issue-1261.js:9,8]", + "WARN: Dropping __PURE__ call [test/compress/issue-1261.js:9,24]", + "WARN: Dropping __PURE__ call [test/compress/issue-1261.js:10,31]", + "WARN: Condition always false [test/compress/issue-1261.js:10,8]", ] } diff --git a/test/compress/issue-208.js b/test/compress/issue-208.js index aed3bcc4b3..3f732755eb 100644 --- a/test/compress/issue-208.js +++ b/test/compress/issue-208.js @@ -64,11 +64,11 @@ mixed: { x = 0; } expect_warnings: [ - "WARN: global_defs ENV redefined [test/compress/issue-208.js:45,12]", - "WARN: global_defs FOO redefined [test/compress/issue-208.js:46,12]", - "WARN: global_defs FOO redefined [test/compress/issue-208.js:48,10]", - "WARN: global_defs DEBUG redefined [test/compress/issue-208.js:49,8]", - "WARN: global_defs DEBUG redefined [test/compress/issue-208.js:50,8]", - "WARN: global_defs DEBUG redefined [test/compress/issue-208.js:51,8]", + "WARN: global_defs ENV redefined [test/compress/issue-208.js:1,12]", + "WARN: global_defs FOO redefined [test/compress/issue-208.js:2,12]", + "WARN: global_defs FOO redefined [test/compress/issue-208.js:4,10]", + "WARN: global_defs DEBUG redefined [test/compress/issue-208.js:5,8]", + "WARN: global_defs DEBUG redefined [test/compress/issue-208.js:6,8]", + "WARN: global_defs DEBUG redefined [test/compress/issue-208.js:7,8]", ] } diff --git a/test/compress/issue-269.js b/test/compress/issue-269.js index 3145b3b3df..20b11b0a51 100644 --- a/test/compress/issue-269.js +++ b/test/compress/issue-269.js @@ -93,6 +93,6 @@ regexp: { RegExp("should", "fail"); } expect_warnings: [ - 'WARN: Error converting RegExp("should","fail") [test/compress/issue-269.js:86,2]', + 'WARN: Error converting RegExp("should","fail") [test/compress/issue-269.js:5,2]', ] } diff --git a/test/compress/issue-2719.js b/test/compress/issue-2719.js index 93e51d7c0d..c8b0918e46 100644 --- a/test/compress/issue-2719.js +++ b/test/compress/issue-2719.js @@ -26,7 +26,7 @@ warn: { }().length); } expect_warnings: [ - "WARN: Function.protoype.caller not supported [test/compress/issue-2719.js:17,19]", - "WARN: Function.protoype.arguments not supported [test/compress/issue-2719.js:17,19]", + "WARN: Function.protoype.caller not supported [test/compress/issue-2719.js:5,19]", + "WARN: Function.protoype.arguments not supported [test/compress/issue-2719.js:5,19]", ] } diff --git a/test/compress/preserve_line.js b/test/compress/preserve_line.js new file mode 100644 index 0000000000..5b1638cad8 --- /dev/null +++ b/test/compress/preserve_line.js @@ -0,0 +1,181 @@ +return_1: { + beautify = { + beautify: false, + preserve_line: true, + } + input: { + console.log(function f() { + return ( + f.toString() != 42 + ); + }()); + } + expect_exact: [ + "console.log(function f(){", + "", + "return 42!=f.toString()}());", + ] + expect_stdout: "true" +} + +return_2: { + beautify = { + beautify: true, + preserve_line: true, + } + input: { + console.log(function f() { + return ( + f.toString() != 42 + ); + }()); + } + expect_exact: [ + "console.log(function f() {", + "", + " return 42 != f.toString();", + "}());", + ] + expect_stdout: "true" +} + +return_3: { + options = {} + beautify = { + beautify: false, + preserve_line: true, + } + input: { + console.log(function f() { + return ( + f.toString() != 42 + ); + }()); + } + expect_exact: [ + "console.log(function f(){", + "", + "return 42!=f.toString()}());", + ] + expect_stdout: "true" +} + +return_4: { + options = {} + beautify = { + beautify: true, + preserve_line: true, + } + input: { + console.log(function f() { + return ( + f.toString() != 42 + ); + }()); + } + expect_exact: [ + "console.log(function f() {", + "", + " return 42 != f.toString();", + "}());", + ] + expect_stdout: "true" +} + +return_5: { + beautify = { + beautify: false, + preserve_line: true, + } + input: { + _is_selected = function(tags, slug) { + var ref; + return (ref = _.find(tags, { + slug: slug + })) != null ? ref.selected : void 0; + }; + } + expect_exact: [ + "_is_selected=function(tags,slug){", + "var ref", + "", + "", + ";return null!=(ref=_.find(tags,{slug:slug}))?ref.selected:void 0};", + ] +} + +return_6: { + beautify = { + beautify: true, + preserve_line: true, + } + input: { + _is_selected = function(tags, slug) { + var ref; + return (ref = _.find(tags, { + slug: slug + })) != null ? ref.selected : void 0; + }; + } + expect_exact: [ + "_is_selected = function(tags, slug) {", + " var ref;", + "", + "", + " return null != (ref = _.find(tags, {", + " slug: slug", + " })) ? ref.selected : void 0;", + "};", + ] +} + +return_7: { + options = {} + mangle = {} + beautify = { + beautify: false, + preserve_line: true, + } + input: { + _is_selected = function(tags, slug) { + var ref; + return (ref = _.find(tags, { + slug: slug + })) != null ? ref.selected : void 0; + }; + } + expect_exact: [ + "_is_selected=function(e,l){", + "var n", + "", + "", + ";return null!=(n=_.find(e,{slug:l}))?n.selected:void 0};", + ] +} + +return_8: { + options = {} + mangle = {} + beautify = { + beautify: true, + preserve_line: true, + } + input: { + _is_selected = function(tags, slug) { + var ref; + return (ref = _.find(tags, { + slug: slug + })) != null ? ref.selected : void 0; + }; + } + expect_exact: [ + "_is_selected = function(e, l) {", + " var n;", + "", + "", + " return null != (n = _.find(e, {", + " slug: l", + " })) ? n.selected : void 0;", + "};", + ] +} diff --git a/test/exports.js b/test/exports.js index 997ad513c9..85a3c3bf41 100644 --- a/test/exports.js +++ b/test/exports.js @@ -1,14 +1,16 @@ -exports["Compressor"] = Compressor; -exports["JS_Parse_Error"] = JS_Parse_Error; -exports["OutputStream"] = OutputStream; -exports["SourceMap"] = SourceMap; -exports["TreeWalker"] = TreeWalker; exports["base54"] = base54; +exports["Compressor"] = Compressor; exports["defaults"] = defaults; +exports["is_identifier"] = is_identifier; +exports["JS_Parse_Error"] = JS_Parse_Error; exports["mangle_properties"] = mangle_properties; exports["minify"] = minify; +exports["OutputStream"] = OutputStream; exports["parse"] = parse; +exports["push_uniq"] = push_uniq; exports["reserve_quoted_keys"] = reserve_quoted_keys; +exports["SourceMap"] = SourceMap; exports["string_template"] = string_template; exports["tokenizer"] = tokenizer; -exports["is_identifier"] = is_identifier; +exports["TreeTransformer"] = TreeTransformer; +exports["TreeWalker"] = TreeWalker; diff --git a/test/run-tests.js b/test/run-tests.js index 42b579c90d..f78deb31a2 100755 --- a/test/run-tests.js +++ b/test/run-tests.js @@ -23,68 +23,200 @@ require("./mocha.js"); /* -----[ utils ]----- */ -function tmpl() { - return U.string_template.apply(this, arguments); +function evaluate(code) { + if (code instanceof U.AST_Node) code = make_code(code, { beautify: true }); + return new Function("return(" + code + ")")(); } function log() { - var txt = tmpl.apply(this, arguments); - console.log("%s", txt); + console.log("%s", tmpl.apply(null, arguments)); } -function log_directory(dir) { - log("*** Entering [{dir}]", { dir: dir }); +function make_code(ast, options) { + var stream = U.OutputStream(options); + ast.print(stream); + return stream.get(); } -function log_start_file(file) { - log("--- {file}", { file: file }); -} +function parse_test(file) { + var script = fs.readFileSync(file, "utf8"); + // TODO try/catch can be removed after fixing https://github.com/mishoo/UglifyJS2/issues/348 + try { + var ast = U.parse(script, { + filename: file + }); + } catch (e) { + console.log("Caught error while parsing tests in " + file + "\n"); + console.log(e); + throw e; + } + var tests = {}; + var tw = new U.TreeWalker(function(node, descend) { + if (node instanceof U.AST_LabeledStatement + && tw.parent() instanceof U.AST_Toplevel) { + var name = node.label.name; + if (name in tests) { + throw new Error('Duplicated test name "' + name + '" in ' + file); + } + tests[name] = get_one_test(name, node.body); + return true; + } + if (!(node instanceof U.AST_Toplevel)) croak(node); + }); + ast.walk(tw); + return tests; -function log_test(name) { - log(" Running test [{name}]", { name: name }); -} + function croak(node) { + throw new Error(tmpl("Can't understand test file {file} [{line},{col}]\n{code}", { + file: file, + line: node.start.line, + col: node.start.col, + code: make_code(node, { beautify: false }) + })); + } -function find_test_files(dir) { - return fs.readdirSync(dir).filter(function(name) { - return /\.js$/i.test(name); - }); -} + function read_string(stat) { + if (stat.TYPE == "SimpleStatement") { + var body = stat.body; + switch(body.TYPE) { + case "String": + return body.value; + case "Array": + return body.elements.map(function(element) { + if (element.TYPE !== "String") + throw new Error("Should be array of strings"); + return element.value; + }).join("\n"); + } + } + throw new Error("Should be string or array of strings"); + } -function test_directory(dir) { - return path.resolve(tests_dir, dir); + function get_one_test(name, block) { + var test = { name: name, options: {} }; + var tw = new U.TreeWalker(function(node, descend) { + if (node instanceof U.AST_Assign) { + if (!(node.left instanceof U.AST_SymbolRef)) { + croak(node); + } + var name = node.left.name; + test[name] = evaluate(node.right); + return true; + } + if (node instanceof U.AST_LabeledStatement) { + var label = node.label; + assert.ok([ + "input", + "expect", + "expect_exact", + "expect_warnings", + "expect_stdout", + "node_version", + ].indexOf(label.name) >= 0, tmpl("Unsupported label {name} [{line},{col}]", { + name: label.name, + line: label.start.line, + col: label.start.col + })); + var stat = node.body; + if (label.name == "expect_exact" || label.name == "node_version") { + test[label.name] = read_string(stat); + } else if (label.name == "expect_stdout") { + var body = stat.body; + if (body instanceof U.AST_Boolean) { + test[label.name] = body.value; + } else if (body instanceof U.AST_Call) { + var ctor = global[body.expression.name]; + assert.ok(ctor === Error || ctor.prototype instanceof Error, tmpl("Unsupported expect_stdout format [{line},{col}]", { + line: label.start.line, + col: label.start.col + })); + test[label.name] = ctor.apply(null, body.args.map(function(node) { + assert.ok(node instanceof U.AST_Constant, tmpl("Unsupported expect_stdout format [{line},{col}]", { + line: label.start.line, + col: label.start.col + })); + return node.value; + })); + } else { + test[label.name] = read_string(stat) + "\n"; + } + } else { + test[label.name] = stat; + } + return true; + } + }); + block.walk(tw); + return test; + } } -function as_toplevel(input, mangle_options) { - if (!(input instanceof U.AST_BlockStatement)) - throw new Error("Unsupported input syntax"); - for (var i = 0; i < input.body.length; i++) { - var stat = input.body[i]; - if (stat instanceof U.AST_SimpleStatement && stat.body instanceof U.AST_String) - input.body[i] = new U.AST_Directive(stat.body); - else break; +// Try to reminify original input with standard options +// to see if it matches expect_stdout. +function reminify(orig_options, input_code, input_formatted, expect_stdout) { + for (var i = 0; i < minify_options.length; i++) { + var options = JSON.parse(minify_options[i]); + if (options.compress) [ + "keep_fargs", + "keep_fnames", + ].forEach(function(name) { + if (name in orig_options) { + options.compress[name] = orig_options[name]; + } + }); + var options_formatted = JSON.stringify(options, null, 4); + var result = U.minify(input_code, options); + if (result.error) { + log("!!! failed input reminify\n---INPUT---\n{input}\n---OPTIONS---\n{options}\n--ERROR---\n{error}\n\n", { + input: input_formatted, + options: options_formatted, + error: result.error, + }); + return false; + } else { + var stdout = run_code(result.code); + if (typeof expect_stdout != "string" && typeof stdout != "string" && expect_stdout.name == stdout.name) { + stdout = expect_stdout; + } + if (!sandbox.same_stdout(expect_stdout, stdout)) { + log("!!! failed running reminified input\n---INPUT---\n{input}\n---OPTIONS---\n{options}\n---OUTPUT---\n{output}\n---EXPECTED {expected_type}---\n{expected}\n---ACTUAL {actual_type}---\n{actual}\n\n", { + input: input_formatted, + options: options_formatted, + output: result.code, + expected_type: typeof expect_stdout == "string" ? "STDOUT" : "ERROR", + expected: expect_stdout, + actual_type: typeof stdout == "string" ? "STDOUT" : "ERROR", + actual: stdout, + }); + return false; + } + } } - var toplevel = new U.AST_Toplevel(input); - toplevel.figure_out_scope(mangle_options); - return toplevel; + return true; +} + +function run_code(code) { + var result = sandbox.run_code(code, true); + return typeof result == "string" ? result.replace(/\u001b\[\d+m/g, "") : result; } function run_compress_tests() { - var dir = test_directory("compress"); - log_directory("compress"); - var files = find_test_files(dir); - function test_file(file) { - log_start_file(file); + var dir = path.resolve(tests_dir, "compress"); + fs.readdirSync(dir).filter(function(name) { + return /\.js$/i.test(name); + }).forEach(function(file) { + log("--- {file}", { file: file }); function test_case(test) { - log_test(test.name); + log(" Running test [{name}]", { name: test.name }); var output_options = test.beautify || {}; var expect; if (test.expect) { - expect = make_code(as_toplevel(test.expect, test.mangle), output_options); + expect = make_code(to_toplevel(test.expect, test.mangle), output_options); } else { expect = test.expect_exact; } - var input = as_toplevel(test.input, test.mangle); - var input_code = make_code(input, output_options); + var input = to_toplevel(test.input, test.mangle); + var input_code = make_code(input); var input_formatted = make_code(test.input, { beautify: true, quote_style: 3, @@ -209,185 +341,27 @@ function run_compress_tests() { failed_files[file] = 1; } } - } - files.forEach(function(file) { - test_file(file); }); } -function parse_test(file) { - var script = fs.readFileSync(file, "utf8"); - // TODO try/catch can be removed after fixing https://github.com/mishoo/UglifyJS2/issues/348 - try { - var ast = U.parse(script, { - filename: file - }); - } catch (e) { - console.log("Caught error while parsing tests in " + file + "\n"); - console.log(e); - throw e; - } - var tests = {}; - var tw = new U.TreeWalker(function(node, descend) { - if (node instanceof U.AST_LabeledStatement - && tw.parent() instanceof U.AST_Toplevel) { - var name = node.label.name; - if (name in tests) { - throw new Error('Duplicated test name "' + name + '" in ' + file); - } - tests[name] = get_one_test(name, node.body); - return true; - } - if (!(node instanceof U.AST_Toplevel)) croak(node); - }); - ast.walk(tw); - return tests; - - function croak(node) { - throw new Error(tmpl("Can't understand test file {file} [{line},{col}]\n{code}", { - file: file, - line: node.start.line, - col: node.start.col, - code: make_code(node, { beautify: false }) - })); - } - - function read_string(stat) { - if (stat.TYPE == "SimpleStatement") { - var body = stat.body; - switch(body.TYPE) { - case "String": - return body.value; - case "Array": - return body.elements.map(function(element) { - if (element.TYPE !== "String") - throw new Error("Should be array of strings"); - return element.value; - }).join("\n"); - } - } - throw new Error("Should be string or array of strings"); - } - - function get_one_test(name, block) { - var test = { name: name, options: {} }; - var tw = new U.TreeWalker(function(node, descend) { - if (node instanceof U.AST_Assign) { - if (!(node.left instanceof U.AST_SymbolRef)) { - croak(node); - } - var name = node.left.name; - test[name] = evaluate(node.right); - return true; - } - if (node instanceof U.AST_LabeledStatement) { - var label = node.label; - assert.ok( - [ - "input", - "expect", - "expect_exact", - "expect_warnings", - "expect_stdout", - "node_version", - ].indexOf(label.name) >= 0, - tmpl("Unsupported label {name} [{line},{col}]", { - name: label.name, - line: label.start.line, - col: label.start.col - }) - ); - var stat = node.body; - if (label.name == "expect_exact" || label.name == "node_version") { - test[label.name] = read_string(stat); - } else if (label.name == "expect_stdout") { - var body = stat.body; - if (body instanceof U.AST_Boolean) { - test[label.name] = body.value; - } else if (body instanceof U.AST_Call) { - var ctor = global[body.expression.name]; - assert.ok(ctor === Error || ctor.prototype instanceof Error, tmpl("Unsupported expect_stdout format [{line},{col}]", { - line: label.start.line, - col: label.start.col - })); - test[label.name] = ctor.apply(null, body.args.map(function(node) { - assert.ok(node instanceof U.AST_Constant, tmpl("Unsupported expect_stdout format [{line},{col}]", { - line: label.start.line, - col: label.start.col - })); - return node.value; - })); - } else { - test[label.name] = read_string(stat) + "\n"; - } - } else { - test[label.name] = stat; - } - return true; - } - }); - block.walk(tw); - return test; - }; -} - -function make_code(ast, options) { - var stream = U.OutputStream(options); - ast.print(stream); - return stream.get(); -} - -function evaluate(code) { - if (code instanceof U.AST_Node) - code = make_code(code, { beautify: true }); - return new Function("return(" + code + ")")(); -} - -function run_code(code) { - var result = sandbox.run_code(code, true); - return typeof result == "string" ? result.replace(/\u001b\[\d+m/g, "") : result; +function tmpl() { + return U.string_template.apply(null, arguments); } -// Try to reminify original input with standard options -// to see if it matches expect_stdout. -function reminify(orig_options, input_code, input_formatted, expect_stdout) { - for (var i = 0; i < minify_options.length; i++) { - var options = JSON.parse(minify_options[i]); - if (options.compress) [ - "keep_fargs", - "keep_fnames", - ].forEach(function(name) { - if (name in orig_options) { - options.compress[name] = orig_options[name]; - } - }); - var options_formatted = JSON.stringify(options, null, 4); - var result = U.minify(input_code, options); - if (result.error) { - log("!!! failed input reminify\n---INPUT---\n{input}\n---OPTIONS---\n{options}\n--ERROR---\n{error}\n\n", { - input: input_formatted, - options: options_formatted, - error: result.error, - }); - return false; +function to_toplevel(input, mangle_options) { + if (!(input instanceof U.AST_BlockStatement)) throw new Error("Unsupported input syntax"); + var directive = true; + var offset = input.start.line; + var tokens = []; + var toplevel = new U.AST_Toplevel(input.transform(new U.TreeTransformer(function(node) { + if (U.push_uniq(tokens, node.start)) node.start.line -= offset; + if (!directive || node === input) return; + if (node instanceof U.AST_SimpleStatement && node.body instanceof U.AST_String) { + return new U.AST_Directive(node.body); } else { - var stdout = run_code(result.code); - if (typeof expect_stdout != "string" && typeof stdout != "string" && expect_stdout.name == stdout.name) { - stdout = expect_stdout; - } - if (!sandbox.same_stdout(expect_stdout, stdout)) { - log("!!! failed running reminified input\n---INPUT---\n{input}\n---OPTIONS---\n{options}\n---OUTPUT---\n{output}\n---EXPECTED {expected_type}---\n{expected}\n---ACTUAL {actual_type}---\n{actual}\n\n", { - input: input_formatted, - options: options_formatted, - output: result.code, - expected_type: typeof expect_stdout == "string" ? "STDOUT" : "ERROR", - expected: expect_stdout, - actual_type: typeof stdout == "string" ? "STDOUT" : "ERROR", - actual: stdout, - }); - return false; - } + directive = false; } - } - return true; + }))); + toplevel.figure_out_scope(mangle_options); + return toplevel; } diff --git a/tools/exports.js b/tools/exports.js index c09436ab6d..c58f751418 100644 --- a/tools/exports.js +++ b/tools/exports.js @@ -1,6 +1,6 @@ exports["Dictionary"] = Dictionary; -exports["TreeWalker"] = TreeWalker; -exports["TreeTransformer"] = TreeTransformer; exports["minify"] = minify; exports["parse"] = parse; -exports["_push_uniq"] = push_uniq; +exports["push_uniq"] = push_uniq; +exports["TreeTransformer"] = TreeTransformer; +exports["TreeWalker"] = TreeWalker;