This commit is contained in:
Fábio Santos 2015-10-26 21:58:21 +00:00
commit b4eae60dc8
17 changed files with 1240 additions and 77 deletions

View File

@ -143,7 +143,7 @@ var AST_SimpleStatement = DEFNODE("SimpleStatement", "body", {
}, AST_Statement); }, AST_Statement);
function walk_body(node, visitor) { function walk_body(node, visitor) {
if (node.body instanceof AST_Statement) { if (node.body instanceof AST_Node) {
node.body._walk(visitor); node.body._walk(visitor);
} }
else node.body.forEach(function(stat){ else node.body.forEach(function(stat){
@ -263,6 +263,10 @@ var AST_ForIn = DEFNODE("ForIn", "init name object", {
} }
}, AST_IterationStatement); }, AST_IterationStatement);
var AST_ForOf = DEFNODE("ForOf", null, {
$documentation: "A `for ... of` statement",
}, AST_ForIn);
var AST_With = DEFNODE("With", "expression", { var AST_With = DEFNODE("With", "expression", {
$documentation: "A `with` statement", $documentation: "A `with` statement",
$propdoc: { $propdoc: {
@ -359,13 +363,93 @@ var AST_Toplevel = DEFNODE("Toplevel", "globals", {
} }
}, AST_Scope); }, 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", { var AST_Lambda = DEFNODE("Lambda", "name argnames uses_arguments", {
$documentation: "Base class for functions", $documentation: "Base class for functions",
$propdoc: { $propdoc: {
name: "[AST_SymbolDeclaration?] the name of this function", 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" 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) { _walk: function(visitor) {
return visitor._visit(this, function(){ return visitor._visit(this, function(){
if (this.name) this.name._walk(visitor); if (this.name) this.name._walk(visitor);
@ -385,10 +469,66 @@ var AST_Function = DEFNODE("Function", null, {
$documentation: "A function expression" $documentation: "A function expression"
}, AST_Lambda); }, AST_Lambda);
var AST_Arrow = DEFNODE("Arrow", null, {
$documentation: "An ES6 Arrow function ((a) => b)"
}, AST_Lambda);
var AST_Defun = DEFNODE("Defun", null, { var AST_Defun = DEFNODE("Defun", null, {
$documentation: "A function definition" $documentation: "A function definition"
}, AST_Lambda); }, 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 ]----- */ /* -----[ JUMPS ]----- */
var AST_Jump = DEFNODE("Jump", null, { var AST_Jump = DEFNODE("Jump", null, {
@ -542,6 +682,10 @@ var AST_Var = DEFNODE("Var", null, {
$documentation: "A `var` statement" $documentation: "A `var` statement"
}, AST_Definitions); }, AST_Definitions);
var AST_Let = DEFNODE("Let", null, {
$documentation: "A `let` statement"
}, AST_Definitions);
var AST_Const = DEFNODE("Const", null, { var AST_Const = DEFNODE("Const", null, {
$documentation: "A `const` statement" $documentation: "A `const` statement"
}, AST_Definitions); }, AST_Definitions);
@ -549,9 +693,12 @@ var AST_Const = DEFNODE("Const", null, {
var AST_VarDef = DEFNODE("VarDef", "name value", { var AST_VarDef = DEFNODE("VarDef", "name value", {
$documentation: "A variable declaration; only appears in a AST_Definitions node", $documentation: "A variable declaration; only appears in a AST_Definitions node",
$propdoc: { $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" value: "[AST_Node?] initializer, or null of there's no initializer"
}, },
is_destructuring: function() {
return this.name instanceof AST_Destructuring;
},
_walk: function(visitor) { _walk: function(visitor) {
return visitor._visit(this, function(){ return visitor._visit(this, function(){
this.name._walk(visitor); this.name._walk(visitor);
@ -774,6 +921,28 @@ var AST_ObjectKeyVal = DEFNODE("ObjectKeyVal", "quote", {
} }
}, AST_ObjectProperty); }, 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, { var AST_ObjectSetter = DEFNODE("ObjectSetter", null, {
$documentation: "An object setter property", $documentation: "An object setter property",
}, AST_ObjectProperty); }, AST_ObjectProperty);
@ -849,6 +1018,10 @@ var AST_This = DEFNODE("This", null, {
$documentation: "The `this` symbol", $documentation: "The `this` symbol",
}, AST_Symbol); }, AST_Symbol);
var AST_Super = DEFNODE("Super", null, {
$documentation: "The `super` symbol",
}, AST_Symbol);
var AST_Constant = DEFNODE("Constant", null, { var AST_Constant = DEFNODE("Constant", null, {
$documentation: "Base class for all constants", $documentation: "Base class for all constants",
getValue: function() { getValue: function() {

View File

@ -231,8 +231,16 @@ merge(Compressor.prototype, {
return /@ngInject/.test(comment.value); return /@ngInject/.test(comment.value);
} }
function make_arguments_names_list(func) { function make_arguments_names_list(func) {
var foundDestructuring = false;
return func.argnames.map(function(sym){ 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 }); return make_node(AST_String, sym, { value: sym.name });
}).filter(function (name) {
return name !== null;
}); });
} }
function make_array(orig, elements) { function make_array(orig, elements) {
@ -731,6 +739,9 @@ merge(Compressor.prototype, {
// places too. :-( Wish JS had multiple inheritance. // places too. :-( Wish JS had multiple inheritance.
throw def; throw def;
}); });
def(AST_Arrow, function() {
throw def;
});
function ev(node, compressor) { function ev(node, compressor) {
if (!compressor) throw new Error("Compressor must be passed"); if (!compressor) throw new Error("Compressor must be passed");
@ -749,7 +760,8 @@ merge(Compressor.prototype, {
case "typeof": case "typeof":
// Function would be evaluated to an array and so typeof would // Function would be evaluated to an array and so typeof would
// incorrectly return 'object'. Hence making is a special case. // 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); e = ev(e, compressor);
@ -940,6 +952,9 @@ merge(Compressor.prototype, {
return false; return false;
}); });
def(AST_ObjectProperty, function(compressor){ 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); return this.value.has_side_effects(compressor);
}); });
def(AST_Array, function(compressor){ def(AST_Array, function(compressor){
@ -1012,6 +1027,7 @@ merge(Compressor.prototype, {
}); });
OPT(AST_Block, function(self, compressor){ OPT(AST_Block, function(self, compressor){
if (self.body instanceof AST_Node) { return self; }
self.body = tighten_body(self.body, compressor); self.body = tighten_body(self.body, compressor);
return self; return self;
}); });
@ -1045,6 +1061,7 @@ merge(Compressor.prototype, {
} }
if (node instanceof AST_Definitions && scope === self) { if (node instanceof AST_Definitions && scope === self) {
node.definitions.forEach(function(def){ node.definitions.forEach(function(def){
if (def.is_destructuring()) return; /* Destructurings are type assertions! */
if (def.value) { if (def.value) {
initializations.add(def.name.name, def.value); initializations.add(def.name.name, def.value);
if (def.value.has_side_effects(compressor)) { if (def.value.has_side_effects(compressor)) {
@ -1091,7 +1108,18 @@ merge(Compressor.prototype, {
if (node instanceof AST_Lambda && !(node instanceof AST_Accessor)) { if (node instanceof AST_Lambda && !(node instanceof AST_Accessor)) {
if (!compressor.option("keep_fargs")) { if (!compressor.option("keep_fargs")) {
for (var a = node.argnames, i = a.length; --i >= 0;) { for (var a = node.argnames, i = a.length; --i >= 0;) {
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]; var sym = a[i];
if (sym instanceof AST_Expansion) {
sym = sym.symbol;
}
if (sym.unreferenced()) { if (sym.unreferenced()) {
a.pop(); a.pop();
compressor.warn("Dropping unused function argument {name} [{file}:{line},{col}]", { compressor.warn("Dropping unused function argument {name} [{file}:{line},{col}]", {
@ -1105,6 +1133,7 @@ merge(Compressor.prototype, {
} }
} }
} }
}
if (node instanceof AST_Defun && node !== self) { if (node instanceof AST_Defun && node !== self) {
if (!member(node.name.definition(), in_use)) { if (!member(node.name.definition(), in_use)) {
compressor.warn("Dropping unused function {name} [{file}:{line},{col}]", { compressor.warn("Dropping unused function {name} [{file}:{line},{col}]", {
@ -1119,6 +1148,7 @@ merge(Compressor.prototype, {
} }
if (node instanceof AST_Definitions && !(tt.parent() instanceof AST_ForIn)) { if (node instanceof AST_Definitions && !(tt.parent() instanceof AST_ForIn)) {
var def = node.definitions.filter(function(def){ var def = node.definitions.filter(function(def){
if (def.is_destructuring()) return true;
if (member(def.name.definition(), in_use)) return true; if (member(def.name.definition(), in_use)) return true;
var w = { var w = {
name : def.name.name, name : def.name.name,
@ -1209,8 +1239,12 @@ merge(Compressor.prototype, {
AST_Scope.DEFMETHOD("hoist_declarations", function(compressor){ AST_Scope.DEFMETHOD("hoist_declarations", function(compressor){
var self = this; var self = this;
if (compressor.has_directive("use asm")) return self; 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_funs = compressor.option("hoist_funs");
var hoist_vars = compressor.option("hoist_vars"); var hoist_vars = compressor.option("hoist_vars");
if (hoist_funs || hoist_vars) { if (hoist_funs || hoist_vars) {
var dirs = []; var dirs = [];
var hoisted = []; var hoisted = [];
@ -1239,6 +1273,7 @@ merge(Compressor.prototype, {
} }
if (node instanceof AST_Var && hoist_vars) { if (node instanceof AST_Var && hoist_vars) {
node.definitions.forEach(function(def){ node.definitions.forEach(function(def){
if (def.is_destructuring()) { return; }
vars.set(def.name.name, def); vars.set(def.name.name, def);
++vars_found; ++vars_found;
}); });
@ -1268,7 +1303,7 @@ merge(Compressor.prototype, {
vars.each(function(def, name){ vars.each(function(def, name){
if (self instanceof AST_Lambda if (self instanceof AST_Lambda
&& find_if(function(x){ return x.name == def.name.name }, && find_if(function(x){ return x.name == def.name.name },
self.argnames)) { self.args_as_names())) {
vars.del(name); vars.del(name);
} else { } else {
def = def.clone(); def = def.clone();
@ -1679,13 +1714,23 @@ merge(Compressor.prototype, {
AST_Definitions.DEFMETHOD("to_assignments", function(){ AST_Definitions.DEFMETHOD("to_assignments", function(){
var assignments = this.definitions.reduce(function(a, def){ 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); var name = make_node(AST_SymbolRef, def.name, def.name);
a.push(make_node(AST_Assign, def, { a.push(make_node(AST_Assign, def, {
operator : "=", operator : "=",
left : name, left : name,
right : def.value 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; return a;
}, []); }, []);
@ -1812,6 +1857,11 @@ merge(Compressor.prototype, {
} }
} }
break; 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) { else if (exp instanceof AST_Dot && exp.property == "toString" && self.args.length == 0) {

View File

@ -617,6 +617,22 @@ function OutputStream(options) {
output.print_string(self.value, self.quote); output.print_string(self.value, self.quote);
output.semicolon(); 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){ DEFPRINT(AST_Debugger, function(self, output){
output.print("debugger"); output.print("debugger");
output.semicolon(); output.semicolon();
@ -727,7 +743,11 @@ function OutputStream(options) {
output.with_parens(function(){ output.with_parens(function(){
self.init.print(output); self.init.print(output);
output.space(); output.space();
if (self instanceof AST_ForOf) {
output.print("of");
} else {
output.print("in"); output.print("in");
}
output.space(); output.space();
self.object.print(output); self.object.print(output);
}); });
@ -767,6 +787,52 @@ function OutputStream(options) {
self._do_print(output); 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 ]----- */ /* -----[ exits ]----- */
AST_Exit.DEFMETHOD("_do_print", function(output, kind){ AST_Exit.DEFMETHOD("_do_print", function(output, kind){
output.print(kind); output.print(kind);
@ -939,6 +1005,9 @@ function OutputStream(options) {
if (!avoid_semicolon) if (!avoid_semicolon)
output.semicolon(); output.semicolon();
}); });
DEFPRINT(AST_Let, function(self, output){
self._do_print(output, "let");
});
DEFPRINT(AST_Var, function(self, output){ DEFPRINT(AST_Var, function(self, output){
self._do_print(output, "var"); self._do_print(output, "var");
}); });
@ -1146,10 +1215,34 @@ function OutputStream(options) {
self.key.print(output); self.key.print(output);
self.value._do_print(output, true); 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){ DEFPRINT(AST_Symbol, function(self, output){
var def = self.definition(); var def = self.definition();
output.print_name(def ? def.mangled_name || def.name : self.name); 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){ DEFPRINT(AST_Undefined, function(self, output){
output.print("void 0"); output.print("void 0");
}); });
@ -1163,6 +1256,9 @@ function OutputStream(options) {
DEFPRINT(AST_This, function(self, output){ DEFPRINT(AST_This, function(self, output){
output.print("this"); output.print("this");
}); });
DEFPRINT(AST_Super, function(self, output){
output.print("super");
});
DEFPRINT(AST_Constant, function(self, output){ DEFPRINT(AST_Constant, function(self, output){
output.print(self.getValue()); output.print(self.getValue());
}); });

View File

@ -44,7 +44,7 @@
"use strict"; "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 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' 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; + " " + KEYWORDS_ATOM + " " + KEYWORDS;
@ -59,6 +59,8 @@ var OPERATOR_CHARS = makePredicate(characters("+-*&%=<>!?|~^"));
var RE_HEX_NUMBER = /^0x[0-9a-f]+$/i; var RE_HEX_NUMBER = /^0x[0-9a-f]+$/i;
var RE_OCT_NUMBER = /^0[0-7]+$/; 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 RE_DEC_NUMBER = /^\d*\.?\d*(?:e[+-]?\d*(?:\d\.?|\.?\d)\d*)?$/i;
var OPERATORS = makePredicate([ 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_BEFORE_EXPRESSION = makePredicate(characters("[{(,.;:"));
var PUNC_CHARS = makePredicate(characters("[]{}(),;:")); var PUNC_CHARS = makePredicate(characters("[]{}(),;:`"));
var REGEXP_MODIFIERS = makePredicate(characters("gmsiy")); var REGEXP_MODIFIERS = makePredicate(characters("gmsiy"));
@ -182,6 +184,10 @@ function parse_js_number(num) {
return parseInt(num.substr(2), 16); return parseInt(num.substr(2), 16);
} else if (RE_OCT_NUMBER.test(num)) { } else if (RE_OCT_NUMBER.test(num)) {
return parseInt(num.substr(1), 8); 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)) { } else if (RE_DEC_NUMBER.test(num)) {
return parseFloat(num); return parseFloat(num);
} }
@ -518,11 +524,28 @@ function tokenizer($TEXT, filename, html5_comments, shebang) {
return S.regex_allowed ? read_regexp("") : read_operator("/"); 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() { function handle_dot() {
next(); next();
return is_digit(peek().charCodeAt(0)) if (is_digit(peek().charCodeAt(0))) {
? read_num(".") return read_num(".");
: token("punc", "."); }
if (peek() === ".") {
next(); // Consume second dot
next(); // Consume third dot
return token("expand", "...");
}
return token("punc", ".");
}; };
function read_word() { function read_word() {
@ -567,6 +590,7 @@ function tokenizer($TEXT, filename, html5_comments, shebang) {
case 34: case 39: return read_string(ch); case 34: case 39: return read_string(ch);
case 46: return handle_dot(); case 46: return handle_dot();
case 47: return handle_slash(); case 47: return handle_slash();
case 61: return handle_eq_sign();
} }
if (is_digit(code)) return read_num(); if (is_digit(code)) return read_num();
if (PUNC_CHARS(ch)) return token("punc", next()); if (PUNC_CHARS(ch)) return token("punc", next());
@ -582,6 +606,9 @@ function tokenizer($TEXT, filename, html5_comments, shebang) {
parse_error("Unexpected character '" + ch + "'"); parse_error("Unexpected character '" + ch + "'");
}; };
next_token.next = next;
next_token.peek = peek;
next_token.context = function(nc) { next_token.context = function(nc) {
if (nc) S = nc; if (nc) S = nc;
return S; return S;
@ -740,7 +767,7 @@ function parse($TEXT, options) {
function embed_tokens(parser) { function embed_tokens(parser) {
return function() { return function() {
var start = S.token; var start = S.token;
var expr = parser(); var expr = parser.apply(null, arguments);
var end = prev(); var end = prev();
expr.start = start; expr.start = start;
expr.end = end; expr.end = end;
@ -792,6 +819,7 @@ function parse($TEXT, options) {
}); });
case "[": case "[":
case "(": case "(":
case "`":
return simple_statement(); return simple_statement();
case ";": case ";":
next(); next();
@ -863,6 +891,9 @@ function parse($TEXT, options) {
case "var": case "var":
return tmp = var_(), semicolon(), tmp; return tmp = var_(), semicolon(), tmp;
case "let":
return tmp = let_(), semicolon(), tmp;
case "const": case "const":
return tmp = const_(), semicolon(), tmp; return tmp = const_(), semicolon(), tmp;
@ -933,14 +964,22 @@ function parse($TEXT, options) {
expect("("); expect("(");
var init = null; var init = null;
if (!is("punc", ";")) { if (!is("punc", ";")) {
init = is("keyword", "var") init =
? (next(), var_(true)) is("keyword", "var") ? (next(), var_(true)) :
: expression(true, true); is("keyword", "let") ? (next(), let_(true)) :
if (is("operator", "in")) { expression(true, true);
if (init instanceof AST_Var && init.definitions.length > 1) 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"); croak("Only one variable declaration allowed in for..in loop");
next(); next();
if (is_in) {
return for_in(init); return for_in(init);
} else {
return for_of(init);
}
} }
} }
return regular_for(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) { function for_in(init) {
var lhs = init instanceof AST_Var ? init.definitions[0].name : null; var lhs = init instanceof AST_Var ? init.definitions[0].name : null;
var obj = expression(true); var obj = expression(true);
@ -972,35 +1023,86 @@ 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 function_ = function(ctor) {
var start = S.token
var in_statement = ctor === AST_Defun; var in_statement = ctor === AST_Defun;
var name = is("name") ? as_symbol(in_statement ? AST_SymbolDefun : AST_SymbolLambda) : null; var name = is("name") ? as_symbol(in_statement ? AST_SymbolDefun : AST_SymbolLambda) : null;
if (in_statement && !name) if (in_statement && !name)
unexpected(); unexpected();
expect("(");
var args = params_or_seq_().as_params(croak);
var body = _function_body(true);
return new ctor({ return new ctor({
name: name, start : args.start,
argnames: (function(first, a){ 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", ")")) { while (!is("punc", ")")) {
if (first) first = false; else expect(","); if (first) first = false; else expect(",");
a.push(as_symbol(AST_SymbolFunarg)); if (is("expand", "...")) {
}
next(); next();
return a; a.push(new AST_Expansion({
})(true, []), start: prev(),
body: (function(loop, labels){ 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; ++S.in_function;
if (block)
S.in_directives = true; S.in_directives = true;
S.in_loop = 0; S.in_loop = 0;
S.labels = []; S.labels = [];
if (block)
var a = block_(); var a = block_();
else
var a = expression(false);
--S.in_function; --S.in_function;
S.in_loop = loop; S.in_loop = loop;
S.labels = labels; S.labels = labels;
return a; return a;
})(S.in_loop, S.labels) }
});
};
function if_() { function if_() {
var cond = parenthesised(), body = statement(), belse = null; var cond = parenthesised(), body = statement(), belse = null;
@ -1096,13 +1198,25 @@ function parse($TEXT, options) {
function vardefs(no_in, in_const) { function vardefs(no_in, in_const) {
var a = []; var a = [];
var def;
for (;;) { for (;;) {
a.push(new AST_VarDef({ 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, start : S.token,
name : as_symbol(in_const ? AST_SymbolConst : AST_SymbolVar), name : as_symbol(sym_type),
value : is("operator", "=") ? (next(), expression(false, no_in)) : null, value : is("operator", "=") ? (next(), expression(false, no_in)) : null,
end : prev() end : prev()
})); })
}
a.push(def);
if (!is("punc", ",")) if (!is("punc", ","))
break; break;
next(); next();
@ -1110,6 +1224,44 @@ function parse($TEXT, options) {
return a; 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) { var var_ = function(no_in) {
return new AST_Var({ return new AST_Var({
start : prev(), 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() { var const_ = function() {
return new AST_Const({ return new AST_Const({
start : prev(), start : prev(),
@ -1191,16 +1351,19 @@ function parse($TEXT, options) {
if (is("punc")) { if (is("punc")) {
switch (start.value) { switch (start.value) {
case "(": case "(":
next(); var ex = params_or_seq_();
var ex = expression(true);
ex.start = start; ex.start = start;
ex.end = S.token; ex.end = S.token;
expect(")"); if (is("arrow", "=>")) {
return subscripts(ex, allow_calls); return arrow_function(ex);
}
return subscripts(ex.as_expr(croak), allow_calls);
case "[": case "[":
return subscripts(array_(), allow_calls); return subscripts(array_(), allow_calls);
case "{": case "{":
return subscripts(object_(), allow_calls); return subscripts(object_or_object_destructuring_(), allow_calls);
case "`":
return subscripts(template_string(), allow_calls);
} }
unexpected(); unexpected();
} }
@ -1217,6 +1380,38 @@ function parse($TEXT, options) {
unexpected(); 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) { function expr_list(closing, allow_trailing_comma, allow_empty) {
var first = true, a = []; var first = true, a = [];
while (!is("punc", closing)) { 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("{"); expect("{");
var first = true, a = [];
while (!is("punc", "}")) { while (!is("punc", "}")) {
if (first) first = false; else expect(","); if (first) first = false; else expect(",");
if (!options.strict && is("punc", "}")) if (!options.strict && is("punc", "}"))
@ -1270,23 +1465,51 @@ function parse($TEXT, options) {
continue; continue;
} }
} }
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(":"); expect(":");
a.push(new AST_ObjectKeyVal({ a.push(new AST_ObjectKeyVal({
start : start, start : start,
quote : start.quote,
key : name, key : name,
value : expression(false), value : expression(false),
end : prev() end : prev()
})); }));
} }
}
next(); next();
return new AST_Object({ properties: a }); return new AST_Object({ properties: a })
}); });
function as_property_name() { function as_property_name() {
var tmp = S.token; var tmp = S.token;
next(); next();
switch (tmp.type) { switch (tmp.type) {
case "punc":
if (tmp.value === "[") {
var ex = expression(false);
expect("]");
return ex;
} else unexpected();
case "num": case "num":
case "string": case "string":
case "name": case "name":
@ -1315,7 +1538,9 @@ function parse($TEXT, options) {
function _make_symbol(type) { function _make_symbol(type) {
var name = S.token.value; 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), name : String(name),
start : S.token, start : S.token,
end : S.token end : S.token
@ -1359,13 +1584,32 @@ function parse($TEXT, options) {
return subscripts(new AST_Call({ return subscripts(new AST_Call({
start : start, start : start,
expression : expr, expression : expr,
args : expr_list(")"), args : call_args(),
end : prev() end : prev()
}), true); }), true);
} }
return expr; 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 maybe_unary = function(allow_calls) {
var start = S.token; var start = S.token;
if (is("operator") && UNARY_PREFIX(start.value)) { if (is("operator") && UNARY_PREFIX(start.value)) {
@ -1435,12 +1679,17 @@ function parse($TEXT, options) {
function is_assignable(expr) { function is_assignable(expr) {
if (!options.strict) return true; if (!options.strict) return true;
if (expr instanceof AST_This) return false; if (expr instanceof AST_This) return false;
if (expr instanceof AST_Super) return false;
return (expr instanceof AST_PropAccess || expr instanceof AST_Symbol); return (expr instanceof AST_PropAccess || expr instanceof AST_Symbol);
}; };
// In ES6, AssignmentExpression can also be an ArrowFunction
var maybe_assign = function(no_in) { var maybe_assign = function(no_in) {
var start = S.token; 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("operator") && ASSIGNMENT(val)) {
if (is_assignable(left)) { if (is_assignable(left)) {
next(); next();
@ -1460,6 +1709,21 @@ function parse($TEXT, options) {
var expression = function(commas, no_in) { var expression = function(commas, no_in) {
var start = S.token; var start = S.token;
var expr = maybe_assign(no_in); 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", ",")) { if (commas && is("punc", ",")) {
next(); next();
return new AST_Seq({ return new AST_Seq({

View File

@ -90,6 +90,9 @@ function mangle_properties(ast, options) {
if (node instanceof AST_ObjectKeyVal) { if (node instanceof AST_ObjectKeyVal) {
add(node.key); add(node.key);
} }
else if (node instanceof AST_ObjectSymbol) {
add(node.symbol.name);
}
else if (node instanceof AST_ObjectProperty) { else if (node instanceof AST_ObjectProperty) {
// setter or getter, since KeyVal is handled above // setter or getter, since KeyVal is handled above
add(node.key.name); add(node.key.name);
@ -111,6 +114,11 @@ function mangle_properties(ast, options) {
if (node instanceof AST_ObjectKeyVal) { if (node instanceof AST_ObjectKeyVal) {
node.key = mangle(node.key); 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) { else if (node instanceof AST_ObjectProperty) {
// setter or getter // setter or getter
node.key.name = mangle(node.key.name); node.key.name = mangle(node.key.name);

View File

@ -50,6 +50,7 @@ function SymbolDef(scope, index, orig) {
this.references = []; this.references = [];
this.global = false; this.global = false;
this.mangled_name = null; this.mangled_name = null;
this.object_destructuring_arg = false;
this.undeclared = false; this.undeclared = false;
this.constant = false; this.constant = false;
this.index = index; this.index = index;
@ -60,6 +61,7 @@ SymbolDef.prototype = {
if (!options) options = {}; if (!options) options = {};
return (this.global && !options.toplevel) return (this.global && !options.toplevel)
|| this.object_destructuring_arg
|| this.undeclared || this.undeclared
|| (!options.eval && (this.scope.uses_eval || this.scope.uses_with)) || (!options.eval && (this.scope.uses_eval || this.scope.uses_with))
|| (options.keep_fnames || (options.keep_fnames
@ -94,6 +96,7 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){
var scope = self.parent_scope = null; var scope = self.parent_scope = null;
var defun = null; var defun = null;
var nesting = 0; var nesting = 0;
var in_destructuring = null;
var tw = new TreeWalker(function(node, descend){ var tw = new TreeWalker(function(node, descend){
if (options.screw_ie8 && node instanceof AST_Catch) { if (options.screw_ie8 && node instanceof AST_Catch) {
var save_scope = scope; var save_scope = scope;
@ -104,6 +107,12 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){
scope = save_scope; scope = save_scope;
return true; 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) { if (node instanceof AST_Scope) {
node.init_scope_vars(nesting); node.init_scope_vars(nesting);
var save_scope = node.parent_scope = scope; var save_scope = node.parent_scope = scope;
@ -131,6 +140,10 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){
if (node instanceof AST_Symbol) { if (node instanceof AST_Symbol) {
node.scope = scope; node.scope = scope;
} }
if (node instanceof AST_SymbolFunarg) {
node.object_destructuring_arg = !!in_destructuring;
defun.def_variable(node);
}
if (node instanceof AST_SymbolLambda) { if (node instanceof AST_SymbolLambda) {
defun.def_function(node); defun.def_function(node);
} }
@ -146,6 +159,7 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){
|| node instanceof AST_SymbolConst) { || node instanceof AST_SymbolConst) {
var def = defun.def_variable(node); var def = defun.def_variable(node);
def.constant = node instanceof AST_SymbolConst; def.constant = node instanceof AST_SymbolConst;
def.destructuring = in_destructuring;
def.init = tw.parent().value; def.init = tw.parent().value;
} }
else if (node instanceof AST_SymbolCatch) { else if (node instanceof AST_SymbolCatch) {
@ -254,6 +268,7 @@ AST_Scope.DEFMETHOD("def_variable", function(symbol){
if (!this.variables.has(symbol.name)) { if (!this.variables.has(symbol.name)) {
def = new SymbolDef(this, this.variables.size(), symbol); def = new SymbolDef(this, this.variables.size(), symbol);
this.variables.set(symbol.name, def); this.variables.set(symbol.name, def);
def.object_destructuring_arg = symbol.object_destructuring_arg;
def.global = !this.parent_scope; def.global = !this.parent_scope;
} else { } else {
def = this.variables.get(symbol.name); def = this.variables.get(symbol.name);
@ -402,7 +417,10 @@ AST_Toplevel.DEFMETHOD("mangle_names", function(options){
} }
}); });
this.walk(tw); 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) { if (options.cache) {
options.cache.cname = this.cname; options.cache.cname = this.cname;
@ -462,6 +480,8 @@ AST_Toplevel.DEFMETHOD("compute_char_frequency", function(options){
base54.consider("new"); base54.consider("new");
else if (node instanceof AST_This) else if (node instanceof AST_This)
base54.consider("this"); base54.consider("this");
else if (node instanceof AST_Super)
base54.consider("super");
else if (node instanceof AST_Try) else if (node instanceof AST_Try)
base54.consider("try"); base54.consider("try");
else if (node instanceof AST_Catch) else if (node instanceof AST_Catch)

View File

@ -163,10 +163,18 @@ TreeTransformer.prototype = new TreeWalker;
if (self.value) self.value = self.value.transform(tw); 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){ _(AST_Lambda, function(self, tw){
if (self.name) self.name = self.name.transform(tw); if (self.name) self.name = self.name.transform(tw);
self.argnames = do_list(self.argnames, tw); self.argnames = do_list(self.argnames, tw);
if (self.body instanceof AST_Node) {
self.body = self.body.transform(tw);
} else {
self.body = do_list(self.body, tw); self.body = do_list(self.body, tw);
}
}); });
_(AST_Call, function(self, tw){ _(AST_Call, function(self, tw){
@ -211,6 +219,10 @@ TreeTransformer.prototype = new TreeWalker;
self.properties = do_list(self.properties, tw); self.properties = do_list(self.properties, tw);
}); });
_(AST_ObjectSymbol, function(self, tw){
self.symbol = self.symbol.transform(tw);
});
_(AST_ObjectProperty, function(self, tw){ _(AST_ObjectProperty, function(self, tw){
self.value = self.value.transform(tw); self.value = self.value.transform(tw);
}); });

View 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;
}
}
}
}

View 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});"
}

View File

@ -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: { keep_fnames: {
options = { unused: true, keep_fnames: true, unsafe: true }; options = { unused: true, keep_fnames: true, unsafe: true };
input: { input: {

View 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
View 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
View 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;
}
}
}

View 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
View 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
View 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();
}

View File

@ -26,6 +26,10 @@ run_ast_conversion_tests({
iterations: 1000 iterations: 1000
}); });
var run_parser_tests = require('./parser.js');
run_parser_tests();
/* -----[ utils ]----- */ /* -----[ utils ]----- */
function tmpl() { function tmpl() {