Compare commits
105 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5538ec7bd8 | ||
|
|
f101d6429b | ||
|
|
f36a1eaa8b | ||
|
|
a64bdda9ae | ||
|
|
01d19b4b52 | ||
|
|
f0c1a01bc2 | ||
|
|
7be680d3f8 | ||
|
|
57dab1e1db | ||
|
|
21b3c890a1 | ||
|
|
fb0ec720a4 | ||
|
|
7971ed33d1 | ||
|
|
885835a655 | ||
|
|
4c64554808 | ||
|
|
548beeb6b1 | ||
|
|
e3066f9577 | ||
|
|
e391367488 | ||
|
|
18ddf2f7b5 | ||
|
|
f8ee5a0785 | ||
|
|
b467a3c877 | ||
|
|
f2d48e9019 | ||
|
|
5e314bf3e9 | ||
|
|
05ba26c7c8 | ||
|
|
87b72364a4 | ||
|
|
0e3ff1f970 | ||
|
|
ec3e74d7f4 | ||
|
|
62bda71c85 | ||
|
|
83e0939088 | ||
|
|
9798d96e37 | ||
|
|
ac2caf1088 | ||
|
|
8511e80f48 | ||
|
|
91bc3f1f92 | ||
|
|
8463b48f90 | ||
|
|
e3342a3cf6 | ||
|
|
7bf59b5bcd | ||
|
|
025f3e9596 | ||
|
|
8258edd8a5 | ||
|
|
8669ca219b | ||
|
|
71652690b6 | ||
|
|
37693d2812 | ||
|
|
8fbe200012 | ||
|
|
1a34a13e33 | ||
|
|
ef772b0049 | ||
|
|
6fcabbde08 | ||
|
|
14f290f8ab | ||
|
|
e2e09d5754 | ||
|
|
448a8d3845 | ||
|
|
514936beb8 | ||
|
|
f5c09d0bbf | ||
|
|
014f655c5f | ||
|
|
bf30dc3038 | ||
|
|
ef2ef07cbd | ||
|
|
1a4440080d | ||
|
|
ac0086a745 | ||
|
|
2494daaf68 | ||
|
|
9b404f9de6 | ||
|
|
5344b7dab8 | ||
|
|
0007a53b9c | ||
|
|
1dd05f44eb | ||
|
|
bf7b122ab2 | ||
|
|
e29048b54a | ||
|
|
2eeb640eca | ||
|
|
ceb200fe81 | ||
|
|
f5f8239057 | ||
|
|
6f9d051784 | ||
|
|
931862e97f | ||
|
|
1d0127de21 | ||
|
|
2d8fc61677 | ||
|
|
1e31011874 | ||
|
|
75cdbf19aa | ||
|
|
4339bd5cfa | ||
|
|
1ab2fdaa10 | ||
|
|
eda540f6ec | ||
|
|
90a330da16 | ||
|
|
cad1f9cbd1 | ||
|
|
f6203bd5a8 | ||
|
|
03cf94ebe8 | ||
|
|
c3087dd179 | ||
|
|
2c305af478 | ||
|
|
72e6f64ca8 | ||
|
|
b9fac687ff | ||
|
|
2c88eb6fbe | ||
|
|
a67e3bfdd3 | ||
|
|
27142df4f5 | ||
|
|
5e4c7f4245 | ||
|
|
b521b4b926 | ||
|
|
aa9de76370 | ||
|
|
5a083a938d | ||
|
|
7a30d826b8 | ||
|
|
be55a09edf | ||
|
|
15a148ff6d | ||
|
|
428e19fed2 | ||
|
|
f65e55dff4 | ||
|
|
b634018618 | ||
|
|
fa3300f314 | ||
|
|
bd0886a2c0 | ||
|
|
248f304f02 | ||
|
|
dc5f70eab5 | ||
|
|
df8c5623af | ||
|
|
a790c09c91 | ||
|
|
8f35a363d9 | ||
|
|
d2190c2bf3 | ||
|
|
ea10642572 | ||
|
|
547561a568 | ||
|
|
c16d538ce7 | ||
|
|
dfa395f6ff |
@@ -1,7 +1,6 @@
|
||||
language: node_js
|
||||
before_install: "npm install -g npm"
|
||||
node_js:
|
||||
- "0.4"
|
||||
- "0.6"
|
||||
- "0.8"
|
||||
- "0.10"
|
||||
- "0.11"
|
||||
|
||||
47
README.md
47
README.md
@@ -54,6 +54,10 @@ The available options are:
|
||||
--source-map-url The path to the source map to be added in //#
|
||||
sourceMappingURL. Defaults to the value passed with
|
||||
--source-map. [string]
|
||||
--source-map-include-sources
|
||||
Pass this flag if you want to include the content of
|
||||
source files in the source map as sourcesContent
|
||||
property. [boolean]
|
||||
--in-source-map Input source map, useful if you're compressing JS that was
|
||||
generated from some other original code.
|
||||
--screw-ie8 Pass this flag if you don't care about full compliance
|
||||
@@ -169,7 +173,7 @@ To enable the mangler you need to pass `--mangle` (`-m`). The following
|
||||
- `toplevel` — mangle names declared in the toplevel scope (disabled by
|
||||
default).
|
||||
|
||||
- `eval` — mangle names visible in scopes where `eval` or `when` are used
|
||||
- `eval` — mangle names visible in scopes where `eval` or `with` are used
|
||||
(disabled by default).
|
||||
|
||||
When mangling is enabled but you want to prevent certain names from being
|
||||
@@ -249,6 +253,13 @@ to set `true`; it's effectively a shortcut for `foo=true`).
|
||||
statement would get discarded. The current implementation adds some
|
||||
overhead (compression will be slower).
|
||||
|
||||
- `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
|
||||
@@ -393,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
|
||||
-------------
|
||||
|
||||
@@ -502,7 +545,7 @@ something like this:
|
||||
```javascript
|
||||
var toplevel = null;
|
||||
files.forEach(function(file){
|
||||
var code = fs.readFileSync(file);
|
||||
var code = fs.readFileSync(file, "utf8");
|
||||
toplevel = UglifyJS.parse(code, {
|
||||
filename: file,
|
||||
toplevel: toplevel
|
||||
|
||||
31
bin/uglifyjs
31
bin/uglifyjs
@@ -22,6 +22,7 @@ mangling you need to use `-c` and `-m`.\
|
||||
.describe("source-map", "Specify an output file where to generate source map.")
|
||||
.describe("source-map-root", "The path to the original source to be included in the source map.")
|
||||
.describe("source-map-url", "The path to the source map to be added in //# sourceMappingURL. Defaults to the value passed with --source-map.")
|
||||
.describe("source-map-include-sources", "Pass this flag if you want to include the content of source files in the source map as sourcesContent property.")
|
||||
.describe("in-source-map", "Input source map, useful if you're compressing JS that was generated from some other original code.")
|
||||
.describe("screw-ie8", "Pass this flag if you don't care about full compliance with Internet Explorer 6-8 quirks (by default UglifyJS will try to be IE-proof).")
|
||||
.describe("expr", "Parse a single expression, rather than a program (for parsing JSON)")
|
||||
@@ -62,6 +63,8 @@ You need to pass an argument to this option to specify the name that your module
|
||||
.describe("lint", "Display some scope warnings")
|
||||
.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")
|
||||
@@ -87,6 +90,7 @@ You need to pass an argument to this option to specify the name that your module
|
||||
.string("p")
|
||||
|
||||
.boolean("expr")
|
||||
.boolean("source-map-include-sources")
|
||||
.boolean("screw-ie8")
|
||||
.boolean("export-all")
|
||||
.boolean("self")
|
||||
@@ -96,6 +100,8 @@ You need to pass an argument to this option to specify the name that your module
|
||||
.boolean("spidermonkey")
|
||||
.boolean("lint")
|
||||
.boolean("V")
|
||||
.boolean("noerr")
|
||||
.boolean("bare-returns")
|
||||
|
||||
.wrap(80)
|
||||
|
||||
@@ -104,6 +110,12 @@ You need to pass an argument to this option to specify the name that your module
|
||||
|
||||
normalize(ARGS);
|
||||
|
||||
if (ARGS.noerr) {
|
||||
UglifyJS.DefaultsError.croak = function(msg, defs) {
|
||||
sys.error("WARN: " + msg);
|
||||
};
|
||||
}
|
||||
|
||||
if (ARGS.version || ARGS.V) {
|
||||
var json = require("../package.json");
|
||||
sys.puts(json.name + ' ' + json.version);
|
||||
@@ -210,6 +222,7 @@ var STATS = {};
|
||||
var OUTPUT_FILE = ARGS.o;
|
||||
var TOPLEVEL = null;
|
||||
var P_RELATIVE = ARGS.p && ARGS.p == "relative";
|
||||
var SOURCES_CONTENT = {};
|
||||
|
||||
var SOURCE_MAP = ARGS.source_map ? UglifyJS.SourceMap({
|
||||
file: P_RELATIVE ? path.relative(path.dirname(ARGS.source_map), OUTPUT_FILE) : OUTPUT_FILE,
|
||||
@@ -247,6 +260,7 @@ async.eachLimit(files, 1, function (file, cb) {
|
||||
}
|
||||
}
|
||||
}
|
||||
SOURCES_CONTENT[file] = code;
|
||||
time_it("parse", function(){
|
||||
if (ARGS.spidermonkey) {
|
||||
var program = JSON.parse(code);
|
||||
@@ -263,9 +277,10 @@ async.eachLimit(files, 1, function (file, cb) {
|
||||
else {
|
||||
try {
|
||||
TOPLEVEL = UglifyJS.parse(code, {
|
||||
filename : file,
|
||||
toplevel : TOPLEVEL,
|
||||
expression : ARGS.expr,
|
||||
filename : file,
|
||||
toplevel : TOPLEVEL,
|
||||
expression : ARGS.expr,
|
||||
bare_returns : ARGS.bare_returns,
|
||||
});
|
||||
} catch(ex) {
|
||||
if (ex instanceof UglifyJS.JS_Parse_Error) {
|
||||
@@ -329,6 +344,15 @@ async.eachLimit(files, 1, function (file, cb) {
|
||||
if (MANGLE) time_it("mangle", function(){
|
||||
TOPLEVEL.mangle_names(MANGLE);
|
||||
});
|
||||
|
||||
if (ARGS.source_map_include_sources) {
|
||||
for (var file in SOURCES_CONTENT) {
|
||||
if (SOURCES_CONTENT.hasOwnProperty(file)) {
|
||||
SOURCE_MAP.get().setSourceContent(file, SOURCES_CONTENT[file]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
time_it("generate", function(){
|
||||
TOPLEVEL.print(output);
|
||||
});
|
||||
@@ -349,7 +373,6 @@ async.eachLimit(files, 1, function (file, cb) {
|
||||
fs.writeFileSync(OUTPUT_FILE, output, "utf8");
|
||||
} else {
|
||||
sys.print(output);
|
||||
sys.error("\n");
|
||||
}
|
||||
|
||||
if (ARGS.stats) {
|
||||
|
||||
12
lib/ast.js
12
lib/ast.js
@@ -295,10 +295,10 @@ var AST_Toplevel = DEFNODE("Toplevel", "globals", {
|
||||
var parameters = [];
|
||||
|
||||
arg_parameter_pairs.forEach(function(pair) {
|
||||
var split = pair.split(":");
|
||||
var splitAt = pair.lastIndexOf(":");
|
||||
|
||||
args.push(split[0]);
|
||||
parameters.push(split[1]);
|
||||
args.push(pair.substr(0, splitAt));
|
||||
parameters.push(pair.substr(splitAt + 1));
|
||||
});
|
||||
|
||||
var wrapped_tl = "(function(" + parameters.join(",") + "){ '$ORIG'; })(" + args.join(",") + ")";
|
||||
@@ -498,12 +498,6 @@ var AST_Try = DEFNODE("Try", "bcatch bfinally", {
|
||||
}
|
||||
}, AST_Block);
|
||||
|
||||
// XXX: this is wrong according to ECMA-262 (12.4). the catch block
|
||||
// should introduce another scope, as the argname should be visible
|
||||
// only inside the catch block. However, doing it this way because of
|
||||
// IE which simply introduces the name in the surrounding scope. If
|
||||
// we ever want to fix this then AST_Catch should inherit from
|
||||
// AST_Scope.
|
||||
var AST_Catch = DEFNODE("Catch", "argname", {
|
||||
$documentation: "A `catch` node; only makes sense as part of a `try` statement",
|
||||
$propdoc: {
|
||||
|
||||
189
lib/compress.js
189
lib/compress.js
@@ -61,6 +61,7 @@ function Compressor(options, false_by_default) {
|
||||
loops : !false_by_default,
|
||||
unused : !false_by_default,
|
||||
hoist_funs : !false_by_default,
|
||||
keep_fargs : false,
|
||||
hoist_vars : false,
|
||||
if_return : !false_by_default,
|
||||
join_vars : !false_by_default,
|
||||
@@ -70,6 +71,8 @@ function Compressor(options, false_by_default) {
|
||||
pure_funcs : null,
|
||||
negate_iife : !false_by_default,
|
||||
screw_ie8 : false,
|
||||
drop_console : false,
|
||||
angular : false,
|
||||
|
||||
warnings : true,
|
||||
global_defs : {}
|
||||
@@ -197,6 +200,9 @@ merge(Compressor.prototype, {
|
||||
var CHANGED;
|
||||
do {
|
||||
CHANGED = false;
|
||||
if (compressor.option("angular")) {
|
||||
statements = process_for_angular(statements);
|
||||
}
|
||||
statements = eliminate_spurious_blocks(statements);
|
||||
if (compressor.option("dead_code")) {
|
||||
statements = eliminate_dead_code(statements, compressor);
|
||||
@@ -218,6 +224,50 @@ merge(Compressor.prototype, {
|
||||
|
||||
return statements;
|
||||
|
||||
function process_for_angular(statements) {
|
||||
function make_injector(func, name) {
|
||||
return make_node(AST_SimpleStatement, func, {
|
||||
body: make_node(AST_Assign, func, {
|
||||
operator: "=",
|
||||
left: make_node(AST_Dot, name, {
|
||||
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 });
|
||||
})
|
||||
})
|
||||
})
|
||||
});
|
||||
}
|
||||
return statements.reduce(function(a, stat){
|
||||
a.push(stat);
|
||||
var token = stat.start;
|
||||
var comments = token.comments_before;
|
||||
if (comments && comments.length > 0) {
|
||||
var last = comments.pop();
|
||||
if (/@ngInject/.test(last.value)) {
|
||||
// case 1: defun
|
||||
if (stat instanceof AST_Defun) {
|
||||
a.push(make_injector(stat, stat.name));
|
||||
}
|
||||
else if (stat instanceof AST_Definitions) {
|
||||
stat.definitions.forEach(function(def){
|
||||
if (def.value && def.value instanceof AST_Lambda) {
|
||||
a.push(make_injector(def.value, def.name));
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
compressor.warn("Unknown statement marked with @ngInject [{file}:{line},{col}]", token);
|
||||
}
|
||||
}
|
||||
}
|
||||
return a;
|
||||
}, []);
|
||||
}
|
||||
|
||||
function eliminate_spurious_blocks(statements) {
|
||||
var seen_dirs = [];
|
||||
return statements.reduce(function(a, stat){
|
||||
@@ -327,7 +377,7 @@ merge(Compressor.prototype, {
|
||||
stat = stat.clone();
|
||||
stat.condition = stat.condition.negate(compressor);
|
||||
stat.body = make_node(AST_BlockStatement, stat, {
|
||||
body: ret
|
||||
body: as_statement_array(stat.alternative).concat(ret)
|
||||
});
|
||||
stat.alternative = make_node(AST_BlockStatement, stat, {
|
||||
body: body
|
||||
@@ -725,6 +775,14 @@ merge(Compressor.prototype, {
|
||||
if (d && d.constant && d.init) return ev(d.init, compressor);
|
||||
throw def;
|
||||
});
|
||||
def(AST_Dot, function(compressor){
|
||||
if (compressor.option("unsafe") && this.property == "length") {
|
||||
var str = ev(this.expression, compressor);
|
||||
if (typeof str == "string")
|
||||
return str.length;
|
||||
}
|
||||
throw def;
|
||||
});
|
||||
})(function(node, func){
|
||||
node.DEFMETHOD("_eval", func);
|
||||
});
|
||||
@@ -839,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))
|
||||
@@ -995,18 +1055,20 @@ merge(Compressor.prototype, {
|
||||
var tt = new TreeTransformer(
|
||||
function before(node, descend, in_list) {
|
||||
if (node instanceof AST_Lambda && !(node instanceof AST_Accessor)) {
|
||||
for (var a = node.argnames, i = a.length; --i >= 0;) {
|
||||
var sym = a[i];
|
||||
if (sym.unreferenced()) {
|
||||
a.pop();
|
||||
compressor.warn("Dropping unused function argument {name} [{file}:{line},{col}]", {
|
||||
name : sym.name,
|
||||
file : sym.start.file,
|
||||
line : sym.start.line,
|
||||
col : sym.start.col
|
||||
});
|
||||
if (!compressor.option("keep_fargs")) {
|
||||
for (var a = node.argnames, i = a.length; --i >= 0;) {
|
||||
var sym = a[i];
|
||||
if (sym.unreferenced()) {
|
||||
a.pop();
|
||||
compressor.warn("Dropping unused function argument {name} [{file}:{line},{col}]", {
|
||||
name : sym.name,
|
||||
file : sym.start.file,
|
||||
line : sym.start.line,
|
||||
col : sym.start.col
|
||||
});
|
||||
}
|
||||
else break;
|
||||
}
|
||||
else break;
|
||||
}
|
||||
}
|
||||
if (node instanceof AST_Defun && node !== self) {
|
||||
@@ -1669,10 +1731,10 @@ merge(Compressor.prototype, {
|
||||
return arg.value;
|
||||
}).join(",") + "){" + self.args[self.args.length - 1].value + "})()";
|
||||
var ast = parse(code);
|
||||
ast.figure_out_scope();
|
||||
ast.figure_out_scope({ screw_ie8: compressor.option("screw_ie8") });
|
||||
var comp = new Compressor(compressor.options);
|
||||
ast = ast.transform(comp);
|
||||
ast.figure_out_scope();
|
||||
ast.figure_out_scope({ screw_ie8: compressor.option("screw_ie8") });
|
||||
ast.mangle_names();
|
||||
var fun;
|
||||
try {
|
||||
@@ -1685,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()
|
||||
@@ -1773,6 +1836,14 @@ merge(Compressor.prototype, {
|
||||
return make_node(AST_Undefined, self).transform(compressor);
|
||||
}
|
||||
}
|
||||
if (compressor.option("drop_console")) {
|
||||
if (self.expression instanceof AST_PropAccess &&
|
||||
self.expression.expression instanceof AST_SymbolRef &&
|
||||
self.expression.expression.name == "console" &&
|
||||
self.expression.expression.undeclared()) {
|
||||
return make_node(AST_Undefined, self).transform(compressor);
|
||||
}
|
||||
}
|
||||
return self.evaluate(compressor)[0];
|
||||
});
|
||||
|
||||
@@ -1811,9 +1882,15 @@ merge(Compressor.prototype, {
|
||||
}
|
||||
if (compressor.option("cascade")) {
|
||||
if (self.car instanceof AST_Assign
|
||||
&& !self.car.left.has_side_effects(compressor)
|
||||
&& self.car.left.equivalent_to(self.cdr)) {
|
||||
return self.car;
|
||||
&& !self.car.left.has_side_effects(compressor)) {
|
||||
if (self.car.left.equivalent_to(self.cdr)) {
|
||||
return self.car;
|
||||
}
|
||||
if (self.cdr instanceof AST_Call
|
||||
&& self.cdr.expression.equivalent_to(self.car.left)) {
|
||||
self.cdr.expression = self.car;
|
||||
return self.cdr;
|
||||
}
|
||||
}
|
||||
if (!self.car.has_side_effects(compressor)
|
||||
&& !self.cdr.has_side_effects(compressor)
|
||||
@@ -1821,6 +1898,18 @@ merge(Compressor.prototype, {
|
||||
return self.car;
|
||||
}
|
||||
}
|
||||
if (self.cdr instanceof AST_UnaryPrefix
|
||||
&& self.cdr.operator == "void"
|
||||
&& !self.cdr.expression.has_side_effects(compressor)) {
|
||||
self.cdr.operator = self.car;
|
||||
return self.cdr;
|
||||
}
|
||||
if (self.cdr instanceof AST_Undefined) {
|
||||
return make_node(AST_UnaryPrefix, self, {
|
||||
operator : "void",
|
||||
expression : self.car
|
||||
});
|
||||
}
|
||||
return self;
|
||||
});
|
||||
|
||||
@@ -2192,7 +2281,7 @@ merge(Compressor.prototype, {
|
||||
* ==>
|
||||
* exp = foo ? something : something_else;
|
||||
*/
|
||||
self = make_node(AST_Assign, self, {
|
||||
return make_node(AST_Assign, self, {
|
||||
operator: consequent.operator,
|
||||
left: consequent.left,
|
||||
right: make_node(AST_Conditional, self, {
|
||||
@@ -2202,6 +2291,49 @@ merge(Compressor.prototype, {
|
||||
})
|
||||
});
|
||||
}
|
||||
if (consequent instanceof AST_Call
|
||||
&& alternative.TYPE === consequent.TYPE
|
||||
&& consequent.args.length == alternative.args.length
|
||||
&& 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;
|
||||
}
|
||||
}
|
||||
// x?y?z:a:a --> x&&y?z:a
|
||||
if (consequent instanceof AST_Conditional
|
||||
&& consequent.alternative.equivalent_to(alternative)) {
|
||||
return make_node(AST_Conditional, self, {
|
||||
condition: make_node(AST_Binary, self, {
|
||||
left: self.condition,
|
||||
operator: "&&",
|
||||
right: consequent.condition
|
||||
}),
|
||||
consequent: consequent.consequent,
|
||||
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;
|
||||
});
|
||||
|
||||
@@ -2239,12 +2371,31 @@ merge(Compressor.prototype, {
|
||||
return make_node(AST_Dot, self, {
|
||||
expression : self.expression,
|
||||
property : prop
|
||||
}).optimize(compressor);
|
||||
}
|
||||
var v = parseFloat(prop);
|
||||
if (!isNaN(v) && v.toString() == prop) {
|
||||
self.property = make_node(AST_Number, self.property, {
|
||||
value: v
|
||||
});
|
||||
}
|
||||
}
|
||||
return self;
|
||||
});
|
||||
|
||||
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];
|
||||
});
|
||||
|
||||
function literals_in_boolean_context(self, compressor) {
|
||||
if (compressor.option("booleans") && compressor.in_boolean_context()) {
|
||||
return make_node(AST_True, self);
|
||||
|
||||
@@ -46,53 +46,68 @@
|
||||
(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[0]),
|
||||
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
|
||||
});
|
||||
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(M.value),
|
||||
key : name,
|
||||
value : from_moz(M.value)
|
||||
};
|
||||
switch (M.kind) {
|
||||
case "init":
|
||||
return new AST_ObjectKeyVal(args);
|
||||
case "set":
|
||||
args.value.name = from_moz(key);
|
||||
return new AST_ObjectSetter(args);
|
||||
case "get":
|
||||
args.value.name = from_moz(key);
|
||||
return new AST_ObjectGetter(args);
|
||||
}
|
||||
},
|
||||
ObjectExpression : function(M) {
|
||||
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;
|
||||
var name = key.type == "Identifier" ? key.name : key.value;
|
||||
var args = {
|
||||
start : my_start_token(key),
|
||||
end : my_end_token(prop.value),
|
||||
key : name,
|
||||
value : from_moz(prop.value)
|
||||
};
|
||||
switch (prop.kind) {
|
||||
case "init":
|
||||
return new AST_ObjectKeyVal(args);
|
||||
case "set":
|
||||
args.value.name = from_moz(key);
|
||||
return new AST_ObjectSetter(args);
|
||||
case "get":
|
||||
args.value.name = from_moz(key);
|
||||
return new AST_ObjectGetter(args);
|
||||
}
|
||||
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)
|
||||
};
|
||||
};
|
||||
|
||||
})();
|
||||
|
||||
120
lib/output.js
120
lib/output.js
@@ -46,22 +46,23 @@
|
||||
function OutputStream(options) {
|
||||
|
||||
options = defaults(options, {
|
||||
indent_start : 0,
|
||||
indent_level : 4,
|
||||
quote_keys : false,
|
||||
space_colon : true,
|
||||
ascii_only : false,
|
||||
inline_script : false,
|
||||
width : 80,
|
||||
max_line_len : 32000,
|
||||
beautify : false,
|
||||
source_map : null,
|
||||
bracketize : false,
|
||||
semicolons : true,
|
||||
comments : false,
|
||||
preserve_line : false,
|
||||
screw_ie8 : false,
|
||||
preamble : null,
|
||||
indent_start : 0,
|
||||
indent_level : 4,
|
||||
quote_keys : false,
|
||||
space_colon : true,
|
||||
ascii_only : false,
|
||||
unescape_regexps : false,
|
||||
inline_script : false,
|
||||
width : 80,
|
||||
max_line_len : 32000,
|
||||
beautify : false,
|
||||
source_map : null,
|
||||
bracketize : false,
|
||||
semicolons : true,
|
||||
comments : false,
|
||||
preserve_line : false,
|
||||
screw_ie8 : false,
|
||||
preamble : null,
|
||||
}, true);
|
||||
|
||||
var indentation = 0;
|
||||
@@ -386,13 +387,20 @@ function OutputStream(options) {
|
||||
var comments = start.comments_before || [];
|
||||
|
||||
// XXX: ugly fix for https://github.com/mishoo/UglifyJS2/issues/112
|
||||
// if this node is `return` or `throw`, we cannot allow comments before
|
||||
// the returned or thrown value.
|
||||
if (self instanceof AST_Exit && self.value
|
||||
&& self.value.start.comments_before
|
||||
&& self.value.start.comments_before.length > 0) {
|
||||
comments = comments.concat(self.value.start.comments_before);
|
||||
self.value.start.comments_before = [];
|
||||
// and https://github.com/mishoo/UglifyJS2/issues/372
|
||||
if (self instanceof AST_Exit && self.value) {
|
||||
self.value.walk(new TreeWalker(function(node){
|
||||
if (node.start && node.start.comments_before) {
|
||||
comments = comments.concat(node.start.comments_before);
|
||||
node.start.comments_before = [];
|
||||
}
|
||||
if (node instanceof AST_Function ||
|
||||
node instanceof AST_Array ||
|
||||
node instanceof AST_Object)
|
||||
{
|
||||
return true; // don't go inside.
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
if (c.test) {
|
||||
@@ -426,7 +434,13 @@ function OutputStream(options) {
|
||||
/* -----[ PARENTHESES ]----- */
|
||||
|
||||
function PARENS(nodetype, func) {
|
||||
nodetype.DEFMETHOD("needs_parens", func);
|
||||
if (Array.isArray(nodetype)) {
|
||||
nodetype.forEach(function(nodetype){
|
||||
PARENS(nodetype, func);
|
||||
});
|
||||
} else {
|
||||
nodetype.DEFMETHOD("needs_parens", func);
|
||||
}
|
||||
};
|
||||
|
||||
PARENS(AST_Node, function(){
|
||||
@@ -445,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;
|
||||
});
|
||||
@@ -456,7 +470,7 @@ function OutputStream(options) {
|
||||
|| p instanceof AST_Unary // !(foo, bar, baz)
|
||||
|| p instanceof AST_Binary // 1 + (2, 3) + 4 ==> 8
|
||||
|| p instanceof AST_VarDef // var a = (1, 2), b = a + a; ==> b == 4
|
||||
|| p instanceof AST_Dot // (1, {foo:2}).foo ==> 2
|
||||
|| p instanceof AST_PropAccess // (1, {foo:2}).foo or (1, {foo:2})["foo"] ==> 2
|
||||
|| p instanceof AST_Array // [ 1, (2, 3), 4 ] ==> [ 1, 3, 4 ]
|
||||
|| p instanceof AST_ObjectProperty // { foo: (1, 2) }.foo ==> 2
|
||||
|| p instanceof AST_Conditional /* (false, true) ? (a = 10, b = 20) : (c = 30)
|
||||
@@ -541,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)
|
||||
@@ -558,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 ]----- */
|
||||
|
||||
@@ -648,7 +659,7 @@ function OutputStream(options) {
|
||||
output.print("for");
|
||||
output.space();
|
||||
output.with_parens(function(){
|
||||
if (self.init) {
|
||||
if (self.init && !(self.init instanceof AST_EmptyStatement)) {
|
||||
if (self.init instanceof AST_Definitions) {
|
||||
self.init.print(output);
|
||||
} else {
|
||||
@@ -988,8 +999,12 @@ function OutputStream(options) {
|
||||
DEFPRINT(AST_UnaryPrefix, function(self, output){
|
||||
var op = self.operator;
|
||||
output.print(op);
|
||||
if (/^[a-z]/i.test(op))
|
||||
if (/^[a-z]/i.test(op)
|
||||
|| (/[+-]$/.test(op)
|
||||
&& self.expression instanceof AST_UnaryPrefix
|
||||
&& /^[+-]/.test(self.expression.operator))) {
|
||||
output.space();
|
||||
}
|
||||
self.expression.print(output);
|
||||
});
|
||||
DEFPRINT(AST_UnaryPostfix, function(self, output){
|
||||
@@ -1111,10 +1126,47 @@ function OutputStream(options) {
|
||||
DEFPRINT(AST_Number, function(self, output){
|
||||
output.print(make_num(self.getValue()));
|
||||
});
|
||||
|
||||
function regexp_safe_literal(code) {
|
||||
return [
|
||||
0x5c , // \
|
||||
0x2f , // /
|
||||
0x2e , // .
|
||||
0x2b , // +
|
||||
0x2a , // *
|
||||
0x3f , // ?
|
||||
0x28 , // (
|
||||
0x29 , // )
|
||||
0x5b , // [
|
||||
0x5d , // ]
|
||||
0x7b , // {
|
||||
0x7d , // }
|
||||
0x24 , // $
|
||||
0x5e , // ^
|
||||
0x3a , // :
|
||||
0x7c , // |
|
||||
0x21 , // !
|
||||
0x0a , // \n
|
||||
0x0d , // \r
|
||||
0x00 , // \0
|
||||
0xfeff , // Unicode BOM
|
||||
0x2028 , // unicode "line separator"
|
||||
0x2029 , // unicode "paragraph separator"
|
||||
].indexOf(code) < 0;
|
||||
};
|
||||
|
||||
DEFPRINT(AST_RegExp, function(self, output){
|
||||
var str = self.getValue().toString();
|
||||
if (output.option("ascii_only"))
|
||||
if (output.option("ascii_only")) {
|
||||
str = output.to_ascii(str);
|
||||
} else if (output.option("unescape_regexps")) {
|
||||
str = str.split("\\\\").map(function(str){
|
||||
return str.replace(/\\u[0-9a-fA-F]{4}|\\x[0-9a-fA-F]{2}/g, function(s){
|
||||
var code = parseInt(s.substr(2), 16);
|
||||
return regexp_safe_literal(code) ? String.fromCharCode(code) : s;
|
||||
});
|
||||
}).join("\\\\");
|
||||
}
|
||||
output.print(str);
|
||||
var p = output.parent();
|
||||
if (p instanceof AST_Binary && /^in/.test(p.operator) && p.left === self)
|
||||
|
||||
16
lib/parse.js
16
lib/parse.js
@@ -46,7 +46,7 @@
|
||||
|
||||
var KEYWORDS = 'break case catch const continue debugger default delete do else finally for function if in instanceof new return switch throw try typeof var void while with';
|
||||
var KEYWORDS_ATOM = 'false null true';
|
||||
var RESERVED_WORDS = 'abstract boolean byte char class double enum export extends final float goto implements import int interface long native package private protected public short static super synchronized this throws transient volatile'
|
||||
var RESERVED_WORDS = 'abstract boolean byte char class double enum export extends final float goto implements import int interface long native package private protected public short static super synchronized this throws transient volatile yield'
|
||||
+ " " + KEYWORDS_ATOM + " " + KEYWORDS;
|
||||
var KEYWORDS_BEFORE_EXPRESSION = 'return new delete throw else case';
|
||||
|
||||
@@ -168,14 +168,7 @@ function is_identifier_char(ch) {
|
||||
};
|
||||
|
||||
function is_identifier_string(str){
|
||||
var i = str.length;
|
||||
if (i == 0) return false;
|
||||
if (!is_identifier_start(str.charCodeAt(0))) return false;
|
||||
while (--i >= 0) {
|
||||
if (!is_identifier_char(str.charAt(i)))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
return /^[a-z_$][a-z0-9_$]*$/i.test(str);
|
||||
};
|
||||
|
||||
function parse_js_number(num) {
|
||||
@@ -616,6 +609,7 @@ function parse($TEXT, options) {
|
||||
toplevel : null,
|
||||
expression : false,
|
||||
html5_comments : true,
|
||||
bare_returns : false,
|
||||
});
|
||||
|
||||
var S = {
|
||||
@@ -795,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", ";")
|
||||
@@ -1381,7 +1375,7 @@ function parse($TEXT, options) {
|
||||
condition : expr,
|
||||
consequent : yes,
|
||||
alternative : expression(false, no_in),
|
||||
end : peek()
|
||||
end : prev()
|
||||
});
|
||||
}
|
||||
return expr;
|
||||
|
||||
54
lib/scope.js
54
lib/scope.js
@@ -71,27 +71,34 @@ SymbolDef.prototype = {
|
||||
}
|
||||
};
|
||||
|
||||
AST_Toplevel.DEFMETHOD("figure_out_scope", function(){
|
||||
// This does what ast_add_scope did in UglifyJS v1.
|
||||
//
|
||||
// Part of it could be done at parse time, but it would complicate
|
||||
// the parser (and it's already kinda complex). It's also worth
|
||||
// having it separated because we might need to call it multiple
|
||||
// times on the same tree.
|
||||
AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){
|
||||
options = defaults(options, {
|
||||
screw_ie8: false
|
||||
});
|
||||
|
||||
// pass 1: setup scope chaining and handle definitions
|
||||
var self = this;
|
||||
var scope = self.parent_scope = null;
|
||||
var defun = null;
|
||||
var nesting = 0;
|
||||
var tw = new TreeWalker(function(node, descend){
|
||||
if (options.screw_ie8 && node instanceof AST_Catch) {
|
||||
var save_scope = scope;
|
||||
scope = new AST_Scope(node);
|
||||
scope.init_scope_vars(nesting);
|
||||
scope.parent_scope = save_scope;
|
||||
descend();
|
||||
scope = save_scope;
|
||||
return true;
|
||||
}
|
||||
if (node instanceof AST_Scope) {
|
||||
node.init_scope_vars(nesting);
|
||||
var save_scope = node.parent_scope = scope;
|
||||
++nesting;
|
||||
scope = node;
|
||||
descend();
|
||||
var save_defun = defun;
|
||||
defun = scope = node;
|
||||
++nesting; descend(); --nesting;
|
||||
scope = save_scope;
|
||||
--nesting;
|
||||
defun = save_defun;
|
||||
return true; // don't descend again in TreeWalker
|
||||
}
|
||||
if (node instanceof AST_Directive) {
|
||||
@@ -108,7 +115,7 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(){
|
||||
node.scope = scope;
|
||||
}
|
||||
if (node instanceof AST_SymbolLambda) {
|
||||
scope.def_function(node);
|
||||
defun.def_function(node);
|
||||
}
|
||||
else if (node instanceof AST_SymbolDefun) {
|
||||
// Careful here, the scope where this should be defined is
|
||||
@@ -116,22 +123,17 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(){
|
||||
// scope when we encounter the AST_Defun node (which is
|
||||
// instanceof AST_Scope) but we get to the symbol a bit
|
||||
// later.
|
||||
(node.scope = scope.parent_scope).def_function(node);
|
||||
(node.scope = defun.parent_scope).def_function(node);
|
||||
}
|
||||
else if (node instanceof AST_SymbolVar
|
||||
|| node instanceof AST_SymbolConst) {
|
||||
var def = scope.def_variable(node);
|
||||
var def = defun.def_variable(node);
|
||||
def.constant = node instanceof AST_SymbolConst;
|
||||
def.init = tw.parent().value;
|
||||
}
|
||||
else if (node instanceof AST_SymbolCatch) {
|
||||
// XXX: this is wrong according to ECMA-262 (12.4). the
|
||||
// `catch` argument name should be visible only inside the
|
||||
// catch block. For a quick fix AST_Catch should inherit
|
||||
// from AST_Scope. Keeping it this way because of IE,
|
||||
// which doesn't obey the standard. (it introduces the
|
||||
// identifier in the enclosing scope)
|
||||
scope.def_variable(node);
|
||||
(options.screw_ie8 ? scope : defun)
|
||||
.def_variable(node);
|
||||
}
|
||||
});
|
||||
self.walk(tw);
|
||||
@@ -244,6 +246,11 @@ AST_Scope.DEFMETHOD("next_mangled", function(options){
|
||||
out: while (true) {
|
||||
var m = base54(++this.cname);
|
||||
if (!is_identifier(m)) continue; // skip over "do"
|
||||
|
||||
// https://github.com/mishoo/UglifyJS2/issues/242 -- do not
|
||||
// shadow a name excepted from mangling.
|
||||
if (options.except.indexOf(m) >= 0) continue;
|
||||
|
||||
// we must ensure that the mangled name does not shadow a name
|
||||
// from some parent scope that is referenced in this or in
|
||||
// inner scopes.
|
||||
@@ -358,6 +365,10 @@ AST_Toplevel.DEFMETHOD("mangle_names", function(options){
|
||||
node.mangled_name = name;
|
||||
return true;
|
||||
}
|
||||
if (options.screw_ie8 && node instanceof AST_SymbolCatch) {
|
||||
to_mangle.push(node.definition());
|
||||
return;
|
||||
}
|
||||
});
|
||||
this.walk(tw);
|
||||
to_mangle.forEach(function(def){ def.mangle(options) });
|
||||
@@ -521,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",
|
||||
|
||||
@@ -49,6 +49,9 @@ function SourceMap(options) {
|
||||
file : null,
|
||||
root : null,
|
||||
orig : null,
|
||||
|
||||
orig_line_diff : 0,
|
||||
dest_line_diff : 0,
|
||||
});
|
||||
var generator = new MOZ_SourceMap.SourceMapGenerator({
|
||||
file : options.file,
|
||||
@@ -61,14 +64,17 @@ function SourceMap(options) {
|
||||
line: orig_line,
|
||||
column: orig_col
|
||||
});
|
||||
if (info.source === null) {
|
||||
return;
|
||||
}
|
||||
source = info.source;
|
||||
orig_line = info.line;
|
||||
orig_col = info.column;
|
||||
name = info.name;
|
||||
name = info.name || name;
|
||||
}
|
||||
generator.addMapping({
|
||||
generated : { line: gen_line, column: gen_col },
|
||||
original : { line: orig_line, column: orig_col },
|
||||
generated : { line: gen_line + options.dest_line_diff, column: gen_col },
|
||||
original : { line: orig_line + options.orig_line_diff, column: orig_col },
|
||||
source : source,
|
||||
name : name
|
||||
});
|
||||
|
||||
@@ -82,16 +82,23 @@ function repeat_string(str, i) {
|
||||
};
|
||||
|
||||
function DefaultsError(msg, defs) {
|
||||
Error.call(this, msg);
|
||||
this.msg = msg;
|
||||
this.defs = defs;
|
||||
};
|
||||
DefaultsError.prototype = Object.create(Error.prototype);
|
||||
DefaultsError.prototype.constructor = DefaultsError;
|
||||
|
||||
DefaultsError.croak = function(msg, defs) {
|
||||
throw new DefaultsError(msg, defs);
|
||||
};
|
||||
|
||||
function defaults(args, defs, croak) {
|
||||
if (args === true)
|
||||
args = {};
|
||||
var ret = args || {};
|
||||
if (croak) for (var i in ret) if (ret.hasOwnProperty(i) && !defs.hasOwnProperty(i))
|
||||
throw new DefaultsError("`" + i + "` is not a supported option", defs);
|
||||
DefaultsError.croak("`" + i + "` is not a supported option", defs);
|
||||
for (var i in defs) if (defs.hasOwnProperty(i)) {
|
||||
ret[i] = (args && args.hasOwnProperty(i)) ? args[i] : defs[i];
|
||||
}
|
||||
|
||||
17
package.json
17
package.json
@@ -1,9 +1,9 @@
|
||||
{
|
||||
"name": "uglify-js",
|
||||
"description": "JavaScript parser, mangler/compressor and beautifier toolkit",
|
||||
"name": "uglify-js",
|
||||
"description": "JavaScript parser, mangler/compressor and beautifier toolkit",
|
||||
"homepage": "http://lisperator.net/uglifyjs",
|
||||
"main": "tools/node.js",
|
||||
"version": "2.4.4",
|
||||
"version": "2.4.16",
|
||||
"engines": { "node" : ">=0.4.0" },
|
||||
"maintainers": [{
|
||||
"name": "Mihai Bazon",
|
||||
@@ -16,15 +16,22 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"async" : "~0.2.6",
|
||||
"source-map" : "~0.1.7",
|
||||
"optimist" : "~0.3.5",
|
||||
"source-map" : "0.1.34",
|
||||
"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"}
|
||||
}
|
||||
|
||||
@@ -141,3 +141,174 @@ ifs_6: {
|
||||
x = foo || bar || baz || boo ? 20 : 10;
|
||||
}
|
||||
}
|
||||
|
||||
cond_1: {
|
||||
options = {
|
||||
conditionals: true
|
||||
};
|
||||
input: {
|
||||
if (some_condition()) {
|
||||
do_something(x);
|
||||
} else {
|
||||
do_something(y);
|
||||
}
|
||||
}
|
||||
expect: {
|
||||
do_something(some_condition() ? x : y);
|
||||
}
|
||||
}
|
||||
|
||||
cond_2: {
|
||||
options = {
|
||||
conditionals: true
|
||||
};
|
||||
input: {
|
||||
if (some_condition()) {
|
||||
x = new FooBar(1);
|
||||
} else {
|
||||
x = new FooBar(2);
|
||||
}
|
||||
}
|
||||
expect: {
|
||||
x = new FooBar(some_condition() ? 1 : 2);
|
||||
}
|
||||
}
|
||||
|
||||
cond_3: {
|
||||
options = {
|
||||
conditionals: true
|
||||
};
|
||||
input: {
|
||||
if (some_condition()) {
|
||||
new FooBar(1);
|
||||
} else {
|
||||
FooBar(2);
|
||||
}
|
||||
}
|
||||
expect: {
|
||||
some_condition() ? new FooBar(1) : FooBar(2);
|
||||
}
|
||||
}
|
||||
|
||||
cond_4: {
|
||||
options = {
|
||||
conditionals: true
|
||||
};
|
||||
input: {
|
||||
if (some_condition()) {
|
||||
do_something();
|
||||
} else {
|
||||
do_something();
|
||||
}
|
||||
}
|
||||
expect: {
|
||||
some_condition(), do_something();
|
||||
}
|
||||
}
|
||||
|
||||
cond_5: {
|
||||
options = {
|
||||
conditionals: true
|
||||
};
|
||||
input: {
|
||||
if (some_condition()) {
|
||||
if (some_other_condition()) {
|
||||
do_something();
|
||||
} else {
|
||||
alternate();
|
||||
}
|
||||
} else {
|
||||
alternate();
|
||||
}
|
||||
|
||||
if (some_condition()) {
|
||||
if (some_other_condition()) {
|
||||
do_something();
|
||||
}
|
||||
}
|
||||
}
|
||||
expect: {
|
||||
some_condition() && some_other_condition() ? do_something() : alternate();
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,3 +119,47 @@ unused_keep_setter_arg: {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unused_var_in_catch: {
|
||||
options = { unused: true };
|
||||
input: {
|
||||
function foo() {
|
||||
try {
|
||||
foo();
|
||||
} catch(ex) {
|
||||
var x = 10;
|
||||
}
|
||||
}
|
||||
}
|
||||
expect: {
|
||||
function foo() {
|
||||
try {
|
||||
foo();
|
||||
} catch(ex) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
used_var_in_catch: {
|
||||
options = { unused: true };
|
||||
input: {
|
||||
function foo() {
|
||||
try {
|
||||
foo();
|
||||
} catch(ex) {
|
||||
var x = 10;
|
||||
}
|
||||
return x;
|
||||
}
|
||||
}
|
||||
expect: {
|
||||
function foo() {
|
||||
try {
|
||||
foo();
|
||||
} catch(ex) {
|
||||
var x = 10;
|
||||
}
|
||||
return x;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,16 +60,16 @@ negate_iife_4: {
|
||||
};
|
||||
input: {
|
||||
if ((function(){ return true })()) {
|
||||
console.log(true);
|
||||
foo(true);
|
||||
} else {
|
||||
console.log(false);
|
||||
bar(false);
|
||||
}
|
||||
(function(){
|
||||
console.log("something");
|
||||
})();
|
||||
}
|
||||
expect: {
|
||||
!function(){ return true }() ? console.log(false) : console.log(true), function(){
|
||||
!function(){ return true }() ? bar(false) : foo(true), function(){
|
||||
console.log("something");
|
||||
}();
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ dot_properties: {
|
||||
a.foo = "bar";
|
||||
a["if"] = "if";
|
||||
a["*"] = "asterisk";
|
||||
a.\u0EB3 = "unicode";
|
||||
a["\u0EB3"] = "unicode";
|
||||
a[""] = "whitespace";
|
||||
a["1_1"] = "foo";
|
||||
}
|
||||
@@ -48,7 +48,27 @@ dot_properties_es5: {
|
||||
a.foo = "bar";
|
||||
a.if = "if";
|
||||
a["*"] = "asterisk";
|
||||
a.\u0EB3 = "unicode";
|
||||
a["\u0EB3"] = "unicode";
|
||||
a[""] = "whitespace";
|
||||
}
|
||||
}
|
||||
|
||||
evaluate_length: {
|
||||
options = {
|
||||
properties: true,
|
||||
unsafe: true,
|
||||
evaluate: true
|
||||
};
|
||||
input: {
|
||||
a = "foo".length;
|
||||
a = ("foo" + "bar")["len" + "gth"];
|
||||
a = b.length;
|
||||
a = ("foo" + b).length;
|
||||
}
|
||||
expect: {
|
||||
a = 3;
|
||||
a = 6;
|
||||
a = b.length;
|
||||
a = ("foo" + b).length;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
103
test/mozilla-ast.js
Normal 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");
|
||||
};
|
||||
@@ -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() {
|
||||
|
||||
@@ -51,6 +51,7 @@ for (var i in UglifyJS) {
|
||||
|
||||
exports.minify = function(files, options) {
|
||||
options = UglifyJS.defaults(options, {
|
||||
spidermonkey : false,
|
||||
outSourceMap : null,
|
||||
sourceRoot : null,
|
||||
inSourceMap : null,
|
||||
@@ -60,22 +61,28 @@ exports.minify = function(files, options) {
|
||||
output : null,
|
||||
compress : {}
|
||||
});
|
||||
if (typeof files == "string")
|
||||
files = [ files ];
|
||||
|
||||
UglifyJS.base54.reset();
|
||||
|
||||
// 1. parse
|
||||
var toplevel = null;
|
||||
files.forEach(function(file){
|
||||
var code = options.fromString
|
||||
? file
|
||||
: fs.readFileSync(file, "utf8");
|
||||
toplevel = UglifyJS.parse(code, {
|
||||
filename: options.fromString ? "?" : file,
|
||||
toplevel: toplevel
|
||||
var toplevel = null,
|
||||
sourcesContent = {};
|
||||
|
||||
if (options.spidermonkey) {
|
||||
toplevel = UglifyJS.AST_Node.from_mozilla_ast(files);
|
||||
} else {
|
||||
if (typeof files == "string")
|
||||
files = [ files ];
|
||||
files.forEach(function(file){
|
||||
var code = options.fromString
|
||||
? file
|
||||
: fs.readFileSync(file, "utf8");
|
||||
sourcesContent[file] = code;
|
||||
toplevel = UglifyJS.parse(code, {
|
||||
filename: options.fromString ? "?" : file,
|
||||
toplevel: toplevel
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 2. compress
|
||||
if (options.compress) {
|
||||
@@ -105,12 +112,25 @@ exports.minify = function(files, options) {
|
||||
orig: inMap,
|
||||
root: options.sourceRoot
|
||||
});
|
||||
if (options.sourceMapIncludeSources) {
|
||||
for (var file in sourcesContent) {
|
||||
if (sourcesContent.hasOwnProperty(file)) {
|
||||
output.source_map.get().setSourceContent(file, sourcesContent[file]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
if (options.output) {
|
||||
UglifyJS.merge(output, options.output);
|
||||
}
|
||||
var stream = UglifyJS.OutputStream(output);
|
||||
toplevel.print(stream);
|
||||
|
||||
if(options.outSourceMap){
|
||||
stream += "\n//# sourceMappingURL=" + options.outSourceMap;
|
||||
}
|
||||
|
||||
return {
|
||||
code : stream + "",
|
||||
map : output.source_map + ""
|
||||
|
||||
Reference in New Issue
Block a user