diff --git a/README.md b/README.md index d1e9ca09..9efa0c94 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,9 @@ There's also an [in-browser online demo](http://lisperator.net/uglifyjs/#demo) (for Firefox, Chrome and probably Safari). +Note: release versions of `uglify-js` only support ECMAScript 5 (ES5). If you wish to minify +ES2015+ (ES6+) code then please use the [harmony](#harmony) development branch. + Install ------- @@ -969,3 +972,20 @@ The `source_map_options` (optional) can contain the following properties: [codegen]: http://lisperator.net/uglifyjs/codegen [compressor]: http://lisperator.net/uglifyjs/compress [parser]: http://lisperator.net/uglifyjs/parser + +#### Harmony + +If you wish to use the experimental [harmony](https://github.com/mishoo/UglifyJS2/commits/harmony) +branch to minify ES2015+ (ES6+) code please use the following in your `package.json` file: + +``` +"uglify-js": "git+https://github.com/mishoo/UglifyJS2.git#harmony" +``` + +or to directly install the experimental harmony version of uglify: + +``` +npm install --save-dev uglify-js@github:mishoo/UglifyJS2#harmony +``` + +See [#448](https://github.com/mishoo/UglifyJS2/issues/448) for additional details. diff --git a/bin/uglifyjs b/bin/uglifyjs index d0c3abb2..367d66e2 100755 --- a/bin/uglifyjs +++ b/bin/uglifyjs @@ -364,7 +364,21 @@ async.eachLimit(files, 1, function (file, cb) { } catch(ex) { if (ex instanceof UglifyJS.JS_Parse_Error) { print_error("Parse error at " + file + ":" + ex.line + "," + ex.col); - print_error(ex.message); + var col = ex.col; + var line = code.split(/\r?\n/)[ex.line - (col ? 1 : 2)]; + if (line) { + if (col > 40) { + line = line.slice(col - 40); + col = 40; + } + if (col) { + print_error(line.slice(0, 80)); + print_error(line.slice(0, col).replace(/\S/g, " ") + "^"); + } else { + print_error(line.slice(-40)); + print_error(line.slice(-40).replace(/\S/g, " ") + "^"); + } + } print_error(ex.stack); process.exit(1); } @@ -390,7 +404,7 @@ async.eachLimit(files, 1, function (file, cb) { var compressor = COMPRESS && UglifyJS.Compressor(COMPRESS); } catch(ex) { if (ex instanceof UglifyJS.DefaultsError) { - print_error(ex.msg); + print_error(ex.message); print_error("Supported options:"); print_error(sys.inspect(ex.defs)); process.exit(1); diff --git a/lib/compress.js b/lib/compress.js index 6e083ed7..03c253a6 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -1376,9 +1376,7 @@ merge(Compressor.prototype, { && (comments = this.start.comments_before) && comments.length && /[@#]__PURE__/.test((last_comment = comments[comments.length - 1]).value)) { - compressor.warn("Dropping __PURE__ call [{file}:{line},{col}]", this.start); - last_comment.value = last_comment.value.replace(/[@#]__PURE__/g, ' '); - pure = true; + pure = last_comment; } return this.pure = pure; }); @@ -1677,8 +1675,7 @@ merge(Compressor.prototype, { line : def.name.start.line, col : def.name.start.col }; - if (def.value && def.value.has_side_effects(compressor)) { - def._unused_side_effects = true; + if (def.value && (def._unused_side_effects = def.value.drop_side_effect_free(compressor))) { compressor.warn("Side effects in initialization of unused variable {name} [{file}:{line},{col}]", w); return true; } @@ -1698,7 +1695,7 @@ merge(Compressor.prototype, { for (var i = 0; i < def.length;) { var x = def[i]; if (x._unused_side_effects) { - side_effects.push(x.value); + side_effects.push(x._unused_side_effects); def.splice(i, 1); } else { if (side_effects.length > 0) { @@ -1928,6 +1925,10 @@ merge(Compressor.prototype, { def(AST_This, return_null); def(AST_Call, function(compressor, first_in_statement){ if (!this.has_pure_annotation(compressor) && compressor.pure_funcs(this)) return this; + if (this.pure) { + compressor.warn("Dropping __PURE__ call [{file}:{line},{col}]", this.start); + this.pure.value = this.pure.value.replace(/[@#]__PURE__/g, ' '); + } var args = trim(this.args, compressor, first_in_statement); return args && AST_Seq.from_array(args); }); @@ -1979,8 +1980,17 @@ merge(Compressor.prototype, { case "typeof": if (this.expression instanceof AST_SymbolRef) return null; default: - if (first_in_statement && is_iife_call(this.expression)) return this; - return this.expression.drop_side_effect_free(compressor, first_in_statement); + var expression = this.expression.drop_side_effect_free(compressor, first_in_statement); + if (first_in_statement + && this instanceof AST_UnaryPrefix + && is_iife_call(expression)) { + if (expression === this.expression && this.operator.length === 1) return this; + return make_node(AST_UnaryPrefix, this, { + operator: this.operator.length === 1 ? this.operator : "!", + expression: expression + }); + } + return expression; } }); def(AST_SymbolRef, function() { @@ -2028,10 +2038,6 @@ merge(Compressor.prototype, { OPT(AST_SimpleStatement, function(self, compressor){ if (compressor.option("side_effects")) { var body = self.body; - if (!body.has_side_effects(compressor)) { - compressor.warn("Dropping side-effect-free statement [{file}:{line},{col}]", self.start); - return make_node(AST_EmptyStatement, self); - } var node = body.drop_side_effect_free(compressor, true); if (!node) { compressor.warn("Dropping side-effect-free statement [{file}:{line},{col}]", self.start); @@ -2687,17 +2693,10 @@ merge(Compressor.prototype, { } } if (!self.car.has_side_effects(compressor) - && !self.cdr.has_side_effects(compressor) && self.car.equivalent_to(self.cdr)) { return self.car; } } - if (self.cdr instanceof AST_UnaryPrefix - && self.cdr.operator == "void" - && !self.cdr.expression.has_side_effects(compressor)) { - self.cdr.expression = self.car; - return self.cdr; - } if (self.cdr instanceof AST_Undefined) { return make_node(AST_UnaryPrefix, self, { operator : "void", @@ -2726,8 +2725,20 @@ merge(Compressor.prototype, { }); OPT(AST_UnaryPrefix, function(self, compressor){ - self = self.lift_sequences(compressor); + var seq = self.lift_sequences(compressor); + if (seq !== self) { + return seq; + } var e = self.expression; + if (compressor.option("side_effects") && self.operator == "void") { + e = e.drop_side_effect_free(compressor); + if (e) { + self.expression = e; + return self; + } else { + return make_node(AST_Undefined, self); + } + } if (compressor.option("booleans") && compressor.in_boolean_context()) { switch (self.operator) { case "!": @@ -2744,13 +2755,10 @@ merge(Compressor.prototype, { // typeof always returns a non-empty string, thus it's // always true in booleans compressor.warn("Boolean expression always true [{file}:{line},{col}]", self.start); - if (self.expression.has_side_effects(compressor)) { - return make_node(AST_Seq, self, { - car: self.expression, - cdr: make_node(AST_True, self) - }); - } - return make_node(AST_True, self); + return make_node(AST_Seq, self, { + car: e, + cdr: make_node(AST_True, self) + }).optimize(compressor); } } return self.evaluate(compressor)[0]; @@ -2880,13 +2888,10 @@ merge(Compressor.prototype, { var rr = self.right.evaluate(compressor); if ((ll.length > 1 && !ll[1]) || (rr.length > 1 && !rr[1])) { compressor.warn("Boolean && always false [{file}:{line},{col}]", self.start); - if (self.left.has_side_effects(compressor)) { - return make_node(AST_Seq, self, { - car: self.left, - cdr: make_node(AST_False) - }).optimize(compressor); - } - return make_node(AST_False, self); + return make_node(AST_Seq, self, { + car: self.left, + cdr: make_node(AST_False) + }).optimize(compressor); } if (ll.length > 1 && ll[1]) { return rr[0]; @@ -2900,13 +2905,10 @@ merge(Compressor.prototype, { var rr = self.right.evaluate(compressor); if ((ll.length > 1 && ll[1]) || (rr.length > 1 && rr[1])) { compressor.warn("Boolean || always true [{file}:{line},{col}]", self.start); - if (self.left.has_side_effects(compressor)) { - return make_node(AST_Seq, self, { - car: self.left, - cdr: make_node(AST_True) - }).optimize(compressor); - } - return make_node(AST_True, self); + return make_node(AST_Seq, self, { + car: self.left, + cdr: make_node(AST_True, self) + }).optimize(compressor); } if (ll.length > 1 && !ll[1]) { return rr[0]; @@ -2918,10 +2920,19 @@ merge(Compressor.prototype, { case "+": var ll = self.left.evaluate(compressor); var rr = self.right.evaluate(compressor); - if ((ll.length > 1 && ll[0] instanceof AST_String && ll[1] && !self.right.has_side_effects(compressor)) || - (rr.length > 1 && rr[0] instanceof AST_String && rr[1] && !self.left.has_side_effects(compressor))) { + if (ll.length > 1 && ll[0] instanceof AST_String && ll[1]) { compressor.warn("+ in boolean context always true [{file}:{line},{col}]", self.start); - return make_node(AST_True, self); + return make_node(AST_Seq, self, { + car: self.right, + cdr: make_node(AST_True, self) + }).optimize(compressor); + } + if (rr.length > 1 && rr[0] instanceof AST_String && rr[1]) { + compressor.warn("+ in boolean context always true [{file}:{line},{col}]", self.start); + return make_node(AST_Seq, self, { + car: self.left, + cdr: make_node(AST_True, self) + }).optimize(compressor); } break; } @@ -3171,7 +3182,8 @@ merge(Compressor.prototype, { && alternative instanceof AST_Assign && consequent.operator == alternative.operator && consequent.left.equivalent_to(alternative.left) - && !consequent.left.has_side_effects(compressor) + && (!consequent.left.has_side_effects(compressor) + || !self.condition.has_side_effects(compressor)) ) { /* * Stuff like this: @@ -3189,25 +3201,19 @@ merge(Compressor.prototype, { }) }); } + // x ? y(a) : y(b) --> y(x ? a : b) if (consequent instanceof AST_Call && alternative.TYPE === consequent.TYPE - && consequent.args.length == alternative.args.length - && !consequent.expression.has_side_effects(compressor) - && consequent.expression.equivalent_to(alternative.expression)) { - if (consequent.args.length == 0) { - return make_node(AST_Seq, self, { - car: self.condition, - cdr: consequent - }); - } - if (consequent.args.length == 1) { - consequent.args[0] = make_node(AST_Conditional, self, { - condition: self.condition, - consequent: consequent.args[0], - alternative: alternative.args[0] - }); - return consequent; - } + && consequent.args.length == 1 + && alternative.args.length == 1 + && consequent.expression.equivalent_to(alternative.expression) + && !consequent.expression.has_side_effects(compressor)) { + consequent.args[0] = make_node(AST_Conditional, self, { + condition: self.condition, + consequent: consequent.args[0], + alternative: alternative.args[0] + }); + return consequent; } // x?y?z:a:a --> x&&y?z:a if (consequent instanceof AST_Conditional @@ -3222,16 +3228,12 @@ merge(Compressor.prototype, { alternative: alternative }); } - // y?1:1 --> 1 - if (consequent.is_constant() - && alternative.is_constant() - && consequent.equivalent_to(alternative)) { - var consequent_value = consequent.evaluate(compressor)[0]; - if (self.condition.has_side_effects(compressor)) { - return AST_Seq.from_array([self.condition, consequent_value]); - } else { - return consequent_value; - } + // x ? y : y --> x, y + if (consequent.equivalent_to(alternative)) { + return make_node(AST_Seq, self, { + car: self.condition, + cdr: consequent + }).optimize(compressor); } if (is_true(self.consequent)) { @@ -3390,8 +3392,12 @@ merge(Compressor.prototype, { }); function literals_in_boolean_context(self, compressor) { - if (compressor.option("booleans") && compressor.in_boolean_context() && !self.has_side_effects(compressor)) { - return make_node(AST_True, self); + if (compressor.option("booleans") && compressor.in_boolean_context()) { + var best = first_in_statement(compressor) ? best_of_statement : best_of; + return best(self, make_node(AST_Seq, self, { + car: self, + cdr: make_node(AST_True, self) + }).optimize(compressor)); } return self; }; diff --git a/lib/parse.js b/lib/parse.js index 37f06df7..9b198ccd 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -195,12 +195,11 @@ function JS_Parse_Error(message, filename, line, col, pos) { this.line = line; this.col = col; this.pos = pos; - this.stack = new Error().stack; -}; - -JS_Parse_Error.prototype.toString = function() { - return this.message + " (line: " + this.line + ", col: " + this.col + ", pos: " + this.pos + ")" + "\n\n" + this.stack; }; +JS_Parse_Error.prototype = Object.create(Error.prototype); +JS_Parse_Error.prototype.constructor = JS_Parse_Error; +JS_Parse_Error.prototype.name = "SyntaxError"; +configure_error_stack(JS_Parse_Error); function js_error(message, filename, line, col, pos) { throw new JS_Parse_Error(message, filename, line, col, pos); @@ -350,13 +349,13 @@ function tokenizer($TEXT, filename, html5_comments, shebang) { }); if (prefix) num = prefix + num; if (RE_OCT_NUMBER.test(num) && next_token.has_directive("use strict")) { - parse_error("SyntaxError: Legacy octal literals are not allowed in strict mode"); + parse_error("Legacy octal literals are not allowed in strict mode"); } var valid = parse_js_number(num); if (!isNaN(valid)) { return token("num", valid); } else { - parse_error("SyntaxError: Invalid syntax: " + num); + parse_error("Invalid syntax: " + num); } }; @@ -395,7 +394,7 @@ function tokenizer($TEXT, filename, html5_comments, shebang) { // Parse if (ch === "0") return "\0"; if (ch.length > 0 && next_token.has_directive("use strict")) - parse_error("SyntaxError: Legacy octal escape sequences are not allowed in strict mode"); + parse_error("Legacy octal escape sequences are not allowed in strict mode"); return String.fromCharCode(parseInt(ch, 8)); } @@ -404,18 +403,18 @@ function tokenizer($TEXT, filename, html5_comments, shebang) { for (; n > 0; --n) { var digit = parseInt(next(true), 16); if (isNaN(digit)) - parse_error("SyntaxError: Invalid hex-character pattern in string"); + parse_error("Invalid hex-character pattern in string"); num = (num << 4) | digit; } return num; }; - var read_string = with_eof_error("SyntaxError: Unterminated string constant", function(quote_char){ + var read_string = with_eof_error("Unterminated string constant", function(quote_char){ var quote = next(), ret = ""; for (;;) { var ch = next(true, true); if (ch == "\\") ch = read_escaped_char(true); - else if (NEWLINE_CHARS(ch)) parse_error("SyntaxError: Unterminated string constant"); + else if (NEWLINE_CHARS(ch)) parse_error("Unterminated string constant"); else if (ch == quote) break; ret += ch; } @@ -440,7 +439,7 @@ function tokenizer($TEXT, filename, html5_comments, shebang) { return next_token; }; - var skip_multiline_comment = with_eof_error("SyntaxError: Unterminated multiline comment", function(){ + 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).replace(/\r\n|\r|\u2028|\u2029/g, '\n'); @@ -460,9 +459,9 @@ function tokenizer($TEXT, filename, html5_comments, shebang) { else break; } else { - if (ch != "u") parse_error("SyntaxError: Expecting UnicodeEscapeSequence -- uXXXX"); + if (ch != "u") parse_error("Expecting UnicodeEscapeSequence -- uXXXX"); ch = read_escaped_char(); - if (!is_identifier_char(ch)) parse_error("SyntaxError: Unicode char: " + ch.charCodeAt(0) + " is not valid in identifier"); + if (!is_identifier_char(ch)) parse_error("Unicode char: " + ch.charCodeAt(0) + " is not valid in identifier"); name += ch; backslash = false; } @@ -474,10 +473,10 @@ function tokenizer($TEXT, filename, html5_comments, shebang) { return name; }; - var read_regexp = with_eof_error("SyntaxError: Unterminated regular expression", function(regexp){ + var read_regexp = with_eof_error("Unterminated regular expression", function(regexp){ var prev_backslash = false, ch, in_class = false; while ((ch = next(true))) if (NEWLINE_CHARS(ch)) { - parse_error("SyntaxError: Unexpected line terminator"); + parse_error("Unexpected line terminator"); } else if (prev_backslash) { regexp += "\\" + ch; prev_backslash = false; @@ -498,7 +497,7 @@ function tokenizer($TEXT, filename, html5_comments, shebang) { try { return token("regexp", new RegExp(regexp, mods)); } catch(e) { - parse_error("SyntaxError: " + e.message); + parse_error(e.message); } }); @@ -599,7 +598,7 @@ function tokenizer($TEXT, filename, html5_comments, shebang) { } break; } - parse_error("SyntaxError: Unexpected character '" + ch + "'"); + parse_error("Unexpected character '" + ch + "'"); }; next_token.context = function(nc) { @@ -756,14 +755,14 @@ function parse($TEXT, options) { function unexpected(token) { if (token == null) token = S.token; - token_error(token, "SyntaxError: Unexpected token: " + token.type + " (" + token.value + ")"); + token_error(token, "Unexpected token: " + token.type + " (" + token.value + ")"); }; function expect_token(type, val) { if (is(type, val)) { return next(); } - token_error(S.token, "SyntaxError: Unexpected token " + S.token.type + " «" + S.token.value + "»" + ", expected " + type + " «" + val + "»"); + token_error(S.token, "Unexpected token " + S.token.type + " «" + S.token.value + "»" + ", expected " + type + " «" + val + "»"); }; function expect(punc) { return expect_token("punc", punc); }; @@ -892,7 +891,7 @@ function parse($TEXT, options) { case "return": if (S.in_function == 0 && !options.bare_returns) - croak("SyntaxError: 'return' outside of function"); + croak("'return' outside of function"); return new AST_Return({ value: ( is("punc", ";") ? (next(), null) @@ -909,7 +908,7 @@ function parse($TEXT, options) { case "throw": if (S.token.nlb) - croak("SyntaxError: Illegal newline after 'throw'"); + croak("Illegal newline after 'throw'"); return new AST_Throw({ value: (tmp = expression(true), semicolon(), tmp) }); @@ -925,7 +924,7 @@ function parse($TEXT, options) { case "with": if (S.input.has_directive("use strict")) { - croak("SyntaxError: Strict mode may not include a with statement"); + croak("Strict mode may not include a with statement"); } return new AST_With({ expression : parenthesised(), @@ -945,7 +944,7 @@ function parse($TEXT, options) { // syntactically incorrect if it contains a // LabelledStatement that is enclosed by a // LabelledStatement with the same Identifier as label. - croak("SyntaxError: Label " + label.name + " defined twice"); + croak("Label " + label.name + " defined twice"); } expect(":"); S.labels.push(label); @@ -958,7 +957,7 @@ function parse($TEXT, options) { label.references.forEach(function(ref){ if (ref instanceof AST_Continue) { ref = ref.label.start; - croak("SyntaxError: Continue label `" + label.name + "` refers to non-IterationStatement.", + croak("Continue label `" + label.name + "` refers to non-IterationStatement.", ref.line, ref.col, ref.pos); } }); @@ -978,11 +977,11 @@ function parse($TEXT, options) { if (label != null) { ldef = find_if(function(l){ return l.name == label.name }, S.labels); if (!ldef) - croak("SyntaxError: Undefined label " + label.name); + croak("Undefined label " + label.name); label.thedef = ldef; } else if (S.in_loop == 0) - croak("SyntaxError: " + type.TYPE + " not inside a loop or switch"); + croak(type.TYPE + " not inside a loop or switch"); semicolon(); var stat = new type({ label: label }); if (ldef) ldef.references.push(stat); @@ -998,7 +997,7 @@ function parse($TEXT, options) { : expression(true, true); if (is("operator", "in")) { if (init instanceof AST_Var && init.definitions.length > 1) - croak("SyntaxError: Only one variable declaration allowed in for..in loop"); + croak("Only one variable declaration allowed in for..in loop"); next(); return for_in(init); } @@ -1148,7 +1147,7 @@ function parse($TEXT, options) { }); } if (!bcatch && !bfinally) - croak("SyntaxError: Missing catch/finally blocks"); + croak("Missing catch/finally blocks"); return new AST_Try({ body : body, bcatch : bcatch, @@ -1242,7 +1241,7 @@ function parse($TEXT, options) { break; case "operator": if (!is_identifier_string(tok.value)) { - croak("SyntaxError: Invalid getter/setter name: " + tok.value, + croak("Invalid getter/setter name: " + tok.value, tok.line, tok.col, tok.pos); } ret = _make_symbol(AST_SymbolRef); @@ -1397,7 +1396,7 @@ function parse($TEXT, options) { function as_symbol(type, noerror) { if (!is("name")) { - if (!noerror) croak("SyntaxError: Name expected"); + if (!noerror) croak("Name expected"); return null; } var sym = _make_symbol(type); @@ -1461,7 +1460,7 @@ function parse($TEXT, options) { function make_unary(ctor, op, expr) { if ((op == "++" || op == "--") && !is_assignable(expr)) - croak("SyntaxError: Invalid use of " + op + " operator"); + croak("Invalid use of " + op + " operator"); return new ctor({ operator: op, expression: expr }); }; @@ -1525,7 +1524,7 @@ function parse($TEXT, options) { end : prev() }); } - croak("SyntaxError: Invalid assignment"); + croak("Invalid assignment"); } return left; }; diff --git a/lib/utils.js b/lib/utils.js index a0571d65..46adfd4c 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -78,13 +78,28 @@ function repeat_string(str, i) { return d; }; +function configure_error_stack(fn) { + Object.defineProperty(fn.prototype, "stack", { + get: function() { + var err = new Error(this.message); + err.name = this.name; + try { + throw err; + } catch(e) { + return e.stack; + } + } + }); +} + function DefaultsError(msg, defs) { - Error.call(this, msg); - this.msg = msg; + this.message = msg; this.defs = defs; }; DefaultsError.prototype = Object.create(Error.prototype); DefaultsError.prototype.constructor = DefaultsError; +DefaultsError.prototype.name = "DefaultsError"; +configure_error_stack(DefaultsError); DefaultsError.croak = function(msg, defs) { throw new DefaultsError(msg, defs); diff --git a/package.json b/package.json index 2316283f..beec960c 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.7.5", + "version": "2.8.0", "engines": { "node": ">=0.8.0" }, diff --git a/test/compress/conditionals.js b/test/compress/conditionals.js index d88c5b90..074d2a65 100644 --- a/test/compress/conditionals.js +++ b/test/compress/conditionals.js @@ -50,7 +50,8 @@ ifs_3_should_warn: { conditionals : true, dead_code : true, evaluate : true, - booleans : true + booleans : true, + side_effects : true, }; input: { var x, y; @@ -135,16 +136,28 @@ ifs_6: { comparisons: true }; input: { - var x; + var x, y; if (!foo && !bar && !baz && !boo) { x = 10; } else { x = 20; } + if (y) { + x[foo] = 10; + } else { + x[foo] = 20; + } + if (foo) { + x[bar] = 10; + } else { + x[bar] = 20; + } } expect: { - var x; + var x, y; x = foo || bar || baz || boo ? 20 : 10; + x[foo] = y ? 10 : 20; + foo ? x[bar] = 10 : x[bar] = 20; } } @@ -159,10 +172,16 @@ cond_1: { } else { do_something(y); } + if (some_condition()) { + side_effects(x); + } else { + side_effects(y); + } } expect: { var do_something; do_something(some_condition() ? x : y); + some_condition() ? side_effects(x) : side_effects(y); } } @@ -213,10 +232,16 @@ cond_4: { } else { do_something(); } + if (some_condition()) { + side_effects(); + } else { + side_effects(); + } } expect: { var do_something; some_condition(), do_something(); + some_condition(), side_effects(); } } @@ -250,7 +275,8 @@ cond_5: { cond_7: { options = { conditionals: true, - evaluate : true + evaluate : true, + side_effects: true, }; input: { var x, y, z, a, b; @@ -714,6 +740,7 @@ issue_1154: { conditionals: true, evaluate : true, booleans : true, + side_effects: true, }; input: { function f1(x) { return x ? -1 : -1; } @@ -742,7 +769,7 @@ issue_1154: { function g2() { return g(), 2; } function g3() { return g(), -4; } function g4() { return g(), !1; } - function g5() { return g(), void 0; } + function g5() { return void g(); } function g6() { return g(), "number"; } } } @@ -750,7 +777,8 @@ issue_1154: { no_evaluate: { options = { conditionals: true, - evaluate : false + evaluate : false, + side_effects: true, } input: { function f(b) { diff --git a/test/compress/dead-code.js b/test/compress/dead-code.js index 2596e80e..cd96d02d 100644 --- a/test/compress/dead-code.js +++ b/test/compress/dead-code.js @@ -64,7 +64,8 @@ dead_code_constant_boolean_should_warn_more: { loops : true, booleans : true, conditionals : true, - evaluate : true + evaluate : true, + side_effects : true, }; input: { while (!((foo && bar) || (x + "0"))) { diff --git a/test/compress/evaluate.js b/test/compress/evaluate.js index 5cefadc8..6bed73fb 100644 --- a/test/compress/evaluate.js +++ b/test/compress/evaluate.js @@ -624,25 +624,35 @@ in_boolean_context: { options = { booleans: true, evaluate: true, + sequences: true, + side_effects: true, } input: { - !42; - !"foo"; - ![1, 2]; - !/foo/; - !b(42); - !b("foo"); - !b([1, 2]); - !b(/foo/); + console.log( + !42, + !"foo", + ![1, 2], + !/foo/, + !b(42), + !b("foo"), + !b([1, 2]), + !b(/foo/), + ![1, foo()], + ![1, foo(), 2] + ); } expect: { - !1; - !1; - !1; - !1; - !b(42); - !b("foo"); - !b([1, 2]); - !b(/foo/); + console.log( + !1, + !1, + !1, + !1, + !b(42), + !b("foo"), + !b([1, 2]), + !b(/foo/), + ![1, foo()], + (foo(), !1) + ); } } diff --git a/test/compress/issue-1261.js b/test/compress/issue-1261.js index dfbe2100..a872c578 100644 --- a/test/compress/issue-1261.js +++ b/test/compress/issue-1261.js @@ -116,3 +116,61 @@ pure_function_calls_toplevel: { "WARN: Dropping unused variable iife1 [test/compress/issue-1261.js:84,12]", ] } + +should_warn: { + options = { + booleans: true, + conditionals: true, + evaluate: true, + side_effects: true, + } + input: { + /* @__PURE__ */(function(){x})(), void/* @__PURE__ */(function(){y})(); + /* @__PURE__ */(function(){x})() || true ? foo() : bar(); + true || /* @__PURE__ */(function(){y})() ? foo() : bar(); + /* @__PURE__ */(function(){x})() && false ? foo() : bar(); + false && /* @__PURE__ */(function(){y})() ? foo() : bar(); + /* @__PURE__ */(function(){x})() + "foo" ? bar() : baz(); + "foo" + /* @__PURE__ */(function(){y})() ? bar() : baz(); + /* @__PURE__ */(function(){x})() ? foo() : foo(); + [/* @__PURE__ */(function(){x})()] ? foo() : bar(); + !{ foo: /* @__PURE__ */(function(){x})() } ? bar() : baz(); + } + expect: { + foo(); + foo(); + bar(); + bar(); + bar(); + bar(); + foo(); + foo(); + baz(); + } + expect_warnings: [ + "WARN: Dropping __PURE__ call [test/compress/issue-1261.js:128,61]", + "WARN: Dropping __PURE__ call [test/compress/issue-1261.js:128,23]", + "WARN: Dropping side-effect-free statement [test/compress/issue-1261.js:128,23]", + "WARN: Boolean || always true [test/compress/issue-1261.js:129,23]", + "WARN: Dropping __PURE__ call [test/compress/issue-1261.js:129,23]", + "WARN: Condition always true [test/compress/issue-1261.js:129,23]", + "WARN: Boolean || always true [test/compress/issue-1261.js:130,8]", + "WARN: Condition always true [test/compress/issue-1261.js:130,8]", + "WARN: Boolean && always false [test/compress/issue-1261.js:131,23]", + "WARN: Dropping __PURE__ call [test/compress/issue-1261.js:131,23]", + "WARN: Condition always false [test/compress/issue-1261.js:131,23]", + "WARN: Boolean && always false [test/compress/issue-1261.js:132,8]", + "WARN: Condition always false [test/compress/issue-1261.js:132,8]", + "WARN: + in boolean context always true [test/compress/issue-1261.js:133,23]", + "WARN: Dropping __PURE__ call [test/compress/issue-1261.js:133,23]", + "WARN: Condition always true [test/compress/issue-1261.js:133,23]", + "WARN: + in boolean context always true [test/compress/issue-1261.js:134,8]", + "WARN: Dropping __PURE__ call [test/compress/issue-1261.js:134,31]", + "WARN: Condition always true [test/compress/issue-1261.js:134,8]", + "WARN: Dropping __PURE__ call [test/compress/issue-1261.js:135,23]", + "WARN: Dropping __PURE__ call [test/compress/issue-1261.js:136,24]", + "WARN: Condition always true [test/compress/issue-1261.js:136,8]", + "WARN: Dropping __PURE__ call [test/compress/issue-1261.js:137,31]", + "WARN: Condition always false [test/compress/issue-1261.js:137,8]", + ] +} diff --git a/test/compress/issue-1275.js b/test/compress/issue-1275.js index e88e284c..51f696af 100644 --- a/test/compress/issue-1275.js +++ b/test/compress/issue-1275.js @@ -35,7 +35,7 @@ string_plus_optimization: { throw "nope"; } try { - console.log('0' + throwing_function() ? "yes" : "no"); + console.log((throwing_function(), "yes")); } catch (ex) { console.log(ex); } diff --git a/test/compress/typeof.js b/test/compress/typeof.js index fb391573..7bf8e5e3 100644 --- a/test/compress/typeof.js +++ b/test/compress/typeof.js @@ -29,6 +29,7 @@ typeof_in_boolean_context: { booleans : true, evaluate : true, conditionals : true, + side_effects : true, }; input: { function f1(x) { return typeof x ? "yes" : "no"; } @@ -36,12 +37,14 @@ typeof_in_boolean_context: { typeof 0 ? foo() : bar(); !typeof console.log(1); var a = !typeof console.log(2); + if (typeof (1 + foo())); } expect: { function f1(x) { return "yes"; } function f2() { return g(), "Yes"; } foo(); - !(console.log(1), !0); + console.log(1); var a = !(console.log(2), !0); + foo(); } } diff --git a/test/input/invalid/eof.js b/test/input/invalid/eof.js new file mode 100644 index 00000000..330d5023 --- /dev/null +++ b/test/input/invalid/eof.js @@ -0,0 +1 @@ +foo, bar( diff --git a/test/input/invalid/simple.js b/test/input/invalid/simple.js new file mode 100644 index 00000000..98a07d20 --- /dev/null +++ b/test/input/invalid/simple.js @@ -0,0 +1 @@ +function f(a{} diff --git a/test/input/invalid/tab.js b/test/input/invalid/tab.js new file mode 100644 index 00000000..f209b8ee --- /dev/null +++ b/test/input/invalid/tab.js @@ -0,0 +1 @@ + foo( xyz, 0abc); diff --git a/test/mocha/cli.js b/test/mocha/cli.js index 52c70935..c07eeee7 100644 --- a/test/mocha/cli.js +++ b/test/mocha/cli.js @@ -199,4 +199,43 @@ describe("bin/uglifyjs", function () { done(); }); }); + it("Should fail with invalid syntax", function(done) { + var command = uglifyjscmd + ' test/input/invalid/simple.js'; + + exec(command, function (err, stdout, stderr) { + assert.ok(err); + var lines = stderr.split(/\n/); + assert.strictEqual(lines[0], "Parse error at test/input/invalid/simple.js:1,12"); + assert.strictEqual(lines[1], "function f(a{}"); + assert.strictEqual(lines[2], " ^"); + assert.strictEqual(lines[3], "SyntaxError: Unexpected token punc «{», expected punc «,»"); + done(); + }); + }); + it("Should fail with correct marking of tabs", function(done) { + var command = uglifyjscmd + ' test/input/invalid/tab.js'; + + exec(command, function (err, stdout, stderr) { + assert.ok(err); + var lines = stderr.split(/\n/); + assert.strictEqual(lines[0], "Parse error at test/input/invalid/tab.js:1,12"); + assert.strictEqual(lines[1], "\t\tfoo(\txyz, 0abc);"); + assert.strictEqual(lines[2], "\t\t \t ^"); + assert.strictEqual(lines[3], "SyntaxError: Invalid syntax: 0abc"); + done(); + }); + }); + it("Should fail with correct marking at start of line", function(done) { + var command = uglifyjscmd + ' test/input/invalid/eof.js'; + + exec(command, function (err, stdout, stderr) { + assert.ok(err); + var lines = stderr.split(/\n/); + assert.strictEqual(lines[0], "Parse error at test/input/invalid/eof.js:2,0"); + assert.strictEqual(lines[1], "foo, bar("); + assert.strictEqual(lines[2], " ^"); + assert.strictEqual(lines[3], "SyntaxError: Unexpected token: eof (undefined)"); + done(); + }); + }); }); diff --git a/test/mocha/comment.js b/test/mocha/comment.js index 69cdb3d5..56470e0f 100644 --- a/test/mocha/comment.js +++ b/test/mocha/comment.js @@ -13,7 +13,7 @@ describe("Comment", function() { var fail = function(e) { return e instanceof uglify.JS_Parse_Error && - e.message === "SyntaxError: Unexpected token: operator (>)" && + e.message === "Unexpected token: operator (>)" && e.line === 2 && e.col === 0; } @@ -36,7 +36,7 @@ describe("Comment", function() { var fail = function(e) { return e instanceof uglify.JS_Parse_Error && - e.message === "SyntaxError: Unexpected token: operator (>)" && + e.message === "Unexpected token: operator (>)" && e.line === 5 && e.col === 0; } diff --git a/test/mocha/directives.js b/test/mocha/directives.js index 82594758..bc763ae0 100644 --- a/test/mocha/directives.js +++ b/test/mocha/directives.js @@ -168,7 +168,7 @@ describe("Directives", function() { throw new Error("Expected parser to fail"); } catch (e) { assert.strictEqual(e instanceof uglify.JS_Parse_Error, true); - assert.strictEqual(e.message, "SyntaxError: Unexpected token: punc (])"); + assert.strictEqual(e.message, "Unexpected token: punc (])"); } test_directive(tokenizer, tests[i]); diff --git a/test/mocha/getter-setter.js b/test/mocha/getter-setter.js index a292fa00..641a2026 100644 --- a/test/mocha/getter-setter.js +++ b/test/mocha/getter-setter.js @@ -71,7 +71,7 @@ describe("Getters and setters", function() { var fail = function(data) { return function (e) { return e instanceof UglifyJS.JS_Parse_Error && - e.message === "SyntaxError: Invalid getter/setter name: " + data.operator; + e.message === "Invalid getter/setter name: " + data.operator; }; }; diff --git a/test/mocha/line-endings.js b/test/mocha/line-endings.js index ef46bccd..10e2a1c5 100644 --- a/test/mocha/line-endings.js +++ b/test/mocha/line-endings.js @@ -50,7 +50,7 @@ describe("line-endings", function() { } var fail = function(e) { return e instanceof Uglify.JS_Parse_Error && - e.message === "SyntaxError: Unexpected line terminator"; + e.message === "Unexpected line terminator"; } for (var i = 0; i < inputs.length; i++) { assert.throws(test(inputs[i]), fail); diff --git a/test/mocha/minify.js b/test/mocha/minify.js index 1b830cb5..baac2a41 100644 --- a/test/mocha/minify.js +++ b/test/mocha/minify.js @@ -110,7 +110,7 @@ describe("minify", function() { inSourceMap: "inline", sourceMapInline: true }); - }, "multiple input and inline source map"); + }); }); it("Should fail with SpiderMonkey and inline source map", function() { assert.throws(function() { @@ -119,7 +119,7 @@ describe("minify", function() { sourceMapInline: true, spidermonkey: true }); - }, "SpiderMonkey and inline source map"); + }); }); }); @@ -154,6 +154,32 @@ describe("minify", function() { var code = result.code; assert.strictEqual(code, "// comment1 comment2\nbar();"); }); + it("should not drop #__PURE__ hint if function is retained", function() { + var result = Uglify.minify("var a = /*#__PURE__*/(function(){return 1})();", { + fromString: true, + output: { + comments: "all", + beautify: false, + } + }); + var code = result.code; + assert.strictEqual(code, "var a=/*#__PURE__*/function(){return 1}();"); + }) + }); + + describe("JS_Parse_Error", function() { + it("should throw syntax error", function() { + assert.throws(function() { + Uglify.minify("function f(a{}", { fromString: true }); + }, function(err) { + assert.ok(err instanceof Error); + assert.strictEqual(err.stack.split(/\n/)[0], "SyntaxError: Unexpected token punc «{», expected punc «,»"); + assert.strictEqual(err.filename, 0); + assert.strictEqual(err.line, 1); + assert.strictEqual(err.col, 12); + return true; + }); + }); }); }); diff --git a/test/mocha/number-literal.js b/test/mocha/number-literal.js index 8e05574a..e80a5313 100644 --- a/test/mocha/number-literal.js +++ b/test/mocha/number-literal.js @@ -15,7 +15,7 @@ describe("Number literals", function () { } var error = function(e) { return e instanceof uglify.JS_Parse_Error && - e.message === "SyntaxError: Legacy octal literals are not allowed in strict mode"; + e.message === "Legacy octal literals are not allowed in strict mode"; } for (var i = 0; i < inputs.length; i++) { assert.throws(test(inputs[i]), error, inputs[i]); diff --git a/test/mocha/string-literal.js b/test/mocha/string-literal.js index eb9e6f1c..6e337a24 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 === "SyntaxError: Unterminated string constant"; + e.message === "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 === "SyntaxError: Legacy octal escape sequences are not allowed in strict mode"; + e.message === "Legacy octal escape sequences are not allowed in strict mode"; } for (var input in inputs) { diff --git a/test/mocha/with.js b/test/mocha/with.js index 734e1e13..a74ef41a 100644 --- a/test/mocha/with.js +++ b/test/mocha/with.js @@ -9,7 +9,7 @@ describe("With", function() { } var error = function(e) { return e instanceof uglify.JS_Parse_Error && - e.message === "SyntaxError: Strict mode may not include a with statement"; + e.message === "Strict mode may not include a with statement"; } assert.throws(test, error); });