generators-syntax.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. //-------------------------------------------------------------------------------------------------------
  2. // Copyright (C) Microsoft. All rights reserved.
  3. // Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
  4. //-------------------------------------------------------------------------------------------------------
  5. // ES6 Generators Syntax tests -- verifies function* and yield syntax spec conformance
  6. WScript.LoadScriptFile("..\\UnitTestFramework\\UnitTestFramework.js");
  7. var tests = [
  8. {
  9. name: "Simple valid syntax forms",
  10. body: function () {
  11. assert.doesNotThrow(function () { eval("function* gf() { }"); }, "Simple generator function declaration is valid syntax");
  12. assert.doesNotThrow(function () { eval("var gfe = function* () { }"); }, "Simple generator function expression w/o name is valid syntax");
  13. assert.doesNotThrow(function () { eval("var gfe = function* rgfe() { }"); }, "Simple generator function expression w/ name is valid syntax");
  14. assert.doesNotThrow(function () { eval("class C { *gm() { } }"); }, "Simple generator method declaration is valid syntax");
  15. assert.doesNotThrow(function () { eval("var o = { *gcf() { } }"); }, "Simple generator concise function declaration is valid syntax");
  16. assert.doesNotThrow(function () { eval("function* gf() { yield; }"); }, "yield without operand is a valid expression statement");
  17. assert.doesNotThrow(function () { eval("function* gf() { yield }"); }, "yield without operand is a valid expression statement with implicit semicolon insertion");
  18. assert.doesNotThrow(function () { eval("function* gf() { var a = yield; }"); }, "yield without operand is a valid RHS expression");
  19. assert.doesNotThrow(function () { eval("function* gf() { foo(yield); }"); }, "yield without operand is a valid argument expression");
  20. assert.doesNotThrow(function () { eval("function* gf() { foo[yield]; }"); }, "yield without operand is a valid element expression");
  21. assert.doesNotThrow(function () { eval("function* gf() { yield, 10; }"); }, "yield without operand is a valid LHS of the comma operator");
  22. assert.doesNotThrow(function () { eval("function* gf() { switch (1) { case yield: break; } }"); }, "yield without operand is a valid switch case label operand");
  23. 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");
  24. 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");
  25. 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");
  26. 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");
  27. assert.doesNotThrow(function () { eval("function* gf() { yield 'foo'; }"); }, "yield with operand is a valid expression statement");
  28. assert.doesNotThrow(function () { eval("function* gf() { yield 'foo' }"); }, "yield with operand is a valid expression statement with implicit semicolon insertion");
  29. assert.doesNotThrow(function () { eval("function* gf() { var a = yield 'foo'; }"); }, "yield with operand is a valid RHS expression");
  30. assert.doesNotThrow(function () { eval("function* gf() { foo(yield 'foo'); }"); }, "yield with operand is a valid argument expression");
  31. assert.doesNotThrow(function () { eval("function* gf() { foo[yield 'foo']; }"); }, "yield with operand is a valid element expression");
  32. assert.doesNotThrow(function () { eval("function* gf() { yield 'foo', 10; }"); }, "yield with operand is a valid LHS of the comma operator");
  33. assert.doesNotThrow(function () { eval("function* gf() { switch (1) { case yield 'foo': break; } }"); }, "yield with operand is a valid switch case label operand");
  34. 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");
  35. 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");
  36. 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");
  37. 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");
  38. assert.doesNotThrow(function () { eval("function* gf() { yield* 'foo'; }"); }, "yield* with operand is a valid expression statement");
  39. assert.doesNotThrow(function () { eval("function* gf() { yield* 'foo' }"); }, "yield* with operand is a valid expression statement with implicit semicolon insertion");
  40. assert.doesNotThrow(function () { eval("function* gf() { var a = yield* 'foo'; }"); }, "yield* with operand is a valid RHS expression");
  41. assert.doesNotThrow(function () { eval("function* gf() { foo(yield* 'foo'); }"); }, "yield* with operand is a valid argument expression");
  42. assert.doesNotThrow(function () { eval("function* gf() { foo[yield* 'foo']; }"); }, "yield* with operand is a valid element expression");
  43. assert.doesNotThrow(function () { eval("function* gf() { yield* 'foo', 10; }"); }, "yield* with operand is a valid LHS of the comma operator");
  44. assert.doesNotThrow(function () { eval("function* gf() { switch (1) { case yield* 'foo': break; } }"); }, "yield* with operand is a valid switch case label operand");
  45. 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");
  46. 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");
  47. assert.doesNotThrow(function () { eval("var o = { *gf() { yield* 'foo'; } }"); }, "yield* with an operand as a valid expression statement inside method definition");
  48. 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");
  49. }
  50. },
  51. {
  52. name: "Invalid syntax forms -- noteworthy forms found in testing",
  53. body: function () {
  54. assert.throws(function () { eval("class C { gm*() { } }"); }, SyntaxError, "Star does not work on RHS of method name for declaring a generator method", "Expected '('");
  55. }
  56. },
  57. {
  58. name: "Invalid syntax forms -- yield expressions cannot appear higher than assignment level precedence",
  59. body: function () {
  60. assert.throws(function () { eval("function* gf() { 1 + yield; }"); }, SyntaxError, "yield cannot appear in AdditiveExpression -- e.g. rhs operand of binary +", "Syntax error");
  61. assert.throws(function () { eval("function* gf() { 1 + yield 2; }"); }, SyntaxError, "yield with operand cannot appear in AdditiveExpression -- e.g. rhs of +", "Syntax error");
  62. assert.throws(function () { eval("function* gf() { 1 + yield* 'foo'; }"); }, SyntaxError, "yield* with operand cannot appear in AdditiveExpression -- e.g. rhs of +", "Syntax error");
  63. assert.throws(function () { eval("function* gf() { +yield; }"); }, SyntaxError, "yield cannot appear in UnaryExpression -- e.g. operand of unary +", "Syntax error");
  64. assert.throws(function () { eval("function* gf() { +yield 2; }"); }, SyntaxError, "yield with operand cannot appear in UnaryExpression -- e.g. operand of unary +", "Syntax error");
  65. assert.throws(function () { eval("function* gf() { +yield* 'foo'; }"); }, SyntaxError, "yield* with operand cannot appear in UnaryExpression -- e.g. operand of unary +", "Syntax error");
  66. assert.throws(function () { eval("function* gf() { yield++; }"); }, SyntaxError, "yield cannot appear in PostfixExpression -- e.g. operand of postfix ++", "Syntax error");
  67. }
  68. },
  69. {
  70. name: "Invalid use of yield in lvalue positions that are runtime errors",
  71. body: function () {
  72. 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");
  73. 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");
  74. 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");
  75. }
  76. },
  77. {
  78. name: "'yield' cannot be used as a BindingIdentifier for declarations in generator bodies",
  79. body: function () {
  80. 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");
  81. 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");
  82. 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");
  83. 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");
  84. 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");
  85. 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");
  86. 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");
  87. 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");
  88. // TODO: Is this correct or a spec bug that will be fixed?
  89. // var yield = 10; function* gf() { var o = { yield }; } gf();
  90. 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");
  91. // Note, reserved words are allowed for object literal and class PropertyNames, so these cases parse without error.
  92. assert.doesNotThrow(function () { eval("function* gf() { var fe = function yield() { } }"); }, "Can name function expression 'yield' in generator body");
  93. assert.doesNotThrow(function () { eval("function* gf() { var o = { yield: 10 } }"); }, "Can name object literal property 'yield' in generator body");
  94. assert.doesNotThrow(function () { eval("function* gf() { var o = { get yield() { } } }"); }, "Can name accessor method 'yield' in generator body");
  95. assert.doesNotThrow(function () { eval("function* gf() { var o = { yield() { } } }"); }, "Can name concise method 'yield' in generator body");
  96. assert.doesNotThrow(function () { eval("function* gf() { var o = { *yield() { } } }"); }, "Can name generator concise method 'yield' in generator body");
  97. assert.doesNotThrow(function () { eval("function* gf() { class C { yield() { } } }"); }, "Can name method 'yield' in generator body");
  98. assert.doesNotThrow(function () { eval("function* gf() { class C { *yield() { } } }"); }, "Can name generator method 'yield' in generator body");
  99. }
  100. },
  101. {
  102. name: "It is a SyntaxError if formal parameters' default argument expressions contain a yield expression",
  103. body: function () {
  104. assert.throws(function () { eval("function *gf(b, a = 1 + yield) {}"); }, SyntaxError, "Formal parameters of generator declaration cannot contain yield expression", "Syntax error");
  105. 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");
  106. assert.throws(function () { eval("function *gf(a = (10, yield, 20)) {}"); }, SyntaxError, "Parameter initializers cannot contain yield expression inside generator declaration", "Syntax error");
  107. assert.throws(function () { eval("gf = function* (b, a = yield) {}"); }, SyntaxError, "Formal parameters of generator expression cannot contain yield expression", "Syntax error");
  108. 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");
  109. assert.throws(function () { eval("var obj = { *gf(b, a = yield) {} }"); }, SyntaxError, "Formal parameters of generator methods cannot contain yield expression", "Syntax error");
  110. 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");
  111. }
  112. },
  113. {
  114. name: "'yield' can be used as a BindingIdentifier for declarations in non-strict, non-generator bodies",
  115. body: function () {
  116. assert.doesNotThrow(function () { eval("function f() { var yield; }"); }, "Can name var variable 'yield' in non-generator body");
  117. assert.doesNotThrow(function () { eval("function f() { let yield; }"); }, "Can name let variable 'yield' in non-generator body");
  118. assert.doesNotThrow(function () { eval("function f() { const yield = 10; }"); }, "Can name const variable 'yield' in non-generator body");
  119. assert.doesNotThrow(function () { eval("function f() { function yield() { } }"); }, "Can name function 'yield' in non-generator body");
  120. assert.doesNotThrow(function () { eval("function f() { function* yield() { } }"); }, "Can name generator function 'yield' in non-generator body");
  121. assert.doesNotThrow(function () { eval("function f() { var fe = function yield() { } }"); }, "Can name function expression 'yield' in non-generator body");
  122. assert.doesNotThrow(function () { eval("function f() { class yield { } }"); }, "Can name class 'yield' in non-generator body");
  123. assert.doesNotThrow(function () { eval("function f() { var o = { yield: 10 } }"); }, "Can name object literal property 'yield' in non-generator body");
  124. assert.doesNotThrow(function () { eval("function f() { var o = { get yield() { } } }"); }, "Can name accessor method 'yield' in non-generator body");
  125. assert.doesNotThrow(function () { eval("function f() { var o = { yield() { } } }"); }, "Can name concise method 'yield' in non-generator body");
  126. assert.doesNotThrow(function () { eval("function f() { var o = { *yield() { } } }"); }, "Can name generator concise method 'yield' in non-generator body");
  127. assert.doesNotThrow(function () { eval("function f() { var yield = 10; var o = { yield }; }"); }, "Can name shorthand property 'yield' in non-generator body");
  128. assert.doesNotThrow(function () { eval("function f() { class C { yield() { } } }"); }, "Can name method 'yield' in non-generator body");
  129. assert.doesNotThrow(function () { eval("function f() { class C { *yield() { } } }"); }, "Can name generator method 'yield' in non-generator body");
  130. }
  131. },
  132. {
  133. name: "Yield and yield* followed by new line",
  134. body: function () {
  135. var x = 0;
  136. var gf1 = function *() {
  137. yield
  138. x = 1;
  139. }
  140. var g1 = gf1();
  141. assert.areEqual({value : undefined, done : false}, g1.next(), "Yield followed by a line terminator is treated as a no operand yield");
  142. assert.areEqual(0, x, "x is initialized with zero");
  143. assert.areEqual({value : undefined, done : true }, g1.next(), "Generator is in closed state");
  144. assert.areEqual(1, x, "Assignment expression is evaluated as a separate expression");
  145. g1 = 10;
  146. function *gf2() {
  147. return yield
  148. *g1;
  149. }
  150. var g2 = gf2();
  151. 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");
  152. assert.areEqual({value: 100, done: true}, g2.next(10), "*g1 gets treated as a multiplication operation");
  153. }
  154. },
  155. {
  156. name: "Scenarios with yield not a keyword inside generator",
  157. body: function () {
  158. var result;
  159. var x = 0;
  160. function *gf() {
  161. var g = function yield(a) {
  162. if (!a) {
  163. return yield(1);
  164. } else {
  165. return 10;
  166. }
  167. };
  168. yield g();
  169. };
  170. var g = gf();
  171. assert.areEqual({value: 10, done: false}, g.next(), "Yield is allowed as the name of a function expression");
  172. assert.areEqual({value: undefined, done: true}, g.next(), "Generator is closed");
  173. }
  174. },
  175. // TODO: add test case for function* gfoo() { (yield) => { /* use yield here */ } }
  176. ];
  177. testRunner.runTests(tests, { verbose: WScript.Arguments[0] != "summary" });