From 4bceb85cbfa2c944fb97bb3baaa403b266d075c6 Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Tue, 21 Mar 2017 14:11:32 +0800 Subject: [PATCH 1/6] throw parse error on invalid assignments (#1627) fixes #1626 --- lib/parse.js | 4 +--- test/compress/html_comments.js | 16 ---------------- 2 files changed, 1 insertion(+), 19 deletions(-) diff --git a/lib/parse.js b/lib/parse.js index 25fe9319..4d37b85e 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -1501,9 +1501,7 @@ function parse($TEXT, options) { }; function is_assignable(expr) { - if (!options.strict) return true; - if (expr instanceof AST_This) return false; - return (expr instanceof AST_PropAccess || expr instanceof AST_Symbol); + return expr instanceof AST_PropAccess || expr instanceof AST_SymbolRef; }; var maybe_assign = function(no_in) { diff --git a/test/compress/html_comments.js b/test/compress/html_comments.js index 8495b433..fe6ff8ac 100644 --- a/test/compress/html_comments.js +++ b/test/compress/html_comments.js @@ -47,22 +47,6 @@ html_comment_in_greater_than_or_equal: { expect_exact: "function f(a,b){return a-- >=b}"; } -html_comment_in_right_shift_assign: { - input: { - // Note: illegal javascript - function f(a, b) { return a-- >>= b; } - } - expect_exact: "function f(a,b){return a-- >>=b}"; -} - -html_comment_in_zero_fill_right_shift_assign: { - input: { - // Note: illegal javascript - function f(a, b) { return a-- >>>= b; } - } - expect_exact: "function f(a,b){return a-- >>>=b}"; -} - html_comment_in_string_literal: { input: { function f() { return "comment in"; } From ee95c1b38bcf0fbb6c676e98540c1d33f669e936 Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Thu, 23 Mar 2017 01:31:46 +0800 Subject: [PATCH 2/6] metadata cleanup (#1630) - mention performance anomaly in Node 7 and drop from CI - remove unused npm "scripts" - mark browserify dependency as optional - stop `test/mozilla-ast.js` from spamming console output in later versions of Node.js --- .travis.yml | 1 - README.md | 4 +++- package.json | 5 +++-- test/mozilla-ast.js | 4 ++-- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index b2aef3dc..06929a34 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,7 +5,6 @@ node_js: - "0.12" - "4" - "6" - - "7" env: - UGLIFYJS_TEST_ALL=1 matrix: diff --git a/README.md b/README.md index 396f9a94..2399e23f 100644 --- a/README.md +++ b/README.md @@ -10,8 +10,10 @@ There's also an [in-browser online demo](http://lisperator.net/uglifyjs/#demo) (for Firefox, Chrome and probably Safari). -Note: release versions of `uglify-js` only support ECMAScript 5 (ES5). If you wish to minify +#### Note: +- release versions of `uglify-js` only support ECMAScript 5 (ES5). If you wish to minify ES2015+ (ES6+) code then please use the [harmony](#harmony) development branch. +- Node 7 has a known performance regression and runs `uglify-js` twice as slow. Install ------- diff --git a/package.json b/package.json index ab0b87e3..efedaca8 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,6 @@ ], "dependencies": { "source-map": "~0.5.1", - "uglify-to-browserify": "~1.0.0", "yargs": "~3.10.0" }, "devDependencies": { @@ -40,13 +39,15 @@ "estraverse": "~1.5.1", "mocha": "~2.3.4" }, + "optionalDependencies": { + "uglify-to-browserify": "~1.0.0" + }, "browserify": { "transform": [ "uglify-to-browserify" ] }, "scripts": { - "shrinkwrap": "rm ./npm-shrinkwrap.json; rm -rf ./node_modules; npm i && npm shrinkwrap && npm outdated", "test": "node test/run-tests.js" }, "keywords": ["uglify", "uglify-js", "minify", "minifier"] diff --git a/test/mozilla-ast.js b/test/mozilla-ast.js index b5c6c6ed..e4c84df8 100644 --- a/test/mozilla-ast.js +++ b/test/mozilla-ast.js @@ -5,7 +5,7 @@ var UglifyJS = require(".."), escodegen = require("escodegen"), esfuzz = require("esfuzz"), estraverse = require("estraverse"), - prefix = Array(20).join("\b") + " "; + prefix = "\r "; // Normalizes input AST for UglifyJS in order to get correct comparison. @@ -62,7 +62,7 @@ module.exports = function(options) { var ast1 = normalizeInput(esfuzz.generate({ maxDepth: options.maxDepth })); - + var ast2 = UglifyJS .AST_Node From a00040dd93083548aa065c5ae8942e3c2600cbbe Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Thu, 23 Mar 2017 06:11:16 +0800 Subject: [PATCH 3/6] fix a bug in simple_glob (#1632) - "?" should not match "/" - other minor clean-ups --- test/input/issue-1632/^{foo}[bar](baz)+$.js | 1 + test/mocha/glob.js | 34 +++++++++++ tools/exports.js | 2 +- tools/node.js | 65 ++++++++------------- 4 files changed, 61 insertions(+), 41 deletions(-) create mode 100644 test/input/issue-1632/^{foo}[bar](baz)+$.js diff --git a/test/input/issue-1632/^{foo}[bar](baz)+$.js b/test/input/issue-1632/^{foo}[bar](baz)+$.js new file mode 100644 index 00000000..f92e3a10 --- /dev/null +++ b/test/input/issue-1632/^{foo}[bar](baz)+$.js @@ -0,0 +1 @@ +console.log(x); \ No newline at end of file diff --git a/test/mocha/glob.js b/test/mocha/glob.js index 30313656..557489c1 100644 --- a/test/mocha/glob.js +++ b/test/mocha/glob.js @@ -1,5 +1,6 @@ var Uglify = require('../../'); var assert = require("assert"); +var path = require("path"); describe("minify() with input file globs", function() { it("minify() with one input file glob string.", function() { @@ -21,4 +22,37 @@ describe("minify() with input file globs", function() { }); assert.strictEqual(result.code, 'var print=console.log.bind(console);print("qux",function(n){return 3*n}(3),function(n){return n/2}(12)),function(n){print("Foo:",2*n)}(11);'); }); + it("should throw with non-matching glob string", function() { + var glob = "test/input/issue-1242/blah.*"; + assert.strictEqual(Uglify.simple_glob(glob).length, 1); + assert.strictEqual(Uglify.simple_glob(glob)[0], glob); + assert.throws(function() { + Uglify.minify(glob); + }, "should throw file not found"); + }); + it('"?" in glob string should not match "/"', function() { + var glob = "test/input?issue-1242/foo.*"; + assert.strictEqual(Uglify.simple_glob(glob).length, 1); + assert.strictEqual(Uglify.simple_glob(glob)[0], glob); + assert.throws(function() { + Uglify.minify(glob); + }, "should throw file not found"); + }); + it("should handle special characters in glob string", function() { + var result = Uglify.minify("test/input/issue-1632/^{*}[???](*)+$.??"); + assert.strictEqual(result.code, "console.log(x);"); + }); + it("should handle array of glob strings - matching and otherwise", function() { + var dir = "test/input/issue-1242"; + var matches = Uglify.simple_glob([ + path.join(dir, "b*.es5"), + path.join(dir, "z*.es5"), + path.join(dir, "*.js"), + ]); + assert.strictEqual(matches.length, 4); + assert.strictEqual(matches[0], path.join(dir, "bar.es5")); + assert.strictEqual(matches[1], path.join(dir, "baz.es5")); + assert.strictEqual(matches[2], path.join(dir, "z*.es5")); + assert.strictEqual(matches[3], path.join(dir, "qux.js")); + }); }); diff --git a/tools/exports.js b/tools/exports.js index 54aa23e8..d83739d5 100644 --- a/tools/exports.js +++ b/tools/exports.js @@ -18,6 +18,6 @@ exports["tokenizer"] = tokenizer; exports["is_identifier"] = is_identifier; exports["SymbolDef"] = SymbolDef; -if (typeof DEBUG !== "undefined" && DEBUG) { +if (global.UGLIFY_DEBUG) { exports["EXPECT_DIRECTIVE"] = EXPECT_DIRECTIVE; } diff --git a/tools/node.js b/tools/node.js index c64b4e5c..6568a741 100644 --- a/tools/node.js +++ b/tools/node.js @@ -7,7 +7,8 @@ var path = require("path"); var fs = require("fs"); -var FILES = exports.FILES = [ +var UglifyJS = exports; +var FILES = UglifyJS.FILES = [ "../lib/utils.js", "../lib/ast.js", "../lib/parse.js", @@ -20,17 +21,14 @@ var FILES = exports.FILES = [ "../lib/propmangle.js", "./exports.js", ].map(function(file){ - return fs.realpathSync(path.join(path.dirname(__filename), file)); + return require.resolve(file); }); -var UglifyJS = exports; - -new Function("MOZ_SourceMap", "exports", "DEBUG", FILES.map(function(file){ +new Function("MOZ_SourceMap", "exports", FILES.map(function(file){ return fs.readFileSync(file, "utf8"); }).join("\n\n"))( require("source-map"), - UglifyJS, - !!global.UGLIFY_DEBUG + UglifyJS ); UglifyJS.AST_Node.warn_function = function(txt) { @@ -46,7 +44,7 @@ function read_source_map(code) { return JSON.parse(new Buffer(match[2], "base64")); } -exports.minify = function(files, options) { +UglifyJS.minify = function(files, options) { options = UglifyJS.defaults(options, { spidermonkey : false, outSourceMap : null, @@ -181,7 +179,7 @@ exports.minify = function(files, options) { }; }; -// exports.describe_ast = function() { +// UglifyJS.describe_ast = function() { // function doitem(ctor) { // var sub = {}; // ctor.SUBCLASSES.forEach(function(ctor){ @@ -195,7 +193,7 @@ exports.minify = function(files, options) { // return doitem(UglifyJS.AST_Node).sub; // } -exports.describe_ast = function() { +UglifyJS.describe_ast = function() { var out = UglifyJS.OutputStream({ beautify: true }); function doitem(ctor) { out.print("AST_" + ctor.TYPE); @@ -249,13 +247,13 @@ function readReservedFile(filename, reserved) { return reserved; } -exports.readReservedFile = readReservedFile; +UglifyJS.readReservedFile = readReservedFile; -exports.readDefaultReservedFile = function(reserved) { - return readReservedFile(path.join(__dirname, "domprops.json"), reserved); +UglifyJS.readDefaultReservedFile = function(reserved) { + return readReservedFile(require.resolve("./domprops.json"), reserved); }; -exports.readNameCache = function(filename, key) { +UglifyJS.readNameCache = function(filename, key) { var cache = null; if (filename) { try { @@ -273,7 +271,7 @@ exports.readNameCache = function(filename, key) { return cache; }; -exports.writeNameCache = function(filename, key, cache) { +UglifyJS.writeNameCache = function(filename, key, cache) { if (filename) { var data; try { @@ -294,13 +292,9 @@ exports.writeNameCache = function(filename, key, cache) { // Example: "foo/bar/*baz??.*.js" // Argument `glob` may be a string or an array of strings. // Returns an array of strings. Garbage in, garbage out. -exports.simple_glob = function simple_glob(glob) { - var results = []; +UglifyJS.simple_glob = function simple_glob(glob) { if (Array.isArray(glob)) { - glob.forEach(function(elem) { - results = results.concat(simple_glob(elem)); - }); - return results; + return [].concat.apply([], glob.map(simple_glob)); } if (glob.match(/\*|\?/)) { var dir = path.dirname(glob); @@ -308,28 +302,19 @@ exports.simple_glob = function simple_glob(glob) { var entries = fs.readdirSync(dir); } catch (ex) {} if (entries) { - var pattern = "^" + (path.basename(glob) - .replace(/\(/g, "\\(") - .replace(/\)/g, "\\)") - .replace(/\{/g, "\\{") - .replace(/\}/g, "\\}") - .replace(/\[/g, "\\[") - .replace(/\]/g, "\\]") - .replace(/\+/g, "\\+") - .replace(/\^/g, "\\^") - .replace(/\$/g, "\\$") + var pattern = "^" + path.basename(glob) + .replace(/[.+^$[\]\\(){}]/g, "\\$&") .replace(/\*/g, "[^/\\\\]*") - .replace(/\./g, "\\.") - .replace(/\?/g, ".")) + "$"; + .replace(/\?/g, "[^/\\\\]") + "$"; var mod = process.platform === "win32" ? "i" : ""; var rx = new RegExp(pattern, mod); - for (var i in entries) { - if (rx.test(entries[i])) - results.push(dir + "/" + entries[i]); - } + var results = entries.filter(function(name) { + return rx.test(name); + }).map(function(name) { + return path.join(dir, name); + }); + if (results.length) return results; } } - if (results.length === 0) - results = [ glob ]; - return results; + return [ glob ]; }; From c0f3feae9f251abbea5ae1198e1a6784dd3261f7 Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Thu, 23 Mar 2017 06:49:49 +0800 Subject: [PATCH 4/6] introduce compressor.info() (#1633) report the following only when `options.warnings = "verbose"` - unused elements due to inlining - collpased variables --- lib/compress.js | 13 +++++++++---- test/compress/issue-1034.js | 7 ++----- test/run-tests.js | 2 +- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/lib/compress.js b/lib/compress.js index 41612ad7..a8fbb8b3 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -131,6 +131,11 @@ merge(Compressor.prototype, { } return node; }, + info: function() { + if (this.options.warnings == "verbose") { + AST_Node.warn.apply(AST_Node, arguments); + } + }, warn: function(text, props) { if (this.options.warnings) { // only emit unique warnings @@ -664,7 +669,7 @@ merge(Compressor.prototype, { // Further optimize statement after substitution. stat.reset_opt_flags(compressor); - compressor.warn("Collapsing " + (is_constant ? "constant" : "variable") + + compressor.info("Collapsing " + (is_constant ? "constant" : "variable") + " " + var_name + " [{file}:{line},{col}]", node.start); CHANGED = true; return value; @@ -1828,7 +1833,7 @@ merge(Compressor.prototype, { sym.__unused = true; if (trim) { a.pop(); - compressor.warn("Dropping unused function argument {name} [{file}:{line},{col}]", { + compressor[sym.unreferenced() ? "warn" : "info"]("Dropping unused function argument {name} [{file}:{line},{col}]", { name : sym.name, file : sym.start.file, line : sym.start.line, @@ -1843,7 +1848,7 @@ merge(Compressor.prototype, { } if (drop_funcs && node instanceof AST_Defun && node !== self) { if (!(node.name.definition().id in in_use_ids)) { - compressor.warn("Dropping unused function {name} [{file}:{line},{col}]", { + compressor[node.name.unreferenced() ? "warn" : "info"]("Dropping unused function {name} [{file}:{line},{col}]", { name : node.name.name, file : node.name.start.file, line : node.name.start.line, @@ -1867,7 +1872,7 @@ merge(Compressor.prototype, { compressor.warn("Side effects in initialization of unused variable {name} [{file}:{line},{col}]", w); return true; } - compressor.warn("Dropping unused variable {name} [{file}:{line},{col}]", w); + compressor[def.name.unreferenced() ? "warn" : "info"]("Dropping unused variable {name} [{file}:{line},{col}]", w); return false; }); // place uninitialized names at the start diff --git a/test/compress/issue-1034.js b/test/compress/issue-1034.js index b91eaced..57c584ab 100644 --- a/test/compress/issue-1034.js +++ b/test/compress/issue-1034.js @@ -39,7 +39,7 @@ non_hoisted_function_after_return_2a: { hoist_funs: false, dead_code: true, conditionals: true, comparisons: true, evaluate: true, booleans: true, loops: true, unused: true, keep_fargs: true, if_return: true, join_vars: true, cascade: true, side_effects: true, - collapse_vars: false, passes: 2 + collapse_vars: false, passes: 2, warnings: "verbose" } input: { function foo(x) { @@ -75,7 +75,7 @@ non_hoisted_function_after_return_2a: { "WARN: Declarations in unreachable code! [test/compress/issue-1034.js:53,12]", "WARN: Dropping unreachable code [test/compress/issue-1034.js:56,12]", "WARN: Dropping unused variable b [test/compress/issue-1034.js:51,20]", - "WARN: Dropping unused variable c [test/compress/issue-1034.js:53,16]" + "WARN: Dropping unused variable c [test/compress/issue-1034.js:53,16]", ] } @@ -114,8 +114,5 @@ non_hoisted_function_after_return_2b: { "WARN: Dropping unreachable code [test/compress/issue-1034.js:97,12]", "WARN: Declarations in unreachable code! [test/compress/issue-1034.js:97,12]", "WARN: Dropping unreachable code [test/compress/issue-1034.js:101,12]", - "WARN: Dropping unused variable b [test/compress/issue-1034.js:95,20]", - "WARN: Dropping unused variable c [test/compress/issue-1034.js:97,16]" ] } - diff --git a/test/run-tests.js b/test/run-tests.js index a3184d72..09e70021 100755 --- a/test/run-tests.js +++ b/test/run-tests.js @@ -114,7 +114,7 @@ function run_compress_tests() { U.AST_Node.warn_function = function(text) { warnings_emitted.push("WARN: " + text); }; - options.warnings = true; + if (!options.warnings) options.warnings = true; } var cmp = new U.Compressor(options, true); var output_options = test.beautify || {}; From 48ffbef51d914824916f387d756b263c341f032e Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Thu, 23 Mar 2017 07:17:34 +0800 Subject: [PATCH 5/6] account for cross-scope modifications in `collapse_vars` (#1634) mostly done by @kzc fixes #1631 --- lib/compress.js | 12 ++++ test/compress/collapse_vars.js | 107 +++++++++++++++++++++++++++++++++ test/mocha/glob.js | 4 +- 3 files changed, 121 insertions(+), 2 deletions(-) diff --git a/lib/compress.js b/lib/compress.js index a8fbb8b3..cfa8f230 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -619,12 +619,24 @@ merge(Compressor.prototype, { || node instanceof AST_IterationStatement || (parent instanceof AST_If && node !== parent.condition) || (parent instanceof AST_Conditional && node !== parent.condition) + || (node instanceof AST_SymbolRef + && !are_references_in_scope(node.definition(), self)) || (parent instanceof AST_Binary && (parent.operator == "&&" || parent.operator == "||") && node === parent.right) || (parent instanceof AST_Switch && node !== parent.expression)) { return side_effects_encountered = unwind = true, node; } + function are_references_in_scope(def, scope) { + if (def.orig.length === 1 + && def.orig[0] instanceof AST_SymbolDefun) return true; + if (def.scope !== scope) return false; + var refs = def.references; + for (var i = 0, len = refs.length; i < len; i++) { + if (refs[i].scope !== scope) return false; + } + return true; + } }, function postorder(node) { if (unwind) return node; diff --git a/test/compress/collapse_vars.js b/test/compress/collapse_vars.js index 5a7c001f..6f273b97 100644 --- a/test/compress/collapse_vars.js +++ b/test/compress/collapse_vars.js @@ -1415,3 +1415,110 @@ issue_1605_2: { (new Object).p = 1; } } + +issue_1631_1: { + options = { + cascade: true, + collapse_vars: true, + hoist_funs: true, + join_vars: true, + sequences: true, + side_effects: true, + } + input: { + var pc = 0; + function f(x) { + pc = 200; + return 100; + } + function x() { + var t = f(); + pc += t; + return pc; + } + console.log(x()); + } + expect: { + function f(x) { + return pc = 200, 100; + } + function x() { + var t = f(); + return pc += t; + } + var pc = 0; + console.log(x()); + } + expect_stdout: "300" +} + +issue_1631_2: { + options = { + cascade: true, + collapse_vars: true, + hoist_funs: true, + join_vars: true, + sequences: true, + side_effects: true, + } + input: { + var a = 0, b = 1; + function f() { + a = 2; + return 4; + } + function g() { + var t = f(); + b = a + t; + return b; + } + console.log(g()); + } + expect: { + function f() { + return a = 2, 4; + } + function g() { + var t = f(); + return b = a + t; + } + var a = 0, b = 1; + console.log(g()); + } + expect_stdout: "6" +} + +issue_1631_3: { + options = { + cascade: true, + collapse_vars: true, + hoist_funs: true, + join_vars: true, + sequences: true, + side_effects: true, + } + input: { + function g() { + var a = 0, b = 1; + function f() { + a = 2; + return 4; + } + var t = f(); + b = a + t; + return b; + } + console.log(g()); + } + expect: { + function g() { + function f() { + return a = 2, 4; + } + var a = 0, b = 1, t = f(); + return b = a + t; + } + console.log(g()); + } + expect_stdout: "6" +} diff --git a/test/mocha/glob.js b/test/mocha/glob.js index 557489c1..e291efc8 100644 --- a/test/mocha/glob.js +++ b/test/mocha/glob.js @@ -5,7 +5,7 @@ var path = require("path"); describe("minify() with input file globs", function() { it("minify() with one input file glob string.", function() { var result = Uglify.minify("test/input/issue-1242/foo.*"); - assert.strictEqual(result.code, 'function foo(o){print("Foo:",2*o)}var print=console.log.bind(console);'); + assert.strictEqual(result.code, 'function foo(o){var n=2*o;print("Foo:",n)}var print=console.log.bind(console);'); }); it("minify() with an array of one input file glob.", function() { var result = Uglify.minify([ @@ -20,7 +20,7 @@ describe("minify() with input file globs", function() { ], { compress: { toplevel: true } }); - assert.strictEqual(result.code, 'var print=console.log.bind(console);print("qux",function(n){return 3*n}(3),function(n){return n/2}(12)),function(n){print("Foo:",2*n)}(11);'); + assert.strictEqual(result.code, 'var print=console.log.bind(console),a=function(n){return 3*n}(3),b=function(n){return n/2}(12);print("qux",a,b),function(n){var o=2*n;print("Foo:",o)}(11);'); }); it("should throw with non-matching glob string", function() { var glob = "test/input/issue-1242/blah.*"; From 6b2f34769a5572bdd9db034f19bbc2a0b6e6dabb Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Thu, 23 Mar 2017 13:36:47 +0800 Subject: [PATCH 6/6] v2.8.15 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index efedaca8..f095c793 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "homepage": "http://lisperator.net/uglifyjs", "author": "Mihai Bazon (http://lisperator.net/)", "license": "BSD-2-Clause", - "version": "2.8.14", + "version": "2.8.15", "engines": { "node": ">=0.8.0" },