Compare commits

...

26 Commits

Author SHA1 Message Date
Mihai Bazon
5538ec7bd8 v2.4.16 2014-12-09 15:21:44 +02:00
Mihai Bazon
f101d6429b Merge pull request #546 from jacobk/patch-1
Use uglify source map token names if missing
2014-12-04 14:07:08 +02:00
Mihai Bazon
f36a1eaa8b Add option to allow return outside of functions.
Close #288
2014-10-20 18:12:13 +03:00
Mihai Bazon
a64bdda9ae Document keep_fargs. Close #557 2014-09-28 12:36:36 +03:00
Mihai Bazon
01d19b4b52 Referencing a global is assumed to have side effects.
Close #550
2014-09-28 11:18:25 +03:00
Mihai Bazon
f0c1a01bc2 Merge pull request #549 from Arnavion/unreferenced-catch-symbol
Don't warn for an unreferenced exception symbol in a catch block.
2014-09-13 09:29:34 +03:00
Arnavion
7be680d3f8 Don't warn for an unreferenced exception symbol in a catch block. 2014-09-12 21:01:19 -07:00
Mihai Bazon
57dab1e1db Merge pull request #541 from TalAter/conditional-improvements
Conditional assignment of equivalent constants compressed  ( x=y?1:1 --> x=1 )
2014-09-09 18:45:12 +03:00
Jacob Kristhammar
21b3c890a1 Use uglify source map token names if missing 2014-09-09 13:02:50 +02:00
Tal Ater
fb0ec720a4 Compress conditions that have side effects using sequences 2014-09-04 02:57:49 +03:00
Tal Ater
7971ed33d1 Added a test for else if 2014-09-03 01:35:30 +03:00
Tal Ater
885835a655 Compress conditional assignments where all possible outcomes are equivalant and condition has no side effects 2014-09-02 23:30:25 +03:00
Mihai Bazon
4c64554808 Turn foo.new into foo["new"] when not --screw-ie8. Fix #534 2014-08-26 10:11:01 +03:00
Mihai Bazon
548beeb6b1 Prevent error for Function(""). Close #538 2014-08-20 09:16:34 +03:00
Mihai Bazon
e3066f9577 Merge pull request #529 from RReverser/master
Added example for usage with SpiderMonkey AST
2014-08-04 23:05:29 +03:00
Ingvar Stepanyan
e391367488 Added example for usage with SpiderMonkey AST. 2014-08-04 20:48:14 +03:00
Mihai Bazon
18ddf2f7b5 Merge branch 'master' of https://github.com/RReverser/UglifyJS2 2014-08-04 09:01:19 +03:00
Ingvar Stepanyan
f8ee5a0785 Install newest NPM on oldest Node.js. 2014-08-03 21:44:59 +03:00
Ingvar Stepanyan
b467a3c877 Added generative testing for AST conversions. 2014-08-03 20:48:59 +03:00
Mihai Bazon
f2d48e9019 Merge branch 'patch-1' of https://github.com/gdw2/UglifyJS2 2014-08-03 11:08:39 +03:00
Ingvar Stepanyan
5e314bf3e9 SpiderMonkey Identifier nodes should contain mangled names. 2014-08-03 01:28:58 +03:00
Ingvar Stepanyan
05ba26c7c8 Small fixes for AST conversion. 2014-08-02 13:18:27 +03:00
Ingvar Stepanyan
87b72364a4 Fixes and improvements for UglifyJS->SM AST conversion.
* Explicitly forbidden multiple catch clauses as SM-specific feature.
* Simplified describing of UglifyJS->Mozilla AST conversion rules.
* Moved alias rules to single place.
* Removed usage of dynamic type bindings in generated code (speed-up).
2014-08-01 23:45:37 +03:00
Ingvar Stepanyan
0e3ff1f970 Improved UglifyJS<->SpiderMonkey AST conversions.
* Added directives recognition in SM AST.
* Moved semi-standard SM `Property` type to separate handler.
* Added `const` recognition from SM AST.
* Removed redundant `this`-as-identifier recognition.
* Removed redundant rules for abstract SM types.
* Described `CatchClause` using string syntax.
* Added support for semi-standard `range` tuple as location source.
* Added back-conversion support (to be improved).
2014-08-01 23:42:34 +03:00
gdw2
ec3e74d7f4 Added license 2014-07-28 13:49:44 -07:00
Mihai Bazon
62bda71c85 Fix parens for AST_Undefined
Do the same as for AST_Unary, since we output undefined as `void 0`.

Reported at https://github.com/mishoo/UglifyJS2/issues/338#issuecomment-48858341
2014-07-18 11:31:41 +03:00
14 changed files with 603 additions and 88 deletions

View File

@@ -1,4 +1,5 @@
language: node_js
before_install: "npm install -g npm"
node_js:
- "0.8"
- "0.10"

View File

@@ -256,6 +256,10 @@ to set `true`; it's effectively a shortcut for `foo=true`).
- `drop_console` -- default `false`. Pass `true` to discard calls to
`console.*` functions.
- `keep_fargs` -- default `false`. Pass `true` to prevent the
compressor from discarding unused function arguments. You need this
for code which relies on `Function.length`.
### The `unsafe` option
It enables some transformations that *might* break code logic in certain
@@ -400,6 +404,38 @@ Acorn is really fast (e.g. 250ms instead of 380ms on some 650K code), but
converting the SpiderMonkey tree that Acorn produces takes another 150ms so
in total it's a bit more than just using UglifyJS's own parser.
### Using UglifyJS to transform SpiderMonkey AST
Now you can use UglifyJS as any other intermediate tool for transforming
JavaScript ASTs in SpiderMonkey format.
Example:
```javascript
function uglify(ast, options, mangle) {
// Conversion from SpiderMonkey AST to internal format
var uAST = UglifyJS.AST_Node.from_mozilla_ast(ast);
// Compression
uAST.figure_out_scope();
uAST = uAST.transform(UglifyJS.Compressor(options));
// Mangling (optional)
if (mangle) {
uAST.figure_out_scope();
uAST.compute_char_frequency();
uAST.mangle_names();
}
// Back-conversion to SpiderMonkey AST
return uAST.to_mozilla_ast();
}
```
Check out
[original blog post](http://rreverser.com/using-mozilla-ast-with-uglifyjs/)
for details.
API Reference
-------------

View File

@@ -64,6 +64,7 @@ You need to pass an argument to this option to specify the name that your module
.describe("v", "Verbose")
.describe("V", "Print version number and exit.")
.describe("noerr", "Don't throw an error for unknown options in -c, -b or -m.")
.describe("bare-returns", "Allow return outside of functions. Useful when minifying CommonJS modules.")
.alias("p", "prefix")
.alias("o", "output")
@@ -100,6 +101,7 @@ You need to pass an argument to this option to specify the name that your module
.boolean("lint")
.boolean("V")
.boolean("noerr")
.boolean("bare-returns")
.wrap(80)
@@ -278,6 +280,7 @@ async.eachLimit(files, 1, function (file, cb) {
filename : file,
toplevel : TOPLEVEL,
expression : ARGS.expr,
bare_returns : ARGS.bare_returns,
});
} catch(ex) {
if (ex instanceof UglifyJS.JS_Parse_Error) {

View File

@@ -897,7 +897,9 @@ merge(Compressor.prototype, {
|| this.operator == "--"
|| this.expression.has_side_effects(compressor);
});
def(AST_SymbolRef, function(compressor){ return false });
def(AST_SymbolRef, function(compressor){
return this.global() && this.undeclared();
});
def(AST_Object, function(compressor){
for (var i = this.properties.length; --i >= 0;)
if (this.properties[i].has_side_effects(compressor))
@@ -1745,6 +1747,7 @@ merge(Compressor.prototype, {
} catch(ex) {
if (ex !== ast) throw ex;
};
if (!fun) return self;
var args = fun.argnames.map(function(arg, i){
return make_node(AST_String, self.args[i], {
value: arg.print_to_string()
@@ -2320,6 +2323,17 @@ merge(Compressor.prototype, {
alternative: alternative
});
}
// x=y?1:1 --> x=1
if (consequent instanceof AST_Constant
&& alternative instanceof AST_Constant
&& consequent.equivalent_to(alternative)) {
if (self.condition.has_side_effects(compressor)) {
return AST_Seq.from_array([self.condition, make_node_from_constant(compressor, consequent.value, self)]);
} else {
return make_node_from_constant(compressor, consequent.value, self);
}
}
return self;
});
@@ -2370,6 +2384,15 @@ merge(Compressor.prototype, {
});
OPT(AST_Dot, function(self, compressor){
var prop = self.property;
if (RESERVED_WORDS(prop) && !compressor.option("screw_ie8")) {
return make_node(AST_Sub, self, {
expression : self.expression,
property : make_node(AST_String, self, {
value: prop
})
}).optimize(compressor);
}
return self.evaluate(compressor)[0];
});

View File

@@ -46,37 +46,44 @@
(function(){
var MOZ_TO_ME = {
TryStatement : function(M) {
ExpressionStatement: function(M) {
var expr = M.expression;
if (expr.type === "Literal" && typeof expr.value === "string") {
return new AST_Directive({
start: my_start_token(M),
end: my_end_token(M),
value: expr.value
});
}
return new AST_SimpleStatement({
start: my_start_token(M),
end: my_end_token(M),
body: from_moz(expr)
});
},
TryStatement: function(M) {
var handlers = M.handlers || [M.handler];
if (handlers.length > 1 || M.guardedHandlers && M.guardedHandlers.length) {
throw new Error("Multiple catch clauses are not supported.");
}
return new AST_Try({
start : my_start_token(M),
end : my_end_token(M),
body : from_moz(M.block).body,
bcatch : from_moz(M.handlers ? M.handlers[0] : M.handler),
bcatch : from_moz(handlers[0]),
bfinally : M.finalizer ? new AST_Finally(from_moz(M.finalizer)) : null
});
},
CatchClause : function(M) {
return new AST_Catch({
start : my_start_token(M),
end : my_end_token(M),
argname : from_moz(M.param),
body : from_moz(M.body).body
});
},
ObjectExpression : function(M) {
return new AST_Object({
start : my_start_token(M),
end : my_end_token(M),
properties : M.properties.map(function(prop){
var key = prop.key;
Property: function(M) {
var key = M.key;
var name = key.type == "Identifier" ? key.name : key.value;
var args = {
start : my_start_token(key),
end : my_end_token(prop.value),
end : my_end_token(M.value),
key : name,
value : from_moz(prop.value)
value : from_moz(M.value)
};
switch (prop.kind) {
switch (M.kind) {
case "init":
return new AST_ObjectKeyVal(args);
case "set":
@@ -86,13 +93,21 @@
args.value.name = from_moz(key);
return new AST_ObjectGetter(args);
}
},
ObjectExpression: function(M) {
return new AST_Object({
start : my_start_token(M),
end : my_end_token(M),
properties : M.properties.map(function(prop){
prop.type = "Property";
return from_moz(prop)
})
});
},
SequenceExpression : function(M) {
SequenceExpression: function(M) {
return AST_Seq.from_array(M.expressions.map(from_moz));
},
MemberExpression : function(M) {
MemberExpression: function(M) {
return new (M.computed ? AST_Sub : AST_Dot)({
start : my_start_token(M),
end : my_end_token(M),
@@ -100,7 +115,7 @@
expression : from_moz(M.object)
});
},
SwitchCase : function(M) {
SwitchCase: function(M) {
return new (M.test ? AST_Case : AST_Default)({
start : my_start_token(M),
end : my_end_token(M),
@@ -108,7 +123,14 @@
body : M.consequent.map(from_moz)
});
},
Literal : function(M) {
VariableDeclaration: function(M) {
return new (M.kind === "const" ? AST_Const : AST_Var)({
start : my_start_token(M),
end : my_end_token(M),
definitions : M.declarations.map(from_moz)
});
},
Literal: function(M) {
var val = M.value, args = {
start : my_start_token(M),
end : my_end_token(M)
@@ -128,12 +150,9 @@
return new AST_RegExp(args);
}
},
UnaryExpression: From_Moz_Unary,
UpdateExpression: From_Moz_Unary,
Identifier: function(M) {
var p = FROM_MOZ_STACK[FROM_MOZ_STACK.length - 2];
return new (M.name == "this" ? AST_This
: p.type == "LabeledStatement" ? AST_Label
return new ( p.type == "LabeledStatement" ? AST_Label
: p.type == "VariableDeclarator" && p.id === M ? (p.kind == "const" ? AST_SymbolConst : AST_SymbolVar)
: p.type == "FunctionExpression" ? (p.id === M ? AST_SymbolLambda : AST_SymbolFunarg)
: p.type == "FunctionDeclaration" ? (p.id === M ? AST_SymbolDefun : AST_SymbolFunarg)
@@ -147,7 +166,8 @@
}
};
function From_Moz_Unary(M) {
MOZ_TO_ME.UpdateExpression =
MOZ_TO_ME.UnaryExpression = function To_Moz_Unary(M) {
var prefix = "prefix" in M ? M.prefix
: M.type == "UnaryExpression" ? true : false;
return new (prefix ? AST_UnaryPrefix : AST_UnaryPostfix)({
@@ -158,14 +178,9 @@
});
};
var ME_TO_MOZ = {};
map("Node", AST_Node);
map("Program", AST_Toplevel, "body@body");
map("Function", AST_Function, "id>name, params@argnames, body%body");
map("EmptyStatement", AST_EmptyStatement);
map("BlockStatement", AST_BlockStatement, "body@body");
map("ExpressionStatement", AST_SimpleStatement, "expression>body");
map("IfStatement", AST_If, "test>condition, consequent>body, alternate>alternative");
map("LabeledStatement", AST_LabeledStatement, "label>label, body>body");
map("BreakStatement", AST_Break, "label>label");
@@ -180,71 +195,257 @@
map("ForInStatement", AST_ForIn, "left>init, right>object, body>body");
map("DebuggerStatement", AST_Debugger);
map("FunctionDeclaration", AST_Defun, "id>name, params@argnames, body%body");
map("VariableDeclaration", AST_Var, "declarations@definitions");
map("VariableDeclarator", AST_VarDef, "id>name, init>value");
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("AssignmentExpression", AST_Assign, "operator=operator, left>left, right>right");
map("LogicalExpression", AST_Binary, "operator=operator, left>left, right>right");
map("AssignmentExpression", AST_Assign, "operator=operator, left>left, right>right");
map("ConditionalExpression", AST_Conditional, "test>condition, consequent>consequent, alternate>alternative");
map("NewExpression", AST_New, "callee>expression, arguments@args");
map("CallExpression", AST_Call, "callee>expression, arguments@args");
def_to_moz(AST_Directive, function To_Moz_Directive(M) {
return {
type: "ExpressionStatement",
expression: {
type: "Literal",
value: M.value
}
};
});
def_to_moz(AST_SimpleStatement, function To_Moz_ExpressionStatement(M) {
return {
type: "ExpressionStatement",
expression: to_moz(M.body)
};
});
def_to_moz(AST_SwitchBranch, function To_Moz_SwitchCase(M) {
return {
type: "SwitchCase",
test: to_moz(M.expression),
consequent: M.body.map(to_moz)
};
});
def_to_moz(AST_Try, function To_Moz_TryStatement(M) {
return {
type: "TryStatement",
block: to_moz_block(M),
handler: to_moz(M.bcatch),
guardedHandlers: [],
finalizer: to_moz(M.bfinally)
};
});
def_to_moz(AST_Catch, function To_Moz_CatchClause(M) {
return {
type: "CatchClause",
param: to_moz(M.argname),
guard: null,
body: to_moz_block(M)
};
});
def_to_moz(AST_Definitions, function To_Moz_VariableDeclaration(M) {
return {
type: "VariableDeclaration",
kind: M instanceof AST_Const ? "const" : "var",
declarations: M.definitions.map(to_moz)
};
});
def_to_moz(AST_Seq, function To_Moz_SequenceExpression(M) {
return {
type: "SequenceExpression",
expressions: M.to_array().map(to_moz)
};
});
def_to_moz(AST_PropAccess, function To_Moz_MemberExpression(M) {
var isComputed = M instanceof AST_Sub;
return {
type: "MemberExpression",
object: to_moz(M.expression),
computed: isComputed,
property: isComputed ? to_moz(M.property) : {type: "Identifier", name: M.property}
};
});
def_to_moz(AST_Unary, function To_Moz_Unary(M) {
return {
type: M.operator == "++" || M.operator == "--" ? "UpdateExpression" : "UnaryExpression",
operator: M.operator,
prefix: M instanceof AST_UnaryPrefix,
argument: to_moz(M.expression)
};
});
def_to_moz(AST_Binary, function To_Moz_BinaryExpression(M) {
return {
type: M.operator == "&&" || M.operator == "||" ? "LogicalExpression" : "BinaryExpression",
left: to_moz(M.left),
operator: M.operator,
right: to_moz(M.right)
};
});
def_to_moz(AST_Object, function To_Moz_ObjectExpression(M) {
return {
type: "ObjectExpression",
properties: M.properties.map(to_moz)
};
});
def_to_moz(AST_ObjectProperty, function To_Moz_Property(M) {
var key = (
is_identifier(M.key)
? {type: "Identifier", name: M.key}
: {type: "Literal", value: M.key}
);
var kind;
if (M instanceof AST_ObjectKeyVal) {
kind = "init";
} else
if (M instanceof AST_ObjectGetter) {
kind = "get";
} else
if (M instanceof AST_ObjectSetter) {
kind = "set";
}
return {
type: "Property",
kind: kind,
key: key,
value: to_moz(M.value)
};
});
def_to_moz(AST_Symbol, function To_Moz_Identifier(M) {
var def = M.definition();
return {
type: "Identifier",
name: def ? def.mangled_name || def.name : M.name
};
});
def_to_moz(AST_Constant, function To_Moz_Literal(M) {
var value = M.value;
if (typeof value === 'number' && (value < 0 || (value === 0 && 1 / value < 0))) {
return {
type: "UnaryExpression",
operator: "-",
prefix: true,
argument: {
type: "Literal",
value: -value
}
};
}
return {
type: "Literal",
value: value
};
});
def_to_moz(AST_Atom, function To_Moz_Atom(M) {
return {
type: "Identifier",
name: String(M.value)
};
});
AST_Boolean.DEFMETHOD("to_mozilla_ast", AST_Constant.prototype.to_mozilla_ast);
AST_Null.DEFMETHOD("to_mozilla_ast", AST_Constant.prototype.to_mozilla_ast);
AST_Hole.DEFMETHOD("to_mozilla_ast", function To_Moz_ArrayHole() { return null });
AST_Block.DEFMETHOD("to_mozilla_ast", AST_BlockStatement.prototype.to_mozilla_ast);
AST_Lambda.DEFMETHOD("to_mozilla_ast", AST_Function.prototype.to_mozilla_ast);
/* -----[ tools ]----- */
function my_start_token(moznode) {
var loc = moznode.loc;
var range = moznode.range;
return new AST_Token({
file : moznode.loc && moznode.loc.source,
line : moznode.loc && moznode.loc.start.line,
col : moznode.loc && moznode.loc.start.column,
pos : moznode.start,
endpos : moznode.start
file : loc && loc.source,
line : loc && loc.start.line,
col : loc && loc.start.column,
pos : range ? range[0] : moznode.start,
endpos : range ? range[0] : moznode.start
});
};
function my_end_token(moznode) {
var loc = moznode.loc;
var range = moznode.range;
return new AST_Token({
file : moznode.loc && moznode.loc.source,
line : moznode.loc && moznode.loc.end.line,
col : moznode.loc && moznode.loc.end.column,
pos : moznode.end,
endpos : moznode.end
file : loc && loc.source,
line : loc && loc.end.line,
col : loc && loc.end.column,
pos : range ? range[1] : moznode.end,
endpos : range ? range[1] : moznode.end
});
};
function map(moztype, mytype, propmap) {
var moz_to_me = "function From_Moz_" + moztype + "(M){\n";
moz_to_me += "return new mytype({\n" +
moz_to_me += "return new " + mytype.name + "({\n" +
"start: my_start_token(M),\n" +
"end: my_end_token(M)";
var me_to_moz = "function To_Moz_" + moztype + "(M){\n";
me_to_moz += "return {\n" +
"type: " + JSON.stringify(moztype);
if (propmap) propmap.split(/\s*,\s*/).forEach(function(prop){
var m = /([a-z0-9$_]+)(=|@|>|%)([a-z0-9$_]+)/i.exec(prop);
if (!m) throw new Error("Can't understand property map: " + prop);
var moz = "M." + m[1], how = m[2], my = m[3];
var moz = m[1], how = m[2], my = m[3];
moz_to_me += ",\n" + my + ": ";
if (how == "@") {
moz_to_me += moz + ".map(from_moz)";
} else if (how == ">") {
moz_to_me += "from_moz(" + moz + ")";
} else if (how == "=") {
moz_to_me += moz;
} else if (how == "%") {
moz_to_me += "from_moz(" + moz + ").body";
} else throw new Error("Can't understand operator in propmap: " + prop);
me_to_moz += ",\n" + moz + ": ";
switch (how) {
case "@":
moz_to_me += "M." + moz + ".map(from_moz)";
me_to_moz += "M." + my + ".map(to_moz)";
break;
case ">":
moz_to_me += "from_moz(M." + moz + ")";
me_to_moz += "to_moz(M." + my + ")";
break;
case "=":
moz_to_me += "M." + moz;
me_to_moz += "M." + my;
break;
case "%":
moz_to_me += "from_moz(M." + moz + ").body";
me_to_moz += "to_moz_block(M)";
break;
default:
throw new Error("Can't understand operator in propmap: " + prop);
}
});
moz_to_me += "\n})}";
// moz_to_me = parse(moz_to_me).print_to_string({ beautify: true });
// console.log(moz_to_me);
moz_to_me += "\n})\n}";
me_to_moz += "\n}\n}";
moz_to_me = new Function("mytype", "my_start_token", "my_end_token", "from_moz", "return(" + moz_to_me + ")")(
mytype, my_start_token, my_end_token, from_moz
//moz_to_me = parse(moz_to_me).print_to_string({ beautify: true });
//me_to_moz = parse(me_to_moz).print_to_string({ beautify: true });
//console.log(moz_to_me);
moz_to_me = new Function("my_start_token", "my_end_token", "from_moz", "return(" + moz_to_me + ")")(
my_start_token, my_end_token, from_moz
);
return MOZ_TO_ME[moztype] = moz_to_me;
me_to_moz = new Function("to_moz", "to_moz_block", "return(" + me_to_moz + ")")(
to_moz, to_moz_block
);
MOZ_TO_ME[moztype] = moz_to_me;
def_to_moz(mytype, me_to_moz);
};
var FROM_MOZ_STACK = null;
@@ -264,4 +465,46 @@
return ast;
};
function moz_sub_loc(token) {
return token.line ? {
line: token.line,
column: token.col
} : null;
};
function set_moz_loc(mynode, moznode) {
var start = mynode.start;
var end = mynode.end;
if (start.pos != null && end.pos != null) {
moznode.range = [start.pos, end.pos];
}
if (start.line) {
moznode.loc = {
start: moz_sub_loc(start),
end: moz_sub_loc(end)
};
if (start.file) {
moznode.loc.source = start.file;
}
}
return moznode;
};
function def_to_moz(mytype, handler) {
mytype.DEFMETHOD("to_mozilla_ast", function() {
return set_moz_loc(this, handler(this));
});
};
function to_moz(node) {
return node != null ? node.to_mozilla_ast() : null;
};
function to_moz_block(node) {
return {
type: "BlockStatement",
body: node.body.map(to_moz)
};
};
})();

View File

@@ -434,7 +434,13 @@ function OutputStream(options) {
/* -----[ PARENTHESES ]----- */
function PARENS(nodetype, func) {
if (Array.isArray(nodetype)) {
nodetype.forEach(function(nodetype){
PARENS(nodetype, func);
});
} else {
nodetype.DEFMETHOD("needs_parens", func);
}
};
PARENS(AST_Node, function(){
@@ -453,7 +459,7 @@ function OutputStream(options) {
return first_in_statement(output);
});
PARENS(AST_Unary, function(output){
PARENS([ AST_Unary, AST_Undefined ], function(output){
var p = output.parent();
return p instanceof AST_PropAccess && p.expression === this;
});
@@ -549,7 +555,7 @@ function OutputStream(options) {
return true;
});
function assign_and_conditional_paren_rules(output) {
PARENS([ AST_Assign, AST_Conditional ], function (output){
var p = output.parent();
// !(a = false) → true
if (p instanceof AST_Unary)
@@ -566,10 +572,7 @@ function OutputStream(options) {
// (a = foo)["prop"] —or— (a = foo).prop
if (p instanceof AST_PropAccess && p.expression === this)
return true;
};
PARENS(AST_Assign, assign_and_conditional_paren_rules);
PARENS(AST_Conditional, assign_and_conditional_paren_rules);
});
/* -----[ PRINTERS ]----- */

View File

@@ -609,6 +609,7 @@ function parse($TEXT, options) {
toplevel : null,
expression : false,
html5_comments : true,
bare_returns : false,
});
var S = {
@@ -788,7 +789,7 @@ function parse($TEXT, options) {
return if_();
case "return":
if (S.in_function == 0)
if (S.in_function == 0 && !options.bare_returns)
croak("'return' outside of function");
return new AST_Return({
value: ( is("punc", ";")

View File

@@ -532,6 +532,7 @@ AST_Toplevel.DEFMETHOD("scope_warnings", function(options){
}
if (options.unreferenced
&& (node instanceof AST_SymbolDeclaration || node instanceof AST_Label)
&& !(node instanceof AST_SymbolCatch)
&& node.unreferenced()) {
AST_Node.warn("{type} {name} is declared but not referenced [{file}:{line},{col}]", {
type: node instanceof AST_Label ? "Label" : "Symbol",

View File

@@ -70,7 +70,7 @@ function SourceMap(options) {
source = info.source;
orig_line = info.line;
orig_col = info.column;
name = info.name;
name = info.name || name;
}
generator.addMapping({
generated : { line: gen_line + options.dest_line_diff, column: gen_col },

View File

@@ -3,7 +3,7 @@
"description": "JavaScript parser, mangler/compressor and beautifier toolkit",
"homepage": "http://lisperator.net/uglifyjs",
"main": "tools/node.js",
"version": "2.4.15",
"version": "2.4.16",
"engines": { "node" : ">=0.4.0" },
"maintainers": [{
"name": "Mihai Bazon",
@@ -17,14 +17,21 @@
"dependencies": {
"async" : "~0.2.6",
"source-map" : "0.1.34",
"optimist" : "~0.3.5",
"optimist": "~0.3.5",
"uglify-to-browserify": "~1.0.0"
},
"devDependencies": {
"acorn": "~0.6.0",
"escodegen": "~1.3.3",
"esfuzz": "~0.3.1",
"estraverse": "~1.5.1"
},
"browserify": {
"transform": [ "uglify-to-browserify" ]
},
"bin": {
"uglifyjs" : "bin/uglifyjs"
},
"license": "BSD",
"scripts": {"test": "node test/run-tests.js"}
}

View File

@@ -232,3 +232,83 @@ cond_5: {
some_condition() && some_other_condition() && do_something();
}
}
cond_7: {
options = {
conditionals: true,
evaluate : true
};
input: {
var x, y, z, a, b;
// compress these
if (y) {
x = 1+1;
} else {
x = 2;
}
if (y) {
x = 1+1;
} else if (z) {
x = 2;
} else {
x = 3-1;
}
x = y ? 'foo' : 'fo'+'o';
x = y ? 'foo' : y ? 'foo' : 'fo'+'o';
// Compress conditions that have side effects
if (condition()) {
x = 10+10;
} else {
x = 20;
}
if (z) {
x = 'fuji';
} else if (condition()) {
x = 'fu'+'ji';
} else {
x = 'fuji';
}
x = condition() ? 'foobar' : 'foo'+'bar';
// don't compress these
x = y ? a : b;
x = y ? 'foo' : 'fo';
}
expect: {
var x, y, z, a, b;
x = 2;
x = 2;
x = 'foo';
x = 'foo';
x = (condition(), 20);
x = z ? 'fuji' : (condition(), 'fuji');
x = (condition(), 'foobar');
x = y ? a : b;
x = y ? 'foo' : 'fo';
}
}
cond_7_1: {
options = {
conditionals: true,
evaluate : true
};
input: {
// access to global should be assumed to have side effects
if (y) {
x = 1+1;
} else {
x = 2;
}
}
expect: {
x = (y, 2);
}
}

View File

@@ -91,9 +91,11 @@ make_sequences_4: {
lift_sequences_1: {
options = { sequences: true };
input: {
var foo, x, y, bar;
foo = !(x(), y(), bar());
}
expect: {
var foo, x, y, bar;
x(), y(), foo = !bar();
}
}
@@ -101,10 +103,12 @@ lift_sequences_1: {
lift_sequences_2: {
options = { sequences: true, evaluate: true };
input: {
var foo, bar;
foo.x = (foo = {}, 10);
bar = (bar = {}, 10);
}
expect: {
var foo, bar;
foo.x = (foo = {}, 10),
bar = {}, bar = 10;
}
@@ -113,9 +117,11 @@ lift_sequences_2: {
lift_sequences_3: {
options = { sequences: true, conditionals: true };
input: {
var x, foo, bar, baz;
x = (foo(), bar(), baz()) ? 10 : 20;
}
expect: {
var x, foo, bar, baz;
foo(), bar(), x = baz() ? 10 : 20;
}
}
@@ -123,9 +129,11 @@ lift_sequences_3: {
lift_sequences_4: {
options = { side_effects: true };
input: {
var x, foo, bar, baz;
x = (foo, bar, baz);
}
expect: {
var x, foo, bar, baz;
x = baz;
}
}

103
test/mozilla-ast.js Normal file
View File

@@ -0,0 +1,103 @@
// Testing UglifyJS <-> SpiderMonkey AST conversion
// through generative testing.
var UglifyJS = require(".."),
escodegen = require("escodegen"),
esfuzz = require("esfuzz"),
estraverse = require("estraverse"),
prefix = Array(20).join("\b") + " ";
// Normalizes input AST for UglifyJS in order to get correct comparison.
function normalizeInput(ast) {
return estraverse.replace(ast, {
enter: function(node, parent) {
switch (node.type) {
// Internally mark all the properties with semi-standard type "Property".
case "ObjectExpression":
node.properties.forEach(function (property) {
property.type = "Property";
});
break;
// Since UglifyJS doesn"t recognize different types of property keys,
// decision on SpiderMonkey node type is based on check whether key
// can be valid identifier or not - so we do in input AST.
case "Property":
var key = node.key;
if (key.type === "Literal" && typeof key.value === "string" && UglifyJS.is_identifier(key.value)) {
node.key = {
type: "Identifier",
name: key.value
};
} else if (key.type === "Identifier" && !UglifyJS.is_identifier(key.name)) {
node.key = {
type: "Literal",
value: key.name
};
}
break;
// UglifyJS internally flattens all the expression sequences - either
// to one element (if sequence contains only one element) or flat list.
case "SequenceExpression":
node.expressions = node.expressions.reduce(function flatten(list, expr) {
return list.concat(expr.type === "SequenceExpression" ? expr.expressions.reduce(flatten, []) : [expr]);
}, []);
if (node.expressions.length === 1) {
return node.expressions[0];
}
break;
}
}
});
}
module.exports = function(options) {
console.log("--- UglifyJS <-> Mozilla AST conversion");
for (var counter = 0; counter < options.iterations; counter++) {
process.stdout.write(prefix + counter + "/" + options.iterations);
var ast1 = normalizeInput(esfuzz.generate({
maxDepth: options.maxDepth
}));
var ast2 =
UglifyJS
.AST_Node
.from_mozilla_ast(ast1)
.to_mozilla_ast();
var astPair = [
{name: 'expected', value: ast1},
{name: 'actual', value: ast2}
];
var jsPair = astPair.map(function(item) {
return {
name: item.name,
value: escodegen.generate(item.value)
}
});
if (jsPair[0].value !== jsPair[1].value) {
var fs = require("fs");
var acorn = require("acorn");
fs.existsSync("tmp") || fs.mkdirSync("tmp");
jsPair.forEach(function (item) {
var fileName = "tmp/dump_" + item.name;
var ast = acorn.parse(item.value);
fs.writeFileSync(fileName + ".js", item.value);
fs.writeFileSync(fileName + ".json", JSON.stringify(ast, null, 2));
});
process.stdout.write("\n");
throw new Error("Got different outputs, check out tmp/dump_*.{js,json} for codes and ASTs.");
}
}
process.stdout.write(prefix + "Probability of error is less than " + (100 / options.iterations) + "%, stopping.\n");
};

View File

@@ -17,6 +17,12 @@ if (failures) {
process.exit(1);
}
var run_ast_conversion_tests = require("./mozilla-ast");
run_ast_conversion_tests({
iterations: 1000
});
/* -----[ utils ]----- */
function tmpl() {