Destructuring consistency fixes (#1417)

- Use AST_Destructuring for lhf assignment patterns
- Use AST_DefaultAssign for default assignments
- Add more checks for lhs expressions
- Add lots of testing
- Cleanup ast (e.g. remove default property)
- Fix #1402 based on a patch from @kzc
- Refine spread allowance in array destructring pattern
- Add destructuring AST tree checker
This commit is contained in:
Anthony Van de Gejuchte
2017-02-24 01:49:19 +01:00
committed by Alex Lam S.L
parent 85c1cba760
commit 07734b000a
16 changed files with 1253 additions and 141 deletions

View File

@@ -392,46 +392,56 @@ var AST_ArrowParametersOrSeq = DEFNODE("ArrowParametersOrSeq", "expressions", {
// We don't want anything which doesn't belong in a destructuring
var root = this;
return this.expressions.map(function to_fun_args(ex, _, __, default_seen_above) {
var insert_default = function(ex, default_value) {
if (default_value) {
return new AST_DefaultAssign({
start: ex.start,
left: ex,
operator: "=",
right: default_value,
end: default_value.end
});
}
return ex;
}
if (ex instanceof AST_Object) {
return new AST_Destructuring({
return insert_default(new AST_Destructuring({
start: ex.start,
end: ex.end,
is_array: false,
default: default_seen_above,
names: ex.properties.map(to_fun_args)
});
}), default_seen_above);
} else if (ex instanceof AST_ObjectKeyVal) {
if (ex.key instanceof AST_SymbolRef) {
ex.key = to_fun_args(ex.key, 0, [ex.key], ex.default);
ex.key = to_fun_args(ex.key, 0, [ex.key]);
}
ex.value = to_fun_args(ex.value, 0, [ex.key], ex.default);
return ex;
ex.value = to_fun_args(ex.value, 0, [ex.key]);
return insert_default(ex, default_seen_above);
} else if (ex instanceof AST_Hole) {
return ex;
} 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;
return insert_default(ex, default_seen_above);
} else if (ex instanceof AST_SymbolRef) {
return new AST_SymbolFunarg({
return insert_default(new AST_SymbolFunarg({
name: ex.name,
default: default_seen_above,
start: ex.start,
end: ex.end
});
}), default_seen_above);
} else if (ex instanceof AST_Expansion) {
return ex;
ex.expression = to_fun_args(ex.expression);
return insert_default(ex, default_seen_above);
} else if (ex instanceof AST_Array) {
return new AST_Destructuring({
return insert_default(new AST_Destructuring({
start: ex.start,
end: ex.end,
is_array: true,
default: default_seen_above,
names: ex.elements.map(to_fun_args)
});
}), default_seen_above);
} else if (ex instanceof AST_Assign) {
return to_fun_args(ex.left, undefined, undefined, ex.right);
return insert_default(to_fun_args(ex.left, undefined, undefined, ex.right), default_seen_above);
} else {
croak("Invalid function parameter", ex.start.line, ex.start.col);
}
@@ -447,7 +457,7 @@ var AST_Lambda = DEFNODE("Lambda", "name argnames uses_arguments is_generator",
$propdoc: {
is_generator: "[boolean] is generatorFn or not",
name: "[AST_SymbolDeclaration?] the name of this function",
argnames: "[AST_SymbolFunarg|AST_Destructuring|AST_Expansion*] array of function arguments, destructurings, or expanding arguments",
argnames: "[AST_SymbolFunarg|AST_Destructuring|AST_Expansion|AST_DefaultAssign*] array of function arguments, destructurings, or expanding arguments",
uses_arguments: "[boolean/S] tells whether this function accesses the arguments array"
},
args_as_names: function () {
@@ -489,12 +499,11 @@ var AST_Defun = DEFNODE("Defun", null, {
}, AST_Lambda);
/* -----[ DESTRUCTURING ]----- */
var AST_Destructuring = DEFNODE("Destructuring", "names is_array default", {
var AST_Destructuring = DEFNODE("Destructuring", "names is_array", {
$documentation: "A destructuring of several names. Used in destructuring assignment and with destructuring function argument names",
$propdoc: {
"names": "[AST_Destructuring|AST_Expansion|AST_Hole|AST_ObjectKeyVal|AST_Symbol] Array of properties or elements",
"is_array": "[Boolean] Whether the destructuring represents an object or array",
"default": "[AST_Node?] Default assign value"
"names": "[AST_Node*] Array of properties or elements",
"is_array": "[Boolean] Whether the destructuring represents an object or array"
},
_walk: function(visitor) {
return visitor._visit(this, function(){
@@ -672,7 +681,7 @@ var AST_Try = DEFNODE("Try", "bcatch bfinally", {
var AST_Catch = DEFNODE("Catch", "argname", {
$documentation: "A `catch` node; only makes sense as part of a `try` statement",
$propdoc: {
argname: "[AST_SymbolCatch] symbol for the exception"
argname: "[AST_SymbolCatch|AST_Destructuring|AST_Expansion|AST_DefaultAssign] symbol for the exception"
},
_walk: function(visitor) {
return visitor._visit(this, function(){
@@ -957,6 +966,10 @@ var AST_Assign = DEFNODE("Assign", null, {
$documentation: "An assignment expression — `a = b + 5`",
}, AST_Binary);
var AST_DefaultAssign = DEFNODE("DefaultAssign", null, {
$documentation: "A default assignment expression like in `(a = 3) => a`"
}, AST_Binary);
/* -----[ LITERALS ]----- */
var AST_Array = DEFNODE("Array", "elements", {
@@ -991,8 +1004,7 @@ var AST_ObjectProperty = DEFNODE("ObjectProperty", "key value", {
$documentation: "Base class for literal object properties",
$propdoc: {
key: "[string|AST_Node] the property name converted to a string for ObjectKeyVal. For setters, getters and computed property this is an arbitrary AST_Node",
value: "[AST_Node] property value. For setters and getters this is an AST_Function.",
default: "[AST_Expression] The default for this parameter, only used when nested inside a binding pattern"
value: "[AST_Node] property value. For setters and getters this is an AST_Function."
},
_walk: function(visitor) {
return visitor._visit(this, function(){
@@ -1003,11 +1015,10 @@ var AST_ObjectProperty = DEFNODE("ObjectProperty", "key value", {
}
});
var AST_ObjectKeyVal = DEFNODE("ObjectKeyVal", "quote default", {
var AST_ObjectKeyVal = DEFNODE("ObjectKeyVal", "quote", {
$documentation: "A key: value object property",
$propdoc: {
quote: "[string] the original quote character",
default: "[AST_Expression] The default parameter value, only used when nested inside a binding pattern"
quote: "[string] the original quote character"
}
}, AST_ObjectProperty);
@@ -1066,19 +1077,13 @@ var AST_ClassExpression = DEFNODE("ClassExpression", null, {
$documentation: "A class expression."
}, AST_Class);
var AST_Symbol = DEFNODE("Symbol", "scope name thedef default", {
var AST_Symbol = DEFNODE("Symbol", "scope name thedef", {
$propdoc: {
name: "[string] name of this symbol",
scope: "[AST_Scope/S] the current scope (not necessarily the definition scope)",
thedef: "[SymbolDef/S] the definition of this symbol",
default: "[AST_Expression] The default parameter value, only used when nested inside a binding pattern"
thedef: "[SymbolDef/S] the definition of this symbol"
},
$documentation: "Base class for all symbols",
_walk: function (visitor) {
return visitor._visit(this, function() {
if (this.default) this.default._walk(visitor);
});
}
$documentation: "Base class for all symbols"
});
var AST_NewTarget = DEFNODE("NewTarget", null, {