Merge branch 'master' into harmony

This commit is contained in:
Richard van Velzen
2015-12-26 17:55:38 +01:00
16 changed files with 609 additions and 124 deletions

View File

@@ -348,7 +348,7 @@ to set `true`; it's effectively a shortcut for `foo=true`).
- `drop_console` -- default `false`. Pass `true` to discard calls to - `drop_console` -- default `false`. Pass `true` to discard calls to
`console.*` functions. `console.*` functions.
- `keep_fargs` -- default `false`. Pass `true` to prevent the - `keep_fargs` -- default `true`. Prevents the
compressor from discarding unused function arguments. You need this compressor from discarding unused function arguments. You need this
for code which relies on `Function.length`. for code which relies on `Function.length`.
@@ -372,7 +372,6 @@ when this flag is on:
- `void 0` → `undefined` (if there is a variable named "undefined" in - `void 0` → `undefined` (if there is a variable named "undefined" in
scope; we do it because the variable name will be mangled, typically scope; we do it because the variable name will be mangled, typically
reduced to a single character) reduced to a single character)
- discards unused function arguments (affects `function.length`)
### Conditional compilation ### Conditional compilation
@@ -784,7 +783,7 @@ The `source_map_options` (optional) can contain the following properties:
came from. It can be simply a string in JSON, or a JSON object containing came from. It can be simply a string in JSON, or a JSON object containing
the original source map. the original source map.
[acorn]: https://github.com/marijnh/acorn [acorn]: https://github.com/ternjs/acorn
[source-map]: https://github.com/mozilla/source-map [source-map]: https://github.com/mozilla/source-map
[sm-spec]: https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit [sm-spec]: https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit
[codegen]: http://lisperator.net/uglifyjs/codegen [codegen]: http://lisperator.net/uglifyjs/codegen

View File

@@ -73,6 +73,7 @@ You need to pass an argument to this option to specify the name that your module
.describe("mangle-regex", "Only mangle property names matching the regex") .describe("mangle-regex", "Only mangle property names matching the regex")
.describe("name-cache", "File to hold mangled names mappings") .describe("name-cache", "File to hold mangled names mappings")
.describe("pure-funcs", "List of functions that can be safely removed if their return value is not used") .describe("pure-funcs", "List of functions that can be safely removed if their return value is not used")
.describe("dump-spidermonkey-ast", "Dump SpiderMonkey AST to stdout.")
.alias("p", "prefix") .alias("p", "prefix")
.alias("o", "output") .alias("o", "output")
@@ -117,6 +118,7 @@ You need to pass an argument to this option to specify the name that your module
.boolean("stats") .boolean("stats")
.boolean("acorn") .boolean("acorn")
.boolean("spidermonkey") .boolean("spidermonkey")
.boolean("dump-spidermonkey-ast")
.boolean("lint") .boolean("lint")
.boolean("V") .boolean("V")
.boolean("version") .boolean("version")
@@ -407,14 +409,17 @@ async.eachLimit(files, 1, function (file, cb) {
writeNameCache("props", cache); writeNameCache("props", cache);
})(); })();
var SCOPE_IS_NEEDED = COMPRESS || MANGLE || ARGS.lint
var TL_CACHE = readNameCache("vars"); var TL_CACHE = readNameCache("vars");
time_it("scope", function(){ if (SCOPE_IS_NEEDED) {
TOPLEVEL.figure_out_scope({ screw_ie8: ARGS.screw_ie8, cache: TL_CACHE }); time_it("scope", function(){
if (ARGS.lint) { TOPLEVEL.figure_out_scope({ screw_ie8: ARGS.screw_ie8, cache: TL_CACHE });
TOPLEVEL.scope_warnings(); if (ARGS.lint) {
} TOPLEVEL.scope_warnings();
}); }
});
}
if (COMPRESS) { if (COMPRESS) {
time_it("squeeze", function(){ time_it("squeeze", function(){
@@ -422,12 +427,14 @@ async.eachLimit(files, 1, function (file, cb) {
}); });
} }
time_it("scope", function(){ if (SCOPE_IS_NEEDED) {
TOPLEVEL.figure_out_scope({ screw_ie8: ARGS.screw_ie8, cache: TL_CACHE }); time_it("scope", function(){
if (MANGLE && !TL_CACHE) { TOPLEVEL.figure_out_scope({ screw_ie8: ARGS.screw_ie8, cache: TL_CACHE });
TOPLEVEL.compute_char_frequency(MANGLE); if (MANGLE && !TL_CACHE) {
} TOPLEVEL.compute_char_frequency(MANGLE);
}); }
});
}
if (MANGLE) time_it("mangle", function(){ if (MANGLE) time_it("mangle", function(){
MANGLE.cache = TL_CACHE; MANGLE.cache = TL_CACHE;
@@ -444,26 +451,30 @@ async.eachLimit(files, 1, function (file, cb) {
} }
} }
time_it("generate", function(){ if (ARGS.dump_spidermonkey_ast) {
TOPLEVEL.print(output); print(JSON.stringify(TOPLEVEL.to_mozilla_ast(), null, 2));
});
output = output.get();
if (SOURCE_MAP) {
fs.writeFileSync(ARGS.source_map, SOURCE_MAP, "utf8");
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) {
fs.writeFileSync(OUTPUT_FILE, output, "utf8");
} else { } else {
print(output); time_it("generate", function(){
TOPLEVEL.print(output);
});
output = output.get();
if (SOURCE_MAP) {
fs.writeFileSync(ARGS.source_map, SOURCE_MAP, "utf8");
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) {
fs.writeFileSync(OUTPUT_FILE, output, "utf8");
} else {
print(output);
}
} }
if (ARGS.stats) { if (ARGS.stats) {

View File

@@ -85,7 +85,7 @@ function DEFNODE(type, props, methods, base) {
return ctor; return ctor;
}; };
var AST_Token = DEFNODE("Token", "type value line col pos endline endcol endpos nlb comments_before file", { var AST_Token = DEFNODE("Token", "type value line col pos endline endcol endpos nlb comments_before file raw", {
}, null); }, null);
var AST_Node = DEFNODE("Node", "start end", { var AST_Node = DEFNODE("Node", "start end", {
@@ -1159,27 +1159,36 @@ var AST_True = DEFNODE("True", null, {
function TreeWalker(callback) { function TreeWalker(callback) {
this.visit = callback; this.visit = callback;
this.stack = []; this.stack = [];
this.directives = Object.create(null);
}; };
TreeWalker.prototype = { TreeWalker.prototype = {
_visit: function(node, descend) { _visit: function(node, descend) {
this.stack.push(node); this.push(node);
var ret = this.visit(node, descend ? function(){ var ret = this.visit(node, descend ? function(){
descend.call(node); descend.call(node);
} : noop); } : noop);
if (!ret && descend) { if (!ret && descend) {
descend.call(node); descend.call(node);
} }
this.stack.pop(); this.pop(node);
return ret; return ret;
}, },
parent: function(n) { parent: function(n) {
return this.stack[this.stack.length - 2 - (n || 0)]; return this.stack[this.stack.length - 2 - (n || 0)];
}, },
push: function (node) { push: function (node) {
if (node instanceof AST_Lambda) {
this.directives = Object.create(this.directives);
} else if (node instanceof AST_Directive) {
this.directives[node.value] = this.directives[node.value] ? "up" : true;
}
this.stack.push(node); this.stack.push(node);
}, },
pop: function() { pop: function(node) {
return this.stack.pop(); this.stack.pop();
if (node instanceof AST_Lambda) {
this.directives = Object.getPrototypeOf(this.directives);
}
}, },
self: function() { self: function() {
return this.stack[this.stack.length - 1]; return this.stack[this.stack.length - 1];
@@ -1192,7 +1201,16 @@ TreeWalker.prototype = {
} }
}, },
has_directive: function(type) { has_directive: function(type) {
return this.find_parent(AST_Scope).has_directive(type); var dir = this.directives[type];
if (dir) return dir;
var node = this.stack[this.stack.length - 1];
if (node instanceof AST_Scope && node.body) {
for (var i = 0; i < node.body.length; ++i) {
var st = node.body[i];
if (!(st instanceof AST_Directive)) break;
if (st.value == type) return true;
}
}
}, },
in_boolean_context: function() { in_boolean_context: function() {
var stack = this.stack; var stack = this.stack;

View File

@@ -199,7 +199,7 @@ merge(Compressor.prototype, {
}; };
function tighten_body(statements, compressor) { function tighten_body(statements, compressor) {
var CHANGED; var CHANGED, max_iter = 10;
do { do {
CHANGED = false; CHANGED = false;
if (compressor.option("angular")) { if (compressor.option("angular")) {
@@ -218,7 +218,7 @@ merge(Compressor.prototype, {
if (compressor.option("join_vars")) { if (compressor.option("join_vars")) {
statements = join_consecutive_vars(statements, compressor); statements = join_consecutive_vars(statements, compressor);
} }
} while (CHANGED); } while (CHANGED && max_iter-- > 0);
if (compressor.option("negate_iife")) { if (compressor.option("negate_iife")) {
negate_iifes(statements, compressor); negate_iifes(statements, compressor);
@@ -392,7 +392,12 @@ merge(Compressor.prototype, {
continue loop; continue loop;
} }
//--- //---
if (ret.length == 1 && in_lambda && ret[0] instanceof AST_SimpleStatement // XXX: what was the intention of this case?
// if sequences is not enabled, this can lead to an endless loop (issue #866).
// however, with sequences on this helps producing slightly better output for
// the example code.
if (compressor.option("sequences")
&& ret.length == 1 && in_lambda && ret[0] instanceof AST_SimpleStatement
&& (!stat.alternative || stat.alternative instanceof AST_SimpleStatement)) { && (!stat.alternative || stat.alternative instanceof AST_SimpleStatement)) {
CHANGED = true; CHANGED = true;
ret.push(make_node(AST_Return, ret[0], { ret.push(make_node(AST_Return, ret[0], {
@@ -729,6 +734,32 @@ merge(Compressor.prototype, {
return [ this ]; return [ this ];
} }
}); });
AST_Node.DEFMETHOD("is_constant", function(compressor){
// Accomodate when compress option evaluate=false
// as well as the common constant expressions !0 and !1
return this instanceof AST_Constant
|| (this instanceof AST_UnaryPrefix && this.operator == "!"
&& this.expression instanceof AST_Constant)
|| this.evaluate(compressor).length > 1;
});
// Obtain the constant value of an expression already known to be constant.
// Result only valid iff this.is_constant(compressor) is true.
AST_Node.DEFMETHOD("constant_value", function(compressor){
// Accomodate when option evaluate=false.
if (this instanceof AST_Constant) return this.value;
// Accomodate the common constant expressions !0 and !1 when option evaluate=false.
if (this instanceof AST_UnaryPrefix
&& this.operator == "!"
&& this.expression instanceof AST_Constant) {
return !this.expression.value;
}
var result = this.evaluate(compressor)
if (result.length > 1) {
return result[1];
}
// should never be reached
return undefined;
});
def(AST_Statement, function(){ def(AST_Statement, function(){
throw new Error(string_template("Cannot evaluate a statement [{file}:{line},{col}]", this.start)); throw new Error(string_template("Cannot evaluate a statement [{file}:{line},{col}]", this.start));
}); });
@@ -1006,7 +1037,7 @@ merge(Compressor.prototype, {
/* -----[ optimizers ]----- */ /* -----[ optimizers ]----- */
OPT(AST_Directive, function(self, compressor){ OPT(AST_Directive, function(self, compressor){
if (self.scope.has_directive(self.value) !== self.scope) { if (compressor.has_directive(self.value) === "up") {
return make_node(AST_EmptyStatement, self); return make_node(AST_EmptyStatement, self);
} }
return self; return self;
@@ -2472,32 +2503,48 @@ merge(Compressor.prototype, {
alternative: alternative alternative: alternative
}); });
} }
// x=y?1:1 --> x=1 // y?1:1 --> 1
if (consequent instanceof AST_Constant if (consequent.is_constant(compressor)
&& alternative instanceof AST_Constant && alternative.is_constant(compressor)
&& consequent.equivalent_to(alternative)) { && consequent.equivalent_to(alternative)) {
var consequent_value = consequent.constant_value();
if (self.condition.has_side_effects(compressor)) { if (self.condition.has_side_effects(compressor)) {
return AST_Seq.from_array([self.condition, make_node_from_constant(compressor, consequent.value, self)]); return AST_Seq.from_array([self.condition, make_node_from_constant(compressor, consequent_value, self)]);
} else { } else {
return make_node_from_constant(compressor, consequent.value, self); return make_node_from_constant(compressor, consequent_value, self);
} }
} }
// x=y?true:false --> x=!!y
if (consequent instanceof AST_True // y?true:false --> !!y
&& alternative instanceof AST_False) { if (is_true(consequent) && is_false(alternative)) {
self.condition = self.condition.negate(compressor); self.condition = self.condition.negate(compressor);
return make_node(AST_UnaryPrefix, self.condition, { return make_node(AST_UnaryPrefix, self.condition, {
operator: "!", operator: "!",
expression: self.condition expression: self.condition
}); });
} }
// x=y?false:true --> x=!y // y?false:true --> !y
if (consequent instanceof AST_False if (is_false(consequent) && is_true(alternative)) {
&& alternative instanceof AST_True) {
return self.condition.negate(compressor) return self.condition.negate(compressor)
} }
return self; return self;
// AST_True or !0
function is_true(node) {
return node instanceof AST_True
|| (node instanceof AST_UnaryPrefix
&& node.operator == "!"
&& node.expression instanceof AST_Constant
&& !node.expression.value);
}
// AST_False or !1
function is_false(node) {
return node instanceof AST_False
|| (node instanceof AST_UnaryPrefix
&& node.operator == "!"
&& node.expression instanceof AST_Constant
&& !!node.expression.value);
}
}); });
OPT(AST_Boolean, function(self, compressor){ OPT(AST_Boolean, function(self, compressor){
@@ -2569,4 +2616,11 @@ merge(Compressor.prototype, {
OPT(AST_Object, literals_in_boolean_context); OPT(AST_Object, literals_in_boolean_context);
OPT(AST_RegExp, literals_in_boolean_context); OPT(AST_RegExp, literals_in_boolean_context);
OPT(AST_Return, function(self, compressor){
if (self.value instanceof AST_Undefined) {
self.value = null;
}
return self;
});
})(); })();

View File

@@ -146,7 +146,14 @@
case "boolean": case "boolean":
return new (val ? AST_True : AST_False)(args); return new (val ? AST_True : AST_False)(args);
default: default:
args.value = val; var rx = M.regex;
if (rx && rx.pattern) {
// RegExpLiteral as per ESTree AST spec
args.value = new RegExp(rx.pattern, rx.flags).toString();
} else {
// support legacy RegExp
args.value = M.regex && M.raw ? M.raw : val;
}
return new AST_RegExp(args); return new AST_RegExp(args);
} }
}, },
@@ -334,6 +341,19 @@
}; };
}); });
def_to_moz(AST_RegExp, function To_Moz_RegExpLiteral(M) {
var value = M.value;
return {
type: "Literal",
value: value,
raw: value.toString(),
regex: {
pattern: value.source,
flags: value.toString().match(/[gimuy]*$/)[0]
}
};
});
def_to_moz(AST_Constant, function To_Moz_Literal(M) { def_to_moz(AST_Constant, function To_Moz_Literal(M) {
var value = M.value; var value = M.value;
if (typeof value === 'number' && (value < 0 || (value === 0 && 1 / value < 0))) { if (typeof value === 'number' && (value < 0 || (value === 0 && 1 / value < 0))) {
@@ -343,13 +363,15 @@
prefix: true, prefix: true,
argument: { argument: {
type: "Literal", type: "Literal",
value: -value value: -value,
raw: M.start.raw
} }
}; };
} }
return { return {
type: "Literal", type: "Literal",
value: value value: value,
raw: M.start.raw
}; };
}); });
@@ -369,6 +391,12 @@
/* -----[ tools ]----- */ /* -----[ tools ]----- */
function raw_token(moznode) {
if (moznode.type == "Literal") {
return moznode.raw != null ? moznode.raw : moznode.value + "";
}
}
function my_start_token(moznode) { function my_start_token(moznode) {
var loc = moznode.loc, start = loc && loc.start; var loc = moznode.loc, start = loc && loc.start;
var range = moznode.range; var range = moznode.range;
@@ -379,7 +407,8 @@
pos : range ? range[0] : moznode.start, pos : range ? range[0] : moznode.start,
endline : start && start.line, endline : start && start.line,
endcol : start && start.column, endcol : start && start.column,
endpos : range ? range[0] : moznode.start endpos : range ? range[0] : moznode.start,
raw : raw_token(moznode),
}); });
}; };
@@ -393,7 +422,8 @@
pos : range ? range[1] : moznode.end, pos : range ? range[1] : moznode.end,
endline : end && end.line, endline : end && end.line,
endcol : end && end.column, endcol : end && end.column,
endpos : range ? range[1] : moznode.end endpos : range ? range[1] : moznode.end,
raw : raw_token(moznode),
}); });
}; };

View File

@@ -95,7 +95,7 @@ function OutputStream(options) {
case "\f": return "\\f"; case "\f": return "\\f";
case "\n": return "\\n"; case "\n": return "\\n";
case "\r": return "\\r"; case "\r": return "\\r";
case "\x0B": return output.option("screw_ie8") ? "\\v" : "\\x0B"; case "\x0B": return options.screw_ie8 ? "\\v" : "\\x0B";
case "\u2028": return "\\u2028"; case "\u2028": return "\\u2028";
case "\u2029": return "\\u2029"; case "\u2029": return "\\u2029";
case '"': ++dq; return '"'; case '"': ++dq; return '"';
@@ -382,8 +382,13 @@ function OutputStream(options) {
nodetype.DEFMETHOD("_codegen", generator); nodetype.DEFMETHOD("_codegen", generator);
}; };
var use_asm = false;
AST_Node.DEFMETHOD("print", function(stream, force_parens){ AST_Node.DEFMETHOD("print", function(stream, force_parens){
var self = this, generator = self._codegen; var self = this, generator = self._codegen, prev_use_asm = use_asm;
if (self instanceof AST_Directive && self.value == "use asm") {
use_asm = true;
}
function doit() { function doit() {
self.add_comments(stream); self.add_comments(stream);
self.add_source_map(stream); self.add_source_map(stream);
@@ -396,6 +401,9 @@ function OutputStream(options) {
doit(); doit();
} }
stream.pop_node(); stream.pop_node();
if (self instanceof AST_Lambda) {
use_asm = prev_use_asm;
}
}); });
AST_Node.DEFMETHOD("print_to_string", function(options){ AST_Node.DEFMETHOD("print_to_string", function(options){
@@ -1311,10 +1319,8 @@ function OutputStream(options) {
output.print_string(self.getValue(), self.quote); output.print_string(self.getValue(), self.quote);
}); });
DEFPRINT(AST_Number, function(self, output){ DEFPRINT(AST_Number, function(self, output){
if (self.literal !== undefined if (use_asm && self.start.raw != null) {
&& +self.literal === self.value /* paranoid check */ output.print(self.start.raw);
&& self.scope && self.scope.has_directive('use asm')) {
output.print(self.literal);
} else { } else {
output.print(make_num(self.getValue())); output.print(make_num(self.getValue()));
} }

View File

@@ -190,6 +190,9 @@ function parse_js_number(num) {
return parseInt(num.substr(2), 2); return parseInt(num.substr(2), 2);
} else if (RE_DEC_NUMBER.test(num)) { } else if (RE_DEC_NUMBER.test(num)) {
return parseFloat(num); return parseFloat(num);
} else {
var val = parseFloat(num);
if (val == num) return val;
} }
}; };
@@ -291,6 +294,9 @@ function tokenizer($TEXT, filename, html5_comments, shebang) {
nlb : S.newline_before, nlb : S.newline_before,
file : filename file : filename
}; };
if (/^(?:num|string|regexp)$/i.test(type)) {
ret.raw = $TEXT.substring(ret.pos, ret.endpos);
}
if (!is_comment) { if (!is_comment) {
ret.comments_before = S.comments_before; ret.comments_before = S.comments_before;
S.comments_before = []; S.comments_before = [];
@@ -341,11 +347,7 @@ function tokenizer($TEXT, filename, html5_comments, shebang) {
if (prefix) num = prefix + num; if (prefix) num = prefix + num;
var valid = parse_js_number(num); var valid = parse_js_number(num);
if (!isNaN(valid)) { if (!isNaN(valid)) {
var tok = token("num", valid); return token("num", valid);
if (num.indexOf('.') >= 0) {
tok.literal = num;
}
return tok;
} else { } else {
parse_error("Invalid syntax: " + num); parse_error("Invalid syntax: " + num);
} }
@@ -406,6 +408,7 @@ function tokenizer($TEXT, filename, html5_comments, shebang) {
if (octal_len > 0) ch = String.fromCharCode(parseInt(ch, 8)); if (octal_len > 0) ch = String.fromCharCode(parseInt(ch, 8));
else ch = read_escaped_char(true); else ch = read_escaped_char(true);
} }
else if (ch == "\n") parse_error("Unterminated string constant");
else if (ch == quote) break; else if (ch == quote) break;
ret += ch; ret += ch;
} }
@@ -752,9 +755,9 @@ function parse($TEXT, options) {
); );
}; };
function semicolon() { function semicolon(optional) {
if (is("punc", ";")) next(); if (is("punc", ";")) next();
else if (!can_insert_semicolon()) unexpected(); else if (!optional && !can_insert_semicolon()) unexpected();
}; };
function parenthesised() { function parenthesised() {
@@ -843,7 +846,7 @@ function parse($TEXT, options) {
case "do": case "do":
return new AST_Do({ return new AST_Do({
body : in_loop(statement), body : in_loop(statement),
condition : (expect_token("keyword", "while"), tmp = parenthesised(), semicolon(), tmp) condition : (expect_token("keyword", "while"), tmp = parenthesised(), semicolon(true), tmp)
}); });
case "while": case "while":
@@ -1323,7 +1326,7 @@ function parse($TEXT, options) {
ret = _make_symbol(AST_SymbolRef); ret = _make_symbol(AST_SymbolRef);
break; break;
case "num": case "num":
ret = new AST_Number({ start: tok, end: tok, value: tok.value, literal: tok.literal }); ret = new AST_Number({ start: tok, end: tok, value: tok.value });
break; break;
case "string": case "string":
ret = new AST_String({ ret = new AST_String({

View File

@@ -98,6 +98,7 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){
// pass 1: setup scope chaining and handle definitions // pass 1: setup scope chaining and handle definitions
var self = this; var self = this;
var scope = self.parent_scope = null; var scope = self.parent_scope = null;
var labels = new Dictionary();
var defun = null; var defun = null;
var nesting = 0; var nesting = 0;
var in_destructuring = null; var in_destructuring = null;
@@ -121,20 +122,24 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){
node.init_scope_vars(nesting); node.init_scope_vars(nesting);
var save_scope = node.parent_scope = scope; var save_scope = node.parent_scope = scope;
var save_defun = defun; var save_defun = defun;
var save_labels = labels;
defun = scope = node; defun = scope = node;
labels = new Dictionary();
++nesting; descend(); --nesting; ++nesting; descend(); --nesting;
scope = save_scope; scope = save_scope;
defun = save_defun; defun = save_defun;
labels = save_labels;
return true; // don't descend again in TreeWalker return true; // don't descend again in TreeWalker
} }
if (node instanceof AST_Directive) { if (node instanceof AST_LabeledStatement) {
node.scope = scope; var l = node.label;
push_uniq(scope.directives, node.value); if (labels.has(l.name)) {
return true; throw new Error(string_template("Label {name} defined twice", l));
} }
if (node instanceof AST_Number) { labels.set(l.name, l);
node.scope = scope; descend();
return true; labels.del(l.name);
return true; // no descend again
} }
if (node instanceof AST_With) { if (node instanceof AST_With) {
for (var s = scope; s; s = s.parent_scope) for (var s = scope; s; s = s.parent_scope)
@@ -148,6 +153,10 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){
node.object_destructuring_arg = !!in_destructuring; node.object_destructuring_arg = !!in_destructuring;
defun.def_variable(node); defun.def_variable(node);
} }
if (node instanceof AST_Label) {
node.thedef = node;
node.references = [];
}
if (node instanceof AST_SymbolLambda) { if (node instanceof AST_SymbolLambda) {
defun.def_function(node); defun.def_function(node);
} }
@@ -178,6 +187,15 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){
(options.screw_ie8 ? scope : defun) (options.screw_ie8 ? scope : defun)
.def_variable(node); .def_variable(node);
} }
else 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); self.walk(tw);
@@ -200,6 +218,10 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){
cls = prev_cls; cls = prev_cls;
return true; return true;
} }
if (node instanceof AST_LoopControl && node.label) {
node.label.thedef.references.push(node);
return true;
}
if (node instanceof AST_SymbolRef) { if (node instanceof AST_SymbolRef) {
var name = node.name; var name = node.name;
var sym = node.scope.find_variable(name); var sym = node.scope.find_variable(name);
@@ -236,7 +258,6 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){
}); });
AST_Scope.DEFMETHOD("init_scope_vars", function(nesting){ AST_Scope.DEFMETHOD("init_scope_vars", function(nesting){
this.directives = []; // contains the directives defined in this scope, i.e. "use strict"
this.variables = new Dictionary(); // map name to AST_SymbolVar (variables defined in this scope; includes functions) this.variables = new Dictionary(); // map name to AST_SymbolVar (variables defined in this scope; includes functions)
this.functions = new Dictionary(); // map name to AST_SymbolDefun (functions defined in this scope) this.functions = new Dictionary(); // map name to AST_SymbolDefun (functions defined in this scope)
this.uses_with = false; // will be set to true if this or some nested scope uses the `with` statement this.uses_with = false; // will be set to true if this or some nested scope uses the `with` statement
@@ -247,10 +268,6 @@ AST_Scope.DEFMETHOD("init_scope_vars", function(nesting){
this.nesting = nesting; // the nesting level of this scope (0 means toplevel) this.nesting = nesting; // the nesting level of this scope (0 means toplevel)
}); });
AST_Scope.DEFMETHOD("strict", function(){
return this.has_directive("use strict");
});
AST_Lambda.DEFMETHOD("init_scope_vars", function(){ AST_Lambda.DEFMETHOD("init_scope_vars", function(){
AST_Scope.prototype.init_scope_vars.apply(this, arguments); AST_Scope.prototype.init_scope_vars.apply(this, arguments);
this.uses_arguments = false; this.uses_arguments = false;
@@ -274,11 +291,6 @@ AST_Scope.DEFMETHOD("find_variable", function(name){
|| (this.parent_scope && this.parent_scope.find_variable(name)); || (this.parent_scope && this.parent_scope.find_variable(name));
}); });
AST_Scope.DEFMETHOD("has_directive", function(value){
return this.parent_scope && this.parent_scope.has_directive(value)
|| (this.directives.indexOf(value) >= 0 ? this : null);
});
AST_Scope.DEFMETHOD("def_function", function(symbol){ AST_Scope.DEFMETHOD("def_function", function(symbol){
this.functions.set(symbol.name, this.def_variable(symbol)); this.functions.set(symbol.name, this.def_variable(symbol));
}); });

View File

@@ -70,7 +70,7 @@ TreeTransformer.prototype = new TreeWalker;
if (y !== undefined) x = y; if (y !== undefined) x = y;
} }
} }
tw.pop(); tw.pop(this);
return x; return x;
}); });
}; };

View File

@@ -4,7 +4,7 @@
"homepage": "http://lisperator.net/uglifyjs", "homepage": "http://lisperator.net/uglifyjs",
"author": "Mihai Bazon <mihai.bazon@gmail.com> (http://lisperator.net/)", "author": "Mihai Bazon <mihai.bazon@gmail.com> (http://lisperator.net/)",
"license": "BSD-2-Clause", "license": "BSD-2-Clause",
"version": "2.5.0", "version": "2.6.1",
"engines": { "engines": {
"node": ">=0.8.0" "node": ">=0.8.0"
}, },

View File

@@ -332,53 +332,247 @@ cond_7_1: {
cond_8: { cond_8: {
options = { options = {
conditionals: true, conditionals: true,
evaluate : true evaluate : true,
booleans : false
}; };
input: { input: {
var a; var a;
// compress these // compress these
a = condition ? true : false; a = condition ? true : false;
a = !condition ? true : false; a = !condition ? true : false;
a = condition() ? true : false; a = condition() ? true : false;
a = condition ? !0 : !1;
a = !condition ? !null : !2;
a = condition() ? !0 : !-3.5;
if (condition) { if (condition) {
a = true; a = true;
} else { } else {
a = false; a = false;
} }
if (condition) {
a = !0;
} else {
a = !1;
}
a = condition ? false : true; a = condition ? false : true;
a = !condition ? false : true; a = !condition ? false : true;
a = condition() ? false : true; a = condition() ? false : true;
a = condition ? !3 : !0;
a = !condition ? !2 : !0;
a = condition() ? !1 : !0;
if (condition) { if (condition) {
a = false; a = false;
} else { } else {
a = true; a = true;
} }
if (condition) {
a = !1;
} else {
a = !0;
}
// don't compress these // don't compress these
a = condition ? 1 : false; a = condition ? 1 : false;
a = !condition ? true : 0; a = !condition ? true : 0;
a = condition ? 1 : 0; a = condition ? 1 : 0;
} }
expect: { expect: {
var a; var a;
a = !!condition; a = !!condition;
a = !condition; a = !condition;
a = !!condition(); a = !!condition();
a = !!condition; a = !!condition;
a = !condition;
a = !!condition();
a = !!condition;
a = !!condition;
a = !condition; a = !condition;
a = !!condition; a = !!condition;
a = !condition(); a = !condition();
a = !condition; a = !condition;
a = !!condition;
a = !condition();
a = !condition;
a = !condition;
a = condition ? 1 : false;
a = condition ? 0 : true;
a = condition ? 1 : 0;
}
}
cond_8b: {
options = {
conditionals: true,
evaluate : true,
booleans : true
};
input: {
var a;
// compress these
a = condition ? true : false;
a = !condition ? true : false;
a = condition() ? true : false;
a = condition ? !0 : !1;
a = !condition ? !null : !2;
a = condition() ? !0 : !-3.5;
if (condition) {
a = true;
} else {
a = false;
}
if (condition) {
a = !0;
} else {
a = !1;
}
a = condition ? false : true;
a = !condition ? false : true;
a = condition() ? false : true;
a = condition ? !3 : !0;
a = !condition ? !2 : !0;
a = condition() ? !1 : !0;
if (condition) {
a = false;
} else {
a = true;
}
if (condition) {
a = !1;
} else {
a = !0;
}
a = condition ? 1 : false;
a = !condition ? true : 0;
a = condition ? 1 : 0;
}
expect: {
var a;
a = !!condition;
a = !condition;
a = !!condition();
a = !!condition;
a = !condition;
a = !!condition();
a = !!condition;
a = !!condition;
a = !condition;
a = !!condition;
a = !condition();
a = !condition;
a = !!condition;
a = !condition();
a = !condition;
a = !condition;
a = condition ? 1 : !1;
a = condition ? 0 : !0;
a = condition ? 1 : 0;
}
}
cond_8c: {
options = {
conditionals: true,
evaluate : false,
booleans : false
};
input: {
var a;
// compress these
a = condition ? true : false;
a = !condition ? true : false;
a = condition() ? true : false;
a = condition ? !0 : !1;
a = !condition ? !null : !2;
a = condition() ? !0 : !-3.5;
if (condition) {
a = true;
} else {
a = false;
}
if (condition) {
a = !0;
} else {
a = !1;
}
a = condition ? false : true;
a = !condition ? false : true;
a = condition() ? false : true;
a = condition ? !3 : !0;
a = !condition ? !2 : !0;
a = condition() ? !1 : !0;
if (condition) {
a = false;
} else {
a = true;
}
if (condition) {
a = !1;
} else {
a = !0;
}
a = condition ? 1 : false;
a = !condition ? true : 0;
a = condition ? 1 : 0;
}
expect: {
var a;
a = !!condition;
a = !condition;
a = !!condition();
a = !!condition;
a = !condition;
a = condition() ? !0 : !-3.5;
a = !!condition;
a = !!condition;
a = !condition;
a = !!condition;
a = !condition();
a = !condition;
a = !!condition;
a = !condition();
a = !condition;
a = !condition;
a = condition ? 1 : false; a = condition ? 1 : false;
a = condition ? 0 : true; a = condition ? 0 : true;
a = condition ? 1 : 0; a = condition ? 1 : 0;

View File

@@ -121,3 +121,27 @@ drop_if_else_break_4: {
for (; bar() && (x(), y(), foo());) baz(), z(), k(); for (; bar() && (x(), y(), foo());) baz(), z(), k();
} }
} }
parse_do_while_with_semicolon: {
options = { loops: false };
input: {
do {
x();
} while (false);y()
}
expect: {
do x(); while (false);y();
}
}
parse_do_while_without_semicolon: {
options = { loops: false };
input: {
do {
x();
} while (false)y()
}
expect: {
do x(); while (false);y();
}
}

View File

@@ -0,0 +1,124 @@
return_undefined: {
options = {
sequences : false,
if_return : true,
evaluate : true,
dead_code : true,
conditionals : true,
comparisons : true,
booleans : true,
unused : true,
side_effects : true,
properties : true,
drop_debugger : true,
loops : true,
hoist_funs : true,
keep_fargs : true,
keep_fnames : false,
hoist_vars : true,
join_vars : true,
cascade : true,
negate_iife : true
};
input: {
function f0() {
}
function f1() {
return undefined;
}
function f2() {
return void 0;
}
function f3() {
return void 123;
}
function f4() {
return;
}
function f5(a, b) {
console.log(a, b);
baz(a);
return;
}
function f6(a, b) {
console.log(a, b);
if (a) {
foo(b);
baz(a);
return a + b;
}
return undefined;
}
function f7(a, b) {
console.log(a, b);
if (a) {
foo(b);
baz(a);
return void 0;
}
return a + b;
}
function f8(a, b) {
foo(a);
bar(b);
return void 0;
}
function f9(a, b) {
foo(a);
bar(b);
return undefined;
}
function f10() {
return false;
}
function f11() {
return null;
}
function f12() {
return 0;
}
}
expect: {
function f0() {}
function f1() {}
function f2() {}
function f3() {}
function f4() {}
function f5(a, b) {
console.log(a, b);
baz(a);
}
function f6(a, b) {
console.log(a, b);
if (a) {
foo(b);
baz(a);
return a + b;
}
}
function f7(a, b) {
console.log(a, b);
if (!a)
return a + b;
foo(b);
baz(a);
}
function f8(a, b) {
foo(a);
bar(b);
}
function f9(a, b) {
foo(a);
bar(b);
}
function f10() {
return !1;
}
function f11() {
return null;
}
function f12() {
return 0;
}
}
}

View File

@@ -0,0 +1,18 @@
do_screw: {
options = { screw_ie8: true };
beautify = {
screw_ie8: true,
ascii_only: true
};
input: f("\v");
expect_exact: 'f("\\v");';
}
dont_screw: {
options = { screw_ie8: false };
beautify = { screw_ie8: false, ascii_only: true };
input: f("\v");
expect_exact: 'f("\\x0B");';
}

View File

@@ -4,7 +4,6 @@ var U = require("../tools/node");
var path = require("path"); var path = require("path");
var fs = require("fs"); var fs = require("fs");
var assert = require("assert"); var assert = require("assert");
var sys = require("util");
var tests_dir = path.dirname(module.filename); var tests_dir = path.dirname(module.filename);
var failures = 0; var failures = 0;
@@ -12,8 +11,8 @@ var failed_files = {};
run_compress_tests(); run_compress_tests();
if (failures) { if (failures) {
sys.error("\n!!! Failed " + failures + " test cases."); console.error("\n!!! Failed " + failures + " test cases.");
sys.error("!!! " + Object.keys(failed_files).join(", ")); console.error("!!! " + Object.keys(failed_files).join(", "));
process.exit(1); process.exit(1);
} }
@@ -91,14 +90,15 @@ function run_compress_tests() {
warnings: false warnings: false
}); });
var cmp = new U.Compressor(options, true); var cmp = new U.Compressor(options, true);
var output_options = test.beautify || {};
var expect; var expect;
if (test.expect) { if (test.expect) {
expect = make_code(as_toplevel(test.expect), false); expect = make_code(as_toplevel(test.expect), output_options);
} else { } else {
expect = test.expect_exact; expect = test.expect_exact;
} }
var input = as_toplevel(test.input); var input = as_toplevel(test.input);
var input_code = make_code(test.input); var input_code = make_code(test.input, { beautify: true });
if (test.mangle_props) { if (test.mangle_props) {
input = U.mangle_properties(input, test.mangle_props); input = U.mangle_properties(input, test.mangle_props);
} }
@@ -107,7 +107,7 @@ function run_compress_tests() {
if (test.mangle) { if (test.mangle) {
output.mangle_names(test.mangle); output.mangle_names(test.mangle);
} }
output = make_code(output, false); output = make_code(output, output_options);
if (expect != output) { if (expect != output) {
log("!!! failed\n---INPUT---\n{input}\n---OUTPUT---\n{output}\n---EXPECTED---\n{expected}\n\n", { log("!!! failed\n---INPUT---\n{input}\n---OUTPUT---\n{output}\n---EXPECTED---\n{expected}\n\n", {
input: input_code, input: input_code,
@@ -151,7 +151,7 @@ function parse_test(file) {
file: file, file: file,
line: node.start.line, line: node.start.line,
col: node.start.col, col: node.start.col,
code: make_code(node, false) code: make_code(node, { beautify: false })
})); }));
} }
@@ -198,15 +198,15 @@ function parse_test(file) {
}; };
} }
function make_code(ast, beautify) { function make_code(ast, options) {
if (arguments.length == 1) beautify = true; options.inline_script = true;
var stream = U.OutputStream({ beautify: beautify, inline_script: true }); var stream = U.OutputStream(options);
ast.print(stream); ast.print(stream);
return stream.get(); return stream.get();
} }
function evaluate(code) { function evaluate(code) {
if (code instanceof U.AST_Node) if (code instanceof U.AST_Node)
code = make_code(code); code = make_code(code, { beautify: true });
return new Function("return(" + code + ")")(); return new Function("return(" + code + ")")();
} }

View File

@@ -45,7 +45,6 @@ exports.minify = function(files, options) {
UglifyJS.base54.reset(); UglifyJS.base54.reset();
// 1. parse // 1. parse
var haveScope = false;
var toplevel = null, var toplevel = null,
sourcesContent = {}; sourcesContent = {};
@@ -74,7 +73,6 @@ exports.minify = function(files, options) {
var compress = { warnings: options.warnings }; var compress = { warnings: options.warnings };
UglifyJS.merge(compress, options.compress); UglifyJS.merge(compress, options.compress);
toplevel.figure_out_scope(); toplevel.figure_out_scope();
haveScope = true;
var sq = UglifyJS.Compressor(compress); var sq = UglifyJS.Compressor(compress);
toplevel = toplevel.transform(sq); toplevel = toplevel.transform(sq);
} }
@@ -82,17 +80,11 @@ exports.minify = function(files, options) {
// 3. mangle // 3. mangle
if (options.mangle) { if (options.mangle) {
toplevel.figure_out_scope(options.mangle); toplevel.figure_out_scope(options.mangle);
haveScope = true;
toplevel.compute_char_frequency(options.mangle); toplevel.compute_char_frequency(options.mangle);
toplevel.mangle_names(options.mangle); toplevel.mangle_names(options.mangle);
} }
// 4. scope (if needed) // 4. output
if (!haveScope) {
toplevel.figure_out_scope();
}
// 5. output
var inMap = options.inSourceMap; var inMap = options.inSourceMap;
var output = {}; var output = {};
if (typeof options.inSourceMap == "string") { if (typeof options.inSourceMap == "string") {