From 524a8a42a4c8d136a8f33739f8fea58ce78dd92c Mon Sep 17 00:00:00 2001 From: Artemy Tregubenko Date: Fri, 9 May 2014 18:09:38 +0200 Subject: [PATCH 01/30] added @ngInject support for inline functions --- lib/compress.js | 76 +++++++++++++++++++++++---------- test/compress/angular-inject.js | 67 +++++++++++++++++++++++++++++ 2 files changed, 120 insertions(+), 23 deletions(-) create mode 100644 test/compress/angular-inject.js diff --git a/lib/compress.js b/lib/compress.js index 7df66938..5d8e6d32 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -224,6 +224,17 @@ merge(Compressor.prototype, { return statements; function process_for_angular(statements) { + function has_inject(comment) { + return /@ngInject/.test(comment.value); + } + function make_arguments_names_list(func) { + return func.argnames.map(function(sym){ + return make_node(AST_String, sym, { value: sym.name }); + }); + } + function make_array(orig, elements) { + return make_node(AST_Array, orig, { elements: elements }); + } function make_injector(func, name) { return make_node(AST_SimpleStatement, func, { body: make_node(AST_Assign, func, { @@ -232,37 +243,56 @@ merge(Compressor.prototype, { expression: make_node(AST_SymbolRef, name, name), property: "$inject" }), - right: make_node(AST_Array, func, { - elements: func.argnames.map(function(sym){ - return make_node(AST_String, sym, { value: sym.name }); - }) - }) + right: make_array(func, make_arguments_names_list(func)) }) }); } + function check_expression(body) { + if (body && body.args) { + // if this is a function call check all of arguments passed + body.args.forEach(function(argument, index, array) { + var comments = argument.start.comments_before; + // if the argument is function preceded by @ngInject + if (argument instanceof AST_Lambda && comments.length && has_inject(comments[0])) { + // replace the function with an array of names of its parameters and function at the end + array[index] = make_array(argument, make_arguments_names_list(argument).concat(argument)); + } + }); + // if this is chained call check previous one recursively + if (body.expression && body.expression.expression) { + check_expression(body.expression.expression); + } + } + } return statements.reduce(function(a, stat){ a.push(stat); - var token = stat.start; - var comments = token.comments_before; - if (comments && comments.length > 0) { - var last = comments.pop(); - if (/@ngInject/.test(last.value)) { - // case 1: defun - if (stat instanceof AST_Defun) { - a.push(make_injector(stat, stat.name)); - } - else if (stat instanceof AST_Definitions) { - stat.definitions.forEach(function(def){ - if (def.value && def.value instanceof AST_Lambda) { - a.push(make_injector(def.value, def.name)); - } - }); - } - else { - compressor.warn("Unknown statement marked with @ngInject [{file}:{line},{col}]", token); + + if (stat.body && stat.body.args) { + check_expression(stat.body); + } else { + var token = stat.start; + var comments = token.comments_before; + if (comments && comments.length > 0) { + var last = comments.pop(); + if (has_inject(last)) { + // case 1: defun + if (stat instanceof AST_Defun) { + a.push(make_injector(stat, stat.name)); + } + else if (stat instanceof AST_Definitions) { + stat.definitions.forEach(function(def) { + if (def.value && def.value instanceof AST_Lambda) { + a.push(make_injector(def.value, def.name)); + } + }); + } + else { + compressor.warn("Unknown statement marked with @ngInject [{file}:{line},{col}]", token); + } } } } + return a; }, []); } diff --git a/test/compress/angular-inject.js b/test/compress/angular-inject.js new file mode 100644 index 00000000..1459361c --- /dev/null +++ b/test/compress/angular-inject.js @@ -0,0 +1,67 @@ +ng_inject_defun: { + options = { + angular: true + }; + input: { + /*@ngInject*/ + function Controller(dependency) { + return dependency; + } + } + expect: { + function Controller(dependency) { + return dependency; + } + Controller.$inject=['dependency'] + } +} + +ng_inject_assignment: { + options = { + angular: true + }; + input: { + /*@ngInject*/ + var Controller = function(dependency) { + return dependency; + } + } + expect: { + var Controller = function(dependency) { + return dependency; + } + Controller.$inject=['dependency'] + } +} + +ng_inject_inline: { + options = { + angular: true + }; + input: { + angular.module('a'). + factory('b', + /*@ngInject*/ + function(dependency) { + return dependency; + }). + directive('c', + /*@ngInject*/ + function(anotherDependency) { + return anotherDependency; + }) + } + expect: { + angular.module('a'). + factory('b',[ + 'dependency', + function(dependency) { + return dependency; + }]). + directive('c',[ + 'anotherDependency', + function(anotherDependency) { + return anotherDependency; + }]) + } +} \ No newline at end of file From 6006dd933d30b5480002e23b5ae77e0c76059233 Mon Sep 17 00:00:00 2001 From: Artemy Tregubenko Date: Tue, 8 Jul 2014 11:16:35 +0200 Subject: [PATCH 02/30] added newline at the end of the file --- test/compress/angular-inject.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/compress/angular-inject.js b/test/compress/angular-inject.js index 1459361c..8b24c846 100644 --- a/test/compress/angular-inject.js +++ b/test/compress/angular-inject.js @@ -64,4 +64,4 @@ ng_inject_inline: { return anotherDependency; }]) } -} \ No newline at end of file +} From 21b3c890a1dab21156f358bf388a2f9f507d1711 Mon Sep 17 00:00:00 2001 From: Jacob Kristhammar Date: Tue, 9 Sep 2014 13:02:50 +0200 Subject: [PATCH 03/30] Use uglify source map token names if missing --- lib/sourcemap.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/sourcemap.js b/lib/sourcemap.js index 663ef12e..948e3b39 100644 --- a/lib/sourcemap.js +++ b/lib/sourcemap.js @@ -70,7 +70,7 @@ function SourceMap(options) { source = info.source; orig_line = info.line; orig_col = info.column; - name = info.name; + name = info.name || name; } generator.addMapping({ generated : { line: gen_line + options.dest_line_diff, column: gen_col }, From f36a1eaa8b5203ab7e4616108c33a0b68668a8db Mon Sep 17 00:00:00 2001 From: Mihai Bazon Date: Mon, 20 Oct 2014 18:12:13 +0300 Subject: [PATCH 04/30] Add option to allow return outside of functions. Close #288 --- bin/uglifyjs | 9 ++++++--- lib/parse.js | 3 ++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/bin/uglifyjs b/bin/uglifyjs index 3a3318b2..fc33f96f 100755 --- a/bin/uglifyjs +++ b/bin/uglifyjs @@ -64,6 +64,7 @@ You need to pass an argument to this option to specify the name that your module .describe("v", "Verbose") .describe("V", "Print version number and exit.") .describe("noerr", "Don't throw an error for unknown options in -c, -b or -m.") + .describe("bare-returns", "Allow return outside of functions. Useful when minifying CommonJS modules.") .alias("p", "prefix") .alias("o", "output") @@ -100,6 +101,7 @@ You need to pass an argument to this option to specify the name that your module .boolean("lint") .boolean("V") .boolean("noerr") + .boolean("bare-returns") .wrap(80) @@ -275,9 +277,10 @@ async.eachLimit(files, 1, function (file, cb) { else { try { TOPLEVEL = UglifyJS.parse(code, { - filename : file, - toplevel : TOPLEVEL, - expression : ARGS.expr, + filename : file, + toplevel : TOPLEVEL, + expression : ARGS.expr, + bare_returns : ARGS.bare_returns, }); } catch(ex) { if (ex instanceof UglifyJS.JS_Parse_Error) { diff --git a/lib/parse.js b/lib/parse.js index de982b1e..931e5f66 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -609,6 +609,7 @@ function parse($TEXT, options) { toplevel : null, expression : false, html5_comments : true, + bare_returns : false, }); var S = { @@ -788,7 +789,7 @@ function parse($TEXT, options) { return if_(); case "return": - if (S.in_function == 0) + if (S.in_function == 0 && !options.bare_returns) croak("'return' outside of function"); return new AST_Return({ value: ( is("punc", ";") From fe06fc85d3324b13518c8d44977dcd5ac19f9bbf Mon Sep 17 00:00:00 2001 From: Cheng Liangyu Date: Mon, 1 Dec 2014 13:16:44 +0800 Subject: [PATCH 05/30] fix base54 --- lib/scope.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/scope.js b/lib/scope.js index 6f29921f..d252d535 100644 --- a/lib/scope.js +++ b/lib/scope.js @@ -471,7 +471,9 @@ var base54 = (function() { base54.freq = function(){ return frequency }; function base54(num) { var ret = "", base = 54; + num++; do { + num--; ret += String.fromCharCode(chars[num % base]); num = Math.floor(num / base); base = 64; From 5538ec7bd8a64c7fcc45895308a8463e4ca4d00a Mon Sep 17 00:00:00 2001 From: Mihai Bazon Date: Tue, 9 Dec 2014 15:21:44 +0200 Subject: [PATCH 06/30] v2.4.16 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f3e3c533..9d1ee87a 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "description": "JavaScript parser, mangler/compressor and beautifier toolkit", "homepage": "http://lisperator.net/uglifyjs", "main": "tools/node.js", - "version": "2.4.15", + "version": "2.4.16", "engines": { "node" : ">=0.4.0" }, "maintainers": [{ "name": "Mihai Bazon", From 39d8880f2cd17c9b96bf73a3bcd7c27a89cf919e Mon Sep 17 00:00:00 2001 From: micschro Date: Wed, 17 Dec 2014 16:31:03 +0100 Subject: [PATCH 07/30] Fix max_line_len not working for JSON files As `maybe_newline()` is only called when `might_need_semicolon` is `true`, the `max_line_len` option has no effect for files without (or with very few) semicolons (like JSON files). A simple for this problem is to use `maybe_newline()` instead of `noop` as the `newline()` function in non-beautify mode. --- lib/output.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/output.js b/lib/output.js index 7fe61af3..b713be83 100644 --- a/lib/output.js +++ b/lib/output.js @@ -221,7 +221,7 @@ function OutputStream(options) { var newline = options.beautify ? function() { print("\n"); - } : noop; + } : maybe_newline; var semicolon = options.beautify ? function() { print(";"); From c75f5a1fd85368edb3456637c544dee0cad1011e Mon Sep 17 00:00:00 2001 From: Richard van Velzen Date: Wed, 31 Dec 2014 12:23:00 +0100 Subject: [PATCH 08/30] Fix #597 NaN and Infinity were replaced in the output generation, instead of during compression. This could lead to results where `1/0` was inserted without parens leading to invalid output. The nodes are replaced in the compression step now, and the output generation returns their regular names. This should not be a problem, since they're already only constructed from the original name. --- lib/compress.js | 20 ++++++++++++++++++-- lib/output.js | 10 ++-------- test/compress/issue-597.js | 25 +++++++++++++++++++++++++ 3 files changed, 45 insertions(+), 10 deletions(-) create mode 100644 test/compress/issue-597.js diff --git a/lib/compress.js b/lib/compress.js index e63ce148..f8030722 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -2203,14 +2203,30 @@ merge(Compressor.prototype, { case "undefined": return make_node(AST_Undefined, self); case "NaN": - return make_node(AST_NaN, self); + return make_node(AST_NaN, self).transform(compressor); case "Infinity": - return make_node(AST_Infinity, self); + return make_node(AST_Infinity, self).transform(compressor); } } return self; }); + OPT(AST_Infinity, function (self, compressor) { + return make_node(AST_Binary, self, { + operator : '/', + left : make_node(AST_Number, null, {value: 1}), + right : make_node(AST_Number, null, {value: 0}) + }); + }); + + OPT(AST_NaN, function (self, compressor) { + return make_node(AST_Binary, self, { + operator : '/', + left : make_node(AST_Number, null, {value: 0}), + right : make_node(AST_Number, null, {value: 0}) + }); + }); + OPT(AST_Undefined, function(self, compressor){ if (compressor.option("unsafe")) { var scope = compressor.find_parent(AST_Scope); diff --git a/lib/output.js b/lib/output.js index 7fe61af3..5a8f603c 100644 --- a/lib/output.js +++ b/lib/output.js @@ -549,12 +549,6 @@ function OutputStream(options) { return true; }); - PARENS(AST_NaN, function(output){ - var p = output.parent(); - if (p instanceof AST_PropAccess && p.expression === this) - return true; - }); - PARENS([ AST_Assign, AST_Conditional ], function (output){ var p = output.parent(); // !(a = false) → true @@ -1109,10 +1103,10 @@ function OutputStream(options) { }); DEFPRINT(AST_Hole, noop); DEFPRINT(AST_Infinity, function(self, output){ - output.print("1/0"); + output.print("Infinity"); }); DEFPRINT(AST_NaN, function(self, output){ - output.print("0/0"); + output.print("NaN"); }); DEFPRINT(AST_This, function(self, output){ output.print("this"); diff --git a/test/compress/issue-597.js b/test/compress/issue-597.js new file mode 100644 index 00000000..3c651a4c --- /dev/null +++ b/test/compress/issue-597.js @@ -0,0 +1,25 @@ +NaN_and_Infinity_must_have_parens: { + options = {}; + input: { + Infinity.toString(); + NaN.toString(); + } + expect: { + (1/0).toString(); + (0/0).toString(); + } +} + +NaN_and_Infinity_should_not_be_replaced_when_they_are_redefined: { + options = {}; + input: { + var Infinity, NaN; + Infinity.toString(); + NaN.toString(); + } + expect: { + var Infinity, NaN; + Infinity.toString(); + NaN.toString(); + } +} From 6d1c3e1aec62021c475c51f21fd553b83147218c Mon Sep 17 00:00:00 2001 From: Kenneth Powers Date: Thu, 1 Jan 2015 01:04:54 -0500 Subject: [PATCH 09/30] Use yargs instead of optimist. --- bin/uglifyjs | 6 +++--- package.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/bin/uglifyjs b/bin/uglifyjs index fc33f96f..bade20cc 100755 --- a/bin/uglifyjs +++ b/bin/uglifyjs @@ -5,12 +5,12 @@ var UglifyJS = require("../tools/node"); var sys = require("util"); -var optimist = require("optimist"); +var yargs = require("yargs"); var fs = require("fs"); var path = require("path"); var async = require("async"); var acorn; -var ARGS = optimist +var ARGS = yargs .usage("$0 input1.js [input2.js ...] [options]\n\ Use a single dash to read input from the standard input.\ \n\n\ @@ -129,7 +129,7 @@ if (ARGS.ast_help) { } if (ARGS.h || ARGS.help) { - sys.puts(optimist.help()); + sys.puts(yargs.help()); process.exit(0); } diff --git a/package.json b/package.json index 9d1ee87a..8e066602 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "dependencies": { "async" : "~0.2.6", "source-map" : "0.1.34", - "optimist": "~0.3.5", + "yargs": "~1.3.3", "uglify-to-browserify": "~1.0.0" }, "devDependencies": { From f4d36a58c28cd6dd598a5e92c6ab56605b8c17e4 Mon Sep 17 00:00:00 2001 From: Richard van Velzen Date: Thu, 23 Oct 2014 15:57:12 +0200 Subject: [PATCH 10/30] Fix #569 When no arguments are given to `new Function()`, it should be treated as a regular anonymous function (http://es5.github.io/#x15.3.2.1) --- lib/compress.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/compress.js b/lib/compress.js index f8030722..2c950992 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -1722,6 +1722,11 @@ merge(Compressor.prototype, { }).transform(compressor); break; case "Function": + // new Function() => function(){} + if (self.args.length == 0) return make_node(AST_Function, self, { + argnames: [], + body: [] + }); if (all(self.args, function(x){ return x instanceof AST_String })) { // quite a corner-case, but we can handle it: // https://github.com/mishoo/UglifyJS2/issues/203 From 24bc09b79bf9ca3a09eef4f536e7637568f7097a Mon Sep 17 00:00:00 2001 From: Richard van Velzen Date: Thu, 23 Oct 2014 16:16:19 +0200 Subject: [PATCH 11/30] Fix #556 `\uFEFF` (ZERO WIDTH NO-BREAK SPACE) is removed when parsing, but was un-escaped for the output when `ascii_only` was false. When using UglifyJS multiple times (creating packages from minified sources, for example), this would lead to problems because the byte was removed when parsing for the second time. --- lib/output.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/output.js b/lib/output.js index 7bca4dae..72bcdd5e 100644 --- a/lib/output.js +++ b/lib/output.js @@ -86,7 +86,7 @@ function OutputStream(options) { function make_string(str) { var dq = 0, sq = 0; - str = str.replace(/[\\\b\f\n\r\t\x22\x27\u2028\u2029\0]/g, function(s){ + str = str.replace(/[\\\b\f\n\r\t\x22\x27\u2028\u2029\0\ufeff]/g, function(s){ switch (s) { case "\\": return "\\\\"; case "\b": return "\\b"; @@ -98,6 +98,7 @@ function OutputStream(options) { case '"': ++dq; return '"'; case "'": ++sq; return "'"; case "\0": return "\\x00"; + case "\ufeff": return "\\ufeff"; } return s; }); From 5bff65c1321994f6e84aa726d68451a086cab937 Mon Sep 17 00:00:00 2001 From: Peter Dave Hello Date: Thu, 25 Dec 2014 22:41:33 +0800 Subject: [PATCH 12/30] Use svg instead of png to get better image quality --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f4d82ace..b5b5eecb 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ UglifyJS 2 ========== -[![Build Status](https://travis-ci.org/mishoo/UglifyJS2.png)](https://travis-ci.org/mishoo/UglifyJS2) +[![Build Status](https://travis-ci.org/mishoo/UglifyJS2.svg)](https://travis-ci.org/mishoo/UglifyJS2) UglifyJS is a JavaScript parser, minifier, compressor or beautifier toolkit. From aa5dd1535245b3785704b2ac7075c2eb51ff42da Mon Sep 17 00:00:00 2001 From: Austin Brown Date: Wed, 26 Mar 2014 23:15:44 -0700 Subject: [PATCH 13/30] Update README.md otions => options --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b5b5eecb..a04ac4e4 100644 --- a/README.md +++ b/README.md @@ -612,7 +612,7 @@ or, for a shortcut you can do: var code = compressed_ast.print_to_string(options); ``` -As usual, `options` is optional. The output stream accepts a lot of otions, +As usual, `options` is optional. The output stream accepts a lot of options, most of them documented above in section “Beautifier options”. The two which we care about here are `source_map` and `comments`. From 718e4756134053bbcf62e9686f698fa3cb5a7e03 Mon Sep 17 00:00:00 2001 From: Derek Wickern Date: Thu, 23 Oct 2014 16:27:53 -0700 Subject: [PATCH 14/30] Fix backslashes in source-map paths on Windows --- bin/uglifyjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/uglifyjs b/bin/uglifyjs index bade20cc..a177cb6f 100755 --- a/bin/uglifyjs +++ b/bin/uglifyjs @@ -252,7 +252,7 @@ async.eachLimit(files, 1, function (file, cb) { } if (ARGS.p != null) { if (P_RELATIVE) { - file = path.relative(path.dirname(ARGS.source_map), file); + file = path.relative(path.dirname(ARGS.source_map), file).replace(/\\/g, '/'); } else { var p = parseInt(ARGS.p, 10); if (!isNaN(p)) { From 4613644cce9f64cef4115cbfff660b41d51a9b9d Mon Sep 17 00:00:00 2001 From: achingbrain Date: Thu, 28 Nov 2013 08:32:39 +0000 Subject: [PATCH 15/30] passes in references to process and Buffer to silence ReferenceErrors --- tools/node.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tools/node.js b/tools/node.js index 084998da..b08559ad 100644 --- a/tools/node.js +++ b/tools/node.js @@ -6,6 +6,8 @@ var sys = require("util"); var UglifyJS = vm.createContext({ sys : sys, console : console, + process : process, + Buffer : Buffer, MOZ_SourceMap : require("source-map") }); From 0d48af3f36e99252a42b2f40d8fc6d361aa5a7b1 Mon Sep 17 00:00:00 2001 From: Richard van Velzen Date: Sun, 4 Jan 2015 20:14:38 +0100 Subject: [PATCH 16/30] Add a "keep_fnames" option to the compressor to retain function expression names See #552. This is useful for stack traces. --- lib/compress.js | 3 ++- test/compress/drop-unused.js | 14 ++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/lib/compress.js b/lib/compress.js index 2c950992..345d8ad6 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -62,6 +62,7 @@ function Compressor(options, false_by_default) { unused : !false_by_default, hoist_funs : !false_by_default, keep_fargs : false, + keep_fnames : false, hoist_vars : false, if_return : !false_by_default, join_vars : !false_by_default, @@ -1666,7 +1667,7 @@ merge(Compressor.prototype, { OPT(AST_Function, function(self, compressor){ self = AST_Lambda.prototype.optimize.call(self, compressor); - if (compressor.option("unused")) { + if (compressor.option("unused") && !compressor.option("keep_fnames")) { if (self.name && self.name.unreferenced()) { self.name = null; } diff --git a/test/compress/drop-unused.js b/test/compress/drop-unused.js index 89bf0088..de4b2220 100644 --- a/test/compress/drop-unused.js +++ b/test/compress/drop-unused.js @@ -163,3 +163,17 @@ used_var_in_catch: { } } } + +keep_fnames: { + options = { unused: true, keep_fnames: true }; + input: { + function foo() { + return function bar(baz) {}; + } + } + expect: { + function foo() { + return function bar() {}; + } + } +} \ No newline at end of file From 0f80b1058d233f11b95fef567d1b37dd88c94f09 Mon Sep 17 00:00:00 2001 From: truiken Date: Fri, 29 Aug 2014 11:41:13 -0700 Subject: [PATCH 17/30] Resolve the relative path to lib files last This allows usage of UglifyJS on build systems which have a flat (or non-matching relative) directory structure for source files. --- tools/node.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/node.js b/tools/node.js index b08559ad..9e9c2c14 100644 --- a/tools/node.js +++ b/tools/node.js @@ -35,7 +35,7 @@ var FILES = exports.FILES = [ "../lib/sourcemap.js", "../lib/mozilla-ast.js" ].map(function(file){ - return path.join(path.dirname(fs.realpathSync(__filename)), file); + return fs.realpathSync(path.join(path.dirname(__filename), file)); }); FILES.forEach(load_global); From ad18689d926d25c7a25b95c630c2ad05b7b5f5b5 Mon Sep 17 00:00:00 2001 From: Caridy Patino Date: Tue, 11 Nov 2014 14:38:01 -0500 Subject: [PATCH 18/30] using the original sourcemap as the base * Creates a new SourceMapGenerator based on a SourceMapConsumer: https://github.com/mozilla/source-map#sourcemapgeneratorfromsourcemapsourcemapconsumer --- lib/sourcemap.js | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/lib/sourcemap.js b/lib/sourcemap.js index 948e3b39..3998e405 100644 --- a/lib/sourcemap.js +++ b/lib/sourcemap.js @@ -53,11 +53,16 @@ function SourceMap(options) { orig_line_diff : 0, dest_line_diff : 0, }); - var generator = new MOZ_SourceMap.SourceMapGenerator({ - file : options.file, - sourceRoot : options.root - }); var orig_map = options.orig && new MOZ_SourceMap.SourceMapConsumer(options.orig); + var generator; + if (orig_map) { + generator = MOZ_SourceMap.SourceMapGenerator.fromSourceMap(orig_map); + } else { + generator = new MOZ_SourceMap.SourceMapGenerator({ + file : options.file, + sourceRoot : options.root + }); + } function add(source, gen_line, gen_col, orig_line, orig_col, name) { if (orig_map) { var info = orig_map.originalPositionFor({ @@ -78,7 +83,7 @@ function SourceMap(options) { source : source, name : name }); - }; + } return { add : add, get : function() { return generator }, From e37b67d013c4537a36bb3c24f4f99e72efbf6d4b Mon Sep 17 00:00:00 2001 From: Richard van Velzen Date: Sun, 4 Jan 2015 20:53:19 +0100 Subject: [PATCH 19/30] Add an option to prevent function names from being mangled See #552. This is mostly useful for having the actual function names in traces. --- lib/scope.js | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/lib/scope.js b/lib/scope.js index d252d535..73442a30 100644 --- a/lib/scope.js +++ b/lib/scope.js @@ -57,9 +57,14 @@ function SymbolDef(scope, index, orig) { SymbolDef.prototype = { unmangleable: function(options) { - return (this.global && !(options && options.toplevel)) + if (!options) options = {}; + + return (this.global && !options.toplevel) || this.undeclared - || (!(options && options.eval) && (this.scope.uses_eval || this.scope.uses_with)); + || (!options.eval && (this.scope.uses_eval || this.scope.uses_with)) + || (options.keep_fnames + && (this.orig[0] instanceof AST_SymbolLambda + || this.orig[0] instanceof AST_SymbolDefun)); }, mangle: function(options) { if (!this.mangled_name && !this.unmangleable(options)) { @@ -322,11 +327,12 @@ AST_Symbol.DEFMETHOD("global", function(){ AST_Toplevel.DEFMETHOD("_default_mangler_options", function(options){ return defaults(options, { - except : [], - eval : false, - sort : false, - toplevel : false, - screw_ie8 : false + except : [], + eval : false, + sort : false, + toplevel : false, + screw_ie8 : false, + keep_fnames : false }); }); From e1f0747e4cca7b77a0189dacbff9fa4800a2835c Mon Sep 17 00:00:00 2001 From: Mihai Bazon Date: Mon, 5 Jan 2015 11:03:13 +0200 Subject: [PATCH 20/30] Support keep_fnames in compressor, and --keep-fnames. #552 Passing `--keep-fnames` will enable it both for compressor/mangler, so that function names will not be dropped (when unused) nor mangled. --- bin/uglifyjs | 6 ++++++ lib/compress.js | 3 ++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/bin/uglifyjs b/bin/uglifyjs index a177cb6f..44afd2f4 100755 --- a/bin/uglifyjs +++ b/bin/uglifyjs @@ -65,6 +65,7 @@ You need to pass an argument to this option to specify the name that your module .describe("V", "Print version number and exit.") .describe("noerr", "Don't throw an error for unknown options in -c, -b or -m.") .describe("bare-returns", "Allow return outside of functions. Useful when minifying CommonJS modules.") + .describe("keep-fnames", "Do not mangle/drop function names. Useful for code relying on Function.prototype.name.") .alias("p", "prefix") .alias("o", "output") @@ -160,6 +161,11 @@ if (ARGS.screw_ie8) { OUTPUT_OPTIONS.screw_ie8 = true; } +if (ARGS.keep_fnames) { + if (COMPRESS) COMPRESS.keep_fnames = true; + if (MANGLE) MANGLE.keep_fnames = true; +} + if (BEAUTIFY) UglifyJS.merge(OUTPUT_OPTIONS, BEAUTIFY); diff --git a/lib/compress.js b/lib/compress.js index 2c950992..345d8ad6 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -62,6 +62,7 @@ function Compressor(options, false_by_default) { unused : !false_by_default, hoist_funs : !false_by_default, keep_fargs : false, + keep_fnames : false, hoist_vars : false, if_return : !false_by_default, join_vars : !false_by_default, @@ -1666,7 +1667,7 @@ merge(Compressor.prototype, { OPT(AST_Function, function(self, compressor){ self = AST_Lambda.prototype.optimize.call(self, compressor); - if (compressor.option("unused")) { + if (compressor.option("unused") && !compressor.option("keep_fnames")) { if (self.name && self.name.unreferenced()) { self.name = null; } From 93a6e5780e51c44d79778636f2794a63267a8c6f Mon Sep 17 00:00:00 2001 From: Mihai Bazon Date: Mon, 5 Jan 2015 11:20:00 +0200 Subject: [PATCH 21/30] Declare boolean type for --keep-fnames --- bin/uglifyjs | 1 + 1 file changed, 1 insertion(+) diff --git a/bin/uglifyjs b/bin/uglifyjs index 44afd2f4..11c3a01a 100755 --- a/bin/uglifyjs +++ b/bin/uglifyjs @@ -103,6 +103,7 @@ You need to pass an argument to this option to specify the name that your module .boolean("V") .boolean("noerr") .boolean("bare-returns") + .boolean("keep-fnames") .wrap(80) From 13219cebcb11f95fdaa77af653e96ac42e7e6dcc Mon Sep 17 00:00:00 2001 From: Mihai Bazon Date: Mon, 5 Jan 2015 12:14:42 +0200 Subject: [PATCH 22/30] Fix handling \r\n Close #437 --- lib/parse.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/parse.js b/lib/parse.js index 931e5f66..c3f0822c 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -206,7 +206,7 @@ var EX_EOF = {}; function tokenizer($TEXT, filename, html5_comments) { var S = { - text : $TEXT.replace(/\r\n?|[\n\u2028\u2029]/g, "\n").replace(/\uFEFF/g, ''), + text : $TEXT.replace(/\uFEFF/g, ''), filename : filename, pos : 0, tokpos : 0, @@ -225,10 +225,15 @@ function tokenizer($TEXT, filename, html5_comments) { var ch = S.text.charAt(S.pos++); if (signal_eof && !ch) throw EX_EOF; - if (ch == "\n") { + if ("\r\n\u2028\u2029".indexOf(ch) >= 0) { S.newline_before = S.newline_before || !in_string; ++S.line; S.col = 0; + if (!in_string && ch == "\r" && peek() == "\n") { + // treat a \r\n sequence as a single \n + ++S.pos; + ch = "\n"; + } } else { ++S.col; } From 7f9bc9e863addac4bd17c09beb94a01faaea0aad Mon Sep 17 00:00:00 2001 From: Richard van Velzen Date: Mon, 5 Jan 2015 19:10:32 +0100 Subject: [PATCH 23/30] Pass mangle options to `figure_out_scope` and `compute_char_frequence` Fix #219. Because the options were not set and `toplevel` is `false` by default, some toplevel names would sometimes not be mangled correctly. --- tools/node.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/node.js b/tools/node.js index 9e9c2c14..4bc8517b 100644 --- a/tools/node.js +++ b/tools/node.js @@ -97,8 +97,8 @@ exports.minify = function(files, options) { // 3. mangle if (options.mangle) { - toplevel.figure_out_scope(); - toplevel.compute_char_frequency(); + toplevel.figure_out_scope(options.mangle); + toplevel.compute_char_frequency(options.mangle); toplevel.mangle_names(options.mangle); } From 6b23cbc8522bdc6d28e1abb44eb2d1d6eb6b697a Mon Sep 17 00:00:00 2001 From: Mihai Bazon Date: Tue, 6 Jan 2015 12:27:23 +0200 Subject: [PATCH 24/30] AST_Do nodes: walk body before condition --- lib/ast.js | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/lib/ast.js b/lib/ast.js index 051cd2fb..2eb8cc86 100644 --- a/lib/ast.js +++ b/lib/ast.js @@ -205,21 +205,27 @@ var AST_DWLoop = DEFNODE("DWLoop", "condition", { $documentation: "Base class for do/while statements", $propdoc: { condition: "[AST_Node] the loop condition. Should not be instanceof AST_Statement" - }, + } +}, AST_IterationStatement); + +var AST_Do = DEFNODE("Do", null, { + $documentation: "A `do` statement", + _walk: function(visitor) { + return visitor._visit(this, function(){ + this.body._walk(visitor); + this.condition._walk(visitor); + }); + } +}, AST_DWLoop); + +var AST_While = DEFNODE("While", null, { + $documentation: "A `while` statement", _walk: function(visitor) { return visitor._visit(this, function(){ this.condition._walk(visitor); this.body._walk(visitor); }); } -}, AST_IterationStatement); - -var AST_Do = DEFNODE("Do", null, { - $documentation: "A `do` statement", -}, AST_DWLoop); - -var AST_While = DEFNODE("While", null, { - $documentation: "A `while` statement", }, AST_DWLoop); var AST_For = DEFNODE("For", "init condition step", { From ae5366a31de7d65964400ffc7b2f05132e2538b6 Mon Sep 17 00:00:00 2001 From: Ingvar Stepanyan Date: Fri, 8 Aug 2014 14:15:43 +0300 Subject: [PATCH 25/30] Track ending lines/columns; fix end locations in Mozilla AST. --- lib/ast.js | 2 +- lib/mozilla-ast.js | 37 ++++++++++++++++--------------------- lib/parse.js | 19 +++++++++++-------- 3 files changed, 28 insertions(+), 30 deletions(-) diff --git a/lib/ast.js b/lib/ast.js index 2eb8cc86..5aa1be30 100644 --- a/lib/ast.js +++ b/lib/ast.js @@ -84,7 +84,7 @@ function DEFNODE(type, props, methods, base) { return ctor; }; -var AST_Token = DEFNODE("Token", "type value line col pos endpos nlb comments_before file", { +var AST_Token = DEFNODE("Token", "type value line col pos endline endcol endpos nlb comments_before file", { }, null); var AST_Node = DEFNODE("Node", "start end", { diff --git a/lib/mozilla-ast.js b/lib/mozilla-ast.js index 602ef0e6..5056ffed 100644 --- a/lib/mozilla-ast.js +++ b/lib/mozilla-ast.js @@ -370,26 +370,28 @@ /* -----[ tools ]----- */ function my_start_token(moznode) { - var loc = moznode.loc; + var loc = moznode.loc, start = loc && loc.start; var range = moznode.range; return new AST_Token({ file : loc && loc.source, - line : loc && loc.start.line, - col : loc && loc.start.column, + line : start && start.line, + col : start && start.column, pos : range ? range[0] : moznode.start, endpos : range ? range[0] : moznode.start }); }; function my_end_token(moznode) { - var loc = moznode.loc; + var loc = moznode.loc, end = loc && loc.end; var range = moznode.range; return new AST_Token({ - file : loc && loc.source, - line : loc && loc.end.line, - col : loc && loc.end.column, - pos : range ? range[1] : moznode.end, - endpos : range ? range[1] : moznode.end + file : loc && loc.source, + line : end && end.line, + col : end && end.column, + pos : range ? range[1] : moznode.end, + endline : end && end.line, + endcol : end && end.column, + endpos : range ? range[1] : moznode.end }); }; @@ -465,23 +467,16 @@ return ast; }; - function moz_sub_loc(token) { - return token.line ? { - line: token.line, - column: token.col - } : null; - }; - - function set_moz_loc(mynode, moznode) { + function set_moz_loc(mynode, moznode, myparent) { var start = mynode.start; var end = mynode.end; - if (start.pos != null && end.pos != null) { - moznode.range = [start.pos, end.pos]; + if (start.pos != null && end.endpos != null) { + moznode.range = [start.pos, end.endpos]; } if (start.line) { moznode.loc = { - start: moz_sub_loc(start), - end: moz_sub_loc(end) + start: {line: start.line, column: start.col}, + end: end.endline ? {line: end.endline, column: end.endcol} : null }; if (start.file) { moznode.loc.source = start.file; diff --git a/lib/parse.js b/lib/parse.js index c3f0822c..f463526f 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -267,14 +267,16 @@ function tokenizer($TEXT, filename, html5_comments) { (type == "punc" && PUNC_BEFORE_EXPRESSION(value))); prev_was_dot = (type == "punc" && value == "."); var ret = { - type : type, - value : value, - line : S.tokline, - col : S.tokcol, - pos : S.tokpos, - endpos : S.pos, - nlb : S.newline_before, - file : filename + type : type, + value : value, + line : S.tokline, + col : S.tokcol, + pos : S.tokpos, + endline : S.line, + endcol : S.col, + endpos : S.pos, + nlb : S.newline_before, + file : filename }; if (!is_comment) { ret.comments_before = S.comments_before; @@ -397,6 +399,7 @@ function tokenizer($TEXT, filename, html5_comments) { ret = S.text.substring(S.pos, i); S.pos = i; } + S.col = S.tokcol + (S.pos - S.tokpos); S.comments_before.push(token(type, ret, true)); S.regex_allowed = regex_allowed; return next_token(); From f16033aafdd7f88da6b53c58f86b8effd1fb2a3d Mon Sep 17 00:00:00 2001 From: Ingvar Stepanyan Date: Fri, 8 Aug 2014 14:54:34 +0300 Subject: [PATCH 26/30] Location fix for Mozilla AST start token. --- lib/mozilla-ast.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/lib/mozilla-ast.js b/lib/mozilla-ast.js index 5056ffed..1deb18e2 100644 --- a/lib/mozilla-ast.js +++ b/lib/mozilla-ast.js @@ -373,11 +373,13 @@ var loc = moznode.loc, start = loc && loc.start; var range = moznode.range; return new AST_Token({ - file : loc && loc.source, - line : start && start.line, - col : start && start.column, - pos : range ? range[0] : moznode.start, - endpos : range ? range[0] : moznode.start + file : loc && loc.source, + line : start && start.line, + col : start && start.column, + pos : range ? range[0] : moznode.start, + endline : start && start.line, + endcol : start && start.column, + endpos : range ? range[0] : moznode.start }); }; From d2d716483aa69927541a0a29f3c1ee3c7bba30b4 Mon Sep 17 00:00:00 2001 From: Mihai Bazon Date: Tue, 6 Jan 2015 13:57:18 +0200 Subject: [PATCH 27/30] aborts(AST_If) returns the `if` node Previously it returned the abort node from the alternative branch. This is not much use as it can be different from the one in the body branch (i.e. return vs. throw) and can trick us into issues like #591. Fix #591 --- lib/compress.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/compress.js b/lib/compress.js index 345d8ad6..83c3e6d0 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -950,7 +950,7 @@ merge(Compressor.prototype, { def(AST_BlockStatement, block_aborts); def(AST_SwitchBranch, block_aborts); def(AST_If, function(){ - return this.alternative && aborts(this.body) && aborts(this.alternative); + return this.alternative && aborts(this.body) && aborts(this.alternative) && this; }); })(function(node, func){ node.DEFMETHOD("aborts", func); From 61c233a08ed3768f19f05579888b327eee7286ec Mon Sep 17 00:00:00 2001 From: Mihai Bazon Date: Wed, 7 Jan 2015 11:20:04 +0200 Subject: [PATCH 28/30] Fix make_node_from_constant for Regexp-s Close #588 --- lib/compress.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/compress.js b/lib/compress.js index 83c3e6d0..d503ed17 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -163,10 +163,10 @@ merge(Compressor.prototype, { return make_node(AST_Undefined, orig).optimize(compressor); default: if (val === null) { - return make_node(AST_Null, orig).optimize(compressor); + return make_node(AST_Null, orig, { value: null }).optimize(compressor); } if (val instanceof RegExp) { - return make_node(AST_RegExp, orig).optimize(compressor); + return make_node(AST_RegExp, orig, { value: val }).optimize(compressor); } throw new Error(string_template("Can't handle constant of type: {type}", { type: typeof val From 285bffd2c6597420c4d3e937fea731a9041b385f Mon Sep 17 00:00:00 2001 From: Richard van Velzen Date: Wed, 7 Jan 2015 19:04:10 +0100 Subject: [PATCH 29/30] Document `--` for usage in CLI class Close #518 --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index a04ac4e4..d2ec432f 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,11 @@ variable/function declared in another file will be matched properly. If you want to read from STDIN instead, pass a single dash instead of input files. +If you wish to pass your options before the input files, separate the two with +a double dash to prevent input files being used as option arguments: + + uglifyjs --compress --mangle -- input.js + The available options are: ``` From 42ecd42ac00881f7d540cb08969ce7e2721e30d5 Mon Sep 17 00:00:00 2001 From: Richard van Velzen Date: Mon, 12 Jan 2015 17:09:34 +0100 Subject: [PATCH 30/30] Replace the correct node when replacing in `void` sequences Close #611. --- lib/compress.js | 2 +- test/compress/issue-611.js | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 test/compress/issue-611.js diff --git a/lib/compress.js b/lib/compress.js index 1f1d4b50..72e4d92d 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -1937,7 +1937,7 @@ merge(Compressor.prototype, { if (self.cdr instanceof AST_UnaryPrefix && self.cdr.operator == "void" && !self.cdr.expression.has_side_effects(compressor)) { - self.cdr.operator = self.car; + self.cdr.expression = self.car; return self.cdr; } if (self.cdr instanceof AST_Undefined) { diff --git a/test/compress/issue-611.js b/test/compress/issue-611.js new file mode 100644 index 00000000..6f2c65d2 --- /dev/null +++ b/test/compress/issue-611.js @@ -0,0 +1,21 @@ +issue_611: { + options = { + sequences: true, + side_effects: true + }; + input: { + define(function() { + function fn() {} + if (fn()) { + fn(); + return void 0; + } + }); + } + expect: { + define(function() { + function fn(){} + if (fn()) return void fn(); + }); + } +}