Improve support for binding pattern
Including improvements for parameters, variable assignment and catch parameter.
This commit is contained in:
@@ -989,7 +989,8 @@ 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."
|
||||
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"
|
||||
},
|
||||
_walk: function(visitor) {
|
||||
return visitor._visit(this, function(){
|
||||
|
||||
@@ -595,6 +595,10 @@ function OutputStream(options) {
|
||||
|| p instanceof AST_PropAccess // (1, {foo:2}).foo or (1, {foo:2})["foo"] ==> 2
|
||||
|| p instanceof AST_Array // [ 1, (2, 3), 4 ] ==> [ 1, 3, 4 ]
|
||||
|| p instanceof AST_ObjectProperty // { foo: (1, 2) }.foo ==> 2
|
||||
|| (p instanceof AST_SymbolConst && p.default === this) // const { xCover = (0, function() {}) }
|
||||
|| (p instanceof AST_SymbolLet && p.default === this) // let { xCover = (0, function() {}) }
|
||||
|| (p instanceof AST_SymbolVar && p.default === this) // var { xCover = (0, function() {}) }
|
||||
|| (p instanceof AST_SymbolCatch && p.default === this) // } catch (xCover = (0, function() {}) ) {
|
||||
|| p instanceof AST_Conditional /* (false, true) ? (a = 10, b = 20) : (c = 30)
|
||||
* ==> 20 (side effect, set a := 10 and b := 20) */
|
||||
|| p instanceof AST_Arrow // x => (x, x)
|
||||
@@ -1451,6 +1455,12 @@ function OutputStream(options) {
|
||||
self.print_property_name(self.key, self.quote, output);
|
||||
output.colon();
|
||||
self.value.print(output);
|
||||
if (self.default) {
|
||||
output.space();
|
||||
output.print('=');
|
||||
output.space();
|
||||
self.default.print(output);
|
||||
}
|
||||
});
|
||||
AST_ObjectProperty.DEFMETHOD("_print_getter_setter", function(type, self, output) {
|
||||
if (self.static) {
|
||||
@@ -1498,6 +1508,12 @@ function OutputStream(options) {
|
||||
output.print("]:");
|
||||
output.space();
|
||||
self.value.print(output);
|
||||
if (self.default) {
|
||||
output.space();
|
||||
output.print('=');
|
||||
output.space();
|
||||
self.default.print(output);
|
||||
}
|
||||
});
|
||||
AST_Symbol.DEFMETHOD("_do_print", function(output){
|
||||
var def = this.definition();
|
||||
|
||||
320
lib/parse.js
320
lib/parse.js
@@ -46,7 +46,7 @@
|
||||
|
||||
var KEYWORDS = 'break case catch class const continue debugger default delete do else export extends finally for function if in instanceof new return switch throw try typeof var let void while with import';
|
||||
var KEYWORDS_ATOM = 'false null true';
|
||||
var RESERVED_WORDS = 'enum implements interface package private protected public static super this' + KEYWORDS_ATOM + " " + KEYWORDS;
|
||||
var RESERVED_WORDS = 'enum implements interface package private protected public static super this ' + KEYWORDS_ATOM + " " + KEYWORDS;
|
||||
var KEYWORDS_BEFORE_EXPRESSION = 'return new delete throw else case yield';
|
||||
|
||||
KEYWORDS = makePredicate(KEYWORDS);
|
||||
@@ -1266,7 +1266,7 @@ function parse($TEXT, options) {
|
||||
if (in_statement && !name)
|
||||
unexpected();
|
||||
|
||||
var args = params_or_seq_().as_params(croak);
|
||||
var args = parameters();
|
||||
var body = _function_body(true, is_generator || is_generator_property);
|
||||
return new ctor({
|
||||
start : args.start,
|
||||
@@ -1278,6 +1278,276 @@ function parse($TEXT, options) {
|
||||
});
|
||||
};
|
||||
|
||||
function track_used_binding_identifiers(is_parameter, strict) {
|
||||
var parameters = {};
|
||||
var duplicate = false;
|
||||
var default_assignment = false;
|
||||
var spread = false;
|
||||
var strict_mode = !!strict;
|
||||
var tracker = {
|
||||
add_parameter: function(token) {
|
||||
if (parameters["$" + token.value] !== undefined) {
|
||||
if (duplicate === false) {
|
||||
duplicate = token;
|
||||
}
|
||||
tracker.check_strict();
|
||||
} else {
|
||||
parameters["$" + token.value] = true;
|
||||
if (is_parameter) {
|
||||
switch (token.value) {
|
||||
case "arguments":
|
||||
case "eval":
|
||||
case "yield":
|
||||
if (strict_mode) {
|
||||
token_error(token, "SyntaxError: Unexpected " + token.value + " identifier as parameter inside strict mode");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (RESERVED_WORDS(token.value)) {
|
||||
unexpected();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
mark_default_assignment: function(token) {
|
||||
if (default_assignment === false) {
|
||||
default_assignment = token;
|
||||
}
|
||||
},
|
||||
mark_spread: function(token) {
|
||||
if (spread === false) {
|
||||
spread = token;
|
||||
}
|
||||
},
|
||||
mark_strict_mode: function() {
|
||||
strict_mode = true;
|
||||
},
|
||||
is_strict: function() {
|
||||
return default_assignment !== false || spread !== false || strict_mode
|
||||
},
|
||||
check_strict: function() {
|
||||
if (tracker.is_strict() && duplicate !== false) {
|
||||
token_error(duplicate, "SyntaxError: Parameter " + duplicate.value + " was used already");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return tracker;
|
||||
}
|
||||
|
||||
function parameters() {
|
||||
var start = S.token;
|
||||
var first = true;
|
||||
var params = [];
|
||||
var used_parameters = track_used_binding_identifiers(true, S.input.has_directive("use strict"));
|
||||
|
||||
expect("(");
|
||||
|
||||
while (!is("punc", ")")) {
|
||||
if (first) {
|
||||
first = false;
|
||||
} else {
|
||||
expect(",");
|
||||
}
|
||||
|
||||
var param = parameter(used_parameters);
|
||||
params.push(param);
|
||||
|
||||
if (param instanceof AST_Expansion) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
next();
|
||||
return params;
|
||||
}
|
||||
|
||||
function parameter(used_parameters, symbol_type) {
|
||||
var param;
|
||||
var expand = false;
|
||||
if (used_parameters === undefined) {
|
||||
used_parameters = track_used_binding_identifiers(true, S.input.has_directive("use strict"));
|
||||
}
|
||||
if (is("expand", "...")) {
|
||||
expand = S.token;
|
||||
used_parameters.mark_spread(S.token);
|
||||
next();
|
||||
}
|
||||
param = binding_element(used_parameters, symbol_type);
|
||||
|
||||
if (is("operator", "=") && expand === false) {
|
||||
used_parameters.mark_default_assignment(S.token);
|
||||
next();
|
||||
param.default = expression(false);
|
||||
}
|
||||
|
||||
if (expand !== false) {
|
||||
if (!is("punc", ")")) {
|
||||
unexpected();
|
||||
}
|
||||
param = new AST_Expansion({
|
||||
start: expand,
|
||||
expression: param,
|
||||
end: expand
|
||||
});
|
||||
}
|
||||
used_parameters.check_strict();
|
||||
|
||||
return param;
|
||||
}
|
||||
|
||||
function binding_element(used_parameters, symbol_type) {
|
||||
var elements = [];
|
||||
var first = true;
|
||||
var is_expand = false;
|
||||
var expand_token;
|
||||
var first_token = S.token;
|
||||
if (used_parameters === undefined) {
|
||||
used_parameters = track_used_binding_identifiers(false, S.input.has_directive("use strict"));
|
||||
}
|
||||
symbol_type = symbol_type === undefined ? AST_SymbolFunarg : symbol_type;
|
||||
if (is("punc", "[")) {
|
||||
next();
|
||||
while (!is("punc", "]")) {
|
||||
if (first) {
|
||||
first = false;
|
||||
} else {
|
||||
expect(",");
|
||||
}
|
||||
|
||||
if (is("expand", "...")) {
|
||||
is_expand = true;
|
||||
expand_token = S.token;
|
||||
used_parameters.mark_spread(S.token);
|
||||
next();
|
||||
}
|
||||
if (is("punc")) {
|
||||
switch (S.token.value) {
|
||||
case ",":
|
||||
case "]": // Last element
|
||||
elements.push(new AST_Hole({
|
||||
start: S.token,
|
||||
end: S.token
|
||||
}));
|
||||
continue;
|
||||
case "[":
|
||||
case "{":
|
||||
elements.push(binding_element(used_parameters, symbol_type));
|
||||
break;
|
||||
default:
|
||||
unexpected();
|
||||
}
|
||||
} else if (is("name")) {
|
||||
used_parameters.add_parameter(S.token);
|
||||
elements.push(new symbol_type({
|
||||
start: S.token,
|
||||
name: S.token.value,
|
||||
end: S.token
|
||||
}));
|
||||
next();
|
||||
} else {
|
||||
croak("SyntaxError: Invalid function parameter");
|
||||
}
|
||||
if (is("operator", "=") && is_expand === false) {
|
||||
used_parameters.mark_default_assignment(S.token);
|
||||
next();
|
||||
elements[elements.length - 1].default = expression(false);
|
||||
}
|
||||
if (is_expand) {
|
||||
if (!is("punc", "]")) {
|
||||
unexpected(); // Must be last element
|
||||
}
|
||||
elements[elements.length - 1] = new AST_Expansion({
|
||||
start: expand_token,
|
||||
expression: elements[elements.length - 1],
|
||||
end: expand_token
|
||||
});
|
||||
}
|
||||
}
|
||||
expect("]");
|
||||
used_parameters.check_strict();
|
||||
return new AST_Destructuring({
|
||||
start: first_token,
|
||||
names: elements,
|
||||
is_array: true,
|
||||
end: prev()
|
||||
});
|
||||
} else if (is("punc", "{")) {
|
||||
next();
|
||||
while (!is("punc", "}")) {
|
||||
if (first) {
|
||||
first = false;
|
||||
} else {
|
||||
expect(",");
|
||||
}
|
||||
if (is("name") && (is_token(peek(), "punc") || is_token(peek(), "operator")) && [",", "}", "="].indexOf(peek().value) !== -1) {
|
||||
used_parameters.add_parameter(S.token);
|
||||
elements.push(new symbol_type({
|
||||
start: S.token,
|
||||
name: S.token.value,
|
||||
end: S.token
|
||||
}));
|
||||
next();
|
||||
} else if (is("punc", "}")) {
|
||||
continue; // Allow trailing hole
|
||||
} else {
|
||||
var property_token = S.token;
|
||||
var property = as_property_name();
|
||||
if (property === null) {
|
||||
unexpected(prev());
|
||||
} else if (prev().type === "name" && !is("punc", ":")) {
|
||||
elements.push(new AST_SymbolFunarg({
|
||||
start: prev(),
|
||||
name: property_token,
|
||||
end: prev()
|
||||
}));
|
||||
} else if (property instanceof AST_Node) {
|
||||
expect(":");
|
||||
elements.push(new AST_ObjectComputedKeyVal({
|
||||
start: property_token,
|
||||
key: property,
|
||||
value: binding_element(used_parameters, symbol_type),
|
||||
end: prev()
|
||||
}));
|
||||
} else {
|
||||
expect(":");
|
||||
elements.push(new AST_ObjectKeyVal({
|
||||
start: property_token,
|
||||
quote: property_token.quote,
|
||||
end: prev(),
|
||||
key: property,
|
||||
value: binding_element(used_parameters, symbol_type)
|
||||
}));
|
||||
}
|
||||
}
|
||||
if (is("operator", "=")) {
|
||||
used_parameters.mark_default_assignment(S.token);
|
||||
next();
|
||||
elements[elements.length - 1].default = expression(false);
|
||||
}
|
||||
}
|
||||
expect("}");
|
||||
used_parameters.check_strict();
|
||||
return new AST_Destructuring({
|
||||
start: first_token,
|
||||
names: elements,
|
||||
is_array: false,
|
||||
end: prev()
|
||||
});
|
||||
} else if (is("name")) {
|
||||
used_parameters.add_parameter(S.token);
|
||||
next();
|
||||
return new symbol_type({
|
||||
start: prev(),
|
||||
name: prev().value,
|
||||
end: prev()
|
||||
});
|
||||
} else {
|
||||
croak("SyntaxError: Invalid function parameter");
|
||||
}
|
||||
}
|
||||
|
||||
function params_or_seq_() {
|
||||
var start = S.token
|
||||
expect("(");
|
||||
@@ -1434,7 +1704,7 @@ function parse($TEXT, options) {
|
||||
var start = S.token;
|
||||
next();
|
||||
expect("(");
|
||||
var name = as_symbol(AST_SymbolCatch);
|
||||
var name = parameter(undefined, AST_SymbolCatch);
|
||||
expect(")");
|
||||
bcatch = new AST_Catch({
|
||||
start : start,
|
||||
@@ -1472,7 +1742,7 @@ function parse($TEXT, options) {
|
||||
if (is("punc", "{") || is("punc", "[")) {
|
||||
def = new AST_VarDef({
|
||||
start: S.token,
|
||||
name: destructuring_(sym_type),
|
||||
name: binding_element(undefined ,sym_type),
|
||||
value: is("operator", "=") ? (expect_token("operator", "="), expression(false, no_in)) : null,
|
||||
end: prev()
|
||||
});
|
||||
@@ -1492,48 +1762,6 @@ 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(),
|
||||
expression: symbol,
|
||||
end: S.token
|
||||
}));
|
||||
next();
|
||||
} else if (is("name")) {
|
||||
children.push(new (sym_type)({
|
||||
name : String(S.token.value),
|
||||
start : S.token,
|
||||
default: (next(), is("operator", "=")) ? (next(), expression(false)) : undefined,
|
||||
end : S.token
|
||||
}));
|
||||
} 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(),
|
||||
|
||||
@@ -1,27 +1,75 @@
|
||||
|
||||
destructuring_arrays: {
|
||||
input: {
|
||||
{const [aa, bb] = cc;}
|
||||
{const [aa, [bb, cc]] = dd;}
|
||||
{let [aa, bb] = cc;}
|
||||
{let [aa, [bb, cc]] = dd;}
|
||||
var [aa, bb] = cc;
|
||||
var [aa, [bb, cc]] = dd;
|
||||
var [,[,,,,,],,,zz,] = xx;
|
||||
}
|
||||
expect: {
|
||||
var[aa,bb]=cc;
|
||||
{const [aa, bb] = cc;}
|
||||
{const [aa, [bb, cc]] = dd;}
|
||||
{let [aa, bb] = cc;}
|
||||
{let [aa, [bb, cc]] = dd;}
|
||||
var [aa, bb] = cc;
|
||||
var [aa, [bb, cc]] = dd;
|
||||
var [,[,,,,,],,,zz,] = xx;
|
||||
}
|
||||
}
|
||||
|
||||
destructuring_objects: {
|
||||
input: {
|
||||
{const {aa, bb} = {aa:1, bb:2};}
|
||||
{const {aa, bb: {cc, dd}} = {aa:1, bb: {cc:2, dd: 3}};}
|
||||
{let {aa, bb} = {aa:1, bb:2};}
|
||||
{let {aa, bb: {cc, dd}} = {aa:1, bb: {cc:2, dd: 3}};}
|
||||
var {aa, bb} = {aa:1, bb:2};
|
||||
var {aa, bb: {cc, dd}} = {aa:1, bb: {cc:2, dd: 3}};
|
||||
}
|
||||
expect: {
|
||||
var{aa,bb}={aa:1,bb:2};
|
||||
{const {aa, bb} = {aa:1, bb:2};}
|
||||
{const {aa, bb: {cc, dd}} = {aa:1, bb: {cc:2, dd: 3}};}
|
||||
{let {aa, bb} = {aa:1, bb:2};}
|
||||
{let {aa, bb: {cc, dd}} = {aa:1, bb: {cc:2, dd: 3}};}
|
||||
var {aa, bb} = {aa:1, bb:2};
|
||||
var {aa, bb: {cc, dd}} = {aa:1, bb: {cc:2, dd: 3}};
|
||||
}
|
||||
}
|
||||
|
||||
destructuring_objects_trailing_elision: {
|
||||
input: {
|
||||
var {cc,} = foo;
|
||||
}
|
||||
expect_exact: "var{cc}=foo;"
|
||||
}
|
||||
|
||||
nested_destructuring_objects: {
|
||||
input: {
|
||||
const [{a},b] = c;
|
||||
let [{a},b] = c;
|
||||
var [{a},b] = c;
|
||||
}
|
||||
expect_exact: 'var[{a},b]=c;';
|
||||
expect_exact: 'const[{a},b]=c;let[{a},b]=c;var[{a},b]=c;';
|
||||
}
|
||||
|
||||
destructuring_constdef_in_loops: {
|
||||
input: {
|
||||
for (const [x,y] in pairs);
|
||||
for (const [a] = 0;;);
|
||||
for (const {c} of cees);
|
||||
}
|
||||
expect_exact: "for(const[x,y]in pairs);for(const[a]=0;;);for(const{c}of cees);"
|
||||
}
|
||||
|
||||
destructuring_letdef_in_loops: {
|
||||
input: {
|
||||
for (let [x,y] in pairs);
|
||||
for (let [a] = 0;;);
|
||||
for (let {c} of cees);
|
||||
}
|
||||
expect_exact: "for(let[x,y]in pairs);for(let[a]=0;;);for(let{c}of cees);"
|
||||
}
|
||||
|
||||
destructuring_vardef_in_loops: {
|
||||
@@ -32,6 +80,7 @@ destructuring_vardef_in_loops: {
|
||||
}
|
||||
expect_exact: "for(var[x,y]in pairs);for(var[a]=0;;);for(var{c}of cees);"
|
||||
}
|
||||
|
||||
destructuring_expressions: {
|
||||
input: {
|
||||
({a, b});
|
||||
|
||||
@@ -1,17 +1,3 @@
|
||||
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 && (() => {});
|
||||
@@ -25,28 +11,6 @@ arrow_function_parens_2: {
|
||||
expect_exact: "(()=>null)();"
|
||||
}
|
||||
|
||||
regression_arrow_functions_and_hoist: {
|
||||
options = {
|
||||
hoist_vars: true,
|
||||
hoist_funs: true
|
||||
}
|
||||
input: {
|
||||
(a) => b;
|
||||
}
|
||||
expect_exact: "a=>b;"
|
||||
}
|
||||
|
||||
regression_assign_arrow_functions: {
|
||||
input: {
|
||||
oninstall = e => false;
|
||||
oninstall = () => false;
|
||||
}
|
||||
expect: {
|
||||
oninstall=e=>false;
|
||||
oninstall=()=>false;
|
||||
}
|
||||
}
|
||||
|
||||
typeof_arrow_functions: {
|
||||
options = {
|
||||
evaluate: true
|
||||
@@ -57,86 +21,6 @@ typeof_arrow_functions: {
|
||||
expect_exact: "var foo=\"function\";"
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
default_arguments: {
|
||||
input: {
|
||||
function x(a = 6) { }
|
||||
function x(a = (6 + 5)) { }
|
||||
function x({ foo } = {}, [ bar ] = [ 1 ]) { }
|
||||
}
|
||||
expect_exact: "function x(a=6){}function x(a=6+5){}function x({foo}={},[bar]=[1]){}"
|
||||
}
|
||||
|
||||
default_values_in_destructurings: {
|
||||
input: {
|
||||
function x({a=(4), b}) {}
|
||||
function x([b, c=(12)]) {}
|
||||
var { x = (6), y } = x;
|
||||
var [ x, y = (6) ] = x;
|
||||
}
|
||||
expect_exact: "function x({a=4,b}){}function x([b,c=12]){}var{x=6,y}=x;var[x,y=6]=x;"
|
||||
}
|
||||
|
||||
classes: {
|
||||
input: {
|
||||
class SomeClass {
|
||||
|
||||
159
test/compress/parameters.js
Normal file
159
test/compress/parameters.js
Normal file
@@ -0,0 +1,159 @@
|
||||
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\";"
|
||||
}
|
||||
|
||||
regression_arrow_functions_and_hoist: {
|
||||
options = {
|
||||
hoist_vars: true,
|
||||
hoist_funs: true
|
||||
}
|
||||
input: {
|
||||
(a) => b;
|
||||
}
|
||||
expect_exact: "a=>b;"
|
||||
}
|
||||
|
||||
regression_assign_arrow_functions: {
|
||||
input: {
|
||||
oninstall = e => false;
|
||||
oninstall = () => false;
|
||||
}
|
||||
expect: {
|
||||
oninstall=e=>false;
|
||||
oninstall=()=>false;
|
||||
}
|
||||
}
|
||||
|
||||
destructuring_arguments_1: {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
destructuring_arguments_2: {
|
||||
input: {
|
||||
(function([]) {});
|
||||
(function({}) {});
|
||||
(function([,,,,,]) {});
|
||||
(function ([a, {b: c}]) {});
|
||||
(function ([...args]) {});
|
||||
(function ({x,}) {});
|
||||
class a { *method({ [thrower()]: x } = {}) {}};
|
||||
(function(a, b, c, d, [{e: [...f]}]){})(1, 2, 3, 4, [{e: [1, 2, 3]}]);
|
||||
}
|
||||
expect: {
|
||||
(function([]) {});
|
||||
(function({}) {});
|
||||
(function([,,,,,]) {});
|
||||
(function ([a, {b: c}]) {});
|
||||
(function ([...args]) {});
|
||||
(function ({x,}) {});
|
||||
class a { *method({ [thrower()]: x } = {}) {}};
|
||||
(function(a, b, c, d, [{e: [...f]}]){})(1, 2, 3, 4, [{e: [1, 2, 3]}]);
|
||||
}
|
||||
}
|
||||
|
||||
destructuring_arguments_3: {
|
||||
input: {
|
||||
function fn3({x: {y: {z: {} = 42}}}) {}
|
||||
const { cover = (function () {}), xCover = (0, function() {}) } = {};
|
||||
let { cover = (function () {}), xCover = (0, function() {}) } = {};
|
||||
var { cover = (function () {}), xCover = (0, function() {}) } = {};
|
||||
}
|
||||
expect_exact: "function fn3({x:{y:{z:{}=42}}}){}const{cover=function(){},xCover=(0,function(){})}={};let{cover=function(){},xCover=(0,function(){})}={};var{cover=function(){},xCover=(0,function(){})}={};"
|
||||
}
|
||||
|
||||
default_arguments: {
|
||||
input: {
|
||||
function x(a = 6) { }
|
||||
function x(a = (6 + 5)) { }
|
||||
function x({ foo } = {}, [ bar ] = [ 1 ]) { }
|
||||
}
|
||||
expect_exact: "function x(a=6){}function x(a=6+5){}function x({foo}={},[bar]=[1]){}"
|
||||
}
|
||||
|
||||
default_values_in_destructurings: {
|
||||
input: {
|
||||
function x({a=(4), b}) {}
|
||||
function x([b, c=(12)]) {}
|
||||
var { x = (6), y } = x;
|
||||
var [ x, y = (6) ] = x;
|
||||
}
|
||||
expect_exact: "function x({a=4,b}){}function x([b,c=12]){}var{x=6,y}=x;var[x,y=6]=x;"
|
||||
}
|
||||
|
||||
accept_duplicated_parameters_in_non_strict_without_spread_or_default_assignment: {
|
||||
input: {
|
||||
function a(b, b){}
|
||||
function b({c: test, c: test}){}
|
||||
}
|
||||
expect: {
|
||||
function a(b, b){}
|
||||
function b({c: test, c: test}){}
|
||||
}
|
||||
}
|
||||
9
test/compress/try-catch.js
Normal file
9
test/compress/try-catch.js
Normal file
@@ -0,0 +1,9 @@
|
||||
catch_destructuring_with_sequence: {
|
||||
input: {
|
||||
try {
|
||||
throw {};
|
||||
} catch ({xCover = (0, function() {})} ) {
|
||||
}
|
||||
}
|
||||
expect_exact: "try{throw{}}catch({xCover=(0,function(){})}){}"
|
||||
}
|
||||
@@ -16,7 +16,7 @@ describe("Class", function() {
|
||||
}
|
||||
var error = function(e) {
|
||||
return e instanceof uglify.JS_Parse_Error &&
|
||||
e.message === "SyntaxError: Unexpected token: expand (...)";
|
||||
e.message.substr(0, 31) === "SyntaxError: Unexpected token: ";
|
||||
}
|
||||
|
||||
for (var i = 0; i < tests.length; i++) {
|
||||
|
||||
@@ -2,6 +2,101 @@ var assert = require("assert");
|
||||
var uglify = require("../../");
|
||||
|
||||
describe("Function", function() {
|
||||
it ("Should parse binding patterns correctly", function() {
|
||||
// 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 = uglify.parse('(function ({a, b}) {})').body[0].body;
|
||||
var destr_fun2 = uglify.parse('(function ([a, [b]]) {})').body[0].body;
|
||||
|
||||
assert.equal(destr_fun1.argnames.length, 1);
|
||||
assert.equal(destr_fun2.argnames.length, 1);
|
||||
|
||||
var destr_fun1 = uglify.parse('({a, b}) => null').body[0].body;
|
||||
var destr_fun2 = uglify.parse('([a, [b]]) => null').body[0].body;
|
||||
|
||||
assert.equal(destr_fun1.argnames.length, 1);
|
||||
assert.equal(destr_fun2.argnames.length, 1);
|
||||
|
||||
var destruct1 = destr_fun1.argnames[0];
|
||||
var destruct2 = destr_fun2.argnames[0];
|
||||
|
||||
assert(destruct1 instanceof uglify.AST_Destructuring);
|
||||
assert(destruct2 instanceof uglify.AST_Destructuring);
|
||||
assert(destruct2.names[1] instanceof uglify.AST_Destructuring);
|
||||
|
||||
assert.equal(destruct1.start.value, '{');
|
||||
assert.equal(destruct1.end.value, '}');
|
||||
assert.equal(destruct2.start.value, '[');
|
||||
assert.equal(destruct2.end.value, ']');
|
||||
|
||||
assert.equal(destruct1.is_array, false);
|
||||
assert.equal(destruct2.is_array, true);
|
||||
|
||||
var aAndB = [
|
||||
['SymbolFunarg', 'a'],
|
||||
['SymbolFunarg', 'b']
|
||||
];
|
||||
|
||||
assert.deepEqual(
|
||||
[
|
||||
destruct1.names[0].TYPE,
|
||||
destruct1.names[0].name],
|
||||
aAndB[0]);
|
||||
|
||||
assert.deepEqual(
|
||||
[
|
||||
destruct2.names[1].names[0].TYPE,
|
||||
destruct2.names[1].names[0].name
|
||||
],
|
||||
aAndB[1]);
|
||||
|
||||
assert.deepEqual(
|
||||
get_args(destr_fun1.args_as_names()),
|
||||
aAndB);
|
||||
assert.deepEqual(
|
||||
get_args(destr_fun2.args_as_names()),
|
||||
aAndB);
|
||||
|
||||
// Making sure we don't accidentally accept things which
|
||||
// Aren't argument destructurings
|
||||
|
||||
assert.throws(function () {
|
||||
uglify.parse('(function ( { a, [ b ] } ) { })')
|
||||
});
|
||||
|
||||
assert.throws(function () {
|
||||
uglify.parse('(function (1) { })');
|
||||
}, /Invalid function parameter/);
|
||||
|
||||
assert.throws(function () {
|
||||
uglify.parse('(function (this) { })');
|
||||
});
|
||||
|
||||
assert.throws(function () {
|
||||
uglify.parse('(function ([1]) { })');
|
||||
}, /Invalid function parameter/);
|
||||
|
||||
assert.throws(function () {
|
||||
uglify.parse('(function [a] { })');
|
||||
});
|
||||
|
||||
// generators
|
||||
var generators_def = uglify.parse('function* fn() {}').body[0];
|
||||
assert.equal(generators_def.is_generator, true);
|
||||
|
||||
assert.throws(function () {
|
||||
uglify.parse('function* (){ }');
|
||||
});
|
||||
|
||||
var generators_yield_def = uglify.parse('function* fn() {\nyield remote();\}').body[0].body[0];
|
||||
assert.strictEqual(generators_yield_def.body.is_star, false);
|
||||
});
|
||||
it("Should not accept spread on non-last parameters", function() {
|
||||
var tests = [
|
||||
"var a = function(...a, b) { return a.join(b) }",
|
||||
@@ -20,11 +115,76 @@ describe("Function", function() {
|
||||
}
|
||||
var error = function(e) {
|
||||
return e instanceof uglify.JS_Parse_Error &&
|
||||
e.message === "SyntaxError: Unexpected token: expand (...)";
|
||||
e.message.substr(0, 31) === "SyntaxError: Unexpected token: ";
|
||||
}
|
||||
|
||||
for (var i = 0; i < tests.length; i++) {
|
||||
assert.throws(test(tests[i]), error);
|
||||
}
|
||||
});
|
||||
it("Should not accept empty parameters after elision", function() {
|
||||
var tests = [
|
||||
"(function(,){})()",
|
||||
"(function(a,){})()",
|
||||
];
|
||||
var test = function(code) {
|
||||
return function() {
|
||||
uglify.parse(code, {fromString: true});
|
||||
}
|
||||
}
|
||||
var error = function(e) {
|
||||
return e instanceof uglify.JS_Parse_Error &&
|
||||
e.message === "SyntaxError: Invalid function parameter";
|
||||
}
|
||||
for (var i = 0; i < tests.length; i++) {
|
||||
assert.throws(test(tests[i]), error);
|
||||
}
|
||||
});
|
||||
it("Should not accept an initializer when parameter is a rest parameter", function() {
|
||||
var tests = [
|
||||
"(function(...a = b){})()",
|
||||
"(function(a, ...b = [c, d]))"
|
||||
];
|
||||
var test = function(code) {
|
||||
return function () {
|
||||
uglify.parse(code, {fromString: true});
|
||||
}
|
||||
}
|
||||
var error = function (e) {
|
||||
return e instanceof uglify.JS_Parse_Error;
|
||||
}
|
||||
for (var i = 0; i < tests.length; i++) {
|
||||
assert.throws(test(tests[i]), error, tests[i]);
|
||||
}
|
||||
});
|
||||
it("Shoult not accept duplicated identifiers inside parameters in strict mode or when using default assigment or spread", function() {
|
||||
// From: ES2016 9.2.12 FunctionDeclarationInstantiation (func, argumentsList)
|
||||
// NOTE Early errors ensure that duplicate parameter names can only occur
|
||||
// in non-strict functions that do not have parameter default values or
|
||||
// rest parameters.
|
||||
var tests = [
|
||||
"(function(a = 1, a){})()",
|
||||
"(function(a, [a = 3]){})()",
|
||||
"(function(a, b, c, d, [{e: [...a]}]){})()",
|
||||
"'use strict'; (function(a, a){})",
|
||||
"(function({a, a = b}))",
|
||||
"(function(a, [...a]){})",
|
||||
"(function(a, ...a){})",
|
||||
"(function(a, [a, ...b]){})",
|
||||
"(function(a, {b: a, c: [...d]}){})",
|
||||
"(function(a, a, {b: [...c]}){})"
|
||||
];
|
||||
var test = function(code) {
|
||||
return function () {
|
||||
uglify.parse(code, {fromString: true});
|
||||
}
|
||||
}
|
||||
var error = function (e) {
|
||||
return e instanceof uglify.JS_Parse_Error &&
|
||||
/^SyntaxError: Parameter [a-zA-Z]+ was used already$/.test(e.message);
|
||||
}
|
||||
for (var i = 0; i < tests.length; i++) {
|
||||
assert.throws(test(tests[i]), error, tests[i]);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
29
test/mocha/lhs-expressions.js
Normal file
29
test/mocha/lhs-expressions.js
Normal file
@@ -0,0 +1,29 @@
|
||||
var assert = require("assert");
|
||||
var uglify = require("../../");
|
||||
|
||||
describe("Left-hand side expressions", function () {
|
||||
it("Should parse destructuring with const/let/var correctly", function () {
|
||||
var decls = uglify.parse('var {a,b} = foo, { c, d } = bar');
|
||||
|
||||
assert.equal(decls.body[0].TYPE, 'Var');
|
||||
assert.equal(decls.body[0].definitions.length, 2);
|
||||
assert.equal(decls.body[0].definitions[0].name.TYPE, 'Destructuring');
|
||||
assert.equal(decls.body[0].definitions[0].value.TYPE, 'SymbolRef');
|
||||
|
||||
var nested_def = uglify.parse('var [{x}] = foo').body[0].definitions[0];
|
||||
|
||||
assert.equal(nested_def.name.names[0].names[0].TYPE, 'SymbolVar');
|
||||
assert.equal(nested_def.name.names[0].names[0].name, 'x');
|
||||
|
||||
var holey_def = uglify.parse('const [,,third] = [1,2,3]').body[0].definitions[0];
|
||||
|
||||
assert.equal(holey_def.name.names[0].TYPE, 'Hole');
|
||||
assert.equal(holey_def.name.names[2].TYPE, 'SymbolConst');
|
||||
|
||||
var expanding_def = uglify.parse('var [first, ...rest] = [1,2,3]').body[0].definitions[0];
|
||||
|
||||
assert.equal(expanding_def.name.names[0].TYPE, 'SymbolVar');
|
||||
assert.equal(expanding_def.name.names[1].TYPE, 'Expansion');
|
||||
assert.equal(expanding_def.name.names[1].expression.TYPE, 'SymbolVar');
|
||||
});
|
||||
});
|
||||
22
test/mocha/try.js
Normal file
22
test/mocha/try.js
Normal file
@@ -0,0 +1,22 @@
|
||||
var assert = require("assert");
|
||||
var uglify = require("../../");
|
||||
|
||||
describe("Try", function() {
|
||||
it("Should not allow catch with an empty parameter", function() {
|
||||
var tests = [
|
||||
"try {} catch() {}"
|
||||
];
|
||||
|
||||
var test = function(code) {
|
||||
return function () {
|
||||
uglify.parse(code, {fromString: true});
|
||||
}
|
||||
}
|
||||
var error = function (e) {
|
||||
return e instanceof uglify.JS_Parse_Error;
|
||||
}
|
||||
for (var i = 0; i < tests.length; i++) {
|
||||
assert.throws(test(tests[i]), error, tests[i]);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -90,7 +90,7 @@ describe("Yield", function() {
|
||||
|
||||
var fail = function(e) {
|
||||
return e instanceof UglifyJS.JS_Parse_Error &&
|
||||
e.message === "SyntaxError: Unexpected yield identifier inside strict mode";
|
||||
/SyntaxError: Unexpected yield identifier (?:as parameter )?inside strict mode/.test(e.message);
|
||||
}
|
||||
|
||||
var test = function(input) {
|
||||
|
||||
138
test/parser.js
138
test/parser.js
@@ -1,138 +0,0 @@
|
||||
|
||||
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].expression.TYPE, 'SymbolVar');
|
||||
|
||||
// generators
|
||||
var generators_def = UglifyJS.parse('function* fn() {}').body[0];
|
||||
ok.equal(generators_def.is_generator, true);
|
||||
|
||||
ok.throws(function () {
|
||||
UglifyJS.parse('function* (){ }');
|
||||
});
|
||||
|
||||
var generators_yield_def = UglifyJS.parse('function* fn() {\nyield remote();\}').body[0].body[0];
|
||||
ok.strictEqual(generators_yield_def.body.is_star, false);
|
||||
}
|
||||
|
||||
// Run standalone
|
||||
if (module.parent === null) {
|
||||
module.exports();
|
||||
}
|
||||
|
||||
@@ -30,10 +30,6 @@ 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