Merge remote-tracking branch 'upstream/master' into ternary-improvements

This commit is contained in:
Tal Ater 2015-01-13 18:50:00 +02:00
commit d522107537
15 changed files with 328 additions and 112 deletions

View File

@ -1,6 +1,6 @@
UglifyJS 2
==========
[![Build Status](https://travis-ci.org/mishoo/UglifyJS2.png)](https://travis-ci.org/mishoo/UglifyJS2)
[![Build Status](https://travis-ci.org/mishoo/UglifyJS2.svg)](https://travis-ci.org/mishoo/UglifyJS2)
UglifyJS is a JavaScript parser, minifier, compressor or beautifier toolkit.
@ -44,6 +44,11 @@ variable/function declared in another file will be matched properly.
If you want to read from STDIN instead, pass a single dash instead of input
files.
If you wish to pass your options before the input files, separate the two with
a double dash to prevent input files being used as option arguments:
uglifyjs --compress --mangle -- input.js
The available options are:
```
@ -612,7 +617,7 @@ or, for a shortcut you can do:
var code = compressed_ast.print_to_string(options);
```
As usual, `options` is optional. The output stream accepts a lot of otions,
As usual, `options` is optional. The output stream accepts a lot of options,
most of them documented above in section “Beautifier options”. The two
which we care about here are `source_map` and `comments`.

View File

@ -5,12 +5,12 @@
var UglifyJS = require("../tools/node");
var sys = require("util");
var optimist = require("optimist");
var yargs = require("yargs");
var fs = require("fs");
var path = require("path");
var async = require("async");
var acorn;
var ARGS = optimist
var ARGS = yargs
.usage("$0 input1.js [input2.js ...] [options]\n\
Use a single dash to read input from the standard input.\
\n\n\
@ -64,6 +64,8 @@ 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.")
.describe("keep-fnames", "Do not mangle/drop function names. Useful for code relying on Function.prototype.name.")
.alias("p", "prefix")
.alias("o", "output")
@ -100,6 +102,8 @@ 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")
.boolean("keep-fnames")
.wrap(80)
@ -127,7 +131,7 @@ if (ARGS.ast_help) {
}
if (ARGS.h || ARGS.help) {
sys.puts(optimist.help());
sys.puts(yargs.help());
process.exit(0);
}
@ -158,6 +162,11 @@ if (ARGS.screw_ie8) {
OUTPUT_OPTIONS.screw_ie8 = true;
}
if (ARGS.keep_fnames) {
if (COMPRESS) COMPRESS.keep_fnames = true;
if (MANGLE) MANGLE.keep_fnames = true;
}
if (BEAUTIFY)
UglifyJS.merge(OUTPUT_OPTIONS, BEAUTIFY);
@ -250,7 +259,7 @@ async.eachLimit(files, 1, function (file, cb) {
}
if (ARGS.p != null) {
if (P_RELATIVE) {
file = path.relative(path.dirname(ARGS.source_map), file);
file = path.relative(path.dirname(ARGS.source_map), file).replace(/\\/g, '/');
} else {
var p = parseInt(ARGS.p, 10);
if (!isNaN(p)) {
@ -278,6 +287,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

@ -84,7 +84,7 @@ function DEFNODE(type, props, methods, base) {
return ctor;
};
var AST_Token = DEFNODE("Token", "type value line col pos endpos nlb comments_before file", {
var AST_Token = DEFNODE("Token", "type value line col pos endline endcol endpos nlb comments_before file", {
}, null);
var AST_Node = DEFNODE("Node", "start end", {
@ -205,21 +205,27 @@ var AST_DWLoop = DEFNODE("DWLoop", "condition", {
$documentation: "Base class for do/while statements",
$propdoc: {
condition: "[AST_Node] the loop condition. Should not be instanceof AST_Statement"
},
}
}, AST_IterationStatement);
var AST_Do = DEFNODE("Do", null, {
$documentation: "A `do` statement",
_walk: function(visitor) {
return visitor._visit(this, function(){
this.body._walk(visitor);
this.condition._walk(visitor);
});
}
}, AST_DWLoop);
var AST_While = DEFNODE("While", null, {
$documentation: "A `while` statement",
_walk: function(visitor) {
return visitor._visit(this, function(){
this.condition._walk(visitor);
this.body._walk(visitor);
});
}
}, AST_IterationStatement);
var AST_Do = DEFNODE("Do", null, {
$documentation: "A `do` statement",
}, AST_DWLoop);
var AST_While = DEFNODE("While", null, {
$documentation: "A `while` statement",
}, AST_DWLoop);
var AST_For = DEFNODE("For", "init condition step", {

View File

@ -62,6 +62,7 @@ function Compressor(options, false_by_default) {
unused : !false_by_default,
hoist_funs : !false_by_default,
keep_fargs : false,
keep_fnames : false,
hoist_vars : false,
if_return : !false_by_default,
join_vars : !false_by_default,
@ -162,10 +163,10 @@ merge(Compressor.prototype, {
return make_node(AST_Undefined, orig).optimize(compressor);
default:
if (val === null) {
return make_node(AST_Null, orig).optimize(compressor);
return make_node(AST_Null, orig, { value: null }).optimize(compressor);
}
if (val instanceof RegExp) {
return make_node(AST_RegExp, orig).optimize(compressor);
return make_node(AST_RegExp, orig, { value: val }).optimize(compressor);
}
throw new Error(string_template("Can't handle constant of type: {type}", {
type: typeof val
@ -225,6 +226,17 @@ merge(Compressor.prototype, {
return statements;
function process_for_angular(statements) {
function has_inject(comment) {
return /@ngInject/.test(comment.value);
}
function make_arguments_names_list(func) {
return func.argnames.map(function(sym){
return make_node(AST_String, sym, { value: sym.name });
});
}
function make_array(orig, elements) {
return make_node(AST_Array, orig, { elements: elements });
}
function make_injector(func, name) {
return make_node(AST_SimpleStatement, func, {
body: make_node(AST_Assign, func, {
@ -233,21 +245,38 @@ merge(Compressor.prototype, {
expression: make_node(AST_SymbolRef, name, name),
property: "$inject"
}),
right: make_node(AST_Array, func, {
elements: func.argnames.map(function(sym){
return make_node(AST_String, sym, { value: sym.name });
})
})
right: make_array(func, make_arguments_names_list(func))
})
});
}
function check_expression(body) {
if (body && body.args) {
// if this is a function call check all of arguments passed
body.args.forEach(function(argument, index, array) {
var comments = argument.start.comments_before;
// if the argument is function preceded by @ngInject
if (argument instanceof AST_Lambda && comments.length && has_inject(comments[0])) {
// replace the function with an array of names of its parameters and function at the end
array[index] = make_array(argument, make_arguments_names_list(argument).concat(argument));
}
});
// if this is chained call check previous one recursively
if (body.expression && body.expression.expression) {
check_expression(body.expression.expression);
}
}
}
return statements.reduce(function(a, stat){
a.push(stat);
if (stat.body && stat.body.args) {
check_expression(stat.body);
} else {
var token = stat.start;
var comments = token.comments_before;
if (comments && comments.length > 0) {
var last = comments.pop();
if (/@ngInject/.test(last.value)) {
if (has_inject(last)) {
// case 1: defun
if (stat instanceof AST_Defun) {
a.push(make_injector(stat, stat.name));
@ -264,6 +293,8 @@ merge(Compressor.prototype, {
}
}
}
}
return a;
}, []);
}
@ -949,7 +980,7 @@ merge(Compressor.prototype, {
def(AST_BlockStatement, block_aborts);
def(AST_SwitchBranch, block_aborts);
def(AST_If, function(){
return this.alternative && aborts(this.body) && aborts(this.alternative);
return this.alternative && aborts(this.body) && aborts(this.alternative) && this;
});
})(function(node, func){
node.DEFMETHOD("aborts", func);
@ -1666,7 +1697,7 @@ merge(Compressor.prototype, {
OPT(AST_Function, function(self, compressor){
self = AST_Lambda.prototype.optimize.call(self, compressor);
if (compressor.option("unused")) {
if (compressor.option("unused") && !compressor.option("keep_fnames")) {
if (self.name && self.name.unreferenced()) {
self.name = null;
}
@ -1722,6 +1753,11 @@ merge(Compressor.prototype, {
}).transform(compressor);
break;
case "Function":
// new Function() => function(){}
if (self.args.length == 0) return make_node(AST_Function, self, {
argnames: [],
body: []
});
if (all(self.args, function(x){ return x instanceof AST_String })) {
// quite a corner-case, but we can handle it:
// https://github.com/mishoo/UglifyJS2/issues/203
@ -1901,7 +1937,7 @@ merge(Compressor.prototype, {
if (self.cdr instanceof AST_UnaryPrefix
&& self.cdr.operator == "void"
&& !self.cdr.expression.has_side_effects(compressor)) {
self.cdr.operator = self.car;
self.cdr.expression = self.car;
return self.cdr;
}
if (self.cdr instanceof AST_Undefined) {
@ -2203,14 +2239,30 @@ merge(Compressor.prototype, {
case "undefined":
return make_node(AST_Undefined, self);
case "NaN":
return make_node(AST_NaN, self);
return make_node(AST_NaN, self).transform(compressor);
case "Infinity":
return make_node(AST_Infinity, self);
return make_node(AST_Infinity, self).transform(compressor);
}
}
return self;
});
OPT(AST_Infinity, function (self, compressor) {
return make_node(AST_Binary, self, {
operator : '/',
left : make_node(AST_Number, null, {value: 1}),
right : make_node(AST_Number, null, {value: 0})
});
});
OPT(AST_NaN, function (self, compressor) {
return make_node(AST_Binary, self, {
operator : '/',
left : make_node(AST_Number, null, {value: 0}),
right : make_node(AST_Number, null, {value: 0})
});
});
OPT(AST_Undefined, function(self, compressor){
if (compressor.option("unsafe")) {
var scope = compressor.find_parent(AST_Scope);

View File

@ -370,25 +370,29 @@
/* -----[ tools ]----- */
function my_start_token(moznode) {
var loc = moznode.loc;
var loc = moznode.loc, start = loc && loc.start;
var range = moznode.range;
return new AST_Token({
file : loc && loc.source,
line : loc && loc.start.line,
col : loc && loc.start.column,
line : start && start.line,
col : start && start.column,
pos : range ? range[0] : moznode.start,
endline : start && start.line,
endcol : start && start.column,
endpos : range ? range[0] : moznode.start
});
};
function my_end_token(moznode) {
var loc = moznode.loc;
var loc = moznode.loc, end = loc && loc.end;
var range = moznode.range;
return new AST_Token({
file : loc && loc.source,
line : loc && loc.end.line,
col : loc && loc.end.column,
line : end && end.line,
col : end && end.column,
pos : range ? range[1] : moznode.end,
endline : end && end.line,
endcol : end && end.column,
endpos : range ? range[1] : moznode.end
});
};
@ -465,23 +469,16 @@
return ast;
};
function moz_sub_loc(token) {
return token.line ? {
line: token.line,
column: token.col
} : null;
};
function set_moz_loc(mynode, moznode) {
function set_moz_loc(mynode, moznode, myparent) {
var start = mynode.start;
var end = mynode.end;
if (start.pos != null && end.pos != null) {
moznode.range = [start.pos, end.pos];
if (start.pos != null && end.endpos != null) {
moznode.range = [start.pos, end.endpos];
}
if (start.line) {
moznode.loc = {
start: moz_sub_loc(start),
end: moz_sub_loc(end)
start: {line: start.line, column: start.col},
end: end.endline ? {line: end.endline, column: end.endcol} : null
};
if (start.file) {
moznode.loc.source = start.file;

View File

@ -86,7 +86,7 @@ function OutputStream(options) {
function make_string(str) {
var dq = 0, sq = 0;
str = str.replace(/[\\\b\f\n\r\t\x22\x27\u2028\u2029\0]/g, function(s){
str = str.replace(/[\\\b\f\n\r\t\x22\x27\u2028\u2029\0\ufeff]/g, function(s){
switch (s) {
case "\\": return "\\\\";
case "\b": return "\\b";
@ -98,6 +98,7 @@ function OutputStream(options) {
case '"': ++dq; return '"';
case "'": ++sq; return "'";
case "\0": return "\\x00";
case "\ufeff": return "\\ufeff";
}
return s;
});
@ -221,7 +222,7 @@ function OutputStream(options) {
var newline = options.beautify ? function() {
print("\n");
} : noop;
} : maybe_newline;
var semicolon = options.beautify ? function() {
print(";");
@ -549,12 +550,6 @@ function OutputStream(options) {
return true;
});
PARENS(AST_NaN, function(output){
var p = output.parent();
if (p instanceof AST_PropAccess && p.expression === this)
return true;
});
PARENS([ AST_Assign, AST_Conditional ], function (output){
var p = output.parent();
// !(a = false) → true
@ -1109,10 +1104,10 @@ function OutputStream(options) {
});
DEFPRINT(AST_Hole, noop);
DEFPRINT(AST_Infinity, function(self, output){
output.print("1/0");
output.print("Infinity");
});
DEFPRINT(AST_NaN, function(self, output){
output.print("0/0");
output.print("NaN");
});
DEFPRINT(AST_This, function(self, output){
output.print("this");

View File

@ -206,7 +206,7 @@ var EX_EOF = {};
function tokenizer($TEXT, filename, html5_comments) {
var S = {
text : $TEXT.replace(/\r\n?|[\n\u2028\u2029]/g, "\n").replace(/\uFEFF/g, ''),
text : $TEXT.replace(/\uFEFF/g, ''),
filename : filename,
pos : 0,
tokpos : 0,
@ -225,10 +225,15 @@ function tokenizer($TEXT, filename, html5_comments) {
var ch = S.text.charAt(S.pos++);
if (signal_eof && !ch)
throw EX_EOF;
if (ch == "\n") {
if ("\r\n\u2028\u2029".indexOf(ch) >= 0) {
S.newline_before = S.newline_before || !in_string;
++S.line;
S.col = 0;
if (!in_string && ch == "\r" && peek() == "\n") {
// treat a \r\n sequence as a single \n
++S.pos;
ch = "\n";
}
} else {
++S.col;
}
@ -267,6 +272,8 @@ function tokenizer($TEXT, filename, html5_comments) {
line : S.tokline,
col : S.tokcol,
pos : S.tokpos,
endline : S.line,
endcol : S.col,
endpos : S.pos,
nlb : S.newline_before,
file : filename
@ -392,6 +399,7 @@ function tokenizer($TEXT, filename, html5_comments) {
ret = S.text.substring(S.pos, i);
S.pos = i;
}
S.col = S.tokcol + (S.pos - S.tokpos);
S.comments_before.push(token(type, ret, true));
S.regex_allowed = regex_allowed;
return next_token();
@ -609,6 +617,7 @@ function parse($TEXT, options) {
toplevel : null,
expression : false,
html5_comments : true,
bare_returns : false,
});
var S = {
@ -788,7 +797,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

@ -57,9 +57,14 @@ function SymbolDef(scope, index, orig) {
SymbolDef.prototype = {
unmangleable: function(options) {
return (this.global && !(options && options.toplevel))
if (!options) options = {};
return (this.global && !options.toplevel)
|| this.undeclared
|| (!(options && options.eval) && (this.scope.uses_eval || this.scope.uses_with));
|| (!options.eval && (this.scope.uses_eval || this.scope.uses_with))
|| (options.keep_fnames
&& (this.orig[0] instanceof AST_SymbolLambda
|| this.orig[0] instanceof AST_SymbolDefun));
},
mangle: function(options) {
if (!this.mangled_name && !this.unmangleable(options)) {
@ -326,7 +331,8 @@ AST_Toplevel.DEFMETHOD("_default_mangler_options", function(options){
eval : false,
sort : false,
toplevel : false,
screw_ie8 : false
screw_ie8 : false,
keep_fnames : false
});
});
@ -471,7 +477,9 @@ var base54 = (function() {
base54.freq = function(){ return frequency };
function base54(num) {
var ret = "", base = 54;
num++;
do {
num--;
ret += String.fromCharCode(chars[num % base]);
num = Math.floor(num / base);
base = 64;

View File

@ -53,11 +53,16 @@ function SourceMap(options) {
orig_line_diff : 0,
dest_line_diff : 0,
});
var generator = new MOZ_SourceMap.SourceMapGenerator({
var orig_map = options.orig && new MOZ_SourceMap.SourceMapConsumer(options.orig);
var generator;
if (orig_map) {
generator = MOZ_SourceMap.SourceMapGenerator.fromSourceMap(orig_map);
} else {
generator = new MOZ_SourceMap.SourceMapGenerator({
file : options.file,
sourceRoot : options.root
});
var orig_map = options.orig && new MOZ_SourceMap.SourceMapConsumer(options.orig);
}
function add(source, gen_line, gen_col, orig_line, orig_col, name) {
if (orig_map) {
var info = orig_map.originalPositionFor({
@ -70,7 +75,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 },
@ -78,7 +83,7 @@ function SourceMap(options) {
source : source,
name : name
});
};
}
return {
add : add,
get : function() { return generator },

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,7 +17,7 @@
"dependencies": {
"async" : "~0.2.6",
"source-map" : "0.1.34",
"optimist": "~0.3.5",
"yargs": "~1.3.3",
"uglify-to-browserify": "~1.0.0"
},
"devDependencies": {

67
test/compress/angular-inject.js vendored Normal file
View File

@ -0,0 +1,67 @@
ng_inject_defun: {
options = {
angular: true
};
input: {
/*@ngInject*/
function Controller(dependency) {
return dependency;
}
}
expect: {
function Controller(dependency) {
return dependency;
}
Controller.$inject=['dependency']
}
}
ng_inject_assignment: {
options = {
angular: true
};
input: {
/*@ngInject*/
var Controller = function(dependency) {
return dependency;
}
}
expect: {
var Controller = function(dependency) {
return dependency;
}
Controller.$inject=['dependency']
}
}
ng_inject_inline: {
options = {
angular: true
};
input: {
angular.module('a').
factory('b',
/*@ngInject*/
function(dependency) {
return dependency;
}).
directive('c',
/*@ngInject*/
function(anotherDependency) {
return anotherDependency;
})
}
expect: {
angular.module('a').
factory('b',[
'dependency',
function(dependency) {
return dependency;
}]).
directive('c',[
'anotherDependency',
function(anotherDependency) {
return anotherDependency;
}])
}
}

View File

@ -163,3 +163,17 @@ used_var_in_catch: {
}
}
}
keep_fnames: {
options = { unused: true, keep_fnames: true };
input: {
function foo() {
return function bar(baz) {};
}
}
expect: {
function foo() {
return function bar() {};
}
}
}

View File

@ -0,0 +1,25 @@
NaN_and_Infinity_must_have_parens: {
options = {};
input: {
Infinity.toString();
NaN.toString();
}
expect: {
(1/0).toString();
(0/0).toString();
}
}
NaN_and_Infinity_should_not_be_replaced_when_they_are_redefined: {
options = {};
input: {
var Infinity, NaN;
Infinity.toString();
NaN.toString();
}
expect: {
var Infinity, NaN;
Infinity.toString();
NaN.toString();
}
}

View File

@ -0,0 +1,21 @@
issue_611: {
options = {
sequences: true,
side_effects: true
};
input: {
define(function() {
function fn() {}
if (fn()) {
fn();
return void 0;
}
});
}
expect: {
define(function() {
function fn(){}
if (fn()) return void fn();
});
}
}

View File

@ -6,6 +6,8 @@ var sys = require("util");
var UglifyJS = vm.createContext({
sys : sys,
console : console,
process : process,
Buffer : Buffer,
MOZ_SourceMap : require("source-map")
});
@ -33,7 +35,7 @@ var FILES = exports.FILES = [
"../lib/sourcemap.js",
"../lib/mozilla-ast.js"
].map(function(file){
return path.join(path.dirname(fs.realpathSync(__filename)), file);
return fs.realpathSync(path.join(path.dirname(__filename), file));
});
FILES.forEach(load_global);
@ -95,8 +97,8 @@ exports.minify = function(files, options) {
// 3. mangle
if (options.mangle) {
toplevel.figure_out_scope();
toplevel.compute_char_frequency();
toplevel.figure_out_scope(options.mangle);
toplevel.compute_char_frequency(options.mangle);
toplevel.mangle_names(options.mangle);
}