| //------------------------------------------------------------------------------------------------------- |
| // Copyright (C) Microsoft. All rights reserved. |
| // Licensed under the MIT license. See LICENSE.txt file in the project root for full license information. |
| //------------------------------------------------------------------------------------------------------- |
| |
| // ES6 Generators Syntax tests -- verifies function* and yield syntax spec conformance |
| |
| WScript.LoadScriptFile("..\\UnitTestFramework\\UnitTestFramework.js"); |
| |
| var tests = [ |
| { |
| name: "Simple valid syntax forms", |
| body: function () { |
| assert.doesNotThrow(function () { eval("function* gf() { }"); }, "Simple generator function declaration is valid syntax"); |
| assert.doesNotThrow(function () { eval("var gfe = function* () { }"); }, "Simple generator function expression w/o name is valid syntax"); |
| assert.doesNotThrow(function () { eval("var gfe = function* rgfe() { }"); }, "Simple generator function expression w/ name is valid syntax"); |
| assert.doesNotThrow(function () { eval("class C { *gm() { } }"); }, "Simple generator method declaration is valid syntax"); |
| assert.doesNotThrow(function () { eval("var o = { *gcf() { } }"); }, "Simple generator concise function declaration is valid syntax"); |
| |
| assert.doesNotThrow(function () { eval("function* gf() { yield; }"); }, "yield without operand is a valid expression statement"); |
| assert.doesNotThrow(function () { eval("function* gf() { yield }"); }, "yield without operand is a valid expression statement with implicit semicolon insertion"); |
| assert.doesNotThrow(function () { eval("function* gf() { var a = yield; }"); }, "yield without operand is a valid RHS expression"); |
| assert.doesNotThrow(function () { eval("function* gf() { foo(yield); }"); }, "yield without operand is a valid argument expression"); |
| assert.doesNotThrow(function () { eval("function* gf() { foo[yield]; }"); }, "yield without operand is a valid element expression"); |
| assert.doesNotThrow(function () { eval("function* gf() { yield, 10; }"); }, "yield without operand is a valid LHS of the comma operator"); |
| assert.doesNotThrow(function () { eval("function* gf() { switch (1) { case yield: break; } }"); }, "yield without operand is a valid switch case label operand"); |
| assert.doesNotThrow(function () { eval("var gfe = function* () { switch (1) { case yield: break; } }"); }, "yield without operand is a valid switch case label operand inside function expression w/o name"); |
| assert.doesNotThrow(function () { eval("var gfe = function* rgfe() { switch (1) { case yield: break; } }"); }, "yield without operand is a valid switch case label operand inside function expression w/ name"); |
| assert.doesNotThrow(function () { eval("var o = { *gf() { switch (1) { case yield: break; } } }"); }, "yield without operand is a valid switch case label operand inside method definition"); |
| assert.doesNotThrow(function () { eval("class C { *gf() { switch (1) { case yield: break; } } }"); }, "yield without operand is a valid switch case label operand inside class method"); |
| |
| assert.doesNotThrow(function () { eval("function* gf() { yield 'foo'; }"); }, "yield with operand is a valid expression statement"); |
| assert.doesNotThrow(function () { eval("function* gf() { yield 'foo' }"); }, "yield with operand is a valid expression statement with implicit semicolon insertion"); |
| assert.doesNotThrow(function () { eval("function* gf() { var a = yield 'foo'; }"); }, "yield with operand is a valid RHS expression"); |
| assert.doesNotThrow(function () { eval("function* gf() { foo(yield 'foo'); }"); }, "yield with operand is a valid argument expression"); |
| assert.doesNotThrow(function () { eval("function* gf() { foo[yield 'foo']; }"); }, "yield with operand is a valid element expression"); |
| assert.doesNotThrow(function () { eval("function* gf() { yield 'foo', 10; }"); }, "yield with operand is a valid LHS of the comma operator"); |
| assert.doesNotThrow(function () { eval("function* gf() { switch (1) { case yield 'foo': break; } }"); }, "yield with operand is a valid switch case label operand"); |
| assert.doesNotThrow(function () { eval("var gfe = function* () { switch (1) { case yield 'foo': break; } }"); }, "yield with operand is a valid switch case label operand inside function expression w/o name"); |
| assert.doesNotThrow(function () { eval("var gfe = function* rgfe() { switch (1) { case yield 'foo': break; } }"); }, "yield with operand is a valid switch case label operand inside function expression w/ name"); |
| assert.doesNotThrow(function () { eval("var o = { *gf() { switch (1) { case yield 'foo': break; } } }"); }, "yield with operand is a valid switch case label operand inside method definition"); |
| assert.doesNotThrow(function () { eval("class C { *gf() { switch (1) { case yield 'foo': break; } } }"); }, "yield with operand is a valid switch case label operand inside class method"); |
| |
| assert.doesNotThrow(function () { eval("function* gf() { yield* 'foo'; }"); }, "yield* with operand is a valid expression statement"); |
| assert.doesNotThrow(function () { eval("function* gf() { yield* 'foo' }"); }, "yield* with operand is a valid expression statement with implicit semicolon insertion"); |
| assert.doesNotThrow(function () { eval("function* gf() { var a = yield* 'foo'; }"); }, "yield* with operand is a valid RHS expression"); |
| assert.doesNotThrow(function () { eval("function* gf() { foo(yield* 'foo'); }"); }, "yield* with operand is a valid argument expression"); |
| assert.doesNotThrow(function () { eval("function* gf() { foo[yield* 'foo']; }"); }, "yield* with operand is a valid element expression"); |
| assert.doesNotThrow(function () { eval("function* gf() { yield* 'foo', 10; }"); }, "yield* with operand is a valid LHS of the comma operator"); |
| assert.doesNotThrow(function () { eval("function* gf() { switch (1) { case yield* 'foo': break; } }"); }, "yield* with operand is a valid switch case label operand"); |
| assert.doesNotThrow(function () { eval("var gfe = function* () { switch (1) { case yield* 'foo': break; } }"); }, "yield* with operand is a valid switch case label operand inside function expression w/o name"); |
| assert.doesNotThrow(function () { eval("var gfe = function* rgfe() { switch (1) { case yield* 'foo': break; } }"); }, "yield* with operand is a valid switch case label operand inside function expression w/ name"); |
| assert.doesNotThrow(function () { eval("var o = { *gf() { yield* 'foo'; } }"); }, "yield* with an operand as a valid expression statement inside method definition"); |
| assert.doesNotThrow(function () { eval("class C { *gf() { switch (1) { case yield* 'foo': break; } } }"); }, "yield* with operand is a valid switch case label operand inside class method"); |
| } |
| }, |
| { |
| name: "Invalid syntax forms -- noteworthy forms found in testing", |
| body: function () { |
| assert.throws(function () { eval("class C { gm*() { } }"); }, SyntaxError, "Star does not work on RHS of method name for declaring a generator method", "Expected '('"); |
| } |
| }, |
| { |
| name: "Invalid syntax forms -- yield expressions cannot appear higher than assignment level precedence", |
| body: function () { |
| assert.throws(function () { eval("function* gf() { 1 + yield; }"); }, SyntaxError, "yield cannot appear in AdditiveExpression -- e.g. rhs operand of binary +", "Syntax error"); |
| assert.throws(function () { eval("function* gf() { 1 + yield 2; }"); }, SyntaxError, "yield with operand cannot appear in AdditiveExpression -- e.g. rhs of +", "Syntax error"); |
| assert.throws(function () { eval("function* gf() { 1 + yield* 'foo'; }"); }, SyntaxError, "yield* with operand cannot appear in AdditiveExpression -- e.g. rhs of +", "Syntax error"); |
| |
| assert.throws(function () { eval("function* gf() { +yield; }"); }, SyntaxError, "yield cannot appear in UnaryExpression -- e.g. operand of unary +", "Syntax error"); |
| assert.throws(function () { eval("function* gf() { +yield 2; }"); }, SyntaxError, "yield with operand cannot appear in UnaryExpression -- e.g. operand of unary +", "Syntax error"); |
| assert.throws(function () { eval("function* gf() { +yield* 'foo'; }"); }, SyntaxError, "yield* with operand cannot appear in UnaryExpression -- e.g. operand of unary +", "Syntax error"); |
| |
| assert.throws(function () { eval("function* gf() { yield++; }"); }, SyntaxError, "yield cannot appear in PostfixExpression -- e.g. operand of postfix ++", "Syntax error"); |
| } |
| }, |
| { |
| name: "Invalid use of yield in lvalue positions that are runtime errors", |
| body: function () { |
| assert.throws(function () { eval('function* gf() { (yield) = 10; }'); var g = gf(); g.next(); g.next(); }, ReferenceError, "yield cannot be the LHS target of an assignment", "Invalid left-hand side in assignment"); |
| assert.throws(function () { eval('function* gf() { ++(yield); }'); var g = gf(); g.next(); g.next(); }, ReferenceError, "yield cannot be the target of an increment operator", "Invalid left-hand side in assignment"); |
| assert.throws(function () { eval('function* gf() { (yield)++; }'); var g = gf(); g.next(); g.next(); }, ReferenceError, "yield cannot be the target of an increment operator", "Invalid left-hand side in assignment"); |
| } |
| }, |
| { |
| name: "'yield' cannot be used as a BindingIdentifier for declarations in generator bodies", |
| body: function () { |
| assert.throws(function () { eval("let gfe = function* yield() { }"); }, SyntaxError, "Cannot name generator function expression 'yield' in any context", "The use of a keyword for an identifier is invalid"); |
| |
| assert.throws(function () { eval("function* gf() { var yield; }"); }, SyntaxError, "Cannot name var variable 'yield' in generator body", "The use of a keyword for an identifier is invalid"); |
| assert.throws(function () { eval("function* gf() { let yield; }"); }, SyntaxError, "Cannot name let variable 'yield' in generator body", "The use of a keyword for an identifier is invalid"); |
| assert.throws(function () { eval("function* gf() { const yield = 10; }"); }, SyntaxError, "Cannot name const variable 'yield' in generator body", "The use of a keyword for an identifier is invalid"); |
| |
| assert.throws(function () { eval("function* gf() { function yield() { } }"); }, SyntaxError, "Cannot name function 'yield' in generator body", "The use of a keyword for an identifier is invalid"); |
| assert.throws(function () { eval("function* gf() { function* yield() { } }"); }, SyntaxError, "Cannot name generator function 'yield' in generator body", "The use of a keyword for an identifier is invalid"); |
| assert.throws(function () { eval("function* gf() { var gfe = function* yield() { } }"); }, SyntaxError, "Cannot name generator function expression 'yield' in generator body", "The use of a keyword for an identifier is invalid"); |
| |
| assert.throws(function () { eval("function* gf() { class yield { } }"); }, SyntaxError, "Cannot name class 'yield' in generator body", "The use of a keyword for an identifier is invalid"); |
| |
| // TODO: Is this correct or a spec bug that will be fixed? |
| // var yield = 10; function* gf() { var o = { yield }; } gf(); |
| assert.throws(function () { eval("function* gf() { var o = { yield }; }"); }, SyntaxError, "Cannot name shorthand property 'yield' in generator body", "The use of a keyword for an identifier is invalid"); |
| |
| // Note, reserved words are allowed for object literal and class PropertyNames, so these cases parse without error. |
| assert.doesNotThrow(function () { eval("function* gf() { var fe = function yield() { } }"); }, "Can name function expression 'yield' in generator body"); |
| assert.doesNotThrow(function () { eval("function* gf() { var o = { yield: 10 } }"); }, "Can name object literal property 'yield' in generator body"); |
| assert.doesNotThrow(function () { eval("function* gf() { var o = { get yield() { } } }"); }, "Can name accessor method 'yield' in generator body"); |
| assert.doesNotThrow(function () { eval("function* gf() { var o = { yield() { } } }"); }, "Can name concise method 'yield' in generator body"); |
| assert.doesNotThrow(function () { eval("function* gf() { var o = { *yield() { } } }"); }, "Can name generator concise method 'yield' in generator body"); |
| assert.doesNotThrow(function () { eval("function* gf() { class C { yield() { } } }"); }, "Can name method 'yield' in generator body"); |
| assert.doesNotThrow(function () { eval("function* gf() { class C { *yield() { } } }"); }, "Can name generator method 'yield' in generator body"); |
| } |
| }, |
| { |
| name: "It is a SyntaxError if formal parameters' default argument expressions contain a yield expression", |
| body: function () { |
| assert.throws(function () { eval("function *gf(b, a = 1 + yield) {}"); }, SyntaxError, "Formal parameters of generator declaration cannot contain yield expression", "Syntax error"); |
| assert.throws(function () { eval("function *gf(b, yield) {}"); }, SyntaxError, "Formal parameters cannot be named yield inside generator declaration", "The use of a keyword for an identifier is invalid"); |
| assert.throws(function () { eval("function *gf(a = (10, yield, 20)) {}"); }, SyntaxError, "Parameter initializers cannot contain yield expression inside generator declaration", "Syntax error"); |
| assert.throws(function () { eval("gf = function* (b, a = yield) {}"); }, SyntaxError, "Formal parameters of generator expression cannot contain yield expression", "Syntax error"); |
| assert.throws(function () { eval("gf = function* (b, yield) {}"); }, SyntaxError, "Formal parameters cannot be named yield inside generator expression", "The use of a keyword for an identifier is invalid"); |
| assert.throws(function () { eval("var obj = { *gf(b, a = yield) {} }"); }, SyntaxError, "Formal parameters of generator methods cannot contain yield expression", "Syntax error"); |
| assert.throws(function () { eval("var obj = { *gf(b, yield) {} }"); }, SyntaxError, "Formal parameters cannot be named yield inside generator methods", "The use of a keyword for an identifier is invalid"); |
| } |
| }, |
| { |
| name: "'yield' can be used as a BindingIdentifier for declarations in non-strict, non-generator bodies", |
| body: function () { |
| assert.doesNotThrow(function () { eval("function f() { var yield; }"); }, "Can name var variable 'yield' in non-generator body"); |
| assert.doesNotThrow(function () { eval("function f() { let yield; }"); }, "Can name let variable 'yield' in non-generator body"); |
| assert.doesNotThrow(function () { eval("function f() { const yield = 10; }"); }, "Can name const variable 'yield' in non-generator body"); |
| |
| assert.doesNotThrow(function () { eval("function f() { function yield() { } }"); }, "Can name function 'yield' in non-generator body"); |
| assert.doesNotThrow(function () { eval("function f() { function* yield() { } }"); }, "Can name generator function 'yield' in non-generator body"); |
| assert.doesNotThrow(function () { eval("function f() { var fe = function yield() { } }"); }, "Can name function expression 'yield' in non-generator body"); |
| |
| assert.doesNotThrow(function () { eval("function f() { class yield { } }"); }, "Can name class 'yield' in non-generator body"); |
| |
| assert.doesNotThrow(function () { eval("function f() { var o = { yield: 10 } }"); }, "Can name object literal property 'yield' in non-generator body"); |
| assert.doesNotThrow(function () { eval("function f() { var o = { get yield() { } } }"); }, "Can name accessor method 'yield' in non-generator body"); |
| assert.doesNotThrow(function () { eval("function f() { var o = { yield() { } } }"); }, "Can name concise method 'yield' in non-generator body"); |
| assert.doesNotThrow(function () { eval("function f() { var o = { *yield() { } } }"); }, "Can name generator concise method 'yield' in non-generator body"); |
| assert.doesNotThrow(function () { eval("function f() { var yield = 10; var o = { yield }; }"); }, "Can name shorthand property 'yield' in non-generator body"); |
| assert.doesNotThrow(function () { eval("function f() { class C { yield() { } } }"); }, "Can name method 'yield' in non-generator body"); |
| assert.doesNotThrow(function () { eval("function f() { class C { *yield() { } } }"); }, "Can name generator method 'yield' in non-generator body"); |
| } |
| }, |
| { |
| name: "Yield and yield* followed by new line", |
| body: function () { |
| var x = 0; |
| var gf1 = function *() { |
| yield |
| x = 1; |
| } |
| var g1 = gf1(); |
| assert.areEqual({value : undefined, done : false}, g1.next(), "Yield followed by a line terminator is treated as a no operand yield"); |
| assert.areEqual(0, x, "x is initialized with zero"); |
| assert.areEqual({value : undefined, done : true }, g1.next(), "Generator is in closed state"); |
| assert.areEqual(1, x, "Assignment expression is evaluated as a separate expression"); |
| |
| g1 = 10; |
| function *gf2() { |
| return yield |
| *g1; |
| } |
| var g2 = gf2(); |
| assert.areEqual({value: undefined, done: false}, g2.next(), "If there is a new line between yield and star then yield is treated as yield with no assignment expression"); |
| assert.areEqual({value: 100, done: true}, g2.next(10), "*g1 gets treated as a multiplication operation"); |
| } |
| }, |
| { |
| name: "Scenarios with yield not a keyword inside generator", |
| body: function () { |
| var result; |
| var x = 0; |
| function *gf() { |
| var g = function yield(a) { |
| if (!a) { |
| return yield(1); |
| } else { |
| return 10; |
| } |
| }; |
| yield g(); |
| }; |
| |
| var g = gf(); |
| assert.areEqual({value: 10, done: false}, g.next(), "Yield is allowed as the name of a function expression"); |
| assert.areEqual({value: undefined, done: true}, g.next(), "Generator is closed"); |
| } |
| }, |
| // TODO: add test case for function* gfoo() { (yield) => { /* use yield here */ } } |
| ]; |
| |
| testRunner.runTests(tests, { verbose: WScript.Arguments[0] != "summary" }); |