Compare commits

..

45 Commits

Author SHA1 Message Date
Mihai Bazon
db66eca958 v2.2.5 2013-02-14 12:51:13 +02:00
Mihai Bazon
916faf0a48 Force space after literal regexp when used in "instanceof" or "in"
Close #118
2013-02-06 11:57:59 +02:00
Mihai Bazon
f36e4e9a78 Give up evaluating (unary-prefix '-' 0)
Close #117

------

    JS, WHY YOU SUCK SO BADLY? ;-(
2013-02-06 11:51:09 +02:00
Mihai Bazon
fdf8b5eb71 Fix parens for NaN
Close #116
2013-02-06 11:38:29 +02:00
Mihai Bazon
de7ec7f1b7 Fix parens for negative numbers
Close #115
2013-02-06 11:36:04 +02:00
Mihai Bazon
3c8a0bdff4 Fix parens for AST_New
Close #114
2013-02-06 11:28:49 +02:00
Mihai Bazon
9e8ba27dcd Fix handling of constants
Close #113
2013-02-06 11:15:31 +02:00
Mihai Bazon
719a8fd102 Ugly hack to print comments before return/throw statements
Close #112
2013-02-05 19:11:39 +02:00
Mihai Bazon
3a22e917de Merge pull request #111 from mattrobenolt/safer-sourcemap
Wraps sourceMappingURL in a multiline comment. Fixes #108
2013-02-03 23:44:31 -08:00
Matt Robenolt
a9af2c9e62 Wraps sourceMappingURL in a multiline comment. Fixes #108 2013-02-03 16:01:01 -08:00
Mihai Bazon
31e99cebe7 v2.2.4 2013-02-01 13:32:15 +02:00
Mihai Bazon
a5b209470c Fix end token for Assign nodes 2013-02-01 13:32:15 +02:00
Mihai Bazon
e9a571b2a1 Merge pull request #94 from paulmillr/patch-1
Add better fromstring docs.
2013-01-31 23:50:59 -08:00
Mihai Bazon
8bf83f42ea Merge pull request #106 from gibson042/105
Fix #105: property comparison to undefined is not always safe
2013-01-24 05:51:33 -08:00
Richard Gibson
522566ea80 Fix #105: property comparison to undefined is not always safe 2013-01-23 23:52:04 -05:00
Mihai Bazon
297af47c89 Add --source-map-url option
Fix #100
Fix #47
2013-01-20 12:32:07 +02:00
Mihai Bazon
faa354f5ca [AST_Hole] the print function can be a no-op. 2013-01-17 11:36:10 +02:00
David Glasser
1529ab965a Fix output for arrays containing undefined values.
1b6bcca7 was a first attempt at this. That commit made Uglify stop replacing
holes with undefined, but instead it started replacing undefined with
holes. This is slightly problematic, because there is a difference between a
hole and an undefined value. More problematically, it changed [1,undefined] to
[1,] which generally doesn't even parse as a hole (just as a trailing comma), so
it didn't even preserve the length of the array!

Instead, parse holes as their own special AST node which prints invisibly.
2013-01-17 11:36:10 +02:00
Mihai Bazon
605f330e69 Merge pull request #98 from ForbesLindesay/patch-1
Update installation instructions
2013-01-17 01:08:59 -08:00
Mihai Bazon
f0909bdc8f Handle String() with no arguments.
Fix #91
2013-01-17 11:01:38 +02:00
Forbes Lindesay
c13e7e621d Update installation instructions re #4 2013-01-17 00:13:42 +00:00
Paul Miller
ad071f8017 Add better fromstring docs. 2013-01-13 18:45:43 +02:00
Mihai Bazon
c058d8b9cd Merge pull request #90 from jakearchibald/patch-1
Compressor options use underscores rather than hyphens
2013-01-08 14:21:25 -08:00
Jake Archibald
1d8871a092 Compressor options use underscores rather than hyphens 2013-01-08 12:33:58 -08:00
Mihai Bazon
16953c2064 v2.2.3 2013-01-04 22:50:53 +02:00
Mihai Bazon
6b14f7c224 Fix handling of labels in nested scopes 2013-01-04 14:17:33 +02:00
Mihai Bazon
130c623be7 Support output, mangle and compress options to UglifyJS.minify.
Close #57
Close #86
Close #33
2013-01-04 11:25:13 +02:00
Mihai Bazon
47c9895d59 Merge pull request #87 from BenoitZugmeyer/master
Add a --version option
2013-01-03 02:28:35 -08:00
Benoît Zugmeyer
ba403331c5 Set --version as a boolean #87 2013-01-03 11:22:37 +01:00
Benoît Zugmeyer
e82e89d1b0 --version option 2013-01-03 11:07:53 +01:00
Mihai Bazon
83a4ebfedc Implement -m sort=true
close #83
2013-01-02 12:39:00 +02:00
Mihai Bazon
9916d0e547 Accept string or number as name of an accessor.
[not sure I'm happy about this fix]

Reference mishoo/UglifyJS#478
2012-12-22 01:24:04 +02:00
Mihai Bazon
31c4a37e37 Optimize new Array(1, 2, 3) → [1, 2, 3]
Close #74
2012-12-21 21:04:35 +02:00
Mihai Bazon
08219f0cee Fix output when semicolons is off.
(need to force a semicolon for the empty body of an `if`)

Close #72
2012-12-21 11:57:08 +02:00
Mihai Bazon
c4993e1e5c Small cleanup 2012-12-12 11:51:55 +02:00
Mihai Bazon
6064bea3db v2.2.2 2012-12-06 14:25:18 +02:00
Mihai Bazon
98978fc827 Add proper parens in "NoIn" expressions.
fix #60.
2012-12-06 12:27:57 +02:00
Mihai Bazon
16430acc1f small improvement on merging assignments into hoisted vars 2012-12-05 13:14:49 +02:00
Mihai Bazon
320c110b33 When hoisting variables, try to merge in assignments that follow. 2012-12-05 12:30:25 +02:00
Mihai Bazon
dbe33bbfc5 Revert "Fixed reading from STDIN."
It breaks usage like this:

    echo '...code...' | uglifyjs

This reverts commit e48802ad29.
2012-11-30 11:33:50 +02:00
Mihai Bazon
b5c3253b49 Add test for issue #59 2012-11-30 11:26:37 +02:00
Mihai Bazon
5cc90db7d0 Don't messup compressor stack while optimizing Switch
Fix #59
2012-11-30 11:16:09 +02:00
Mihai Bazon
f427e5efc7 Merge pull request #58 from roxeteer/master
Fixed reading from STDIN
2012-11-29 01:23:07 -08:00
Visa Kopu
e48802ad29 Fixed reading from STDIN. 2012-11-29 10:51:15 +02:00
Mihai Bazon
13c4dfcabd fix #55 2012-11-24 10:02:08 +02:00
13 changed files with 327 additions and 105 deletions

View File

@@ -12,7 +12,14 @@ Chrome and probably Safari).
Install Install
------- -------
From NPM: First make sure you have installed the latest version of [node.js](http://nodejs.org/)
(You may need to restart your computer after this step).
From NPM for use as a command line app:
npm install uglify-js -g
From NPM for programmatic use:
npm install uglify-js npm install uglify-js
@@ -42,6 +49,9 @@ The available options are:
[string] [string]
--source-map-root The path to the original source to be included in the --source-map-root The path to the original source to be included in the
source map. [string] source map. [string]
--source-map-url The path to the source map to be added in //@
sourceMappingURL. Defaults to the value passed with
--source-map. [string]
--in-source-map Input source map, useful if you're compressing JS that was --in-source-map Input source map, useful if you're compressing JS that was
generated from some other original code. generated from some other original code.
-p, --prefix Skip prefix for original filenames that appear in source -p, --prefix Skip prefix for original filenames that appear in source
@@ -78,7 +88,9 @@ The available options are:
[string] [string]
--export-all Only used when --wrap, this tells UglifyJS to add code to --export-all Only used when --wrap, this tells UglifyJS to add code to
automatically export all globals. [boolean] automatically export all globals. [boolean]
--lint Display some scope warnings [boolean]
-v, --verbose Verbose [boolean] -v, --verbose Verbose [boolean]
-V, --version Print version number and exit. [boolean]
Specify `--output` (`-o`) to declare the output file. Otherwise the output Specify `--output` (`-o`) to declare the output file. Otherwise the output
goes to STDOUT. goes to STDOUT.
@@ -130,7 +142,7 @@ input files from the command line.
## Mangler options ## Mangler options
To enable the mangler you need to pass `--mangle` (`-m`). Optionally you To enable the mangler you need to pass `--mangle` (`-m`). Optionally you
can pass `-m sort` (we'll possibly have other flags in the future) in order can pass `-m sort=true` (we'll possibly have other flags in the future) in order
to assign shorter names to most frequently used variables. This saves a few to assign shorter names to most frequently used variables. This saves a few
hundred bytes on jQuery before gzip, but the output is _bigger_ after gzip hundred bytes on jQuery before gzip, but the output is _bigger_ after gzip
(and seems to happen for other libraries I tried it on) therefore it's not (and seems to happen for other libraries I tried it on) therefore it's not
@@ -157,8 +169,8 @@ the available options (all are `true` by default, except `hoist_vars`):
- `sequences` -- join consecutive simple statements using the comma operator - `sequences` -- join consecutive simple statements using the comma operator
- `properties` -- rewrite property access using the dot notation, for - `properties` -- rewrite property access using the dot notation, for
example `foo["bar"] → foo.bar` example `foo["bar"] → foo.bar`
- `dead-code` -- remove unreachable code - `dead_code` -- remove unreachable code
- `drop-debugger` -- remove `debugger;` statements - `drop_debugger` -- remove `debugger;` statements
- `unsafe` -- apply "unsafe" transformations (discussion below) - `unsafe` -- apply "unsafe" transformations (discussion below)
- `conditionals` -- apply optimizations for `if`-s and conditional - `conditionals` -- apply optimizations for `if`-s and conditional
expressions expressions
@@ -171,11 +183,11 @@ the available options (all are `true` by default, except `hoist_vars`):
- `loops` -- optimizations for `do`, `while` and `for` loops when we can - `loops` -- optimizations for `do`, `while` and `for` loops when we can
statically determine the condition statically determine the condition
- `unused` -- drop unreferenced functions and variables - `unused` -- drop unreferenced functions and variables
- `hoist-funs` -- hoist function declarations - `hoist_funs` -- hoist function declarations
- `hoist-vars` -- hoist `var` declarations (this is `false` by default - `hoist_vars` -- hoist `var` declarations (this is `false` by default
because it seems to increase the size of the output in general) because it seems to increase the size of the output in general)
- `if-return` -- optimizations for if/return and if/continue - `if_return` -- optimizations for if/return and if/continue
- `join-vars` -- join consecutive `var` statements - `join_vars` -- join consecutive `var` statements
- `cascade` -- small optimization for sequences, transform `x, x` into `x` - `cascade` -- small optimization for sequences, transform `x, x` into `x`
and `x = something(), x` into `x = something()` and `x = something(), x` into `x = something()`
- `warnings` -- display warnings when dropping unreachable code or unused - `warnings` -- display warnings when dropping unreachable code or unused
@@ -213,6 +225,7 @@ will evaluate references to them to the value itself and drop unreachable
code as usual. The possible downside of this approach is that the build code as usual. The possible downside of this approach is that the build
will contain the `const` declarations. will contain the `const` declarations.
<a name="codegen-options"></a>
## Beautifier options ## Beautifier options
The code generator tries to output shortest code possible by default. In The code generator tries to output shortest code possible by default. In
@@ -322,9 +335,10 @@ There's a single toplevel function which combines all the steps. If you
don't need additional customization, you might want to go with `minify`. don't need additional customization, you might want to go with `minify`.
Example: Example:
// see "fromString" below if you need to pass code instead of file name
var result = UglifyJS.minify("/path/to/file.js"); var result = UglifyJS.minify("/path/to/file.js");
console.log(result.code); // minified output console.log(result.code); // minified output
// if you need to pass code instead of file name
var result = UglifyJS.minify("var b = function () {};", {fromString: true});
You can also compress multiple files: You can also compress multiple files:
@@ -366,9 +380,19 @@ no sense otherwise).
Other options: Other options:
- `warnings` (default `false`) — pass `true` to display compressor warnings. - `warnings` (default `false`) — pass `true` to display compressor warnings.
- `fromString` (default `false`) — if you pass `true` then you can pass - `fromString` (default `false`) — if you pass `true` then you can pass
JavaScript source code, rather than file names. JavaScript source code, rather than file names.
- `mangle` — pass `false` to skip mangling names.
- `output` (default `null`) — pass an object if you wish to specify
additional [output options][codegen]. The defaults are optimized
for best compression.
- `compress` (default `{}`) — pass `false` to skip compressing entirely.
Pass an object to specify custom [compressor options][compressor].
We could add more options to `UglifyJS.minify` — if you need additional We could add more options to `UglifyJS.minify` — if you need additional
functionality please suggest! functionality please suggest!
@@ -516,3 +540,5 @@ The `source_map_options` (optional) can contain the following properties:
[acorn]: https://github.com/marijnh/acorn [acorn]: https://github.com/marijnh/acorn
[source-map]: https://github.com/mozilla/source-map [source-map]: https://github.com/mozilla/source-map
[sm-spec]: https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit [sm-spec]: https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit
[codegen]: http://lisperator.net/uglifyjs/codegen
[compressor]: http://lisperator.net/uglifyjs/compress

View File

@@ -19,6 +19,7 @@ mangling you need to use `-c` and `-m`.\
") ")
.describe("source-map", "Specify an output file where to generate source map.") .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-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("in-source-map", "Input source map, useful if you're compressing JS that was generated from some other original code.") .describe("in-source-map", "Input source map, useful if you're compressing JS that was generated from some other original code.")
.describe("p", "Skip prefix for original filenames that appear in source maps. \ .describe("p", "Skip prefix for original filenames that appear in source maps. \
For example -p 3 will drop 3 directories from file names and ensure they are relative paths.") For example -p 3 will drop 3 directories from file names and ensure they are relative paths.")
@@ -49,6 +50,7 @@ You need to pass an argument to this option to specify the name that your module
.describe("export-all", "Only used when --wrap, this tells UglifyJS to add code to automatically export all globals.") .describe("export-all", "Only used when --wrap, this tells UglifyJS to add code to automatically export all globals.")
.describe("lint", "Display some scope warnings") .describe("lint", "Display some scope warnings")
.describe("v", "Verbose") .describe("v", "Verbose")
.describe("V", "Print version number and exit.")
.alias("p", "prefix") .alias("p", "prefix")
.alias("o", "output") .alias("o", "output")
@@ -58,9 +60,11 @@ You need to pass an argument to this option to specify the name that your module
.alias("c", "compress") .alias("c", "compress")
.alias("d", "define") .alias("d", "define")
.alias("r", "reserved") .alias("r", "reserved")
.alias("V", "version")
.string("source-map") .string("source-map")
.string("source-map-root") .string("source-map-root")
.string("source-map-url")
.string("b") .string("b")
.string("m") .string("m")
.string("c") .string("c")
@@ -74,6 +78,7 @@ You need to pass an argument to this option to specify the name that your module
.boolean("acorn") .boolean("acorn")
.boolean("spidermonkey") .boolean("spidermonkey")
.boolean("lint") .boolean("lint")
.boolean("V")
.wrap(80) .wrap(80)
@@ -82,6 +87,12 @@ You need to pass an argument to this option to specify the name that your module
normalize(ARGS); normalize(ARGS);
if (ARGS.version || ARGS.V) {
var json = require("../package.json");
sys.puts(json.name + ' ' + json.version);
process.exit(0);
}
if (ARGS.ast_help) { if (ARGS.ast_help) {
var desc = UglifyJS.describe_ast(); var desc = UglifyJS.describe_ast();
sys.puts(typeof desc == "string" ? desc : JSON.stringify(desc, null, 2)); sys.puts(typeof desc == "string" ? desc : JSON.stringify(desc, null, 2));
@@ -268,7 +279,7 @@ output = output.get();
if (SOURCE_MAP) { if (SOURCE_MAP) {
fs.writeFileSync(ARGS.source_map, SOURCE_MAP, "utf8"); fs.writeFileSync(ARGS.source_map, SOURCE_MAP, "utf8");
output += "\n//@ sourceMappingURL=" + ARGS.source_map; output += "\n/*\n//@ sourceMappingURL=" + (ARGS.source_map_url || ARGS.source_map) + "\n*/";
} }
if (OUTPUT_FILE) { if (OUTPUT_FILE) {

View File

@@ -287,9 +287,9 @@ var AST_Toplevel = DEFNODE("Toplevel", "globals", {
}, },
wrap_commonjs: function(name, export_all) { wrap_commonjs: function(name, export_all) {
var self = this; var self = this;
var to_export = [];
if (export_all) { if (export_all) {
self.figure_out_scope(); self.figure_out_scope();
var to_export = [];
self.walk(new TreeWalker(function(node){ self.walk(new TreeWalker(function(node){
if (node instanceof AST_SymbolDeclaration && node.definition().global) { if (node instanceof AST_SymbolDeclaration && node.definition().global) {
if (!find_if(function(n){ return n.name == node.name }, to_export)) if (!find_if(function(n){ return n.name == node.name }, to_export))
@@ -863,6 +863,11 @@ var AST_Undefined = DEFNODE("Undefined", null, {
value: (function(){}()) value: (function(){}())
}, AST_Atom); }, AST_Atom);
var AST_Hole = DEFNODE("Hole", null, {
$documentation: "A hole in an array",
value: (function(){}())
}, AST_Atom);
var AST_Infinity = DEFNODE("Infinity", null, { var AST_Infinity = DEFNODE("Infinity", null, {
$documentation: "The `Infinity` value", $documentation: "The `Infinity` value",
value: 1/0 value: 1/0

View File

@@ -628,7 +628,10 @@ merge(Compressor.prototype, {
case "typeof": return typeof ev(e); case "typeof": return typeof ev(e);
case "void": return void ev(e); case "void": return void ev(e);
case "~": return ~ev(e); case "~": return ~ev(e);
case "-": return -ev(e); case "-":
e = ev(e);
if (e === 0) throw def;
return -e;
case "+": return +ev(e); case "+": return +ev(e);
} }
throw def; throw def;
@@ -669,12 +672,7 @@ merge(Compressor.prototype, {
}); });
def(AST_SymbolRef, function(){ def(AST_SymbolRef, function(){
var d = this.definition(); var d = this.definition();
if (d && d.constant) { if (d && d.constant && d.init) return ev(d.init);
var orig = d.orig[0];
if (orig) orig = orig.init[0];
orig = orig && orig.value;
if (orig) return ev(orig);
}
throw def; throw def;
}); });
})(function(node, func){ })(function(node, func){
@@ -883,18 +881,23 @@ merge(Compressor.prototype, {
&& !self.uses_eval && !self.uses_eval
) { ) {
var in_use = []; var in_use = [];
var initializations = new Dictionary();
// pass 1: find out which symbols are directly used in // pass 1: find out which symbols are directly used in
// this scope (not in nested scopes). // this scope (not in nested scopes).
var scope = this; var scope = this;
var tw = new TreeWalker(function(node, descend){ var tw = new TreeWalker(function(node, descend){
if (node !== self) { if (node !== self) {
if (node instanceof AST_Defun) { if (node instanceof AST_Defun) {
initializations.add(node.name.name, node);
return true; // don't go in nested scopes return true; // don't go in nested scopes
} }
if (node instanceof AST_Definitions && scope === self) { if (node instanceof AST_Definitions && scope === self) {
node.definitions.forEach(function(def){ node.definitions.forEach(function(def){
if (def.value && def.value.has_side_effects()) { if (def.value) {
def.value.walk(tw); initializations.add(def.name.name, def.value);
if (def.value.has_side_effects()) {
def.value.walk(tw);
}
} }
}); });
return true; return true;
@@ -919,16 +922,15 @@ merge(Compressor.prototype, {
for (var i = 0; i < in_use.length; ++i) { for (var i = 0; i < in_use.length; ++i) {
in_use[i].orig.forEach(function(decl){ in_use[i].orig.forEach(function(decl){
// undeclared globals will be instanceof AST_SymbolRef // undeclared globals will be instanceof AST_SymbolRef
if (decl instanceof AST_SymbolDeclaration) { var init = initializations.get(decl.name);
decl.init.forEach(function(init){ if (init) init.forEach(function(init){
var tw = new TreeWalker(function(node){ var tw = new TreeWalker(function(node){
if (node instanceof AST_SymbolRef) { if (node instanceof AST_SymbolRef) {
push_uniq(in_use, node.definition()); push_uniq(in_use, node.definition());
} }
});
init.walk(tw);
}); });
} init.walk(tw);
});
}); });
} }
// pass 3: we should drop declarations not in_use // pass 3: we should drop declarations not in_use
@@ -1100,13 +1102,71 @@ merge(Compressor.prototype, {
} }
); );
self = self.transform(tt); self = self.transform(tt);
if (vars_found > 0) hoisted.unshift(make_node(AST_Var, self, { if (vars_found > 0) {
definitions: vars.map(function(def){ // collect only vars which don't show up in self's arguments list
def = def.clone(); var defs = [];
def.value = null; vars.each(function(def, name){
return def; if (self instanceof AST_Lambda
}) && find_if(function(x){ return x.name == def.name.name },
})); self.argnames)) {
vars.del(name);
} else {
def = def.clone();
def.value = null;
defs.push(def);
vars.set(name, def);
}
});
if (defs.length > 0) {
// try to merge in assignments
for (var i = 0; i < self.body.length;) {
if (self.body[i] instanceof AST_SimpleStatement) {
var expr = self.body[i].body, sym, assign;
if (expr instanceof AST_Assign
&& expr.operator == "="
&& (sym = expr.left) instanceof AST_Symbol
&& vars.has(sym.name))
{
var def = vars.get(sym.name);
if (def.value) break;
def.value = expr.right;
remove(defs, def);
defs.push(def);
self.body.splice(i, 1);
continue;
}
if (expr instanceof AST_Seq
&& (assign = expr.car) instanceof AST_Assign
&& assign.operator == "="
&& (sym = assign.left) instanceof AST_Symbol
&& vars.has(sym.name))
{
var def = vars.get(sym.name);
if (def.value) break;
def.value = assign.right;
remove(defs, def);
defs.push(def);
self.body[i].body = expr.cdr;
continue;
}
}
if (self.body[i] instanceof AST_EmptyStatement) {
self.body.splice(i, 1);
continue;
}
if (self.body[i] instanceof AST_BlockStatement) {
var tmp = [ i, 1 ].concat(self.body[i].body);
self.body.splice.apply(self.body, tmp);
continue;
}
break;
}
defs = make_node(AST_Var, self, {
definitions: defs
});
hoisted.push(defs);
};
}
self.body = dirs.concat(hoisted, self.body); self.body = dirs.concat(hoisted, self.body);
} }
return self; return self;
@@ -1430,7 +1490,7 @@ merge(Compressor.prototype, {
return node; return node;
} }
}); });
tt.stack = compressor.stack; // so that's able to see parent nodes tt.stack = compressor.stack.slice(); // so that's able to see parent nodes
self = self.transform(tt); self = self.transform(tt);
} catch(ex) { } catch(ex) {
if (ex !== self) throw ex; if (ex !== self) throw ex;
@@ -1504,6 +1564,9 @@ merge(Compressor.prototype, {
} }
break; break;
case "String": case "String":
if (self.args.length == 0) return make_node(AST_String, self, {
value: ""
});
return make_node(AST_Binary, self, { return make_node(AST_Binary, self, {
left: self.args[0], left: self.args[0],
operator: "+", operator: "+",
@@ -1539,7 +1602,7 @@ merge(Compressor.prototype, {
case "Function": case "Function":
case "Error": case "Error":
case "Array": case "Array":
return make_node(AST_Call, self, self); return make_node(AST_Call, self, self).transform(compressor);
} }
} }
} }
@@ -1674,7 +1737,8 @@ merge(Compressor.prototype, {
if (self.left instanceof AST_String if (self.left instanceof AST_String
&& self.left.value == "undefined" && self.left.value == "undefined"
&& self.right instanceof AST_UnaryPrefix && self.right instanceof AST_UnaryPrefix
&& self.right.operator == "typeof") { && self.right.operator == "typeof"
&& compressor.option("unsafe")) {
if (!(self.right.expression instanceof AST_SymbolRef) if (!(self.right.expression instanceof AST_SymbolRef)
|| !self.right.expression.undeclared()) { || !self.right.expression.undeclared()) {
self.left = self.right.expression; self.left = self.right.expression;

View File

@@ -340,23 +340,25 @@ function OutputStream(options) {
/* -----[ utils ]----- */ /* -----[ utils ]----- */
function DEFPRINT(nodetype, generator) { function DEFPRINT(nodetype, generator) {
nodetype.DEFMETHOD("print", function(stream){ nodetype.DEFMETHOD("_codegen", generator);
var self = this; };
stream.push_node(self);
if (self.needs_parens(stream)) { AST_Node.DEFMETHOD("print", function(stream, force_parens){
stream.with_parens(function(){ var self = this, generator = self._codegen;
self.add_comments(stream); stream.push_node(self);
self.add_source_map(stream); if (force_parens || self.needs_parens(stream)) {
generator(self, stream); stream.with_parens(function(){
});
} else {
self.add_comments(stream); self.add_comments(stream);
self.add_source_map(stream); self.add_source_map(stream);
generator(self, stream); generator(self, stream);
} });
stream.pop_node(); } else {
}); self.add_comments(stream);
}; self.add_source_map(stream);
generator(self, stream);
}
stream.pop_node();
});
AST_Node.DEFMETHOD("print_to_string", function(options){ AST_Node.DEFMETHOD("print_to_string", function(options){
var s = OutputStream(options); var s = OutputStream(options);
@@ -373,6 +375,16 @@ function OutputStream(options) {
if (start && !start._comments_dumped) { if (start && !start._comments_dumped) {
start._comments_dumped = true; start._comments_dumped = true;
var comments = start.comments_before; 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.length > 0) {
comments = (comments || []).concat(self.value.start.comments_before);
self.value.start.comments_before = [];
}
if (c.test) { if (c.test) {
comments = comments.filter(function(comment){ comments = comments.filter(function(comment){
return c.test(comment.value); return c.test(comment.value);
@@ -467,18 +479,6 @@ function OutputStream(options) {
return true; return true;
} }
} }
// for (var i = (foo in bar);;); ← perhaps useless, but valid syntax
if (this.operator == "in") {
// the “NoIn” stuff :-\
// UglifyJS 1.3.3 misses this one.
if ((p instanceof AST_For || p instanceof AST_ForIn) && p.init === this)
return true;
if (p instanceof AST_VarDef) {
var v = output.parent(1), p2 = output.parent(2);
if ((p2 instanceof AST_For || p2 instanceof AST_ForIn) && p2.init === v)
return true;
}
}
}); });
PARENS(AST_PropAccess, function(output){ PARENS(AST_PropAccess, function(output){
@@ -509,11 +509,23 @@ function OutputStream(options) {
PARENS(AST_New, function(output){ PARENS(AST_New, function(output){
var p = output.parent(); var p = output.parent();
if (no_constructor_parens(this, output) if (no_constructor_parens(this, output)
&& (p instanceof AST_Dot // (new Date).getTime() && (p instanceof AST_PropAccess // (new Date).getTime(), (new Date)["getTime"]()
|| p instanceof AST_Call && p.expression === this)) // (new foo)(bar) || p instanceof AST_Call && p.expression === this)) // (new foo)(bar)
return true; return true;
}); });
PARENS(AST_Number, function(output){
var p = output.parent();
if (this.getValue() < 0 && p instanceof AST_PropAccess && p.expression === this)
return true;
});
PARENS(AST_NaN, function(output){
var p = output.parent();
if (p instanceof AST_PropAccess && p.expression === this)
return true;
});
function assign_and_conditional_paren_rules(output) { function assign_and_conditional_paren_rules(output) {
var p = output.parent(); var p = output.parent();
// !(a = false) → true // !(a = false) → true
@@ -622,7 +634,11 @@ function OutputStream(options) {
output.space(); output.space();
output.with_parens(function(){ output.with_parens(function(){
if (self.init) { if (self.init) {
self.init.print(output); if (self.init instanceof AST_Definitions) {
self.init.print(output);
} else {
parenthesize_for_noin(self.init, output, true);
}
output.print(";"); output.print(";");
output.space(); output.space();
} else { } else {
@@ -734,7 +750,7 @@ function OutputStream(options) {
// to the inner IF). This function checks for this case and // to the inner IF). This function checks for this case and
// adds the block brackets if needed. // adds the block brackets if needed.
if (!self.body) if (!self.body)
return output.semicolon(); return output.force_semicolon();
if (self.body instanceof AST_Do if (self.body instanceof AST_Do
&& output.option("ie_proof")) { && output.option("ie_proof")) {
// https://github.com/mishoo/UglifyJS/issues/#issue/57 IE // https://github.com/mishoo/UglifyJS/issues/#issue/57 IE
@@ -758,7 +774,7 @@ function OutputStream(options) {
} }
else break; else break;
} }
self.body.print(output); force_statement(self.body, output);
}; };
DEFPRINT(AST_If, function(self, output){ DEFPRINT(AST_If, function(self, output){
output.print("if"); output.print("if");
@@ -866,13 +882,32 @@ function OutputStream(options) {
DEFPRINT(AST_Const, function(self, output){ DEFPRINT(AST_Const, function(self, output){
self._do_print(output, "const"); self._do_print(output, "const");
}); });
function parenthesize_for_noin(node, output, noin) {
if (!noin) node.print(output);
else try {
// need to take some precautions here:
// https://github.com/mishoo/UglifyJS2/issues/60
node.walk(new TreeWalker(function(node){
if (node instanceof AST_Binary && node.operator == "in")
throw output;
}));
node.print(output);
} catch(ex) {
if (ex !== output) throw ex;
node.print(output, true);
}
};
DEFPRINT(AST_VarDef, function(self, output){ DEFPRINT(AST_VarDef, function(self, output){
self.name.print(output); self.name.print(output);
if (self.value) { if (self.value) {
output.space(); output.space();
output.print("="); output.print("=");
output.space(); output.space();
self.value.print(output); var p = output.parent(1);
var noin = p instanceof AST_For || p instanceof AST_ForIn;
parenthesize_for_noin(self.value, output, noin);
} }
}); });
@@ -891,7 +926,7 @@ function OutputStream(options) {
DEFPRINT(AST_New, function(self, output){ DEFPRINT(AST_New, function(self, output){
output.print("new"); output.print("new");
output.space(); output.space();
AST_Call.prototype.print.call(self, output); AST_Call.prototype._codegen(self, output);
}); });
AST_Seq.DEFMETHOD("_do_print", function(output){ AST_Seq.DEFMETHOD("_do_print", function(output){
@@ -919,7 +954,7 @@ function OutputStream(options) {
DEFPRINT(AST_Dot, function(self, output){ DEFPRINT(AST_Dot, function(self, output){
var expr = self.expression; var expr = self.expression;
expr.print(output); expr.print(output);
if (expr instanceof AST_Number) { if (expr instanceof AST_Number && expr.getValue() >= 0) {
if (!/[xa-f.]/i.test(output.last())) { if (!/[xa-f.]/i.test(output.last())) {
output.print("."); output.print(".");
} }
@@ -971,8 +1006,7 @@ function OutputStream(options) {
if (len > 0) output.space(); if (len > 0) output.space();
a.forEach(function(exp, i){ a.forEach(function(exp, i){
if (i) output.comma(); if (i) output.comma();
if (!(exp instanceof AST_Undefined)) exp.print(output);
exp.print(output);
}); });
if (len > 0) output.space(); if (len > 0) output.space();
}); });
@@ -1023,6 +1057,7 @@ function OutputStream(options) {
DEFPRINT(AST_Undefined, function(self, output){ DEFPRINT(AST_Undefined, function(self, output){
output.print("void 0"); output.print("void 0");
}); });
DEFPRINT(AST_Hole, noop);
DEFPRINT(AST_Infinity, function(self, output){ DEFPRINT(AST_Infinity, function(self, output){
output.print("1/0"); output.print("1/0");
}); });
@@ -1046,6 +1081,9 @@ function OutputStream(options) {
if (output.option("ascii_only")) if (output.option("ascii_only"))
str = output.to_ascii(str); str = output.to_ascii(str);
output.print(str); output.print(str);
var p = output.parent();
if (p instanceof AST_Binary && /^in/.test(p.operator) && p.left === self)
output.print(" ");
}); });
function force_statement(stat, output) { function force_statement(stat, output) {

View File

@@ -881,11 +881,14 @@ function parse($TEXT, options) {
}; };
var function_ = function(in_statement, ctor) { var function_ = function(in_statement, ctor) {
var name = is("name") ? as_symbol(in_statement var is_accessor = ctor === AST_Accessor;
? AST_SymbolDefun var name = (is("name") ? as_symbol(in_statement
: ctor === AST_Accessor ? AST_SymbolDefun
? AST_SymbolAccessor : is_accessor
: AST_SymbolLambda) : null; ? AST_SymbolAccessor
: AST_SymbolLambda)
: is_accessor && (is("string") || is("num")) ? as_atom_node()
: null);
if (in_statement && !name) if (in_statement && !name)
unexpected(); unexpected();
expect("("); expect("(");
@@ -1128,7 +1131,7 @@ function parse($TEXT, options) {
if (first) first = false; else expect(","); if (first) first = false; else expect(",");
if (allow_trailing_comma && is("punc", closing)) break; if (allow_trailing_comma && is("punc", closing)) break;
if (is("punc", ",") && allow_empty) { if (is("punc", ",") && allow_empty) {
a.push(new AST_Undefined({ start: S.token, end: S.token })); a.push(new AST_Hole({ start: S.token, end: S.token }));
} else { } else {
a.push(expression(false)); a.push(expression(false));
} }
@@ -1355,7 +1358,7 @@ function parse($TEXT, options) {
left : left, left : left,
operator : val, operator : val,
right : maybe_assign(no_in), right : maybe_assign(no_in),
end : peek() end : prev()
}); });
} }
croak("Invalid assignment"); croak("Invalid assignment");

View File

@@ -84,9 +84,12 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(){
if (node instanceof AST_Scope) { if (node instanceof AST_Scope) {
node.init_scope_vars(nesting); node.init_scope_vars(nesting);
var save_scope = node.parent_scope = scope; var save_scope = node.parent_scope = scope;
var save_labels = labels;
++nesting; ++nesting;
scope = node; scope = node;
labels = new Dictionary();
descend(); descend();
labels = save_labels;
scope = save_scope; scope = save_scope;
--nesting; --nesting;
return true; // don't descend again in TreeWalker return true; // don't descend again in TreeWalker
@@ -110,9 +113,6 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(){
labels.del(l.name); labels.del(l.name);
return true; // no descend again return true; // no descend again
} }
if (node instanceof AST_SymbolDeclaration) {
node.init_scope_vars();
}
if (node instanceof AST_Symbol) { if (node instanceof AST_Symbol) {
node.scope = scope; node.scope = scope;
} }
@@ -128,8 +128,6 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(){
// scope. Don't like this fix but seems we can't do any // scope. Don't like this fix but seems we can't do any
// better. IE: please die. Please! // better. IE: please die. Please!
(node.scope = scope.parent_scope).def_function(node); (node.scope = scope.parent_scope).def_function(node);
node.init.push(tw.parent());
} }
else if (node instanceof AST_SymbolDefun) { else if (node instanceof AST_SymbolDefun) {
// Careful here, the scope where this should be defined is // Careful here, the scope where this should be defined is
@@ -138,14 +136,12 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(){
// instanceof AST_Scope) but we get to the symbol a bit // instanceof AST_Scope) but we get to the symbol a bit
// later. // later.
(node.scope = scope.parent_scope).def_function(node); (node.scope = scope.parent_scope).def_function(node);
node.init.push(tw.parent());
} }
else if (node instanceof AST_SymbolVar else if (node instanceof AST_SymbolVar
|| node instanceof AST_SymbolConst) { || node instanceof AST_SymbolConst) {
var def = scope.def_variable(node); var def = scope.def_variable(node);
def.constant = node instanceof AST_SymbolConst; def.constant = node instanceof AST_SymbolConst;
def = tw.parent(); def.init = tw.parent().value;
if (def.value) node.init.push(def);
} }
else if (node instanceof AST_SymbolCatch) { else if (node instanceof AST_SymbolCatch) {
// XXX: this is wrong according to ECMA-262 (12.4). the // XXX: this is wrong according to ECMA-262 (12.4). the
@@ -246,10 +242,6 @@ AST_SymbolRef.DEFMETHOD("reference", function() {
this.frame = this.scope.nesting - def.scope.nesting; this.frame = this.scope.nesting - def.scope.nesting;
}); });
AST_SymbolDeclaration.DEFMETHOD("init_scope_vars", function(){
this.init = [];
});
AST_Label.DEFMETHOD("init_scope_vars", function(){ AST_Label.DEFMETHOD("init_scope_vars", function(){
this.references = []; this.references = [];
}); });
@@ -351,6 +343,7 @@ AST_Toplevel.DEFMETHOD("_default_mangler_options", function(options){
return defaults(options, { return defaults(options, {
except : [], except : [],
eval : false, eval : false,
sort : false
}); });
}); });
@@ -371,12 +364,16 @@ AST_Toplevel.DEFMETHOD("mangle_names", function(options){
return true; // don't descend again in TreeWalker return true; // don't descend again in TreeWalker
} }
if (node instanceof AST_Scope) { if (node instanceof AST_Scope) {
var p = tw.parent(); var p = tw.parent(), a = [];
node.variables.each(function(symbol){ node.variables.each(function(symbol){
if (options.except.indexOf(symbol.name) < 0) { if (options.except.indexOf(symbol.name) < 0) {
to_mangle.push(symbol); a.push(symbol);
} }
}); });
if (options.sort) a.sort(function(a, b){
return b.references.length - a.references.length;
});
to_mangle.push.apply(to_mangle, a);
return; return;
} }
if (node instanceof AST_Label) { if (node instanceof AST_Label) {

View File

@@ -255,6 +255,14 @@ Dictionary.prototype = {
this._values["$" + key] = val; this._values["$" + key] = val;
return this; return this;
}, },
add: function(key, val) {
if (this.has(key)) {
this.get(key).push(val);
} else {
this.set(key, [ val ]);
}
return this;
},
get: function(key) { return this._values["$" + key] }, get: function(key) { return this._values["$" + key] },
del: function(key) { del: function(key) {
if (this.has(key)) { if (this.has(key)) {

View File

@@ -3,7 +3,7 @@
"description": "JavaScript parser, mangler/compressor and beautifier toolkit", "description": "JavaScript parser, mangler/compressor and beautifier toolkit",
"homepage": "http://lisperator.net/uglifyjs", "homepage": "http://lisperator.net/uglifyjs",
"main": "tools/node.js", "main": "tools/node.js",
"version": "2.2.1", "version": "2.2.5",
"engines": { "node" : ">=0.4.0" }, "engines": { "node" : ">=0.4.0" },
"maintainers": [{ "maintainers": [{
"name": "Mihai Bazon", "name": "Mihai Bazon",

12
test/compress/arrays.js Normal file
View File

@@ -0,0 +1,12 @@
holes_and_undefined: {
input: {
x = [1, 2, undefined];
y = [1, , 2, ];
z = [1, undefined, 3];
}
expect: {
x=[1,2,void 0];
y=[1,,2];
z=[1,void 0,3];
}
}

View File

@@ -0,0 +1,17 @@
typeof_eq_undefined: {
options = {
comparisons: true,
unsafe: false
};
input: { a = typeof b.c != "undefined" }
expect: { a = "undefined" != typeof b.c }
}
typeof_eq_undefined_unsafe: {
options = {
comparisons: true,
unsafe: true
};
input: { a = typeof b.c != "undefined" }
expect: { a = b.c !== void 0 }
}

30
test/compress/issue-59.js Normal file
View File

@@ -0,0 +1,30 @@
keep_continue: {
options = {
dead_code: true,
evaluate: true
};
input: {
while (a) {
if (b) {
switch (true) {
case c():
d();
}
continue;
}
f();
}
}
expect: {
while (a) {
if (b) {
switch (true) {
case c():
d();
}
continue;
}
f();
}
}
}

View File

@@ -56,6 +56,9 @@ exports.minify = function(files, options) {
inSourceMap : null, inSourceMap : null,
fromString : false, fromString : false,
warnings : false, warnings : false,
mangle : {},
output : null,
compress : {}
}); });
if (typeof files == "string") if (typeof files == "string")
files = [ files ]; files = [ files ];
@@ -73,16 +76,20 @@ exports.minify = function(files, options) {
}); });
// 2. compress // 2. compress
toplevel.figure_out_scope(); if (options.compress) {
var sq = UglifyJS.Compressor({ var compress = { warnings: options.warnings };
warnings: options.warnings, UglifyJS.merge(compress, options.compress);
}); toplevel.figure_out_scope();
toplevel = toplevel.transform(sq); var sq = UglifyJS.Compressor(compress);
toplevel = toplevel.transform(sq);
}
// 3. mangle // 3. mangle
toplevel.figure_out_scope(); if (options.mangle) {
toplevel.compute_char_frequency(); toplevel.figure_out_scope();
toplevel.mangle_names(); toplevel.compute_char_frequency();
toplevel.mangle_names(options.mangle);
}
// 4. output // 4. output
var map = null; var map = null;
@@ -95,7 +102,11 @@ exports.minify = function(files, options) {
orig: inMap, orig: inMap,
root: options.sourceRoot root: options.sourceRoot
}); });
var stream = UglifyJS.OutputStream({ source_map: map }); var output = { source_map: map };
if (options.output) {
UglifyJS.merge(output, options.output);
}
var stream = UglifyJS.OutputStream(output);
toplevel.print(stream); toplevel.print(stream);
return { return {
code : stream + "", code : stream + "",