Improve support for binding pattern

Including improvements for parameters, variable assignment and
catch parameter.
This commit is contained in:
Anthony Van de Gejuchte
2016-08-20 22:32:29 +02:00
parent 1db50c3b16
commit 13ed445607
14 changed files with 727 additions and 312 deletions

View File

@@ -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(){

View File

@@ -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();

View File

@@ -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(),

View File

@@ -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: {
{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: {
{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});

View File

@@ -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
View 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}){}
}
}

View File

@@ -0,0 +1,9 @@
catch_destructuring_with_sequence: {
input: {
try {
throw {};
} catch ({xCover = (0, function() {})} ) {
}
}
expect_exact: "try{throw{}}catch({xCover=(0,function(){})}){}"
}

View File

@@ -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++) {

View File

@@ -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]);
}
});
});

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

View File

@@ -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) {

View File

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

View File

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