Starting destructuring.
This commit is contained in:
committed by
Richard van Velzen
parent
252fc65558
commit
32f76f7ff8
90
lib/ast.js
90
lib/ast.js
@@ -359,13 +359,73 @@ var AST_Toplevel = DEFNODE("Toplevel", "globals", {
|
||||
}
|
||||
}, AST_Scope);
|
||||
|
||||
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*] 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_SymbolRef) {
|
||||
return new AST_SymbolFunarg({
|
||||
name: ex.name,
|
||||
start: ex.start,
|
||||
end: ex.end
|
||||
});
|
||||
} 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 {
|
||||
console.log(ex.__proto__.TYPE)
|
||||
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*] array of function arguments or destructurings",
|
||||
uses_arguments: "[boolean/S] tells whether this function accesses the arguments array"
|
||||
},
|
||||
args_as_names: function () {
|
||||
var out = [];
|
||||
this.walk(new TreeWalker(function (parm) {
|
||||
var that = this;
|
||||
if (parm instanceof AST_SymbolFunarg) {
|
||||
out.push(parm);
|
||||
}
|
||||
}));
|
||||
return out;
|
||||
},
|
||||
_walk: function(visitor) {
|
||||
return visitor._visit(this, function(){
|
||||
if (this.name) this.name._walk(visitor);
|
||||
@@ -385,10 +445,26 @@ 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);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/* -----[ JUMPS ]----- */
|
||||
|
||||
var AST_Jump = DEFNODE("Jump", null, {
|
||||
@@ -774,6 +850,18 @@ var AST_ObjectKeyVal = DEFNODE("ObjectKeyVal", "quote", {
|
||||
}
|
||||
}, 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);
|
||||
|
||||
@@ -231,6 +231,7 @@ merge(Compressor.prototype, {
|
||||
}
|
||||
function make_arguments_names_list(func) {
|
||||
return func.argnames.map(function(sym){
|
||||
// TODO not sure what to do here with destructuring
|
||||
return make_node(AST_String, sym, { value: sym.name });
|
||||
});
|
||||
}
|
||||
@@ -1089,17 +1090,26 @@ merge(Compressor.prototype, {
|
||||
if (node instanceof AST_Lambda && !(node instanceof AST_Accessor)) {
|
||||
if (compressor.option("unsafe") && !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.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1263,9 +1273,10 @@ merge(Compressor.prototype, {
|
||||
// collect only vars which don't show up in self's arguments list
|
||||
var defs = [];
|
||||
vars.each(function(def, name){
|
||||
// TODO test this too
|
||||
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();
|
||||
@@ -1785,6 +1796,7 @@ merge(Compressor.prototype, {
|
||||
if (ex !== ast) throw ex;
|
||||
};
|
||||
if (!fun) return self;
|
||||
// TODO does this work with destructuring? Test it.
|
||||
var args = fun.argnames.map(function(arg, i){
|
||||
return make_node(AST_String, self.args[i], {
|
||||
value: arg.print_to_string()
|
||||
|
||||
@@ -598,6 +598,17 @@ function OutputStream(options) {
|
||||
output.print_string(self.value, self.quote);
|
||||
output.semicolon();
|
||||
});
|
||||
|
||||
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();
|
||||
|
||||
102
lib/parse.js
102
lib/parse.js
@@ -644,6 +644,7 @@ function parse($TEXT, options) {
|
||||
prev : null,
|
||||
peeked : null,
|
||||
in_function : 0,
|
||||
in_parameters : false,
|
||||
in_directives : true,
|
||||
in_loop : 0,
|
||||
labels : []
|
||||
@@ -957,35 +958,58 @@ function parse($TEXT, options) {
|
||||
};
|
||||
|
||||
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();
|
||||
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 = [];
|
||||
S.in_parameters = true;
|
||||
while (!is("punc", ")")) {
|
||||
if (first) first = false; else expect(",");
|
||||
a.push(expression(false));
|
||||
}
|
||||
S.in_parameters = false;
|
||||
var end = S.token
|
||||
next();
|
||||
return new AST_ArrowParametersOrSeq({
|
||||
start: start,
|
||||
end: end,
|
||||
expressions: a
|
||||
});
|
||||
}
|
||||
|
||||
function _function_body() {
|
||||
var loop = S.in_loop;
|
||||
var labels = S.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;
|
||||
}
|
||||
|
||||
function if_() {
|
||||
var cond = parenthesised(), body = statement(), belse = null;
|
||||
if (is("keyword", "else")) {
|
||||
@@ -1224,6 +1248,7 @@ function parse($TEXT, options) {
|
||||
});
|
||||
|
||||
var object_ = embed_tokens(function() {
|
||||
var start = S.token;
|
||||
expect("{");
|
||||
var first = true, a = [];
|
||||
while (!is("punc", "}")) {
|
||||
@@ -1254,14 +1279,33 @@ function parse($TEXT, options) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
expect(":");
|
||||
a.push(new AST_ObjectKeyVal({
|
||||
start : start,
|
||||
quote : start.quote,
|
||||
key : name,
|
||||
value : expression(false),
|
||||
end : prev()
|
||||
}));
|
||||
|
||||
if (!is("punc", ":")) {
|
||||
// It's one of those object destructurings, the value is its own name
|
||||
if (!S.in_parameters) {
|
||||
croak("Invalid syntax", S.token.line, S.token.col);
|
||||
}
|
||||
a.push(new AST_ObjectSymbol({
|
||||
start: start,
|
||||
end: start,
|
||||
symbol: new AST_SymbolRef({
|
||||
start: start,
|
||||
end: start,
|
||||
name: name
|
||||
})
|
||||
}));
|
||||
} else {
|
||||
if (S.in_parameters) {
|
||||
croak("Cannot destructure", S.token.line, S.token.col);
|
||||
}
|
||||
expect(":");
|
||||
a.push(new AST_ObjectKeyVal({
|
||||
start : start,
|
||||
key : name,
|
||||
value : expression(false),
|
||||
end : prev()
|
||||
}));
|
||||
}
|
||||
}
|
||||
next();
|
||||
return new AST_Object({ properties: a });
|
||||
|
||||
14
lib/scope.js
14
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 object_destructuring_arg = false;
|
||||
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) {
|
||||
object_destructuring_arg = true; // These don't nest
|
||||
descend();
|
||||
object_destructuring_arg = false;
|
||||
return true;
|
||||
}
|
||||
if (node instanceof AST_Scope) {
|
||||
node.init_scope_vars(nesting);
|
||||
var save_scope = node.parent_scope = scope;
|
||||
@@ -127,6 +136,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 = object_destructuring_arg;
|
||||
defun.def_variable(node);
|
||||
}
|
||||
if (node instanceof AST_SymbolLambda) {
|
||||
defun.def_function(node);
|
||||
}
|
||||
@@ -250,6 +263,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);
|
||||
|
||||
103
test/parser.js
Normal file
103
test/parser.js
Normal file
@@ -0,0 +1,103 @@
|
||||
|
||||
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 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] { })');
|
||||
});
|
||||
|
||||
ok.throws(function () {
|
||||
// Note: this *is* a valid destructuring, but before we implement
|
||||
// destructuring (right now it's only destructuring *arguments*),
|
||||
// this won't do.
|
||||
UglifyJS.parse('[{a}]');
|
||||
});
|
||||
}
|
||||
|
||||
// Run standalone
|
||||
if (module.parent === null) {
|
||||
module.exports();
|
||||
}
|
||||
|
||||
@@ -23,6 +23,10 @@ run_ast_conversion_tests({
|
||||
iterations: 1000
|
||||
});
|
||||
|
||||
var run_parser_tests = require('./parser.js');
|
||||
|
||||
run_parser_tests();
|
||||
|
||||
/* -----[ utils ]----- */
|
||||
|
||||
function tmpl() {
|
||||
|
||||
Reference in New Issue
Block a user