From 9794ebf88ce877c83ef522642bbd75f72bb80ac7 Mon Sep 17 00:00:00 2001 From: Mihai Bazon Date: Mon, 29 Apr 2013 15:03:52 +0300 Subject: [PATCH 001/165] Workaround for missing `prefix` in UnaryExpression generated by Esprima See #193 --- lib/mozilla-ast.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/mozilla-ast.js b/lib/mozilla-ast.js index 982d621a..d7950942 100644 --- a/lib/mozilla-ast.js +++ b/lib/mozilla-ast.js @@ -148,12 +148,14 @@ }; function From_Moz_Unary(M) { - return new (M.prefix ? AST_UnaryPrefix : AST_UnaryPostfix)({ + var prefix = "prefix" in M ? M.prefix + : M.type == "UnaryExpression" ? true : false; + return new (prefix ? AST_UnaryPrefix : AST_UnaryPostfix)({ start : my_start_token(M), end : my_end_token(M), operator : M.operator, expression : from_moz(M.argument) - }) + }); }; var ME_TO_MOZ = {}; From 5c22a1bdf5e66df68593b872dc7e226a9688b762 Mon Sep 17 00:00:00 2001 From: Mihai Bazon Date: Wed, 1 May 2013 13:14:07 +0300 Subject: [PATCH 002/165] v2.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 48e36cb3..7cb25efd 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.2.5", + "version": "2.3", "engines": { "node" : ">=0.4.0" }, "maintainers": [{ "name": "Mihai Bazon", From 8e6266136dd7e7c14b53219bf8c3f08d58778e7e Mon Sep 17 00:00:00 2001 From: Mihai Bazon Date: Wed, 1 May 2013 13:15:34 +0300 Subject: [PATCH 003/165] Take two. v2.3.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7cb25efd..e30b268f 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.3", + "version": "2.3.0", "engines": { "node" : ">=0.4.0" }, "maintainers": [{ "name": "Mihai Bazon", From a54b6703c095880eb048574e2a1d9dbb21a0b680 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uli=20K=C3=B6hler?= Date: Wed, 1 May 2013 15:56:20 +0200 Subject: [PATCH 004/165] Add README syntax highlighting --- README.md | 174 +++++++++++++++++++++++++++++------------------------- 1 file changed, 95 insertions(+), 79 deletions(-) diff --git a/README.md b/README.md index ce55dc30..38653ef3 100644 --- a/README.md +++ b/README.md @@ -222,10 +222,11 @@ You can use the `--define` (`-d`) switch in order to declare global variables that UglifyJS will assume to be constants (unless defined in scope). For example if you pass `--define DEBUG=false` then, coupled with dead code removal UglifyJS will discard the following from the output: - - if (DEBUG) { - console.log("debug stuff"); - } +```javascript +if (DEBUG) { + console.log("debug stuff"); +} +``` UglifyJS will warn about the condition being always false and about dropping unreachable code; for now there is no option to turn off only this specific @@ -234,10 +235,11 @@ warning, you can pass `warnings=false` to turn off *all* warnings. Another way of doing that is to declare your globals as constants in a separate file and include it into the build. For example you can have a `build/defines.js` file with the following: - - const DEBUG = false; - const PRODUCTION = true; - // etc. +```javascript +const DEBUG = false; +const PRODUCTION = true; +// etc. +``` and build your code like this: @@ -296,14 +298,15 @@ keep only comments that match this regexp. For example `--comments Note, however, that there might be situations where comments are lost. For example: - - function f() { - /** @preserve Foo Bar */ - function g() { - // this function is never called - } - return something(); - } +```javascript +function f() { + /** @preserve Foo Bar */ + function g() { + // this function is never called + } + return something(); +} +``` Even though it has "@preserve", the comment will be lost because the inner function `g` (which is the AST node to which the comment is attached to) is @@ -345,8 +348,9 @@ API Reference Assuming installation via NPM, you can load UglifyJS in your application like this: - - var UglifyJS = require("uglify-js"); +```javascript +var UglifyJS = require("uglify-js"); +``` It exports a lot of names, but I'll discuss here the basics that are needed for parsing, mangling and compressing a piece of code. The sequence is (1) @@ -357,45 +361,49 @@ parse, (2) compress, (3) mangle, (4) generate output code. There's a single toplevel function which combines all the steps. If you don't need additional customization, you might want to go with `minify`. Example: - - var result = UglifyJS.minify("/path/to/file.js"); - console.log(result.code); // minified output - // if you need to pass code instead of file name - var result = UglifyJS.minify("var b = function () {};", {fromString: true}); +```javascript +var result = UglifyJS.minify("/path/to/file.js"); +console.log(result.code); // minified output +// if you need to pass code instead of file name +var result = UglifyJS.minify("var b = function () {};", {fromString: true}); +``` You can also compress multiple files: - - var result = UglifyJS.minify([ "file1.js", "file2.js", "file3.js" ]); - console.log(result.code); +```javascript +var result = UglifyJS.minify([ "file1.js", "file2.js", "file3.js" ]); +console.log(result.code); +``` To generate a source map: - - var result = UglifyJS.minify([ "file1.js", "file2.js", "file3.js" ], { - outSourceMap: "out.js.map" - }); - console.log(result.code); // minified output - console.log(result.map); +```javascript +var result = UglifyJS.minify([ "file1.js", "file2.js", "file3.js" ], { + outSourceMap: "out.js.map" +}); +console.log(result.code); // minified output +console.log(result.map); +``` Note that the source map is not saved in a file, it's just returned in `result.map`. The value passed for `outSourceMap` is only used to set the `file` attribute in the source map (see [the spec][sm-spec]). You can also specify sourceRoot property to be included in source map: - - var result = UglifyJS.minify([ "file1.js", "file2.js", "file3.js" ], { - outSourceMap: "out.js.map", - sourceRoot: "http://example.com/src" - }); - +```javascript +var result = UglifyJS.minify([ "file1.js", "file2.js", "file3.js" ], { + outSourceMap: "out.js.map", + sourceRoot: "http://example.com/src" +}); +``` If you're compressing compiled JavaScript and have a source map for it, you can use the `inSourceMap` argument: - - var result = UglifyJS.minify("compiled.js", { - inSourceMap: "compiled.js.map", - outSourceMap: "minified.js.map" - }); - // same as before, it returns `code` and `map` +```javascript +var result = UglifyJS.minify("compiled.js", { + inSourceMap: "compiled.js.map", + outSourceMap: "minified.js.map" +}); +// same as before, it returns `code` and `map` +``` The `inSourceMap` is only used if you also request `outSourceMap` (it makes no sense otherwise). @@ -425,8 +433,9 @@ Following there's more detailed API info, in case the `minify` function is too simple for your needs. #### The parser - - var toplevel_ast = UglifyJS.parse(code, options); +```javascript +var toplevel_ast = UglifyJS.parse(code, options); +``` `options` is optional and if present it must be an object. The following properties are available: @@ -440,15 +449,16 @@ properties are available: The last two options are useful when you'd like to minify multiple files and get a single file as the output and a proper source map. Our CLI tool does something like this: - - var toplevel = null; - files.forEach(function(file){ - var code = fs.readFileSync(file); - toplevel = UglifyJS.parse(code, { - filename: file, - toplevel: toplevel - }); - }); +```javascript +var toplevel = null; +files.forEach(function(file){ + var code = fs.readFileSync(file); + toplevel = UglifyJS.parse(code, { + filename: file, + toplevel: toplevel + }); +}); +``` After this, we have in `toplevel` a big AST containing all our files, with each token having proper information about where it came from. @@ -462,15 +472,17 @@ referenced, if it is a global or not, if a function is using `eval` or the `with` statement etc. I will discuss this some place else, for now what's important to know is that you need to call the following before doing anything with the tree: - - toplevel.figure_out_scope() +```javascript +toplevel.figure_out_scope() +``` #### Compression Like this: - - var compressor = UglifyJS.Compressor(options); - var compressed_ast = toplevel.transform(compressor); +```javascript +var compressor = UglifyJS.Compressor(options); +var compressed_ast = toplevel.transform(compressor); +``` The `options` can be missing. Available options are discussed above in “Compressor options”. Defaults should lead to best compression in most @@ -486,23 +498,26 @@ the compressor might drop unused variables / unreachable code and this might change the number of identifiers or their position). Optionally, you can call a trick that helps after Gzip (counting character frequency in non-mangleable words). Example: - - compressed_ast.figure_out_scope(); - compressed_ast.compute_char_frequency(); - compressed_ast.mangle_names(); +```javascript +compressed_ast.figure_out_scope(); +compressed_ast.compute_char_frequency(); +compressed_ast.mangle_names(); +``` #### Generating output AST nodes have a `print` method that takes an output stream. Essentially, to generate code you do this: - - var stream = UglifyJS.OutputStream(options); - compressed_ast.print(stream); - var code = stream.toString(); // this is your minified code +```javascript +var stream = UglifyJS.OutputStream(options); +compressed_ast.print(stream); +var code = stream.toString(); // this is your minified code +``` or, for a shortcut you can do: - - var code = compressed_ast.print_to_string(options); +```javascript +var code = compressed_ast.print_to_string(options); +``` As usual, `options` is optional. The output stream accepts a lot of otions, most of them documented above in section “Beautifier options”. The two @@ -540,16 +555,17 @@ to be a `SourceMap` object (which is a thin wrapper on top of the [source-map][source-map] library). Example: +```javascript +var source_map = UglifyJS.SourceMap(source_map_options); +var stream = UglifyJS.OutputStream({ + ... + source_map: source_map +}); +compressed_ast.print(stream); - var source_map = UglifyJS.SourceMap(source_map_options); - var stream = UglifyJS.OutputStream({ - ... - source_map: source_map - }); - compressed_ast.print(stream); - - var code = stream.toString(); - var map = source_map.toString(); // json output for your source map +var code = stream.toString(); +var map = source_map.toString(); // json output for your source map +``` The `source_map_options` (optional) can contain the following properties: From 790b3bcdc66061b63de0e2538a49c6f3edbdfc0e Mon Sep 17 00:00:00 2001 From: Kim Joar Bekkelund Date: Thu, 2 May 2013 11:15:33 +0200 Subject: [PATCH 005/165] Fix typo in bin and readme --- README.md | 2 +- bin/uglifyjs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 38653ef3..4640977f 100644 --- a/README.md +++ b/README.md @@ -80,7 +80,7 @@ The available options are: cascading statements into sequences. [string] --stats Display operations run time on STDERR. [boolean] --acorn Use Acorn for parsing. [boolean] - --spidermonkey Assume input fles are SpiderMonkey AST format (as JSON). + --spidermonkey Assume input files are SpiderMonkey AST format (as JSON). [boolean] --self Build itself (UglifyJS2) as a library (implies --wrap=UglifyJS --export-all) [boolean] diff --git a/bin/uglifyjs b/bin/uglifyjs index ee32fdf1..fab9c665 100755 --- a/bin/uglifyjs +++ b/bin/uglifyjs @@ -46,7 +46,7 @@ because of dead code removal or cascading statements into sequences.") .describe("stats", "Display operations run time on STDERR.") .describe("acorn", "Use Acorn for parsing.") - .describe("spidermonkey", "Assume input fles are SpiderMonkey AST format (as JSON).") + .describe("spidermonkey", "Assume input files are SpiderMonkey AST format (as JSON).") .describe("self", "Build itself (UglifyJS2) as a library (implies --wrap=UglifyJS --export-all)") .describe("wrap", "Embed everything in a big function, making the “exports” and “global” variables available. \ You need to pass an argument to this option to specify the name that your module will take when included in, say, a browser.") From 8227e8795b309b46e0f2ef998d7238198eb07425 Mon Sep 17 00:00:00 2001 From: Justin Lau Date: Sun, 5 May 2013 22:08:13 +0800 Subject: [PATCH 006/165] Added scenario in test case where properties shouldn't be accessed with dotted syntax even with screw_ie8 option. Signed-off-by: Justin Lau --- test/compress/properties.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/compress/properties.js b/test/compress/properties.js index 9b066ec9..118bc4c2 100644 --- a/test/compress/properties.js +++ b/test/compress/properties.js @@ -17,10 +17,12 @@ dot_properties: { input: { a["foo"] = "bar"; a["if"] = "if"; + a["*"] = "asterisk"; } expect: { a.foo = "bar"; a["if"] = "if"; + a["*"] = "asterisk"; } } @@ -32,9 +34,11 @@ dot_properties_es5: { input: { a["foo"] = "bar"; a["if"] = "if"; + a["*"] = "asterisk"; } expect: { a.foo = "bar"; a.if = "if"; + a["*"] = "asterisk"; } } From 1e3bc0caa0a8ad8c3bcab07d539ee153ea8e96b3 Mon Sep 17 00:00:00 2001 From: Justin Lau Date: Sun, 5 May 2013 22:27:43 +0800 Subject: [PATCH 007/165] Fixed dot property issue with invlid identifier names. Signed-off-by: Justin Lau --- lib/compress.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/compress.js b/lib/compress.js index ebd3dd7a..623fe45e 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -1962,7 +1962,8 @@ merge(Compressor.prototype, { var prop = self.property; if (prop instanceof AST_String && compressor.option("properties")) { prop = prop.getValue(); - if (is_identifier(prop) || compressor.option("screw_ie8")) { + if (is_identifier(prop) + || (compressor.option("screw_ie8") && /^[a-z_$][a-z0-9_$]*$/i.test(prop))) { return make_node(AST_Dot, self, { expression : self.expression, property : prop From fcd544cc106bbd04ca8003046fa76154cdb4046e Mon Sep 17 00:00:00 2001 From: Justin Lau Date: Mon, 6 May 2013 01:26:33 +0800 Subject: [PATCH 008/165] Added test scenario with unicode in properties name. Signed-off-by: Justin Lau --- test/compress/properties.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/compress/properties.js b/test/compress/properties.js index 118bc4c2..d52680c2 100644 --- a/test/compress/properties.js +++ b/test/compress/properties.js @@ -18,11 +18,13 @@ dot_properties: { a["foo"] = "bar"; a["if"] = "if"; a["*"] = "asterisk"; + a["\u0EB3"] = "unicode"; } expect: { a.foo = "bar"; a["if"] = "if"; a["*"] = "asterisk"; + a.\u0EB3 = "unicode"; } } @@ -35,10 +37,12 @@ dot_properties_es5: { a["foo"] = "bar"; a["if"] = "if"; a["*"] = "asterisk"; + a["\u0EB3"] = "unicode"; } expect: { a.foo = "bar"; a.if = "if"; a["*"] = "asterisk"; + a.\u0EB3 = "unicode"; } } From 9af2bbffde2653237b36a06b005209be9f6cc1e6 Mon Sep 17 00:00:00 2001 From: Justin Lau Date: Mon, 6 May 2013 02:45:41 +0800 Subject: [PATCH 009/165] Fixed dot properties not optimizing unicode identifiers. Signed-off-by: Justin Lau --- lib/compress.js | 4 ++-- lib/parse.js | 8 ++++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/lib/compress.js b/lib/compress.js index 623fe45e..992d78f8 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -1962,8 +1962,8 @@ merge(Compressor.prototype, { var prop = self.property; if (prop instanceof AST_String && compressor.option("properties")) { prop = prop.getValue(); - if (is_identifier(prop) - || (compressor.option("screw_ie8") && /^[a-z_$][a-z0-9_$]*$/i.test(prop))) { + if (compressor.option("screw_ie8") && RESERVED_WORDS(prop) + || !(RESERVED_WORDS(prop)) && is_identifier_string(prop)) { return make_node(AST_Dot, self, { expression : self.expression, property : prop diff --git a/lib/parse.js b/lib/parse.js index da39b5b1..b3687201 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -167,6 +167,14 @@ function is_identifier_char(ch) { ; }; +function is_identifier_string(str){ + for (var i = str.length; --i >= 0;) { + if (!is_identifier_char(str.charAt(i))) + return false; + } + return true; +}; + function parse_js_number(num) { if (RE_HEX_NUMBER.test(num)) { return parseInt(num.substr(2), 16); From 672699613e79c24d4e744313f5c0a51400958d8a Mon Sep 17 00:00:00 2001 From: Justin Lau Date: Sun, 5 May 2013 19:54:27 +0800 Subject: [PATCH 010/165] Added test cases for #104. Signed-off-by: Justin Lau --- test/compress/issue-143.js | 48 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 test/compress/issue-143.js diff --git a/test/compress/issue-143.js b/test/compress/issue-143.js new file mode 100644 index 00000000..4c79790b --- /dev/null +++ b/test/compress/issue-143.js @@ -0,0 +1,48 @@ +/** + * There was an incorrect sort behaviour documented in issue #143: + * (x = f(…)) <= x → x >= (x = f(…)) + * + * For example, let the equation be: + * (a = parseInt('100')) <= a + * + * If a was an integer and has the value of 99, + * (a = parseInt('100')) <= a → 100 <= 100 → true + * + * When transformed incorrectly: + * a >= (a = parseInt('100')) → 99 >= 100 → false + */ + +tranformation_sort_order_equal: { + options = { + comparisons: true, + }; + + input: { (a = parseInt('100')) == a } + expect: { (a = parseInt('100')) == a } +} + +tranformation_sort_order_unequal: { + options = { + comparisons: true, + }; + + input: { (a = parseInt('100')) != a } + expect: { (a = parseInt('100')) != a } +} + +tranformation_sort_order_lesser_or_equal: { + options = { + comparisons: true, + }; + + input: { (a = parseInt('100')) <= a } + expect: { (a = parseInt('100')) <= a } +} +tranformation_sort_order_greater_or_equal: { + options = { + comparisons: true, + }; + + input: { (a = parseInt('100')) >= a } + expect: { (a = parseInt('100')) >= a } +} \ No newline at end of file From a1958aad563030b76bf4d9af7bf80a160c625e27 Mon Sep 17 00:00:00 2001 From: Justin Lau Date: Sun, 5 May 2013 20:38:32 +0800 Subject: [PATCH 011/165] Fixed typeof undefined optimization and updated related test case to accomodates the sort behaviour changes made in commit mishoo/UglifyJS2@aebafad41eab48f43ed649ce8c77e8f1528b50da. Signed-off-by: Justin Lau --- lib/compress.js | 16 ++++++++-------- test/compress/issue-105.js | 5 ++--- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/lib/compress.js b/lib/compress.js index 992d78f8..c994a3ab 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -1751,14 +1751,14 @@ merge(Compressor.prototype, { // XXX: intentionally falling down to the next case case "==": case "!=": - if (self.left instanceof AST_String - && self.left.value == "undefined" - && self.right instanceof AST_UnaryPrefix - && self.right.operator == "typeof" - && compressor.option("unsafe")) { - if (!(self.right.expression instanceof AST_SymbolRef) - || !self.right.expression.undeclared()) { - self.left = self.right.expression; + if (compressor.option("unsafe") + && self.left instanceof AST_UnaryPrefix + && self.left.operator == "typeof" + && self.right instanceof AST_String + && self.right.value == "undefined") { + if (!(self.left.expression instanceof AST_SymbolRef) + || !self.left.expression.undeclared()) { + self.left = self.left.expression; self.right = make_node(AST_Undefined, self.left).optimize(compressor); if (self.operator.length == 2) self.operator += "="; } diff --git a/test/compress/issue-105.js b/test/compress/issue-105.js index 349d732d..0c37eb82 100644 --- a/test/compress/issue-105.js +++ b/test/compress/issue-105.js @@ -1,10 +1,9 @@ typeof_eq_undefined: { options = { - comparisons: true, - unsafe: false + comparisons: true }; input: { a = typeof b.c != "undefined" } - expect: { a = "undefined" != typeof b.c } + expect: { a = typeof b.c != "undefined" } } typeof_eq_undefined_unsafe: { From a6ed2c84ac65f68dc6cf102dffb44f3b00fd0277 Mon Sep 17 00:00:00 2001 From: Mihai Bazon Date: Wed, 8 May 2013 16:22:39 +0300 Subject: [PATCH 012/165] Better fix for equality of typeof ... against "undefined" --- lib/compress.js | 27 +++++++++++++++------------ test/compress/issue-105.js | 13 +++++++++++-- 2 files changed, 26 insertions(+), 14 deletions(-) diff --git a/lib/compress.js b/lib/compress.js index c994a3ab..c94af7d0 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -1726,8 +1726,8 @@ merge(Compressor.prototype, { var commutativeOperators = makePredicate("== === != !== * & | ^"); OPT(AST_Binary, function(self, compressor){ - function reverse(op) { - if (!(self.left.has_side_effects() || self.right.has_side_effects())) { + function reverse(op, force) { + if (force || !(self.left.has_side_effects() || self.right.has_side_effects())) { if (op) self.operator = op; var tmp = self.left; self.left = self.right; @@ -1737,7 +1737,10 @@ merge(Compressor.prototype, { if (commutativeOperators(self.operator)) { if (self.right instanceof AST_Constant && !(self.left instanceof AST_Constant)) { - reverse(); + // if right is a constant, whatever side effects the + // left side might have could not influence the + // result. hence, force switch. + reverse(null, true); } } self = self.lift_sequences(compressor); @@ -1751,15 +1754,15 @@ merge(Compressor.prototype, { // XXX: intentionally falling down to the next case case "==": case "!=": - if (compressor.option("unsafe") - && self.left instanceof AST_UnaryPrefix - && self.left.operator == "typeof" - && self.right instanceof AST_String - && self.right.value == "undefined") { - if (!(self.left.expression instanceof AST_SymbolRef) - || !self.left.expression.undeclared()) { - self.left = self.left.expression; - self.right = make_node(AST_Undefined, self.left).optimize(compressor); + if (self.left instanceof AST_String + && self.left.value == "undefined" + && self.right instanceof AST_UnaryPrefix + && self.right.operator == "typeof" + && compressor.option("unsafe")) { + if (!(self.right.expression instanceof AST_SymbolRef) + || !self.right.expression.undeclared()) { + self.right = self.right.expression; + self.left = make_node(AST_Undefined, self.left).optimize(compressor); if (self.operator.length == 2) self.operator += "="; } } diff --git a/test/compress/issue-105.js b/test/compress/issue-105.js index 0c37eb82..ca17adbf 100644 --- a/test/compress/issue-105.js +++ b/test/compress/issue-105.js @@ -3,7 +3,7 @@ typeof_eq_undefined: { comparisons: true }; input: { a = typeof b.c != "undefined" } - expect: { a = typeof b.c != "undefined" } + expect: { a = "undefined" != typeof b.c } } typeof_eq_undefined_unsafe: { @@ -12,5 +12,14 @@ typeof_eq_undefined_unsafe: { unsafe: true }; input: { a = typeof b.c != "undefined" } - expect: { a = b.c !== void 0 } + expect: { a = void 0 !== b.c } +} + +typeof_eq_undefined_unsafe2: { + options = { + comparisons: true, + unsafe: true + }; + input: { a = "undefined" != typeof b.c } + expect: { a = void 0 !== b.c } } From 0f509f8336b2cacff664f29255534f49c3f8b109 Mon Sep 17 00:00:00 2001 From: Mihai Bazon Date: Wed, 8 May 2013 16:45:36 +0300 Subject: [PATCH 013/165] v2.3.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e30b268f..2407a504 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.3.0", + "version": "2.3.1", "engines": { "node" : ">=0.4.0" }, "maintainers": [{ "name": "Mihai Bazon", From 11e24d53a195685578abe76b6ef34cb80f3a5730 Mon Sep 17 00:00:00 2001 From: Mihai Bazon Date: Wed, 8 May 2013 22:37:48 +0300 Subject: [PATCH 014/165] Fix property names Close #199 --- lib/parse.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/parse.js b/lib/parse.js index b3687201..c45c064e 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -168,7 +168,9 @@ function is_identifier_char(ch) { }; function is_identifier_string(str){ - for (var i = str.length; --i >= 0;) { + var i = str.length; + if (i == 0) return false; + while (--i >= 0) { if (!is_identifier_char(str.charAt(i))) return false; } From 1e9f98aa51fa87678c6d5191903fc249df4f3e35 Mon Sep 17 00:00:00 2001 From: Trey Griffith Date: Wed, 8 May 2013 15:29:46 -0400 Subject: [PATCH 015/165] add a test for zero-length string in is_identifier_string, which is used in property compression. Also added a test exercising the change. --- test/compress/properties.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/compress/properties.js b/test/compress/properties.js index d52680c2..f490fd86 100644 --- a/test/compress/properties.js +++ b/test/compress/properties.js @@ -19,12 +19,14 @@ dot_properties: { a["if"] = "if"; a["*"] = "asterisk"; a["\u0EB3"] = "unicode"; + a[""] = "whitespace"; } expect: { a.foo = "bar"; a["if"] = "if"; a["*"] = "asterisk"; a.\u0EB3 = "unicode"; + a[""] = "whitespace"; } } @@ -38,11 +40,13 @@ dot_properties_es5: { a["if"] = "if"; a["*"] = "asterisk"; a["\u0EB3"] = "unicode"; + a[""] = "whitespace"; } expect: { a.foo = "bar"; a.if = "if"; a["*"] = "asterisk"; a.\u0EB3 = "unicode"; + a[""] = "whitespace"; } } From 4a19802d0cb49aea001f43c0dd7ea2dbe2a67443 Mon Sep 17 00:00:00 2001 From: Nick Schonning Date: Wed, 8 May 2013 23:42:06 -0400 Subject: [PATCH 016/165] Add CI build for supported Node versions --- .travis.yml | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..d9591272 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,7 @@ +language: node_js +node_js: + - "0.4" + - "0.6" + - "0.8" + - "0.10" + - "0.11" From 46814f88d93f3ed1375c80f084a524722d61cb38 Mon Sep 17 00:00:00 2001 From: Nick Schonning Date: Wed, 8 May 2013 23:48:12 -0400 Subject: [PATCH 017/165] Add Travis build badge to README --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 4640977f..88dc310e 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ UglifyJS 2 ========== +[![Build Status](https://travis-ci.org/mishoo/UglifyJS2.png)](https://travis-ci.org/mishoo/UglifyJS2) UglifyJS is a JavaScript parser, minifier, compressor or beautifier toolkit. From 064e7aa1bbb70baec40a9812a8801627846bde2f Mon Sep 17 00:00:00 2001 From: Mihai Bazon Date: Thu, 9 May 2013 08:44:24 +0300 Subject: [PATCH 018/165] Fix is_assignable (not likely to be noticed, it's only used in `strict` parse mode) --- lib/parse.js | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/lib/parse.js b/lib/parse.js index c45c064e..c8c5d0a0 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -1340,15 +1340,8 @@ function parse($TEXT, options) { function is_assignable(expr) { if (!options.strict) return true; - switch (expr[0]+"") { - case "dot": - case "sub": - case "new": - case "call": - return true; - case "name": - return expr[1] != "this"; - } + if (expr instanceof AST_This) return false; + return (expr instanceof AST_PropAccess || expr instanceof AST_Symbol); }; var maybe_assign = function(no_in) { From a9511dfbe5c0d96d8cacb87582aa9a19737bbb98 Mon Sep 17 00:00:00 2001 From: Mihai Bazon Date: Thu, 9 May 2013 08:58:47 +0300 Subject: [PATCH 019/165] Use the negation hack rather than parens for a toplevel function expression call (only in !beautify mode) --- lib/output.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/output.js b/lib/output.js index defd0215..a13c92ea 100644 --- a/lib/output.js +++ b/lib/output.js @@ -351,7 +351,9 @@ function OutputStream(options) { AST_Node.DEFMETHOD("print", function(stream, force_parens){ var self = this, generator = self._codegen; stream.push_node(self); - if (force_parens || self.needs_parens(stream)) { + var needs_parens = self.needs_parens(stream); + var fc = self instanceof AST_Function && !stream.option("beautify"); + if (force_parens || (needs_parens && !fc)) { stream.with_parens(function(){ self.add_comments(stream); self.add_source_map(stream); @@ -359,6 +361,7 @@ function OutputStream(options) { }); } else { self.add_comments(stream); + if (needs_parens && fc) stream.print("!"); self.add_source_map(stream); generator(self, stream); } From 7f77edadb36bb4d29a2b6b1203eb037bb2aa0604 Mon Sep 17 00:00:00 2001 From: Mihai Bazon Date: Thu, 9 May 2013 08:58:55 +0300 Subject: [PATCH 020/165] v2.3.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2407a504..19eac5b4 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.3.1", + "version": "2.3.2", "engines": { "node" : ">=0.4.0" }, "maintainers": [{ "name": "Mihai Bazon", From d56ebd7d7b8de0b46ce203b580df28d76fa16692 Mon Sep 17 00:00:00 2001 From: Mihai Bazon Date: Tue, 14 May 2013 10:41:28 +0300 Subject: [PATCH 021/165] Fix a["1_1"] Close #204 --- lib/compress.js | 4 ++-- lib/parse.js | 1 + test/compress/properties.js | 2 ++ 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/compress.js b/lib/compress.js index c94af7d0..24cdc026 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -1965,8 +1965,8 @@ merge(Compressor.prototype, { var prop = self.property; if (prop instanceof AST_String && compressor.option("properties")) { prop = prop.getValue(); - if (compressor.option("screw_ie8") && RESERVED_WORDS(prop) - || !(RESERVED_WORDS(prop)) && is_identifier_string(prop)) { + if ((compressor.option("screw_ie8") && RESERVED_WORDS(prop)) + || (!(RESERVED_WORDS(prop)) && is_identifier_string(prop))) { return make_node(AST_Dot, self, { expression : self.expression, property : prop diff --git a/lib/parse.js b/lib/parse.js index c8c5d0a0..a687495b 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -170,6 +170,7 @@ function is_identifier_char(ch) { function is_identifier_string(str){ var i = str.length; if (i == 0) return false; + if (is_digit(str.charCodeAt(0))) return false; while (--i >= 0) { if (!is_identifier_char(str.charAt(i))) return false; diff --git a/test/compress/properties.js b/test/compress/properties.js index f490fd86..85045961 100644 --- a/test/compress/properties.js +++ b/test/compress/properties.js @@ -20,6 +20,7 @@ dot_properties: { a["*"] = "asterisk"; a["\u0EB3"] = "unicode"; a[""] = "whitespace"; + a["1_1"] = "foo"; } expect: { a.foo = "bar"; @@ -27,6 +28,7 @@ dot_properties: { a["*"] = "asterisk"; a.\u0EB3 = "unicode"; a[""] = "whitespace"; + a["1_1"] = "foo"; } } From f64539fb7692c7fbfed0b266afea95d370453ddb Mon Sep 17 00:00:00 2001 From: Mihai Bazon Date: Tue, 14 May 2013 10:47:06 +0300 Subject: [PATCH 022/165] Compress code passed to `new Function` if it's a constant. Only for `--unsafe`. Close #203 --- lib/compress.js | 39 +++++++++++++++++++++++++++++++++++++++ lib/transform.js | 1 - 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/lib/compress.js b/lib/compress.js index 24cdc026..56fb6851 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -1589,6 +1589,45 @@ merge(Compressor.prototype, { operator: "+", right: make_node(AST_String, self, { value: "" }) }); + case "Function": + if (self.args[self.args.length - 1] instanceof AST_String) { + // quite a corner-case, but we can handle it: + // https://github.com/mishoo/UglifyJS2/issues/203 + // if the code argument is a constant, then we can minify it. + try { + var code = "(function(" + self.args.slice(0, -1).map(function(arg){ + return arg.value; + }).join(",") + "){" + self.args[self.args.length - 1].value + "})()"; + var ast = parse(code); + ast.figure_out_scope(); + var comp = new Compressor(compressor.options); + ast = ast.transform(comp); + ast.figure_out_scope(); + ast.mangle_names(); + var fun = ast.body[0].body.expression; + var args = fun.argnames.map(function(arg, i){ + return make_node(AST_String, self.args[i], { + value: arg.print_to_string() + }); + }); + var code = OutputStream(); + AST_BlockStatement.prototype._codegen.call(fun, fun, code); + code = code.toString().replace(/^\{|\}$/g, ""); + args.push(make_node(AST_String, self.args[self.args.length - 1], { + value: code + })); + self.args = args; + return self; + } catch(ex) { + if (ex instanceof JS_Parse_Error) { + compressor.warn("Error parsing code passed to new Function [{file}:{line},{col}]", self.args[self.args.length - 1].start); + compressor.warn(ex.toString()); + } else { + console.log(ex); + } + } + } + break; } } else if (exp instanceof AST_Dot && exp.property == "toString" && self.args.length == 0) { diff --git a/lib/transform.js b/lib/transform.js index 8b4fd9fd..7a61e5f3 100644 --- a/lib/transform.js +++ b/lib/transform.js @@ -44,7 +44,6 @@ "use strict"; // Tree transformer helpers. -// XXX: eventually I should refactor the compressor to use this infrastructure. function TreeTransformer(before, after) { TreeWalker.call(this); From d13aa3954d2c0c5145b32c9623a05f383a93e0c3 Mon Sep 17 00:00:00 2001 From: Mihai Bazon Date: Tue, 14 May 2013 11:33:28 +0300 Subject: [PATCH 023/165] v2.3.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 19eac5b4..18c42322 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.3.2", + "version": "2.3.3", "engines": { "node" : ">=0.4.0" }, "maintainers": [{ "name": "Mihai Bazon", From caa8896a8a9d619334c21e49964e4a6c1a1a15eb Mon Sep 17 00:00:00 2001 From: Mihai Bazon Date: Tue, 14 May 2013 18:36:31 +0300 Subject: [PATCH 024/165] Only compress code in `new Function` if all arguments are strings. --- lib/compress.js | 2 +- lib/utils.js | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/compress.js b/lib/compress.js index 56fb6851..57554fa5 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -1590,7 +1590,7 @@ merge(Compressor.prototype, { right: make_node(AST_String, self, { value: "" }) }); case "Function": - if (self.args[self.args.length - 1] instanceof AST_String) { + 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 // if the code argument is a constant, then we can minify it. diff --git a/lib/utils.js b/lib/utils.js index c95b9824..73964a09 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -245,6 +245,13 @@ function makePredicate(words) { return new Function("str", f); }; +function all(array, predicate) { + for (var i = array.length; --i >= 0;) + if (!predicate(array[i])) + return false; + return true; +}; + function Dictionary() { this._values = Object.create(null); this._size = 0; From ca3388cf5a27e48b8d41ba41114dd329de4708ee Mon Sep 17 00:00:00 2001 From: Mihai Bazon Date: Wed, 15 May 2013 13:27:23 +0300 Subject: [PATCH 025/165] Add `--expr`, an option to parse a single expression (suitable for JSON) --- bin/uglifyjs | 8 ++++++-- lib/parse.js | 11 ++++++++--- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/bin/uglifyjs b/bin/uglifyjs index fab9c665..b30b7599 100755 --- a/bin/uglifyjs +++ b/bin/uglifyjs @@ -23,6 +23,7 @@ mangling you need to use `-c` and `-m`.\ .describe("source-map-url", "The path to the source map to be added in //@ sourceMappingURL. Defaults to the value passed with --source-map.") .describe("in-source-map", "Input source map, useful if you're compressing JS that was generated from some other original code.") .describe("screw-ie8", "Pass this flag if you don't care about full compliance with Internet Explorer 6-8 quirks (by default UglifyJS will try to be IE-proof).") + .describe("expr", "Parse a single expression, rather than a program (for parsing JSON)") .describe("p", "Skip prefix for original filenames that appear in source maps. \ For example -p 3 will drop 3 directories from file names and ensure they are relative paths.") .describe("o", "Output file (default STDOUT).") @@ -76,6 +77,8 @@ You need to pass an argument to this option to specify the name that your module .string("e") .string("comments") .string("wrap") + + .boolean("expr") .boolean("screw-ie8") .boolean("export-all") .boolean("self") @@ -242,8 +245,9 @@ async.eachLimit(files, 1, function (file, cb) { } else { TOPLEVEL = UglifyJS.parse(code, { - filename: file, - toplevel: TOPLEVEL + filename : file, + toplevel : TOPLEVEL, + expression : ARGS.expr, }); }; }); diff --git a/lib/parse.js b/lib/parse.js index a687495b..e561ab67 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -588,9 +588,10 @@ var ATOMIC_START_TOKEN = array_to_hash([ "atom", "num", "string", "regexp", "nam function parse($TEXT, options) { options = defaults(options, { - strict : false, - filename : null, - toplevel : null + strict : false, + filename : null, + toplevel : null, + expression : false }); var S = { @@ -1386,6 +1387,10 @@ function parse($TEXT, options) { return ret; }; + if (options.expression) { + return expression(true); + } + return (function(){ var start = S.token; var body = []; From 2b40a5ac62102f6e61a891afa1276eeba948f6a4 Mon Sep 17 00:00:00 2001 From: Mihai Bazon Date: Wed, 15 May 2013 13:27:40 +0300 Subject: [PATCH 026/165] v2.3.4 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 18c42322..35c2ff04 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.3.3", + "version": "2.3.4", "engines": { "node" : ">=0.4.0" }, "maintainers": [{ "name": "Mihai Bazon", From ad1fc3b71ad0c5d24fde1b5be5cfdbdeced1dedc Mon Sep 17 00:00:00 2001 From: Mihai Bazon Date: Sun, 19 May 2013 14:24:33 +0300 Subject: [PATCH 027/165] Fix package.json (use `repository` instead of `repositories`) --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 35c2ff04..9f08ff8c 100644 --- a/package.json +++ b/package.json @@ -10,10 +10,10 @@ "email": "mihai.bazon@gmail.com", "web": "http://lisperator.net/" }], - "repositories": [{ + "repository": { "type": "git", "url": "https://github.com/mishoo/UglifyJS2.git" - }], + }, "dependencies": { "async" : "~0.2.6", "source-map" : "~0.1.7", From f652372c9a55ac7c6c3d965f0dff47bf0d0c0b3e Mon Sep 17 00:00:00 2001 From: Mihai Bazon Date: Sun, 19 May 2013 14:25:05 +0300 Subject: [PATCH 028/165] v2.3.5 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9f08ff8c..80194910 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.3.4", + "version": "2.3.5", "engines": { "node" : ">=0.4.0" }, "maintainers": [{ "name": "Mihai Bazon", From 22a038e6a23d1c1e8c5068b73f8af059521a6f2d Mon Sep 17 00:00:00 2001 From: Mihai Bazon Date: Mon, 20 May 2013 08:27:37 +0300 Subject: [PATCH 029/165] Fix output of statement: `new function(){...};` Close #209 --- lib/output.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/output.js b/lib/output.js index a13c92ea..b510cb35 100644 --- a/lib/output.js +++ b/lib/output.js @@ -1122,7 +1122,7 @@ function OutputStream(options) { if (p instanceof AST_Statement && p.body === node) return true; if ((p instanceof AST_Seq && p.car === node ) || - (p instanceof AST_Call && p.expression === node ) || + (p instanceof AST_Call && p.expression === node && !(p instanceof AST_New) ) || (p instanceof AST_Dot && p.expression === node ) || (p instanceof AST_Sub && p.expression === node ) || (p instanceof AST_Conditional && p.condition === node ) || From 1dbffd48ea49ee278454d6b1c632b7ba4bbf1868 Mon Sep 17 00:00:00 2001 From: Matt Robenolt Date: Tue, 21 May 2013 08:46:27 -0600 Subject: [PATCH 030/165] SourceMapping pragma has changed to //# See: https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit The spec was updated on May 16th since `//@` was causing some issues with IE. --- bin/uglifyjs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/uglifyjs b/bin/uglifyjs index b30b7599..3ac2a44d 100755 --- a/bin/uglifyjs +++ b/bin/uglifyjs @@ -20,7 +20,7 @@ mangling you need to use `-c` and `-m`.\ ") .describe("source-map", "Specify an output file where to generate source map.") .describe("source-map-root", "The path to the original source to be included in the source map.") - .describe("source-map-url", "The path to the source map to be added in //@ sourceMappingURL. Defaults to the value passed with --source-map.") + .describe("source-map-url", "The path to the source map to be added in //# sourceMappingURL. Defaults to the value passed with --source-map.") .describe("in-source-map", "Input source map, useful if you're compressing JS that was generated from some other original code.") .describe("screw-ie8", "Pass this flag if you don't care about full compliance with Internet Explorer 6-8 quirks (by default UglifyJS will try to be IE-proof).") .describe("expr", "Parse a single expression, rather than a program (for parsing JSON)") @@ -309,7 +309,7 @@ async.eachLimit(files, 1, function (file, cb) { if (SOURCE_MAP) { fs.writeFileSync(ARGS.source_map, SOURCE_MAP, "utf8"); - output += "\n/*\n//@ sourceMappingURL=" + (ARGS.source_map_url || ARGS.source_map) + "\n*/"; + output += "\n/*\n//# sourceMappingURL=" + (ARGS.source_map_url || ARGS.source_map) + "\n*/"; } if (OUTPUT_FILE) { From 3a218615804ceaf0fa1fcf7b8a0b3e10df3e6c86 Mon Sep 17 00:00:00 2001 From: Matt Robenolt Date: Tue, 21 May 2013 08:50:21 -0600 Subject: [PATCH 031/165] The extra /* */ isn't needed now --- bin/uglifyjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/uglifyjs b/bin/uglifyjs index 3ac2a44d..2611d960 100755 --- a/bin/uglifyjs +++ b/bin/uglifyjs @@ -309,7 +309,7 @@ async.eachLimit(files, 1, function (file, cb) { if (SOURCE_MAP) { fs.writeFileSync(ARGS.source_map, SOURCE_MAP, "utf8"); - output += "\n/*\n//# sourceMappingURL=" + (ARGS.source_map_url || ARGS.source_map) + "\n*/"; + output += "\n//# sourceMappingURL=" + (ARGS.source_map_url || ARGS.source_map); } if (OUTPUT_FILE) { From 9fc8a52142041b903b650923bb08b57876dd4c8c Mon Sep 17 00:00:00 2001 From: Mihai Bazon Date: Wed, 22 May 2013 13:08:19 +0300 Subject: [PATCH 032/165] Set "global" on undeclared SymbolDef-s --- lib/scope.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/scope.js b/lib/scope.js index ea271639..d15cec75 100644 --- a/lib/scope.js +++ b/lib/scope.js @@ -187,6 +187,7 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(){ } else { g = new SymbolDef(self, globals.size(), node); g.undeclared = true; + g.global = true; globals.set(name, g); } node.thedef = g; From 9fc6796d2a5f385500efc84020b5f6f73a6bb4a0 Mon Sep 17 00:00:00 2001 From: Mihai Bazon Date: Wed, 22 May 2013 21:22:14 +0300 Subject: [PATCH 033/165] Add `negate_iife` option to the code generator. See discussion in a9511dfbe5c0d96d8cacb87582aa9a19737bbb98 --- README.md | 4 ++++ lib/output.js | 5 +++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 88dc310e..749b8cec 100644 --- a/README.md +++ b/README.md @@ -287,6 +287,10 @@ can pass additional arguments that control the code output: you pass `false` then whenever possible we will use a newline instead of a semicolon, leading to more readable output of uglified code (size before gzip could be smaller; size after gzip insignificantly larger). +- `negate-iife` (default `!beautify`) -- prefer negation, rather than + parens, for "Immediately-Called Function Expressions". This defaults to + `true` when beautification is off, and `false` if beautification is on; + pass it manually to force a value. ### Keeping copyright notices or other comments diff --git a/lib/output.js b/lib/output.js index b510cb35..60a4a26c 100644 --- a/lib/output.js +++ b/lib/output.js @@ -60,7 +60,8 @@ function OutputStream(options) { bracketize : false, semicolons : true, comments : false, - preserve_line : false + preserve_line : false, + negate_iife : !(options && options.beautify), }, true); var indentation = 0; @@ -352,7 +353,7 @@ function OutputStream(options) { var self = this, generator = self._codegen; stream.push_node(self); var needs_parens = self.needs_parens(stream); - var fc = self instanceof AST_Function && !stream.option("beautify"); + var fc = self instanceof AST_Function && stream.option("negate_iife"); if (force_parens || (needs_parens && !fc)) { stream.with_parens(function(){ self.add_comments(stream); From 188e28efd7fa45711be5ced62b04da13511feb8b Mon Sep 17 00:00:00 2001 From: Mihai Bazon Date: Thu, 23 May 2013 23:42:32 +0300 Subject: [PATCH 034/165] v2.3.6 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 80194910..1262343d 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.3.5", + "version": "2.3.6", "engines": { "node" : ">=0.4.0" }, "maintainers": [{ "name": "Mihai Bazon", From f29f07aabd9d9f67ef7b826abb949262033d9273 Mon Sep 17 00:00:00 2001 From: Ville Lautanala Date: Mon, 3 Jun 2013 20:18:42 +0300 Subject: [PATCH 035/165] Escape null characters as \x00 Since \0 might be mistakenly interpreted as octal if followed by a number and using literal null is in some cases interpreted as end of string, escape null as \x00. --- lib/output.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/output.js b/lib/output.js index 60a4a26c..dc78a5da 100644 --- a/lib/output.js +++ b/lib/output.js @@ -96,7 +96,7 @@ function OutputStream(options) { case "\u2029": return "\\u2029"; case '"': ++dq; return '"'; case "'": ++sq; return "'"; - case "\0": return "\\0"; + case "\0": return "\\x00"; } return s; }); From 02a84385a02ccf4cf3a1ccca2fc5f6a90fb43487 Mon Sep 17 00:00:00 2001 From: Mihai Bazon Date: Fri, 7 Jun 2013 12:51:23 +0300 Subject: [PATCH 036/165] Don't swap binary ops when "use asm" is in effect. Refs #167 --- lib/ast.js | 3 +++ lib/compress.js | 17 +++++++++-------- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/lib/ast.js b/lib/ast.js index a1301da8..b8e89f23 100644 --- a/lib/ast.js +++ b/lib/ast.js @@ -945,6 +945,9 @@ TreeWalker.prototype = { if (x instanceof type) return x; } }, + has_directive: function(type) { + return this.find_parent(AST_Scope).has_directive(type); + }, in_boolean_context: function() { var stack = this.stack; var i = stack.length, self = stack[--i]; diff --git a/lib/compress.js b/lib/compress.js index 57554fa5..9eed8280 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -1765,14 +1765,15 @@ merge(Compressor.prototype, { var commutativeOperators = makePredicate("== === != !== * & | ^"); OPT(AST_Binary, function(self, compressor){ - function reverse(op, force) { - if (force || !(self.left.has_side_effects() || self.right.has_side_effects())) { - if (op) self.operator = op; - var tmp = self.left; - self.left = self.right; - self.right = tmp; - } - }; + var reverse = compressor.has_directive("use asm") ? noop + : function(op, force) { + if (force || !(self.left.has_side_effects() || self.right.has_side_effects())) { + if (op) self.operator = op; + var tmp = self.left; + self.left = self.right; + self.right = tmp; + } + }; if (commutativeOperators(self.operator)) { if (self.right instanceof AST_Constant && !(self.left instanceof AST_Constant)) { From d0689c81bb859ac78a966c526073dcc50a8ccde9 Mon Sep 17 00:00:00 2001 From: Mihai Bazon Date: Fri, 28 Jun 2013 10:08:13 +0300 Subject: [PATCH 037/165] Reset the base54 counters every time minify is called. Close #229 --- tools/node.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tools/node.js b/tools/node.js index c0dd3dbc..614e083d 100644 --- a/tools/node.js +++ b/tools/node.js @@ -63,6 +63,8 @@ exports.minify = function(files, options) { if (typeof files == "string") files = [ files ]; + UglifyJS.base54.reset(); + // 1. parse var toplevel = null; files.forEach(function(file){ From fc9ba323c475b107503f408af7fca19cb31a20f4 Mon Sep 17 00:00:00 2001 From: Mihai Bazon Date: Fri, 12 Jul 2013 09:56:58 +0300 Subject: [PATCH 038/165] Fix typo. Close #239 --- lib/parse.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/parse.js b/lib/parse.js index e561ab67..48ab2167 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -255,7 +255,7 @@ function tokenizer($TEXT, filename) { }; function token(type, value, is_comment) { - S.regex_allowed = ((type == "operator" && !UNARY_POSTFIX[value]) || + S.regex_allowed = ((type == "operator" && !UNARY_POSTFIX(value)) || (type == "keyword" && KEYWORDS_BEFORE_EXPRESSION(value)) || (type == "punc" && PUNC_BEFORE_EXPRESSION(value))); var ret = { From 9243b0cb9dd8469b6f4dbbf6ff9508e56f0e6fc2 Mon Sep 17 00:00:00 2001 From: Mihai Bazon Date: Sun, 14 Jul 2013 13:24:09 +0300 Subject: [PATCH 039/165] Apply transformer to AST_VarDef's name Fix #237 --- lib/transform.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/transform.js b/lib/transform.js index 7a61e5f3..c3c34f58 100644 --- a/lib/transform.js +++ b/lib/transform.js @@ -159,6 +159,7 @@ TreeTransformer.prototype = new TreeWalker; }); _(AST_VarDef, function(self, tw){ + self.name = self.name.transform(tw); if (self.value) self.value = self.value.transform(tw); }); From 4a0bab0fa3fc6d69507e91fdf17322b808aff7e4 Mon Sep 17 00:00:00 2001 From: Mihai Bazon Date: Mon, 15 Jul 2013 11:27:11 +0300 Subject: [PATCH 040/165] Add "position" option to parser, to specify initial pos/line/col (for parsing embedded scripts) --- lib/parse.js | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/lib/parse.js b/lib/parse.js index 48ab2167..35fd9c75 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -210,17 +210,17 @@ function is_token(token, type, val) { var EX_EOF = {}; -function tokenizer($TEXT, filename) { +function tokenizer($TEXT, filename, position) { var S = { text : $TEXT.replace(/\r\n?|[\n\u2028\u2029]/g, "\n").replace(/\uFEFF/g, ''), filename : filename, - pos : 0, - tokpos : 0, - line : 1, - tokline : 0, - col : 0, - tokcol : 0, + pos : position && position.pos || 0, + tokpos : position && position.pos || 0, + line : position && position.line || 1, + tokline : position && position.line || 1, + col : position && position.col || 0, + tokcol : position && position.col || 0, newline_before : false, regex_allowed : false, comments_before : [] @@ -591,7 +591,8 @@ function parse($TEXT, options) { strict : false, filename : null, toplevel : null, - expression : false + expression : false, + position : { pos: 0, line: 1, col: 0 }, }); var S = { From 193049af197ee73275dced11289b9b6755f4f469 Mon Sep 17 00:00:00 2001 From: Mihai Bazon Date: Mon, 15 Jul 2013 11:59:23 +0300 Subject: [PATCH 041/165] Revert previous patch, it was no good. --- lib/parse.js | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/lib/parse.js b/lib/parse.js index 35fd9c75..48ab2167 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -210,17 +210,17 @@ function is_token(token, type, val) { var EX_EOF = {}; -function tokenizer($TEXT, filename, position) { +function tokenizer($TEXT, filename) { var S = { text : $TEXT.replace(/\r\n?|[\n\u2028\u2029]/g, "\n").replace(/\uFEFF/g, ''), filename : filename, - pos : position && position.pos || 0, - tokpos : position && position.pos || 0, - line : position && position.line || 1, - tokline : position && position.line || 1, - col : position && position.col || 0, - tokcol : position && position.col || 0, + pos : 0, + tokpos : 0, + line : 1, + tokline : 0, + col : 0, + tokcol : 0, newline_before : false, regex_allowed : false, comments_before : [] @@ -591,8 +591,7 @@ function parse($TEXT, options) { strict : false, filename : null, toplevel : null, - expression : false, - position : { pos: 0, line: 1, col: 0 }, + expression : false }); var S = { From b1febde3e9be32b9d88918ed733efc3796e3f143 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Wed, 16 Jan 2013 14:59:19 -0500 Subject: [PATCH 042/165] Fix output for arrays whose last element is a hole: [1,,] 1529ab96 started to do this (by considering holes to be separate from "undefined") but it still converted [1,,] (length 2, last element hole, trailing comma) to [1,] (length 1, trailing comma) Unfortunately the test suite doesn't really make this clear: the new test here passes with or without this patch because run-tests.js beautifys the expected output (in "make_code"), which does the incorrect transformation! If you make some manual change to arrays.js to make the test fail and see the INPUT and OUTPUT, then you can see that without this fix, [1,,] -> [1,], and with this fix it stays [1,,]. --- lib/output.js | 5 +++++ test/compress/arrays.js | 2 ++ 2 files changed, 7 insertions(+) diff --git a/lib/output.js b/lib/output.js index dc78a5da..90589a2d 100644 --- a/lib/output.js +++ b/lib/output.js @@ -1016,6 +1016,11 @@ function OutputStream(options) { a.forEach(function(exp, i){ if (i) output.comma(); exp.print(output); + // If the final element is a hole, we need to make sure it + // doesn't look like a trailing comma, by inserting an actual + // trailing comma. + if (i === len - 1 && exp instanceof AST_Hole) + output.comma(); }); if (len > 0) output.space(); }); diff --git a/test/compress/arrays.js b/test/compress/arrays.js index 10fe6eb5..0c3d8ba2 100644 --- a/test/compress/arrays.js +++ b/test/compress/arrays.js @@ -1,10 +1,12 @@ holes_and_undefined: { input: { + w = [1,,]; x = [1, 2, undefined]; y = [1, , 2, ]; z = [1, undefined, 3]; } expect: { + w=[1,,]; x=[1,2,void 0]; y=[1,,2]; z=[1,void 0,3]; From dfa395f6ff405bbea018dbc24b31cbd49a9ac6ef Mon Sep 17 00:00:00 2001 From: Forbes Lindesay Date: Mon, 22 Jul 2013 01:44:03 +0100 Subject: [PATCH 043/165] Make `DefaultsError` a real `Error` object --- lib/utils.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/utils.js b/lib/utils.js index 73964a09..20f9a8e1 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -82,9 +82,12 @@ function repeat_string(str, i) { }; function DefaultsError(msg, defs) { + Error.call(this, msg) this.msg = msg; this.defs = defs; }; +DefaultsError.prototype = Object.create(Error.prototype) +DefaultsError.prototype.constructor = DefaultsError function defaults(args, defs, croak) { if (args === true) From e54df2226f7f3887d2f850cea8caf5c0353dce00 Mon Sep 17 00:00:00 2001 From: Dusan Bartos Date: Thu, 25 Jul 2013 15:32:21 +0200 Subject: [PATCH 044/165] added option for dropping unused params --- lib/compress.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/compress.js b/lib/compress.js index 9eed8280..c07ccd33 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -67,6 +67,7 @@ function Compressor(options, false_by_default) { cascade : !false_by_default, side_effects : !false_by_default, screw_ie8 : false, + drop_unused : !false_by_default, warnings : true, global_defs : {} @@ -83,7 +84,7 @@ merge(Compressor.prototype, { before: function(node, descend, in_list) { if (node._squeezed) return node; if (node instanceof AST_Scope) { - node.drop_unused(this); + if (this.options.drop_unused) node.drop_unused(this); node = node.hoist_declarations(this); } descend(node, this); @@ -96,7 +97,7 @@ merge(Compressor.prototype, { // no point to repeat warnings. var save_warnings = this.options.warnings; this.options.warnings = false; - node.drop_unused(this); + if (this.options.drop_unused) node.drop_unused(this); this.options.warnings = save_warnings; } node._squeezed = true; From 41c627379c31b4886091f19fc8385cd8619e645a Mon Sep 17 00:00:00 2001 From: Mihai Bazon Date: Thu, 25 Jul 2013 18:08:36 +0300 Subject: [PATCH 045/165] Reverting "added option for dropping unused params" Revert "added option for dropping unused params" (turns out we already had the `unused` option for this.) This reverts commit e54df2226f7f3887d2f850cea8caf5c0353dce00. --- lib/compress.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/compress.js b/lib/compress.js index c07ccd33..9eed8280 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -67,7 +67,6 @@ function Compressor(options, false_by_default) { cascade : !false_by_default, side_effects : !false_by_default, screw_ie8 : false, - drop_unused : !false_by_default, warnings : true, global_defs : {} @@ -84,7 +83,7 @@ merge(Compressor.prototype, { before: function(node, descend, in_list) { if (node._squeezed) return node; if (node instanceof AST_Scope) { - if (this.options.drop_unused) node.drop_unused(this); + node.drop_unused(this); node = node.hoist_declarations(this); } descend(node, this); @@ -97,7 +96,7 @@ merge(Compressor.prototype, { // no point to repeat warnings. var save_warnings = this.options.warnings; this.options.warnings = false; - if (this.options.drop_unused) node.drop_unused(this); + node.drop_unused(this); this.options.warnings = save_warnings; } node._squeezed = true; From 3435af494f62c59ce0d769e762966b91c57090b4 Mon Sep 17 00:00:00 2001 From: Mihai Bazon Date: Sun, 28 Jul 2013 11:11:11 +0300 Subject: [PATCH 046/165] Don't require arguments to --enclose --- bin/uglifyjs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/bin/uglifyjs b/bin/uglifyjs index 2611d960..56ff2235 100755 --- a/bin/uglifyjs +++ b/bin/uglifyjs @@ -264,11 +264,12 @@ async.eachLimit(files, 1, function (file, cb) { if (ARGS.enclose) { var arg_parameter_list = ARGS.enclose; - - if (!(arg_parameter_list instanceof Array)) { + if (arg_parameter_list === true) { + arg_parameter_list = []; + } + else if (!(arg_parameter_list instanceof Array)) { arg_parameter_list = [arg_parameter_list]; } - TOPLEVEL = TOPLEVEL.wrap_enclose(arg_parameter_list); } From b7adbcab1f4c9e846ae0b9ec3fd32991d62b68cf Mon Sep 17 00:00:00 2001 From: Mihai Bazon Date: Tue, 30 Jul 2013 12:16:29 +0300 Subject: [PATCH 047/165] Fix #251 --- bin/uglifyjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/uglifyjs b/bin/uglifyjs index 56ff2235..1e686d1b 100755 --- a/bin/uglifyjs +++ b/bin/uglifyjs @@ -223,7 +223,7 @@ try { async.eachLimit(files, 1, function (file, cb) { read_whole_file(file, function (err, code) { if (err) { - sys.error("ERROR: can't read file: " + filename); + sys.error("ERROR: can't read file: " + file); process.exit(1); } if (ARGS.p != null) { From 964d5b9aa47bad28bc200de90f3acd6d899b2733 Mon Sep 17 00:00:00 2001 From: Mihai Bazon Date: Sun, 4 Aug 2013 21:44:17 +0300 Subject: [PATCH 048/165] Don't pretend to evaluate lambdas Fix #255 --- lib/compress.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/compress.js b/lib/compress.js index 9eed8280..fcc3f31e 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -611,7 +611,7 @@ merge(Compressor.prototype, { // inherits from AST_Statement; however, an AST_Function // isn't really a statement. This could byte in other // places too. :-( Wish JS had multiple inheritance. - return [ this ]; + throw def; }); function ev(node) { return node._eval(); From 2604aadb371d5cb5a689da49c7bb2f820ae046ec Mon Sep 17 00:00:00 2001 From: Forbes Lindesay Date: Thu, 1 Aug 2013 05:51:09 +0100 Subject: [PATCH 049/165] Add support for browserify --- package.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 1262343d..d53a57a0 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,11 @@ "dependencies": { "async" : "~0.2.6", "source-map" : "~0.1.7", - "optimist" : "~0.3.5" + "optimist" : "~0.3.5", + "uglify-to-browserify": "~1.0.0" + }, + "browserify": { + "transform": [ "uglify-to-browserify" ] }, "bin": { "uglifyjs" : "bin/uglifyjs" From 4aa4b3e69493097c6674ad79a2300a64f77842cd Mon Sep 17 00:00:00 2001 From: Mihai Bazon Date: Wed, 7 Aug 2013 11:43:47 +0300 Subject: [PATCH 050/165] Support `-p relative`. Fix #256 --- README.md | 105 +++++++++++++++++++++++++++------------------------ bin/uglifyjs | 25 ++++++++++-- 2 files changed, 77 insertions(+), 53 deletions(-) diff --git a/README.md b/README.md index 749b8cec..46ba2a5d 100644 --- a/README.md +++ b/README.md @@ -46,55 +46,62 @@ files. The available options are: - --source-map Specify an output file where to generate source map. - [string] - --source-map-root The path to the original source to be included in the - source map. [string] - --source-map-url The path to the source map to be added in //@ - sourceMappingURL. Defaults to the value passed with - --source-map. [string] - --in-source-map Input source map, useful if you're compressing JS that was - generated from some other original code. - --screw-ie8 Pass this flag if you don't care about full compliance with - Internet Explorer 6-8 quirks (by default UglifyJS will try - to be IE-proof). - -p, --prefix Skip prefix for original filenames that appear in source - maps. For example -p 3 will drop 3 directories from file - names and ensure they are relative paths. - -o, --output Output file (default STDOUT). - -b, --beautify Beautify output/specify output options. [string] - -m, --mangle Mangle names/pass mangler options. [string] - -r, --reserved Reserved names to exclude from mangling. - -c, --compress Enable compressor/pass compressor options. Pass options - like -c hoist_vars=false,if_return=false. Use -c with no - argument to use the default compression options. [string] - -d, --define Global definitions [string] - --comments Preserve copyright comments in the output. By default this - works like Google Closure, keeping JSDoc-style comments - that contain "@license" or "@preserve". You can optionally - pass one of the following arguments to this flag: - - "all" to keep all comments - - a valid JS regexp (needs to start with a slash) to keep - only comments that match. - Note that currently not *all* comments can be kept when - compression is on, because of dead code removal or - cascading statements into sequences. [string] - --stats Display operations run time on STDERR. [boolean] - --acorn Use Acorn for parsing. [boolean] - --spidermonkey Assume input files are SpiderMonkey AST format (as JSON). - [boolean] - --self Build itself (UglifyJS2) as a library (implies - --wrap=UglifyJS --export-all) [boolean] - --wrap Embed everything in a big function, making the “exports” - and “global” variables available. You need to pass an - argument to this option to specify the name that your - module will take when included in, say, a browser. - [string] - --export-all Only used when --wrap, this tells UglifyJS to add code to - automatically export all globals. [boolean] - --lint Display some scope warnings [boolean] - -v, --verbose Verbose [boolean] - -V, --version Print version number and exit. [boolean] + --source-map Specify an output file where to generate source map. + [string] + --source-map-root The path to the original source to be included in the + source map. [string] + --source-map-url The path to the source map to be added in //# + sourceMappingURL. Defaults to the value passed with + --source-map. [string] + --in-source-map Input source map, useful if you're compressing JS that was + generated from some other original code. + --screw-ie8 Pass this flag if you don't care about full compliance + with Internet Explorer 6-8 quirks (by default UglifyJS + will try to be IE-proof). [boolean] + --expr Parse a single expression, rather than a program (for + parsing JSON) [boolean] + -p, --prefix Skip prefix for original filenames that appear in source + maps. For example -p 3 will drop 3 directories from file + names and ensure they are relative paths. You can also + specify -p relative, which will make UglifyJS figure out + itself the relative paths between original sources, the + source map and the output file. [string] + -o, --output Output file (default STDOUT). + -b, --beautify Beautify output/specify output options. [string] + -m, --mangle Mangle names/pass mangler options. [string] + -r, --reserved Reserved names to exclude from mangling. + -c, --compress Enable compressor/pass compressor options. Pass options + like -c hoist_vars=false,if_return=false. Use -c with no + argument to use the default compression options. [string] + -d, --define Global definitions [string] + -e, --enclose Embed everything in a big function, with a configurable + parameter/argument list. [string] + --comments Preserve copyright comments in the output. By default this + works like Google Closure, keeping JSDoc-style comments + that contain "@license" or "@preserve". You can optionally + pass one of the following arguments to this flag: + - "all" to keep all comments + - a valid JS regexp (needs to start with a slash) to keep + only comments that match. + Note that currently not *all* comments can be kept when + compression is on, because of dead code removal or + cascading statements into sequences. [string] + --stats Display operations run time on STDERR. [boolean] + --acorn Use Acorn for parsing. [boolean] + --spidermonkey Assume input files are SpiderMonkey AST format (as JSON). + [boolean] + --self Build itself (UglifyJS2) as a library (implies + --wrap=UglifyJS --export-all) [boolean] + --wrap Embed everything in a big function, making the “exports” + and “global” variables available. You need to pass an + argument to this option to specify the name that your + module will take when included in, say, a browser. + [string] + --export-all Only used when --wrap, this tells UglifyJS to add code to + automatically export all globals. [boolean] + --lint Display some scope warnings [boolean] + -v, --verbose Verbose [boolean] + -V, --version Print version number and exit. [boolean] Specify `--output` (`-o`) to declare the output file. Otherwise the output goes to STDOUT. diff --git a/bin/uglifyjs b/bin/uglifyjs index 1e686d1b..cad29ee0 100755 --- a/bin/uglifyjs +++ b/bin/uglifyjs @@ -7,6 +7,7 @@ var UglifyJS = require("../tools/node"); var sys = require("util"); var optimist = require("optimist"); var fs = require("fs"); +var path = require("path"); var async = require("async"); var acorn; var ARGS = optimist @@ -25,7 +26,9 @@ mangling you need to use `-c` and `-m`.\ .describe("screw-ie8", "Pass this flag if you don't care about full compliance with Internet Explorer 6-8 quirks (by default UglifyJS will try to be IE-proof).") .describe("expr", "Parse a single expression, rather than a program (for parsing JSON)") .describe("p", "Skip prefix for original filenames that appear in source maps. \ -For example -p 3 will drop 3 directories from file names and ensure they are relative paths.") +For example -p 3 will drop 3 directories from file names and ensure they are relative paths. \ +You can also specify -p relative, which will make UglifyJS figure out itself the relative paths between original sources, \ +the source map and the output file.") .describe("o", "Output file (default STDOUT).") .describe("b", "Beautify output/specify output options.") .describe("m", "Mangle names/pass mangler options.") @@ -77,6 +80,7 @@ You need to pass an argument to this option to specify the name that your module .string("e") .string("comments") .string("wrap") + .string("p") .boolean("expr") .boolean("screw-ie8") @@ -199,9 +203,10 @@ if (files.filter(function(el){ return el == "-" }).length > 1) { var STATS = {}; var OUTPUT_FILE = ARGS.o; var TOPLEVEL = null; +var P_RELATIVE = ARGS.p && ARGS.p == "relative"; var SOURCE_MAP = ARGS.source_map ? UglifyJS.SourceMap({ - file: OUTPUT_FILE, + file: P_RELATIVE ? path.relative(path.dirname(ARGS.source_map), OUTPUT_FILE) : OUTPUT_FILE, root: ARGS.source_map_root, orig: ORIG_MAP, }) : null; @@ -227,7 +232,14 @@ async.eachLimit(files, 1, function (file, cb) { process.exit(1); } if (ARGS.p != null) { - file = file.replace(/^\/+/, "").split(/\/+/).slice(ARGS.p).join("/"); + if (P_RELATIVE) { + file = path.relative(path.dirname(ARGS.source_map), file); + } else { + var p = parseInt(ARGS.p, 10); + if (!isNaN(p)) { + file = file.replace(/^\/+/, "").split(/\/+/).slice(ARGS.p).join("/"); + } + } } time_it("parse", function(){ if (ARGS.spidermonkey) { @@ -310,7 +322,12 @@ async.eachLimit(files, 1, function (file, cb) { if (SOURCE_MAP) { fs.writeFileSync(ARGS.source_map, SOURCE_MAP, "utf8"); - output += "\n//# sourceMappingURL=" + (ARGS.source_map_url || ARGS.source_map); + var source_map_url = ARGS.source_map_url || ( + P_RELATIVE + ? path.relative(path.dirname(OUTPUT_FILE), ARGS.source_map) + : ARGS.source_map + ); + output += "\n//# sourceMappingURL=" + source_map_url; } if (OUTPUT_FILE) { From 4c4dc2137cc256bb1e7a3827b4c96c95be42782e Mon Sep 17 00:00:00 2001 From: Mihai Bazon Date: Wed, 7 Aug 2013 12:04:58 +0300 Subject: [PATCH 051/165] Don't drop unused setter argument. Fix #257 --- lib/compress.js | 2 +- test/compress/drop-unused.js | 24 ++++++++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/lib/compress.js b/lib/compress.js index fcc3f31e..3bf9f672 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -948,7 +948,7 @@ merge(Compressor.prototype, { // pass 3: we should drop declarations not in_use var tt = new TreeTransformer( function before(node, descend, in_list) { - if (node instanceof AST_Lambda) { + if (node instanceof AST_Lambda && !(node instanceof AST_Accessor)) { for (var a = node.argnames, i = a.length; --i >= 0;) { var sym = a[i]; if (sym.unreferenced()) { diff --git a/test/compress/drop-unused.js b/test/compress/drop-unused.js index bf5cd296..406ce9e6 100644 --- a/test/compress/drop-unused.js +++ b/test/compress/drop-unused.js @@ -95,3 +95,27 @@ unused_circular_references_3: { } } } + +unused_keep_setter_arg: { + options = { unused: true }; + input: { + var x = { + _foo: null, + set foo(val) { + }, + get foo() { + return this._foo; + } + } + } + expect: { + var x = { + _foo: null, + set foo(val) { + }, + get foo() { + return this._foo; + } + } + } +} From 6ea3f7fe34e73f7f64b1a52c6dd0a3dcd9faa72e Mon Sep 17 00:00:00 2001 From: Mihai Bazon Date: Thu, 8 Aug 2013 09:15:13 +0300 Subject: [PATCH 052/165] fix usage --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 46ba2a5d..b735eb6e 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,7 @@ files. The available options are: +``` --source-map Specify an output file where to generate source map. [string] --source-map-root The path to the original source to be included in the @@ -102,6 +103,7 @@ The available options are: --lint Display some scope warnings [boolean] -v, --verbose Verbose [boolean] -V, --version Print version number and exit. [boolean] +``` Specify `--output` (`-o`) to declare the output file. Otherwise the output goes to STDOUT. From d9ad3c7cbf92c9b08d4f8eb4ec697a00d96c700a Mon Sep 17 00:00:00 2001 From: Michael Ficarra Date: Sun, 18 Aug 2013 19:45:06 -0500 Subject: [PATCH 053/165] fixes #259: don't unnecessarily quote object properties when --screw-ie8 --- bin/uglifyjs | 11 ++++++----- lib/compress.js | 3 +-- lib/output.js | 10 +++++----- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/bin/uglifyjs b/bin/uglifyjs index cad29ee0..7a4f6224 100755 --- a/bin/uglifyjs +++ b/bin/uglifyjs @@ -129,11 +129,6 @@ if (ARGS.d) { if (COMPRESS) COMPRESS.global_defs = getOptions("d"); } -if (ARGS.screw_ie8) { - if (COMPRESS) COMPRESS.screw_ie8 = true; - if (MANGLE) MANGLE.screw_ie8 = true; -} - if (ARGS.r) { if (MANGLE) MANGLE.except = ARGS.r.replace(/^\s+|\s+$/g).split(/\s*,+\s*/); } @@ -142,6 +137,12 @@ var OUTPUT_OPTIONS = { beautify: BEAUTIFY ? true : false }; +if (ARGS.screw_ie8) { + if (COMPRESS) COMPRESS.screw_ie8 = true; + if (MANGLE) MANGLE.screw_ie8 = true; + OUTPUT_OPTIONS.screw_ie8 = true; +} + if (BEAUTIFY) UglifyJS.merge(OUTPUT_OPTIONS, BEAUTIFY); diff --git a/lib/compress.js b/lib/compress.js index 3bf9f672..8dfbc72b 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -2005,8 +2005,7 @@ merge(Compressor.prototype, { var prop = self.property; if (prop instanceof AST_String && compressor.option("properties")) { prop = prop.getValue(); - if ((compressor.option("screw_ie8") && RESERVED_WORDS(prop)) - || (!(RESERVED_WORDS(prop)) && is_identifier_string(prop))) { + if (RESERVED_WORDS(prop) ? compressor.option("screw_ie8") : is_identifier_string(prop)) { return make_node(AST_Dot, self, { expression : self.expression, property : prop diff --git a/lib/output.js b/lib/output.js index 90589a2d..6d0dac5a 100644 --- a/lib/output.js +++ b/lib/output.js @@ -54,13 +54,13 @@ function OutputStream(options) { inline_script : false, width : 80, max_line_len : 32000, - ie_proof : true, beautify : false, source_map : null, bracketize : false, semicolons : true, comments : false, preserve_line : false, + screw_ie8 : false, negate_iife : !(options && options.beautify), }, true); @@ -761,7 +761,7 @@ function OutputStream(options) { if (!self.body) return output.force_semicolon(); if (self.body instanceof AST_Do - && output.option("ie_proof")) { + && !output.option("screw_ie8")) { // https://github.com/mishoo/UglifyJS/issues/#issue/57 IE // croaks with "syntax error" on code like this: if (foo) // do ... while(cond); else ... we need block brackets @@ -1048,10 +1048,10 @@ function OutputStream(options) { && +key + "" == key) && parseFloat(key) >= 0) { output.print(make_num(key)); - } else if (!is_identifier(key)) { - output.print_string(key); - } else { + } else if (RESERVED_WORDS(key) ? output.option("screw_ie8") : is_identifier_string(key)) { output.print_name(key); + } else { + output.print_string(key); } output.colon(); self.value.print(output); From ed80b4a534083510082419b305e0b60b395b10c6 Mon Sep 17 00:00:00 2001 From: Mihai Bazon Date: Tue, 20 Aug 2013 17:45:52 +0300 Subject: [PATCH 054/165] Move support for `negate_iife` in the compressor, rather than code generator (the code generator doesn't maintain enough context to know whether the return value is important or discarded) Fixes #272 --- README.md | 7 ++-- lib/compress.js | 40 +++++++++++++++++++ lib/output.js | 19 ++++----- test/compress/negate-iife.js | 76 ++++++++++++++++++++++++++++++++++++ 4 files changed, 126 insertions(+), 16 deletions(-) create mode 100644 test/compress/negate-iife.js diff --git a/README.md b/README.md index b735eb6e..ad394b25 100644 --- a/README.md +++ b/README.md @@ -209,6 +209,9 @@ to set `true`; it's effectively a shortcut for `foo=true`). and `x = something(), x` into `x = something()` - `warnings` -- display warnings when dropping unreachable code or unused declarations etc. +- `negate_iife` -- negate "Immediately-Called Function Expressions" + where the return value is discarded, to avoid the parens that the + code generator would insert. ### The `unsafe` option @@ -296,10 +299,6 @@ can pass additional arguments that control the code output: you pass `false` then whenever possible we will use a newline instead of a semicolon, leading to more readable output of uglified code (size before gzip could be smaller; size after gzip insignificantly larger). -- `negate-iife` (default `!beautify`) -- prefer negation, rather than - parens, for "Immediately-Called Function Expressions". This defaults to - `true` when beautification is off, and `false` if beautification is on; - pass it manually to force a value. ### Keeping copyright notices or other comments diff --git a/lib/compress.js b/lib/compress.js index 8dfbc72b..8d936bac 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -66,6 +66,7 @@ function Compressor(options, false_by_default) { join_vars : !false_by_default, cascade : !false_by_default, side_effects : !false_by_default, + negate_iife : !false_by_default, screw_ie8 : false, warnings : true, @@ -214,6 +215,11 @@ merge(Compressor.prototype, { statements = join_consecutive_vars(statements, compressor); } } while (CHANGED); + + if (compressor.option("negate_iife")) { + negate_iifes(statements, compressor); + } + return statements; function eliminate_spurious_blocks(statements) { @@ -497,6 +503,40 @@ merge(Compressor.prototype, { }, []); }; + function negate_iifes(statements, compressor) { + statements.forEach(function(stat){ + if (stat instanceof AST_SimpleStatement) { + stat.body = (function transform(thing) { + return thing.transform(new TreeTransformer(function(node){ + if (node instanceof AST_Call && node.expression instanceof AST_Function) { + return make_node(AST_UnaryPrefix, node, { + operator: "!", + expression: node + }); + } + else if (node instanceof AST_Call) { + node.expression = transform(node.expression); + } + else if (node instanceof AST_Seq) { + node.car = transform(node.car); + } + else if (node instanceof AST_Conditional) { + var expr = transform(node.condition); + if (expr !== node.condition) { + // it has been negated, reverse + node.condition = expr; + var tmp = node.consequent; + node.consequent = node.alternative; + node.alternative = tmp; + } + } + return node; + })); + })(stat.body); + } + }); + }; + }; function extract_declarations_from_unreachable_code(compressor, stat, target) { diff --git a/lib/output.js b/lib/output.js index 6d0dac5a..b7bcd1e3 100644 --- a/lib/output.js +++ b/lib/output.js @@ -61,7 +61,6 @@ function OutputStream(options) { comments : false, preserve_line : false, screw_ie8 : false, - negate_iife : !(options && options.beautify), }, true); var indentation = 0; @@ -351,21 +350,17 @@ function OutputStream(options) { AST_Node.DEFMETHOD("print", function(stream, force_parens){ var self = this, generator = self._codegen; - stream.push_node(self); - var needs_parens = self.needs_parens(stream); - var fc = self instanceof AST_Function && stream.option("negate_iife"); - if (force_parens || (needs_parens && !fc)) { - stream.with_parens(function(){ - self.add_comments(stream); - self.add_source_map(stream); - generator(self, stream); - }); - } else { + function doit() { self.add_comments(stream); - if (needs_parens && fc) stream.print("!"); self.add_source_map(stream); generator(self, stream); } + stream.push_node(self); + if (force_parens || self.needs_parens(stream)) { + stream.with_parens(doit); + } else { + doit(); + } stream.pop_node(); }); diff --git a/test/compress/negate-iife.js b/test/compress/negate-iife.js new file mode 100644 index 00000000..0362ffce --- /dev/null +++ b/test/compress/negate-iife.js @@ -0,0 +1,76 @@ +negate_iife_1: { + options = { + negate_iife: true + }; + input: { + (function(){ stuff() })(); + } + expect: { + !function(){ stuff() }(); + } +} + +negate_iife_2: { + options = { + negate_iife: true + }; + input: { + (function(){ return {} })().x = 10; // should not transform this one + } + expect: { + (function(){ return {} })().x = 10; + } +} + +negate_iife_3: { + options = { + negate_iife: true, + }; + input: { + (function(){ return true })() ? console.log(true) : console.log(false); + } + expect: { + !function(){ return true }() ? console.log(false) : console.log(true); + } +} + +negate_iife_3: { + options = { + negate_iife: true, + sequences: true + }; + input: { + (function(){ return true })() ? console.log(true) : console.log(false); + (function(){ + console.log("something"); + })(); + } + expect: { + !function(){ return true }() ? console.log(false) : console.log(true), function(){ + console.log("something"); + }(); + } +} + +negate_iife_4: { + options = { + negate_iife: true, + sequences: true, + conditionals: true, + }; + input: { + if ((function(){ return true })()) { + console.log(true); + } else { + console.log(false); + } + (function(){ + console.log("something"); + })(); + } + expect: { + !function(){ return true }() ? console.log(false) : console.log(true), function(){ + console.log("something"); + }(); + } +} From 1a95007ec103d8799b9f544948f81fa8c3c9dcb1 Mon Sep 17 00:00:00 2001 From: Mihai Bazon Date: Thu, 22 Aug 2013 10:10:25 +0300 Subject: [PATCH 055/165] Remove --ie-proof from the readme. Fix #276 --- README.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/README.md b/README.md index ad394b25..4356d4f2 100644 --- a/README.md +++ b/README.md @@ -289,9 +289,6 @@ can pass additional arguments that control the code output: It doesn't work very well currently, but it does make the code generated by UglifyJS more readable. - `max-line-len` (default 32000) -- maximum line length (for uglified code) -- `ie-proof` (default `true`) -- generate “IE-proof” code (for now this - means add brackets around the do/while in code like this: `if (foo) do - something(); while (bar); else ...`. - `bracketize` (default `false`) -- always insert brackets in `if`, `for`, `do`, `while` or `with` statements, even if their body is a single statement. From c28e1a023764993a3c9fd65ece2070ccc661237e Mon Sep 17 00:00:00 2001 From: Mihai Bazon Date: Thu, 22 Aug 2013 15:06:42 +0300 Subject: [PATCH 056/165] v2.4.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d53a57a0..a47563ac 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.3.6", + "version": "2.4.0", "engines": { "node" : ">=0.4.0" }, "maintainers": [{ "name": "Mihai Bazon", From a89d23331802cabb690ac817a5a7c93144a4f9c9 Mon Sep 17 00:00:00 2001 From: Mihai Bazon Date: Mon, 2 Sep 2013 09:55:34 +0300 Subject: [PATCH 057/165] Better reporting of parse errors --- bin/uglifyjs | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/bin/uglifyjs b/bin/uglifyjs index 7a4f6224..90b71a20 100755 --- a/bin/uglifyjs +++ b/bin/uglifyjs @@ -257,11 +257,21 @@ async.eachLimit(files, 1, function (file, cb) { }); } else { - TOPLEVEL = UglifyJS.parse(code, { - filename : file, - toplevel : TOPLEVEL, - expression : ARGS.expr, - }); + try { + TOPLEVEL = UglifyJS.parse(code, { + filename : file, + toplevel : TOPLEVEL, + expression : ARGS.expr, + }); + } catch(ex) { + if (ex instanceof UglifyJS.JS_Parse_Error) { + sys.error("Parse error at " + file + ":" + ex.line + "," + ex.col); + sys.error(ex.message); + sys.error(ex.stack); + process.exit(1); + } + throw ex; + } }; }); cb(); From 78a217b94c7648c88c46e0f5e9c15c747bef35dd Mon Sep 17 00:00:00 2001 From: Mihai Bazon Date: Mon, 2 Sep 2013 09:56:27 +0300 Subject: [PATCH 058/165] Fix parsing regexp after unary-prefix operator ++/x/.y Fix #284 --- lib/parse.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/parse.js b/lib/parse.js index 48ab2167..cc80a935 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -688,12 +688,16 @@ function parse($TEXT, options) { }; }; - var statement = embed_tokens(function() { - var tmp; + function handle_regexp() { if (is("operator", "/") || is("operator", "/=")) { S.peeked = null; S.token = S.input(S.token.value.substr(1)); // force regexp } + }; + + var statement = embed_tokens(function() { + var tmp; + handle_regexp(); switch (S.token.type) { case "string": var dir = S.in_directives, stat = simple_statement(); @@ -1279,6 +1283,7 @@ function parse($TEXT, options) { var start = S.token; if (is("operator") && UNARY_PREFIX(start.value)) { next(); + handle_regexp(); var ex = make_unary(AST_UnaryPrefix, start.value, maybe_unary(allow_calls)); ex.start = start; ex.end = prev(); From b0ca896d988bdafe5f28aa13dd14e4d6e4703108 Mon Sep 17 00:00:00 2001 From: Mihai Bazon Date: Mon, 2 Sep 2013 11:09:54 +0300 Subject: [PATCH 059/165] Fix parsing `a.case /= 1` Close #286 --- lib/parse.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/parse.js b/lib/parse.js index cc80a935..8801b50b 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -254,10 +254,12 @@ function tokenizer($TEXT, filename) { S.tokpos = S.pos; }; + var prev_was_dot = false; function token(type, value, is_comment) { S.regex_allowed = ((type == "operator" && !UNARY_POSTFIX(value)) || - (type == "keyword" && KEYWORDS_BEFORE_EXPRESSION(value)) || + (!prev_was_dot && type == "keyword" && KEYWORDS_BEFORE_EXPRESSION(value)) || (type == "punc" && PUNC_BEFORE_EXPRESSION(value))); + prev_was_dot = (type == "punc" && value == "."); var ret = { type : type, value : value, From 1c6efdae34f308a8cd22c41f48adf96dd428f4ee Mon Sep 17 00:00:00 2001 From: Mihai Bazon Date: Mon, 2 Sep 2013 11:36:48 +0300 Subject: [PATCH 060/165] Better fix for #286 --- lib/parse.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/parse.js b/lib/parse.js index 8801b50b..abd4d7b7 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -257,7 +257,7 @@ function tokenizer($TEXT, filename) { var prev_was_dot = false; function token(type, value, is_comment) { S.regex_allowed = ((type == "operator" && !UNARY_POSTFIX(value)) || - (!prev_was_dot && type == "keyword" && KEYWORDS_BEFORE_EXPRESSION(value)) || + (type == "keyword" && KEYWORDS_BEFORE_EXPRESSION(value)) || (type == "punc" && PUNC_BEFORE_EXPRESSION(value))); prev_was_dot = (type == "punc" && value == "."); var ret = { @@ -493,6 +493,7 @@ function tokenizer($TEXT, filename) { function read_word() { var word = read_name(); + if (prev_was_dot) return token("name", word); return KEYWORDS_ATOM(word) ? token("atom", word) : !KEYWORDS(word) ? token("name", word) : OPERATORS(word) ? token("operator", word) From 85b527ba3d609e87b5b5f758b429ef371dc3e459 Mon Sep 17 00:00:00 2001 From: Mihai Bazon Date: Mon, 2 Sep 2013 19:36:16 +0300 Subject: [PATCH 061/165] Disallow `continue` referring to a non-IterationStatement. Fix #287 Simplifies handling of labels (their definition/references can be easily figured out at parse time, no need to do it in `figure_out_scope`). --- lib/ast.js | 16 ++++++++++++---- lib/compress.js | 6 +++--- lib/parse.js | 22 +++++++++++++++++++--- lib/scope.js | 38 -------------------------------------- 4 files changed, 34 insertions(+), 48 deletions(-) diff --git a/lib/ast.js b/lib/ast.js index b8e89f23..e51cdd02 100644 --- a/lib/ast.js +++ b/lib/ast.js @@ -197,6 +197,10 @@ var AST_LabeledStatement = DEFNODE("LabeledStatement", "label", { } }, AST_StatementWithBody); +var AST_IterationStatement = DEFNODE("Loop", null, { + $documentation: "Internal class. All loops inherit from it." +}, AST_StatementWithBody); + var AST_DWLoop = DEFNODE("DWLoop", "condition", { $documentation: "Base class for do/while statements", $propdoc: { @@ -208,7 +212,7 @@ var AST_DWLoop = DEFNODE("DWLoop", "condition", { this.body._walk(visitor); }); } -}, AST_StatementWithBody); +}, AST_IterationStatement); var AST_Do = DEFNODE("Do", null, { $documentation: "A `do` statement", @@ -233,7 +237,7 @@ var AST_For = DEFNODE("For", "init condition step", { this.body._walk(visitor); }); } -}, AST_StatementWithBody); +}, AST_IterationStatement); var AST_ForIn = DEFNODE("ForIn", "init name object", { $documentation: "A `for ... in` statement", @@ -249,7 +253,7 @@ var AST_ForIn = DEFNODE("ForIn", "init name object", { this.body._walk(visitor); }); } -}, AST_StatementWithBody); +}, AST_IterationStatement); var AST_With = DEFNODE("With", "expression", { $documentation: "A `with` statement", @@ -821,7 +825,11 @@ var AST_SymbolCatch = DEFNODE("SymbolCatch", null, { var AST_Label = DEFNODE("Label", "references", { $documentation: "Symbol naming a label (declaration)", $propdoc: { - references: "[AST_LabelRef*] a list of nodes referring to this label" + references: "[AST_LoopControl*] a list of nodes referring to this label" + }, + initialize: function() { + this.references = []; + this.thedef = this; } }, AST_Symbol); diff --git a/lib/compress.js b/lib/compress.js index 8d936bac..0d2053e7 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -324,7 +324,7 @@ merge(Compressor.prototype, { || (ab instanceof AST_Continue && self === loop_body(lct)) || (ab instanceof AST_Break && lct instanceof AST_BlockStatement && self === lct))) { if (ab.label) { - remove(ab.label.thedef.references, ab.label); + remove(ab.label.thedef.references, ab); } CHANGED = true; var body = as_statement_array(stat.body).slice(0, -1); @@ -346,7 +346,7 @@ merge(Compressor.prototype, { || (ab instanceof AST_Continue && self === loop_body(lct)) || (ab instanceof AST_Break && lct instanceof AST_BlockStatement && self === lct))) { if (ab.label) { - remove(ab.label.thedef.references, ab.label); + remove(ab.label.thedef.references, ab); } CHANGED = true; stat = stat.clone(); @@ -385,7 +385,7 @@ merge(Compressor.prototype, { && loop_body(lct) === self) || (stat instanceof AST_Continue && loop_body(lct) === self)) { if (stat.label) { - remove(stat.label.thedef.references, stat.label); + remove(stat.label.thedef.references, stat); } } else { a.push(stat); diff --git a/lib/parse.js b/lib/parse.js index abd4d7b7..6946d93c 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -828,6 +828,18 @@ function parse($TEXT, options) { S.labels.push(label); var stat = statement(); S.labels.pop(); + if (!(stat instanceof AST_IterationStatement)) { + // check for `continue` that refers to this label. + // those should be reported as syntax errors. + // https://github.com/mishoo/UglifyJS2/issues/287 + label.references.forEach(function(ref){ + if (ref instanceof AST_Continue) { + ref = ref.label.start; + croak("Continue label `" + label.name + "` refers to non-loop statement.", + ref.line, ref.col, ref.pos); + } + }); + } return new AST_LabeledStatement({ body: stat, label: label }); }; @@ -836,18 +848,22 @@ function parse($TEXT, options) { }; function break_cont(type) { - var label = null; + var label = null, ldef; if (!can_insert_semicolon()) { label = as_symbol(AST_LabelRef, true); } if (label != null) { - if (!find_if(function(l){ return l.name == label.name }, S.labels)) + ldef = find_if(function(l){ return l.name == label.name }, S.labels); + if (!ldef) croak("Undefined label " + label.name); + label.thedef = ldef; } else if (S.in_loop == 0) croak(type.TYPE + " not inside a loop or switch"); semicolon(); - return new type({ label: label }); + var stat = new type({ label: label }); + if (ldef) ldef.references.push(stat); + return stat; }; function for_() { diff --git a/lib/scope.js b/lib/scope.js index d15cec75..8fc1ce0b 100644 --- a/lib/scope.js +++ b/lib/scope.js @@ -82,18 +82,14 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(){ // pass 1: setup scope chaining and handle definitions var self = this; var scope = self.parent_scope = null; - var labels = new Dictionary(); var nesting = 0; var tw = new TreeWalker(function(node, descend){ if (node instanceof AST_Scope) { node.init_scope_vars(nesting); var save_scope = node.parent_scope = scope; - var save_labels = labels; ++nesting; scope = node; - labels = new Dictionary(); descend(); - labels = save_labels; scope = save_scope; --nesting; return true; // don't descend again in TreeWalker @@ -108,22 +104,9 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(){ s.uses_with = true; return; } - if (node instanceof AST_LabeledStatement) { - var l = node.label; - if (labels.has(l.name)) - throw new Error(string_template("Label {name} defined twice", l)); - labels.set(l.name, l); - descend(); - labels.del(l.name); - return true; // no descend again - } if (node instanceof AST_Symbol) { node.scope = scope; } - if (node instanceof AST_Label) { - node.thedef = node; - node.init_scope_vars(); - } if (node instanceof AST_SymbolLambda) { scope.def_function(node); } @@ -150,15 +133,6 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(){ // identifier in the enclosing scope) scope.def_variable(node); } - if (node instanceof AST_LabelRef) { - var sym = labels.get(node.name); - if (!sym) throw new Error(string_template("Undefined label {name} [{line},{col}]", { - name: node.name, - line: node.start.line, - col: node.start.col - })); - node.thedef = sym; - } }); self.walk(tw); @@ -173,10 +147,6 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(){ func = prev_func; return true; } - if (node instanceof AST_LabelRef) { - node.reference(); - return true; - } if (node instanceof AST_SymbolRef) { var name = node.name; var sym = node.scope.find_variable(name); @@ -241,14 +211,6 @@ AST_SymbolRef.DEFMETHOD("reference", function() { this.frame = this.scope.nesting - def.scope.nesting; }); -AST_Label.DEFMETHOD("init_scope_vars", function(){ - this.references = []; -}); - -AST_LabelRef.DEFMETHOD("reference", function(){ - this.thedef.references.push(this); -}); - AST_Scope.DEFMETHOD("find_variable", function(name){ if (name instanceof AST_Symbol) name = name.name; return this.variables.get(name) From 5d8da864c55ad6f56a7ea82dd702339df04e2d3d Mon Sep 17 00:00:00 2001 From: Mihai Bazon Date: Mon, 2 Sep 2013 19:38:00 +0300 Subject: [PATCH 062/165] Fix names. --- lib/ast.js | 2 +- lib/parse.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/ast.js b/lib/ast.js index e51cdd02..878ceb94 100644 --- a/lib/ast.js +++ b/lib/ast.js @@ -197,7 +197,7 @@ var AST_LabeledStatement = DEFNODE("LabeledStatement", "label", { } }, AST_StatementWithBody); -var AST_IterationStatement = DEFNODE("Loop", null, { +var AST_IterationStatement = DEFNODE("IterationStatement", null, { $documentation: "Internal class. All loops inherit from it." }, AST_StatementWithBody); diff --git a/lib/parse.js b/lib/parse.js index 6946d93c..6a9f1ec9 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -835,7 +835,7 @@ function parse($TEXT, options) { label.references.forEach(function(ref){ if (ref instanceof AST_Continue) { ref = ref.label.start; - croak("Continue label `" + label.name + "` refers to non-loop statement.", + croak("Continue label `" + label.name + "` refers to non-IterationStatement.", ref.line, ref.col, ref.pos); } }); From cb9d16fbe4b9af135209e7f01cf1d40bf388c3d7 Mon Sep 17 00:00:00 2001 From: Mihai Bazon Date: Fri, 6 Sep 2013 09:52:56 +0300 Subject: [PATCH 063/165] minor --- lib/ast.js | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/lib/ast.js b/lib/ast.js index 878ceb94..1e6c836e 100644 --- a/lib/ast.js +++ b/lib/ast.js @@ -976,21 +976,15 @@ TreeWalker.prototype = { }, loopcontrol_target: function(label) { var stack = this.stack; - if (label) { - for (var i = stack.length; --i >= 0;) { - var x = stack[i]; - if (x instanceof AST_LabeledStatement && x.label.name == label.name) { - return x.body; - } - } - } else { - for (var i = stack.length; --i >= 0;) { - var x = stack[i]; - if (x instanceof AST_Switch - || x instanceof AST_For - || x instanceof AST_ForIn - || x instanceof AST_DWLoop) return x; + if (label) for (var i = stack.length; --i >= 0;) { + var x = stack[i]; + if (x instanceof AST_LabeledStatement && x.label.name == label.name) { + return x.body; } + } else for (var i = stack.length; --i >= 0;) { + var x = stack[i]; + if (x instanceof AST_Switch || x instanceof AST_IterationStatement) + return x; } } }; From 7c10b253462b0af3b8700e71ae2ae4aaac1a953e Mon Sep 17 00:00:00 2001 From: Mihai Bazon Date: Fri, 6 Sep 2013 09:54:30 +0300 Subject: [PATCH 064/165] Support HTML5 comment syntax (enabled by default!) See http://javascript.spec.whatwg.org/#comment-syntax https://github.com/mishoo/UglifyJS/issues/503 https://github.com/marijnh/acorn/issues/62 --- lib/output.js | 2 +- lib/parse.js | 64 +++++++++++++++++++++++++++++++++++---------------- 2 files changed, 45 insertions(+), 21 deletions(-) diff --git a/lib/output.js b/lib/output.js index b7bcd1e3..df2692ad 100644 --- a/lib/output.js +++ b/lib/output.js @@ -399,7 +399,7 @@ function OutputStream(options) { }); } comments.forEach(function(c){ - if (c.type == "comment1") { + if (/comment[134]/.test(c.type)) { output.print("//" + c.value + "\n"); output.indent(); } diff --git a/lib/parse.js b/lib/parse.js index 6a9f1ec9..a423aa0a 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -210,7 +210,7 @@ function is_token(token, type, val) { var EX_EOF = {}; -function tokenizer($TEXT, filename) { +function tokenizer($TEXT, filename, html5_comments) { var S = { text : $TEXT.replace(/\r\n?|[\n\u2028\u2029]/g, "\n").replace(/\uFEFF/g, ''), @@ -242,6 +242,14 @@ function tokenizer($TEXT, filename) { return ch; }; + function forward(i) { + while (i-- > 0) next(); + }; + + function looking_at(str) { + return S.text.substr(S.pos, str.length) == str; + }; + function find(what, signal_eof) { var pos = S.text.indexOf(what, S.pos); if (signal_eof && pos == -1) throw EX_EOF; @@ -381,8 +389,8 @@ function tokenizer($TEXT, filename) { return token("string", ret); }); - function read_line_comment() { - next(); + function skip_line_comment(type) { + var regex_allowed = S.regex_allowed; var i = find("\n"), ret; if (i == -1) { ret = S.text.substr(S.pos); @@ -391,11 +399,13 @@ function tokenizer($TEXT, filename) { ret = S.text.substring(S.pos, i); S.pos = i; } - return token("comment1", ret, true); + S.comments_before.push(token(type, ret, true)); + S.regex_allowed = regex_allowed; + return next_token(); }; - var read_multiline_comment = with_eof_error("Unterminated multiline comment", function(){ - next(); + var skip_multiline_comment = with_eof_error("Unterminated multiline comment", function(){ + var regex_allowed = S.regex_allowed; var i = find("*/", true); var text = S.text.substring(S.pos, i); var a = text.split("\n"), n = a.length; @@ -405,8 +415,11 @@ function tokenizer($TEXT, filename) { if (n > 1) S.col = a[n - 1].length; else S.col += a[n - 1].length; S.col += 2; - S.newline_before = S.newline_before || text.indexOf("\n") >= 0; - return token("comment2", text, true); + var nlb = S.newline_before = S.newline_before || text.indexOf("\n") >= 0; + S.comments_before.push(token("comment2", text, true)); + S.regex_allowed = regex_allowed; + S.newline_before = nlb; + return next_token(); }); function read_name() { @@ -470,16 +483,13 @@ function tokenizer($TEXT, filename) { function handle_slash() { next(); - var regex_allowed = S.regex_allowed; switch (peek()) { case "/": - S.comments_before.push(read_line_comment()); - S.regex_allowed = regex_allowed; - return next_token(); + next(); + return skip_line_comment("comment1"); case "*": - S.comments_before.push(read_multiline_comment()); - S.regex_allowed = regex_allowed; - return next_token(); + next(); + return skip_multiline_comment(); } return S.regex_allowed ? read_regexp("") : read_operator("/"); }; @@ -516,6 +526,16 @@ function tokenizer($TEXT, filename) { return read_regexp(force_regexp); skip_whitespace(); start_token(); + if (html5_comments) { + if (looking_at("") && S.newline_before) { + forward(3); + return skip_line_comment("comment4"); + } + } var ch = peek(); if (!ch) return token("eof"); var code = ch.charCodeAt(0); @@ -591,14 +611,18 @@ var ATOMIC_START_TOKEN = array_to_hash([ "atom", "num", "string", "regexp", "nam function parse($TEXT, options) { options = defaults(options, { - strict : false, - filename : null, - toplevel : null, - expression : false + strict : false, + filename : null, + toplevel : null, + expression : false, + html5_comments : true, }); var S = { - input : typeof $TEXT == "string" ? tokenizer($TEXT, options.filename) : $TEXT, + input : (typeof $TEXT == "string" + ? tokenizer($TEXT, options.filename, + options.html5_comments) + : $TEXT), token : null, prev : null, peeked : null, From 83ba338bd0e59267080c37bd235e1b0a601edf36 Mon Sep 17 00:00:00 2001 From: Mihai Bazon Date: Fri, 6 Sep 2013 10:10:45 +0300 Subject: [PATCH 065/165] Avoid printing x&&y?z:a + if (consequent instanceof AST_Conditional + && consequent.alternative.equivalent_to(alternative)) { + return make_node(AST_Conditional, self, { + condition: make_node(AST_Binary, self, { + left: self.condition, + operator: "&&", + right: consequent.condition + }), + consequent: consequent.consequent, + alternative: alternative + }); + } return self; }); diff --git a/test/compress/conditionals.js b/test/compress/conditionals.js index 9ef30ac1..213b246b 100644 --- a/test/compress/conditionals.js +++ b/test/compress/conditionals.js @@ -205,3 +205,30 @@ cond_4: { some_condition(), do_something(); } } + +cond_5: { + options = { + conditionals: true + }; + input: { + if (some_condition()) { + if (some_other_condition()) { + do_something(); + } else { + alternate(); + } + } else { + alternate(); + } + + if (some_condition()) { + if (some_other_condition()) { + do_something(); + } + } + } + expect: { + some_condition() && some_other_condition() ? do_something() : alternate(); + some_condition() && some_other_condition() && do_something(); + } +} From ef2ef07cbd945c7a6456adedc413858145a9ed10 Mon Sep 17 00:00:00 2001 From: Mihai Bazon Date: Sat, 8 Feb 2014 12:33:56 +0200 Subject: [PATCH 153/165] Add option `keep_fargs`. By default it's `false`. Pass `true` if you need to keep unused function arguments. Close #188. --- lib/compress.js | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/lib/compress.js b/lib/compress.js index 005c606d..b589aca5 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -61,6 +61,7 @@ function Compressor(options, false_by_default) { loops : !false_by_default, unused : !false_by_default, hoist_funs : !false_by_default, + keep_fargs : false, hoist_vars : false, if_return : !false_by_default, join_vars : !false_by_default, @@ -1044,18 +1045,20 @@ merge(Compressor.prototype, { var tt = new TreeTransformer( function before(node, descend, in_list) { if (node instanceof AST_Lambda && !(node instanceof AST_Accessor)) { - for (var a = node.argnames, i = a.length; --i >= 0;) { - var sym = a[i]; - if (sym.unreferenced()) { - a.pop(); - compressor.warn("Dropping unused function argument {name} [{file}:{line},{col}]", { - name : sym.name, - file : sym.start.file, - line : sym.start.line, - col : sym.start.col - }); + if (!compressor.option("keep_fargs")) { + for (var a = node.argnames, i = a.length; --i >= 0;) { + var sym = a[i]; + if (sym.unreferenced()) { + a.pop(); + compressor.warn("Dropping unused function argument {name} [{file}:{line},{col}]", { + name : sym.name, + file : sym.start.file, + line : sym.start.line, + col : sym.start.col + }); + } + else break; } - else break; } } if (node instanceof AST_Defun && node !== self) { From bf30dc30383520015bd265e528325c64e5ebe4ce Mon Sep 17 00:00:00 2001 From: Mihai Bazon Date: Fri, 14 Feb 2014 13:58:14 +0200 Subject: [PATCH 154/165] Mangle name of exception when --screw-ie8. Fix #430. The effect of not mangling it was visible only with --screw-ie8 (otherwise the names would be mangled exactly because they leaked into the parent scope). --- lib/scope.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/scope.js b/lib/scope.js index cce0f3c5..1ce17fa6 100644 --- a/lib/scope.js +++ b/lib/scope.js @@ -365,6 +365,10 @@ AST_Toplevel.DEFMETHOD("mangle_names", function(options){ node.mangled_name = name; return true; } + if (options.screw_ie8 && node instanceof AST_SymbolCatch) { + to_mangle.push(node.definition()); + return; + } }); this.walk(tw); to_mangle.forEach(function(def){ def.mangle(options) }); From 014f655c5f9f0a32994ee556e40acbe8349943fe Mon Sep 17 00:00:00 2001 From: Arnavion Date: Sun, 2 Mar 2014 19:20:19 -0800 Subject: [PATCH 155/165] Handle the case when SourceMapConsumer.originalPositionFor returns null source. This happens when SourceMapConsumer does not have a valid position to map the input line and column. This is a change in mozilla/source-map starting from version 0.1.33 Fixes #436 --- lib/sourcemap.js | 3 +++ package.json | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/sourcemap.js b/lib/sourcemap.js index 8e86ebd8..663ef12e 100644 --- a/lib/sourcemap.js +++ b/lib/sourcemap.js @@ -64,6 +64,9 @@ function SourceMap(options) { line: orig_line, column: orig_col }); + if (info.source === null) { + return; + } source = info.source; orig_line = info.line; orig_col = info.column; diff --git a/package.json b/package.json index fb1f7e6a..b9747ade 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ }, "dependencies": { "async" : "~0.2.6", - "source-map" : "~0.1.31", + "source-map" : "~0.1.33", "optimist" : "~0.3.5", "uglify-to-browserify": "~1.0.0" }, From 514936beb875b27d5df8b16b59a71b7424a427e1 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Thu, 6 Mar 2014 17:07:49 -0800 Subject: [PATCH 156/165] Handle TryStatements trees from acorn >=0.2.0 --- lib/mozilla-ast.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mozilla-ast.js b/lib/mozilla-ast.js index d7950942..bc24dfd6 100644 --- a/lib/mozilla-ast.js +++ b/lib/mozilla-ast.js @@ -51,7 +51,7 @@ start : my_start_token(M), end : my_end_token(M), body : from_moz(M.block).body, - bcatch : from_moz(M.handlers[0]), + bcatch : from_moz(M.handlers ? M.handlers[0] : M.handler), bfinally : M.finalizer ? new AST_Finally(from_moz(M.finalizer)) : null }); }, From 448a8d3845e16cd4c03fb2a0cf26b1d55307a635 Mon Sep 17 00:00:00 2001 From: Mihai Bazon Date: Tue, 11 Mar 2014 15:22:37 +0200 Subject: [PATCH 157/165] v2.4.13 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b9747ade..031e3395 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.12", + "version": "2.4.13", "engines": { "node" : ">=0.4.0" }, "maintainers": [{ "name": "Mihai Bazon", From e2e09d57548de10892d0595e9fb2a9978aa57726 Mon Sep 17 00:00:00 2001 From: Arnavion Date: Sat, 22 Mar 2014 18:02:21 -0700 Subject: [PATCH 158/165] Allow colons in the pairs passed to AST_Toplevel.wrap_enclose --- lib/ast.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/ast.js b/lib/ast.js index 2f216c20..051cd2fb 100644 --- a/lib/ast.js +++ b/lib/ast.js @@ -295,10 +295,10 @@ var AST_Toplevel = DEFNODE("Toplevel", "globals", { var parameters = []; arg_parameter_pairs.forEach(function(pair) { - var split = pair.split(":"); + var splitAt = pair.lastIndexOf(":"); - args.push(split[0]); - parameters.push(split[1]); + args.push(pair.substr(0, splitAt)); + parameters.push(pair.substr(splitAt + 1)); }); var wrapped_tl = "(function(" + parameters.join(",") + "){ '$ORIG'; })(" + args.join(",") + ")"; From 6fcabbde08cdac7890fedcc29b0342208998a871 Mon Sep 17 00:00:00 2001 From: ebednarz Date: Sun, 13 Apr 2014 11:16:10 +0200 Subject: [PATCH 159/165] Fix sourceMapIncludeSources exception in Node API https://github.com/mishoo/UglifyJS2/issues/459 --- tools/node.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/node.js b/tools/node.js index bef296ea..04e67e78 100644 --- a/tools/node.js +++ b/tools/node.js @@ -115,7 +115,7 @@ exports.minify = function(files, options) { if (options.sourceMapIncludeSources) { for (var file in sourcesContent) { if (sourcesContent.hasOwnProperty(file)) { - options.source_map.get().setSourceContent(file, sourcesContent[file]); + output.source_map.get().setSourceContent(file, sourcesContent[file]); } } } From ef772b0049828fef64e81ba49f564d45d880b27a Mon Sep 17 00:00:00 2001 From: OiNutter Date: Fri, 11 Apr 2014 16:09:56 +0100 Subject: [PATCH 160/165] add sourceMappingUrl to output in node module If options.outSourceMap is specified the sourceMappingURL comment should be appended to the output stream --- tools/node.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tools/node.js b/tools/node.js index 04e67e78..084998da 100644 --- a/tools/node.js +++ b/tools/node.js @@ -126,6 +126,11 @@ exports.minify = function(files, options) { } var stream = UglifyJS.OutputStream(output); toplevel.print(stream); + + if(options.outSourceMap){ + stream += "\n//# sourceMappingURL=" + options.outSourceMap; + } + return { code : stream + "", map : output.source_map + "" From 8fbe20001215887275b3cf053be675ebdbf331a9 Mon Sep 17 00:00:00 2001 From: Mihai Bazon Date: Fri, 18 Apr 2014 10:47:38 +0300 Subject: [PATCH 161/165] Always quote property names that contain non-ASCII characters. Fix #328 --- lib/parse.js | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/lib/parse.js b/lib/parse.js index b061067e..de982b1e 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -168,14 +168,7 @@ function is_identifier_char(ch) { }; function is_identifier_string(str){ - var i = str.length; - if (i == 0) return false; - if (!is_identifier_start(str.charCodeAt(0))) return false; - while (--i >= 0) { - if (!is_identifier_char(str.charAt(i))) - return false; - } - return true; + return /^[a-z_$][a-z0-9_$]*$/i.test(str); }; function parse_js_number(num) { From 37693d2812ee2e5555dbdf1712e7e3f85d64d870 Mon Sep 17 00:00:00 2001 From: Mihai Bazon Date: Fri, 18 Apr 2014 11:19:52 +0300 Subject: [PATCH 162/165] Update tests. --- test/compress/properties.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/compress/properties.js b/test/compress/properties.js index 85045961..736d9d88 100644 --- a/test/compress/properties.js +++ b/test/compress/properties.js @@ -26,7 +26,7 @@ dot_properties: { a.foo = "bar"; a["if"] = "if"; a["*"] = "asterisk"; - a.\u0EB3 = "unicode"; + a["\u0EB3"] = "unicode"; a[""] = "whitespace"; a["1_1"] = "foo"; } @@ -48,7 +48,7 @@ dot_properties_es5: { a.foo = "bar"; a.if = "if"; a["*"] = "asterisk"; - a.\u0EB3 = "unicode"; + a["\u0EB3"] = "unicode"; a[""] = "whitespace"; } } From 8258edd8a54cd5ed5d15a3a674f2cc716b304aeb Mon Sep 17 00:00:00 2001 From: Mihai Bazon Date: Sun, 27 Apr 2014 20:51:01 +0300 Subject: [PATCH 163/165] Fix parens in +(+x). Close #475 --- lib/output.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/output.js b/lib/output.js index adbc9a41..4a3bfd53 100644 --- a/lib/output.js +++ b/lib/output.js @@ -455,7 +455,8 @@ function OutputStream(options) { PARENS(AST_Unary, function(output){ var p = output.parent(); - return p instanceof AST_PropAccess && p.expression === this; + return (p instanceof AST_PropAccess && p.expression === this) + || (p instanceof AST_Unary && p.operator == "+" && this.operator == "+"); }); PARENS(AST_Seq, function(output){ From 025f3e9596e336ed4367d8621c920a236d6e4c17 Mon Sep 17 00:00:00 2001 From: Mihai Bazon Date: Sun, 27 Apr 2014 20:54:54 +0300 Subject: [PATCH 164/165] Better fix for #475 --- lib/output.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/output.js b/lib/output.js index 4a3bfd53..26f230af 100644 --- a/lib/output.js +++ b/lib/output.js @@ -456,7 +456,7 @@ function OutputStream(options) { PARENS(AST_Unary, function(output){ var p = output.parent(); return (p instanceof AST_PropAccess && p.expression === this) - || (p instanceof AST_Unary && p.operator == "+" && this.operator == "+"); + || (p instanceof AST_Unary && p.operator == this.operator); }); PARENS(AST_Seq, function(output){ From 7bf59b5bcd4fb477ce7ea60aaa29ea89b865a955 Mon Sep 17 00:00:00 2001 From: Mihai Bazon Date: Sun, 27 Apr 2014 21:42:14 +0300 Subject: [PATCH 165/165] Actually, even better. #475 - also handle x = + ++y, x = - --y; - don't use parens, a space suffices. --- lib/output.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/output.js b/lib/output.js index 26f230af..b9637929 100644 --- a/lib/output.js +++ b/lib/output.js @@ -455,8 +455,7 @@ function OutputStream(options) { PARENS(AST_Unary, function(output){ var p = output.parent(); - return (p instanceof AST_PropAccess && p.expression === this) - || (p instanceof AST_Unary && p.operator == this.operator); + return p instanceof AST_PropAccess && p.expression === this; }); PARENS(AST_Seq, function(output){ @@ -997,8 +996,12 @@ function OutputStream(options) { DEFPRINT(AST_UnaryPrefix, function(self, output){ var op = self.operator; output.print(op); - if (/^[a-z]/i.test(op)) + if (/^[a-z]/i.test(op) + || (/[+-]$/.test(op) + && self.expression instanceof AST_UnaryPrefix + && /^[+-]/.test(self.expression.operator))) { output.space(); + } self.expression.print(output); }); DEFPRINT(AST_UnaryPostfix, function(self, output){