diff --git a/README.md b/README.md index 2ab9de62..3245d404 100644 --- a/README.md +++ b/README.md @@ -65,9 +65,11 @@ The available options are: --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). + --screw-ie8 Use this flag if you don't wish to support + Internet Explorer 6-8 quirks. + By default UglifyJS will not try to be IE-proof. + --support-ie8 Use this flag to support Internet Explorer 6-8 quirks. + Note: may break standards compliant `catch` identifiers. --expr Parse a single expression, rather than a program (for parsing JSON) -p, --prefix Skip prefix for original filenames that appear @@ -289,7 +291,14 @@ you can pass a comma-separated list of options. Options are in the form `foo=bar`, or just `foo` (the latter implies a boolean option that you want to set `true`; it's effectively a shortcut for `foo=true`). -- `sequences` -- join consecutive simple statements using the comma operator +- `sequences` (default: true) -- join consecutive simple statements using the + comma operator. May be set to a positive integer to specify the maximum number + of consecutive comma sequences that will be generated. If this option is set to + `true` then the default `sequences` limit is `200`. Set option to `false` or `0` + to disable. The smallest `sequences` length is `2`. A `sequences` value of `1` + is grandfathered to be equivalent to `true` and as such means `200`. On rare + occasions the default sequences limit leads to very slow compress times in which + case a value of `20` or less is recommended. - `properties` -- rewrite property access using the dot notation, for example `foo["bar"] → foo.bar` @@ -661,7 +670,8 @@ Other options: - `fromString` (default `false`) — if you pass `true` then you can pass JavaScript source code, rather than file names. -- `mangle` — pass `false` to skip mangling names. +- `mangle` (default `true`) — pass `false` to skip mangling names, or pass + an object to specify mangling options (see below). - `mangleProperties` (default `false`) — pass an object to specify custom mangle property options. @@ -680,6 +690,32 @@ Other options: - `except` - pass an array of identifiers that should be excluded from mangling + - `toplevel` — mangle names declared in the toplevel scope (disabled by + default). + + - `eval` — mangle names visible in scopes where eval or with are used + (disabled by default). + + Examples: + + ```javascript + //tst.js + var globalVar; + function funcName(firstLongName, anotherLongName) + { + var myVariable = firstLongName + anotherLongName; + } + + UglifyJS.minify("tst.js").code; + // 'function funcName(a,n){}var globalVar;' + + UglifyJS.minify("tst.js", { mangle: { except: ['firstLongName'] }}).code; + // 'function funcName(firstLongName,a){}var globalVar;' + + UglifyJS.minify("tst.js", { mangle: toplevel: true }}).code; + // 'function n(n,a){}var a;' + ``` + ##### mangleProperties options - `regex` — Pass a RegExp to only mangle certain names (maps to the `--mangle-regex` CLI arguments option) diff --git a/bin/uglifyjs b/bin/uglifyjs index b7009426..30d234fd 100755 --- a/bin/uglifyjs +++ b/bin/uglifyjs @@ -10,6 +10,7 @@ var fs = require("fs"); var path = require("path"); var async = require("async"); var acorn; +var screw_ie8 = true; var ARGS = yargs .usage("$0 input1.js [input2.js ...] [options]\n\ Use a single dash to read input from the standard input.\ @@ -24,7 +25,8 @@ 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("source-map-include-sources", "Pass this flag if you want to include the content of source files in the source map as sourcesContent property.") .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("screw-ie8", "Do not support Internet Explorer 6-8 quirks. This flag is enabled by default.") + .describe("support-ie8", "Support non-standard Internet Explorer 6-8 javascript. Note: may break standards compliant `catch` identifiers.") .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. \ @@ -105,12 +107,14 @@ You need to pass an argument to this option to specify the name that your module .string("p") .string("prefix") .string("name-cache") + .array("reserved-file") .array("pure-funcs") .boolean("expr") .boolean("source-map-include-sources") .boolean("screw-ie8") + .boolean("support-ie8") .boolean("export-all") .boolean("self") .boolean("v") @@ -230,12 +234,14 @@ if (ARGS.mangle_props == 2) { COMPRESS.properties = false; } -if (ARGS.screw_ie8) { - if (COMPRESS) COMPRESS.screw_ie8 = true; - if (MANGLE) MANGLE.screw_ie8 = true; - OUTPUT_OPTIONS.screw_ie8 = true; +if (ARGS.support_ie8 === true && ARGS.screw_ie8 !== true) { + screw_ie8 = false; } +if (COMPRESS) COMPRESS.screw_ie8 = screw_ie8; +if (MANGLE) MANGLE.screw_ie8 = screw_ie8; +OUTPUT_OPTIONS.screw_ie8 = screw_ie8; + if (ARGS.keep_fnames) { if (COMPRESS) COMPRESS.keep_fnames = true; if (MANGLE) MANGLE.keep_fnames = true; @@ -426,7 +432,7 @@ async.eachLimit(files, 1, function (file, cb) { if (SCOPE_IS_NEEDED) { time_it("scope", function(){ - TOPLEVEL.figure_out_scope({ screw_ie8: ARGS.screw_ie8, cache: TL_CACHE }); + TOPLEVEL.figure_out_scope({ screw_ie8: screw_ie8, cache: TL_CACHE }); if (ARGS.lint) { TOPLEVEL.scope_warnings(); } @@ -441,7 +447,7 @@ async.eachLimit(files, 1, function (file, cb) { if (SCOPE_IS_NEEDED) { time_it("scope", function(){ - TOPLEVEL.figure_out_scope({ screw_ie8: ARGS.screw_ie8, cache: TL_CACHE }); + TOPLEVEL.figure_out_scope({ screw_ie8: screw_ie8, cache: TL_CACHE }); if (MANGLE && !TL_CACHE) { TOPLEVEL.compute_char_frequency(MANGLE); } diff --git a/lib/compress.js b/lib/compress.js index 0fbed00a..2a22e7f1 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -72,13 +72,15 @@ function Compressor(options, false_by_default) { pure_getters : false, pure_funcs : null, negate_iife : !false_by_default, - screw_ie8 : false, + screw_ie8 : true, drop_console : false, angular : false, warnings : true, global_defs : {}, passes : 1, }, true); + var sequences = this.options["sequences"]; + this.sequences_limit = sequences == 1 ? 200 : sequences | 0; this.warnings_produced = {}; }; @@ -190,7 +192,7 @@ merge(Compressor.prototype, { if ((1 / val) < 0) { return make_node(AST_UnaryPrefix, orig, { operator: "-", - expression: make_node(AST_Number, null, { value: -val }) + expression: make_node(AST_Number, orig, { value: -val }) }); } @@ -274,7 +276,7 @@ merge(Compressor.prototype, { if (compressor.option("if_return")) { statements = handle_if_return(statements, compressor); } - if (compressor.option("sequences")) { + if (compressor.sequences_limit > 0) { statements = sequencesize(statements, compressor); } if (compressor.option("join_vars")) { @@ -737,7 +739,7 @@ merge(Compressor.prototype, { seq = []; }; statements.forEach(function(stat){ - if (stat instanceof AST_SimpleStatement && seqLength(seq) < 2000) { + if (stat instanceof AST_SimpleStatement && seqLength(seq) < compressor.sequences_limit) { seq.push(stat.body); } else { push_seq(); @@ -1079,32 +1081,38 @@ merge(Compressor.prototype, { throw def; }); def(AST_Binary, function(c){ - var left = this.left, right = this.right; + var left = this.left, right = this.right, result; switch (this.operator) { - case "&&" : return ev(left, c) && ev(right, c); - case "||" : return ev(left, c) || ev(right, c); - case "|" : return ev(left, c) | ev(right, c); - case "&" : return ev(left, c) & ev(right, c); - case "^" : return ev(left, c) ^ ev(right, c); - case "+" : return ev(left, c) + ev(right, c); - case "*" : return ev(left, c) * ev(right, c); - case "**" : return Math.pow(ev(left, c), ev(right, c)); - case "/" : return ev(left, c) / ev(right, c); - case "%" : return ev(left, c) % ev(right, c); - case "-" : return ev(left, c) - ev(right, c); - case "<<" : return ev(left, c) << ev(right, c); - case ">>" : return ev(left, c) >> ev(right, c); - case ">>>" : return ev(left, c) >>> ev(right, c); - case "==" : return ev(left, c) == ev(right, c); - case "===" : return ev(left, c) === ev(right, c); - case "!=" : return ev(left, c) != ev(right, c); - case "!==" : return ev(left, c) !== ev(right, c); - case "<" : return ev(left, c) < ev(right, c); - case "<=" : return ev(left, c) <= ev(right, c); - case ">" : return ev(left, c) > ev(right, c); - case ">=" : return ev(left, c) >= ev(right, c); + case "&&" : result = ev(left, c) && ev(right, c); break; + case "||" : result = ev(left, c) || ev(right, c); break; + case "|" : result = ev(left, c) | ev(right, c); break; + case "&" : result = ev(left, c) & ev(right, c); break; + case "^" : result = ev(left, c) ^ ev(right, c); break; + case "+" : result = ev(left, c) + ev(right, c); break; + case "*" : result = ev(left, c) * ev(right, c); break; + case "**" : result = Math.pow(ev(left, c), ev(right, c)); break; + case "/" : result = ev(left, c) / ev(right, c); break; + case "%" : result = ev(left, c) % ev(right, c); break; + case "-" : result = ev(left, c) - ev(right, c); break; + case "<<" : result = ev(left, c) << ev(right, c); break; + case ">>" : result = ev(left, c) >> ev(right, c); break; + case ">>>" : result = ev(left, c) >>> ev(right, c); break; + case "==" : result = ev(left, c) == ev(right, c); break; + case "===" : result = ev(left, c) === ev(right, c); break; + case "!=" : result = ev(left, c) != ev(right, c); break; + case "!==" : result = ev(left, c) !== ev(right, c); break; + case "<" : result = ev(left, c) < ev(right, c); break; + case "<=" : result = ev(left, c) <= ev(right, c); break; + case ">" : result = ev(left, c) > ev(right, c); break; + case ">=" : result = ev(left, c) >= ev(right, c); break; + default: + throw def; } - throw def; + if (isNaN(result) && c.find_parent(AST_With)) { + // leave original expression as is + throw def; + } + return result; }); def(AST_Conditional, function(compressor){ return ev(this.condition, compressor) @@ -2678,13 +2686,16 @@ merge(Compressor.prototype, { if (defines && HOP(defines, self.name)) { return make_node_from_constant(compressor, defines[self.name], self); } - switch (self.name) { - case "undefined": - return make_node(AST_Undefined, self); - case "NaN": - return make_node(AST_NaN, self).transform(compressor); - case "Infinity": - return make_node(AST_Infinity, self).transform(compressor); + // testing against !self.scope.uses_with first is an optimization + if (!self.scope.uses_with || !compressor.find_parent(AST_With)) { + switch (self.name) { + case "undefined": + return make_node(AST_Undefined, self); + case "NaN": + return make_node(AST_NaN, self).transform(compressor); + case "Infinity": + return make_node(AST_Infinity, self).transform(compressor); + } } } return self; @@ -2816,7 +2827,7 @@ merge(Compressor.prototype, { if (consequent.is_constant(compressor) && alternative.is_constant(compressor) && consequent.equivalent_to(alternative)) { - var consequent_value = consequent.constant_value(); + var consequent_value = consequent.constant_value(compressor); if (self.condition.has_side_effects(compressor)) { return AST_Seq.from_array([self.condition, make_node_from_constant(compressor, consequent_value, self)]); } else { diff --git a/lib/mozilla-ast.js b/lib/mozilla-ast.js index c1b2b683..34332215 100644 --- a/lib/mozilla-ast.js +++ b/lib/mozilla-ast.js @@ -94,6 +94,15 @@ return new AST_ObjectGetter(args); } }, + ArrayExpression: function(M) { + return new AST_Array({ + start : my_start_token(M), + end : my_end_token(M), + elements : M.elements.map(function(elem){ + return elem === null ? new AST_Hole() : from_moz(elem); + }) + }); + }, ObjectExpression: function(M) { return new AST_Object({ start : my_start_token(M), @@ -206,7 +215,6 @@ map("CatchClause", AST_Catch, "param>argname, body%body"); map("ThisExpression", AST_This); - map("ArrayExpression", AST_Array, "elements@elements"); map("FunctionExpression", AST_Function, "id>name, params@argnames, body%body"); map("BinaryExpression", AST_Binary, "operator=operator, left>left, right>right"); map("LogicalExpression", AST_Binary, "operator=operator, left>left, right>right"); @@ -302,6 +310,13 @@ }; }); + def_to_moz(AST_Array, function To_Moz_ArrayExpression(M) { + return { + type: "ArrayExpression", + elements: M.elements.map(to_moz) + }; + }); + def_to_moz(AST_Object, function To_Moz_ObjectExpression(M) { return { type: "ObjectExpression", diff --git a/lib/output.js b/lib/output.js index 91f7c504..60c8dad8 100644 --- a/lib/output.js +++ b/lib/output.js @@ -64,7 +64,7 @@ function OutputStream(options) { comments : false, shebang : true, preserve_line : false, - screw_ie8 : false, + screw_ie8 : true, preamble : null, quote_style : 0, keep_quoted_props: false, @@ -998,8 +998,8 @@ function OutputStream(options) { // adds the block brackets if needed. if (!self.body) return output.force_semicolon(); - if (self.body instanceof AST_Do - && !output.option("screw_ie8")) { + if (self.body instanceof AST_Do) { + // Unconditionally use the if/do-while workaround for all browsers. // 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 diff --git a/lib/parse.js b/lib/parse.js index 7f306483..f843bc4b 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -112,7 +112,9 @@ var OPERATORS = makePredicate([ "||" ]); -var WHITESPACE_CHARS = makePredicate(characters(" \u00a0\n\r\t\f\u000b\u200b\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\uFEFF")); +var WHITESPACE_CHARS = makePredicate(characters(" \u00a0\n\r\t\f\u000b\u200b\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u2028\u2029\u202f\u205f\u3000\uFEFF")); + +var NEWLINE_CHARS = makePredicate(characters("\n\r\u2028\u2029")); var PUNC_AFTER_EXPRESSION = makePredicate(characters(";]),:")); @@ -263,7 +265,7 @@ function tokenizer($TEXT, filename, html5_comments, shebang) { var ch = get_full_char(S.text, S.pos++); if (signal_eof && !ch) throw EX_EOF; - if ("\r\n\u2028\u2029".indexOf(ch) >= 0) { + if (NEWLINE_CHARS(ch)) { S.newline_before = S.newline_before || !in_string; ++S.line; S.col = 0; @@ -294,7 +296,7 @@ function tokenizer($TEXT, filename, html5_comments, shebang) { var text = S.text; for (var i = S.pos, n = S.text.length; i < n; ++i) { var ch = text[i]; - if (ch == '\n' || ch == '\r') + if (NEWLINE_CHARS(ch)) return i; } return -1; @@ -346,8 +348,7 @@ function tokenizer($TEXT, filename, html5_comments, shebang) { }; function skip_whitespace() { - var ch; - while (WHITESPACE_CHARS(ch = peek()) || ch == "\u2028" || ch == "\u2029") + while (WHITESPACE_CHARS(peek())) next(); }; @@ -388,7 +389,7 @@ function tokenizer($TEXT, filename, html5_comments, shebang) { if (!isNaN(valid)) { return token("num", valid); } else { - parse_error("Invalid syntax: " + num); + parse_error("SyntaxError: Invalid syntax: " + num); } }; @@ -401,7 +402,6 @@ function tokenizer($TEXT, filename, html5_comments, shebang) { case 98 : return "\b"; case 118 : return "\u000b"; // \v case 102 : return "\f"; - case 48 : return "\0"; case 120 : return String.fromCharCode(hex_bytes(2)); // \x case 117 : // \u if (peek() == "{") { @@ -426,9 +426,27 @@ function tokenizer($TEXT, filename, html5_comments, shebang) { return ""; } } + if (ch >= "0" && ch <= "7") + return read_octal_escape_sequence(ch); return ch; }; + function read_octal_escape_sequence(ch) { + // Read + var p = peek(); + if (p >= "0" && p <= "7") { + ch += next(true); + if (ch[0] <= "3" && (p = peek()) >= "0" && p <= "7") + ch += next(true); + } + + // Parse + if (ch === "0") return "\0"; + if (ch.length > 0 && next_token.has_directive("use strict")) + parse_error("SyntaxError: Octal literals are not allowed in strict mode"); + return String.fromCharCode(parseInt(ch, 8)); + } + function hex_bytes(n) { var num = 0; for (; n > 0; --n) { @@ -440,32 +458,12 @@ function tokenizer($TEXT, filename, html5_comments, shebang) { return num; }; - var read_string = with_eof_error("Unterminated string constant", function(quote_char){ + var read_string = with_eof_error("SyntaxError: Unterminated string constant", function(quote_char){ var quote = next(), ret = ""; for (;;) { var ch = next(true, true); - if (ch == "\\") { - var octal_len = 0, first = null; - ch = read_while(function(ch){ - if (ch >= "0" && ch <= "7") { - if (!first) { - first = ch; - return ++octal_len; - } - else if (first <= "3" && octal_len <= 2) return ++octal_len; - else if (first >= "4" && octal_len <= 1) return ++octal_len; - } - return false; - }); - if (octal_len > 0) { - if (ch !== "0" && next_token.has_directive("use strict")) - parse_error("Octal literals are not allowed in strict mode"); - ch = String.fromCharCode(parseInt(ch, 8)); - } else { - ch = read_escaped_char(true); - } - } - else if ("\r\n\u2028\u2029".indexOf(ch) >= 0) parse_error("Unterminated string constant"); + if (ch == "\\") ch = read_escaped_char(true); + else if (NEWLINE_CHARS(ch)) parse_error("SyntaxError: Unterminated string constant"); else if (ch == quote) break; ret += ch; } @@ -490,21 +488,14 @@ function tokenizer($TEXT, filename, html5_comments, shebang) { return next_token; }; - var skip_multiline_comment = with_eof_error("Unterminated multiline comment", function(){ + var skip_multiline_comment = with_eof_error("SyntaxError: Unterminated multiline comment", function(){ var regex_allowed = S.regex_allowed; var i = find("*/", true); - var text = S.text.substring(S.pos, i).replace(/\r\n|\r/g, '\n'); - var a = text.split("\n"), n = a.length; + var text = S.text.substring(S.pos, i).replace(/\r\n|\r|\u2028|\u2029/g, '\n'); // update stream position - S.pos = i + 2; - S.line += n - 1; - if (n > 1) S.col = a[n - 1].length; - else S.col += a[n - 1].length; - S.col += 2; - var nlb = S.newline_before = S.newline_before || text.indexOf("\n") >= 0; + forward(text.length /* doesn't count \r\n as 2 char while S.pos - i does */ + 2); S.comments_before.push(token("comment2", text, true)); S.regex_allowed = regex_allowed; - S.newline_before = nlb; return next_token; }); @@ -552,9 +543,11 @@ function tokenizer($TEXT, filename, html5_comments, shebang) { return name; }; - var read_regexp = with_eof_error("Unterminated regular expression", function(regexp){ + var read_regexp = with_eof_error("SyntaxError: Unterminated regular expression", function(regexp){ var prev_backslash = false, ch, in_class = false; - while ((ch = next(true))) if (prev_backslash) { + while ((ch = next(true))) if (NEWLINE_CHARS(ch)) { + parse_error("SyntaxError: Unexpected line terminator"); + } else if (prev_backslash) { regexp += "\\" + ch; prev_backslash = false; } else if (ch == "[") { @@ -567,8 +560,6 @@ function tokenizer($TEXT, filename, html5_comments, shebang) { break; } else if (ch == "\\") { prev_backslash = true; - } else if ("\r\n\u2028\u2029".indexOf(ch) >= 0) { - parse_error("Unexpected line terminator"); } else { regexp += ch; } diff --git a/lib/scope.js b/lib/scope.js index 11adbbfd..08d4413a 100644 --- a/lib/scope.js +++ b/lib/scope.js @@ -96,7 +96,7 @@ SymbolDef.prototype = { AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){ options = defaults(options, { - screw_ie8: false, + screw_ie8: true, cache: null }); @@ -449,7 +449,7 @@ AST_Toplevel.DEFMETHOD("_default_mangler_options", function(options){ eval : false, sort : false, // Ignored. Flag retained for backwards compatibility. toplevel : false, - screw_ie8 : false, + screw_ie8 : true, keep_fnames : false, keep_classnames : false }); diff --git a/lib/utils.js b/lib/utils.js index 78c6dbf7..8ef61936 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -227,10 +227,19 @@ function makePredicate(words) { } cats.push([words[i]]); } + function quote(word) { + return JSON.stringify(word).replace(/[\u2028\u2029]/g, function(s) { + switch (s) { + case "\u2028": return "\\u2028"; + case "\u2029": return "\\u2029"; + } + return s; + }); + } function compareTo(arr) { - if (arr.length == 1) return f += "return str === " + JSON.stringify(arr[0]) + ";"; + if (arr.length == 1) return f += "return str === " + quote(arr[0]) + ";"; f += "switch(str){"; - for (var i = 0; i < arr.length; ++i) f += "case " + JSON.stringify(arr[i]) + ":"; + for (var i = 0; i < arr.length; ++i) f += "case " + quote(arr[i]) + ":"; f += "return true}return false;"; } // When there are more than three length categories, an outer diff --git a/package.json b/package.json index 423a3e5d..3ac7b52b 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "homepage": "http://lisperator.net/uglifyjs", "author": "Mihai Bazon (http://lisperator.net/)", "license": "BSD-2-Clause", - "version": "2.6.3", + "version": "2.6.4", "engines": { "node": ">=0.8.0" }, diff --git a/test/compress/ascii.js b/test/compress/ascii.js index 7686ac3f..2232d263 100644 --- a/test/compress/ascii.js +++ b/test/compress/ascii.js @@ -2,6 +2,7 @@ ascii_only_true: { options = {} beautify = { ascii_only : true, + screw_ie8 : true, beautify : false, } input: { @@ -12,13 +13,14 @@ ascii_only_true: { "\x20\x21\x22\x23 ... \x7d\x7e\x7f\x80\x81 ... \xfe\xff\u0fff\uffff"; } } - expect_exact: 'function f(){return"\\x000\\x001\\x007\\08\\0"+"\\0\\x01\\x02\\x03\\x04\\x05\\x06\\x07\\b\\t\\n\\x0B\\f\\r\\x0e\\x0f"+"\\x10\\x11\\x12\\x13\\x14\\x15\\x16\\x17\\x18\\x19\\x1a\\x1b\\x1c\\x1d\\x1e\\x1f"+\' !"# ... }~\\x7f\\x80\\x81 ... \\xfe\\xff\\u0fff\\uffff\'}' + expect_exact: 'function f(){return"\\x000\\x001\\x007\\08\\0"+"\\0\\x01\\x02\\x03\\x04\\x05\\x06\\x07\\b\\t\\n\\v\\f\\r\\x0e\\x0f"+"\\x10\\x11\\x12\\x13\\x14\\x15\\x16\\x17\\x18\\x19\\x1a\\x1b\\x1c\\x1d\\x1e\\x1f"+\' !"# ... }~\\x7f\\x80\\x81 ... \\xfe\\xff\\u0fff\\uffff\'}' } ascii_only_false: { options = {} beautify = { ascii_only : false, + screw_ie8 : true, beautify : false, } input: { @@ -29,6 +31,6 @@ ascii_only_false: { "\x20\x21\x22\x23 ... \x7d\x7e\x7f\x80\x81 ... \xfe\xff\u0fff\uffff"; } } - expect_exact: 'function f(){return"\\x000\\x001\\x007\\08\\0"+"\\0\x01\x02\x03\x04\x05\x06\x07\\b\\t\\n\\x0B\\f\\r\x0e\x0f"+"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f"+\' !"# ... }~\x7f\x80\x81 ... \xfe\xff\u0fff\uffff\'}' + expect_exact: 'function f(){return"\\x000\\x001\\x007\\08\\0"+"\\0\x01\x02\x03\x04\x05\x06\x07\\b\\t\\n\\v\\f\\r\x0e\x0f"+"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f"+\' !"# ... }~\x7f\x80\x81 ... \xfe\xff\u0fff\uffff\'}' } diff --git a/test/compress/conditionals.js b/test/compress/conditionals.js index f5eeb6f2..35cb26f7 100644 --- a/test/compress/conditionals.js +++ b/test/compress/conditionals.js @@ -868,3 +868,41 @@ trivial_boolean_ternary_expressions : { f(!(x >= y)); } } + +issue_1154: { + options = { + conditionals: true, + evaluate : true, + booleans : true, + }; + input: { + function f1(x) { return x ? -1 : -1; } + function f2(x) { return x ? +2 : +2; } + function f3(x) { return x ? ~3 : ~3; } + function f4(x) { return x ? !4 : !4; } + function f5(x) { return x ? void 5 : void 5; } + function f6(x) { return x ? typeof 6 : typeof 6; } + + function g1() { return g() ? -1 : -1; } + function g2() { return g() ? +2 : +2; } + function g3() { return g() ? ~3 : ~3; } + function g4() { return g() ? !4 : !4; } + function g5() { return g() ? void 5 : void 5; } + function g6() { return g() ? typeof 6 : typeof 6; } + } + expect: { + function f1(x) { return -1; } + function f2(x) { return 2; } + function f3(x) { return -4; } + function f4(x) { return !1; } + function f5(x) { return; } + function f6(x) { return "number"; } + + function g1() { return g(), -1; } + function g2() { return g(), 2; } + function g3() { return g(), -4; } + function g4() { return g(), !1; } + function g5() { return g(), void 0; } + function g6() { return g(), "number"; } + } +} diff --git a/test/compress/issue-1105.js b/test/compress/issue-1105.js index 4205fdf5..28f1557a 100644 --- a/test/compress/issue-1105.js +++ b/test/compress/issue-1105.js @@ -144,4 +144,97 @@ check_drop_unused_in_peer_function: { bar(); } } -} \ No newline at end of file +} + +Infinity_not_in_with_scope: { + options = { + unused: true + } + input: { + var o = { Infinity: 'oInfinity' }; + var vInfinity = "Infinity"; + vInfinity = Infinity; + } + expect: { + var o = { Infinity: 'oInfinity' } + var vInfinity = "Infinity" + vInfinity = 1/0 + } +} + +Infinity_in_with_scope: { + options = { + unused: true + } + input: { + var o = { Infinity: 'oInfinity' }; + var vInfinity = "Infinity"; + with (o) { vInfinity = Infinity; } + } + expect: { + var o = { Infinity: 'oInfinity' } + var vInfinity = "Infinity" + with (o) vInfinity = Infinity + } +} + +assorted_Infinity_NaN_undefined_in_with_scope: { + options = { + unused: true, + evaluate: true, + dead_code: true, + conditionals: true, + comparisons: true, + booleans: true, + hoist_funs: true, + keep_fargs: true, + if_return: true, + join_vars: true, + cascade: true, + side_effects: true, + sequences: false, + } + input: { + var o = { + undefined : 3, + NaN : 4, + Infinity : 5, + } + if (o) { + f(undefined, void 0); + f(NaN, 0/0); + f(Infinity, 1/0); + f(-Infinity, -(1/0)); + f(2 + 7 + undefined, 2 + 7 + void 0); + } + with (o) { + f(undefined, void 0); + f(NaN, 0/0); + f(Infinity, 1/0); + f(-Infinity, -(1/0)); + f(2 + 7 + undefined, 2 + 7 + void 0); + } + } + expect: { + var o = { + undefined : 3, + NaN : 4, + Infinity : 5 + } + if (o) { + f(void 0, void 0); + f(NaN, NaN); + f(1/0, 1/0); + f(-(1/0), -(1/0)); + f(NaN, NaN); + } + with (o) { + f(undefined, void 0); + f(NaN, 0/0); + f(Infinity, 1/0); + f(-Infinity, -(1/0)); + f(9 + undefined, 9 + void 0); + } + } +} + diff --git a/test/compress/properties.js b/test/compress/properties.js index 574c5142..cf52e631 100644 --- a/test/compress/properties.js +++ b/test/compress/properties.js @@ -12,7 +12,8 @@ keep_properties: { dot_properties: { options = { - properties: true + properties: true, + screw_ie8: false }; input: { a["foo"] = "bar"; diff --git a/test/compress/string-literal.js b/test/compress/string-literal.js new file mode 100644 index 00000000..8b93961c --- /dev/null +++ b/test/compress/string-literal.js @@ -0,0 +1,10 @@ +octal_escape_sequence: { + input: { + var boundaries = "\0\7\00\07\70\77\000\077\300\377"; + var border_check = "\400\700\0000\3000"; + } + expect: { + var boundaries = "\x00\x07\x00\x07\x38\x3f\x00\x3f\xc0\xff"; + var border_check = "\x20\x30\x38\x30\x00\x30\xc0\x30"; + } +} diff --git a/test/mocha/cli.js b/test/mocha/cli.js new file mode 100644 index 00000000..38b57cd7 --- /dev/null +++ b/test/mocha/cli.js @@ -0,0 +1,22 @@ +var assert = require("assert"); +var exec = require("child_process").exec; + +describe("bin/uglifyjs", function () { + it("should produce a functional build when using --self", function (done) { + this.timeout(5000); + + var uglifyjs = '"' + process.argv[0] + '" bin/uglifyjs'; + var command = uglifyjs + ' --self -cm --wrap WrappedUglifyJS'; + + exec(command, function (err, stdout) { + if (err) throw err; + + eval(stdout); + + assert.strictEqual(typeof WrappedUglifyJS, 'object'); + assert.strictEqual(true, WrappedUglifyJS.parse('foo;') instanceof WrappedUglifyJS.AST_Node); + + done(); + }); + }); +}); diff --git a/test/mocha/comment.js b/test/mocha/comment.js new file mode 100644 index 00000000..69cdb3d5 --- /dev/null +++ b/test/mocha/comment.js @@ -0,0 +1,50 @@ +var assert = require("assert"); +var uglify = require("../../"); + +describe("Comment", function() { + it("Should recognize eol of single line comments", function() { + var tests = [ + "//Some comment 1\n>", + "//Some comment 2\r>", + "//Some comment 3\r\n>", + "//Some comment 4\u2028>", + "//Some comment 5\u2029>" + ]; + + var fail = function(e) { + return e instanceof uglify.JS_Parse_Error && + e.message === "SyntaxError: Unexpected token: operator (>)" && + e.line === 2 && + e.col === 0; + } + + for (var i = 0; i < tests.length; i++) { + assert.throws(function() { + uglify.parse(tests[i], {fromString: true}) + }, fail, tests[i]); + } + }); + + it("Should update the position of a multiline comment correctly", function() { + var tests = [ + "/*Some comment 1\n\n\n*/\n>\n\n\n\n\n\n", + "/*Some comment 2\r\n\r\n\r\n*/\r\n>\n\n\n\n\n\n", + "/*Some comment 3\r\r\r*/\r>\n\n\n\n\n\n", + "/*Some comment 4\u2028\u2028\u2028*/\u2028>\n\n\n\n\n\n", + "/*Some comment 5\u2029\u2029\u2029*/\u2029>\n\n\n\n\n\n" + ]; + + var fail = function(e) { + return e instanceof uglify.JS_Parse_Error && + e.message === "SyntaxError: Unexpected token: operator (>)" && + e.line === 5 && + e.col === 0; + } + + for (var i = 0; i < tests.length; i++) { + assert.throws(function() { + uglify.parse(tests[i], {fromString: true}) + }, fail, tests[i]); + } + }); +}); diff --git a/test/mocha/line-endings.js b/test/mocha/line-endings.js index 3457dd70..ef46bccd 100644 --- a/test/mocha/line-endings.js +++ b/test/mocha/line-endings.js @@ -37,6 +37,10 @@ describe("line-endings", function() { "/\r/", "/\u2028/", "/\u2029/", + "/\\\n/", + "/\\\r/", + "/\\\u2028/", + "/\\\u2029/", "/someRandomTextLike[]()*AndThen\n/" ] var test = function(input) { @@ -46,7 +50,7 @@ describe("line-endings", function() { } var fail = function(e) { return e instanceof Uglify.JS_Parse_Error && - e.message === "Unexpected line terminator"; + e.message === "SyntaxError: Unexpected line terminator"; } for (var i = 0; i < inputs.length; i++) { assert.throws(test(inputs[i]), fail); diff --git a/test/mocha/minify-file-map.js b/test/mocha/minify-file-map.js new file mode 100644 index 00000000..aa42d25a --- /dev/null +++ b/test/mocha/minify-file-map.js @@ -0,0 +1,40 @@ +var Uglify = require('../../'); +var assert = require("assert"); + +describe("Input file as map", function() { + it("Should accept object", function() { + var jsMap = { + '/scripts/foo.js': 'var foo = {"x": 1, y: 2, \'z\': 3};' + }; + var result = Uglify.minify(jsMap, {fromString: true, outSourceMap: true}); + + var map = JSON.parse(result.map); + assert.strictEqual(result.code, 'var foo={x:1,y:2,z:3};'); + assert.deepEqual(map.sources, ['/scripts/foo.js']); + }); + + it("Should accept array of objects and strings", function() { + var jsSeq = [ + {'/scripts/foo.js': 'var foo = {"x": 1, y: 2, \'z\': 3};'}, + 'var bar = 15;' + ]; + var result = Uglify.minify(jsSeq, {fromString: true, outSourceMap: true}); + + var map = JSON.parse(result.map); + assert.strictEqual(result.code, 'var foo={x:1,y:2,z:3},bar=15;'); + assert.strictEqual(map.sources[0], '/scripts/foo.js'); + }); + + it("Should correctly include source", function() { + var jsSeq = [ + {'/scripts/foo.js': 'var foo = {"x": 1, y: 2, \'z\': 3};'}, + 'var bar = 15;' + ]; + var result = Uglify.minify(jsSeq, {fromString: true, outSourceMap: true, sourceMapIncludeSources: true}); + + var map = JSON.parse(result.map); + assert.strictEqual(result.code, 'var foo={x:1,y:2,z:3},bar=15;'); + assert.deepEqual(map.sourcesContent, ['var foo = {"x": 1, y: 2, \'z\': 3};', 'var bar = 15;']); + }); + +}); diff --git a/test/mocha/spidermonkey.js b/test/mocha/spidermonkey.js new file mode 100644 index 00000000..f507ad1f --- /dev/null +++ b/test/mocha/spidermonkey.js @@ -0,0 +1,33 @@ +var assert = require("assert"); +var exec = require("child_process").exec; + +describe("spidermonkey export/import sanity test", function() { + it("should produce a functional build when using --self with spidermonkey", function (done) { + this.timeout(20000); + + var uglifyjs = '"' + process.argv[0] + '" bin/uglifyjs'; + var command = uglifyjs + " --self -cm --wrap SpiderUglify --dump-spidermonkey-ast | " + + uglifyjs + " --spidermonkey -cm"; + + exec(command, function (err, stdout) { + if (err) throw err; + + eval(stdout); + assert.strictEqual(typeof SpiderUglify, "object"); + + var ast = SpiderUglify.parse("foo([true,,2+3]);"); + assert.strictEqual(true, ast instanceof SpiderUglify.AST_Node); + + ast.figure_out_scope(); + ast = SpiderUglify.Compressor({}).compress(ast); + assert.strictEqual(true, ast instanceof SpiderUglify.AST_Node); + + var stream = SpiderUglify.OutputStream({}); + ast.print(stream); + var code = stream.toString(); + assert.strictEqual(code, "foo([!0,,5]);"); + + done(); + }); + }); +}); diff --git a/test/mocha/string-literal.js b/test/mocha/string-literal.js index ea984213..fc4c4277 100644 --- a/test/mocha/string-literal.js +++ b/test/mocha/string-literal.js @@ -19,7 +19,7 @@ describe("String literals", function() { var error = function(e) { return e instanceof UglifyJS.JS_Parse_Error && - e.message === "Unterminated string constant"; + e.message === "SyntaxError: Unterminated string constant"; }; for (var input in inputs) { @@ -49,7 +49,7 @@ describe("String literals", function() { var error = function(e) { return e instanceof UglifyJS.JS_Parse_Error && - e.message === "Octal literals are not allowed in strict mode"; + e.message === "SyntaxError: Octal literals are not allowed in strict mode"; } for (var input in inputs) { diff --git a/tools/node.js b/tools/node.js index 39976371..2ee7df21 100644 --- a/tools/node.js +++ b/tools/node.js @@ -61,18 +61,25 @@ exports.minify = function(files, options) { if (options.spidermonkey) { toplevel = UglifyJS.AST_Node.from_mozilla_ast(files); } else { - if (typeof files == "string") - files = [ files ]; - files.forEach(function(file, i){ + function addFile(file, fileUrl) { var code = options.fromString ? file : fs.readFileSync(file, "utf8"); - sourcesContent[file] = code; + sourcesContent[fileUrl] = code; toplevel = UglifyJS.parse(code, { - filename: options.fromString ? i : file, + filename: fileUrl, toplevel: toplevel, bare_returns: options.parse ? options.parse.bare_returns : undefined }); + } + [].concat(files).forEach(function (files, i) { + if (typeof files === 'string') { + addFile(files, options.fromString ? i : files); + } else { + for (var fileUrl in files) { + addFile(files[fileUrl], fileUrl); + } + } }); } if (options.wrap) {