Merge 2cce61c564 into b5623b19d4
This commit is contained in:
commit
b4eae60dc8
179
lib/ast.js
179
lib/ast.js
|
|
@ -143,7 +143,7 @@ var AST_SimpleStatement = DEFNODE("SimpleStatement", "body", {
|
|||
}, AST_Statement);
|
||||
|
||||
function walk_body(node, visitor) {
|
||||
if (node.body instanceof AST_Statement) {
|
||||
if (node.body instanceof AST_Node) {
|
||||
node.body._walk(visitor);
|
||||
}
|
||||
else node.body.forEach(function(stat){
|
||||
|
|
@ -263,6 +263,10 @@ var AST_ForIn = DEFNODE("ForIn", "init name object", {
|
|||
}
|
||||
}, AST_IterationStatement);
|
||||
|
||||
var AST_ForOf = DEFNODE("ForOf", null, {
|
||||
$documentation: "A `for ... of` statement",
|
||||
}, AST_ForIn);
|
||||
|
||||
var AST_With = DEFNODE("With", "expression", {
|
||||
$documentation: "A `with` statement",
|
||||
$propdoc: {
|
||||
|
|
@ -359,13 +363,93 @@ var AST_Toplevel = DEFNODE("Toplevel", "globals", {
|
|||
}
|
||||
}, AST_Scope);
|
||||
|
||||
var AST_Expansion = DEFNODE("Expansion", "symbol", {
|
||||
$documentation: "An expandible argument, such as ...rest, a splat, such as [1,2,...all], or an expansion in a variable declaration, such as var [first, ...rest] = list",
|
||||
$propdoc: {
|
||||
symbol: "AST_Symbol the thing to be expanded"
|
||||
},
|
||||
_walk: function(visitor) {
|
||||
var self = this;
|
||||
return visitor._visit(this, function(){
|
||||
self.symbol.walk(visitor);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
var AST_ArrowParametersOrSeq = DEFNODE("ArrowParametersOrSeq", "expressions", {
|
||||
$documentation: "A set of arrow function parameters or a sequence expression. This is used because when the parser sees a \"(\" it could be the start of a seq, or the start of a parameter list of an arrow function.",
|
||||
$propdoc: {
|
||||
expressions: "[AST_Expression|AST_Destructuring|AST_Expansion*] array of expressions or argument names or destructurings."
|
||||
},
|
||||
as_params: function (croak) {
|
||||
// We don't want anything which doesn't belong in a destructuring
|
||||
var root = this;
|
||||
return this.expressions.map(function to_fun_args(ex) {
|
||||
if (ex instanceof AST_Object) {
|
||||
if (ex.properties.length == 0)
|
||||
croak("Invalid destructuring function parameter", ex.start.line, ex.start.col);
|
||||
return new AST_Destructuring({
|
||||
start: ex.start,
|
||||
end: ex.end,
|
||||
is_array: false,
|
||||
names: ex.properties.map(to_fun_args)
|
||||
});
|
||||
} else if (ex instanceof AST_ObjectSymbol) {
|
||||
return new AST_SymbolFunarg({
|
||||
name: ex.symbol.name,
|
||||
start: ex.start,
|
||||
end: ex.end
|
||||
});
|
||||
} else if (ex instanceof AST_Destructuring) {
|
||||
if (ex.names.length == 0)
|
||||
croak("Invalid destructuring function parameter", ex.start.line, ex.start.col);
|
||||
ex.names = ex.names.map(to_fun_args);
|
||||
return ex;
|
||||
} else if (ex instanceof AST_SymbolRef) {
|
||||
return new AST_SymbolFunarg({
|
||||
name: ex.name,
|
||||
start: ex.start,
|
||||
end: ex.end
|
||||
});
|
||||
} else if (ex instanceof AST_Expansion) {
|
||||
return ex;
|
||||
} else if (ex instanceof AST_Array) {
|
||||
if (ex.elements.length === 0)
|
||||
croak("Invalid destructuring function parameter", ex.start.line, ex.start.col);
|
||||
return new AST_Destructuring({
|
||||
start: ex.start,
|
||||
end: ex.end,
|
||||
is_array: true,
|
||||
names: ex.elements.map(to_fun_args)
|
||||
});
|
||||
} else {
|
||||
croak("Invalid function parameter", ex.start.line, ex.start.col);
|
||||
}
|
||||
});
|
||||
},
|
||||
as_expr: function (croak) {
|
||||
return AST_Seq.from_array(this.expressions);
|
||||
}
|
||||
});
|
||||
|
||||
var AST_Lambda = DEFNODE("Lambda", "name argnames uses_arguments", {
|
||||
$documentation: "Base class for functions",
|
||||
$propdoc: {
|
||||
name: "[AST_SymbolDeclaration?] the name of this function",
|
||||
argnames: "[AST_SymbolFunarg*] array of function arguments",
|
||||
argnames: "[AST_SymbolFunarg|AST_Destructuring|AST_Expansion*] array of function arguments, destructurings, or expanding arguments",
|
||||
uses_arguments: "[boolean/S] tells whether this function accesses the arguments array"
|
||||
},
|
||||
args_as_names: function () {
|
||||
var out = [];
|
||||
for (var i = 0; i < this.argnames.length; i++) {
|
||||
if (this.argnames[i] instanceof AST_Destructuring) {
|
||||
out = out.concat(this.argnames[i].all_symbols());
|
||||
} else {
|
||||
out.push(this.argnames[i]);
|
||||
}
|
||||
}
|
||||
return out;
|
||||
},
|
||||
_walk: function(visitor) {
|
||||
return visitor._visit(this, function(){
|
||||
if (this.name) this.name._walk(visitor);
|
||||
|
|
@ -385,10 +469,66 @@ var AST_Function = DEFNODE("Function", null, {
|
|||
$documentation: "A function expression"
|
||||
}, AST_Lambda);
|
||||
|
||||
var AST_Arrow = DEFNODE("Arrow", null, {
|
||||
$documentation: "An ES6 Arrow function ((a) => b)"
|
||||
}, AST_Lambda);
|
||||
|
||||
var AST_Defun = DEFNODE("Defun", null, {
|
||||
$documentation: "A function definition"
|
||||
}, AST_Lambda);
|
||||
|
||||
/* -----[ DESTRUCTURING ]----- */
|
||||
var AST_Destructuring = DEFNODE("Destructuring", "names is_array", {
|
||||
$documentation: "A destructuring of several names. Used in destructuring assignment and with destructuring function argument names",
|
||||
_walk: function(visitor) {
|
||||
return visitor._visit(this, function(){
|
||||
this.names.forEach(function(name){
|
||||
name._walk(visitor);
|
||||
});
|
||||
});
|
||||
},
|
||||
all_symbols: function() {
|
||||
var out = [];
|
||||
this.walk(new TreeWalker(function (node) {
|
||||
if (node instanceof AST_Symbol) {
|
||||
out.push(node);
|
||||
}
|
||||
if (node instanceof AST_Expansion) {
|
||||
out.push(node.symbol);
|
||||
}
|
||||
}));
|
||||
return out;
|
||||
}
|
||||
});
|
||||
|
||||
var AST_PrefixedTemplateString = DEFNODE("PrefixedTemplateString", "template_string prefix", {
|
||||
$documentation: "A templatestring with a prefix, such as String.raw`foobarbaz`",
|
||||
$propdoc: {
|
||||
template_string: "[AST_TemplateString] The template string",
|
||||
prefix: "[AST_SymbolRef|AST_PropAccess] The prefix, which can be a symbol such as `foo` or a dotted expression such as `String.raw`."
|
||||
},
|
||||
_walk: function(visitor) {
|
||||
this.prefix._walk(visitor);
|
||||
this.template_string._walk(visitor);
|
||||
}
|
||||
})
|
||||
|
||||
var AST_TemplateString = DEFNODE("TemplateString", "segments", {
|
||||
$documentation: "A template string literal",
|
||||
$propdoc: {
|
||||
segments: "[string|AST_Expression]* One or more segments. They can be the parts that are evaluated, or the raw string parts."
|
||||
},
|
||||
_walk: function(visitor) {
|
||||
return visitor._visit(this, function(){
|
||||
this.segments.forEach(function(seg, i){
|
||||
if (i % 2 !== 0) {
|
||||
seg._walk(visitor);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/* -----[ JUMPS ]----- */
|
||||
|
||||
var AST_Jump = DEFNODE("Jump", null, {
|
||||
|
|
@ -542,6 +682,10 @@ var AST_Var = DEFNODE("Var", null, {
|
|||
$documentation: "A `var` statement"
|
||||
}, AST_Definitions);
|
||||
|
||||
var AST_Let = DEFNODE("Let", null, {
|
||||
$documentation: "A `let` statement"
|
||||
}, AST_Definitions);
|
||||
|
||||
var AST_Const = DEFNODE("Const", null, {
|
||||
$documentation: "A `const` statement"
|
||||
}, AST_Definitions);
|
||||
|
|
@ -549,9 +693,12 @@ var AST_Const = DEFNODE("Const", null, {
|
|||
var AST_VarDef = DEFNODE("VarDef", "name value", {
|
||||
$documentation: "A variable declaration; only appears in a AST_Definitions node",
|
||||
$propdoc: {
|
||||
name: "[AST_SymbolVar|AST_SymbolConst] name of the variable",
|
||||
name: "[AST_SymbolVar|AST_SymbolConst|AST_Destructuring] name of the variable",
|
||||
value: "[AST_Node?] initializer, or null of there's no initializer"
|
||||
},
|
||||
is_destructuring: function() {
|
||||
return this.name instanceof AST_Destructuring;
|
||||
},
|
||||
_walk: function(visitor) {
|
||||
return visitor._visit(this, function(){
|
||||
this.name._walk(visitor);
|
||||
|
|
@ -774,6 +921,28 @@ var AST_ObjectKeyVal = DEFNODE("ObjectKeyVal", "quote", {
|
|||
}
|
||||
}, AST_ObjectProperty);
|
||||
|
||||
var AST_ObjectComputedKeyVal = DEFNODE("ObjectComputedKeyVal", null, {
|
||||
$documentation: "An object property whose key is computed. Like `[Symbol.iterator]: function...` or `[routes.homepage]: renderHomepage`",
|
||||
_walk: function(visitor) {
|
||||
return visitor._visit(this, function(){
|
||||
this.key._walk(visitor);
|
||||
this.value._walk(visitor);
|
||||
});
|
||||
}
|
||||
}, AST_ObjectProperty);
|
||||
|
||||
var AST_ObjectSymbol = DEFNODE("ObjectSymbol", "symbol", {
|
||||
$propdoc: {
|
||||
symbol: "[AST_SymbolRef] what symbol it is"
|
||||
},
|
||||
$documentation: "A symbol in an object",
|
||||
_walk: function (visitor) {
|
||||
return visitor._visit(this, function(){
|
||||
this.symbol._walk(visitor);
|
||||
});
|
||||
}
|
||||
}, AST_ObjectProperty);
|
||||
|
||||
var AST_ObjectSetter = DEFNODE("ObjectSetter", null, {
|
||||
$documentation: "An object setter property",
|
||||
}, AST_ObjectProperty);
|
||||
|
|
@ -849,6 +1018,10 @@ var AST_This = DEFNODE("This", null, {
|
|||
$documentation: "The `this` symbol",
|
||||
}, AST_Symbol);
|
||||
|
||||
var AST_Super = DEFNODE("Super", null, {
|
||||
$documentation: "The `super` symbol",
|
||||
}, AST_Symbol);
|
||||
|
||||
var AST_Constant = DEFNODE("Constant", null, {
|
||||
$documentation: "Base class for all constants",
|
||||
getValue: function() {
|
||||
|
|
|
|||
|
|
@ -231,8 +231,16 @@ merge(Compressor.prototype, {
|
|||
return /@ngInject/.test(comment.value);
|
||||
}
|
||||
function make_arguments_names_list(func) {
|
||||
var foundDestructuring = false;
|
||||
return func.argnames.map(function(sym){
|
||||
if (sym instanceof AST_Destructuring) {
|
||||
compressor.warn("Function with destructuring arguments marked with @ngInject [{file}:{line},{col}]", token);
|
||||
foundDestructuring = true;
|
||||
}
|
||||
if (foundDestructuring) { return null; }
|
||||
return make_node(AST_String, sym, { value: sym.name });
|
||||
}).filter(function (name) {
|
||||
return name !== null;
|
||||
});
|
||||
}
|
||||
function make_array(orig, elements) {
|
||||
|
|
@ -731,6 +739,9 @@ merge(Compressor.prototype, {
|
|||
// places too. :-( Wish JS had multiple inheritance.
|
||||
throw def;
|
||||
});
|
||||
def(AST_Arrow, function() {
|
||||
throw def;
|
||||
});
|
||||
function ev(node, compressor) {
|
||||
if (!compressor) throw new Error("Compressor must be passed");
|
||||
|
||||
|
|
@ -749,7 +760,8 @@ merge(Compressor.prototype, {
|
|||
case "typeof":
|
||||
// Function would be evaluated to an array and so typeof would
|
||||
// incorrectly return 'object'. Hence making is a special case.
|
||||
if (e instanceof AST_Function) return typeof function(){};
|
||||
if (e instanceof AST_Function ||
|
||||
e instanceof AST_Arrow) return typeof function(){};
|
||||
|
||||
e = ev(e, compressor);
|
||||
|
||||
|
|
@ -940,6 +952,9 @@ merge(Compressor.prototype, {
|
|||
return false;
|
||||
});
|
||||
def(AST_ObjectProperty, function(compressor){
|
||||
if (this instanceof AST_ObjectComputedKeyVal &&
|
||||
this.key.has_side_effects(compressor))
|
||||
return true;
|
||||
return this.value.has_side_effects(compressor);
|
||||
});
|
||||
def(AST_Array, function(compressor){
|
||||
|
|
@ -1012,6 +1027,7 @@ merge(Compressor.prototype, {
|
|||
});
|
||||
|
||||
OPT(AST_Block, function(self, compressor){
|
||||
if (self.body instanceof AST_Node) { return self; }
|
||||
self.body = tighten_body(self.body, compressor);
|
||||
return self;
|
||||
});
|
||||
|
|
@ -1045,6 +1061,7 @@ merge(Compressor.prototype, {
|
|||
}
|
||||
if (node instanceof AST_Definitions && scope === self) {
|
||||
node.definitions.forEach(function(def){
|
||||
if (def.is_destructuring()) return; /* Destructurings are type assertions! */
|
||||
if (def.value) {
|
||||
initializations.add(def.name.name, def.value);
|
||||
if (def.value.has_side_effects(compressor)) {
|
||||
|
|
@ -1091,17 +1108,29 @@ merge(Compressor.prototype, {
|
|||
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()) {
|
||||
a.pop();
|
||||
compressor.warn("Dropping unused function argument {name} [{file}:{line},{col}]", {
|
||||
name : sym.name,
|
||||
file : sym.start.file,
|
||||
line : sym.start.line,
|
||||
col : sym.start.col
|
||||
});
|
||||
if (a[i] instanceof AST_Destructuring) {
|
||||
// Do not drop destructuring arguments.
|
||||
// They constitute a type assertion, so dropping
|
||||
// them would stop that TypeError which would happen
|
||||
// if someone called it with an incorrectly formatted
|
||||
// parameter.
|
||||
break;
|
||||
} else {
|
||||
var sym = a[i];
|
||||
if (sym instanceof AST_Expansion) {
|
||||
sym = sym.symbol;
|
||||
}
|
||||
if (sym.unreferenced()) {
|
||||
a.pop();
|
||||
compressor.warn("Dropping unused function argument {name} [{file}:{line},{col}]", {
|
||||
name : sym.name,
|
||||
file : sym.start.file,
|
||||
line : sym.start.line,
|
||||
col : sym.start.col
|
||||
});
|
||||
}
|
||||
else break;
|
||||
}
|
||||
else break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1119,6 +1148,7 @@ merge(Compressor.prototype, {
|
|||
}
|
||||
if (node instanceof AST_Definitions && !(tt.parent() instanceof AST_ForIn)) {
|
||||
var def = node.definitions.filter(function(def){
|
||||
if (def.is_destructuring()) return true;
|
||||
if (member(def.name.definition(), in_use)) return true;
|
||||
var w = {
|
||||
name : def.name.name,
|
||||
|
|
@ -1209,8 +1239,12 @@ merge(Compressor.prototype, {
|
|||
AST_Scope.DEFMETHOD("hoist_declarations", function(compressor){
|
||||
var self = this;
|
||||
if (compressor.has_directive("use asm")) return self;
|
||||
// Hoisting makes no sense in an arrow func
|
||||
if (!Array.isArray(self.body)) return self;
|
||||
|
||||
var hoist_funs = compressor.option("hoist_funs");
|
||||
var hoist_vars = compressor.option("hoist_vars");
|
||||
|
||||
if (hoist_funs || hoist_vars) {
|
||||
var dirs = [];
|
||||
var hoisted = [];
|
||||
|
|
@ -1239,6 +1273,7 @@ merge(Compressor.prototype, {
|
|||
}
|
||||
if (node instanceof AST_Var && hoist_vars) {
|
||||
node.definitions.forEach(function(def){
|
||||
if (def.is_destructuring()) { return; }
|
||||
vars.set(def.name.name, def);
|
||||
++vars_found;
|
||||
});
|
||||
|
|
@ -1268,7 +1303,7 @@ merge(Compressor.prototype, {
|
|||
vars.each(function(def, name){
|
||||
if (self instanceof AST_Lambda
|
||||
&& find_if(function(x){ return x.name == def.name.name },
|
||||
self.argnames)) {
|
||||
self.args_as_names())) {
|
||||
vars.del(name);
|
||||
} else {
|
||||
def = def.clone();
|
||||
|
|
@ -1679,13 +1714,23 @@ merge(Compressor.prototype, {
|
|||
|
||||
AST_Definitions.DEFMETHOD("to_assignments", function(){
|
||||
var assignments = this.definitions.reduce(function(a, def){
|
||||
if (def.value) {
|
||||
if (def.value && !def.is_destructuring()) {
|
||||
var name = make_node(AST_SymbolRef, def.name, def.name);
|
||||
a.push(make_node(AST_Assign, def, {
|
||||
operator : "=",
|
||||
left : name,
|
||||
right : def.value
|
||||
}));
|
||||
} else if (def.value) {
|
||||
// Because it's a destructuring, do not turn into an assignment.
|
||||
var varDef = make_node(AST_VarDef, def, {
|
||||
name: def.name,
|
||||
value: def.value
|
||||
});
|
||||
var var_ = make_node(AST_Var, def, {
|
||||
definitions: [ varDef ]
|
||||
});
|
||||
a.push(var_);
|
||||
}
|
||||
return a;
|
||||
}, []);
|
||||
|
|
@ -1812,6 +1857,11 @@ merge(Compressor.prototype, {
|
|||
}
|
||||
}
|
||||
break;
|
||||
case "Symbol":
|
||||
// Symbol's argument is only used for debugging.
|
||||
self.args = [];
|
||||
return self;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (exp instanceof AST_Dot && exp.property == "toString" && self.args.length == 0) {
|
||||
|
|
|
|||
|
|
@ -617,6 +617,22 @@ function OutputStream(options) {
|
|||
output.print_string(self.value, self.quote);
|
||||
output.semicolon();
|
||||
});
|
||||
|
||||
DEFPRINT(AST_Expansion, function (self, output) {
|
||||
output.print('...');
|
||||
self.symbol.print(output);
|
||||
});
|
||||
|
||||
DEFPRINT(AST_Destructuring, function (self, output) {
|
||||
output.print(self.is_array ? "[" : "{");
|
||||
var first = true;
|
||||
self.names.forEach(function (name) {
|
||||
if (first) first = false; else { output.comma(); output.space(); }
|
||||
name.print(output);
|
||||
})
|
||||
output.print(self.is_array ? "]" : "}");
|
||||
})
|
||||
|
||||
DEFPRINT(AST_Debugger, function(self, output){
|
||||
output.print("debugger");
|
||||
output.semicolon();
|
||||
|
|
@ -727,7 +743,11 @@ function OutputStream(options) {
|
|||
output.with_parens(function(){
|
||||
self.init.print(output);
|
||||
output.space();
|
||||
output.print("in");
|
||||
if (self instanceof AST_ForOf) {
|
||||
output.print("of");
|
||||
} else {
|
||||
output.print("in");
|
||||
}
|
||||
output.space();
|
||||
self.object.print(output);
|
||||
});
|
||||
|
|
@ -767,6 +787,52 @@ function OutputStream(options) {
|
|||
self._do_print(output);
|
||||
});
|
||||
|
||||
DEFPRINT(AST_PrefixedTemplateString, function(self, output) {
|
||||
self.prefix.print(output);
|
||||
self.template_string.print(output);
|
||||
});
|
||||
DEFPRINT(AST_TemplateString, function(self, output) {
|
||||
output.print("`");
|
||||
for (var i = 0; i < self.segments.length; i++) {
|
||||
if (typeof self.segments[i] !== "string") {
|
||||
output.print("${");
|
||||
self.segments[i].print(output);
|
||||
output.print("}");
|
||||
} else {
|
||||
output.print(self.segments[i]);
|
||||
}
|
||||
}
|
||||
output.print("`");
|
||||
});
|
||||
|
||||
AST_Arrow.DEFMETHOD("_do_print", function(output){
|
||||
var self = this;
|
||||
var parent = output.parent();
|
||||
var needs_parens = parent instanceof AST_Binary ||
|
||||
parent instanceof AST_Unary ||
|
||||
parent instanceof AST_Call;
|
||||
if (needs_parens) { output.print("(") }
|
||||
if (self.argnames.length === 1 && self.argnames[0] instanceof AST_Symbol) {
|
||||
self.argnames[0].print(output);
|
||||
} else {
|
||||
output.with_parens(function(){
|
||||
self.argnames.forEach(function(arg, i){
|
||||
if (i) output.comma();
|
||||
arg.print(output);
|
||||
});
|
||||
});
|
||||
}
|
||||
output.space();
|
||||
output.print('=>');
|
||||
output.space();
|
||||
if (self.body instanceof AST_Node) {
|
||||
this.body.print(output);
|
||||
} else {
|
||||
print_bracketed(this.body, output);
|
||||
}
|
||||
if (needs_parens) { output.print(")") }
|
||||
});
|
||||
|
||||
/* -----[ exits ]----- */
|
||||
AST_Exit.DEFMETHOD("_do_print", function(output, kind){
|
||||
output.print(kind);
|
||||
|
|
@ -939,6 +1005,9 @@ function OutputStream(options) {
|
|||
if (!avoid_semicolon)
|
||||
output.semicolon();
|
||||
});
|
||||
DEFPRINT(AST_Let, function(self, output){
|
||||
self._do_print(output, "let");
|
||||
});
|
||||
DEFPRINT(AST_Var, function(self, output){
|
||||
self._do_print(output, "var");
|
||||
});
|
||||
|
|
@ -1146,10 +1215,34 @@ function OutputStream(options) {
|
|||
self.key.print(output);
|
||||
self.value._do_print(output, true);
|
||||
});
|
||||
DEFPRINT(AST_ObjectComputedKeyVal, function(self, output) {
|
||||
output.print("[");
|
||||
self.key.print(output);
|
||||
output.print("]:");
|
||||
output.space();
|
||||
self.value.print(output);
|
||||
});
|
||||
DEFPRINT(AST_Symbol, function(self, output){
|
||||
var def = self.definition();
|
||||
output.print_name(def ? def.mangled_name || def.name : self.name);
|
||||
});
|
||||
DEFPRINT(AST_ObjectSymbol, function(self, output){
|
||||
var name = self.mangled_key || self.symbol.name;
|
||||
var def = self.symbol.definition();
|
||||
if (def && def.mangled_name) {
|
||||
output.print(name);
|
||||
output.print(':');
|
||||
output.space();
|
||||
output.print(def.mangled_name);
|
||||
} else if (!(def && def.mangled_name) && self.mangled_key) {
|
||||
output.print(name);
|
||||
output.print(':');
|
||||
output.space();
|
||||
output.print(def.name);
|
||||
} else {
|
||||
output.print(name);
|
||||
}
|
||||
});
|
||||
DEFPRINT(AST_Undefined, function(self, output){
|
||||
output.print("void 0");
|
||||
});
|
||||
|
|
@ -1163,6 +1256,9 @@ function OutputStream(options) {
|
|||
DEFPRINT(AST_This, function(self, output){
|
||||
output.print("this");
|
||||
});
|
||||
DEFPRINT(AST_Super, function(self, output){
|
||||
output.print("super");
|
||||
});
|
||||
DEFPRINT(AST_Constant, function(self, output){
|
||||
output.print(self.getValue());
|
||||
});
|
||||
|
|
|
|||
380
lib/parse.js
380
lib/parse.js
|
|
@ -44,7 +44,7 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
var KEYWORDS = 'break case catch const continue debugger default delete do else finally for function if in instanceof new return switch throw try typeof var void while with';
|
||||
var KEYWORDS = 'break case catch const continue debugger default delete do else finally for function if in instanceof new return switch throw try typeof var let void while with';
|
||||
var KEYWORDS_ATOM = 'false null true';
|
||||
var RESERVED_WORDS = 'abstract boolean byte char class double enum export extends final float goto implements import int interface long native package private protected public short static super synchronized this throws transient volatile yield'
|
||||
+ " " + KEYWORDS_ATOM + " " + KEYWORDS;
|
||||
|
|
@ -59,6 +59,8 @@ var OPERATOR_CHARS = makePredicate(characters("+-*&%=<>!?|~^"));
|
|||
|
||||
var RE_HEX_NUMBER = /^0x[0-9a-f]+$/i;
|
||||
var RE_OCT_NUMBER = /^0[0-7]+$/;
|
||||
var RE_ES6_OCT_NUMBER = /^0o[0-7]+$/i;
|
||||
var RE_BIN_NUMBER = /^0b[01]+$/i;
|
||||
var RE_DEC_NUMBER = /^\d*\.?\d*(?:e[+-]?\d*(?:\d\.?|\.?\d)\d*)?$/i;
|
||||
|
||||
var OPERATORS = makePredicate([
|
||||
|
|
@ -112,7 +114,7 @@ var WHITESPACE_CHARS = makePredicate(characters(" \u00a0\n\r\t\f\u000b\u200b\u18
|
|||
|
||||
var PUNC_BEFORE_EXPRESSION = makePredicate(characters("[{(,.;:"));
|
||||
|
||||
var PUNC_CHARS = makePredicate(characters("[]{}(),;:"));
|
||||
var PUNC_CHARS = makePredicate(characters("[]{}(),;:`"));
|
||||
|
||||
var REGEXP_MODIFIERS = makePredicate(characters("gmsiy"));
|
||||
|
||||
|
|
@ -182,6 +184,10 @@ function parse_js_number(num) {
|
|||
return parseInt(num.substr(2), 16);
|
||||
} else if (RE_OCT_NUMBER.test(num)) {
|
||||
return parseInt(num.substr(1), 8);
|
||||
} else if (RE_ES6_OCT_NUMBER.test(num)) {
|
||||
return parseInt(num.substr(2), 8);
|
||||
} else if (RE_BIN_NUMBER.test(num)) {
|
||||
return parseInt(num.substr(2), 2);
|
||||
} else if (RE_DEC_NUMBER.test(num)) {
|
||||
return parseFloat(num);
|
||||
}
|
||||
|
|
@ -518,11 +524,28 @@ function tokenizer($TEXT, filename, html5_comments, shebang) {
|
|||
return S.regex_allowed ? read_regexp("") : read_operator("/");
|
||||
};
|
||||
|
||||
function handle_eq_sign() {
|
||||
next();
|
||||
if (peek() === ">") {
|
||||
next();
|
||||
return token("arrow", "=>");
|
||||
} else {
|
||||
return read_operator("=");
|
||||
}
|
||||
};
|
||||
|
||||
function handle_dot() {
|
||||
next();
|
||||
return is_digit(peek().charCodeAt(0))
|
||||
? read_num(".")
|
||||
: token("punc", ".");
|
||||
if (is_digit(peek().charCodeAt(0))) {
|
||||
return read_num(".");
|
||||
}
|
||||
if (peek() === ".") {
|
||||
next(); // Consume second dot
|
||||
next(); // Consume third dot
|
||||
return token("expand", "...");
|
||||
}
|
||||
|
||||
return token("punc", ".");
|
||||
};
|
||||
|
||||
function read_word() {
|
||||
|
|
@ -567,6 +590,7 @@ function tokenizer($TEXT, filename, html5_comments, shebang) {
|
|||
case 34: case 39: return read_string(ch);
|
||||
case 46: return handle_dot();
|
||||
case 47: return handle_slash();
|
||||
case 61: return handle_eq_sign();
|
||||
}
|
||||
if (is_digit(code)) return read_num();
|
||||
if (PUNC_CHARS(ch)) return token("punc", next());
|
||||
|
|
@ -582,6 +606,9 @@ function tokenizer($TEXT, filename, html5_comments, shebang) {
|
|||
parse_error("Unexpected character '" + ch + "'");
|
||||
};
|
||||
|
||||
next_token.next = next;
|
||||
next_token.peek = peek;
|
||||
|
||||
next_token.context = function(nc) {
|
||||
if (nc) S = nc;
|
||||
return S;
|
||||
|
|
@ -740,7 +767,7 @@ function parse($TEXT, options) {
|
|||
function embed_tokens(parser) {
|
||||
return function() {
|
||||
var start = S.token;
|
||||
var expr = parser();
|
||||
var expr = parser.apply(null, arguments);
|
||||
var end = prev();
|
||||
expr.start = start;
|
||||
expr.end = end;
|
||||
|
|
@ -792,6 +819,7 @@ function parse($TEXT, options) {
|
|||
});
|
||||
case "[":
|
||||
case "(":
|
||||
case "`":
|
||||
return simple_statement();
|
||||
case ";":
|
||||
next();
|
||||
|
|
@ -863,6 +891,9 @@ function parse($TEXT, options) {
|
|||
case "var":
|
||||
return tmp = var_(), semicolon(), tmp;
|
||||
|
||||
case "let":
|
||||
return tmp = let_(), semicolon(), tmp;
|
||||
|
||||
case "const":
|
||||
return tmp = const_(), semicolon(), tmp;
|
||||
|
||||
|
|
@ -933,14 +964,22 @@ function parse($TEXT, options) {
|
|||
expect("(");
|
||||
var init = null;
|
||||
if (!is("punc", ";")) {
|
||||
init = is("keyword", "var")
|
||||
? (next(), var_(true))
|
||||
: expression(true, true);
|
||||
if (is("operator", "in")) {
|
||||
if (init instanceof AST_Var && init.definitions.length > 1)
|
||||
init =
|
||||
is("keyword", "var") ? (next(), var_(true)) :
|
||||
is("keyword", "let") ? (next(), let_(true)) :
|
||||
expression(true, true);
|
||||
var is_in = is("operator", "in");
|
||||
var is_of = is("name", "of");
|
||||
if (is_in || is_of) {
|
||||
if ((init instanceof AST_Var || init instanceof AST_Let) &&
|
||||
init.definitions.length > 1)
|
||||
croak("Only one variable declaration allowed in for..in loop");
|
||||
next();
|
||||
return for_in(init);
|
||||
if (is_in) {
|
||||
return for_in(init);
|
||||
} else {
|
||||
return for_of(init);
|
||||
}
|
||||
}
|
||||
}
|
||||
return regular_for(init);
|
||||
|
|
@ -960,6 +999,18 @@ function parse($TEXT, options) {
|
|||
});
|
||||
};
|
||||
|
||||
function for_of(init) {
|
||||
var lhs = init instanceof AST_Var ? init.definitions[0].name : null;
|
||||
var obj = expression(true);
|
||||
expect(")");
|
||||
return new AST_ForOf({
|
||||
init : init,
|
||||
name : lhs,
|
||||
object : obj,
|
||||
body : in_loop(statement)
|
||||
});
|
||||
};
|
||||
|
||||
function for_in(init) {
|
||||
var lhs = init instanceof AST_Var ? init.definitions[0].name : null;
|
||||
var obj = expression(true);
|
||||
|
|
@ -972,36 +1023,87 @@ function parse($TEXT, options) {
|
|||
});
|
||||
};
|
||||
|
||||
var arrow_function = function(args) {
|
||||
expect_token("arrow", "=>");
|
||||
|
||||
var argnames = args.as_params(croak);
|
||||
|
||||
var body = is("punc", "{") ?
|
||||
_function_body(true) :
|
||||
_function_body(false);
|
||||
|
||||
return new AST_Arrow({
|
||||
start : args.start,
|
||||
end : body.end,
|
||||
argnames : argnames,
|
||||
body : body
|
||||
});
|
||||
};
|
||||
|
||||
var function_ = function(ctor) {
|
||||
var start = S.token
|
||||
|
||||
var in_statement = ctor === AST_Defun;
|
||||
var name = is("name") ? as_symbol(in_statement ? AST_SymbolDefun : AST_SymbolLambda) : null;
|
||||
if (in_statement && !name)
|
||||
unexpected();
|
||||
expect("(");
|
||||
|
||||
var args = params_or_seq_().as_params(croak);
|
||||
var body = _function_body(true);
|
||||
return new ctor({
|
||||
name: name,
|
||||
argnames: (function(first, a){
|
||||
while (!is("punc", ")")) {
|
||||
if (first) first = false; else expect(",");
|
||||
a.push(as_symbol(AST_SymbolFunarg));
|
||||
}
|
||||
next();
|
||||
return a;
|
||||
})(true, []),
|
||||
body: (function(loop, labels){
|
||||
++S.in_function;
|
||||
S.in_directives = true;
|
||||
S.in_loop = 0;
|
||||
S.labels = [];
|
||||
var a = block_();
|
||||
--S.in_function;
|
||||
S.in_loop = loop;
|
||||
S.labels = labels;
|
||||
return a;
|
||||
})(S.in_loop, S.labels)
|
||||
start : args.start,
|
||||
end : body.end,
|
||||
name : name,
|
||||
argnames: args,
|
||||
body : body
|
||||
});
|
||||
};
|
||||
|
||||
function params_or_seq_() {
|
||||
var start = S.token
|
||||
expect("(");
|
||||
var first = true;
|
||||
var a = [];
|
||||
while (!is("punc", ")")) {
|
||||
if (first) first = false; else expect(",");
|
||||
if (is("expand", "...")) {
|
||||
next();
|
||||
a.push(new AST_Expansion({
|
||||
start: prev(),
|
||||
symbol: as_symbol(AST_SymbolFunarg),
|
||||
end: S.token,
|
||||
}));
|
||||
} else {
|
||||
a.push(expression(false));
|
||||
}
|
||||
}
|
||||
var end = S.token
|
||||
next();
|
||||
return new AST_ArrowParametersOrSeq({
|
||||
start: start,
|
||||
end: end,
|
||||
expressions: a
|
||||
});
|
||||
}
|
||||
|
||||
function _function_body(block) {
|
||||
var loop = S.in_loop;
|
||||
var labels = S.labels;
|
||||
++S.in_function;
|
||||
if (block)
|
||||
S.in_directives = true;
|
||||
S.in_loop = 0;
|
||||
S.labels = [];
|
||||
if (block)
|
||||
var a = block_();
|
||||
else
|
||||
var a = expression(false);
|
||||
--S.in_function;
|
||||
S.in_loop = loop;
|
||||
S.labels = labels;
|
||||
return a;
|
||||
}
|
||||
|
||||
function if_() {
|
||||
var cond = parenthesised(), body = statement(), belse = null;
|
||||
if (is("keyword", "else")) {
|
||||
|
|
@ -1096,13 +1198,25 @@ function parse($TEXT, options) {
|
|||
|
||||
function vardefs(no_in, in_const) {
|
||||
var a = [];
|
||||
var def;
|
||||
for (;;) {
|
||||
a.push(new AST_VarDef({
|
||||
start : S.token,
|
||||
name : as_symbol(in_const ? AST_SymbolConst : AST_SymbolVar),
|
||||
value : is("operator", "=") ? (next(), expression(false, no_in)) : null,
|
||||
end : prev()
|
||||
}));
|
||||
var sym_type = in_const ? AST_SymbolConst : AST_SymbolVar;
|
||||
if (is("punc", "{") || is("punc", "[")) {
|
||||
def = new AST_VarDef({
|
||||
start: S.token,
|
||||
name: destructuring_(sym_type),
|
||||
value: is("operator", "=") ? (expect_token("operator", "="), expression(false, no_in)) : null,
|
||||
end: prev()
|
||||
});
|
||||
} else {
|
||||
def = new AST_VarDef({
|
||||
start : S.token,
|
||||
name : as_symbol(sym_type),
|
||||
value : is("operator", "=") ? (next(), expression(false, no_in)) : null,
|
||||
end : prev()
|
||||
})
|
||||
}
|
||||
a.push(def);
|
||||
if (!is("punc", ","))
|
||||
break;
|
||||
next();
|
||||
|
|
@ -1110,6 +1224,44 @@ function parse($TEXT, options) {
|
|||
return a;
|
||||
};
|
||||
|
||||
var destructuring_ = embed_tokens(function (sym_type) {
|
||||
var is_array = is("punc", "[");
|
||||
var closing = is_array ? ']' : '}';
|
||||
var sym_type = sym_type || AST_SymbolRef;
|
||||
|
||||
next();
|
||||
|
||||
var first = true, children = [];
|
||||
while (!is("punc", closing)) {
|
||||
if (first) first = false; else expect(",");
|
||||
if (is("punc", closing)) break;
|
||||
if (is("punc", ",")) {
|
||||
children.push(new AST_Hole({ start: S.token, end: S.token }));
|
||||
} else if (is("punc", "[") || is("punc", "{")) {
|
||||
children.push(destructuring_(sym_type));
|
||||
} else if (is("expand", "...")) {
|
||||
next();
|
||||
var symbol = _make_symbol(sym_type);
|
||||
children.push(new AST_Expansion({
|
||||
start: prev(),
|
||||
symbol: symbol,
|
||||
end: S.token
|
||||
}));
|
||||
next();
|
||||
} else if (is("name")) {
|
||||
children.push(_make_symbol(sym_type));
|
||||
next();
|
||||
} else {
|
||||
children.push(expression());
|
||||
}
|
||||
}
|
||||
next()
|
||||
return new AST_Destructuring({
|
||||
names: children,
|
||||
is_array: is_array
|
||||
})
|
||||
});
|
||||
|
||||
var var_ = function(no_in) {
|
||||
return new AST_Var({
|
||||
start : prev(),
|
||||
|
|
@ -1118,6 +1270,14 @@ function parse($TEXT, options) {
|
|||
});
|
||||
};
|
||||
|
||||
var let_ = function(no_in) {
|
||||
return new AST_Let({
|
||||
start : prev(),
|
||||
definitions : vardefs(no_in, false),
|
||||
end : prev()
|
||||
});
|
||||
};
|
||||
|
||||
var const_ = function() {
|
||||
return new AST_Const({
|
||||
start : prev(),
|
||||
|
|
@ -1191,16 +1351,19 @@ function parse($TEXT, options) {
|
|||
if (is("punc")) {
|
||||
switch (start.value) {
|
||||
case "(":
|
||||
next();
|
||||
var ex = expression(true);
|
||||
var ex = params_or_seq_();
|
||||
ex.start = start;
|
||||
ex.end = S.token;
|
||||
expect(")");
|
||||
return subscripts(ex, allow_calls);
|
||||
if (is("arrow", "=>")) {
|
||||
return arrow_function(ex);
|
||||
}
|
||||
return subscripts(ex.as_expr(croak), allow_calls);
|
||||
case "[":
|
||||
return subscripts(array_(), allow_calls);
|
||||
case "{":
|
||||
return subscripts(object_(), allow_calls);
|
||||
return subscripts(object_or_object_destructuring_(), allow_calls);
|
||||
case "`":
|
||||
return subscripts(template_string(), allow_calls);
|
||||
}
|
||||
unexpected();
|
||||
}
|
||||
|
|
@ -1217,6 +1380,38 @@ function parse($TEXT, options) {
|
|||
unexpected();
|
||||
};
|
||||
|
||||
function template_string() {
|
||||
var tokenizer_S = S.input, start = S.token, segments = [], segment = "", ch;
|
||||
|
||||
while ((ch = tokenizer_S.next()) !== "`") {
|
||||
if (ch === "$" && tokenizer_S.peek() === "{") {
|
||||
segments.push(segment); segment = "";
|
||||
tokenizer_S.next();
|
||||
next();
|
||||
segments.push(expression());
|
||||
expect("}");
|
||||
if (is("punc", "`")) {
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
segment += ch;
|
||||
if (ch === "\\") {
|
||||
segment += tokenizer_S.next();
|
||||
}
|
||||
}
|
||||
|
||||
segments.push(segment);
|
||||
|
||||
next();
|
||||
|
||||
return new AST_TemplateString({
|
||||
start: start,
|
||||
segments: segments,
|
||||
end: S.token
|
||||
});
|
||||
}
|
||||
|
||||
function expr_list(closing, allow_trailing_comma, allow_empty) {
|
||||
var first = true, a = [];
|
||||
while (!is("punc", closing)) {
|
||||
|
|
@ -1239,9 +1434,9 @@ function parse($TEXT, options) {
|
|||
});
|
||||
});
|
||||
|
||||
var object_ = embed_tokens(function() {
|
||||
var object_or_object_destructuring_ = embed_tokens(function() {
|
||||
var start = S.token, first = true, a = [];
|
||||
expect("{");
|
||||
var first = true, a = [];
|
||||
while (!is("punc", "}")) {
|
||||
if (first) first = false; else expect(",");
|
||||
if (!options.strict && is("punc", "}"))
|
||||
|
|
@ -1270,23 +1465,51 @@ function parse($TEXT, options) {
|
|||
continue;
|
||||
}
|
||||
}
|
||||
expect(":");
|
||||
a.push(new AST_ObjectKeyVal({
|
||||
start : start,
|
||||
quote : start.quote,
|
||||
key : name,
|
||||
value : expression(false),
|
||||
end : prev()
|
||||
}));
|
||||
|
||||
if (type == "punc" && start.value == "[") {
|
||||
expect(":");
|
||||
a.push(new AST_ObjectComputedKeyVal({
|
||||
key: name,
|
||||
value: expression(false)
|
||||
}));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!is("punc", ":")) {
|
||||
// It's one of those object destructurings, the value is its own name
|
||||
a.push(new AST_ObjectSymbol({
|
||||
start: start,
|
||||
end: start,
|
||||
symbol: new AST_SymbolRef({
|
||||
start: start,
|
||||
end: start,
|
||||
name: name
|
||||
})
|
||||
}));
|
||||
} else {
|
||||
expect(":");
|
||||
a.push(new AST_ObjectKeyVal({
|
||||
start : start,
|
||||
key : name,
|
||||
value : expression(false),
|
||||
end : prev()
|
||||
}));
|
||||
}
|
||||
}
|
||||
next();
|
||||
return new AST_Object({ properties: a });
|
||||
return new AST_Object({ properties: a })
|
||||
});
|
||||
|
||||
function as_property_name() {
|
||||
var tmp = S.token;
|
||||
next();
|
||||
switch (tmp.type) {
|
||||
case "punc":
|
||||
if (tmp.value === "[") {
|
||||
var ex = expression(false);
|
||||
expect("]");
|
||||
return ex;
|
||||
} else unexpected();
|
||||
case "num":
|
||||
case "string":
|
||||
case "name":
|
||||
|
|
@ -1315,7 +1538,9 @@ function parse($TEXT, options) {
|
|||
|
||||
function _make_symbol(type) {
|
||||
var name = S.token.value;
|
||||
return new (name == "this" ? AST_This : type)({
|
||||
return new (name == "this" ? AST_This :
|
||||
name == "super" ? AST_Super :
|
||||
type)({
|
||||
name : String(name),
|
||||
start : S.token,
|
||||
end : S.token
|
||||
|
|
@ -1359,13 +1584,32 @@ function parse($TEXT, options) {
|
|||
return subscripts(new AST_Call({
|
||||
start : start,
|
||||
expression : expr,
|
||||
args : expr_list(")"),
|
||||
args : call_args(),
|
||||
end : prev()
|
||||
}), true);
|
||||
}
|
||||
return expr;
|
||||
};
|
||||
|
||||
var call_args = embed_tokens(function call_args() {
|
||||
var first = true;
|
||||
var args = [];
|
||||
while (!is("punc", ")")) {
|
||||
if (first) first = false; else expect(",");
|
||||
if (is("expand", "...")) {
|
||||
next();
|
||||
args.push(new AST_Expansion({
|
||||
start: prev(),
|
||||
symbol: as_symbol(AST_SymbolFunarg)
|
||||
}));
|
||||
} else {
|
||||
args.push(expression(false));
|
||||
}
|
||||
}
|
||||
next();
|
||||
return args;
|
||||
});
|
||||
|
||||
var maybe_unary = function(allow_calls) {
|
||||
var start = S.token;
|
||||
if (is("operator") && UNARY_PREFIX(start.value)) {
|
||||
|
|
@ -1435,12 +1679,17 @@ function parse($TEXT, options) {
|
|||
function is_assignable(expr) {
|
||||
if (!options.strict) return true;
|
||||
if (expr instanceof AST_This) return false;
|
||||
if (expr instanceof AST_Super) return false;
|
||||
return (expr instanceof AST_PropAccess || expr instanceof AST_Symbol);
|
||||
};
|
||||
|
||||
// In ES6, AssignmentExpression can also be an ArrowFunction
|
||||
var maybe_assign = function(no_in) {
|
||||
var start = S.token;
|
||||
var left = maybe_conditional(no_in), val = S.token.value;
|
||||
|
||||
var left = maybe_conditional(no_in);
|
||||
var val = S.token.value;
|
||||
|
||||
if (is("operator") && ASSIGNMENT(val)) {
|
||||
if (is_assignable(left)) {
|
||||
next();
|
||||
|
|
@ -1460,6 +1709,21 @@ function parse($TEXT, options) {
|
|||
var expression = function(commas, no_in) {
|
||||
var start = S.token;
|
||||
var expr = maybe_assign(no_in);
|
||||
if (expr instanceof AST_SymbolRef && is("arrow", "=>")) {
|
||||
expr = new AST_ArrowParametersOrSeq({
|
||||
start: expr.start,
|
||||
end: expr.end,
|
||||
expressions: [expr]
|
||||
});
|
||||
return arrow_function(expr);
|
||||
}
|
||||
if ((expr instanceof AST_SymbolRef || expr instanceof AST_PropAccess) && is("punc", "`")) {
|
||||
return new AST_PrefixedTemplateString({
|
||||
start: start,
|
||||
prefix: expr,
|
||||
template_string: template_string()
|
||||
})
|
||||
}
|
||||
if (commas && is("punc", ",")) {
|
||||
next();
|
||||
return new AST_Seq({
|
||||
|
|
|
|||
|
|
@ -90,6 +90,9 @@ function mangle_properties(ast, options) {
|
|||
if (node instanceof AST_ObjectKeyVal) {
|
||||
add(node.key);
|
||||
}
|
||||
else if (node instanceof AST_ObjectSymbol) {
|
||||
add(node.symbol.name);
|
||||
}
|
||||
else if (node instanceof AST_ObjectProperty) {
|
||||
// setter or getter, since KeyVal is handled above
|
||||
add(node.key.name);
|
||||
|
|
@ -111,6 +114,11 @@ function mangle_properties(ast, options) {
|
|||
if (node instanceof AST_ObjectKeyVal) {
|
||||
node.key = mangle(node.key);
|
||||
}
|
||||
else if (node instanceof AST_ObjectSymbol) {
|
||||
if (should_mangle(node.symbol.name)) {
|
||||
node.mangled_key = mangle(node.symbol.name)
|
||||
}
|
||||
}
|
||||
else if (node instanceof AST_ObjectProperty) {
|
||||
// setter or getter
|
||||
node.key.name = mangle(node.key.name);
|
||||
|
|
|
|||
22
lib/scope.js
22
lib/scope.js
|
|
@ -50,6 +50,7 @@ function SymbolDef(scope, index, orig) {
|
|||
this.references = [];
|
||||
this.global = false;
|
||||
this.mangled_name = null;
|
||||
this.object_destructuring_arg = false;
|
||||
this.undeclared = false;
|
||||
this.constant = false;
|
||||
this.index = index;
|
||||
|
|
@ -60,6 +61,7 @@ SymbolDef.prototype = {
|
|||
if (!options) options = {};
|
||||
|
||||
return (this.global && !options.toplevel)
|
||||
|| this.object_destructuring_arg
|
||||
|| this.undeclared
|
||||
|| (!options.eval && (this.scope.uses_eval || this.scope.uses_with))
|
||||
|| (options.keep_fnames
|
||||
|
|
@ -94,6 +96,7 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){
|
|||
var scope = self.parent_scope = null;
|
||||
var defun = null;
|
||||
var nesting = 0;
|
||||
var in_destructuring = null;
|
||||
var tw = new TreeWalker(function(node, descend){
|
||||
if (options.screw_ie8 && node instanceof AST_Catch) {
|
||||
var save_scope = scope;
|
||||
|
|
@ -104,6 +107,12 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){
|
|||
scope = save_scope;
|
||||
return true;
|
||||
}
|
||||
if (node instanceof AST_Destructuring && node.is_array === false) {
|
||||
in_destructuring = node; // These don't nest
|
||||
descend();
|
||||
in_destructuring = null;
|
||||
return true;
|
||||
}
|
||||
if (node instanceof AST_Scope) {
|
||||
node.init_scope_vars(nesting);
|
||||
var save_scope = node.parent_scope = scope;
|
||||
|
|
@ -131,6 +140,10 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){
|
|||
if (node instanceof AST_Symbol) {
|
||||
node.scope = scope;
|
||||
}
|
||||
if (node instanceof AST_SymbolFunarg) {
|
||||
node.object_destructuring_arg = !!in_destructuring;
|
||||
defun.def_variable(node);
|
||||
}
|
||||
if (node instanceof AST_SymbolLambda) {
|
||||
defun.def_function(node);
|
||||
}
|
||||
|
|
@ -146,6 +159,7 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){
|
|||
|| node instanceof AST_SymbolConst) {
|
||||
var def = defun.def_variable(node);
|
||||
def.constant = node instanceof AST_SymbolConst;
|
||||
def.destructuring = in_destructuring;
|
||||
def.init = tw.parent().value;
|
||||
}
|
||||
else if (node instanceof AST_SymbolCatch) {
|
||||
|
|
@ -254,6 +268,7 @@ AST_Scope.DEFMETHOD("def_variable", function(symbol){
|
|||
if (!this.variables.has(symbol.name)) {
|
||||
def = new SymbolDef(this, this.variables.size(), symbol);
|
||||
this.variables.set(symbol.name, def);
|
||||
def.object_destructuring_arg = symbol.object_destructuring_arg;
|
||||
def.global = !this.parent_scope;
|
||||
} else {
|
||||
def = this.variables.get(symbol.name);
|
||||
|
|
@ -402,7 +417,10 @@ AST_Toplevel.DEFMETHOD("mangle_names", function(options){
|
|||
}
|
||||
});
|
||||
this.walk(tw);
|
||||
to_mangle.forEach(function(def){ def.mangle(options) });
|
||||
to_mangle.forEach(function(def){
|
||||
if (def.destructuring && !def.destructuring.is_array) return;
|
||||
def.mangle(options);
|
||||
});
|
||||
|
||||
if (options.cache) {
|
||||
options.cache.cname = this.cname;
|
||||
|
|
@ -462,6 +480,8 @@ AST_Toplevel.DEFMETHOD("compute_char_frequency", function(options){
|
|||
base54.consider("new");
|
||||
else if (node instanceof AST_This)
|
||||
base54.consider("this");
|
||||
else if (node instanceof AST_Super)
|
||||
base54.consider("super");
|
||||
else if (node instanceof AST_Try)
|
||||
base54.consider("try");
|
||||
else if (node instanceof AST_Catch)
|
||||
|
|
|
|||
|
|
@ -163,10 +163,18 @@ TreeTransformer.prototype = new TreeWalker;
|
|||
if (self.value) self.value = self.value.transform(tw);
|
||||
});
|
||||
|
||||
_(AST_Destructuring, function(self, tw) {
|
||||
self.names = do_list(self.names, tw);
|
||||
});
|
||||
|
||||
_(AST_Lambda, function(self, tw){
|
||||
if (self.name) self.name = self.name.transform(tw);
|
||||
self.argnames = do_list(self.argnames, tw);
|
||||
self.body = do_list(self.body, tw);
|
||||
if (self.body instanceof AST_Node) {
|
||||
self.body = self.body.transform(tw);
|
||||
} else {
|
||||
self.body = do_list(self.body, tw);
|
||||
}
|
||||
});
|
||||
|
||||
_(AST_Call, function(self, tw){
|
||||
|
|
@ -211,6 +219,10 @@ TreeTransformer.prototype = new TreeWalker;
|
|||
self.properties = do_list(self.properties, tw);
|
||||
});
|
||||
|
||||
_(AST_ObjectSymbol, function(self, tw){
|
||||
self.symbol = self.symbol.transform(tw);
|
||||
});
|
||||
|
||||
_(AST_ObjectProperty, function(self, tw){
|
||||
self.value = self.value.transform(tw);
|
||||
});
|
||||
|
|
|
|||
33
test/compress/block-scope.js
Normal file
33
test/compress/block-scope.js
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
|
||||
let_statement: {
|
||||
input: {
|
||||
let x = 6;
|
||||
}
|
||||
expect_exact: "let x=6;"
|
||||
}
|
||||
|
||||
do_not_hoist_let: {
|
||||
options = {
|
||||
hoist_vars: true,
|
||||
};
|
||||
input: {
|
||||
function x() {
|
||||
if (FOO) {
|
||||
let let1;
|
||||
let let2;
|
||||
var var1;
|
||||
var var2;
|
||||
}
|
||||
}
|
||||
}
|
||||
expect: {
|
||||
function x() {
|
||||
var var1, var2;
|
||||
if (FOO) {
|
||||
let let1;
|
||||
let let2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
43
test/compress/destructuring.js
Normal file
43
test/compress/destructuring.js
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
|
||||
destructuring_arrays: {
|
||||
input: {
|
||||
var [aa, bb] = cc;
|
||||
}
|
||||
expect: {
|
||||
var[aa,bb]=cc;
|
||||
}
|
||||
}
|
||||
|
||||
destructuring_objects: {
|
||||
input: {
|
||||
var {aa, bb} = {aa:1, bb:2};
|
||||
}
|
||||
expect: {
|
||||
var{aa,bb}={aa:1,bb:2};
|
||||
}
|
||||
}
|
||||
|
||||
nested_destructuring_objects: {
|
||||
input: {
|
||||
var [{a},b] = c;
|
||||
}
|
||||
expect_exact: 'var[{a},b]=c;';
|
||||
}
|
||||
|
||||
destructuring_vardef_in_loops: {
|
||||
input: {
|
||||
for (var [x,y] in pairs);
|
||||
for (var [a] = 0;;);
|
||||
for (var {c} of cees);
|
||||
}
|
||||
expect_exact: "for(var[x,y]in pairs);for(var[a]=0;;);for(var{c}of cees);"
|
||||
}
|
||||
destructuring_expressions: {
|
||||
input: {
|
||||
({a, b});
|
||||
[{a}];
|
||||
f({x});
|
||||
}
|
||||
expect_exact: "({a,b});[{a}];f({x});"
|
||||
}
|
||||
|
||||
|
|
@ -164,6 +164,21 @@ used_var_in_catch: {
|
|||
}
|
||||
}
|
||||
|
||||
unused_keep_harmony_destructuring: {
|
||||
options = { unused: true };
|
||||
input: {
|
||||
function foo() {
|
||||
var {x, y} = foo;
|
||||
var a = foo;
|
||||
}
|
||||
}
|
||||
expect: {
|
||||
function foo() {
|
||||
var {x, y} = foo;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
keep_fnames: {
|
||||
options = { unused: true, keep_fnames: true, unsafe: true };
|
||||
input: {
|
||||
|
|
|
|||
17
test/compress/expansions.js
Normal file
17
test/compress/expansions.js
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
|
||||
expand_arguments: {
|
||||
input: {
|
||||
func(a, ...rest);
|
||||
func(...all);
|
||||
}
|
||||
expect_exact: "func(a,...rest);func(...all);"
|
||||
}
|
||||
|
||||
expand_parameters: {
|
||||
input: {
|
||||
(function (a, ...b){});
|
||||
(function (...args){});
|
||||
}
|
||||
expect_exact: "(function(a,...b){});(function(...args){});"
|
||||
}
|
||||
|
||||
177
test/compress/harmony.js
Normal file
177
test/compress/harmony.js
Normal file
|
|
@ -0,0 +1,177 @@
|
|||
arrow_functions: {
|
||||
input: {
|
||||
(a) => b; // 1 args
|
||||
(a, b) => c; // n args
|
||||
() => b; // 0 args
|
||||
(a) => (b) => c; // func returns func returns func
|
||||
(a) => ((b) => c); // So these parens are dropped
|
||||
() => (b,c) => d; // func returns func returns func
|
||||
a=>{return b;}
|
||||
a => 'lel'; // Dropping the parens
|
||||
}
|
||||
expect_exact: "a=>b;(a,b)=>c;()=>b;a=>b=>c;a=>b=>c;()=>(b,c)=>d;a=>{return b};a=>\"lel\";"
|
||||
}
|
||||
|
||||
arrow_function_parens: {
|
||||
input: {
|
||||
something && (() => {});
|
||||
}
|
||||
expect_exact: "something&&(()=>{});"
|
||||
}
|
||||
arrow_function_parens_2: {
|
||||
input: {
|
||||
(() => null)();
|
||||
}
|
||||
expect_exact: "(()=>null)();"
|
||||
}
|
||||
|
||||
regression_arrow_functions_and_hoist: {
|
||||
options = {
|
||||
hoist_vars: true,
|
||||
hoist_funs: true
|
||||
}
|
||||
input: {
|
||||
(a) => b;
|
||||
}
|
||||
expect_exact: "a=>b;"
|
||||
}
|
||||
|
||||
computed_property_names: {
|
||||
input: {
|
||||
obj({ ["x" + "x"]: 6 });
|
||||
}
|
||||
expect_exact: 'obj({["x"+"x"]:6});'
|
||||
}
|
||||
|
||||
typeof_arrow_functions: {
|
||||
options = {
|
||||
evaluate: true
|
||||
}
|
||||
input: {
|
||||
typeof (x) => null;
|
||||
}
|
||||
expect_exact: "\"function\";"
|
||||
}
|
||||
|
||||
template_strings: {
|
||||
input: {
|
||||
``;
|
||||
`xx\`x`;
|
||||
`${ foo + 2 }`;
|
||||
` foo ${ bar + `baz ${ qux }` }`;
|
||||
}
|
||||
expect_exact: "``;`xx\\`x`;`${foo+2}`;` foo ${bar+`baz ${qux}`}`;";
|
||||
}
|
||||
|
||||
template_string_prefixes: {
|
||||
input: {
|
||||
String.raw`foo`;
|
||||
foo `bar`;
|
||||
}
|
||||
expect_exact: "String.raw`foo`;foo`bar`;";
|
||||
}
|
||||
|
||||
destructuring_arguments: {
|
||||
input: {
|
||||
(function ( a ) { });
|
||||
(function ( [ a ] ) { });
|
||||
(function ( [ a, b ] ) { });
|
||||
(function ( [ [ a ] ] ) { });
|
||||
(function ( [ [ a, b ] ] ) { });
|
||||
(function ( [ a, [ b ] ] ) { });
|
||||
(function ( [ [ b ], a ] ) { });
|
||||
|
||||
(function ( { a } ) { });
|
||||
(function ( { a, b } ) { });
|
||||
|
||||
(function ( [ { a } ] ) { });
|
||||
(function ( [ { a, b } ] ) { });
|
||||
(function ( [ a, { b } ] ) { });
|
||||
(function ( [ { b }, a ] ) { });
|
||||
|
||||
( [ a ] ) => { };
|
||||
( [ a, b ] ) => { };
|
||||
|
||||
( { a } ) => { };
|
||||
( { a, b, c, d, e } ) => { };
|
||||
|
||||
( [ a ] ) => b;
|
||||
( [ a, b ] ) => c;
|
||||
|
||||
( { a } ) => b;
|
||||
( { a, b } ) => c;
|
||||
}
|
||||
expect: {
|
||||
(function(a){});
|
||||
(function([a]){});
|
||||
(function([a,b]){});
|
||||
(function([[a]]){});
|
||||
(function([[a,b]]){});
|
||||
(function([a,[b]]){});
|
||||
(function([[b],a]){});
|
||||
|
||||
(function({a}){});
|
||||
(function({a,b}){});
|
||||
|
||||
(function([{a}]){});
|
||||
(function([{a,b}]){});
|
||||
(function([a,{b}]){});
|
||||
(function([{b},a]){});
|
||||
|
||||
([a])=>{};
|
||||
([a,b])=>{};
|
||||
|
||||
({a})=>{};
|
||||
({a,b,c,d,e})=>{};
|
||||
|
||||
([a])=>b;
|
||||
([a,b])=>c;
|
||||
|
||||
({a})=>b;
|
||||
({a,b})=>c;
|
||||
}
|
||||
}
|
||||
|
||||
number_literals: {
|
||||
input: {
|
||||
0b1001;
|
||||
0B1001;
|
||||
0o11;
|
||||
0O11;
|
||||
}
|
||||
|
||||
expect: {
|
||||
9;
|
||||
9;
|
||||
9;
|
||||
9;
|
||||
}
|
||||
}
|
||||
|
||||
// Fabio: My patches accidentally caused a crash whenever
|
||||
// there's an extraneous set of parens around an object.
|
||||
regression_cannot_destructure: {
|
||||
input: {
|
||||
var x = ({ x : 3 });
|
||||
x(({ x: 3 }));
|
||||
}
|
||||
expect_exact: "var x={x:3};x({x:3});";
|
||||
}
|
||||
|
||||
regression_cannot_use_of: {
|
||||
input: {
|
||||
function of() {
|
||||
}
|
||||
var of = "is a valid variable name";
|
||||
of = { of: "is ok" };
|
||||
x.of;
|
||||
of: foo()
|
||||
}
|
||||
expect: {
|
||||
function of(){}
|
||||
var of="is a valid variable name";
|
||||
of={of:"is ok"};
|
||||
x.of;
|
||||
foo(); /* Label statement missing? No prob. */
|
||||
}
|
||||
}
|
||||
85
test/compress/hoist.js
Normal file
85
test/compress/hoist.js
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
|
||||
hoist_vars: {
|
||||
options = {
|
||||
hoist_vars: true
|
||||
}
|
||||
input: {
|
||||
function a() {
|
||||
bar();
|
||||
var var1;
|
||||
var var2;
|
||||
}
|
||||
function b(anArg) {
|
||||
bar();
|
||||
var var1;
|
||||
var anArg;
|
||||
}
|
||||
}
|
||||
expect: {
|
||||
function a() {
|
||||
var var1, var2; // Vars go up and are joined
|
||||
bar();
|
||||
}
|
||||
function b(anArg) {
|
||||
var var1;
|
||||
bar();
|
||||
// But vars named like arguments go away!
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
hoist_funs: {
|
||||
options = {
|
||||
hoist_funs: true
|
||||
}
|
||||
input: {
|
||||
function a() {
|
||||
bar();
|
||||
function foo() {}
|
||||
}
|
||||
}
|
||||
expect: {
|
||||
function a() {
|
||||
function foo() {} // Funs go up
|
||||
bar();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
hoist_no_destructurings: {
|
||||
options = {
|
||||
hoist_vars: true,
|
||||
hoist_funs: true
|
||||
}
|
||||
input: {
|
||||
function a([anArg]) {
|
||||
bar();
|
||||
var var1;
|
||||
var anArg; // Because anArg is already declared, this goes away!
|
||||
}
|
||||
}
|
||||
expect: {
|
||||
function a([anArg]) {
|
||||
var var1;
|
||||
bar();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dont_hoist_var_destructurings: {
|
||||
options = {
|
||||
hoist_vars: true,
|
||||
hoist_funs: true
|
||||
}
|
||||
input: {
|
||||
function x() {
|
||||
// If foo is null or undefined, this should be an exception
|
||||
var {x,y} = foo;
|
||||
}
|
||||
}
|
||||
expect: {
|
||||
function x() {
|
||||
var {x,y} = foo;
|
||||
}
|
||||
}
|
||||
}
|
||||
30
test/compress/issue-203.js
Normal file
30
test/compress/issue-203.js
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
|
||||
compress_new_function: {
|
||||
options = {
|
||||
unsafe: true
|
||||
}
|
||||
input: {
|
||||
new Function("aa, bb", 'return aa;');
|
||||
}
|
||||
expect: {
|
||||
Function("a", "b", "return a");
|
||||
}
|
||||
}
|
||||
|
||||
compress_new_function_with_destruct: {
|
||||
options = {
|
||||
unsafe: true
|
||||
}
|
||||
input: {
|
||||
new Function("aa, [bb]", 'return aa;');
|
||||
new Function("aa, {bb}", 'return aa;');
|
||||
new Function("[[aa]], [{bb}]", 'return aa;');
|
||||
}
|
||||
expect: {
|
||||
Function("a", "[b]", "return a");
|
||||
Function("a", "{bb}", "return a");
|
||||
Function("[[a]]", "[{bb}]", 'return a');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
9
test/compress/super.js
Normal file
9
test/compress/super.js
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
|
||||
super_can_be_parsed: {
|
||||
input: {
|
||||
super(1,2);
|
||||
super.meth();
|
||||
}
|
||||
expect_exact: "super(1,2);super.meth();"
|
||||
}
|
||||
|
||||
127
test/parser.js
Normal file
127
test/parser.js
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
|
||||
var UglifyJS = require("..");
|
||||
var ok = require('assert');
|
||||
|
||||
module.exports = function () {
|
||||
console.log("--- Parser tests");
|
||||
|
||||
// Destructuring arguments
|
||||
|
||||
// Function argument nodes are correct
|
||||
function get_args(args) {
|
||||
return args.map(function (arg) {
|
||||
return [arg.TYPE, arg.name];
|
||||
});
|
||||
}
|
||||
|
||||
// Destructurings as arguments
|
||||
var destr_fun1 = UglifyJS.parse('(function ({a, b}) {})').body[0].body;
|
||||
var destr_fun2 = UglifyJS.parse('(function ([a, [b]]) {})').body[0].body;
|
||||
|
||||
ok.equal(destr_fun1.argnames.length, 1);
|
||||
ok.equal(destr_fun2.argnames.length, 1);
|
||||
|
||||
var destr_fun1 = UglifyJS.parse('({a, b}) => null').body[0].body;
|
||||
var destr_fun2 = UglifyJS.parse('([a, [b]]) => null').body[0].body;
|
||||
|
||||
ok.equal(destr_fun1.argnames.length, 1);
|
||||
ok.equal(destr_fun2.argnames.length, 1);
|
||||
|
||||
var destruct1 = destr_fun1.argnames[0];
|
||||
var destruct2 = destr_fun2.argnames[0];
|
||||
|
||||
ok(destruct1 instanceof UglifyJS.AST_Destructuring);
|
||||
ok(destruct2 instanceof UglifyJS.AST_Destructuring);
|
||||
ok(destruct2.names[1] instanceof UglifyJS.AST_Destructuring);
|
||||
|
||||
ok.equal(destruct1.start.value, '{');
|
||||
ok.equal(destruct1.end.value, '}');
|
||||
ok.equal(destruct2.start.value, '[');
|
||||
ok.equal(destruct2.end.value, ']');
|
||||
|
||||
ok.equal(destruct1.is_array, false);
|
||||
ok.equal(destruct2.is_array, true);
|
||||
|
||||
var aAndB = [
|
||||
['SymbolFunarg', 'a'],
|
||||
['SymbolFunarg', 'b']
|
||||
];
|
||||
|
||||
ok.deepEqual(
|
||||
[
|
||||
destruct1.names[0].TYPE,
|
||||
destruct1.names[0].name],
|
||||
aAndB[0]);
|
||||
|
||||
ok.deepEqual(
|
||||
[
|
||||
destruct2.names[1].names[0].TYPE,
|
||||
destruct2.names[1].names[0].name
|
||||
],
|
||||
aAndB[1]);
|
||||
|
||||
ok.deepEqual(
|
||||
get_args(destr_fun1.args_as_names()),
|
||||
aAndB)
|
||||
ok.deepEqual(
|
||||
get_args(destr_fun2.args_as_names()),
|
||||
aAndB)
|
||||
|
||||
// Making sure we don't accidentally accept things which
|
||||
// Aren't argument destructurings
|
||||
|
||||
ok.throws(function () {
|
||||
UglifyJS.parse('(function ([]) {})');
|
||||
}, /Invalid destructuring function parameter/);
|
||||
|
||||
ok.throws(function () {
|
||||
UglifyJS.parse('(function ( { a, [ b ] } ) { })')
|
||||
});
|
||||
|
||||
ok.throws(function () {
|
||||
UglifyJS.parse('(function (1) { })');
|
||||
}, /Invalid function parameter/);
|
||||
|
||||
ok.throws(function () {
|
||||
UglifyJS.parse('(function (this) { })');
|
||||
});
|
||||
|
||||
ok.throws(function () {
|
||||
UglifyJS.parse('(function ([1]) { })');
|
||||
}, /Invalid function parameter/);
|
||||
|
||||
ok.throws(function () {
|
||||
UglifyJS.parse('(function [a] { })');
|
||||
});
|
||||
|
||||
// Destructuring variable declaration
|
||||
|
||||
var decls = UglifyJS.parse('var {a,b} = foo, { c, d } = bar');
|
||||
|
||||
ok.equal(decls.body[0].TYPE, 'Var');
|
||||
ok.equal(decls.body[0].definitions.length, 2);
|
||||
ok.equal(decls.body[0].definitions[0].name.TYPE, 'Destructuring');
|
||||
ok.equal(decls.body[0].definitions[0].value.TYPE, 'SymbolRef');
|
||||
|
||||
var nested_def = UglifyJS.parse('var [{x}] = foo').body[0].definitions[0];
|
||||
|
||||
ok.equal(nested_def.name.names[0].names[0].TYPE, 'SymbolVar');
|
||||
ok.equal(nested_def.name.names[0].names[0].name, 'x');
|
||||
|
||||
var holey_def = UglifyJS.parse('const [,,third] = [1,2,3]').body[0].definitions[0];
|
||||
|
||||
ok.equal(holey_def.name.names[0].TYPE, 'Hole');
|
||||
ok.equal(holey_def.name.names[2].TYPE, 'SymbolConst');
|
||||
|
||||
var expanding_def = UglifyJS.parse('var [first, ...rest] = [1,2,3]').body[0].definitions[0];
|
||||
|
||||
ok.equal(expanding_def.name.names[0].TYPE, 'SymbolVar');
|
||||
ok.equal(expanding_def.name.names[1].TYPE, 'Expansion');
|
||||
ok.equal(expanding_def.name.names[1].symbol.TYPE, 'SymbolVar');
|
||||
}
|
||||
|
||||
// Run standalone
|
||||
if (module.parent === null) {
|
||||
module.exports();
|
||||
}
|
||||
|
||||
|
|
@ -26,6 +26,10 @@ run_ast_conversion_tests({
|
|||
iterations: 1000
|
||||
});
|
||||
|
||||
var run_parser_tests = require('./parser.js');
|
||||
|
||||
run_parser_tests();
|
||||
|
||||
/* -----[ utils ]----- */
|
||||
|
||||
function tmpl() {
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user