406 lines
14 KiB
JavaScript
406 lines
14 KiB
JavaScript
/***********************************************************************
|
|
|
|
A JavaScript tokenizer / parser / beautifier / compressor.
|
|
https://github.com/mishoo/UglifyJS2
|
|
|
|
-------------------------------- (C) ---------------------------------
|
|
|
|
Author: Mihai Bazon
|
|
<mihai.bazon@gmail.com>
|
|
http://mihai.bazon.net/blog
|
|
|
|
Distributed under the BSD license:
|
|
|
|
Copyright 2010 (c) Mihai Bazon <mihai.bazon@gmail.com>
|
|
|
|
Redistribution and use in source and binary forms, with or without
|
|
modification, are permitted provided that the following conditions
|
|
are met:
|
|
|
|
* Redistributions of source code must retain the above
|
|
copyright notice, this list of conditions and the following
|
|
disclaimer.
|
|
|
|
* Redistributions in binary form must reproduce the above
|
|
copyright notice, this list of conditions and the following
|
|
disclaimer in the documentation and/or other materials
|
|
provided with the distribution.
|
|
|
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER “AS IS” AND ANY
|
|
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
|
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE
|
|
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
|
|
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
|
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
|
|
TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
|
|
THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
SUCH DAMAGE.
|
|
|
|
***********************************************************************/
|
|
|
|
// The layout of the compressor follows the code generator (see
|
|
// output.js). Basically each node will have a "squeeze" method
|
|
// that will apply all known compression rules for that node, and
|
|
// return a new node (or the original node if there was no
|
|
// compression). We can't quite use the TreeWalker for this
|
|
// because it's too simplistic.
|
|
|
|
// The Compressor object is for storing the options and for
|
|
// maintaining various internal state that might be useful for
|
|
// squeezing nodes.
|
|
|
|
function Compressor(options, false_by_default) {
|
|
options = defaults(options, {
|
|
sequences : !false_by_default,
|
|
properties : !false_by_default,
|
|
dead_code : !false_by_default,
|
|
keep_comps : !false_by_default,
|
|
drop_debugger : !false_by_default,
|
|
unsafe : !false_by_default,
|
|
|
|
warnings : true
|
|
});
|
|
var stack = [];
|
|
return {
|
|
option : function(key) { return options[key] },
|
|
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)];
|
|
},
|
|
warn : function() {
|
|
if (options.warnings)
|
|
AST_Node.warn.apply(AST_Node, arguments);
|
|
}
|
|
};
|
|
};
|
|
|
|
(function(){
|
|
|
|
AST_Node.DEFMETHOD("squeeze", function(){
|
|
return this;
|
|
});
|
|
|
|
function make_node(ctor, orig, props) {
|
|
if (!props.start) props.start = orig.start;
|
|
if (!props.end) props.end = orig.end;
|
|
return new ctor(props);
|
|
};
|
|
|
|
function SQUEEZE(nodetype, squeeze) {
|
|
nodetype.DEFMETHOD("squeeze", function(compressor){
|
|
compressor.push_node(this);
|
|
var new_node = squeeze(this, compressor);
|
|
compressor.pop_node();
|
|
return new_node || this;
|
|
});
|
|
};
|
|
|
|
function do_list(array, compressor) {
|
|
return MAP(array, function(node){
|
|
return node.squeeze(compressor);
|
|
});
|
|
};
|
|
|
|
SQUEEZE(AST_Debugger, function(self, compressor){
|
|
if (compressor.option("drop_debugger"))
|
|
return new AST_EmptyStatement(self);
|
|
});
|
|
|
|
SQUEEZE(AST_LabeledStatement, function(self, compressor){
|
|
self = self.clone();
|
|
self.statement = self.statement.squeeze(compressor);
|
|
return self.label.references.length == 0 ? self.statement : self;
|
|
});
|
|
|
|
SQUEEZE(AST_Statement, function(self, compressor){
|
|
self = self.clone();
|
|
self.body = self.body.squeeze(compressor);
|
|
return self;
|
|
});
|
|
|
|
function tighten_body(statements, compressor) {
|
|
statements = do_list(statements, compressor);
|
|
statements = eliminate_spurious_blocks(statements);
|
|
if (compressor.option("dead_code")) {
|
|
statements = eliminate_dead_code(statements, compressor);
|
|
}
|
|
if (compressor.option("sequences")) {
|
|
statements = sequencesize(statements);
|
|
}
|
|
return statements;
|
|
};
|
|
|
|
function eliminate_spurious_blocks(statements) {
|
|
return statements.reduce(function(a, stat){
|
|
if (stat.TYPE == "BlockStatement") {
|
|
// XXX: no instanceof here because we would catch
|
|
// AST_Lambda-s and other blocks too. perhaps we
|
|
// should refine the hierarchy.
|
|
a.push.apply(a, stat.body);
|
|
} else {
|
|
a.push(stat);
|
|
}
|
|
return a;
|
|
}, []);
|
|
}
|
|
|
|
function eliminate_dead_code(statements, compressor) {
|
|
var has_quit = false;
|
|
return statements.reduce(function(a, stat){
|
|
if (has_quit) {
|
|
if (stat instanceof AST_Defun) {
|
|
a.push(stat);
|
|
}
|
|
else if (compressor.option("warnings")) {
|
|
stat.walk(new TreeWalker(function(node){
|
|
if (node instanceof AST_Definitions
|
|
|| node instanceof AST_Defun) {
|
|
compressor.warn("Declarations in unreachable code! [{line},{col}]", node.start);
|
|
if (node instanceof AST_Definitions) {
|
|
node = node.clone();
|
|
node.remove_initializers();
|
|
a.push(node);
|
|
}
|
|
else if (node instanceof AST_Defun) {
|
|
a.push(node);
|
|
}
|
|
return true;
|
|
}
|
|
if (node instanceof AST_Scope)
|
|
return true;
|
|
}))
|
|
};
|
|
} else {
|
|
a.push(stat);
|
|
if (stat instanceof AST_Jump) {
|
|
has_quit = true;
|
|
}
|
|
}
|
|
return a;
|
|
}, []);
|
|
}
|
|
|
|
function sequencesize(statements) {
|
|
var prev = null, last = statements.length - 1;
|
|
if (last) statements = statements.reduce(function(a, cur, i){
|
|
if (prev instanceof AST_SimpleStatement
|
|
&& cur instanceof AST_SimpleStatement) {
|
|
var seq = make_node(AST_Seq, prev, {
|
|
first: prev.body,
|
|
second: cur.body
|
|
});
|
|
prev.body = seq;
|
|
}
|
|
else if (i == last && cur instanceof AST_Exit
|
|
&& cur.value && a.length == 1) {
|
|
// it only makes sense to do this transformation
|
|
// if the AST gets to a single statement.
|
|
var seq = make_node(AST_Seq, prev, {
|
|
first: prev.body,
|
|
second: cur.value
|
|
});
|
|
cur.value = seq;
|
|
return [ cur ];
|
|
}
|
|
else {
|
|
a.push(cur);
|
|
prev = cur;
|
|
}
|
|
return a;
|
|
}, []);
|
|
return statements;
|
|
}
|
|
|
|
SQUEEZE(AST_BlockStatement, function(self, compressor){
|
|
self = self.clone();
|
|
self.body = tighten_body(self.body, compressor);
|
|
if (self.body.length == 1 && !self.required)
|
|
return self.body[0];
|
|
return self;
|
|
});
|
|
|
|
SQUEEZE(AST_EmptyStatement, function(self, compressor){
|
|
return self;
|
|
});
|
|
|
|
SQUEEZE(AST_DWLoop, function(self, compressor){
|
|
self = self.clone();
|
|
self.condition = self.condition.squeeze(compressor);
|
|
self.body = self.body.squeeze(compressor);
|
|
return self;
|
|
});
|
|
|
|
SQUEEZE(AST_For, function(self, compressor){
|
|
self = self.clone();
|
|
if (self.init) self.init = self.init.squeeze(compressor);
|
|
if (self.condition) self.condition = self.condition.squeeze(compressor);
|
|
if (self.step) self.step = self.step.squeeze(compressor);
|
|
self.body = self.body.squeeze(compressor);
|
|
return self;
|
|
});
|
|
|
|
SQUEEZE(AST_ForIn, function(self, compressor){
|
|
self = self.clone();
|
|
self.init = self.init.squeeze(compressor);
|
|
self.object = self.object.squeeze(compressor);
|
|
self.body = self.body.squeeze(compressor);
|
|
return self;
|
|
});
|
|
|
|
SQUEEZE(AST_With, function(self, compressor){
|
|
self = self.clone();
|
|
self.expression = self.expression.squeeze(compressor);
|
|
self.body = self.body.squeeze(compressor);
|
|
});
|
|
|
|
SQUEEZE(AST_Exit, function(self, compressor){
|
|
self = self.clone();
|
|
if (self.value) self.value = self.value.squeeze(compressor);
|
|
return self;
|
|
});
|
|
|
|
SQUEEZE(AST_LoopControl, function(self, compressor){
|
|
self = self.clone();
|
|
if (self.label) self.label = self.label.squeeze(compressor);
|
|
return self;
|
|
});
|
|
|
|
SQUEEZE(AST_If, function(self, compressor){
|
|
self = self.clone();
|
|
self.condition = self.condition.squeeze(compressor);
|
|
self.consequent = self.consequent.squeeze(compressor);
|
|
if (self.alternative)
|
|
self.alternative = self.alternative.squeeze(compressor);
|
|
return self;
|
|
});
|
|
|
|
SQUEEZE(AST_Switch, function(self, compressor){
|
|
self = self.clone();
|
|
self.expression = self.expression.squeeze(compressor);
|
|
self.body = self.body.squeeze(compressor);
|
|
return self;
|
|
});
|
|
|
|
SQUEEZE(AST_Case, function(self, compressor){
|
|
self = self.clone();
|
|
self.expression = self.expression.squeeze(compressor);
|
|
self.body = tighten_body(self.body, compressor);
|
|
return self;
|
|
});
|
|
|
|
SQUEEZE(AST_Try, function(self, compressor){
|
|
self = self.clone();
|
|
self.btry = self.btry.squeeze(compressor);
|
|
if (self.bcatch) self.bcatch = self.bcatch.squeeze(compressor);
|
|
if (self.bfinally) self.bfinally = self.bfinally.squeeze(compressor);
|
|
return self;
|
|
});
|
|
|
|
AST_Definitions.DEFMETHOD("remove_initializers", function(){
|
|
this.definitions = this.definitions.map(function(def){
|
|
var def = def.clone();
|
|
def.value = null;
|
|
return def;
|
|
});
|
|
});
|
|
|
|
SQUEEZE(AST_Definitions, function(self, compressor){
|
|
self = self.clone();
|
|
self.definitions = do_list(self.definitions, compressor);
|
|
return self;
|
|
});
|
|
|
|
SQUEEZE(AST_VarDef, function(self, compressor){
|
|
self = self.clone();
|
|
if (self.value) self.value = self.value.squeeze(compressor);
|
|
return self;
|
|
});
|
|
|
|
SQUEEZE(AST_Lambda, function(self, compressor){
|
|
self = self.clone();
|
|
if (self.name) self.name = self.name.squeeze(compressor);
|
|
self.argnames = do_list(self.argnames, compressor);
|
|
self.body = self.body.squeeze(compressor);
|
|
return self;
|
|
});
|
|
|
|
SQUEEZE(AST_Call, function(self, compressor){
|
|
self = self.clone();
|
|
self.expression = self.expression.squeeze(compressor);
|
|
self.args = do_list(self.args, compressor);
|
|
return self;
|
|
});
|
|
|
|
SQUEEZE(AST_Seq, function(self, compressor){
|
|
self = self.clone();
|
|
self.first = self.first.squeeze(compressor);
|
|
self.second = self.second.squeeze(compressor);
|
|
return self;
|
|
});
|
|
|
|
SQUEEZE(AST_Dot, function(self, compressor){
|
|
self = self.clone();
|
|
self.expression = self.expression.squeeze(compressor);
|
|
return self;
|
|
});
|
|
|
|
SQUEEZE(AST_Sub, function(self, compressor){
|
|
self = self.clone();
|
|
self.expression = self.expression.squeeze(compressor);
|
|
var prop = self.property = self.property.squeeze(compressor);
|
|
if (prop instanceof AST_String && compressor.option("properties")) {
|
|
prop = prop.getValue();
|
|
if (is_identifier(prop)) {
|
|
self = new AST_Dot(self);
|
|
self.property = prop;
|
|
}
|
|
}
|
|
return self;
|
|
});
|
|
|
|
SQUEEZE(AST_Unary, function(self, compressor){
|
|
self = self.clone();
|
|
self.expression = self.expression.squeeze(compressor);
|
|
return self;
|
|
});
|
|
|
|
SQUEEZE(AST_Binary, function(self, compressor){
|
|
self = self.clone();
|
|
self.left = self.left.squeeze(compressor);
|
|
self.right = self.right.squeeze(compressor);
|
|
return self;
|
|
});
|
|
|
|
SQUEEZE(AST_Conditional, function(self, compressor){
|
|
self = self.clone();
|
|
self.condition = self.condition.squeeze(compressor);
|
|
self.consequent = self.consequent.squeeze(compressor);
|
|
self.alternative = self.alternative.squeeze(compressor);
|
|
return self;
|
|
});
|
|
|
|
SQUEEZE(AST_Array, function(self, compressor){
|
|
self = self.clone();
|
|
self.elements = do_list(self.elements, compressor);
|
|
return self;
|
|
});
|
|
|
|
SQUEEZE(AST_Object, function(self, compressor){
|
|
self = self.clone();
|
|
self.properties = do_list(self.properties, compressor);
|
|
return self;
|
|
});
|
|
|
|
SQUEEZE(AST_ObjectProperty, function(self, compressor){
|
|
self = self.clone();
|
|
self.value = self.value.squeeze(compressor);
|
|
return self;
|
|
});
|
|
|
|
})();
|