aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorAlex Lam S.L <alexlamsl@gmail.com>2017-02-24 07:33:57 +0800
committerGitHub <noreply@github.com>2017-02-24 07:33:57 +0800
commit229e42cdee14c384b7a48bd0882bb27d98db040a (patch)
treeaefc78b2d7dded10d049296336324933596d770d /lib
parenteb55d8a9bb37cc28303ace91337784dbf0777d03 (diff)
parent4e49302916fe395f5c63992aa28c33392208fb27 (diff)
downloadtracifyjs-229e42cdee14c384b7a48bd0882bb27d98db040a.tar.gz
tracifyjs-229e42cdee14c384b7a48bd0882bb27d98db040a.zip
Merge pull request #1485 from alexlamsl/merge-2.8.0
2.8.0 staging
Diffstat (limited to 'lib')
-rw-r--r--lib/compress.js745
-rw-r--r--lib/output.js78
-rw-r--r--lib/parse.js8
-rw-r--r--lib/scope.js65
-rw-r--r--lib/utils.js23
5 files changed, 646 insertions, 273 deletions
diff --git a/lib/compress.js b/lib/compress.js
index 4e45df92..2bc1c5a5 100644
--- a/lib/compress.js
+++ b/lib/compress.js
@@ -54,20 +54,23 @@ function Compressor(options, false_by_default) {
drop_debugger : !false_by_default,
unsafe : false,
unsafe_comps : false,
+ unsafe_proto : false,
conditionals : !false_by_default,
comparisons : !false_by_default,
evaluate : !false_by_default,
booleans : !false_by_default,
loops : !false_by_default,
unused : !false_by_default,
+ toplevel : !!options["top_retain"],
+ top_retain : null,
hoist_funs : !false_by_default,
keep_fargs : true,
keep_fnames : false,
hoist_vars : false,
if_return : !false_by_default,
join_vars : !false_by_default,
- collapse_vars : false,
- reduce_vars : false,
+ collapse_vars : !false_by_default,
+ reduce_vars : !false_by_default,
cascade : !false_by_default,
side_effects : !false_by_default,
pure_getters : false,
@@ -80,6 +83,29 @@ function Compressor(options, false_by_default) {
global_defs : {},
passes : 1,
}, true);
+ var pure_funcs = this.options["pure_funcs"];
+ if (typeof pure_funcs == "function") {
+ this.pure_funcs = pure_funcs;
+ } else {
+ this.pure_funcs = pure_funcs ? function(node) {
+ return pure_funcs.indexOf(node.expression.print_to_string()) < 0;
+ } : return_true;
+ }
+ var top_retain = this.options["top_retain"];
+ if (top_retain instanceof RegExp) {
+ this.top_retain = function(def) {
+ return top_retain.test(def.name);
+ };
+ } else if (typeof top_retain === "function") {
+ this.top_retain = top_retain;
+ } else if (top_retain) {
+ if (typeof top_retain === "string") {
+ top_retain = top_retain.split(/,/);
+ }
+ this.top_retain = function(def) {
+ return top_retain.indexOf(def.name) >= 0;
+ };
+ }
var sequences = this.options["sequences"];
this.sequences_limit = sequences == 1 ? 200 : sequences | 0;
this.warnings_produced = {};
@@ -91,7 +117,8 @@ merge(Compressor.prototype, {
compress: function(node) {
var passes = +this.options.passes || 1;
for (var pass = 0; pass < passes && pass < 3; ++pass) {
- if (pass > 0) node.clear_opt_flags();
+ if (pass > 0 || this.option("reduce_vars"))
+ node.reset_opt_flags(this);
node = node.transform(this);
}
return node;
@@ -150,19 +177,45 @@ merge(Compressor.prototype, {
return this.print_to_string() == node.print_to_string();
});
- AST_Node.DEFMETHOD("clear_opt_flags", function(){
- this.walk(new TreeWalker(function(node){
+ AST_Node.DEFMETHOD("reset_opt_flags", function(compressor){
+ var reduce_vars = compressor.option("reduce_vars");
+ var tw = new TreeWalker(function(node){
+ if (reduce_vars && node instanceof AST_Scope) {
+ node.variables.each(function(def) {
+ delete def.modified;
+ });
+ }
if (node instanceof AST_SymbolRef) {
var d = node.definition();
- if (d && d.init) {
+ if (d.init) {
delete d.init._evaluated;
}
+ if (reduce_vars && (d.orig.length > 1 || isModified(node, 0))) {
+ d.modified = true;
+ }
+ }
+ if (reduce_vars && node instanceof AST_Call && node.expression instanceof AST_Function) {
+ node.expression.argnames.forEach(function(arg, i) {
+ arg.definition().init = node.args[i] || make_node(AST_Undefined, node);
+ });
}
if (!(node instanceof AST_Directive || node instanceof AST_Constant)) {
node._squeezed = false;
node._optimized = false;
}
- }));
+ });
+ this.walk(tw);
+
+ function isModified(node, level) {
+ var parent = tw.parent(level);
+ if (parent instanceof AST_Unary && (parent.operator === "++" || parent.operator === "--")
+ || parent instanceof AST_Assign && parent.left === node
+ || parent instanceof AST_Call && parent.expression === node) {
+ return true;
+ } else if (parent instanceof AST_PropAccess && parent.expression === node) {
+ return isModified(parent, level + 1);
+ }
+ }
});
function make_node(ctor, orig, props) {
@@ -175,22 +228,11 @@ merge(Compressor.prototype, {
};
function make_node_from_constant(compressor, val, orig) {
- // XXX: WIP.
- // if (val instanceof AST_Node) return val.transform(new TreeTransformer(null, function(node){
- // if (node instanceof AST_SymbolRef) {
- // var scope = compressor.find_parent(AST_Scope);
- // var def = scope.find_variable(node);
- // node.thedef = def;
- // return node;
- // }
- // })).transform(compressor);
-
- if (val instanceof AST_Node) return val.transform(compressor);
switch (typeof val) {
case "string":
return make_node(AST_String, orig, {
value: val
- }).optimize(compressor);
+ });
case "number":
if (isNaN(val)) {
return make_node(AST_NaN, orig);
@@ -203,17 +245,17 @@ merge(Compressor.prototype, {
});
}
- return make_node(AST_Number, orig, { value: val }).optimize(compressor);
+ return make_node(AST_Number, orig, { value: val });
case "boolean":
- return make_node(val ? AST_True : AST_False, orig).optimize(compressor);
+ return make_node(val ? AST_True : AST_False, orig).transform(compressor);
case "undefined":
- return make_node(AST_Undefined, orig).optimize(compressor);
+ return make_node(AST_Undefined, orig).transform(compressor);
default:
if (val === null) {
- return make_node(AST_Null, orig, { value: null }).optimize(compressor);
+ return make_node(AST_Null, orig, { value: null });
}
if (val instanceof RegExp) {
- return make_node(AST_RegExp, orig, { value: val }).optimize(compressor);
+ return make_node(AST_RegExp, orig, { value: val });
}
throw new Error(string_template("Can't handle constant of type: {type}", {
type: typeof val
@@ -261,6 +303,22 @@ merge(Compressor.prototype, {
return x;
};
+ var readOnlyPrefix = makePredicate("! ~ + - void typeof");
+ function statement_to_expression(stat) {
+ if (stat.body instanceof AST_UnaryPrefix && readOnlyPrefix(stat.body.operator)) {
+ return stat.body.expression;
+ } else {
+ return stat.body;
+ }
+ }
+
+ function is_iife_call(node) {
+ if (node instanceof AST_Call && !(node instanceof AST_New)) {
+ return node.expression instanceof AST_Function || is_iife_call(node.expression);
+ }
+ return false;
+ }
+
function tighten_body(statements, compressor) {
var CHANGED, max_iter = 10;
do {
@@ -286,10 +344,6 @@ merge(Compressor.prototype, {
}
} while (CHANGED && max_iter-- > 0);
- if (compressor.option("negate_iife")) {
- negate_iifes(statements, compressor);
- }
-
return statements;
function collapse_single_use_vars(statements, compressor) {
@@ -437,7 +491,7 @@ merge(Compressor.prototype, {
var_defs_removed = true;
}
// Further optimize statement after substitution.
- stat.clear_opt_flags();
+ stat.reset_opt_flags(compressor);
compressor.warn("Replacing " + (is_constant ? "constant" : "variable") +
" " + var_name + " [{file}:{line},{col}]", node.start);
@@ -546,7 +600,7 @@ merge(Compressor.prototype, {
var self = compressor.self();
var multiple_if_returns = has_multiple_if_returns(statements);
var in_lambda = self instanceof AST_Lambda;
- var ret = [];
+ var ret = []; // Optimized statements, build from tail to front
loop: for (var i = statements.length; --i >= 0;) {
var stat = statements[i];
switch (true) {
@@ -607,19 +661,21 @@ merge(Compressor.prototype, {
ret = funs.concat([ stat.transform(compressor) ]);
continue loop;
}
+
//---
- // XXX: what was the intention of this case?
+ // if (a) return b; if (c) return d; e; ==> return a ? b : c ? d : void e;
+ //
// if sequences is not enabled, this can lead to an endless loop (issue #866).
// however, with sequences on this helps producing slightly better output for
// the example code.
if (compressor.option("sequences")
+ && i > 0 && statements[i - 1] instanceof AST_If && statements[i - 1].body instanceof AST_Return
&& ret.length == 1 && in_lambda && ret[0] instanceof AST_SimpleStatement
- && (!stat.alternative || stat.alternative instanceof AST_SimpleStatement)) {
+ && !stat.alternative) {
CHANGED = true;
ret.push(make_node(AST_Return, ret[0], {
value: make_node(AST_Undefined, ret[0])
}).transform(compressor));
- ret = as_statement_array(stat.alternative).concat(ret);
ret.unshift(stat);
continue loop;
}
@@ -730,8 +786,11 @@ merge(Compressor.prototype, {
seq = [];
};
statements.forEach(function(stat){
- if (stat instanceof AST_SimpleStatement && seqLength(seq) < compressor.sequences_limit) {
- seq.push(stat.body);
+ if (stat instanceof AST_SimpleStatement) {
+ if (seqLength(seq) >= compressor.sequences_limit) {
+ push_seq();
+ }
+ seq.push(seq.length > 0 ? statement_to_expression(stat) : stat.body);
} else {
push_seq();
ret.push(stat);
@@ -780,7 +839,7 @@ merge(Compressor.prototype, {
stat.init = cons_seq(stat.init);
}
else if (!stat.init) {
- stat.init = prev.body;
+ stat.init = statement_to_expression(prev);
ret.pop();
}
} catch(ex) {
@@ -837,50 +896,6 @@ merge(Compressor.prototype, {
}, []);
};
- function negate_iifes(statements, compressor) {
- function is_iife_call(node) {
- if (node instanceof AST_Call) {
- return node.expression instanceof AST_Function || is_iife_call(node.expression);
- }
- return false;
- }
-
- statements.forEach(function(stat){
- if (stat instanceof AST_SimpleStatement) {
- stat.body = (function transform(thing) {
- return thing.transform(new TreeTransformer(function(node){
- if (node instanceof AST_New) {
- return node;
- }
- if (is_iife_call(node)) {
- return make_node(AST_UnaryPrefix, node, {
- operator: "!",
- expression: node
- });
- }
- else if (node instanceof AST_Call) {
- node.expression = transform(node.expression);
- }
- else if (node instanceof AST_Seq) {
- node.car = transform(node.car);
- }
- else if (node instanceof AST_Conditional) {
- var expr = transform(node.condition);
- if (expr !== node.condition) {
- // it has been negated, reverse
- node.condition = expr;
- var tmp = node.consequent;
- node.consequent = node.alternative;
- node.alternative = tmp;
- }
- }
- return node;
- }));
- })(stat.body);
- }
- });
- };
-
};
function extract_functions_from_statement_array(statements) {
@@ -981,11 +996,81 @@ merge(Compressor.prototype, {
|| parent instanceof AST_Assign && parent.left === node;
}
+ (function (def){
+ AST_Node.DEFMETHOD("resolve_defines", function(compressor) {
+ if (!compressor.option("global_defs")) return;
+ var def = this._find_defs(compressor, "");
+ if (def) {
+ var node, parent = this, level = 0;
+ do {
+ node = parent;
+ parent = compressor.parent(level++);
+ } while (parent instanceof AST_PropAccess && parent.expression === node);
+ if (isLHS(node, parent)) {
+ compressor.warn('global_defs ' + this.print_to_string() + ' redefined [{file}:{line},{col}]', this.start);
+ } else {
+ return def;
+ }
+ }
+ });
+ function to_node(compressor, value, orig) {
+ if (value instanceof AST_Node) return make_node(value.CTOR, orig, value);
+ if (Array.isArray(value)) return make_node(AST_Array, orig, {
+ elements: value.map(function(value) {
+ return to_node(compressor, value, orig);
+ })
+ });
+ if (value && typeof value == "object") {
+ var props = [];
+ for (var key in value) {
+ props.push(make_node(AST_ObjectKeyVal, orig, {
+ key: key,
+ value: to_node(compressor, value[key], orig)
+ }));
+ }
+ return make_node(AST_Object, orig, {
+ properties: props
+ });
+ }
+ return make_node_from_constant(compressor, value, orig);
+ }
+ def(AST_Node, noop);
+ def(AST_Dot, function(compressor, suffix){
+ return this.expression._find_defs(compressor, suffix + "." + this.property);
+ });
+ def(AST_SymbolRef, function(compressor, suffix){
+ if (!this.global()) return;
+ var name;
+ var defines = compressor.option("global_defs");
+ if (defines && HOP(defines, (name = this.name + suffix))) {
+ var node = to_node(compressor, defines[name], this);
+ var top = compressor.find_parent(AST_Toplevel);
+ node.walk(new TreeWalker(function(node) {
+ if (node instanceof AST_SymbolRef) {
+ node.scope = top;
+ node.thedef = top.def_global(node);
+ }
+ }));
+ return node;
+ }
+ });
+ })(function(node, func){
+ node.DEFMETHOD("_find_defs", func);
+ });
+
function best_of(ast1, ast2) {
return ast1.print_to_string().length >
ast2.print_to_string().length
? ast2 : ast1;
- };
+ }
+
+ function best_of_statement(ast1, ast2) {
+ return best_of(make_node(AST_SimpleStatement, ast1, {
+ body: ast1
+ }), make_node(AST_SimpleStatement, ast2, {
+ body: ast2
+ })).body;
+ }
// methods to evaluate a constant expression
(function (def){
@@ -1167,7 +1252,7 @@ merge(Compressor.prototype, {
this._evaluating = true;
try {
var d = this.definition();
- if (d && (d.constant || compressor.option("reduce_vars") && !d.modified) && d.init) {
+ if (compressor.option("reduce_vars") && !d.modified && d.init) {
if (compressor.option("unsafe")) {
if (!HOP(d.init, '_evaluated')) {
d.init._evaluated = ev(d.init, compressor);
@@ -1205,7 +1290,17 @@ merge(Compressor.prototype, {
operator: "!",
expression: exp
});
- };
+ }
+ function best(orig, alt, first_in_statement) {
+ var negated = basic_negation(orig);
+ if (first_in_statement) {
+ var stat = make_node(AST_SimpleStatement, alt, {
+ body: alt
+ });
+ return best_of(negated, stat) === stat ? alt : negated;
+ }
+ return best_of(negated, alt);
+ }
def(AST_Node, function(){
return basic_negation(this);
});
@@ -1225,13 +1320,13 @@ merge(Compressor.prototype, {
self.cdr = self.cdr.negate(compressor);
return self;
});
- def(AST_Conditional, function(compressor){
+ def(AST_Conditional, function(compressor, first_in_statement){
var self = this.clone();
self.consequent = self.consequent.negate(compressor);
self.alternative = self.alternative.negate(compressor);
- return best_of(basic_negation(this), self);
+ return best(this, self, first_in_statement);
});
- def(AST_Binary, function(compressor){
+ def(AST_Binary, function(compressor, first_in_statement){
var self = this.clone(), op = this.operator;
if (compressor.option("unsafe_comps")) {
switch (op) {
@@ -1248,23 +1343,39 @@ merge(Compressor.prototype, {
case "!==": self.operator = "==="; return self;
case "&&":
self.operator = "||";
- self.left = self.left.negate(compressor);
+ self.left = self.left.negate(compressor, first_in_statement);
self.right = self.right.negate(compressor);
- return best_of(basic_negation(this), self);
+ return best(this, self, first_in_statement);
case "||":
self.operator = "&&";
- self.left = self.left.negate(compressor);
+ self.left = self.left.negate(compressor, first_in_statement);
self.right = self.right.negate(compressor);
- return best_of(basic_negation(this), self);
+ return best(this, self, first_in_statement);
}
return basic_negation(this);
});
})(function(node, func){
- node.DEFMETHOD("negate", function(compressor){
- return func.call(this, compressor);
+ node.DEFMETHOD("negate", function(compressor, first_in_statement){
+ return func.call(this, compressor, first_in_statement);
});
});
+ AST_Call.DEFMETHOD("has_pure_annotation", function(compressor) {
+ if (!compressor.option("side_effects")) return false;
+ if (this.pure !== undefined) return this.pure;
+ var pure = false;
+ var comments, last_comment;
+ if (this.start
+ && (comments = this.start.comments_before)
+ && comments.length
+ && /[@#]__PURE__/.test((last_comment = comments[comments.length - 1]).value)) {
+ compressor.warn("Dropping __PURE__ call [{file}:{line},{col}]", this.start);
+ last_comment.value = last_comment.value.replace(/[@#]__PURE__/g, ' ');
+ pure = true;
+ }
+ return this.pure = pure;
+ });
+
// determine if expression has side effects
(function(def){
def(AST_Node, return_true);
@@ -1274,10 +1385,12 @@ merge(Compressor.prototype, {
def(AST_This, return_false);
def(AST_Call, function(compressor){
- var pure = compressor.option("pure_funcs");
- if (!pure) return true;
- if (typeof pure == "function") return pure(this);
- return pure.indexOf(this.expression.print_to_string()) < 0;
+ if (!this.has_pure_annotation(compressor) && compressor.pure_funcs(this)) return true;
+ for (var i = this.args.length; --i >= 0;) {
+ if (this.args[i].has_side_effects(compressor))
+ return true;
+ }
+ return false;
});
def(AST_Block, function(compressor){
@@ -1407,13 +1520,27 @@ merge(Compressor.prototype, {
AST_Scope.DEFMETHOD("drop_unused", function(compressor){
var self = this;
if (compressor.has_directive("use asm")) return self;
+ var toplevel = compressor.option("toplevel");
if (compressor.option("unused")
- && !(self instanceof AST_Toplevel)
+ && (!(self instanceof AST_Toplevel) || toplevel)
&& !self.uses_eval
- && !self.uses_with
- ) {
+ && !self.uses_with) {
+ var assign_as_unused = !/keep_assign/.test(compressor.option("unused"));
+ var drop_funcs = /funcs/.test(toplevel);
+ var drop_vars = /vars/.test(toplevel);
+ if (!(self instanceof AST_Toplevel) || toplevel == true) {
+ drop_funcs = drop_vars = true;
+ }
var in_use = [];
- var in_use_ids = {}; // avoid expensive linear scans of in_use
+ var in_use_ids = Object.create(null); // avoid expensive linear scans of in_use
+ if (self instanceof AST_Toplevel && compressor.top_retain) {
+ self.variables.each(function(def) {
+ if (compressor.top_retain(def) && !(def.id in in_use_ids)) {
+ in_use_ids[def.id] = true;
+ in_use.push(def);
+ }
+ });
+ }
var initializations = new Dictionary();
// pass 1: find out which symbols are directly used in
// this scope (not in nested scopes).
@@ -1421,11 +1548,25 @@ merge(Compressor.prototype, {
var tw = new TreeWalker(function(node, descend){
if (node !== self) {
if (node instanceof AST_Defun) {
+ if (!drop_funcs && scope === self) {
+ var node_def = node.name.definition();
+ if (!(node_def.id in in_use_ids)) {
+ in_use_ids[node_def.id] = true;
+ in_use.push(node_def);
+ }
+ }
initializations.add(node.name.name, node);
return true; // don't go in nested scopes
}
if (node instanceof AST_Definitions && scope === self) {
node.definitions.forEach(function(def){
+ if (!drop_vars) {
+ var node_def = def.name.definition();
+ if (!(node_def.id in in_use_ids)) {
+ in_use_ids[node_def.id] = true;
+ in_use.push(node_def);
+ }
+ }
if (def.value) {
initializations.add(def.name.name, def.value);
if (def.value.has_side_effects(compressor)) {
@@ -1435,6 +1576,14 @@ merge(Compressor.prototype, {
});
return true;
}
+ if (assign_as_unused
+ && node instanceof AST_Assign
+ && node.operator == "="
+ && node.left instanceof AST_SymbolRef
+ && scope === self) {
+ node.right.walk(tw);
+ return true;
+ }
if (node instanceof AST_SymbolRef) {
var node_def = node.definition();
if (!(node_def.id in in_use_ids)) {
@@ -1477,11 +1626,17 @@ merge(Compressor.prototype, {
// pass 3: we should drop declarations not in_use
var tt = new TreeTransformer(
function before(node, descend, in_list) {
+ if (node instanceof AST_Function
+ && node.name
+ && !compressor.option("keep_fnames")
+ && !(node.name.definition().id in in_use_ids)) {
+ node.name = null;
+ }
if (node instanceof AST_Lambda && !(node instanceof AST_Accessor)) {
if (!compressor.option("keep_fargs")) {
for (var a = node.argnames, i = a.length; --i >= 0;) {
var sym = a[i];
- if (sym.unreferenced()) {
+ if (!(sym.definition().id in in_use_ids)) {
a.pop();
compressor.warn("Dropping unused function argument {name} [{file}:{line},{col}]", {
name : sym.name,
@@ -1494,7 +1649,7 @@ merge(Compressor.prototype, {
}
}
}
- if (node instanceof AST_Defun && node !== self) {
+ if (drop_funcs && node instanceof AST_Defun && node !== self) {
if (!(node.name.definition().id in in_use_ids)) {
compressor.warn("Dropping unused function {name} [{file}:{line},{col}]", {
name : node.name.name,
@@ -1506,7 +1661,7 @@ merge(Compressor.prototype, {
}
return node;
}
- if (node instanceof AST_Definitions && !(tt.parent() instanceof AST_ForIn)) {
+ if (drop_vars && node instanceof AST_Definitions && !(tt.parent() instanceof AST_ForIn)) {
var def = node.definitions.filter(function(def){
if (def.name.definition().id in in_use_ids) return true;
var w = {
@@ -1569,6 +1724,15 @@ merge(Compressor.prototype, {
}
return node;
}
+ if (drop_vars && assign_as_unused
+ && node instanceof AST_Assign
+ && node.operator == "="
+ && node.left instanceof AST_SymbolRef) {
+ var def = node.left.definition();
+ if (!(def.id in in_use_ids) && self.variables.get(def.name) === def) {
+ return node.right;
+ }
+ }
if (node instanceof AST_For) {
descend(node, this);
@@ -1724,12 +1888,151 @@ merge(Compressor.prototype, {
return self;
});
+ // drop_side_effect_free()
+ // remove side-effect-free parts which only affects return value
+ (function(def){
+ function return_this() {
+ return this;
+ }
+
+ function return_null() {
+ return null;
+ }
+
+ // Drop side-effect-free elements from an array of expressions.
+ // Returns an array of expressions with side-effects or null
+ // if all elements were dropped. Note: original array may be
+ // returned if nothing changed.
+ function trim(nodes, compressor, first_in_statement) {
+ var ret = [], changed = false;
+ for (var i = 0, ii = nodes.length; i < ii; i++) {
+ var node = nodes[i].drop_side_effect_free(compressor, first_in_statement);
+ changed |= node !== nodes[i];
+ if (node) {
+ ret.push(node);
+ first_in_statement = false;
+ }
+ }
+ return changed ? ret.length ? ret : null : nodes;
+ }
+
+ def(AST_Node, return_this);
+ def(AST_Constant, return_null);
+ def(AST_This, return_null);
+ def(AST_Call, function(compressor, first_in_statement){
+ if (!this.has_pure_annotation(compressor) && compressor.pure_funcs(this)) return this;
+ var args = trim(this.args, compressor, first_in_statement);
+ return args && AST_Seq.from_array(args);
+ });
+ def(AST_Function, return_null);
+ def(AST_Binary, function(compressor, first_in_statement){
+ var right = this.right.drop_side_effect_free(compressor);
+ if (!right) return this.left.drop_side_effect_free(compressor, first_in_statement);
+ switch (this.operator) {
+ case "&&":
+ case "||":
+ var node = this.clone();
+ node.right = right;
+ return node;
+ default:
+ var left = this.left.drop_side_effect_free(compressor, first_in_statement);
+ if (!left) return this.right.drop_side_effect_free(compressor, first_in_statement);
+ return make_node(AST_Seq, this, {
+ car: left,
+ cdr: right
+ });
+ }
+ });
+ def(AST_Assign, return_this);
+ def(AST_Conditional, function(compressor){
+ var consequent = this.consequent.drop_side_effect_free(compressor);
+ var alternative = this.alternative.drop_side_effect_free(compressor);
+ if (consequent === this.consequent && alternative === this.alternative) return this;
+ if (!consequent) return alternative ? make_node(AST_Binary, this, {
+ operator: "||",
+ left: this.condition,
+ right: alternative
+ }) : this.condition.drop_side_effect_free(compressor);
+ if (!alternative) return make_node(AST_Binary, this, {
+ operator: "&&",
+ left: this.condition,
+ right: consequent
+ });
+ var node = this.clone();
+ node.consequent = consequent;
+ node.alternative = alternative;
+ return node;
+ });
+ def(AST_Unary, function(compressor, first_in_statement){
+ switch (this.operator) {
+ case "delete":
+ case "++":
+ case "--":
+ return this;
+ case "typeof":
+ if (this.expression instanceof AST_SymbolRef) return null;
+ default:
+ if (first_in_statement && is_iife_call(this.expression)) return this;
+ return this.expression.drop_side_effect_free(compressor, first_in_statement);
+ }
+ });
+ def(AST_SymbolRef, function() {
+ return this.undeclared() ? this : null;
+ });
+ def(AST_Object, function(compressor, first_in_statement){
+ var values = trim(this.properties, compressor, first_in_statement);
+ return values && AST_Seq.from_array(values);
+ });
+ def(AST_ObjectProperty, function(compressor, first_in_statement){
+ return this.value.drop_side_effect_free(compressor, first_in_statement);
+ });
+ def(AST_Array, function(compressor, first_in_statement){
+ var values = trim(this.elements, compressor, first_in_statement);
+ return values && AST_Seq.from_array(values);
+ });
+ def(AST_Dot, function(compressor, first_in_statement){
+ if (!compressor.option("pure_getters")) return this;
+ return this.expression.drop_side_effect_free(compressor, first_in_statement);
+ });
+ def(AST_Sub, function(compressor, first_in_statement){
+ if (!compressor.option("pure_getters")) return this;
+ var expression = this.expression.drop_side_effect_free(compressor, first_in_statement);
+ if (!expression) return this.property.drop_side_effect_free(compressor, first_in_statement);
+ var property = this.property.drop_side_effect_free(compressor);
+ if (!property) return expression;
+ return make_node(AST_Seq, this, {
+ car: expression,
+ cdr: property
+ });
+ });
+ def(AST_Seq, function(compressor){
+ var cdr = this.cdr.drop_side_effect_free(compressor);
+ if (cdr === this.cdr) return this;
+ if (!cdr) return this.car;
+ return make_node(AST_Seq, this, {
+ car: this.car,
+ cdr: cdr
+ });
+ });
+ })(function(node, func){
+ node.DEFMETHOD("drop_side_effect_free", func);
+ });
+
OPT(AST_SimpleStatement, function(self, compressor){
if (compressor.option("side_effects")) {
- if (!self.body.has_side_effects(compressor)) {
+ var body = self.body;
+ if (!body.has_side_effects(compressor)) {
compressor.warn("Dropping side-effect-free statement [{file}:{line},{col}]", self.start);
return make_node(AST_EmptyStatement, self);
}
+ var node = body.drop_side_effect_free(compressor, true);
+ if (!node) {
+ compressor.warn("Dropping side-effect-free statement [{file}:{line},{col}]", self.start);
+ return make_node(AST_EmptyStatement, self);
+ }
+ if (node !== body) {
+ return make_node(AST_SimpleStatement, self, { body: node });
+ }
}
return self;
});
@@ -1749,8 +2052,14 @@ merge(Compressor.prototype, {
extract_declarations_from_unreachable_code(compressor, self.body, a);
return make_node(AST_BlockStatement, self, { body: a });
}
+ } else {
+ // self instanceof AST_Do
+ return self.body;
}
}
+ if (self instanceof AST_While) {
+ return make_node(AST_For, self, self).transform(compressor);
+ }
return self;
});
@@ -1799,16 +2108,6 @@ merge(Compressor.prototype, {
}
};
- OPT(AST_While, function(self, compressor) {
- if (!compressor.option("loops")) return self;
- self = AST_DWLoop.prototype.optimize.call(self, compressor);
- if (self instanceof AST_While) {
- if_break_in_loop(self, compressor);
- self = make_node(AST_For, self, self).transform(compressor);
- }
- return self;
- });
-
OPT(AST_For, function(self, compressor){
var cond = self.condition;
if (cond) {
@@ -1838,6 +2137,8 @@ merge(Compressor.prototype, {
});
OPT(AST_If, function(self, compressor){
+ if (is_empty(self.alternative)) self.alternative = null;
+
if (!compressor.option("conditionals")) return self;
// if condition can be statically determined, warn and drop
// one of the blocks. note, statically determined implies
@@ -1866,7 +2167,6 @@ merge(Compressor.prototype, {
}
}
}
- if (is_empty(self.alternative)) self.alternative = null;
var negated = self.condition.negate(compressor);
var self_condition_length = self.condition.print_to_string().length;
var negated_length = negated.print_to_string().length;
@@ -1890,8 +2190,8 @@ merge(Compressor.prototype, {
return make_node(AST_SimpleStatement, self, {
body: make_node(AST_Conditional, self, {
condition : self.condition,
- consequent : self.body.body,
- alternative : self.alternative.body
+ consequent : statement_to_expression(self.body),
+ alternative : statement_to_expression(self.alternative)
})
}).transform(compressor);
}
@@ -1907,14 +2207,14 @@ merge(Compressor.prototype, {
body: make_node(AST_Binary, self, {
operator : "||",
left : negated,
- right : self.body.body
+ right : statement_to_expression(self.body)
})
}).transform(compressor);
return make_node(AST_SimpleStatement, self, {
body: make_node(AST_Binary, self, {
operator : "&&",
left : self.condition,
- right : self.body.body
+ right : statement_to_expression(self.body)
})
}).transform(compressor);
}
@@ -1925,7 +2225,7 @@ merge(Compressor.prototype, {
body: make_node(AST_Binary, self, {
operator : "||",
left : self.condition,
- right : self.alternative.body
+ right : statement_to_expression(self.alternative)
})
}).transform(compressor);
}
@@ -2102,17 +2402,10 @@ merge(Compressor.prototype, {
return self;
});
- OPT(AST_Function, function(self, compressor){
- self = AST_Lambda.prototype.optimize.call(self, compressor);
- if (compressor.option("unused") && !compressor.option("keep_fnames")) {
- if (self.name && self.name.unreferenced()) {
- self.name = null;
- }
- }
- return self;
- });
-
OPT(AST_Call, function(self, compressor){
+ self.args = self.args.map(function(arg) {
+ return arg.evaluate(compressor)[0];
+ });
if (compressor.option("unsafe")) {
var exp = self.expression;
if (exp instanceof AST_SymbolRef && exp.undeclared()) {
@@ -2225,39 +2518,57 @@ merge(Compressor.prototype, {
}).transform(compressor);
}
else if (exp instanceof AST_Dot && exp.expression instanceof AST_Array && exp.property == "join") EXIT: {
- var separator = self.args.length == 0 ? "," : self.args[0].evaluate(compressor)[1];
- if (separator == null) break EXIT; // not a constant
- var elements = exp.expression.elements.reduce(function(a, el){
+ var separator;
+ if (self.args.length > 0) {
+ separator = self.args[0].evaluate(compressor);
+ if (separator.length < 2) break EXIT; // not a constant
+ separator = separator[1];
+ }
+ var elements = [];
+ var consts = [];
+ exp.expression.elements.forEach(function(el) {
el = el.evaluate(compressor);
- if (a.length == 0 || el.length == 1) {
- a.push(el);
+ if (el.length > 1) {
+ consts.push(el[1]);
} else {
- var last = a[a.length - 1];
- if (last.length == 2) {
- // it's a constant
- var val = "" + last[1] + separator + el[1];
- a[a.length - 1] = [ make_node_from_constant(compressor, val, last[0]), val ];
- } else {
- a.push(el);
+ if (consts.length > 0) {
+ elements.push(make_node(AST_String, self, {
+ value: consts.join(separator)
+ }));
+ consts.length = 0;
}
+ elements.push(el[0]);
}
- return a;
- }, []);
+ });
+ if (consts.length > 0) {
+ elements.push(make_node(AST_String, self, {
+ value: consts.join(separator)
+ }));
+ }
if (elements.length == 0) return make_node(AST_String, self, { value: "" });
- if (elements.length == 1) return elements[0][0];
+ if (elements.length == 1) {
+ if (elements[0].is_string(compressor)) {
+ return elements[0];
+ }
+ return make_node(AST_Binary, elements[0], {
+ operator : "+",
+ left : make_node(AST_String, self, { value: "" }),
+ right : elements[0]
+ });
+ }
if (separator == "") {
var first;
- if (elements[0][0] instanceof AST_String
- || elements[1][0] instanceof AST_String) {
- first = elements.shift()[0];
+ if (elements[0].is_string(compressor)
+ || elements[1].is_string(compressor)) {
+ first = elements.shift();
} else {
first = make_node(AST_String, self, { value: "" });
}
return elements.reduce(function(prev, el){
- return make_node(AST_Binary, el[0], {
+ return make_node(AST_Binary, el, {
operator : "+",
left : prev,
- right : el[0],
+ right : el
});
}, first).transform(compressor);
}
@@ -2266,9 +2577,7 @@ merge(Compressor.prototype, {
var node = self.clone();
node.expression = node.expression.clone();
node.expression.expression = node.expression.expression.clone();
- node.expression.expression.elements = elements.map(function(el){
- return el[0];
- });
+ node.expression.expression.elements = elements;
return best_of(self, node);
}
}
@@ -2292,7 +2601,12 @@ merge(Compressor.prototype, {
}
}
}
- return self.evaluate(compressor)[0];
+ if (compressor.option("negate_iife")
+ && compressor.parent() instanceof AST_SimpleStatement
+ && is_iife_call(self)) {
+ return self.negate(compressor, true);
+ }
+ return self;
});
OPT(AST_New, function(self, compressor){
@@ -2315,9 +2629,8 @@ merge(Compressor.prototype, {
OPT(AST_Seq, function(self, compressor){
if (!compressor.option("side_effects"))
return self;
- if (!self.car.has_side_effects(compressor)) {
- return maintain_this_binding(compressor.parent(), self, self.cdr);
- }
+ self.car = self.car.drop_side_effect_free(compressor, first_in_statement(compressor));
+ if (!self.car) return maintain_this_binding(compressor.parent(), self, self.cdr);
if (compressor.option("cascade")) {
if (self.car instanceof AST_Assign
&& !self.car.left.has_side_effects(compressor)) {
@@ -2379,6 +2692,10 @@ merge(Compressor.prototype, {
// !!foo ==> foo, if we're in boolean context
return e.expression;
}
+ if (e instanceof AST_Binary) {
+ var statement = first_in_statement(compressor);
+ self = (statement ? best_of_statement : best_of)(self, e.negate(compressor, statement));
+ }
break;
case "typeof":
// typeof always returns a non-empty string, thus it's
@@ -2392,9 +2709,6 @@ merge(Compressor.prototype, {
}
return make_node(AST_True, self);
}
- if (e instanceof AST_Binary && self.operator == "!") {
- self = best_of(self, e.negate(compressor));
- }
}
return self.evaluate(compressor)[0];
});
@@ -2502,14 +2816,15 @@ merge(Compressor.prototype, {
// XXX: intentionally falling down to the next case
case "==":
case "!=":
+ // "undefined" == typeof x => undefined === x
if (self.left instanceof AST_String
&& self.left.value == "undefined"
&& self.right instanceof AST_UnaryPrefix
- && self.right.operator == "typeof"
- && compressor.option("unsafe")) {
- if (!(self.right.expression instanceof AST_SymbolRef)
- || !self.right.expression.undeclared()) {
- self.right = self.right.expression;
+ && self.right.operator == "typeof") {
+ var expr = self.right.expression;
+ if (expr instanceof AST_SymbolRef ? !expr.undeclared()
+ : !(expr instanceof AST_PropAccess) || compressor.option("screw_ie8")) {
+ self.right = expr;
self.left = make_node(AST_Undefined, self.left).optimize(compressor);
if (self.operator.length == 2) self.operator += "=";
}
@@ -2570,11 +2885,12 @@ merge(Compressor.prototype, {
if (compressor.option("comparisons") && self.is_boolean()) {
if (!(compressor.parent() instanceof AST_Binary)
|| compressor.parent() instanceof AST_Assign) {
+ var statement = first_in_statement(compressor);
var negated = make_node(AST_UnaryPrefix, self, {
operator: "!",
- expression: self.negate(compressor)
+ expression: self.negate(compressor, statement)
});
- self = best_of(self, negated);
+ self = (statement ? best_of_statement : best_of)(self, negated);
}
if (compressor.option("unsafe_comps")) {
switch (self.operator) {
@@ -2670,9 +2986,16 @@ merge(Compressor.prototype, {
}
// x && (y && z) ==> x && y && z
// x || (y || z) ==> x || y || z
+ // x + ("y" + z) ==> x + "y" + z
+ // "x" + (y + "z")==> "x" + y + "z"
if (self.right instanceof AST_Binary
&& self.right.operator == self.operator
- && (self.operator == "&&" || self.operator == "||"))
+ && (self.operator == "&&"
+ || self.operator == "||"
+ || (self.operator == "+"
+ && (self.right.left.is_string(compressor)
+ || (self.left.is_string(compressor)
+ && self.right.right.is_string(compressor))))))
{
self.left = make_node(AST_Binary, self.left, {
operator : self.operator,
@@ -2686,21 +3009,34 @@ merge(Compressor.prototype, {
});
OPT(AST_SymbolRef, function(self, compressor){
- if (self.undeclared() && !isLHS(self, compressor.parent())) {
- var defines = compressor.option("global_defs");
- if (defines && HOP(defines, self.name)) {
- return make_node_from_constant(compressor, defines[self.name], self);
- }
- // testing against !self.scope.uses_with first is an optimization
- if (!self.scope.uses_with || !compressor.find_parent(AST_With)) {
- switch (self.name) {
- case "undefined":
- return make_node(AST_Undefined, self);
- case "NaN":
- return make_node(AST_NaN, self).transform(compressor);
- case "Infinity":
- return make_node(AST_Infinity, self).transform(compressor);
- }
+ var def = self.resolve_defines(compressor);
+ if (def) {
+ return def;
+ }
+ // testing against !self.scope.uses_with first is an optimization
+ if (self.undeclared() && !isLHS(self, compressor.parent())
+ && (!self.scope.uses_with || !compressor.find_parent(AST_With))) {
+ switch (self.name) {
+ case "undefined":
+ return make_node(AST_Undefined, self);
+ case "NaN":
+ return make_node(AST_NaN, self).transform(compressor);
+ case "Infinity":
+ return make_node(AST_Infinity, self).transform(compressor);
+ }
+ }
+ if (compressor.option("evaluate")
+ && compressor.option("reduce_vars")
+ && !isLHS(self, compressor.parent())) {
+ var d = self.definition();
+ if (d.constant && !d.modified && d.init && d.init.is_constant(compressor)) {
+ var original_as_string = self.print_to_string();
+ var const_node = make_node_from_constant(compressor, d.init.constant_value(compressor), self);
+ var const_node_as_string = const_node.print_to_string();
+ var per_const_overhead = d.global || !d.references.length ? 0
+ : (d.name.length + 2 + const_node_as_string.length) / d.references.length;
+ if (const_node_as_string.length <= original_as_string.length + per_const_overhead)
+ return const_node;
}
}
return self;
@@ -2719,13 +3055,11 @@ merge(Compressor.prototype, {
var scope = compressor.find_parent(AST_Scope);
var undef = scope.find_variable("undefined");
if (undef) {
- var ref = make_node(AST_SymbolRef, self, {
+ return make_node(AST_SymbolRef, self, {
name : "undefined",
scope : scope,
thedef : undef
});
- ref.reference();
- return ref;
}
}
return self;
@@ -2773,8 +3107,9 @@ merge(Compressor.prototype, {
return maintain_this_binding(compressor.parent(), self, self.alternative);
}
}
- var negated = cond[0].negate(compressor);
- if (best_of(cond[0], negated) === negated) {
+ var statement = first_in_statement(compressor);
+ var negated = cond[0].negate(compressor, statement);
+ if ((statement ? best_of_statement : best_of)(cond[0], negated) === negated) {
self = make_node(AST_Conditional, self, {
condition: negated,
consequent: self.alternative,
@@ -2967,6 +3302,10 @@ merge(Compressor.prototype, {
});
OPT(AST_Dot, function(self, compressor){
+ var def = self.resolve_defines(compressor);
+ if (def) {
+ return def;
+ }
var prop = self.property;
if (RESERVED_WORDS(prop) && !compressor.option("screw_ie8")) {
return make_node(AST_Sub, self, {
@@ -2976,6 +3315,28 @@ merge(Compressor.prototype, {
})
}).optimize(compressor);
}
+ if (compressor.option("unsafe_proto")
+ && self.expression instanceof AST_Dot
+ && self.expression.property == "prototype") {
+ var exp = self.expression.expression;
+ if (exp instanceof AST_SymbolRef && exp.undeclared()) switch (exp.name) {
+ case "Array":
+ self.expression = make_node(AST_Array, self.expression, {
+ elements: []
+ });
+ break;
+ case "Object":
+ self.expression = make_node(AST_Object, self.expression, {
+ properties: []
+ });
+ break;
+ case "String":
+ self.expression = make_node(AST_String, self.expression, {
+ value: ""
+ });
+ break;
+ }
+ }
return self.evaluate(compressor)[0];
});
@@ -2996,4 +3357,12 @@ merge(Compressor.prototype, {
return self;
});
+ OPT(AST_VarDef, function(self, compressor){
+ var defines = compressor.option("global_defs");
+ if (defines && HOP(defines, self.name.name)) {
+ compressor.warn('global_defs ' + self.name.name + ' redefined [{file}:{line},{col}]', self.start);
+ }
+ return self;
+ });
+
})();
diff --git a/lib/output.js b/lib/output.js
index 50e5aa43..4a0a1e0e 100644
--- a/lib/output.js
+++ b/lib/output.js
@@ -70,7 +70,7 @@ function OutputStream(options) {
unescape_regexps : false,
inline_script : false,
width : 80,
- max_line_len : 32000,
+ max_line_len : false,
beautify : false,
source_map : null,
bracketize : false,
@@ -198,16 +198,29 @@ function OutputStream(options) {
var might_need_space = false;
var might_need_semicolon = false;
+ var might_add_newline = 0;
var last = null;
function last_char() {
return last.charAt(last.length - 1);
};
- function maybe_newline() {
- if (options.max_line_len && current_col > options.max_line_len)
- print("\n");
- };
+ var ensure_line_len = options.max_line_len ? function() {
+ if (current_col > options.max_line_len) {
+ if (might_add_newline) {
+ var left = OUTPUT.slice(0, might_add_newline);
+ var right = OUTPUT.slice(might_add_newline);
+ OUTPUT = left + "\n" + right;
+ current_line++;
+ current_pos++;
+ current_col = right.length;
+ }
+ if (current_col > options.max_line_len) {
+ AST_Node.warn("Output exceeds {max_line_len} characters", options);
+ }
+ }
+ might_add_newline = 0;
+ } : noop;
var requireSemicolonChars = makePredicate("( [ + * / - , .");
@@ -223,6 +236,7 @@ function OutputStream(options) {
current_col++;
current_pos++;
} else {
+ ensure_line_len();
OUTPUT += "\n";
current_pos++;
current_line++;
@@ -243,6 +257,7 @@ function OutputStream(options) {
if (!options.beautify && options.preserve_line && stack[stack.length - 1]) {
var target_line = stack[stack.length - 1].start.line;
while (current_line < target_line) {
+ ensure_line_len();
OUTPUT += "\n";
current_pos++;
current_line++;
@@ -254,8 +269,9 @@ function OutputStream(options) {
if (might_need_space) {
var prev = last_char();
if ((is_identifier_char(prev)
- && (is_identifier_char(ch) || ch == "\\"))
- || (/^[\+\-\/]$/.test(ch) && ch == prev))
+ && (is_identifier_char(ch) || ch == "\\"))
+ || (ch == "/" && ch == prev)
+ || ((ch == "+" || ch == "-") && ch == last))
{
OUTPUT += " ";
current_col++;
@@ -263,16 +279,16 @@ function OutputStream(options) {
}
might_need_space = false;
}
+ OUTPUT += str;
+ current_pos += str.length;
var a = str.split(/\r?\n/), n = a.length - 1;
current_line += n;
- if (n == 0) {
- current_col += a[n].length;
- } else {
+ current_col += a[0].length;
+ if (n > 0) {
+ ensure_line_len();
current_col = a[n].length;
}
- current_pos += str.length;
last = str;
- OUTPUT += str;
};
var space = options.beautify ? function() {
@@ -298,7 +314,10 @@ function OutputStream(options) {
var newline = options.beautify ? function() {
print("\n");
- } : maybe_newline;
+ } : options.max_line_len ? function() {
+ ensure_line_len();
+ might_add_newline = OUTPUT.length;
+ } : noop;
var semicolon = options.beautify ? function() {
print(";");
@@ -375,6 +394,9 @@ function OutputStream(options) {
} : noop;
function get() {
+ if (might_add_newline) {
+ ensure_line_len();
+ }
return OUTPUT;
};
@@ -425,7 +447,6 @@ function OutputStream(options) {
pos : function() { return current_pos },
push_node : function(node) { stack.push(node) },
pop_node : function() { return stack.pop() },
- stack : function() { return stack },
parent : function(n) {
return stack[stack.length - 2 - (n || 0)];
}
@@ -939,7 +960,10 @@ function OutputStream(options) {
output.space();
output.print("else");
output.space();
- force_statement(self.alternative, output);
+ if (self.alternative instanceof AST_If)
+ self.alternative.print(output);
+ else
+ force_statement(self.alternative, output);
} else {
self._do_print_body(output);
}
@@ -1334,30 +1358,6 @@ function OutputStream(options) {
}
};
- // return true if the node at the top of the stack (that means the
- // innermost node in the current output) is lexically the first in
- // a statement.
- function first_in_statement(output) {
- var a = output.stack(), i = a.length, node = a[--i], p = a[--i];
- while (i > 0) {
- if (p instanceof AST_Statement && p.body === node)
- return true;
- if ((p instanceof AST_Seq && p.car === node ) ||
- (p instanceof AST_Call && p.expression === node && !(p instanceof AST_New) ) ||
- (p instanceof AST_Dot && p.expression === node ) ||
- (p instanceof AST_Sub && p.expression === node ) ||
- (p instanceof AST_Conditional && p.condition === node ) ||
- (p instanceof AST_Binary && p.left === node ) ||
- (p instanceof AST_UnaryPostfix && p.expression === node ))
- {
- node = p;
- p = a[--i];
- } else {
- return false;
- }
- }
- };
-
// self should be AST_New. decide if we want to show parens or not.
function need_constructor_parens(self, output) {
// Always print parentheses with arguments
diff --git a/lib/parse.js b/lib/parse.js
index ec82d47d..37f06df7 100644
--- a/lib/parse.js
+++ b/lib/parse.js
@@ -1308,6 +1308,10 @@ function parse($TEXT, options) {
});
});
+ var create_accessor = embed_tokens(function() {
+ return function_(AST_Accessor);
+ });
+
var object_ = embed_tokens(function() {
expect("{");
var first = true, a = [];
@@ -1324,7 +1328,7 @@ function parse($TEXT, options) {
a.push(new AST_ObjectGetter({
start : start,
key : as_atom_node(),
- value : function_(AST_Accessor),
+ value : create_accessor(),
end : prev()
}));
continue;
@@ -1333,7 +1337,7 @@ function parse($TEXT, options) {
a.push(new AST_ObjectSetter({
start : start,
key : as_atom_node(),
- value : function_(AST_Accessor),
+ value : create_accessor(),
end : prev()
}));
continue;
diff --git a/lib/scope.js b/lib/scope.js
index 55d1eff1..29e4103a 100644
--- a/lib/scope.js
+++ b/lib/scope.js
@@ -97,26 +97,24 @@ 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 tw = new TreeWalker(function(node, descend){
if (options.screw_ie8 && node instanceof AST_Catch) {
var save_scope = scope;
scope = new AST_Scope(node);
- scope.init_scope_vars(nesting);
+ scope.init_scope_vars();
scope.parent_scope = save_scope;
descend();
scope = save_scope;
return true;
}
if (node instanceof AST_Scope) {
- node.init_scope_vars(nesting);
+ node.init_scope_vars();
var save_scope = node.parent_scope = scope;
var save_defun = defun;
var save_labels = labels;
defun = scope = node;
labels = new Dictionary();
- ++nesting; descend(); --nesting;
+ descend();
scope = save_scope;
defun = save_defun;
labels = save_labels;
@@ -155,13 +153,10 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){
// later.
(node.scope = defun.parent_scope).def_function(node);
}
- else if (node instanceof AST_Var) {
- last_var_had_const_pragma = node.has_const_pragma();
- }
else if (node instanceof AST_SymbolVar
|| node instanceof AST_SymbolConst) {
var def = defun.def_variable(node);
- def.constant = node instanceof AST_SymbolConst || last_var_had_const_pragma;
+ def.constant = node instanceof AST_SymbolConst;
def.init = tw.parent().value;
}
else if (node instanceof AST_SymbolCatch) {
@@ -184,17 +179,6 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){
var func = null;
var globals = self.globals = new Dictionary();
var tw = new TreeWalker(function(node, descend){
- function isModified(node, level) {
- var parent = tw.parent(level);
- if (parent instanceof AST_Unary && (parent.operator === "++" || parent.operator === "--")
- || parent instanceof AST_Assign && parent.left === node
- || parent instanceof AST_Call && parent.expression === node) {
- return true;
- } else if (parent instanceof AST_PropAccess && parent.expression === node) {
- return isModified(parent, level + 1);
- }
- }
-
if (node instanceof AST_Lambda) {
var prev_func = func;
func = node;
@@ -218,21 +202,9 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){
node.scope.uses_arguments = true;
}
if (!sym) {
- var g;
- if (globals.has(name)) {
- g = globals.get(name);
- } else {
- g = new SymbolDef(self, globals.size(), node);
- g.undeclared = true;
- g.global = true;
- globals.set(name, g);
- }
- sym = g;
+ sym = self.def_global(node);
}
node.thedef = sym;
- if (isModified(node, 0)) {
- sym.modified = true;
- }
node.reference(options);
return true;
}
@@ -244,7 +216,20 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){
}
});
-AST_Scope.DEFMETHOD("init_scope_vars", function(nesting){
+AST_Toplevel.DEFMETHOD("def_global", function(node){
+ var globals = this.globals, name = node.name;
+ if (globals.has(name)) {
+ return globals.get(name);
+ } else {
+ var g = new SymbolDef(this, globals.size(), node);
+ g.undeclared = true;
+ g.global = true;
+ globals.set(name, g);
+ return g;
+ }
+});
+
+AST_Scope.DEFMETHOD("init_scope_vars", function(){
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
@@ -252,7 +237,6 @@ AST_Scope.DEFMETHOD("init_scope_vars", function(nesting){
this.parent_scope = null; // the parent scope
this.enclosed = []; // a list of variables from this or outer scope(s) that are referenced from this or inner scopes
this.cname = -1; // the current index for mangling functions/variables
- this.nesting = nesting; // the nesting level of this scope (0 means toplevel)
});
AST_Lambda.DEFMETHOD("init_scope_vars", function(){
@@ -270,15 +254,14 @@ AST_SymbolRef.DEFMETHOD("reference", function(options) {
var s = this.scope;
while (s) {
push_uniq(s.enclosed, def);
- if (s === def.scope) break;
if (options.keep_fnames) {
- s.variables.each(function(d) {
+ s.functions.each(function(d) {
push_uniq(def.scope.enclosed, d);
});
}
+ if (s === def.scope) break;
s = s.parent_scope;
}
- this.frame = this.scope.nesting - def.scope.nesting;
});
AST_Scope.DEFMETHOD("find_variable", function(name){
@@ -382,12 +365,6 @@ 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 : [],
diff --git a/lib/utils.js b/lib/utils.js
index d0a21ac9..a0571d65 100644
--- a/lib/utils.js
+++ b/lib/utils.js
@@ -320,3 +320,26 @@ Dictionary.fromObject = function(obj) {
function HOP(obj, prop) {
return Object.prototype.hasOwnProperty.call(obj, prop);
}
+
+// return true if the node at the top of the stack (that means the
+// innermost node in the current output) is lexically the first in
+// a statement.
+function first_in_statement(stack) {
+ var node = stack.parent(-1);
+ for (var i = 0, p; p = stack.parent(i); i++) {
+ if (p instanceof AST_Statement && p.body === node)
+ return true;
+ if ((p instanceof AST_Seq && p.car === node ) ||
+ (p instanceof AST_Call && p.expression === node && !(p instanceof AST_New) ) ||
+ (p instanceof AST_Dot && p.expression === node ) ||
+ (p instanceof AST_Sub && p.expression === node ) ||
+ (p instanceof AST_Conditional && p.condition === node ) ||
+ (p instanceof AST_Binary && p.left === node ) ||
+ (p instanceof AST_UnaryPostfix && p.expression === node ))
+ {
+ node = p;
+ } else {
+ return false;
+ }
+ }
+}