Merge 021325f77f into 6d2f77c180
This commit is contained in:
commit
feed0d983d
32
README.md
32
README.md
|
|
@ -125,7 +125,9 @@ The available options are:
|
|||
--noerr Don't throw an error for unknown options in -c,
|
||||
-b or -m.
|
||||
--bare-returns Allow return outside of functions. Useful when
|
||||
minifying CommonJS modules.
|
||||
minifying CommonJS modules and Userscripts that
|
||||
may be anonymous function wrapped (IIFE) by the
|
||||
.user.js engine `caller`.
|
||||
--keep-fnames Do not mangle/drop function names. Useful for
|
||||
code relying on Function.prototype.name.
|
||||
--reserved-file File containing reserved names
|
||||
|
|
@ -190,11 +192,6 @@ input files from the command line.
|
|||
To enable the mangler you need to pass `--mangle` (`-m`). The following
|
||||
(comma-separated) options are supported:
|
||||
|
||||
- `sort` — 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 (and seems to happen for other libraries I tried it
|
||||
on) therefore it's not enabled by default.
|
||||
|
||||
- `toplevel` — mangle names declared in the toplevel scope (disabled by
|
||||
default).
|
||||
|
||||
|
|
@ -323,6 +320,9 @@ to set `true`; it's effectively a shortcut for `foo=true`).
|
|||
- `cascade` -- small optimization for sequences, transform `x, x` into `x`
|
||||
and `x = something(), x` into `x = something()`
|
||||
|
||||
- `collapse_vars` -- default `false`. Collapse single-use `var` and `const`
|
||||
definitions when possible.
|
||||
|
||||
- `warnings` -- display warnings when dropping unreachable code or unused
|
||||
declarations etc.
|
||||
|
||||
|
|
@ -395,6 +395,8 @@ separate file and include it into the build. For example you can have a
|
|||
```javascript
|
||||
const DEBUG = false;
|
||||
const PRODUCTION = true;
|
||||
// Alternative for environments that don't support `const`
|
||||
/** @const */ var STAGING = false;
|
||||
// etc.
|
||||
```
|
||||
|
||||
|
|
@ -404,8 +406,8 @@ and build your code like this:
|
|||
|
||||
UglifyJS will notice the constants and, since they cannot be altered, it
|
||||
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
|
||||
will contain the `const` declarations.
|
||||
code as usual. The build will contain the `const` declarations if you use
|
||||
them. If you are targeting < ES6 environments, use `/** @const */ var`.
|
||||
|
||||
<a name="codegen-options"></a>
|
||||
## Beautifier options
|
||||
|
|
@ -624,6 +626,9 @@ Other options:
|
|||
|
||||
- `mangle` — pass `false` to skip mangling names.
|
||||
|
||||
- `mangleProperties` (default `false`) — pass an object to specify custom
|
||||
mangle property options.
|
||||
|
||||
- `output` (default `null`) — pass an object if you wish to specify
|
||||
additional [output options][codegen]. The defaults are optimized
|
||||
for best compression.
|
||||
|
|
@ -631,6 +636,13 @@ Other options:
|
|||
- `compress` (default `{}`) — pass `false` to skip compressing entirely.
|
||||
Pass an object to specify custom [compressor options][compressor].
|
||||
|
||||
- `parse` (default {}) — pass an object if you wish to specify some
|
||||
additional [parser options][parser]. (not all options available... see below)
|
||||
|
||||
##### mangleProperties options
|
||||
|
||||
- `regex` — Pass a RegExp to only mangle certain names (maps to the `--mange-regex` CLI arguments option)
|
||||
|
||||
We could add more options to `UglifyJS.minify` — if you need additional
|
||||
functionality please suggest!
|
||||
|
||||
|
|
@ -649,6 +661,9 @@ properties are available:
|
|||
|
||||
- `strict` — disable automatic semicolon insertion and support for trailing
|
||||
comma in arrays and objects
|
||||
- `bare_returns` — Allow return outside of functions. (maps to the
|
||||
`--bare-returns` CLI arguments option and available to `minify` `parse`
|
||||
other options object)
|
||||
- `filename` — the name of the file where this code is coming from
|
||||
- `toplevel` — a `toplevel` node (as returned by a previous invocation of
|
||||
`parse`)
|
||||
|
|
@ -788,3 +803,4 @@ The `source_map_options` (optional) can contain the following properties:
|
|||
[sm-spec]: https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit
|
||||
[codegen]: http://lisperator.net/uglifyjs/codegen
|
||||
[compressor]: http://lisperator.net/uglifyjs/compress
|
||||
[parser]: http://lisperator.net/uglifyjs/parser
|
||||
|
|
|
|||
12
bin/uglifyjs
12
bin/uglifyjs
|
|
@ -499,17 +499,19 @@ function normalize(o) {
|
|||
}
|
||||
}
|
||||
|
||||
function getOptions(x, constants) {
|
||||
x = ARGS[x];
|
||||
if (x == null) return null;
|
||||
function getOptions(flag, constants) {
|
||||
var x = ARGS[flag];
|
||||
if (x == null || x === false) return null;
|
||||
var ret = {};
|
||||
if (x !== "") {
|
||||
if (Array.isArray(x)) x = x.map(function (v) { return "(" + v + ")"; }).join(", ");
|
||||
|
||||
var ast;
|
||||
try {
|
||||
ast = UglifyJS.parse(x, { expression: true });
|
||||
} catch(ex) {
|
||||
if (ex instanceof UglifyJS.JS_Parse_Error) {
|
||||
print_error("Error parsing arguments in: " + x);
|
||||
print_error("Error parsing arguments for flag `" + flag + "': " + x);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
|
@ -529,7 +531,7 @@ function getOptions(x, constants) {
|
|||
return true; // no descend
|
||||
}
|
||||
print_error(node.TYPE)
|
||||
print_error("Error parsing arguments in: " + x);
|
||||
print_error("Error parsing arguments for flag `" + flag + "': " + x);
|
||||
process.exit(1);
|
||||
}));
|
||||
}
|
||||
|
|
|
|||
225
lib/compress.js
225
lib/compress.js
|
|
@ -66,6 +66,7 @@ function Compressor(options, false_by_default) {
|
|||
hoist_vars : false,
|
||||
if_return : !false_by_default,
|
||||
join_vars : !false_by_default,
|
||||
collapse_vars : false,
|
||||
cascade : !false_by_default,
|
||||
side_effects : !false_by_default,
|
||||
pure_getters : false,
|
||||
|
|
@ -175,6 +176,23 @@ merge(Compressor.prototype, {
|
|||
}
|
||||
};
|
||||
|
||||
// we shouldn't compress (1,func)(something) to
|
||||
// func(something) because that changes the meaning of
|
||||
// the func (becomes lexical instead of global).
|
||||
function maintain_this_binding(parent, orig, val) {
|
||||
if (parent instanceof AST_Call && parent.expression === orig) {
|
||||
if (val instanceof AST_PropAccess || val instanceof AST_SymbolRef && val.name === "eval") {
|
||||
return make_node(AST_Seq, orig, {
|
||||
car: make_node(AST_Number, orig, {
|
||||
value: 0
|
||||
}),
|
||||
cdr: val
|
||||
});
|
||||
}
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
function as_statement_array(thing) {
|
||||
if (thing === null) return [];
|
||||
if (thing instanceof AST_BlockStatement) return thing.body;
|
||||
|
|
@ -218,6 +236,9 @@ merge(Compressor.prototype, {
|
|||
if (compressor.option("join_vars")) {
|
||||
statements = join_consecutive_vars(statements, compressor);
|
||||
}
|
||||
if (compressor.option("collapse_vars")) {
|
||||
statements = collapse_single_use_vars(statements, compressor);
|
||||
}
|
||||
} while (CHANGED && max_iter-- > 0);
|
||||
|
||||
if (compressor.option("negate_iife")) {
|
||||
|
|
@ -226,6 +247,163 @@ merge(Compressor.prototype, {
|
|||
|
||||
return statements;
|
||||
|
||||
function collapse_single_use_vars(statements, compressor) {
|
||||
// Iterate statements backwards looking for a statement with a var/const
|
||||
// declaration immediately preceding it. Grab the rightmost var definition
|
||||
// and if it has exactly one reference then attempt to replace its reference
|
||||
// in the statement with the var value and then erase the var definition.
|
||||
|
||||
var self = compressor.self();
|
||||
var var_defs_removed = false;
|
||||
for (var stat_index = statements.length; --stat_index >= 0;) {
|
||||
var stat = statements[stat_index];
|
||||
if (stat instanceof AST_Definitions) continue;
|
||||
|
||||
// Process child blocks of statement if present.
|
||||
[stat, stat.body, stat.alternative, stat.bcatch, stat.bfinally].forEach(function(node) {
|
||||
node && node.body && collapse_single_use_vars(node.body, compressor);
|
||||
});
|
||||
|
||||
// The variable definition must precede a statement.
|
||||
if (stat_index <= 0) break;
|
||||
var prev_stat_index = stat_index - 1;
|
||||
var prev_stat = statements[prev_stat_index];
|
||||
if (!(prev_stat instanceof AST_Definitions)) continue;
|
||||
var var_defs = prev_stat.definitions;
|
||||
if (var_defs == null) continue;
|
||||
|
||||
var var_names_seen = {};
|
||||
var side_effects_encountered = false;
|
||||
var lvalues_encountered = false;
|
||||
var lvalues = {};
|
||||
|
||||
// Scan variable definitions from right to left.
|
||||
for (var var_defs_index = var_defs.length; --var_defs_index >= 0;) {
|
||||
|
||||
// Obtain var declaration and var name with basic sanity check.
|
||||
var var_decl = var_defs[var_defs_index];
|
||||
if (var_decl.value == null) break;
|
||||
var var_name = var_decl.name.name;
|
||||
if (!var_name || !var_name.length) break;
|
||||
|
||||
// Bail if we've seen a var definition of same name before.
|
||||
if (var_name in var_names_seen) break;
|
||||
var_names_seen[var_name] = true;
|
||||
|
||||
// Only interested in cases with just one reference to the variable.
|
||||
var def = self.find_variable && self.find_variable(var_name);
|
||||
if (!def || !def.references || def.references.length !== 1 || var_name == "arguments") {
|
||||
side_effects_encountered = true;
|
||||
continue;
|
||||
}
|
||||
var ref = def.references[0];
|
||||
|
||||
// Don't replace ref if eval() or with statement in scope.
|
||||
if (ref.scope.uses_eval || ref.scope.uses_with) break;
|
||||
|
||||
// Constant single use vars can be replaced in any scope.
|
||||
if (var_decl.value.is_constant(compressor)) {
|
||||
var ctt = new TreeTransformer(function(node) {
|
||||
if (node === ref)
|
||||
return replace_var(node, ctt.parent(), true);
|
||||
});
|
||||
stat.transform(ctt);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Restrict var replacement to constants if side effects encountered.
|
||||
if (side_effects_encountered |= lvalues_encountered) continue;
|
||||
|
||||
// Non-constant single use vars can only be replaced in same scope.
|
||||
if (ref.scope !== self) {
|
||||
side_effects_encountered |= var_decl.value.has_side_effects(compressor);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Detect lvalues in var value.
|
||||
var tw = new TreeWalker(function(node){
|
||||
if (node instanceof AST_SymbolRef && is_lvalue(node, tw.parent())) {
|
||||
lvalues[node.name] = lvalues_encountered = true;
|
||||
}
|
||||
});
|
||||
var_decl.value.walk(tw);
|
||||
|
||||
// Replace the non-constant single use var in statement if side effect free.
|
||||
var unwind = false;
|
||||
var tt = new TreeTransformer(
|
||||
function preorder(node) {
|
||||
if (unwind) return node;
|
||||
var parent = tt.parent();
|
||||
if (node instanceof AST_Lambda
|
||||
|| node instanceof AST_Try
|
||||
|| node instanceof AST_With
|
||||
|| node instanceof AST_Case
|
||||
|| node instanceof AST_IterationStatement
|
||||
|| (parent instanceof AST_If && node !== parent.condition)
|
||||
|| (parent instanceof AST_Conditional && node !== parent.condition)
|
||||
|| (parent instanceof AST_Binary
|
||||
&& (parent.operator == "&&" || parent.operator == "||")
|
||||
&& node === parent.right)
|
||||
|| (parent instanceof AST_Switch && node !== parent.expression)) {
|
||||
return side_effects_encountered = unwind = true, node;
|
||||
}
|
||||
},
|
||||
function postorder(node) {
|
||||
if (unwind) return node;
|
||||
if (node === ref)
|
||||
return unwind = true, replace_var(node, tt.parent(), false);
|
||||
if (side_effects_encountered |= node.has_side_effects(compressor))
|
||||
return unwind = true, node;
|
||||
if (lvalues_encountered && node instanceof AST_SymbolRef && node.name in lvalues) {
|
||||
side_effects_encountered = true;
|
||||
return unwind = true, node;
|
||||
}
|
||||
}
|
||||
);
|
||||
stat.transform(tt);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove extraneous empty statments in block after removing var definitions.
|
||||
// Leave at least one statement in `statements`.
|
||||
if (var_defs_removed) for (var i = statements.length; --i >= 0;) {
|
||||
if (statements.length > 1 && statements[i] instanceof AST_EmptyStatement)
|
||||
statements.splice(i, 1);
|
||||
}
|
||||
|
||||
return statements;
|
||||
|
||||
function is_lvalue(node, parent) {
|
||||
return node instanceof AST_SymbolRef && (
|
||||
(parent instanceof AST_Assign && node === parent.left)
|
||||
|| (parent instanceof AST_Unary && parent.expression === node
|
||||
&& (parent.operator == "++" || parent.operator == "--")));
|
||||
}
|
||||
function replace_var(node, parent, is_constant) {
|
||||
if (is_lvalue(node, parent)) return node;
|
||||
|
||||
// Remove var definition and return its value to the TreeTransformer to replace.
|
||||
var value = maintain_this_binding(parent, node, var_decl.value);
|
||||
var_decl.value = null;
|
||||
|
||||
var_defs.splice(var_defs_index, 1);
|
||||
if (var_defs.length === 0) {
|
||||
statements[prev_stat_index] = make_node(AST_EmptyStatement, self);
|
||||
var_defs_removed = true;
|
||||
}
|
||||
// Further optimize statement after substitution.
|
||||
stat.walk(new TreeWalker(function(node){
|
||||
delete node._squeezed;
|
||||
delete node._optimized;
|
||||
}));
|
||||
|
||||
compressor.warn("Replacing " + (is_constant ? "constant" : "variable") +
|
||||
" " + var_name + " [{file}:{line},{col}]", node.start);
|
||||
CHANGED = true;
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
function process_for_angular(statements) {
|
||||
function has_inject(comment) {
|
||||
return /@ngInject/.test(comment.value);
|
||||
|
|
@ -1276,7 +1454,8 @@ merge(Compressor.prototype, {
|
|||
|
||||
var hoist_funs = compressor.option("hoist_funs");
|
||||
var hoist_vars = compressor.option("hoist_vars");
|
||||
|
||||
var self = this;
|
||||
if (!(self.body instanceof Array)) { return self; } // Hoisting makes no sense in an arrow func
|
||||
if (hoist_funs || hoist_vars) {
|
||||
var dirs = [];
|
||||
var hoisted = [];
|
||||
|
|
@ -1312,7 +1491,10 @@ merge(Compressor.prototype, {
|
|||
var seq = node.to_assignments();
|
||||
var p = tt.parent();
|
||||
if (p instanceof AST_ForIn && p.init === node) {
|
||||
if (seq == null) return node.definitions[0].name;
|
||||
if (seq == null) {
|
||||
var def = node.definitions[0].name;
|
||||
return make_node(AST_SymbolRef, def, def);
|
||||
}
|
||||
return seq;
|
||||
}
|
||||
if (p instanceof AST_For && p.init === node) {
|
||||
|
|
@ -1543,9 +1725,13 @@ merge(Compressor.prototype, {
|
|||
}
|
||||
if (is_empty(self.alternative)) self.alternative = null;
|
||||
var negated = self.condition.negate(compressor);
|
||||
var negated_is_best = best_of(self.condition, negated) === negated;
|
||||
var self_condition_length = self.condition.print_to_string().length;
|
||||
var negated_length = negated.print_to_string().length;
|
||||
var negated_is_best = negated_length < self_condition_length;
|
||||
if (self.alternative && negated_is_best) {
|
||||
negated_is_best = false; // because we already do the switch here.
|
||||
// no need to swap values of self_condition_length and negated_length
|
||||
// here because they are only used in an equality comparison later on.
|
||||
self.condition = negated;
|
||||
var tmp = self.body;
|
||||
self.body = self.alternative || make_node(AST_EmptyStatement);
|
||||
|
|
@ -1567,6 +1753,13 @@ merge(Compressor.prototype, {
|
|||
}).transform(compressor);
|
||||
}
|
||||
if (is_empty(self.alternative) && self.body instanceof AST_SimpleStatement) {
|
||||
if (self_condition_length === negated_length && !negated_is_best
|
||||
&& self.condition instanceof AST_Binary && self.condition.operator == "||") {
|
||||
// although the code length of self.condition and negated are the same,
|
||||
// negated does not require additional surrounding parentheses.
|
||||
// see https://github.com/mishoo/UglifyJS2/issues/979
|
||||
negated_is_best = true;
|
||||
}
|
||||
if (negated_is_best) return make_node(AST_SimpleStatement, self, {
|
||||
body: make_node(AST_Binary, self, {
|
||||
operator : "||",
|
||||
|
|
@ -1999,13 +2192,7 @@ merge(Compressor.prototype, {
|
|||
if (!compressor.option("side_effects"))
|
||||
return self;
|
||||
if (!self.car.has_side_effects(compressor)) {
|
||||
// we shouldn't compress (1,func)(something) to
|
||||
// func(something) because that changes the meaning of
|
||||
// the func (becomes lexical instead of global).
|
||||
var p = compressor.parent();
|
||||
if (!(p instanceof AST_Call && p.expression === self)) {
|
||||
return self.cdr;
|
||||
}
|
||||
return maintain_this_binding(compressor.parent(), self, self.cdr);
|
||||
}
|
||||
if (compressor.option("cascade")) {
|
||||
if (self.car instanceof AST_Assign
|
||||
|
|
@ -2195,11 +2382,10 @@ merge(Compressor.prototype, {
|
|||
if (ll.length > 1) {
|
||||
if (ll[1]) {
|
||||
compressor.warn("Condition left of && always true [{file}:{line},{col}]", self.start);
|
||||
var rr = self.right.evaluate(compressor);
|
||||
return rr[0];
|
||||
return maintain_this_binding(compressor.parent(), self, self.right.evaluate(compressor)[0]);
|
||||
} else {
|
||||
compressor.warn("Condition left of && always false [{file}:{line},{col}]", self.start);
|
||||
return ll[0];
|
||||
return maintain_this_binding(compressor.parent(), self, ll[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2208,11 +2394,10 @@ merge(Compressor.prototype, {
|
|||
if (ll.length > 1) {
|
||||
if (ll[1]) {
|
||||
compressor.warn("Condition left of || always true [{file}:{line},{col}]", self.start);
|
||||
return ll[0];
|
||||
return maintain_this_binding(compressor.parent(), self, ll[0]);
|
||||
} else {
|
||||
compressor.warn("Condition left of || always false [{file}:{line},{col}]", self.start);
|
||||
var rr = self.right.evaluate(compressor);
|
||||
return rr[0];
|
||||
return maintain_this_binding(compressor.parent(), self, self.right.evaluate(compressor)[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2437,10 +2622,10 @@ merge(Compressor.prototype, {
|
|||
if (cond.length > 1) {
|
||||
if (cond[1]) {
|
||||
compressor.warn("Condition always true [{file}:{line},{col}]", self.start);
|
||||
return self.consequent;
|
||||
return maintain_this_binding(compressor.parent(), self, self.consequent);
|
||||
} else {
|
||||
compressor.warn("Condition always false [{file}:{line},{col}]", self.start);
|
||||
return self.alternative;
|
||||
return maintain_this_binding(compressor.parent(), self, self.alternative);
|
||||
}
|
||||
}
|
||||
var negated = cond[0].negate(compressor);
|
||||
|
|
@ -2522,6 +2707,10 @@ merge(Compressor.prototype, {
|
|||
|
||||
// y?true:false --> !!y
|
||||
if (is_true(consequent) && is_false(alternative)) {
|
||||
if (self.condition.is_boolean()) {
|
||||
// boolean_expression ? true : false --> boolean_expression
|
||||
return self.condition;
|
||||
}
|
||||
self.condition = self.condition.negate(compressor);
|
||||
return make_node(AST_UnaryPrefix, self.condition, {
|
||||
operator: "!",
|
||||
|
|
|
|||
|
|
@ -444,11 +444,11 @@ function OutputStream(options) {
|
|||
});
|
||||
} else if (c.test) {
|
||||
comments = comments.filter(function(comment){
|
||||
return c.test(comment.value) || comment.type == "comment5";
|
||||
return comment.type == "comment5" || c.test(comment.value);
|
||||
});
|
||||
} else if (typeof c == "function") {
|
||||
comments = comments.filter(function(comment){
|
||||
return c(self, comment) || comment.type == "comment5";
|
||||
return comment.type == "comment5" || c(self, comment);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -596,8 +596,12 @@ function OutputStream(options) {
|
|||
|
||||
PARENS(AST_Number, function(output){
|
||||
var p = output.parent();
|
||||
if (this.getValue() < 0 && p instanceof AST_PropAccess && p.expression === this)
|
||||
return true;
|
||||
if (p instanceof AST_PropAccess && p.expression === this) {
|
||||
var value = this.getValue();
|
||||
if (value < 0 || /^0/.test(make_num(value))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
PARENS([ AST_Assign, AST_Conditional ], function (output){
|
||||
|
|
@ -1172,7 +1176,7 @@ function OutputStream(options) {
|
|||
var expr = self.expression;
|
||||
expr.print(output);
|
||||
if (expr instanceof AST_Number && expr.getValue() >= 0) {
|
||||
if (!/[xa-f.]/i.test(output.last())) {
|
||||
if (!/[xa-f.)]/i.test(output.last())) {
|
||||
output.print(".");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -408,7 +408,7 @@ function tokenizer($TEXT, filename, html5_comments, shebang) {
|
|||
if (octal_len > 0) ch = String.fromCharCode(parseInt(ch, 8));
|
||||
else ch = read_escaped_char(true);
|
||||
}
|
||||
else if (ch == "\n") parse_error("Unterminated string constant");
|
||||
else if ("\r\n\u2028\u2029".indexOf(ch) >= 0) parse_error("Unterminated string constant");
|
||||
else if (ch == quote) break;
|
||||
ret += ch;
|
||||
}
|
||||
|
|
@ -1367,6 +1367,13 @@ function parse($TEXT, options) {
|
|||
break;
|
||||
}
|
||||
break;
|
||||
case "operator":
|
||||
if (!is_identifier_string(tok.value)) {
|
||||
throw new JS_Parse_Error("Invalid getter/setter name: " + tok.value,
|
||||
tok.file, tok.line, tok.col, tok.pos);
|
||||
}
|
||||
ret = _make_symbol(AST_SymbolRef);
|
||||
break;
|
||||
}
|
||||
next();
|
||||
return ret;
|
||||
|
|
|
|||
34
lib/scope.js
34
lib/scope.js
|
|
@ -102,6 +102,7 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){
|
|||
var scope = self.parent_scope = null;
|
||||
var labels = new Dictionary();
|
||||
var defun = null;
|
||||
var last_var_had_const_pragma = false;
|
||||
var nesting = 0;
|
||||
var in_destructuring = null;
|
||||
var in_export;
|
||||
|
|
@ -165,6 +166,10 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){
|
|||
node.thedef = node;
|
||||
node.references = [];
|
||||
}
|
||||
if (node instanceof AST_SymbolFunarg) {
|
||||
node.object_destructuring_arg = !!in_destructuring;
|
||||
defun.def_variable(node, in_export);
|
||||
}
|
||||
if (node instanceof AST_SymbolLambda) {
|
||||
defun.def_function(node, in_export);
|
||||
}
|
||||
|
|
@ -235,6 +240,11 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){
|
|||
}
|
||||
if (node instanceof AST_SymbolRef) {
|
||||
var name = node.name;
|
||||
if (name == "eval" && tw.parent() instanceof AST_Call) {
|
||||
for (var s = node.scope; s && !s.uses_eval; s = s.parent_scope) {
|
||||
s.uses_eval = true;
|
||||
}
|
||||
}
|
||||
var sym = node.scope.find_variable(name);
|
||||
if (!sym) {
|
||||
var g;
|
||||
|
|
@ -269,6 +279,7 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){
|
|||
});
|
||||
|
||||
AST_Scope.DEFMETHOD("init_scope_vars", function(nesting){
|
||||
this.directives = []; // contains the directives defined in this scope, i.e. "use strict"
|
||||
this.variables = new Dictionary(); // map name to AST_SymbolVar (variables defined in this scope; includes functions)
|
||||
this.functions = new Dictionary(); // map name to AST_SymbolDefun (functions defined in this scope)
|
||||
this.uses_with = false; // will be set to true if this or some nested scope uses the `with` statement
|
||||
|
|
@ -279,9 +290,17 @@ AST_Scope.DEFMETHOD("init_scope_vars", function(nesting){
|
|||
this.nesting = nesting; // the nesting level of this scope (0 means toplevel)
|
||||
});
|
||||
|
||||
AST_Scope.DEFMETHOD("strict", function(){
|
||||
return this.has_directive("use strict");
|
||||
});
|
||||
|
||||
AST_Lambda.DEFMETHOD("init_scope_vars", function(){
|
||||
AST_Scope.prototype.init_scope_vars.apply(this, arguments);
|
||||
this.uses_arguments = false;
|
||||
|
||||
var symbol = new AST_VarDef({ name: "arguments", start: this.start, end: this.end });
|
||||
var def = new SymbolDef(this, this.variables.size(), symbol);
|
||||
this.variables.set(symbol.name, def);
|
||||
});
|
||||
|
||||
AST_SymbolRef.DEFMETHOD("reference", function() {
|
||||
|
|
@ -410,11 +429,17 @@ AST_Symbol.DEFMETHOD("global", function(){
|
|||
return this.definition().global;
|
||||
});
|
||||
|
||||
AST_Var.DEFMETHOD("has_const_pragma", function() {
|
||||
var comments_before = this.start && this.start.comments_before;
|
||||
var lastComment = comments_before && comments_before[comments_before.length - 1];
|
||||
return lastComment && /@const\b/.test(lastComment.value);
|
||||
});
|
||||
|
||||
AST_Toplevel.DEFMETHOD("_default_mangler_options", function(options){
|
||||
return defaults(options, {
|
||||
except : [],
|
||||
eval : false,
|
||||
sort : false,
|
||||
sort : false, // Ignored. Flag retained for backwards compatibility.
|
||||
toplevel : false,
|
||||
screw_ie8 : false,
|
||||
keep_fnames : false,
|
||||
|
|
@ -424,6 +449,10 @@ AST_Toplevel.DEFMETHOD("_default_mangler_options", function(options){
|
|||
|
||||
AST_Toplevel.DEFMETHOD("mangle_names", function(options){
|
||||
options = this._default_mangler_options(options);
|
||||
|
||||
// Never mangle arguments
|
||||
options.except.push('arguments');
|
||||
|
||||
// We only need to mangle declaration nodes. Special logic wired
|
||||
// into the code generator will display the mangled name if it's
|
||||
// present (and for AST_SymbolRef-s it'll use the mangled name of
|
||||
|
|
@ -454,9 +483,6 @@ AST_Toplevel.DEFMETHOD("mangle_names", function(options){
|
|||
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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -53,16 +53,11 @@ function SourceMap(options) {
|
|||
orig_line_diff : 0,
|
||||
dest_line_diff : 0,
|
||||
});
|
||||
var generator = new MOZ_SourceMap.SourceMapGenerator({
|
||||
file : options.file,
|
||||
sourceRoot : options.root
|
||||
});
|
||||
var orig_map = options.orig && new MOZ_SourceMap.SourceMapConsumer(options.orig);
|
||||
var generator;
|
||||
if (orig_map) {
|
||||
generator = MOZ_SourceMap.SourceMapGenerator.fromSourceMap(orig_map);
|
||||
} else {
|
||||
generator = new MOZ_SourceMap.SourceMapGenerator({
|
||||
file : options.file,
|
||||
sourceRoot : options.root
|
||||
});
|
||||
}
|
||||
function add(source, gen_line, gen_col, orig_line, orig_col, name) {
|
||||
if (orig_map) {
|
||||
var info = orig_map.originalPositionFor({
|
||||
|
|
@ -83,7 +78,7 @@ function SourceMap(options) {
|
|||
source : source,
|
||||
name : name
|
||||
});
|
||||
}
|
||||
};
|
||||
return {
|
||||
add : add,
|
||||
get : function() { return generator },
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ TreeTransformer.prototype = new TreeWalker;
|
|||
x = this;
|
||||
descend(x, tw);
|
||||
} else {
|
||||
tw.stack[tw.stack.length - 1] = x = this.clone();
|
||||
tw.stack[tw.stack.length - 1] = x = this;
|
||||
descend(x, tw);
|
||||
y = tw.after(x, in_list);
|
||||
if (y !== undefined) x = y;
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
"homepage": "http://lisperator.net/uglifyjs",
|
||||
"author": "Mihai Bazon <mihai.bazon@gmail.com> (http://lisperator.net/)",
|
||||
"license": "BSD-2-Clause",
|
||||
"version": "2.6.1",
|
||||
"version": "2.6.2",
|
||||
"engines": {
|
||||
"node": ">=0.8.0"
|
||||
},
|
||||
|
|
@ -38,7 +38,8 @@
|
|||
"acorn": "~0.6.0",
|
||||
"escodegen": "~1.3.3",
|
||||
"esfuzz": "~0.3.1",
|
||||
"estraverse": "~1.5.1"
|
||||
"estraverse": "~1.5.1",
|
||||
"mocha": "~2.3.4"
|
||||
},
|
||||
"browserify": {
|
||||
"transform": [
|
||||
|
|
@ -48,5 +49,6 @@
|
|||
"scripts": {
|
||||
"shrinkwrap": "rm ./npm-shrinkwrap.json; rm -rf ./node_modules; npm i && npm shrinkwrap && npm outdated",
|
||||
"test": "node test/run-tests.js"
|
||||
}
|
||||
},
|
||||
"keywords": ["uglify", "uglify-js", "minify", "minifier"]
|
||||
}
|
||||
|
|
|
|||
1155
test/compress/collapse_vars.js
Normal file
1155
test/compress/collapse_vars.js
Normal file
File diff suppressed because it is too large
Load Diff
|
|
@ -738,3 +738,77 @@ conditional_or: {
|
|||
a = condition + 3 || null;
|
||||
}
|
||||
}
|
||||
|
||||
trivial_boolean_ternary_expressions : {
|
||||
options = {
|
||||
conditionals: true,
|
||||
evaluate : true,
|
||||
booleans : true
|
||||
};
|
||||
input: {
|
||||
f('foo' in m ? true : false);
|
||||
f('foo' in m ? false : true);
|
||||
|
||||
f(g ? true : false);
|
||||
f(foo() ? true : false);
|
||||
f("bar" ? true : false);
|
||||
f(5 ? true : false);
|
||||
f(5.7 ? true : false);
|
||||
f(x - y ? true : false);
|
||||
|
||||
f(x == y ? true : false);
|
||||
f(x === y ? !0 : !1);
|
||||
f(x < y ? !0 : false);
|
||||
f(x <= y ? true : false);
|
||||
f(x > y ? true : !1);
|
||||
f(x >= y ? !0 : !1);
|
||||
|
||||
f(g ? false : true);
|
||||
f(foo() ? false : true);
|
||||
f("bar" ? false : true);
|
||||
f(5 ? false : true);
|
||||
f(5.7 ? false : true);
|
||||
f(x - y ? false : true);
|
||||
|
||||
f(x == y ? !1 : !0);
|
||||
f(x === y ? false : true);
|
||||
|
||||
f(x < y ? false : true);
|
||||
f(x <= y ? false : !0);
|
||||
f(x > y ? !1 : true);
|
||||
f(x >= y ? !1 : !0);
|
||||
}
|
||||
expect: {
|
||||
f('foo' in m);
|
||||
f(!('foo' in m));
|
||||
|
||||
f(!!g);
|
||||
f(!!foo());
|
||||
f(!0);
|
||||
f(!0);
|
||||
f(!0);
|
||||
f(!!(x - y));
|
||||
|
||||
f(x == y);
|
||||
f(x === y);
|
||||
f(x < y);
|
||||
f(x <= y);
|
||||
f(x > y);
|
||||
f(x >= y);
|
||||
|
||||
f(!g);
|
||||
f(!foo());
|
||||
f(!1);
|
||||
f(!1);
|
||||
f(!1);
|
||||
f(!(x - y));
|
||||
|
||||
f(x != y);
|
||||
f(x !== y);
|
||||
|
||||
f(!(x < y));
|
||||
f(!(x <= y));
|
||||
f(!(x > y));
|
||||
f(!(x >= y));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -87,3 +87,120 @@ dead_code_constant_boolean_should_warn_more: {
|
|||
var moo;
|
||||
}
|
||||
}
|
||||
|
||||
dead_code_const_declaration: {
|
||||
options = {
|
||||
dead_code : true,
|
||||
loops : true,
|
||||
booleans : true,
|
||||
conditionals : true,
|
||||
evaluate : true
|
||||
};
|
||||
input: {
|
||||
var unused;
|
||||
const CONST_FOO = false;
|
||||
if (CONST_FOO) {
|
||||
console.log("unreachable");
|
||||
var moo;
|
||||
function bar() {}
|
||||
}
|
||||
}
|
||||
expect: {
|
||||
var unused;
|
||||
const CONST_FOO = !1;
|
||||
var moo;
|
||||
function bar() {}
|
||||
}
|
||||
}
|
||||
|
||||
dead_code_const_annotation: {
|
||||
options = {
|
||||
dead_code : true,
|
||||
loops : true,
|
||||
booleans : true,
|
||||
conditionals : true,
|
||||
evaluate : true
|
||||
};
|
||||
input: {
|
||||
var unused;
|
||||
/** @const */ var CONST_FOO_ANN = false;
|
||||
if (CONST_FOO_ANN) {
|
||||
console.log("unreachable");
|
||||
var moo;
|
||||
function bar() {}
|
||||
}
|
||||
}
|
||||
expect: {
|
||||
var unused;
|
||||
var CONST_FOO_ANN = !1;
|
||||
var moo;
|
||||
function bar() {}
|
||||
}
|
||||
}
|
||||
|
||||
dead_code_const_annotation_regex: {
|
||||
options = {
|
||||
dead_code : true,
|
||||
loops : true,
|
||||
booleans : true,
|
||||
conditionals : true,
|
||||
evaluate : true
|
||||
};
|
||||
input: {
|
||||
var unused;
|
||||
// @constraint this shouldn't be a constant
|
||||
var CONST_FOO_ANN = false;
|
||||
if (CONST_FOO_ANN) {
|
||||
console.log("reachable");
|
||||
}
|
||||
}
|
||||
expect: {
|
||||
var unused;
|
||||
var CONST_FOO_ANN = !1;
|
||||
CONST_FOO_ANN && console.log('reachable');
|
||||
}
|
||||
}
|
||||
|
||||
dead_code_const_annotation_complex_scope: {
|
||||
options = {
|
||||
dead_code : true,
|
||||
loops : true,
|
||||
booleans : true,
|
||||
conditionals : true,
|
||||
evaluate : true
|
||||
};
|
||||
input: {
|
||||
var unused_var;
|
||||
/** @const */ var test = 'test';
|
||||
// @const
|
||||
var CONST_FOO_ANN = false;
|
||||
var unused_var_2;
|
||||
if (CONST_FOO_ANN) {
|
||||
console.log("unreachable");
|
||||
var moo;
|
||||
function bar() {}
|
||||
}
|
||||
if (test === 'test') {
|
||||
var beef = 'good';
|
||||
/** @const */ var meat = 'beef';
|
||||
var pork = 'bad';
|
||||
if (meat === 'pork') {
|
||||
console.log('also unreachable');
|
||||
} else if (pork === 'good') {
|
||||
console.log('reached, not const');
|
||||
}
|
||||
}
|
||||
}
|
||||
expect: {
|
||||
var unused_var;
|
||||
var test = 'test';
|
||||
var CONST_FOO_ANN = !1;
|
||||
var unused_var_2;
|
||||
var moo;
|
||||
function bar() {}
|
||||
var beef = 'good';
|
||||
var meat = 'beef';
|
||||
var pork = 'bad';
|
||||
'good' === pork && console.log('reached, not const');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,3 +9,50 @@ keep_name_of_setter: {
|
|||
input: { a = { set foo () {} } }
|
||||
expect: { a = { set foo () {} } }
|
||||
}
|
||||
|
||||
setter_with_operator_keys: {
|
||||
input: {
|
||||
var tokenCodes = {
|
||||
get instanceof(){
|
||||
return test0;
|
||||
},
|
||||
set instanceof(value){
|
||||
test0 = value;
|
||||
},
|
||||
set typeof(value){
|
||||
test1 = value;
|
||||
},
|
||||
get typeof(){
|
||||
return test1;
|
||||
},
|
||||
set else(value){
|
||||
test2 = value;
|
||||
},
|
||||
get else(){
|
||||
return test2;
|
||||
}
|
||||
};
|
||||
}
|
||||
expect: {
|
||||
var tokenCodes = {
|
||||
get instanceof(){
|
||||
return test0;
|
||||
},
|
||||
set instanceof(value){
|
||||
test0 = value;
|
||||
},
|
||||
set typeof(value){
|
||||
test1 = value;
|
||||
},
|
||||
get typeof(){
|
||||
return test1;
|
||||
},
|
||||
set else(value){
|
||||
test2 = value;
|
||||
},
|
||||
get else(){
|
||||
return test2;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -1,23 +1,27 @@
|
|||
remove_redundant_sequence_items: {
|
||||
options = { side_effects: true };
|
||||
input: {
|
||||
(0, 1, eval)();
|
||||
(0, 1, logThis)();
|
||||
(0, 1, _decorators.logThis)();
|
||||
}
|
||||
expect: {
|
||||
(0, logThis)();
|
||||
(0, eval)();
|
||||
logThis();
|
||||
(0, _decorators.logThis)();
|
||||
}
|
||||
}
|
||||
|
||||
dont_remove_lexical_binding_sequence: {
|
||||
dont_remove_this_binding_sequence: {
|
||||
options = { side_effects: true };
|
||||
input: {
|
||||
(0, eval)();
|
||||
(0, logThis)();
|
||||
(0, _decorators.logThis)();
|
||||
}
|
||||
expect: {
|
||||
(0, logThis)();
|
||||
(0, eval)();
|
||||
logThis();
|
||||
(0, _decorators.logThis)();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
32
test/compress/issue-892.js
Normal file
32
test/compress/issue-892.js
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
dont_mangle_arguments: {
|
||||
mangle = {
|
||||
};
|
||||
options = {
|
||||
sequences : true,
|
||||
properties : true,
|
||||
dead_code : true,
|
||||
drop_debugger : true,
|
||||
conditionals : true,
|
||||
comparisons : true,
|
||||
evaluate : true,
|
||||
booleans : true,
|
||||
loops : true,
|
||||
unused : true,
|
||||
hoist_funs : true,
|
||||
keep_fargs : true,
|
||||
keep_fnames : false,
|
||||
hoist_vars : true,
|
||||
if_return : true,
|
||||
join_vars : true,
|
||||
cascade : true,
|
||||
side_effects : true,
|
||||
negate_iife : false
|
||||
};
|
||||
input: {
|
||||
(function(){
|
||||
var arguments = arguments, not_arguments = 9;
|
||||
console.log(not_arguments, arguments);
|
||||
})(5,6,7);
|
||||
}
|
||||
expect_exact: "(function(){var arguments=arguments,o=9;console.log(o,arguments)})(5,6,7);"
|
||||
}
|
||||
20
test/compress/issue-913.js
Normal file
20
test/compress/issue-913.js
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
keep_var_for_in: {
|
||||
options = {
|
||||
hoist_vars: true,
|
||||
unused: true
|
||||
};
|
||||
input: {
|
||||
(function(obj){
|
||||
var foo = 5;
|
||||
for (var i in obj)
|
||||
return foo;
|
||||
})();
|
||||
}
|
||||
expect: {
|
||||
(function(obj){
|
||||
var i, foo = 5;
|
||||
for (i in obj)
|
||||
return foo;
|
||||
})();
|
||||
}
|
||||
}
|
||||
96
test/compress/issue-973.js
Normal file
96
test/compress/issue-973.js
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
this_binding_conditionals: {
|
||||
options = {
|
||||
conditionals: true,
|
||||
evaluate : true
|
||||
};
|
||||
input: {
|
||||
(1 && a)();
|
||||
(0 || a)();
|
||||
(0 || 1 && a)();
|
||||
(1 ? a : 0)();
|
||||
|
||||
(1 && a.b)();
|
||||
(0 || a.b)();
|
||||
(0 || 1 && a.b)();
|
||||
(1 ? a.b : 0)();
|
||||
|
||||
(1 && a[b])();
|
||||
(0 || a[b])();
|
||||
(0 || 1 && a[b])();
|
||||
(1 ? a[b] : 0)();
|
||||
|
||||
(1 && eval)();
|
||||
(0 || eval)();
|
||||
(0 || 1 && eval)();
|
||||
(1 ? eval : 0)();
|
||||
}
|
||||
expect: {
|
||||
a();
|
||||
a();
|
||||
a();
|
||||
a();
|
||||
|
||||
(0, a.b)();
|
||||
(0, a.b)();
|
||||
(0, a.b)();
|
||||
(0, a.b)();
|
||||
|
||||
(0, a[b])();
|
||||
(0, a[b])();
|
||||
(0, a[b])();
|
||||
(0, a[b])();
|
||||
|
||||
(0, eval)();
|
||||
(0, eval)();
|
||||
(0, eval)();
|
||||
(0, eval)();
|
||||
}
|
||||
}
|
||||
|
||||
this_binding_collapse_vars: {
|
||||
options = {
|
||||
collapse_vars: true,
|
||||
};
|
||||
input: {
|
||||
var c = a; c();
|
||||
var d = a.b; d();
|
||||
var e = eval; e();
|
||||
}
|
||||
expect: {
|
||||
a();
|
||||
(0, a.b)();
|
||||
(0, eval)();
|
||||
}
|
||||
}
|
||||
|
||||
this_binding_side_effects: {
|
||||
options = {
|
||||
side_effects : true
|
||||
};
|
||||
input: {
|
||||
(function (foo) {
|
||||
(0, foo)();
|
||||
(0, foo.bar)();
|
||||
(0, eval)('console.log(foo);');
|
||||
}());
|
||||
(function (foo) {
|
||||
var eval = console;
|
||||
(0, foo)();
|
||||
(0, foo.bar)();
|
||||
(0, eval)('console.log(foo);');
|
||||
}());
|
||||
}
|
||||
expect: {
|
||||
(function (foo) {
|
||||
foo();
|
||||
(0, foo.bar)();
|
||||
(0, eval)('console.log(foo);');
|
||||
}());
|
||||
(function (foo) {
|
||||
var eval = console;
|
||||
foo();
|
||||
(0, foo.bar)();
|
||||
(0, eval)('console.log(foo);');
|
||||
}());
|
||||
}
|
||||
}
|
||||
88
test/compress/issue-976.js
Normal file
88
test/compress/issue-976.js
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
eval_collapse_vars: {
|
||||
options = {
|
||||
collapse_vars:true, sequences:false, properties:true, dead_code:true, conditionals:true,
|
||||
comparisons:true, evaluate:true, booleans:true, loops:true, unused:true, hoist_funs:true,
|
||||
keep_fargs:true, if_return:true, join_vars:true, cascade:true, side_effects:true
|
||||
};
|
||||
input: {
|
||||
function f1() {
|
||||
var e = 7;
|
||||
var s = "abcdef";
|
||||
var i = 2;
|
||||
var eval = console.log.bind(console);
|
||||
var x = s.charAt(i++);
|
||||
var y = s.charAt(i++);
|
||||
var z = s.charAt(i++);
|
||||
eval(x, y, z, e);
|
||||
}
|
||||
function p1() { var a = foo(), b = bar(), eval = baz(); return a + b + eval; }
|
||||
function p2() { var a = foo(), b = bar(), eval = baz; return a + b + eval(); }
|
||||
(function f2(eval) {
|
||||
var a = 2;
|
||||
console.log(a - 5);
|
||||
eval("console.log(a);");
|
||||
})(eval);
|
||||
}
|
||||
expect: {
|
||||
function f1() {
|
||||
var e = 7,
|
||||
s = "abcdef",
|
||||
i = 2,
|
||||
eval = console.log.bind(console),
|
||||
x = s.charAt(i++),
|
||||
y = s.charAt(i++),
|
||||
z = s.charAt(i++);
|
||||
eval(x, y, z, e);
|
||||
}
|
||||
function p1() { return foo() + bar() + baz(); }
|
||||
function p2() { var a = foo(), b = bar(), eval = baz; return a + b + eval(); }
|
||||
(function f2(eval) {
|
||||
var a = 2;
|
||||
console.log(a - 5);
|
||||
eval("console.log(a);");
|
||||
})(eval);
|
||||
}
|
||||
}
|
||||
|
||||
eval_unused: {
|
||||
options = { unused: true, keep_fargs: false };
|
||||
input: {
|
||||
function f1(a, eval, c, d, e) {
|
||||
return a('c') + eval;
|
||||
}
|
||||
function f2(a, b, c, d, e) {
|
||||
return a + eval('c');
|
||||
}
|
||||
function f3(a, eval, c, d, e) {
|
||||
return a + eval('c');
|
||||
}
|
||||
}
|
||||
expect: {
|
||||
function f1(a, eval) {
|
||||
return a('c') + eval;
|
||||
}
|
||||
function f2(a, b, c, d, e) {
|
||||
return a + eval('c');
|
||||
}
|
||||
function f3(a, eval, c, d, e) {
|
||||
return a + eval('c');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
eval_mangle: {
|
||||
mangle = {
|
||||
};
|
||||
input: {
|
||||
function f1(a, eval, c, d, e) {
|
||||
return a('c') + eval;
|
||||
}
|
||||
function f2(a, b, c, d, e) {
|
||||
return a + eval('c');
|
||||
}
|
||||
function f3(a, eval, c, d, e) {
|
||||
return a + eval('c');
|
||||
}
|
||||
}
|
||||
expect_exact: 'function f1(n,c,e,a,o){return n("c")+c}function f2(a,b,c,d,e){return a+eval("c")}function f3(a,eval,c,d,e){return a+eval("c")}'
|
||||
}
|
||||
89
test/compress/issue-979.js
Normal file
89
test/compress/issue-979.js
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
issue979_reported: {
|
||||
options = {
|
||||
sequences:true, properties:true, dead_code:true, conditionals:true,
|
||||
comparisons:true, evaluate:true, booleans:true, loops:true, unused:true, hoist_funs:true,
|
||||
keep_fargs:true, if_return:true, join_vars:true, cascade:true, side_effects:true
|
||||
}
|
||||
input: {
|
||||
function f1() {
|
||||
if (a == 1 || b == 2) {
|
||||
foo();
|
||||
}
|
||||
}
|
||||
function f2() {
|
||||
if (!(a == 1 || b == 2)) {
|
||||
}
|
||||
else {
|
||||
foo();
|
||||
}
|
||||
}
|
||||
}
|
||||
expect: {
|
||||
function f1() {
|
||||
1!=a&&2!=b||foo();
|
||||
}
|
||||
function f2() {
|
||||
1!=a&&2!=b||foo();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
issue979_test_negated_is_best: {
|
||||
options = {
|
||||
sequences:true, properties:true, dead_code:true, conditionals:true,
|
||||
comparisons:true, evaluate:true, booleans:true, loops:true, unused:true, hoist_funs:true,
|
||||
keep_fargs:true, if_return:true, join_vars:true, cascade:true, side_effects:true
|
||||
}
|
||||
input: {
|
||||
function f3() {
|
||||
if (a == 1 | b == 2) {
|
||||
foo();
|
||||
}
|
||||
}
|
||||
function f4() {
|
||||
if (!(a == 1 | b == 2)) {
|
||||
}
|
||||
else {
|
||||
foo();
|
||||
}
|
||||
}
|
||||
function f5() {
|
||||
if (a == 1 && b == 2) {
|
||||
foo();
|
||||
}
|
||||
}
|
||||
function f6() {
|
||||
if (!(a == 1 && b == 2)) {
|
||||
}
|
||||
else {
|
||||
foo();
|
||||
}
|
||||
}
|
||||
function f7() {
|
||||
if (a == 1 || b == 2) {
|
||||
foo();
|
||||
}
|
||||
else {
|
||||
return bar();
|
||||
}
|
||||
}
|
||||
}
|
||||
expect: {
|
||||
function f3() {
|
||||
1==a|2==b&&foo();
|
||||
}
|
||||
function f4() {
|
||||
1==a|2==b&&foo();
|
||||
}
|
||||
function f5() {
|
||||
1==a&&2==b&&foo();
|
||||
}
|
||||
function f6() {
|
||||
1!=a||2!=b||foo();
|
||||
}
|
||||
function f7() {
|
||||
return 1!=a&&2!=b?bar():void foo();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
19
test/compress/numbers.js
Normal file
19
test/compress/numbers.js
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
hex_numbers_in_parentheses_for_prototype_functions: {
|
||||
input: {
|
||||
(-2);
|
||||
(-2).toFixed(0);
|
||||
|
||||
(2);
|
||||
(2).toFixed(0);
|
||||
|
||||
(0.2);
|
||||
(0.2).toFixed(0);
|
||||
|
||||
(0.00000002);
|
||||
(0.00000002).toFixed(0);
|
||||
|
||||
(1000000000000000128);
|
||||
(1000000000000000128).toFixed(0);
|
||||
}
|
||||
expect_exact: "-2;(-2).toFixed(0);2;2..toFixed(0);.2;.2.toFixed(0);2e-8;2e-8.toFixed(0);0xde0b6b3a7640080;(0xde0b6b3a7640080).toFixed(0);"
|
||||
}
|
||||
29
test/mocha.js
Normal file
29
test/mocha.js
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
var Mocha = require('mocha'),
|
||||
fs = require('fs'),
|
||||
path = require('path');
|
||||
|
||||
// Instantiate a Mocha instance.
|
||||
var mocha = new Mocha({});
|
||||
|
||||
var testDir = __dirname + '/mocha/';
|
||||
|
||||
// Add each .js file to the mocha instance
|
||||
fs.readdirSync(testDir).filter(function(file){
|
||||
// Only keep the .js files
|
||||
return file.substr(-3) === '.js';
|
||||
|
||||
}).forEach(function(file){
|
||||
mocha.addFile(
|
||||
path.join(testDir, file)
|
||||
);
|
||||
});
|
||||
|
||||
module.exports = function() {
|
||||
mocha.run(function(failures) {
|
||||
if (failures !== 0) {
|
||||
process.on('exit', function () {
|
||||
process.exit(failures);
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
22
test/mocha/arguments.js
Normal file
22
test/mocha/arguments.js
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
var UglifyJS = require('../../');
|
||||
var assert = require("assert");
|
||||
|
||||
describe("arguments", function() {
|
||||
it("Should known that arguments in functions are local scoped", function() {
|
||||
var ast = UglifyJS.parse("var arguments; var f = function() {arguments.length}");
|
||||
ast.figure_out_scope();
|
||||
|
||||
// Test scope of `var arguments`
|
||||
assert.strictEqual(ast.find_variable("arguments").global, true);
|
||||
|
||||
// Select arguments symbol in function
|
||||
var symbol = ast.body[1].definitions[0].value.find_variable("arguments");
|
||||
|
||||
assert.strictEqual(symbol.global, false);
|
||||
assert.strictEqual(symbol.scope, ast. // From ast
|
||||
body[1]. // Select 2nd statement (equals to `var f ...`)
|
||||
definitions[0]. // First definition of selected statement
|
||||
value // Select function as scope
|
||||
);
|
||||
});
|
||||
});
|
||||
45
test/mocha/comment-filter.js
Normal file
45
test/mocha/comment-filter.js
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
var UglifyJS = require('../../');
|
||||
var assert = require("assert");
|
||||
|
||||
describe("comment filters", function() {
|
||||
it("Should be able to filter comments by passing regex", function() {
|
||||
var ast = UglifyJS.parse("/*!test1*/\n/*test2*/\n//!test3\n//test4\n<!--test5\n<!--!test6\n-->test7\n-->!test8");
|
||||
assert.strictEqual(ast.print_to_string({comments: /^!/}), "/*!test1*/\n//!test3\n//!test6\n//!test8\n");
|
||||
});
|
||||
|
||||
it("Should be able to filter comments by passing a function", function() {
|
||||
var ast = UglifyJS.parse("/*TEST 123*/\n//An other comment\n//8 chars.");
|
||||
var f = function(node, comment) {
|
||||
return comment.value.length === 8;
|
||||
};
|
||||
|
||||
assert.strictEqual(ast.print_to_string({comments: f}), "/*TEST 123*/\n//8 chars.\n");
|
||||
});
|
||||
|
||||
it("Should be able to get the comment and comment type when using a function", function() {
|
||||
var ast = UglifyJS.parse("/*!test1*/\n/*test2*/\n//!test3\n//test4\n<!--test5\n<!--!test6\n-->test7\n-->!test8");
|
||||
var f = function(node, comment) {
|
||||
return comment.type == "comment1" || comment.type == "comment3";
|
||||
};
|
||||
|
||||
assert.strictEqual(ast.print_to_string({comments: f}), "//!test3\n//test4\n//test5\n//!test6\n");
|
||||
});
|
||||
|
||||
it("Should be able to filter comments by passing a boolean", function() {
|
||||
var ast = UglifyJS.parse("/*!test1*/\n/*test2*/\n//!test3\n//test4\n<!--test5\n<!--!test6\n-->test7\n-->!test8");
|
||||
|
||||
assert.strictEqual(ast.print_to_string({comments: true}), "/*!test1*/\n/*test2*/\n//!test3\n//test4\n//test5\n//!test6\n//test7\n//!test8\n");
|
||||
assert.strictEqual(ast.print_to_string({comments: false}), "");
|
||||
});
|
||||
|
||||
it("Should never be able to filter comment5 (shebangs)", function() {
|
||||
var ast = UglifyJS.parse("#!Random comment\n//test1\n/*test2*/");
|
||||
var f = function(node, comment) {
|
||||
assert.strictEqual(comment.type === "comment5", false);
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
assert.strictEqual(ast.print_to_string({comments: f}), "#!Random comment\n//test1\n/*test2*/\n");
|
||||
});
|
||||
});
|
||||
89
test/mocha/getter-setter.js
Normal file
89
test/mocha/getter-setter.js
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
var UglifyJS = require('../../');
|
||||
var assert = require("assert");
|
||||
|
||||
describe("Getters and setters", function() {
|
||||
it("Should not accept operator symbols as getter/setter name", function() {
|
||||
var illegalOperators = [
|
||||
"++",
|
||||
"--",
|
||||
"+",
|
||||
"-",
|
||||
"!",
|
||||
"~",
|
||||
"&",
|
||||
"|",
|
||||
"^",
|
||||
"*",
|
||||
"/",
|
||||
"%",
|
||||
">>",
|
||||
"<<",
|
||||
">>>",
|
||||
"<",
|
||||
">",
|
||||
"<=",
|
||||
">=",
|
||||
"==",
|
||||
"===",
|
||||
"!=",
|
||||
"!==",
|
||||
"?",
|
||||
"=",
|
||||
"+=",
|
||||
"-=",
|
||||
"/=",
|
||||
"*=",
|
||||
"%=",
|
||||
">>=",
|
||||
"<<=",
|
||||
">>>=",
|
||||
"|=",
|
||||
"^=",
|
||||
"&=",
|
||||
"&&",
|
||||
"||"
|
||||
];
|
||||
var generator = function() {
|
||||
var results = [];
|
||||
|
||||
for (var i in illegalOperators) {
|
||||
results.push({
|
||||
code: "var obj = { get " + illegalOperators[i] + "() { return test; }};",
|
||||
operator: illegalOperators[i],
|
||||
method: "get"
|
||||
});
|
||||
results.push({
|
||||
code: "var obj = { set " + illegalOperators[i] + "(value) { test = value}};",
|
||||
operator: illegalOperators[i],
|
||||
method: "set"
|
||||
});
|
||||
}
|
||||
|
||||
return results;
|
||||
};
|
||||
|
||||
var testCase = function(data) {
|
||||
return function() {
|
||||
UglifyJS.parse(data.code);
|
||||
};
|
||||
};
|
||||
|
||||
var fail = function(data) {
|
||||
return function (e) {
|
||||
return e instanceof UglifyJS.JS_Parse_Error &&
|
||||
e.message === "Invalid getter/setter name: " + data.operator;
|
||||
};
|
||||
};
|
||||
|
||||
var errorMessage = function(data) {
|
||||
return "Expected but didn't get a syntax error while parsing following line:\n" + data.code;
|
||||
};
|
||||
|
||||
var tests = generator();
|
||||
for (var i = 0; i < tests.length; i++) {
|
||||
var test = tests[i];
|
||||
assert.throws(testCase(test), fail(test), errorMessage(test));
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
34
test/mocha/string-literal.js
Normal file
34
test/mocha/string-literal.js
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
var UglifyJS = require('../../');
|
||||
var assert = require("assert");
|
||||
|
||||
describe("String literals", function() {
|
||||
it("Should throw syntax error if a string literal contains a newline", function() {
|
||||
var inputs = [
|
||||
"'\n'",
|
||||
"'\r'",
|
||||
'"\r\n"',
|
||||
"'\u2028'",
|
||||
'"\u2029"'
|
||||
];
|
||||
|
||||
var test = function(input) {
|
||||
return function() {
|
||||
var ast = UglifyJS.parse(input);
|
||||
};
|
||||
};
|
||||
|
||||
var error = function(e) {
|
||||
return e instanceof UglifyJS.JS_Parse_Error &&
|
||||
e.message === "Unterminated string constant";
|
||||
};
|
||||
|
||||
for (var input in inputs) {
|
||||
assert.throws(test(inputs[input]), error);
|
||||
}
|
||||
});
|
||||
|
||||
it("Should not throw syntax error if a string has a line continuation", function() {
|
||||
var output = UglifyJS.parse('var a = "a\\\nb";').print_to_string();
|
||||
assert.equal(output, 'var a="ab";');
|
||||
});
|
||||
});
|
||||
|
|
@ -16,6 +16,9 @@ if (failures) {
|
|||
process.exit(1);
|
||||
}
|
||||
|
||||
var mocha_tests = require("./mocha.js");
|
||||
mocha_tests();
|
||||
|
||||
var run_sourcemaps_tests = require('./sourcemaps');
|
||||
run_sourcemaps_tests();
|
||||
|
||||
|
|
@ -105,6 +108,7 @@ function run_compress_tests() {
|
|||
var output = input.transform(cmp);
|
||||
output.figure_out_scope();
|
||||
if (test.mangle) {
|
||||
output.compute_char_frequency(test.mangle);
|
||||
output.mangle_names(test.mangle);
|
||||
}
|
||||
output = make_code(output, output_options);
|
||||
|
|
|
|||
|
|
@ -15,3 +15,4 @@ exports["parse"] = parse;
|
|||
exports["push_uniq"] = push_uniq;
|
||||
exports["string_template"] = string_template;
|
||||
exports["is_identifier"] = is_identifier;
|
||||
exports["SymbolDef"] = SymbolDef;
|
||||
|
|
|
|||
|
|
@ -32,15 +32,18 @@ UglifyJS.AST_Node.warn_function = function(txt) {
|
|||
|
||||
exports.minify = function(files, options) {
|
||||
options = UglifyJS.defaults(options, {
|
||||
spidermonkey : false,
|
||||
outSourceMap : null,
|
||||
sourceRoot : null,
|
||||
inSourceMap : null,
|
||||
fromString : false,
|
||||
warnings : false,
|
||||
mangle : {},
|
||||
output : null,
|
||||
compress : {}
|
||||
spidermonkey : false,
|
||||
outSourceMap : null,
|
||||
sourceRoot : null,
|
||||
inSourceMap : null,
|
||||
fromString : false,
|
||||
warnings : false,
|
||||
mangle : {},
|
||||
mangleProperties : false,
|
||||
nameCache : null,
|
||||
output : null,
|
||||
compress : {},
|
||||
parse : {}
|
||||
});
|
||||
UglifyJS.base54.reset();
|
||||
|
||||
|
|
@ -60,7 +63,8 @@ exports.minify = function(files, options) {
|
|||
sourcesContent[file] = code;
|
||||
toplevel = UglifyJS.parse(code, {
|
||||
filename: options.fromString ? i : file,
|
||||
toplevel: toplevel
|
||||
toplevel: toplevel,
|
||||
bare_returns: options.parse ? options.parse.bare_returns : undefined
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
@ -77,14 +81,21 @@ exports.minify = function(files, options) {
|
|||
toplevel = toplevel.transform(sq);
|
||||
}
|
||||
|
||||
// 3. mangle
|
||||
// 3. mangle properties
|
||||
if (options.mangleProperties || options.nameCache) {
|
||||
options.mangleProperties.cache = UglifyJS.readNameCache(options.nameCache, "props");
|
||||
toplevel = UglifyJS.mangle_properties(toplevel, options.mangleProperties);
|
||||
UglifyJS.writeNameCache(options.nameCache, "props", options.mangleProperties.cache);
|
||||
}
|
||||
|
||||
// 4. mangle
|
||||
if (options.mangle) {
|
||||
toplevel.figure_out_scope(options.mangle);
|
||||
toplevel.compute_char_frequency(options.mangle);
|
||||
toplevel.mangle_names(options.mangle);
|
||||
}
|
||||
|
||||
// 4. output
|
||||
// 5. output
|
||||
var inMap = options.inSourceMap;
|
||||
var output = {};
|
||||
if (typeof options.inSourceMap == "string") {
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user