From 547f41beba43350970fdbe6a5a3793cb5b607847 Mon Sep 17 00:00:00 2001 From: olsonpm Date: Thu, 11 May 2017 23:29:55 -0500 Subject: [PATCH 01/10] add documentation for `side_effects` & `[#@]__PURE__` (#1925) --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index c50a8ecb..205395f0 100644 --- a/README.md +++ b/README.md @@ -409,6 +409,11 @@ to set `true`; it's effectively a shortcut for `foo=true`). - `keep_infinity` -- default `false`. Pass `true` to prevent `Infinity` from being compressed into `1/0`, which may cause performance issues on Chrome. +- `side_effects` -- default `false`. Pass `true` to potentially drop functions +marked as "pure". (A function is marked as "pure" via the comment annotation +`/* @__PURE__ */` or `/* #__PURE__ */`) + + ### The `unsafe` option It enables some transformations that *might* break code logic in certain From ac73c5d4211b9ecff0f9650a032e964ef1cad585 Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Fri, 12 May 2017 12:34:55 +0800 Subject: [PATCH 02/10] avoid `arguments` and `eval` in `reduce_vars` (#1924) fixes #1922 --- lib/compress.js | 16 ++++++---- test/compress/reduce_vars.js | 57 ++++++++++++++++++++++++++++++++---- 2 files changed, 63 insertions(+), 10 deletions(-) diff --git a/lib/compress.js b/lib/compress.js index aff5c643..1ded032b 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -354,10 +354,14 @@ merge(Compressor.prototype, { // So existing transformation rules can work on them. node.argnames.forEach(function(arg, i) { var d = arg.definition(); - d.fixed = function() { - return iife.args[i] || make_node(AST_Undefined, iife); - }; - mark(d, true); + if (!node.uses_arguments && d.fixed === undefined) { + d.fixed = function() { + return iife.args[i] || make_node(AST_Undefined, iife); + }; + mark(d, true); + } else { + d.fixed = false; + } }); } descend(); @@ -491,7 +495,9 @@ merge(Compressor.prototype, { function reset_def(def) { def.escaped = false; - if (!def.global || def.orig[0] instanceof AST_SymbolConst || compressor.toplevel(def)) { + if (def.scope.uses_eval) { + def.fixed = false; + } else if (!def.global || def.orig[0] instanceof AST_SymbolConst || compressor.toplevel(def)) { def.fixed = undefined; } else { def.fixed = false; diff --git a/test/compress/reduce_vars.js b/test/compress/reduce_vars.js index 5a79f574..d3b3d425 100644 --- a/test/compress/reduce_vars.js +++ b/test/compress/reduce_vars.js @@ -41,20 +41,20 @@ reduce_vars: { var A = 1; (function() { console.log(-3); - console.log(-4); + console.log(A - 5); })(); (function f1() { var a = 2; - console.log(-3); + console.log(a - 5); eval("console.log(a);"); })(); (function f2(eval) { var a = 2; - console.log(-3); + console.log(a - 5); eval("console.log(a);"); })(eval); "yes"; - console.log(2); + console.log(A + 1); } expect_stdout: true } @@ -1749,7 +1749,10 @@ redefine_arguments_3: { console.log(function() { var arguments; return typeof arguments; - }(), "number", "undefined"); + }(), "number", function(x) { + var arguments = x; + return typeof arguments; + }()); } expect_stdout: "object number undefined" } @@ -2461,3 +2464,47 @@ issue_1865: { } expect_stdout: true } + +issue_1922_1: { + options = { + evaluate: true, + reduce_vars: true, + unused: true, + } + input: { + console.log(function(a) { + arguments[0] = 2; + return a; + }(1)); + } + expect: { + console.log(function(a) { + arguments[0] = 2; + return a; + }(1)); + } + expect_stdout: "2" +} + +issue_1922_2: { + options = { + evaluate: true, + reduce_vars: true, + unused: true, + } + input: { + console.log(function() { + var a; + eval("a = 1"); + return a; + }(1)); + } + expect: { + console.log(function() { + var a; + eval("a = 1"); + return a; + }(1)); + } + expect_stdout: "1" +} From c391576d52852322a7fcfbaeabc9d5626e628c8b Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Fri, 12 May 2017 14:57:41 +0800 Subject: [PATCH 03/10] remove support for `const` (#1910) As this is not part of ES5. --- lib/ast.js | 16 +--- lib/compress.js | 19 +--- lib/mozilla-ast.js | 6 +- lib/output.js | 3 - lib/parse.js | 22 +---- lib/scope.js | 7 +- test/compress/collapse_vars.js | 56 +---------- test/compress/const.js | 166 --------------------------------- test/compress/dead-code.js | 125 ------------------------- test/compress/drop-unused.js | 64 ------------- test/compress/evaluate.js | 9 +- test/compress/global_defs.js | 4 +- test/compress/issue-1041.js | 13 --- test/compress/issue-1588.js | 12 --- test/compress/issue-208.js | 6 +- test/compress/loops.js | 44 --------- test/compress/reduce_vars.js | 8 +- test/compress/sequences.js | 24 ----- test/input/invalid/const.js | 8 -- test/mocha/cli.js | 15 --- 20 files changed, 36 insertions(+), 591 deletions(-) delete mode 100644 test/compress/const.js delete mode 100644 test/input/invalid/const.js diff --git a/lib/ast.js b/lib/ast.js index 962c8f7b..57956233 100644 --- a/lib/ast.js +++ b/lib/ast.js @@ -495,10 +495,10 @@ var AST_Finally = DEFNODE("Finally", null, { $documentation: "A `finally` node; only makes sense as part of a `try` statement" }, AST_Block); -/* -----[ VAR/CONST ]----- */ +/* -----[ VAR ]----- */ var AST_Definitions = DEFNODE("Definitions", "definitions", { - $documentation: "Base class for `var` or `const` nodes (variable declarations/initializations)", + $documentation: "Base class for `var` nodes (variable declarations/initializations)", $propdoc: { definitions: "[AST_VarDef*] array of variable definitions" }, @@ -516,14 +516,10 @@ var AST_Var = DEFNODE("Var", null, { $documentation: "A `var` statement" }, AST_Definitions); -var AST_Const = DEFNODE("Const", null, { - $documentation: "A `const` statement" -}, AST_Definitions); - var AST_VarDef = DEFNODE("VarDef", "name value", { $documentation: "A variable declaration; only appears in a AST_Definitions node", $propdoc: { - name: "[AST_SymbolVar|AST_SymbolConst] name of the variable", + name: "[AST_SymbolVar] name of the variable", value: "[AST_Node?] initializer, or null of there's no initializer" }, _walk: function(visitor) { @@ -728,17 +724,13 @@ var AST_SymbolAccessor = DEFNODE("SymbolAccessor", null, { }, AST_Symbol); var AST_SymbolDeclaration = DEFNODE("SymbolDeclaration", "init", { - $documentation: "A declaration symbol (symbol in var/const, function name or argument, symbol in catch)", + $documentation: "A declaration symbol (symbol in var, function name or argument, symbol in catch)", }, AST_Symbol); var AST_SymbolVar = DEFNODE("SymbolVar", null, { $documentation: "Symbol defining a variable", }, AST_SymbolDeclaration); -var AST_SymbolConst = DEFNODE("SymbolConst", null, { - $documentation: "A constant declaration" -}, AST_SymbolDeclaration); - var AST_SymbolFunarg = DEFNODE("SymbolFunarg", null, { $documentation: "Symbol naming a function argument", }, AST_SymbolVar); diff --git a/lib/compress.js b/lib/compress.js index 1ded032b..892bceef 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -479,8 +479,7 @@ merge(Compressor.prototype, { if (def.fixed === false) return false; if (def.fixed != null && (!value || def.references.length > 0)) return false; return !def.orig.some(function(sym) { - return sym instanceof AST_SymbolConst - || sym instanceof AST_SymbolDefun + return sym instanceof AST_SymbolDefun || sym instanceof AST_SymbolLambda; }); } @@ -497,7 +496,7 @@ merge(Compressor.prototype, { def.escaped = false; if (def.scope.uses_eval) { def.fixed = false; - } else if (!def.global || def.orig[0] instanceof AST_SymbolConst || compressor.toplevel(def)) { + } else if (!def.global || compressor.toplevel(def)) { def.fixed = undefined; } else { def.fixed = false; @@ -531,14 +530,6 @@ merge(Compressor.prototype, { return lhs instanceof AST_SymbolRef && lhs.definition().orig[0] instanceof AST_SymbolLambda; } - function is_reference_const(ref) { - if (!(ref instanceof AST_SymbolRef)) return false; - var orig = ref.definition().orig; - for (var i = orig.length; --i >= 0;) { - if (orig[i] instanceof AST_SymbolConst) return true; - } - } - function find_variable(compressor, name) { var scope, i = 0; while (scope = compressor.parent(i++)) { @@ -804,8 +795,7 @@ merge(Compressor.prototype, { return make_node(AST_SymbolRef, expr.name, expr.name); } } else { - var lhs = expr[expr instanceof AST_Assign ? "left" : "expression"]; - return !is_reference_const(lhs) && lhs; + return expr[expr instanceof AST_Assign ? "left" : "expression"]; } } @@ -1988,7 +1978,6 @@ merge(Compressor.prototype, { && node instanceof AST_Assign && node.operator == "=" && node.left instanceof AST_SymbolRef - && !is_reference_const(node.left) && scope === self) { node.right.walk(tw); return true; @@ -3194,7 +3183,7 @@ merge(Compressor.prototype, { && (left.operator == "++" || left.operator == "--")) { left = left.expression; } else left = null; - if (!left || is_lhs_read_only(left) || is_reference_const(left)) { + if (!left || is_lhs_read_only(left)) { expressions[++i] = cdr; continue; } diff --git a/lib/mozilla-ast.js b/lib/mozilla-ast.js index e97d6191..7d246758 100644 --- a/lib/mozilla-ast.js +++ b/lib/mozilla-ast.js @@ -172,7 +172,7 @@ }); }, VariableDeclaration: function(M) { - return new (M.kind === "const" ? AST_Const : AST_Var)({ + return new AST_Var({ start : my_start_token(M), end : my_end_token(M), definitions : M.declarations.map(from_moz) @@ -208,7 +208,7 @@ Identifier: function(M) { var p = FROM_MOZ_STACK[FROM_MOZ_STACK.length - 2]; return new ( p.type == "LabeledStatement" ? AST_Label - : p.type == "VariableDeclarator" && p.id === M ? (p.kind == "const" ? AST_SymbolConst : AST_SymbolVar) + : p.type == "VariableDeclarator" && p.id === M ? AST_SymbolVar : p.type == "FunctionExpression" ? (p.id === M ? AST_SymbolLambda : AST_SymbolFunarg) : p.type == "FunctionDeclaration" ? (p.id === M ? AST_SymbolDefun : AST_SymbolFunarg) : p.type == "CatchClause" ? AST_SymbolCatch @@ -331,7 +331,7 @@ def_to_moz(AST_Definitions, function To_Moz_VariableDeclaration(M) { return { type: "VariableDeclaration", - kind: M instanceof AST_Const ? "const" : "var", + kind: "var", declarations: M.definitions.map(to_moz) }; }); diff --git a/lib/output.js b/lib/output.js index 33f4c533..0b98825f 100644 --- a/lib/output.js +++ b/lib/output.js @@ -1033,9 +1033,6 @@ function OutputStream(options) { DEFPRINT(AST_Var, function(self, output){ self._do_print(output, "var"); }); - DEFPRINT(AST_Const, function(self, output){ - self._do_print(output, "const"); - }); function parenthesize_for_noin(node, output, noin) { if (!noin) node.print(output); diff --git a/lib/parse.js b/lib/parse.js index 3493f8e6..c432ad7a 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -916,9 +916,6 @@ function parse($TEXT, options) { case "var": return tmp = var_(), semicolon(), tmp; - case "const": - return tmp = const_(), semicolon(), tmp; - case "with": if (S.input.has_directive("use strict")) { croak("Strict mode may not include a with statement"); @@ -1153,16 +1150,13 @@ function parse($TEXT, options) { }); }; - function vardefs(no_in, in_const) { + function vardefs(no_in) { var a = []; for (;;) { a.push(new AST_VarDef({ start : S.token, - name : as_symbol(in_const ? AST_SymbolConst : AST_SymbolVar), - value : is("operator", "=") - ? (next(), expression(false, no_in)) - : in_const && S.input.has_directive("use strict") - ? croak("Missing initializer in const declaration") : null, + name : as_symbol(AST_SymbolVar), + value : is("operator", "=") ? (next(), expression(false, no_in)) : null, end : prev() })); if (!is("punc", ",")) @@ -1175,15 +1169,7 @@ function parse($TEXT, options) { var var_ = function(no_in) { return new AST_Var({ start : prev(), - definitions : vardefs(no_in, false), - end : prev() - }); - }; - - var const_ = function() { - return new AST_Const({ - start : prev(), - definitions : vardefs(false, true), + definitions : vardefs(no_in), end : prev() }); }; diff --git a/lib/scope.js b/lib/scope.js index c8d7c6bb..ea08d775 100644 --- a/lib/scope.js +++ b/lib/scope.js @@ -156,8 +156,7 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){ // later. (node.scope = defun.parent_scope).def_function(node); } - else if (node instanceof AST_SymbolVar - || node instanceof AST_SymbolConst) { + else if (node instanceof AST_SymbolVar) { defun.def_variable(node); if (defun !== scope) { node.mark_enclosed(options); @@ -268,7 +267,7 @@ AST_Scope.DEFMETHOD("init_scope_vars", function(parent_scope){ AST_Lambda.DEFMETHOD("init_scope_vars", function(){ AST_Scope.prototype.init_scope_vars.apply(this, arguments); this.uses_arguments = false; - this.def_variable(new AST_SymbolConst({ + this.def_variable(new AST_SymbolFunarg({ name: "arguments", start: this.start, end: this.end @@ -491,8 +490,6 @@ AST_Toplevel.DEFMETHOD("compute_char_frequency", function(options){ } else if (node instanceof AST_Var) base54.consider("var"); - else if (node instanceof AST_Const) - base54.consider("const"); else if (node instanceof AST_Lambda) base54.consider("function"); else if (node instanceof AST_For) diff --git a/test/compress/collapse_vars.js b/test/compress/collapse_vars.js index 0d578d7d..90d7ac93 100644 --- a/test/compress/collapse_vars.js +++ b/test/compress/collapse_vars.js @@ -583,8 +583,8 @@ collapse_vars_assignment: { return a = a; } function f1(c) { - const a = 3 / c; - const b = 1 - a; + var a = 3 / c; + var b = 1 - a; return b; } function f2(c) { @@ -724,10 +724,10 @@ collapse_vars_misc1: { return t; } function f1(x) { var y = 5 - x; return y; } - function f2(x) { const z = foo(), y = z / (5 - x); return y; } + function f2(x) { var z = foo(), y = z / (5 - x); return y; } function f3(x) { var z = foo(), y = (5 - x) / z; return y; } function f4(x) { var z = foo(), y = (5 - u) / z; return y; } - function f5(x) { const z = foo(), y = (5 - window.x) / z; return y; } + function f5(x) { var z = foo(), y = (5 - window.x) / z; return y; } function f6() { var b = window.a * window.z; return b && zap(); } function f7() { var b = window.a * window.z; return b + b; } function f8() { var b = window.a * window.z; var c = b + 5; return b + c; } @@ -744,7 +744,7 @@ collapse_vars_misc1: { function f2(x) { return foo() / (5 - x) } function f3(x) { return (5 - x) / foo() } function f4(x) { var z = foo(); return (5 - u) / z } - function f5(x) { const z = foo(); return (5 - window.x) / z } + function f5(x) { var z = foo(); return (5 - window.x) / z } function f6() { return window.a * window.z && zap() } function f7() { var b = window.a * window.z; return b + b } function f8() { var b = window.a * window.z; return b + (b + 5) } @@ -2186,49 +2186,3 @@ compound_assignment: { } expect_stdout: "4" } - -reassign_const_1: { - options = { - collapse_vars: true, - } - input: { - function f() { - const a = 1; - a = 2; - return a; - } - console.log(f()); - } - expect: { - function f() { - const a = 1; - a = 2; - return a; - } - console.log(f()); - } - expect_stdout: true -} - -reassign_const_2: { - options = { - collapse_vars: true, - } - input: { - function f() { - const a = 1; - ++a; - return a; - } - console.log(f()); - } - expect: { - function f() { - const a = 1; - ++a; - return a; - } - console.log(f()); - } - expect_stdout: true -} diff --git a/test/compress/const.js b/test/compress/const.js deleted file mode 100644 index a88d5946..00000000 --- a/test/compress/const.js +++ /dev/null @@ -1,166 +0,0 @@ -issue_1191: { - options = { - evaluate : true, - booleans : true, - comparisons : true, - dead_code : true, - conditionals : true, - side_effects : true, - unused : true, - hoist_funs : true, - if_return : true, - join_vars : true, - sequences : false, - collapse_vars : false, - reduce_vars : true, - } - input: { - function foo(rot) { - const rotTol = 5; - if (rot < -rotTol || rot > rotTol) - bar(); - baz(); - } - } - expect: { - function foo(rot) { - (rot < -5 || rot > 5) && bar(); - baz(); - } - } -} - -issue_1194: { - options = { - evaluate : true, - booleans : true, - comparisons : true, - dead_code : true, - conditionals : true, - side_effects : true, - unused : true, - hoist_funs : true, - if_return : true, - join_vars : true, - sequences : false, - collapse_vars : false, - reduce_vars : true, - } - input: { - function f1() {const a = "X"; return a + a;} - function f2() {const aa = "X"; return aa + aa;} - function f3() {const aaa = "X"; return aaa + aaa;} - } - expect: { - function f1(){return"XX"} - function f2(){return"XX"} - function f3(){return"XX"} - } -} - -issue_1396: { - options = { - evaluate : true, - booleans : true, - comparisons : true, - dead_code : true, - conditionals : true, - side_effects : true, - unused : true, - hoist_funs : true, - if_return : true, - join_vars : true, - sequences : false, - collapse_vars : false, - reduce_vars : true, - } - input: { - function foo(a) { - const VALUE = 1; - console.log(2 | VALUE); - console.log(VALUE + 1); - console.log(VALUE); - console.log(a & VALUE); - } - function bar() { - const s = "01234567890123456789"; - console.log(s + s + s + s + s); - - const CONSTANT = "abc"; - console.log(CONSTANT + CONSTANT + CONSTANT + CONSTANT + CONSTANT); - } - } - expect: { - function foo(a) { - console.log(3); - console.log(2); - console.log(1); - console.log(1 & a); - } - function bar() { - const s = "01234567890123456789"; - console.log(s + s + s + s + s); - - console.log("abcabcabcabcabc"); - } - } -} - -unused_regexp_literal: { - options = { - evaluate : true, - booleans : true, - comparisons : true, - dead_code : true, - conditionals : true, - side_effects : true, - unused : true, - hoist_funs : true, - if_return : true, - join_vars : true, - sequences : false, - collapse_vars : false, - } - input: { - function f(){ var a = /b/; } - } - expect: { - function f(){} - } -} - -regexp_literal_not_const: { - options = { - evaluate : true, - booleans : true, - comparisons : true, - dead_code : true, - conditionals : true, - side_effects : true, - unused : true, - hoist_funs : true, - if_return : true, - join_vars : true, - sequences : false, - collapse_vars : false, - reduce_vars : true, - } - input: { - (function(){ - var result; - const s = 'acdabcdeabbb'; - const REGEXP_LITERAL = /ab*/g; - while (result = REGEXP_LITERAL.exec(s)) { - console.log(result[0]); - } - })(); - } - expect: { - (function() { - var result; - const REGEXP_LITERAL = /ab*/g; - while (result = REGEXP_LITERAL.exec("acdabcdeabbb")) console.log(result[0]); - })(); - } - expect_stdout: true -} diff --git a/test/compress/dead-code.js b/test/compress/dead-code.js index bb72451c..31d0664a 100644 --- a/test/compress/dead-code.js +++ b/test/compress/dead-code.js @@ -90,131 +90,6 @@ dead_code_constant_boolean_should_warn_more: { expect_stdout: true } -dead_code_const_declaration: { - options = { - dead_code : true, - loops : true, - booleans : true, - conditionals : true, - evaluate : true, - reduce_vars : true, - }; - input: { - var unused; - const CONST_FOO = false; - if (CONST_FOO) { - console.log("unreachable"); - var moo; - function bar() {} - } - } - expect: { - var unused; - const CONST_FOO = !1; - var moo; - function bar() {} - } - expect_stdout: true -} - -dead_code_const_annotation: { - options = { - dead_code : true, - loops : true, - booleans : true, - conditionals : true, - evaluate : true, - reduce_vars : true, - toplevel : true, - }; - input: { - var unused; - /** @const */ var CONST_FOO_ANN = false; - if (CONST_FOO_ANN) { - console.log("unreachable"); - var moo; - function bar() {} - } - } - expect: { - var unused; - var CONST_FOO_ANN = !1; - var moo; - function bar() {} - } - expect_stdout: true -} - -dead_code_const_annotation_regex: { - options = { - dead_code : true, - loops : true, - booleans : true, - conditionals : true, - evaluate : true - }; - input: { - var unused; - // @constraint this shouldn't be a constant - var CONST_FOO_ANN = false; - if (CONST_FOO_ANN) { - console.log("reachable"); - } - } - expect: { - var unused; - var CONST_FOO_ANN = !1; - CONST_FOO_ANN && console.log('reachable'); - } - expect_stdout: true -} - -dead_code_const_annotation_complex_scope: { - options = { - dead_code : true, - loops : true, - booleans : true, - conditionals : true, - evaluate : true, - reduce_vars : true, - toplevel : true, - }; - input: { - var unused_var; - /** @const */ var test = 'test'; - // @const - var CONST_FOO_ANN = false; - var unused_var_2; - if (CONST_FOO_ANN) { - console.log("unreachable"); - var moo; - function bar() {} - } - if (test === 'test') { - var beef = 'good'; - /** @const */ var meat = 'beef'; - var pork = 'bad'; - if (meat === 'pork') { - console.log('also unreachable'); - } else if (pork === 'good') { - console.log('reached, not const'); - } - } - } - expect: { - var unused_var; - var test = 'test'; - var CONST_FOO_ANN = !1; - var unused_var_2; - var moo; - function bar() {} - var beef = 'good'; - var meat = 'beef'; - var pork = 'bad'; - } - expect_stdout: true -} - try_catch_finally: { options = { conditionals: true, diff --git a/test/compress/drop-unused.js b/test/compress/drop-unused.js index 96bd336c..2ef6f796 100644 --- a/test/compress/drop-unused.js +++ b/test/compress/drop-unused.js @@ -649,37 +649,6 @@ drop_value: { } } -const_assign: { - options = { - evaluate: true, - reduce_vars: true, - unused: true, - } - input: { - function f() { - const b = 2; - return 1 + b; - } - - function g() { - const b = 2; - b = 3; - return 1 + b; - } - } - expect: { - function f() { - return 3; - } - - function g() { - const b = 2; - b = 3; - return 1 + b; - } - } -} - issue_1539: { options = { cascade: true, @@ -816,10 +785,6 @@ issue_1709: { var x = 1; return x; }(), - function y() { - const y = 2; - return y; - }(), function z() { function z() {} return z; @@ -832,10 +797,6 @@ issue_1709: { var x = 1; return x; }(), - function() { - const y = 2; - return y; - }(), function() { function z() {} return z; @@ -1147,28 +1108,3 @@ var_catch_toplevel: { }(); } } - -reassign_const: { - options = { - cascade: true, - sequences: true, - side_effects: true, - unused: true, - } - input: { - function f() { - const a = 1; - a = 2; - return a; - } - console.log(f()); - } - expect: { - function f() { - const a = 1; - return a = 2, a; - } - console.log(f()); - } - expect_stdout: true -} diff --git a/test/compress/evaluate.js b/test/compress/evaluate.js index 585ee2b9..b8077564 100644 --- a/test/compress/evaluate.js +++ b/test/compress/evaluate.js @@ -645,16 +645,17 @@ call_args: { options = { evaluate: true, reduce_vars: true, + toplevel: true, } input: { - const a = 1; + var a = 1; console.log(a); +function(a) { return a; }(a); } expect: { - const a = 1; + var a = 1; console.log(1); +(1, 1); } @@ -666,17 +667,17 @@ call_args_drop_param: { evaluate: true, keep_fargs: false, reduce_vars: true, + toplevel: true, unused: true, } input: { - const a = 1; + var a = 1; console.log(a); +function(a) { return a; }(a, b); } expect: { - const a = 1; console.log(1); +(b, 1); } diff --git a/test/compress/global_defs.js b/test/compress/global_defs.js index f1ba8f32..bfd1d5f6 100644 --- a/test/compress/global_defs.js +++ b/test/compress/global_defs.js @@ -120,7 +120,7 @@ mixed: { properties: true, } input: { - const FOO = { BAR: 0 }; + var FOO = { BAR: 0 }; console.log(FOO.BAR); console.log(++CONFIG.DEBUG); console.log(++CONFIG.VALUE); @@ -130,7 +130,7 @@ mixed: { console.log(CONFIG); } expect: { - const FOO = { BAR: 0 }; + var FOO = { BAR: 0 }; console.log("moo"); console.log(++CONFIG.DEBUG); console.log(++CONFIG.VALUE); diff --git a/test/compress/issue-1041.js b/test/compress/issue-1041.js index cdbc22cc..cc351405 100644 --- a/test/compress/issue-1041.js +++ b/test/compress/issue-1041.js @@ -1,16 +1,3 @@ -const_declaration: { - options = { - evaluate: true - }; - - input: { - const goog = goog || {}; - } - expect: { - const goog = goog || {}; - } -} - const_pragma: { options = { evaluate: true, diff --git a/test/compress/issue-1588.js b/test/compress/issue-1588.js index 4e20a21d..187d9f6c 100644 --- a/test/compress/issue-1588.js +++ b/test/compress/issue-1588.js @@ -85,15 +85,3 @@ unsafe_undefined: { } expect_stdout: true } - -runtime_error: { - input: { - const a = 1; - console.log(a++); - } - expect: { - const a = 1; - console.log(a++); - } - expect_stdout: true -} diff --git a/test/compress/issue-208.js b/test/compress/issue-208.js index fb9861f6..faaf4139 100644 --- a/test/compress/issue-208.js +++ b/test/compress/issue-208.js @@ -38,7 +38,7 @@ mixed: { } } input: { - const ENV = 3; + var ENV = 3; var FOO = 4; f(ENV * 10); --FOO; @@ -49,7 +49,7 @@ mixed: { x = DEBUG; } expect: { - const ENV = 3; + var ENV = 3; var FOO = 4; f(10); --FOO; @@ -60,7 +60,7 @@ mixed: { x = 0; } expect_warnings: [ - 'WARN: global_defs ENV redefined [test/compress/issue-208.js:41,14]', + 'WARN: global_defs ENV redefined [test/compress/issue-208.js:41,12]', 'WARN: global_defs FOO redefined [test/compress/issue-208.js:42,12]', 'WARN: global_defs FOO redefined [test/compress/issue-208.js:44,10]', 'WARN: global_defs DEBUG redefined [test/compress/issue-208.js:45,8]', diff --git a/test/compress/loops.js b/test/compress/loops.js index 4d354bcf..89c7e7e9 100644 --- a/test/compress/loops.js +++ b/test/compress/loops.js @@ -146,50 +146,6 @@ parse_do_while_without_semicolon: { } } - -keep_collapse_const_in_own_block_scope: { - options = { - join_vars: true, - loops: true - } - input: { - var i=2; - const c=5; - while(i--) - console.log(i); - console.log(c); - } - expect: { - var i=2; - const c=5; - for(;i--;) - console.log(i); - console.log(c); - } - expect_stdout: true -} - -keep_collapse_const_in_own_block_scope_2: { - options = { - join_vars: true, - loops: true - } - input: { - const c=5; - var i=2; // Moves to loop, while it did not in previous test - while(i--) - console.log(i); - console.log(c); - } - expect: { - const c=5; - for(var i=2;i--;) - console.log(i); - console.log(c); - } - expect_stdout: true -} - evaluate: { options = { loops: true, diff --git a/test/compress/reduce_vars.js b/test/compress/reduce_vars.js index d3b3d425..a0433b5d 100644 --- a/test/compress/reduce_vars.js +++ b/test/compress/reduce_vars.js @@ -2190,10 +2190,11 @@ issue_1814_1: { options = { evaluate: true, reduce_vars: true, + toplevel: true, unused: true, } input: { - const a = 42; + var a = 42; !function() { var b = a; !function(a) { @@ -2202,7 +2203,6 @@ issue_1814_1: { }(); } expect: { - const a = 42; !function() { !function(a) { console.log(a++, 42); @@ -2216,10 +2216,11 @@ issue_1814_2: { options = { evaluate: true, reduce_vars: true, + toplevel: true, unused: true, } input: { - const a = "32"; + var a = "32"; !function() { var b = a + 1; !function(a) { @@ -2228,7 +2229,6 @@ issue_1814_2: { }(); } expect: { - const a = "32"; !function() { !function(a) { console.log(a++, "321"); diff --git a/test/compress/sequences.js b/test/compress/sequences.js index 9edf627e..10492565 100644 --- a/test/compress/sequences.js +++ b/test/compress/sequences.js @@ -710,27 +710,3 @@ issue_27: { })(jQuery); } } - -reassign_const: { - options = { - cascade: true, - sequences: true, - side_effects: true, - } - input: { - function f() { - const a = 1; - a++; - return a; - } - console.log(f()); - } - expect: { - function f() { - const a = 1; - return a++, a; - } - console.log(f()); - } - expect_stdout: true -} diff --git a/test/input/invalid/const.js b/test/input/invalid/const.js deleted file mode 100644 index 7a2bfd3d..00000000 --- a/test/input/invalid/const.js +++ /dev/null @@ -1,8 +0,0 @@ -function f() { - const a; -} - -function g() { - "use strict"; - const a; -} diff --git a/test/mocha/cli.js b/test/mocha/cli.js index 8cf53ab4..38f61f39 100644 --- a/test/mocha/cli.js +++ b/test/mocha/cli.js @@ -379,21 +379,6 @@ describe("bin/uglifyjs", function () { done(); }); }); - it("Should throw syntax error (const a)", function(done) { - var command = uglifyjscmd + ' test/input/invalid/const.js'; - - exec(command, function (err, stdout, stderr) { - assert.ok(err); - assert.strictEqual(stdout, ""); - assert.strictEqual(stderr.split(/\n/).slice(0, 4).join("\n"), [ - "Parse error at test/input/invalid/const.js:7,11", - " const a;", - " ^", - "ERROR: Missing initializer in const declaration" - ].join("\n")); - done(); - }); - }); it("Should throw syntax error (delete x)", function(done) { var command = uglifyjscmd + ' test/input/invalid/delete.js'; From 9e29b6dad21ab796dbf67d6886c198f3be7a29a0 Mon Sep 17 00:00:00 2001 From: olsonpm Date: Fri, 12 May 2017 23:54:01 -0500 Subject: [PATCH 04/10] clarify wording (#1931) --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 205395f0..33a9c716 100644 --- a/README.md +++ b/README.md @@ -410,8 +410,9 @@ to set `true`; it's effectively a shortcut for `foo=true`). being compressed into `1/0`, which may cause performance issues on Chrome. - `side_effects` -- default `false`. Pass `true` to potentially drop functions -marked as "pure". (A function is marked as "pure" via the comment annotation -`/* @__PURE__ */` or `/* #__PURE__ */`) +marked as "pure". A function call is marked as "pure" if a comment annotation +`/*@__PURE__*/` or `/*#__PURE__*/` immediately precedes the call. For example: +`/*@__PURE__*/foo()`; ### The `unsafe` option From fd0951231cce6aa2710bdb4f0ff8a963596f842c Mon Sep 17 00:00:00 2001 From: olsonpm Date: Fri, 12 May 2017 23:54:32 -0500 Subject: [PATCH 05/10] document 3 max passes (#1928) --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 33a9c716..6f24b0a2 100644 --- a/README.md +++ b/README.md @@ -402,9 +402,9 @@ to set `true`; it's effectively a shortcut for `foo=true`). compressor from discarding function names. Useful for code relying on `Function.prototype.name`. See also: the `keep_fnames` [mangle option](#mangle). -- `passes` -- default `1`. Number of times to run compress. Use an - integer argument larger than 1 to further reduce code size in some cases. - Note: raising the number of passes will increase uglify compress time. +- `passes` -- default `1`. Number of times to run compress with a maximum of 3. + In some cases more than one pass leads to further compressed code. Keep in + mind more passes will take more time. - `keep_infinity` -- default `false`. Pass `true` to prevent `Infinity` from being compressed into `1/0`, which may cause performance issues on Chrome. From 3ca902258c24209699f0b5bd5b9654252e492272 Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Sun, 14 May 2017 02:10:34 +0800 Subject: [PATCH 06/10] fix bugs with getter/setter (#1926) - `reduce_vars` - `side_effects` - property access for object - `AST_SymbolAccessor` as key names enhance `test/ufuzz.js` - add object getter & setter - property assignment to setter - avoid infinite recursion in setter - fix & adjust assignment operators - 50% `=` - 25% `+=` - 2.5% each for the rest - avoid "Invalid array length" - fix `console.log()` - bypass getter - curb recursive reference - deprecate `-E`, always report runtime errors --- lib/ast.js | 4 +- lib/compress.js | 49 +++++++----- lib/parse.js | 9 ++- lib/scope.js | 5 -- test/compress/dead-code.js | 16 ++++ test/compress/pure_getters.js | 59 ++++++++++++++ test/compress/reduce_vars.js | 29 +++++++ test/mocha/accessorTokens-1492.js | 2 +- test/mocha/getter-setter.js | 2 +- test/sandbox.js | 12 ++- test/ufuzz.js | 125 +++++++++++++++++++++--------- 11 files changed, 244 insertions(+), 68 deletions(-) diff --git a/lib/ast.js b/lib/ast.js index 57956233..2972b7aa 100644 --- a/lib/ast.js +++ b/lib/ast.js @@ -685,8 +685,8 @@ var AST_Object = DEFNODE("Object", "properties", { var AST_ObjectProperty = DEFNODE("ObjectProperty", "key value", { $documentation: "Base class for literal object properties", $propdoc: { - key: "[string] the property name converted to a string for ObjectKeyVal. For setters and getters this is an arbitrary AST_Node.", - value: "[AST_Node] property value. For setters and getters this is an AST_Function." + key: "[string] the property name converted to a string for ObjectKeyVal. For setters and getters this is an AST_SymbolAccessor.", + value: "[AST_Node] property value. For setters and getters this is an AST_Accessor." }, _walk: function(visitor) { return visitor._visit(this, function(){ diff --git a/lib/compress.js b/lib/compress.js index 892bceef..f168b942 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -368,6 +368,13 @@ merge(Compressor.prototype, { pop(); return true; } + if (node instanceof AST_Accessor) { + var save_ids = safe_ids; + safe_ids = Object.create(null); + descend(); + safe_ids = save_ids; + return true; + } if (node instanceof AST_Binary && (node.operator == "&&" || node.operator == "||")) { node.left.walk(tw); @@ -1220,12 +1227,12 @@ merge(Compressor.prototype, { && !node.expression.has_side_effects(compressor); } - // may_eq_null() - // returns true if this node may evaluate to null or undefined + // may_throw_on_access() + // returns true if this node may be null, undefined or contain `AST_Accessor` (function(def) { - AST_Node.DEFMETHOD("may_eq_null", function(compressor) { + AST_Node.DEFMETHOD("may_throw_on_access", function(compressor) { var pure_getters = compressor.option("pure_getters"); - return !pure_getters || this._eq_null(pure_getters); + return !pure_getters || this._throw_on_access(pure_getters); }); function is_strict(pure_getters) { @@ -1237,7 +1244,12 @@ merge(Compressor.prototype, { def(AST_Undefined, return_true); def(AST_Constant, return_false); def(AST_Array, return_false); - def(AST_Object, return_false); + def(AST_Object, function(pure_getters) { + if (!is_strict(pure_getters)) return false; + for (var i = this.properties.length; --i >=0;) + if (this.properties[i].value instanceof AST_Accessor) return true; + return false; + }); def(AST_Function, return_false); def(AST_UnaryPostfix, return_false); def(AST_UnaryPrefix, function() { @@ -1246,33 +1258,33 @@ merge(Compressor.prototype, { def(AST_Binary, function(pure_getters) { switch (this.operator) { case "&&": - return this.left._eq_null(pure_getters); + return this.left._throw_on_access(pure_getters); case "||": - return this.left._eq_null(pure_getters) - && this.right._eq_null(pure_getters); + return this.left._throw_on_access(pure_getters) + && this.right._throw_on_access(pure_getters); default: return false; } }) def(AST_Assign, function(pure_getters) { return this.operator == "=" - && this.right._eq_null(pure_getters); + && this.right._throw_on_access(pure_getters); }) def(AST_Conditional, function(pure_getters) { - return this.consequent._eq_null(pure_getters) - || this.alternative._eq_null(pure_getters); + return this.consequent._throw_on_access(pure_getters) + || this.alternative._throw_on_access(pure_getters); }) def(AST_Sequence, function(pure_getters) { - return this.expressions[this.expressions.length - 1]._eq_null(pure_getters); + return this.expressions[this.expressions.length - 1]._throw_on_access(pure_getters); }); def(AST_SymbolRef, function(pure_getters) { if (this.is_undefined) return true; if (!is_strict(pure_getters)) return false; var fixed = this.fixed_value(); - return !fixed || fixed._eq_null(pure_getters); + return !fixed || fixed._throw_on_access(pure_getters); }); })(function(node, func) { - node.DEFMETHOD("_eq_null", func); + node.DEFMETHOD("_throw_on_access", func); }); /* -----[ boolean/negation helpers ]----- */ @@ -1813,11 +1825,11 @@ merge(Compressor.prototype, { return any(this.elements, compressor); }); def(AST_Dot, function(compressor){ - return this.expression.may_eq_null(compressor) + return this.expression.may_throw_on_access(compressor) || this.expression.has_side_effects(compressor); }); def(AST_Sub, function(compressor){ - return this.expression.may_eq_null(compressor) + return this.expression.may_throw_on_access(compressor) || this.expression.has_side_effects(compressor) || this.property.has_side_effects(compressor); }); @@ -2370,6 +2382,7 @@ merge(Compressor.prototype, { var args = trim(this.args, compressor, first_in_statement); return args && make_sequence(this, args); }); + def(AST_Accessor, return_null); def(AST_Function, return_null); def(AST_Binary, function(compressor, first_in_statement){ var right = this.right.drop_side_effect_free(compressor); @@ -2437,11 +2450,11 @@ merge(Compressor.prototype, { return values && make_sequence(this, values); }); def(AST_Dot, function(compressor, first_in_statement){ - if (this.expression.may_eq_null(compressor)) return this; + if (this.expression.may_throw_on_access(compressor)) return this; return this.expression.drop_side_effect_free(compressor, first_in_statement); }); def(AST_Sub, function(compressor, first_in_statement){ - if (this.expression.may_eq_null(compressor)) return this; + if (this.expression.may_throw_on_access(compressor)) return this; var expression = this.expression.drop_side_effect_free(compressor, first_in_statement); if (!expression) return this.property.drop_side_effect_free(compressor, first_in_statement); var property = this.property.drop_side_effect_free(compressor); diff --git a/lib/parse.js b/lib/parse.js index c432ad7a..f1089501 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -1310,10 +1310,15 @@ function parse($TEXT, options) { var type = start.type; var name = as_property_name(); if (type == "name" && !is("punc", ":")) { + var key = new AST_SymbolAccessor({ + start: S.token, + name: as_property_name(), + end: prev() + }); if (name == "get") { a.push(new AST_ObjectGetter({ start : start, - key : as_atom_node(), + key : key, value : create_accessor(), end : prev() })); @@ -1322,7 +1327,7 @@ function parse($TEXT, options) { if (name == "set") { a.push(new AST_ObjectSetter({ start : start, - key : as_atom_node(), + key : key, value : create_accessor(), end : prev() })); diff --git a/lib/scope.js b/lib/scope.js index ea08d775..14ffb46f 100644 --- a/lib/scope.js +++ b/lib/scope.js @@ -360,11 +360,6 @@ AST_Symbol.DEFMETHOD("unmangleable", function(options){ return this.definition().unmangleable(options); }); -// property accessors are not mangleable -AST_SymbolAccessor.DEFMETHOD("unmangleable", function(){ - return true; -}); - // labels are always mangleable AST_Label.DEFMETHOD("unmangleable", function(){ return false; diff --git a/test/compress/dead-code.js b/test/compress/dead-code.js index 31d0664a..00dac069 100644 --- a/test/compress/dead-code.js +++ b/test/compress/dead-code.js @@ -131,3 +131,19 @@ try_catch_finally: { "1", ] } + +accessor: { + options = { + side_effects: true, + } + input: { + ({ + get a() {}, + set a(v){ + this.b = 2; + }, + b: 1 + }); + } + expect: {} +} diff --git a/test/compress/pure_getters.js b/test/compress/pure_getters.js index 846b53c3..0ca6a804 100644 --- a/test/compress/pure_getters.js +++ b/test/compress/pure_getters.js @@ -119,3 +119,62 @@ chained: { a.b.c; } } + +impure_getter_1: { + options = { + pure_getters: "strict", + side_effects: true, + } + input: { + ({ + get a() { + console.log(1); + }, + b: 1 + }).a; + ({ + get a() { + console.log(1); + }, + b: 1 + }).b; + } + expect: { + ({ + get a() { + console.log(1); + }, + b: 1 + }).a; + ({ + get a() { + console.log(1); + }, + b: 1 + }).b; + } + expect_stdout: "1" +} + +impure_getter_2: { + options = { + pure_getters: true, + side_effects: true, + } + input: { + // will produce incorrect output because getter is not pure + ({ + get a() { + console.log(1); + }, + b: 1 + }).a; + ({ + get a() { + console.log(1); + }, + b: 1 + }).b; + } + expect: {} +} diff --git a/test/compress/reduce_vars.js b/test/compress/reduce_vars.js index a0433b5d..2a44492a 100644 --- a/test/compress/reduce_vars.js +++ b/test/compress/reduce_vars.js @@ -2508,3 +2508,32 @@ issue_1922_2: { } expect_stdout: "1" } + +accessor: { + options = { + evaluate: true, + reduce_vars: true, + toplevel: true, + } + input: { + var a = 1; + console.log({ + get a() { + a = 2; + return a; + }, + b: 1 + }.b, a); + } + expect: { + var a = 1; + console.log({ + get a() { + a = 2; + return a; + }, + b: 1 + }.b, a); + } + expect_stdout: "1 1" +} diff --git a/test/mocha/accessorTokens-1492.js b/test/mocha/accessorTokens-1492.js index 2b5bbeaa..250d9b97 100644 --- a/test/mocha/accessorTokens-1492.js +++ b/test/mocha/accessorTokens-1492.js @@ -16,7 +16,7 @@ describe("Accessor tokens", function() { assert.equal(node.start.pos, 12); assert.equal(node.end.endpos, 46); - assert(node.key instanceof UglifyJS.AST_SymbolRef); + assert(node.key instanceof UglifyJS.AST_SymbolAccessor); assert.equal(node.key.start.pos, 16); assert.equal(node.key.end.endpos, 22); diff --git a/test/mocha/getter-setter.js b/test/mocha/getter-setter.js index 83bf5792..fe0481b3 100644 --- a/test/mocha/getter-setter.js +++ b/test/mocha/getter-setter.js @@ -71,7 +71,7 @@ describe("Getters and setters", function() { var fail = function(data) { return function (e) { return e instanceof UglifyJS.JS_Parse_Error && - e.message === "Invalid getter/setter name: " + data.operator; + e.message === "Unexpected token: operator (" + data.operator + ")"; }; }; diff --git a/test/sandbox.js b/test/sandbox.js index eb9f1f0f..c155f91c 100644 --- a/test/sandbox.js +++ b/test/sandbox.js @@ -1,14 +1,16 @@ var vm = require("vm"); -function safe_log(arg) { +function safe_log(arg, level) { if (arg) switch (typeof arg) { case "function": return arg.toString(); case "object": if (/Error$/.test(arg.name)) return arg.toString(); arg.constructor.toString(); - for (var key in arg) { - arg[key] = safe_log(arg[key]); + if (level--) for (var key in arg) { + if (!Object.getOwnPropertyDescriptor(arg, key).get) { + arg[key] = safe_log(arg[key], level); + } } } return arg; @@ -48,7 +50,9 @@ exports.run_code = function(code) { ].join("\n"), { console: { log: function() { - return console.log.apply(console, [].map.call(arguments, safe_log)); + return console.log.apply(console, [].map.call(arguments, function(arg) { + return safe_log(arg, 3); + })); } } }, { timeout: 5000 }); diff --git a/test/ufuzz.js b/test/ufuzz.js index 0a043c2f..6e716c5b 100644 --- a/test/ufuzz.js +++ b/test/ufuzz.js @@ -48,7 +48,6 @@ var STMT_COUNT_FROM_GLOBAL = true; // count statement depth from nearest functio var num_iterations = +process.argv[2] || 1/0; var verbose = false; // log every generated test var verbose_interval = false; // log every 100 generated tests -var verbose_error = false; var use_strict = false; for (var i = 2; i < process.argv.length; ++i) { switch (process.argv[i]) { @@ -58,9 +57,6 @@ for (var i = 2; i < process.argv.length; ++i) { case '-V': verbose_interval = true; break; - case '-E': - verbose_error = true; - break; case '-t': MAX_GENERATED_TOPLEVELS_PER_RUN = +process.argv[++i]; if (!MAX_GENERATED_TOPLEVELS_PER_RUN) throw new Error('Must generate at least one toplevel per run'); @@ -103,7 +99,6 @@ for (var i = 2; i < process.argv.length; ++i) { console.log(': generate this many cases (if used must be first arg)'); console.log('-v: print every generated test case'); console.log('-V: print every 100th generated test case'); - console.log('-E: print generated test case with runtime error'); console.log('-t : generate this many toplevels per run (more take longer)'); console.log('-r : maximum recursion depth for generator (higher takes longer)'); console.log('-s1 : force the first level statement to be this one (see list below)'); @@ -192,12 +187,33 @@ var ASSIGNMENTS = [ '=', '=', '=', + '=', + '=', + '=', + '=', + + '=', + '=', + '=', + '=', + '=', + '=', + '=', + '=', + '=', + '=', - '==', - '!=', - '===', - '!==', '+=', + '+=', + '+=', + '+=', + '+=', + '+=', + '+=', + '+=', + '+=', + '+=', + '-=', '*=', '/=', @@ -207,7 +223,8 @@ var ASSIGNMENTS = [ '<<=', '>>=', '>>>=', - '%=' ]; + '%=', +]; var UNARY_SAFE = [ '+', @@ -359,7 +376,6 @@ function createFunction(recurmax, inGlobal, noDecl, canThrow, stmtDepth) { // avoid "function statements" (decl inside statements) else if (inGlobal || rng(10) > 0) s += 'var ' + createVarName(MANDATORY) + ' = ' + name + '(' + createArgs() + ');'; - return s; } @@ -406,7 +422,7 @@ function getLabel(label) { return label && " L" + label; } -function createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth) { +function createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth, target) { ++stmtDepth; var loop = ++loops; if (--recurmax < 0) { @@ -414,10 +430,11 @@ function createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn } // allow to forcefully generate certain structures at first or second recursion level - var target = 0; - if (stmtDepth === 1 && STMT_FIRST_LEVEL_OVERRIDE >= 0) target = STMT_FIRST_LEVEL_OVERRIDE; - else if (stmtDepth === 2 && STMT_SECOND_LEVEL_OVERRIDE >= 0) target = STMT_SECOND_LEVEL_OVERRIDE; - else target = STMTS_TO_USE[rng(STMTS_TO_USE.length)]; + if (target === undefined) { + if (stmtDepth === 1 && STMT_FIRST_LEVEL_OVERRIDE >= 0) target = STMT_FIRST_LEVEL_OVERRIDE; + else if (stmtDepth === 2 && STMT_SECOND_LEVEL_OVERRIDE >= 0) target = STMT_SECOND_LEVEL_OVERRIDE; + else target = STMTS_TO_USE[rng(STMTS_TO_USE.length)]; + } switch (target) { case STMT_BLOCK: @@ -636,7 +653,7 @@ function _createExpression(recurmax, noComma, stmtDepth, canThrow) { strictMode() ); if (instantiate) for (var i = rng(4); --i >= 0;) { - if (rng(2)) s.push('this.' + getDotKey() + createAssignment() + _createBinaryExpr(recurmax, noComma, stmtDepth, canThrow) + ';'); + if (rng(2)) s.push('this.' + getDotKey(true) + createAssignment() + _createBinaryExpr(recurmax, noComma, stmtDepth, canThrow) + ';'); else s.push('this[' + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ']' + createAssignment() + _createBinaryExpr(recurmax, noComma, stmtDepth, canThrow) + ';'); } s.push( @@ -689,19 +706,19 @@ function _createExpression(recurmax, noComma, stmtDepth, canThrow) { ") || " + rng(10) + ").toString()[" + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + "] "; case p++: - return createArrayLiteral(recurmax, COMMA_OK, stmtDepth, canThrow); + return createArrayLiteral(recurmax, stmtDepth, canThrow); case p++: - return createObjectLiteral(recurmax, COMMA_OK, stmtDepth, canThrow); + return createObjectLiteral(recurmax, stmtDepth, canThrow); case p++: - return createArrayLiteral(recurmax, COMMA_OK, stmtDepth, canThrow) + '[' + + return createArrayLiteral(recurmax, stmtDepth, canThrow) + '[' + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ']'; case p++: - return createObjectLiteral(recurmax, COMMA_OK, stmtDepth, canThrow) + '[' + + return createObjectLiteral(recurmax, stmtDepth, canThrow) + '[' + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ']'; case p++: - return createArrayLiteral(recurmax, COMMA_OK, stmtDepth, canThrow) + '.' + getDotKey(); + return createArrayLiteral(recurmax, stmtDepth, canThrow) + '.' + getDotKey(); case p++: - return createObjectLiteral(recurmax, COMMA_OK, stmtDepth, canThrow) + '.' + getDotKey(); + return createObjectLiteral(recurmax, stmtDepth, canThrow) + '.' + getDotKey(); case p++: var name = getVarName(); return name + ' && ' + name + '[' + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ']'; @@ -713,7 +730,7 @@ function _createExpression(recurmax, noComma, stmtDepth, canThrow) { return _createExpression(recurmax, noComma, stmtDepth, canThrow); } -function createArrayLiteral(recurmax, noComma, stmtDepth, canThrow) { +function createArrayLiteral(recurmax, stmtDepth, canThrow) { recurmax--; var arr = "["; for (var i = rng(6); --i >= 0;) { @@ -746,18 +763,56 @@ var KEYS = [ "3", ].concat(SAFE_KEYS); -function getDotKey() { - return SAFE_KEYS[rng(SAFE_KEYS.length)]; +function getDotKey(assign) { + var key; + do { + key = SAFE_KEYS[rng(SAFE_KEYS.length)]; + } while (assign && key == "length"); + return key; } -function createObjectLiteral(recurmax, noComma, stmtDepth, canThrow) { - recurmax--; - var obj = "({"; - for (var i = rng(6); --i >= 0;) { - var key = KEYS[rng(KEYS.length)]; - obj += key + ":(" + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + "), "; +function createAccessor(recurmax, stmtDepth, canThrow) { + var namesLenBefore = VAR_NAMES.length; + var s; + var prop1 = getDotKey(); + if (rng(2) == 0) { + s = [ + 'get ' + prop1 + '(){', + strictMode(), + createStatements(2, recurmax, canThrow, CANNOT_BREAK, CANNOT_CONTINUE, CAN_RETURN, stmtDepth), + createStatement(recurmax, canThrow, CANNOT_BREAK, CANNOT_CONTINUE, CAN_RETURN, stmtDepth, STMT_RETURN_ETC), + '},' + ].join('\n'); + } else { + var prop2; + do { + prop2 = getDotKey(); + } while (prop1 == prop2); + s = [ + 'set ' + prop1 + '(' + createVarName(MANDATORY) + '){', + strictMode(), + createStatements(2, recurmax, canThrow, CANNOT_BREAK, CANNOT_CONTINUE, CAN_RETURN, stmtDepth), + 'this.' + prop2 + createAssignment() + _createBinaryExpr(recurmax, COMMA_OK, stmtDepth, canThrow) + ';', + '},' + ].join('\n'); } - return obj + "})"; + VAR_NAMES.length = namesLenBefore; + return s; +} + +function createObjectLiteral(recurmax, stmtDepth, canThrow) { + recurmax--; + var obj = ['({']; + for (var i = rng(6); --i >= 0;) { + if (rng(20) == 0) { + obj.push(createAccessor(recurmax, stmtDepth, canThrow)); + } else { + var key = KEYS[rng(KEYS.length)]; + obj.push(key + ':(' + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + '),'); + } + } + obj.push('})'); + return obj.join('\n'); } function createNestedBinaryExpr(recurmax, noComma, stmtDepth, canThrow) { @@ -787,7 +842,7 @@ function _createSimpleBinaryExpr(recurmax, noComma, stmtDepth, canThrow) { return canThrow && rng(10) == 0 ? expr : '(' + assignee + ' && ' + expr + ')'; case 4: assignee = getVarName(); - expr = '(' + assignee + '.' + getDotKey() + createAssignment() + expr = '(' + assignee + '.' + getDotKey(true) + createAssignment() + _createBinaryExpr(recurmax, noComma, stmtDepth, canThrow) + ')'; return canThrow && rng(10) == 0 ? expr : '(' + assignee + ' && ' + expr + ')'; default: @@ -992,7 +1047,7 @@ for (var round = 1; round <= num_iterations; round++) { } } if (verbose || (verbose_interval && !(round % INTERVAL_COUNT)) || !ok) log(options); - else if (verbose_error && typeof original_result != "string") { + else if (typeof original_result != "string") { console.log("//============================================================="); console.log("// original code"); try_beautify(original_code, original_result); From 504a436e9daac89f5226280e01ae2818fe4e8436 Mon Sep 17 00:00:00 2001 From: kzc Date: Sat, 13 May 2017 14:12:14 -0400 Subject: [PATCH 07/10] Tweak README Notes (#1934) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6f24b0a2..87079395 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ UglifyJS 3 UglifyJS is a JavaScript parser, minifier, compressor and beautifier toolkit. #### Note: -- **`uglify-js@3.x` has a new API and CLI and is not backwards compatible with [`uglify-js@2.x`](https://github.com/mishoo/UglifyJS2/tree/v2.x)**. +- **`uglify-js@3.x` has a simplified API and CLI that is not backwards compatible with [`uglify-js@2.x`](https://github.com/mishoo/UglifyJS2/tree/v2.x)**. - **Documentation for UglifyJS `2.x` releases can be found [here](https://github.com/mishoo/UglifyJS2/tree/v2.x)**. - `uglify-js` only supports ECMAScript 5 (ES5). - Those wishing to minify From e005099fb1b9a1b87ac50ba8223255e52cec452d Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Mon, 15 May 2017 02:37:53 +0800 Subject: [PATCH 08/10] fix & improve coverage of `estree` (#1935) - fix `estree` conversion of getter/setter - fix non-directive literal in `to_mozilla_ast()` - revamp `test/mozilla-ast.js` - reuse `test/ufuzz.js` for code generation - use `acorn.parse()` for creating `estree` - extend `test/ufuzz.js` for `acorn` workaround - catch variable redefinition - non-trivial literal as directive - adjust options for tolerance Miscellaneous - optional semi-colon when parsing directives fixes #1914 closes #1915 --- lib/mozilla-ast.js | 52 ++++++------- lib/parse.js | 20 ++--- package.json | 5 +- test/mocha/directives.js | 16 +++- test/mozilla-ast.js | 156 ++++++++++++++++----------------------- test/run-tests.js | 6 -- test/ufuzz.js | 99 ++++++++++++++++--------- 7 files changed, 175 insertions(+), 179 deletions(-) diff --git a/lib/mozilla-ast.js b/lib/mozilla-ast.js index 7d246758..8d7ee4b8 100644 --- a/lib/mozilla-ast.js +++ b/lib/mozilla-ast.js @@ -111,23 +111,19 @@ }, Property: function(M) { var key = M.key; - var name = key.type == "Identifier" ? key.name : key.value; var args = { start : my_start_token(key), end : my_end_token(M.value), - key : name, + key : key.type == "Identifier" ? key.name : key.value, value : from_moz(M.value) }; - switch (M.kind) { - case "init": - return new AST_ObjectKeyVal(args); - case "set": - args.value.name = from_moz(key); - return new AST_ObjectSetter(args); - case "get": - args.value.name = from_moz(key); - return new AST_ObjectGetter(args); - } + if (M.kind == "init") return new AST_ObjectKeyVal(args); + args.key = new AST_SymbolAccessor({ + name: args.key + }); + args.value = new AST_Accessor(args.value); + if (M.kind == "get") return new AST_ObjectGetter(args); + if (M.kind == "set") return new AST_ObjectSetter(args); }, ArrayExpression: function(M) { return new AST_Array({ @@ -260,10 +256,7 @@ map("CallExpression", AST_Call, "callee>expression, arguments@args"); def_to_moz(AST_Toplevel, function To_Moz_Program(M) { - return { - type: "Program", - body: M.body.map(to_moz) - }; + return to_moz_scope("Program", M); }); def_to_moz(AST_Defun, function To_Moz_FunctionDeclaration(M) { @@ -271,7 +264,7 @@ type: "FunctionDeclaration", id: to_moz(M.name), params: M.argnames.map(to_moz), - body: to_moz_block(M) + body: to_moz_scope("BlockStatement", M) } }); @@ -280,7 +273,7 @@ type: "FunctionExpression", id: to_moz(M.name), params: M.argnames.map(to_moz), - body: to_moz_block(M) + body: to_moz_scope("BlockStatement", M) } }); @@ -386,11 +379,10 @@ }); def_to_moz(AST_ObjectProperty, function To_Moz_Property(M) { - var key = ( - is_identifier(M.key) - ? {type: "Identifier", name: M.key} - : {type: "Literal", value: M.key} - ); + var key = { + type: "Literal", + value: M.key instanceof AST_SymbolAccessor ? M.key.name : M.key + }; var kind; if (M instanceof AST_ObjectKeyVal) { kind = "init"; @@ -551,8 +543,8 @@ moz_to_me = new Function("U2", "my_start_token", "my_end_token", "from_moz", "return(" + moz_to_me + ")")( exports, my_start_token, my_end_token, from_moz ); - me_to_moz = new Function("to_moz", "to_moz_block", "return(" + me_to_moz + ")")( - to_moz, to_moz_block + me_to_moz = new Function("to_moz", "to_moz_block", "to_moz_scope", "return(" + me_to_moz + ")")( + to_moz, to_moz_block, to_moz_scope ); MOZ_TO_ME[moztype] = moz_to_me; def_to_moz(mytype, me_to_moz); @@ -610,4 +602,14 @@ }; }; + function to_moz_scope(type, node) { + var body = node.body.map(to_moz); + if (node.body[0] instanceof AST_SimpleStatement && node.body[0].body instanceof AST_String) { + body.unshift(to_moz(new AST_EmptyStatement(node.body[0]))); + } + return { + type: type, + body: body + }; + }; })(); diff --git a/lib/parse.js b/lib/parse.js index f1089501..74c00b74 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -805,24 +805,20 @@ function parse($TEXT, options) { handle_regexp(); switch (S.token.type) { case "string": - var dir = false; - if (S.in_directives === true) { - if ((is_token(peek(), "punc", ";") || peek().nlb) && S.token.raw.indexOf("\\") === -1) { + if (S.in_directives) { + tmp = peek(); + if (S.token.raw.indexOf("\\") == -1 + && (tmp.nlb + || is_token(tmp, "eof") + || is_token(tmp, "punc", ";") + || is_token(tmp, "punc", "}"))) { S.input.add_directive(S.token.value); } else { S.in_directives = false; } } var dir = S.in_directives, stat = simple_statement(); - if (dir) { - return new AST_Directive({ - start : stat.body.start, - end : stat.body.end, - quote : stat.body.quote, - value : stat.body.value, - }); - } - return stat; + return dir ? new AST_Directive(stat.body) : stat; case "num": case "regexp": case "operator": diff --git a/package.json b/package.json index 6de380eb..04b49f80 100644 --- a/package.json +++ b/package.json @@ -33,10 +33,7 @@ "source-map": "~0.5.1" }, "devDependencies": { - "acorn": "~0.6.0", - "escodegen": "~1.3.3", - "esfuzz": "~0.3.1", - "estraverse": "~1.5.1", + "acorn": "~5.0.3", "mocha": "~2.3.4" }, "optionalDependencies": { diff --git a/test/mocha/directives.js b/test/mocha/directives.js index ab8ad57f..256bb870 100644 --- a/test/mocha/directives.js +++ b/test/mocha/directives.js @@ -351,18 +351,28 @@ describe("Directives", function() { var tests = [ [ '"use strict";"use strict";"use strict";"use foo";"use strict";;"use sloppy";doSomething("foo");', - '"use strict";"use foo";doSomething("foo");' + '"use strict";"use foo";doSomething("foo");', + 'function f(){ "use strict" }', + 'function f(){ "use asm" }', + 'function f(){ "use nondirective" }', + 'function f(){ ;"use strict" }', + 'function f(){ "use \n"; }', ], [ // Nothing gets optimised in the compressor because "use asm" is the first statement '"use asm";"use\\x20strict";1+1;', - '"use asm";;"use strict";1+1;' // Yet, the parser noticed that "use strict" wasn't a directive + '"use asm";;"use strict";1+1;', // Yet, the parser noticed that "use strict" wasn't a directive + 'function f(){"use strict"}', + 'function f(){"use asm"}', + 'function f(){"use nondirective"}', + 'function f(){}', + 'function f(){}', ] ]; for (var i = 0; i < tests.length; i++) { assert.strictEqual( - uglify.minify(tests[i][0], {compress: {collapse_vars: true, side_effects: true}}).code, + uglify.minify(tests[i][0]).code, tests[i][1], tests[i][0] ); diff --git a/test/mozilla-ast.js b/test/mozilla-ast.js index 544ce8bc..b8026de5 100644 --- a/test/mozilla-ast.js +++ b/test/mozilla-ast.js @@ -1,103 +1,73 @@ // Testing UglifyJS <-> SpiderMonkey AST conversion -// through generative testing. +"use strict"; -var UglifyJS = require("./node"), - escodegen = require("escodegen"), - esfuzz = require("esfuzz"), - estraverse = require("estraverse"), - prefix = "\r "; +var acorn = require("acorn"); +var ufuzz = require("./ufuzz"); +var UglifyJS = require(".."); -// Normalizes input AST for UglifyJS in order to get correct comparison. - -function normalizeInput(ast) { - return estraverse.replace(ast, { - enter: function(node, parent) { - switch (node.type) { - // Internally mark all the properties with semi-standard type "Property". - case "ObjectExpression": - node.properties.forEach(function (property) { - property.type = "Property"; - }); - break; - - // Since UglifyJS doesn"t recognize different types of property keys, - // decision on SpiderMonkey node type is based on check whether key - // can be valid identifier or not - so we do in input AST. - case "Property": - var key = node.key; - if (key.type === "Literal" && typeof key.value === "string" && UglifyJS.is_identifier(key.value)) { - node.key = { - type: "Identifier", - name: key.value - }; - } else if (key.type === "Identifier" && !UglifyJS.is_identifier(key.name)) { - node.key = { - type: "Literal", - value: key.name - }; - } - break; - - // UglifyJS internally flattens all the expression sequences - either - // to one element (if sequence contains only one element) or flat list. - case "SequenceExpression": - node.expressions = node.expressions.reduce(function flatten(list, expr) { - return list.concat(expr.type === "SequenceExpression" ? expr.expressions.reduce(flatten, []) : [expr]); - }, []); - if (node.expressions.length === 1) { - return node.expressions[0]; - } - break; - } +function try_beautify(code) { + var beautified = UglifyJS.minify(code, { + compress: false, + mangle: false, + output: { + beautify: true, + bracketize: true } }); + if (beautified.error) { + console.log("// !!! beautify failed !!!"); + console.log(beautified.error.stack); + console.log(code); + } else { + console.log("// (beautified)"); + console.log(beautified.code); + } } -module.exports = function(options) { - console.log("--- UglifyJS <-> Mozilla AST conversion"); - - for (var counter = 0; counter < options.iterations; counter++) { - process.stdout.write(prefix + counter + "/" + options.iterations); - - var ast1 = normalizeInput(esfuzz.generate({ - maxDepth: options.maxDepth - })); - - var ast2 = - UglifyJS - .AST_Node - .from_mozilla_ast(ast1) - .to_mozilla_ast(); - - var astPair = [ - {name: 'expected', value: ast1}, - {name: 'actual', value: ast2} - ]; - - var jsPair = astPair.map(function(item) { - return { - name: item.name, - value: escodegen.generate(item.value) - } - }); - - if (jsPair[0].value !== jsPair[1].value) { - var fs = require("fs"); - var acorn = require("acorn"); - - fs.existsSync("tmp") || fs.mkdirSync("tmp"); - - jsPair.forEach(function (item) { - var fileName = "tmp/dump_" + item.name; - var ast = acorn.parse(item.value); - fs.writeFileSync(fileName + ".js", item.value); - fs.writeFileSync(fileName + ".json", JSON.stringify(ast, null, 2)); - }); - - process.stdout.write("\n"); - throw new Error("Got different outputs, check out tmp/dump_*.{js,json} for codes and ASTs."); +function test(original, estree, description) { + var transformed = UglifyJS.minify(UglifyJS.AST_Node.from_mozilla_ast(estree), { + compress: false, + mangle: false + }); + if (transformed.error || original !== transformed.code) { + console.log("//============================================================="); + console.log("// !!!!!! Failed... round", round); + console.log("// original code"); + try_beautify(original); + console.log(); + console.log(); + console.log("//-------------------------------------------------------------"); + console.log("//", description); + if (transformed.error) { + console.log(transformed.error.stack); + } else { + try_beautify(transformed.code); } + console.log("!!!!!! Failed... round", round); + process.exit(1); } +} - process.stdout.write(prefix + "Probability of error is less than " + (100 / options.iterations) + "%, stopping.\n"); -}; +var num_iterations = ufuzz.num_iterations; +for (var round = 1; round <= num_iterations; round++) { + process.stdout.write(round + " of " + num_iterations + "\r"); + var code = ufuzz.createTopLevelCode(); + var uglified = UglifyJS.minify(code, { + compress: false, + mangle: false, + output: { + ast: true + } + }); + test(uglified.code, uglified.ast.to_mozilla_ast(), "AST_Node.to_mozilla_ast()"); + try { + test(uglified.code, acorn.parse(code), "acorn.parse()"); + } catch (e) { + console.log("//============================================================="); + console.log("// acorn parser failed... round", round); + console.log(e); + console.log("// original code"); + console.log(code); + } +} +console.log(); diff --git a/test/run-tests.js b/test/run-tests.js index 05afc1b5..edcd7cc9 100755 --- a/test/run-tests.js +++ b/test/run-tests.js @@ -23,12 +23,6 @@ mocha_tests(); var run_sourcemaps_tests = require('./sourcemaps'); run_sourcemaps_tests(); -var run_ast_conversion_tests = require("./mozilla-ast"); - -run_ast_conversion_tests({ - iterations: 1000 -}); - /* -----[ utils ]----- */ function tmpl() { diff --git a/test/ufuzz.js b/test/ufuzz.js index 6e716c5b..ff403c60 100644 --- a/test/ufuzz.js +++ b/test/ufuzz.js @@ -49,6 +49,8 @@ var num_iterations = +process.argv[2] || 1/0; var verbose = false; // log every generated test var verbose_interval = false; // log every 100 generated tests var use_strict = false; +var catch_redef = require.main === module; +var generate_directive = require.main === module; for (var i = 2; i < process.argv.length; ++i) { switch (process.argv[i]) { case '-v': @@ -75,6 +77,12 @@ for (var i = 2; i < process.argv.length; ++i) { STMT_SECOND_LEVEL_OVERRIDE = STMT_ARG_TO_ID[name]; if (!(STMT_SECOND_LEVEL_OVERRIDE >= 0)) throw new Error('Unknown statement name; use -? to get a list'); break; + case '--no-catch-redef': + catch_redef = false; + break; + case '--no-directive': + generate_directive = false; + break; case '--use-strict': use_strict = true; break; @@ -103,6 +111,8 @@ for (var i = 2; i < process.argv.length; ++i) { console.log('-r : maximum recursion depth for generator (higher takes longer)'); console.log('-s1 : force the first level statement to be this one (see list below)'); console.log('-s2 : force the second level statement to be this one (see list below)'); + console.log('--no-catch-redef: do not redefine catch variables'); + console.log('--no-directive: do not generate directives'); console.log('--use-strict: generate "use strict"'); console.log('--stmt-depth-from-func: reset statement depth counter at each function, counts from global otherwise'); console.log('--only-stmt : a comma delimited white list of statements that may be generated'); @@ -293,6 +303,7 @@ var TYPEOF_OUTCOMES = [ 'symbol', 'crap' ]; +var unique_vars = []; var loops = 0; var funcs = 0; var labels = 10000; @@ -307,6 +318,10 @@ function strictMode() { } function createTopLevelCode() { + VAR_NAMES.length = INITIAL_NAMES_LEN; // prune any previous names still in the list + unique_vars.length = 0; + loops = 0; + funcs = 0; return [ strictMode(), 'var a = 100, b = 10, c = 0;', @@ -342,33 +357,36 @@ function createArgs() { return args.join(', '); } +function filterDirective(s) { + if (!generate_directive && !s[1] && /\("/.test(s[2])) s[2] = ';' + s[2]; + return s; +} + function createFunction(recurmax, inGlobal, noDecl, canThrow, stmtDepth) { if (--recurmax < 0) { return ';'; } if (!STMT_COUNT_FROM_GLOBAL) stmtDepth = 0; var func = funcs++; var namesLenBefore = VAR_NAMES.length; - var name = (inGlobal || rng(5) > 0) ? 'f' + func : createVarName(MANDATORY, noDecl); - if (name === 'a' || name === 'b' || name === 'c') name = 'f' + func; // quick hack to prevent assignment to func names of being called - var s = ''; + var name; + if (inGlobal || rng(5) > 0) name = 'f' + func; + else { + unique_vars.push('a', 'b', 'c'); + name = createVarName(MANDATORY, noDecl); + unique_vars.length -= 3; + } + var s = [ + 'function ' + name + '(' + createParams() + '){', + strictMode() + ]; if (rng(5) === 0) { // functions with functions. lower the recursion to prevent a mess. - s = [ - 'function ' + name + '(' + createParams() + '){', - strictMode(), - createFunctions(rng(5) + 1, Math.ceil(recurmax * 0.7), NOT_GLOBAL, ANY_TYPE, canThrow, stmtDepth), - '}', - '' - ].join('\n'); + s.push(createFunctions(rng(5) + 1, Math.ceil(recurmax * 0.7), NOT_GLOBAL, ANY_TYPE, canThrow, stmtDepth)); } else { // functions with statements - s = [ - 'function ' + name + '(' + createParams() + '){', - strictMode(), - createStatements(3, recurmax, canThrow, CANNOT_BREAK, CANNOT_CONTINUE, CAN_RETURN, stmtDepth), - '}', - '' - ].join('\n'); + s.push(createStatements(3, recurmax, canThrow, CANNOT_BREAK, CANNOT_CONTINUE, CAN_RETURN, stmtDepth)); } + s.push('}', ''); + s = filterDirective(s).join('\n'); VAR_NAMES.length = namesLenBefore; @@ -477,20 +495,22 @@ function createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn case STMT_VAR: switch (rng(3)) { case 0: + unique_vars.push('c'); var name = createVarName(MANDATORY); - if (name === 'c') name = 'a'; + unique_vars.pop(); return 'var ' + name + ';'; case 1: // initializer can only have one expression + unique_vars.push('c'); var name = createVarName(MANDATORY); - if (name === 'c') name = 'b'; + unique_vars.pop(); return 'var ' + name + ' = ' + createExpression(recurmax, NO_COMMA, stmtDepth, canThrow) + ';'; default: // initializer can only have one expression + unique_vars.push('c'); var n1 = createVarName(MANDATORY); - if (n1 === 'c') n1 = 'b'; var n2 = createVarName(MANDATORY); - if (n2 === 'c') n2 = 'b'; + unique_vars.pop(); return 'var ' + n1 + ' = ' + createExpression(recurmax, NO_COMMA, stmtDepth, canThrow) + ', ' + n2 + ' = ' + createExpression(recurmax, NO_COMMA, stmtDepth, canThrow) + ';'; } case STMT_RETURN_ETC: @@ -531,8 +551,11 @@ function createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn var nameLenBefore = VAR_NAMES.length; var catchName = createVarName(MANDATORY); var freshCatchName = VAR_NAMES.length !== nameLenBefore; + if (!catch_redef) unique_vars.push(catchName); s += ' catch (' + catchName + ') { ' + createStatements(3, recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth) + ' }'; - if (freshCatchName) VAR_NAMES.splice(nameLenBefore, 1); // remove catch name + // remove catch name + if (!catch_redef) unique_vars.pop(); + if (freshCatchName) VAR_NAMES.splice(nameLenBefore, 1); } if (n !== 0) s += ' finally { ' + createStatements(3, recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth) + ' }'; return s; @@ -610,8 +633,9 @@ function _createExpression(recurmax, noComma, stmtDepth, canThrow) { case p++: case p++: var nameLenBefore = VAR_NAMES.length; + unique_vars.push('c'); var name = createVarName(MAYBE); // note: this name is only accessible from _within_ the function. and immutable at that. - if (name == 'c') name = 'a'; + unique_vars.pop(); var s = []; switch (rng(5)) { case 0: @@ -663,7 +687,7 @@ function _createExpression(recurmax, noComma, stmtDepth, canThrow) { break; } VAR_NAMES.length = nameLenBefore; - return s.join('\n'); + return filterDirective(s).join('\n'); case p++: case p++: return createTypeofExpr(recurmax, stmtDepth, canThrow); @@ -782,7 +806,7 @@ function createAccessor(recurmax, stmtDepth, canThrow) { createStatements(2, recurmax, canThrow, CANNOT_BREAK, CANNOT_CONTINUE, CAN_RETURN, stmtDepth), createStatement(recurmax, canThrow, CANNOT_BREAK, CANNOT_CONTINUE, CAN_RETURN, stmtDepth, STMT_RETURN_ETC), '},' - ].join('\n'); + ]; } else { var prop2; do { @@ -794,10 +818,10 @@ function createAccessor(recurmax, stmtDepth, canThrow) { createStatements(2, recurmax, canThrow, CANNOT_BREAK, CANNOT_CONTINUE, CAN_RETURN, stmtDepth), 'this.' + prop2 + createAssignment() + _createBinaryExpr(recurmax, COMMA_OK, stmtDepth, canThrow) + ';', '},' - ].join('\n'); + ]; } VAR_NAMES.length = namesLenBefore; - return s; + return filterDirective(s).join('\n'); } function createObjectLiteral(recurmax, stmtDepth, canThrow) { @@ -899,17 +923,24 @@ function getVarName() { function createVarName(maybe, dontStore) { if (!maybe || rng(2)) { - var name = VAR_NAMES[rng(VAR_NAMES.length)]; var suffix = rng(3); - if (suffix) { - name += '_' + suffix; - if (!dontStore) VAR_NAMES.push(name); - } + var name; + do { + name = VAR_NAMES[rng(VAR_NAMES.length)]; + if (suffix) name += '_' + suffix; + } while (unique_vars.indexOf(name) >= 0); + if (suffix && !dontStore) VAR_NAMES.push(name); return name; } return ''; } +if (require.main !== module) { + exports.createTopLevelCode = createTopLevelCode; + exports.num_iterations = num_iterations; + return; +} + function try_beautify(code, result) { var beautified = UglifyJS.minify(code, { compress: false, @@ -1028,10 +1059,6 @@ var uglify_code, uglify_result, ok; for (var round = 1; round <= num_iterations; round++) { process.stdout.write(round + " of " + num_iterations + "\r"); - VAR_NAMES.length = INITIAL_NAMES_LEN; // prune any previous names still in the list - loops = 0; - funcs = 0; - original_code = createTopLevelCode(); original_result = sandbox.run_code(original_code); (typeof original_result != "string" ? fallback_options : minify_options).forEach(function(options) { From ff526be61d3af128c59013e2963b5861645badb7 Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Mon, 15 May 2017 11:34:59 +0800 Subject: [PATCH 09/10] v3.0.5 --- package.json | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/package.json b/package.json index 04b49f80..c84ef8b1 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": "3.0.4", + "version": "3.0.5", "engines": { "node": ">=0.8.0" }, @@ -36,14 +36,6 @@ "acorn": "~5.0.3", "mocha": "~2.3.4" }, - "optionalDependencies": { - "uglify-to-browserify": "~1.0.0" - }, - "browserify": { - "transform": [ - "uglify-to-browserify" - ] - }, "scripts": { "test": "node test/run-tests.js" }, From cd6e849555ce1f1d75c6b1e82dd62d0411a4d8e4 Mon Sep 17 00:00:00 2001 From: alexlamsl Date: Mon, 15 May 2017 18:38:16 +0800 Subject: [PATCH 10/10] Revert "remove support for `const` (#1910)" This reverts commit c391576d52852322a7fcfbaeabc9d5626e628c8b. --- lib/ast.js | 16 +++- lib/compress.js | 19 +++- lib/mozilla-ast.js | 6 +- lib/output.js | 3 + lib/parse.js | 22 ++++- lib/scope.js | 7 +- test/compress/collapse_vars.js | 56 ++++++++++- test/compress/const.js | 166 +++++++++++++++++++++++++++++++++ test/compress/dead-code.js | 125 +++++++++++++++++++++++++ test/compress/drop-unused.js | 64 +++++++++++++ test/compress/evaluate.js | 9 +- test/compress/global_defs.js | 4 +- test/compress/issue-1041.js | 13 +++ test/compress/issue-1588.js | 12 +++ test/compress/issue-208.js | 6 +- test/compress/loops.js | 44 +++++++++ test/compress/reduce_vars.js | 8 +- test/compress/sequences.js | 24 +++++ test/input/invalid/const.js | 8 ++ test/mocha/cli.js | 15 +++ 20 files changed, 591 insertions(+), 36 deletions(-) create mode 100644 test/compress/const.js create mode 100644 test/input/invalid/const.js diff --git a/lib/ast.js b/lib/ast.js index 2972b7aa..f4e89ab9 100644 --- a/lib/ast.js +++ b/lib/ast.js @@ -495,10 +495,10 @@ var AST_Finally = DEFNODE("Finally", null, { $documentation: "A `finally` node; only makes sense as part of a `try` statement" }, AST_Block); -/* -----[ VAR ]----- */ +/* -----[ VAR/CONST ]----- */ var AST_Definitions = DEFNODE("Definitions", "definitions", { - $documentation: "Base class for `var` nodes (variable declarations/initializations)", + $documentation: "Base class for `var` or `const` nodes (variable declarations/initializations)", $propdoc: { definitions: "[AST_VarDef*] array of variable definitions" }, @@ -516,10 +516,14 @@ var AST_Var = DEFNODE("Var", null, { $documentation: "A `var` statement" }, AST_Definitions); +var AST_Const = DEFNODE("Const", null, { + $documentation: "A `const` statement" +}, AST_Definitions); + var AST_VarDef = DEFNODE("VarDef", "name value", { $documentation: "A variable declaration; only appears in a AST_Definitions node", $propdoc: { - name: "[AST_SymbolVar] name of the variable", + name: "[AST_SymbolVar|AST_SymbolConst] name of the variable", value: "[AST_Node?] initializer, or null of there's no initializer" }, _walk: function(visitor) { @@ -724,13 +728,17 @@ var AST_SymbolAccessor = DEFNODE("SymbolAccessor", null, { }, AST_Symbol); var AST_SymbolDeclaration = DEFNODE("SymbolDeclaration", "init", { - $documentation: "A declaration symbol (symbol in var, function name or argument, symbol in catch)", + $documentation: "A declaration symbol (symbol in var/const, function name or argument, symbol in catch)", }, AST_Symbol); var AST_SymbolVar = DEFNODE("SymbolVar", null, { $documentation: "Symbol defining a variable", }, AST_SymbolDeclaration); +var AST_SymbolConst = DEFNODE("SymbolConst", null, { + $documentation: "A constant declaration" +}, AST_SymbolDeclaration); + var AST_SymbolFunarg = DEFNODE("SymbolFunarg", null, { $documentation: "Symbol naming a function argument", }, AST_SymbolVar); diff --git a/lib/compress.js b/lib/compress.js index f168b942..7f3313f1 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -486,7 +486,8 @@ merge(Compressor.prototype, { if (def.fixed === false) return false; if (def.fixed != null && (!value || def.references.length > 0)) return false; return !def.orig.some(function(sym) { - return sym instanceof AST_SymbolDefun + return sym instanceof AST_SymbolConst + || sym instanceof AST_SymbolDefun || sym instanceof AST_SymbolLambda; }); } @@ -503,7 +504,7 @@ merge(Compressor.prototype, { def.escaped = false; if (def.scope.uses_eval) { def.fixed = false; - } else if (!def.global || compressor.toplevel(def)) { + } else if (!def.global || def.orig[0] instanceof AST_SymbolConst || compressor.toplevel(def)) { def.fixed = undefined; } else { def.fixed = false; @@ -537,6 +538,14 @@ merge(Compressor.prototype, { return lhs instanceof AST_SymbolRef && lhs.definition().orig[0] instanceof AST_SymbolLambda; } + function is_reference_const(ref) { + if (!(ref instanceof AST_SymbolRef)) return false; + var orig = ref.definition().orig; + for (var i = orig.length; --i >= 0;) { + if (orig[i] instanceof AST_SymbolConst) return true; + } + } + function find_variable(compressor, name) { var scope, i = 0; while (scope = compressor.parent(i++)) { @@ -802,7 +811,8 @@ merge(Compressor.prototype, { return make_node(AST_SymbolRef, expr.name, expr.name); } } else { - return expr[expr instanceof AST_Assign ? "left" : "expression"]; + var lhs = expr[expr instanceof AST_Assign ? "left" : "expression"]; + return !is_reference_const(lhs) && lhs; } } @@ -1990,6 +2000,7 @@ merge(Compressor.prototype, { && node instanceof AST_Assign && node.operator == "=" && node.left instanceof AST_SymbolRef + && !is_reference_const(node.left) && scope === self) { node.right.walk(tw); return true; @@ -3196,7 +3207,7 @@ merge(Compressor.prototype, { && (left.operator == "++" || left.operator == "--")) { left = left.expression; } else left = null; - if (!left || is_lhs_read_only(left)) { + if (!left || is_lhs_read_only(left) || is_reference_const(left)) { expressions[++i] = cdr; continue; } diff --git a/lib/mozilla-ast.js b/lib/mozilla-ast.js index 8d7ee4b8..2a1da9e6 100644 --- a/lib/mozilla-ast.js +++ b/lib/mozilla-ast.js @@ -168,7 +168,7 @@ }); }, VariableDeclaration: function(M) { - return new AST_Var({ + return new (M.kind === "const" ? AST_Const : AST_Var)({ start : my_start_token(M), end : my_end_token(M), definitions : M.declarations.map(from_moz) @@ -204,7 +204,7 @@ Identifier: function(M) { var p = FROM_MOZ_STACK[FROM_MOZ_STACK.length - 2]; return new ( p.type == "LabeledStatement" ? AST_Label - : p.type == "VariableDeclarator" && p.id === M ? AST_SymbolVar + : p.type == "VariableDeclarator" && p.id === M ? (p.kind == "const" ? AST_SymbolConst : AST_SymbolVar) : p.type == "FunctionExpression" ? (p.id === M ? AST_SymbolLambda : AST_SymbolFunarg) : p.type == "FunctionDeclaration" ? (p.id === M ? AST_SymbolDefun : AST_SymbolFunarg) : p.type == "CatchClause" ? AST_SymbolCatch @@ -324,7 +324,7 @@ def_to_moz(AST_Definitions, function To_Moz_VariableDeclaration(M) { return { type: "VariableDeclaration", - kind: "var", + kind: M instanceof AST_Const ? "const" : "var", declarations: M.definitions.map(to_moz) }; }); diff --git a/lib/output.js b/lib/output.js index 0b98825f..33f4c533 100644 --- a/lib/output.js +++ b/lib/output.js @@ -1033,6 +1033,9 @@ function OutputStream(options) { DEFPRINT(AST_Var, function(self, output){ self._do_print(output, "var"); }); + DEFPRINT(AST_Const, function(self, output){ + self._do_print(output, "const"); + }); function parenthesize_for_noin(node, output, noin) { if (!noin) node.print(output); diff --git a/lib/parse.js b/lib/parse.js index 74c00b74..92690dfb 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -912,6 +912,9 @@ function parse($TEXT, options) { case "var": return tmp = var_(), semicolon(), tmp; + case "const": + return tmp = const_(), semicolon(), tmp; + case "with": if (S.input.has_directive("use strict")) { croak("Strict mode may not include a with statement"); @@ -1146,13 +1149,16 @@ function parse($TEXT, options) { }); }; - function vardefs(no_in) { + function vardefs(no_in, in_const) { var a = []; for (;;) { a.push(new AST_VarDef({ start : S.token, - name : as_symbol(AST_SymbolVar), - value : is("operator", "=") ? (next(), expression(false, no_in)) : null, + name : as_symbol(in_const ? AST_SymbolConst : AST_SymbolVar), + value : is("operator", "=") + ? (next(), expression(false, no_in)) + : in_const && S.input.has_directive("use strict") + ? croak("Missing initializer in const declaration") : null, end : prev() })); if (!is("punc", ",")) @@ -1165,7 +1171,15 @@ function parse($TEXT, options) { var var_ = function(no_in) { return new AST_Var({ start : prev(), - definitions : vardefs(no_in), + definitions : vardefs(no_in, false), + end : prev() + }); + }; + + var const_ = function() { + return new AST_Const({ + start : prev(), + definitions : vardefs(false, true), end : prev() }); }; diff --git a/lib/scope.js b/lib/scope.js index 14ffb46f..65144be3 100644 --- a/lib/scope.js +++ b/lib/scope.js @@ -156,7 +156,8 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){ // later. (node.scope = defun.parent_scope).def_function(node); } - else if (node instanceof AST_SymbolVar) { + else if (node instanceof AST_SymbolVar + || node instanceof AST_SymbolConst) { defun.def_variable(node); if (defun !== scope) { node.mark_enclosed(options); @@ -267,7 +268,7 @@ AST_Scope.DEFMETHOD("init_scope_vars", function(parent_scope){ AST_Lambda.DEFMETHOD("init_scope_vars", function(){ AST_Scope.prototype.init_scope_vars.apply(this, arguments); this.uses_arguments = false; - this.def_variable(new AST_SymbolFunarg({ + this.def_variable(new AST_SymbolConst({ name: "arguments", start: this.start, end: this.end @@ -485,6 +486,8 @@ AST_Toplevel.DEFMETHOD("compute_char_frequency", function(options){ } else if (node instanceof AST_Var) base54.consider("var"); + else if (node instanceof AST_Const) + base54.consider("const"); else if (node instanceof AST_Lambda) base54.consider("function"); else if (node instanceof AST_For) diff --git a/test/compress/collapse_vars.js b/test/compress/collapse_vars.js index 90d7ac93..0d578d7d 100644 --- a/test/compress/collapse_vars.js +++ b/test/compress/collapse_vars.js @@ -583,8 +583,8 @@ collapse_vars_assignment: { return a = a; } function f1(c) { - var a = 3 / c; - var b = 1 - a; + const a = 3 / c; + const b = 1 - a; return b; } function f2(c) { @@ -724,10 +724,10 @@ collapse_vars_misc1: { return t; } function f1(x) { var y = 5 - x; return y; } - function f2(x) { var z = foo(), y = z / (5 - x); return y; } + function f2(x) { const z = foo(), y = z / (5 - x); return y; } function f3(x) { var z = foo(), y = (5 - x) / z; return y; } function f4(x) { var z = foo(), y = (5 - u) / z; return y; } - function f5(x) { var z = foo(), y = (5 - window.x) / z; return y; } + function f5(x) { const z = foo(), y = (5 - window.x) / z; return y; } function f6() { var b = window.a * window.z; return b && zap(); } function f7() { var b = window.a * window.z; return b + b; } function f8() { var b = window.a * window.z; var c = b + 5; return b + c; } @@ -744,7 +744,7 @@ collapse_vars_misc1: { function f2(x) { return foo() / (5 - x) } function f3(x) { return (5 - x) / foo() } function f4(x) { var z = foo(); return (5 - u) / z } - function f5(x) { var z = foo(); return (5 - window.x) / z } + function f5(x) { const z = foo(); return (5 - window.x) / z } function f6() { return window.a * window.z && zap() } function f7() { var b = window.a * window.z; return b + b } function f8() { var b = window.a * window.z; return b + (b + 5) } @@ -2186,3 +2186,49 @@ compound_assignment: { } expect_stdout: "4" } + +reassign_const_1: { + options = { + collapse_vars: true, + } + input: { + function f() { + const a = 1; + a = 2; + return a; + } + console.log(f()); + } + expect: { + function f() { + const a = 1; + a = 2; + return a; + } + console.log(f()); + } + expect_stdout: true +} + +reassign_const_2: { + options = { + collapse_vars: true, + } + input: { + function f() { + const a = 1; + ++a; + return a; + } + console.log(f()); + } + expect: { + function f() { + const a = 1; + ++a; + return a; + } + console.log(f()); + } + expect_stdout: true +} diff --git a/test/compress/const.js b/test/compress/const.js new file mode 100644 index 00000000..a88d5946 --- /dev/null +++ b/test/compress/const.js @@ -0,0 +1,166 @@ +issue_1191: { + options = { + evaluate : true, + booleans : true, + comparisons : true, + dead_code : true, + conditionals : true, + side_effects : true, + unused : true, + hoist_funs : true, + if_return : true, + join_vars : true, + sequences : false, + collapse_vars : false, + reduce_vars : true, + } + input: { + function foo(rot) { + const rotTol = 5; + if (rot < -rotTol || rot > rotTol) + bar(); + baz(); + } + } + expect: { + function foo(rot) { + (rot < -5 || rot > 5) && bar(); + baz(); + } + } +} + +issue_1194: { + options = { + evaluate : true, + booleans : true, + comparisons : true, + dead_code : true, + conditionals : true, + side_effects : true, + unused : true, + hoist_funs : true, + if_return : true, + join_vars : true, + sequences : false, + collapse_vars : false, + reduce_vars : true, + } + input: { + function f1() {const a = "X"; return a + a;} + function f2() {const aa = "X"; return aa + aa;} + function f3() {const aaa = "X"; return aaa + aaa;} + } + expect: { + function f1(){return"XX"} + function f2(){return"XX"} + function f3(){return"XX"} + } +} + +issue_1396: { + options = { + evaluate : true, + booleans : true, + comparisons : true, + dead_code : true, + conditionals : true, + side_effects : true, + unused : true, + hoist_funs : true, + if_return : true, + join_vars : true, + sequences : false, + collapse_vars : false, + reduce_vars : true, + } + input: { + function foo(a) { + const VALUE = 1; + console.log(2 | VALUE); + console.log(VALUE + 1); + console.log(VALUE); + console.log(a & VALUE); + } + function bar() { + const s = "01234567890123456789"; + console.log(s + s + s + s + s); + + const CONSTANT = "abc"; + console.log(CONSTANT + CONSTANT + CONSTANT + CONSTANT + CONSTANT); + } + } + expect: { + function foo(a) { + console.log(3); + console.log(2); + console.log(1); + console.log(1 & a); + } + function bar() { + const s = "01234567890123456789"; + console.log(s + s + s + s + s); + + console.log("abcabcabcabcabc"); + } + } +} + +unused_regexp_literal: { + options = { + evaluate : true, + booleans : true, + comparisons : true, + dead_code : true, + conditionals : true, + side_effects : true, + unused : true, + hoist_funs : true, + if_return : true, + join_vars : true, + sequences : false, + collapse_vars : false, + } + input: { + function f(){ var a = /b/; } + } + expect: { + function f(){} + } +} + +regexp_literal_not_const: { + options = { + evaluate : true, + booleans : true, + comparisons : true, + dead_code : true, + conditionals : true, + side_effects : true, + unused : true, + hoist_funs : true, + if_return : true, + join_vars : true, + sequences : false, + collapse_vars : false, + reduce_vars : true, + } + input: { + (function(){ + var result; + const s = 'acdabcdeabbb'; + const REGEXP_LITERAL = /ab*/g; + while (result = REGEXP_LITERAL.exec(s)) { + console.log(result[0]); + } + })(); + } + expect: { + (function() { + var result; + const REGEXP_LITERAL = /ab*/g; + while (result = REGEXP_LITERAL.exec("acdabcdeabbb")) console.log(result[0]); + })(); + } + expect_stdout: true +} diff --git a/test/compress/dead-code.js b/test/compress/dead-code.js index 00dac069..af28e253 100644 --- a/test/compress/dead-code.js +++ b/test/compress/dead-code.js @@ -90,6 +90,131 @@ dead_code_constant_boolean_should_warn_more: { expect_stdout: true } +dead_code_const_declaration: { + options = { + dead_code : true, + loops : true, + booleans : true, + conditionals : true, + evaluate : true, + reduce_vars : true, + }; + input: { + var unused; + const CONST_FOO = false; + if (CONST_FOO) { + console.log("unreachable"); + var moo; + function bar() {} + } + } + expect: { + var unused; + const CONST_FOO = !1; + var moo; + function bar() {} + } + expect_stdout: true +} + +dead_code_const_annotation: { + options = { + dead_code : true, + loops : true, + booleans : true, + conditionals : true, + evaluate : true, + reduce_vars : true, + toplevel : true, + }; + input: { + var unused; + /** @const */ var CONST_FOO_ANN = false; + if (CONST_FOO_ANN) { + console.log("unreachable"); + var moo; + function bar() {} + } + } + expect: { + var unused; + var CONST_FOO_ANN = !1; + var moo; + function bar() {} + } + expect_stdout: true +} + +dead_code_const_annotation_regex: { + options = { + dead_code : true, + loops : true, + booleans : true, + conditionals : true, + evaluate : true + }; + input: { + var unused; + // @constraint this shouldn't be a constant + var CONST_FOO_ANN = false; + if (CONST_FOO_ANN) { + console.log("reachable"); + } + } + expect: { + var unused; + var CONST_FOO_ANN = !1; + CONST_FOO_ANN && console.log('reachable'); + } + expect_stdout: true +} + +dead_code_const_annotation_complex_scope: { + options = { + dead_code : true, + loops : true, + booleans : true, + conditionals : true, + evaluate : true, + reduce_vars : true, + toplevel : true, + }; + input: { + var unused_var; + /** @const */ var test = 'test'; + // @const + var CONST_FOO_ANN = false; + var unused_var_2; + if (CONST_FOO_ANN) { + console.log("unreachable"); + var moo; + function bar() {} + } + if (test === 'test') { + var beef = 'good'; + /** @const */ var meat = 'beef'; + var pork = 'bad'; + if (meat === 'pork') { + console.log('also unreachable'); + } else if (pork === 'good') { + console.log('reached, not const'); + } + } + } + expect: { + var unused_var; + var test = 'test'; + var CONST_FOO_ANN = !1; + var unused_var_2; + var moo; + function bar() {} + var beef = 'good'; + var meat = 'beef'; + var pork = 'bad'; + } + expect_stdout: true +} + try_catch_finally: { options = { conditionals: true, diff --git a/test/compress/drop-unused.js b/test/compress/drop-unused.js index 2ef6f796..96bd336c 100644 --- a/test/compress/drop-unused.js +++ b/test/compress/drop-unused.js @@ -649,6 +649,37 @@ drop_value: { } } +const_assign: { + options = { + evaluate: true, + reduce_vars: true, + unused: true, + } + input: { + function f() { + const b = 2; + return 1 + b; + } + + function g() { + const b = 2; + b = 3; + return 1 + b; + } + } + expect: { + function f() { + return 3; + } + + function g() { + const b = 2; + b = 3; + return 1 + b; + } + } +} + issue_1539: { options = { cascade: true, @@ -785,6 +816,10 @@ issue_1709: { var x = 1; return x; }(), + function y() { + const y = 2; + return y; + }(), function z() { function z() {} return z; @@ -797,6 +832,10 @@ issue_1709: { var x = 1; return x; }(), + function() { + const y = 2; + return y; + }(), function() { function z() {} return z; @@ -1108,3 +1147,28 @@ var_catch_toplevel: { }(); } } + +reassign_const: { + options = { + cascade: true, + sequences: true, + side_effects: true, + unused: true, + } + input: { + function f() { + const a = 1; + a = 2; + return a; + } + console.log(f()); + } + expect: { + function f() { + const a = 1; + return a = 2, a; + } + console.log(f()); + } + expect_stdout: true +} diff --git a/test/compress/evaluate.js b/test/compress/evaluate.js index b8077564..585ee2b9 100644 --- a/test/compress/evaluate.js +++ b/test/compress/evaluate.js @@ -645,17 +645,16 @@ call_args: { options = { evaluate: true, reduce_vars: true, - toplevel: true, } input: { - var a = 1; + const a = 1; console.log(a); +function(a) { return a; }(a); } expect: { - var a = 1; + const a = 1; console.log(1); +(1, 1); } @@ -667,17 +666,17 @@ call_args_drop_param: { evaluate: true, keep_fargs: false, reduce_vars: true, - toplevel: true, unused: true, } input: { - var a = 1; + const a = 1; console.log(a); +function(a) { return a; }(a, b); } expect: { + const a = 1; console.log(1); +(b, 1); } diff --git a/test/compress/global_defs.js b/test/compress/global_defs.js index bfd1d5f6..f1ba8f32 100644 --- a/test/compress/global_defs.js +++ b/test/compress/global_defs.js @@ -120,7 +120,7 @@ mixed: { properties: true, } input: { - var FOO = { BAR: 0 }; + const FOO = { BAR: 0 }; console.log(FOO.BAR); console.log(++CONFIG.DEBUG); console.log(++CONFIG.VALUE); @@ -130,7 +130,7 @@ mixed: { console.log(CONFIG); } expect: { - var FOO = { BAR: 0 }; + const FOO = { BAR: 0 }; console.log("moo"); console.log(++CONFIG.DEBUG); console.log(++CONFIG.VALUE); diff --git a/test/compress/issue-1041.js b/test/compress/issue-1041.js index cc351405..cdbc22cc 100644 --- a/test/compress/issue-1041.js +++ b/test/compress/issue-1041.js @@ -1,3 +1,16 @@ +const_declaration: { + options = { + evaluate: true + }; + + input: { + const goog = goog || {}; + } + expect: { + const goog = goog || {}; + } +} + const_pragma: { options = { evaluate: true, diff --git a/test/compress/issue-1588.js b/test/compress/issue-1588.js index 187d9f6c..4e20a21d 100644 --- a/test/compress/issue-1588.js +++ b/test/compress/issue-1588.js @@ -85,3 +85,15 @@ unsafe_undefined: { } expect_stdout: true } + +runtime_error: { + input: { + const a = 1; + console.log(a++); + } + expect: { + const a = 1; + console.log(a++); + } + expect_stdout: true +} diff --git a/test/compress/issue-208.js b/test/compress/issue-208.js index faaf4139..fb9861f6 100644 --- a/test/compress/issue-208.js +++ b/test/compress/issue-208.js @@ -38,7 +38,7 @@ mixed: { } } input: { - var ENV = 3; + const ENV = 3; var FOO = 4; f(ENV * 10); --FOO; @@ -49,7 +49,7 @@ mixed: { x = DEBUG; } expect: { - var ENV = 3; + const ENV = 3; var FOO = 4; f(10); --FOO; @@ -60,7 +60,7 @@ mixed: { x = 0; } expect_warnings: [ - 'WARN: global_defs ENV redefined [test/compress/issue-208.js:41,12]', + 'WARN: global_defs ENV redefined [test/compress/issue-208.js:41,14]', 'WARN: global_defs FOO redefined [test/compress/issue-208.js:42,12]', 'WARN: global_defs FOO redefined [test/compress/issue-208.js:44,10]', 'WARN: global_defs DEBUG redefined [test/compress/issue-208.js:45,8]', diff --git a/test/compress/loops.js b/test/compress/loops.js index 89c7e7e9..4d354bcf 100644 --- a/test/compress/loops.js +++ b/test/compress/loops.js @@ -146,6 +146,50 @@ parse_do_while_without_semicolon: { } } + +keep_collapse_const_in_own_block_scope: { + options = { + join_vars: true, + loops: true + } + input: { + var i=2; + const c=5; + while(i--) + console.log(i); + console.log(c); + } + expect: { + var i=2; + const c=5; + for(;i--;) + console.log(i); + console.log(c); + } + expect_stdout: true +} + +keep_collapse_const_in_own_block_scope_2: { + options = { + join_vars: true, + loops: true + } + input: { + const c=5; + var i=2; // Moves to loop, while it did not in previous test + while(i--) + console.log(i); + console.log(c); + } + expect: { + const c=5; + for(var i=2;i--;) + console.log(i); + console.log(c); + } + expect_stdout: true +} + evaluate: { options = { loops: true, diff --git a/test/compress/reduce_vars.js b/test/compress/reduce_vars.js index 2a44492a..430e3797 100644 --- a/test/compress/reduce_vars.js +++ b/test/compress/reduce_vars.js @@ -2190,11 +2190,10 @@ issue_1814_1: { options = { evaluate: true, reduce_vars: true, - toplevel: true, unused: true, } input: { - var a = 42; + const a = 42; !function() { var b = a; !function(a) { @@ -2203,6 +2202,7 @@ issue_1814_1: { }(); } expect: { + const a = 42; !function() { !function(a) { console.log(a++, 42); @@ -2216,11 +2216,10 @@ issue_1814_2: { options = { evaluate: true, reduce_vars: true, - toplevel: true, unused: true, } input: { - var a = "32"; + const a = "32"; !function() { var b = a + 1; !function(a) { @@ -2229,6 +2228,7 @@ issue_1814_2: { }(); } expect: { + const a = "32"; !function() { !function(a) { console.log(a++, "321"); diff --git a/test/compress/sequences.js b/test/compress/sequences.js index 10492565..9edf627e 100644 --- a/test/compress/sequences.js +++ b/test/compress/sequences.js @@ -710,3 +710,27 @@ issue_27: { })(jQuery); } } + +reassign_const: { + options = { + cascade: true, + sequences: true, + side_effects: true, + } + input: { + function f() { + const a = 1; + a++; + return a; + } + console.log(f()); + } + expect: { + function f() { + const a = 1; + return a++, a; + } + console.log(f()); + } + expect_stdout: true +} diff --git a/test/input/invalid/const.js b/test/input/invalid/const.js new file mode 100644 index 00000000..7a2bfd3d --- /dev/null +++ b/test/input/invalid/const.js @@ -0,0 +1,8 @@ +function f() { + const a; +} + +function g() { + "use strict"; + const a; +} diff --git a/test/mocha/cli.js b/test/mocha/cli.js index 38f61f39..8cf53ab4 100644 --- a/test/mocha/cli.js +++ b/test/mocha/cli.js @@ -379,6 +379,21 @@ describe("bin/uglifyjs", function () { done(); }); }); + it("Should throw syntax error (const a)", function(done) { + var command = uglifyjscmd + ' test/input/invalid/const.js'; + + exec(command, function (err, stdout, stderr) { + assert.ok(err); + assert.strictEqual(stdout, ""); + assert.strictEqual(stderr.split(/\n/).slice(0, 4).join("\n"), [ + "Parse error at test/input/invalid/const.js:7,11", + " const a;", + " ^", + "ERROR: Missing initializer in const declaration" + ].join("\n")); + done(); + }); + }); it("Should throw syntax error (delete x)", function(done) { var command = uglifyjscmd + ' test/input/invalid/delete.js';