/** * Copyright (c) 2014, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * https://raw.github.com/facebook/regenerator/master/LICENSE file. An * additional grant of patent rights can be found in the PATENTS file in * the same directory. */ var assert = require("assert"); var runningInTranslation = /\.wrap\(/.test(function*(){}); var iteratorSymbol = typeof Symbol === "function" && Symbol.iterator || "@@iterator"; function check(g, yields, returnValue) { for (var i = 0; i < yields.length; ++i) { var info = g.next(i); assert.deepEqual(info.value, yields[i]); assert.strictEqual(info.done, false); } assert.deepEqual( i > 0 ? g.next(i) : g.next(), { value: returnValue, done: true } ); } // A version of `throw` whose behavior can't be statically analyzed. // Useful for testing dynamic exception dispatching. function raise(argument) { throw argument; } function assertAlreadyFinished(generator) { assert.deepEqual(generator.next(), { value: void 0, done: true }); } describe("regeneratorRuntime", function() { it("should be defined globally", function() { var global = Function("return this")(); assert.ok("regeneratorRuntime" in global); assert.strictEqual(global.regeneratorRuntime, regeneratorRuntime); }); it("should have a .wrap method", function() { assert.strictEqual(typeof regeneratorRuntime.wrap, "function"); }); it("should have a .mark method", function() { assert.strictEqual(typeof regeneratorRuntime.mark, "function"); }); it("should be the object name returned by util.runtimeProperty", function() { assert.strictEqual( require("../lib/util").runtimeProperty("foo").object.name, "regeneratorRuntime" ); }); }); (runningInTranslation ? describe : xdescribe)("@@iterator", function() { it("is defined on Generator.prototype and returns this", function() { function *gen(){} var iterator = gen(); assert.ok(!iterator.hasOwnProperty(iteratorSymbol)); assert.ok(!Object.getPrototypeOf(iterator).hasOwnProperty(iteratorSymbol)); assert.ok(Object.getPrototypeOf( Object.getPrototypeOf(iterator) ).hasOwnProperty(iteratorSymbol)); assert.strictEqual(iterator[iteratorSymbol](), iterator); }); }); describe("simple argument yielder", function() { it("should yield only its first argument", function() { function *gen(x) { yield x; } check(gen("oyez"), ["oyez"]); check(gen("foo", "bar"), ["foo"]); }); it("should support multiple yields in expression", function() { function *gen() { return (yield 0) + (yield 0); } var itr = gen(); itr.next(); itr.next(1); assert.equal(itr.next(2).value, 3); }); }); function *range(n) { for (var i = 0; i < n; ++i) { yield i; } } describe("range generator", function() { it("should yield the empty range", function() { check(range(0), []); }) it("should yield the range 0..n-1", function() { check(range(5), [0, 1, 2, 3, 4]); }); }); describe("collatz generator", function() { function *gen(n) { var count = 0; yield n; while (n !== 1) { count += 1; if (n % 2) { yield n = n * 3 + 1; } else { yield n >>= 1; } } return count; } function collatz(n) { var result = [n]; while (n !== 1) { if (n % 2) { n *= 3; n += 1; } else { n >>= 1; } result.push(n); } return result; } var seven = collatz(7); var fiftyTwo = seven.slice(seven.indexOf(52)); var eightyTwo = collatz(82); it("seven", function() { check(gen(7), seven, 16); }); it("fifty two", function() { check(gen(52), fiftyTwo, 11); }); it("eighty two", function() { check(gen(82), eightyTwo, 110); }); }); describe("throw", function() { (runningInTranslation ? it : xit)("should complete generator", function() { function *gen(x) { throw 1; } var u = gen(); try { u.next(); } catch (err) { assert.strictEqual(err, 1); } assertAlreadyFinished(u); }); }); describe("try-catch generator", function() { function *usingThrow(x) { yield 0; try { yield 1; if (x % 2 === 0) throw 2; yield x; } catch (x) { yield x; } yield 3; } function *usingRaise(x) { yield 0; try { yield 1; if (x % 2 === 0) raise(2); yield x; } catch (x) { yield x; } yield 3; } it("should catch static exceptions properly", function() { check(usingThrow(4), [0, 1, 2, 3]); check(usingThrow(5), [0, 1, 5, 3]); }); it("should catch dynamic exceptions properly", function() { check(usingRaise(4), [0, 1, 2, 3]); check(usingRaise(5), [0, 1, 5, 3]); }); }); describe("nested generators in try-catch", function() { function *gen() { try { nonExistent; } catch (e) { yield function* () { yield e; } } } it('should get a reference to the caught error', function () { var genFun2 = gen().next().value; assert.ok(regeneratorRuntime.isGeneratorFunction(genFun2)); var gen2 = genFun2(); var res = gen2.next(); assert.ok(res.value instanceof ReferenceError); // Note that we don't do strict equality over the message because it varies // across browsers (if we ever want to run tests in browsers). assert.ok(res.value.message.match(/nonExistent/)); }); }); describe("try-finally generator", function() { function *usingThrow(condition) { yield 0; try { yield 1; throw 2; yield 3; } finally { if (condition) { yield 4; return 5; } yield 6; return 7; } } function *usingRaise(condition) { yield 0; try { yield 1; raise(2); yield 3; } finally { if (condition) { yield 4; return 5; } yield 6; return 7; } } function *usingAbrupt(abruptType, finallyAbruptType) { yield 0; for (;;) { try { yield 1; if (abruptType === "return") { return 2; } else if (abruptType === "break") { break; } else if (abruptType === "continue") { abruptType = "return"; continue; } } finally { yield 3; if (finallyAbruptType === "return") { return 4; } else if (finallyAbruptType === "break") { break; } else if (finallyAbruptType === "continue") { finallyAbruptType = null; continue; } } } return 5; } it("should honor return", function() { check(usingAbrupt("return", null), [0, 1, 3], 2); }); it("should honor break", function() { check(usingAbrupt("break", null), [0, 1, 3], 5); }); it("should honor continue", function() { check(usingAbrupt("continue", null), [0, 1, 3, 1, 3], 2); }); it("should override abrupt with return", function() { check(usingAbrupt("return", "return"), [0, 1, 3], 4); check(usingAbrupt("break", "return"), [0, 1, 3], 4); check(usingAbrupt("continue", "return"), [0, 1, 3], 4); }); it("should override abrupt with break", function() { check(usingAbrupt("return", "break"), [0, 1, 3], 5); check(usingAbrupt("break", "break"), [0, 1, 3], 5); check(usingAbrupt("continue", "break"), [0, 1, 3], 5); }); it("should override abrupt with continue", function() { check(usingAbrupt("return", "continue"), [0, 1, 3, 1, 3], 2); check(usingAbrupt("break", "continue"), [0, 1, 3, 1, 3], 5); check(usingAbrupt("continue", "continue"), [0, 1, 3, 1, 3], 2); }); it("should execute finally blocks statically", function() { check(usingThrow(true), [0, 1, 4], 5); check(usingThrow(false), [0, 1, 6], 7); }); it("should execute finally blocks dynamically", function() { check(usingRaise(true), [0, 1, 4], 5); check(usingRaise(false), [0, 1, 6], 7); }); it("should execute finally blocks before throwing", function() { var uncaughtError = new Error("uncaught"); function *uncaught(condition) { try { yield 0; if (condition) { yield 1; raise(uncaughtError); } yield 2; } finally { yield 3; } yield 4; } check(uncaught(false), [0, 2, 3, 4]); var u = uncaught(true); assert.deepEqual(u.next(), { value: 0, done: false }); assert.deepEqual(u.next(), { value: 1, done: false }); assert.deepEqual(u.next(), { value: 3, done: false }); try { u.next(); assert.ok(false, "should have thrown an exception"); } catch (err) { assert.strictEqual(err, uncaughtError); } }); it("should throw correct error when finally contains catch", function() { var right = new Error("right"); var wrong = new Error("wrong"); function *gen() { try { yield 0; raise(right); } finally { yield 1; try { raise(wrong); } catch (err) { assert.strictEqual(err, wrong); yield 2; } } } var g = gen(); assert.deepEqual(g.next(), { value: 0, done: false }); assert.deepEqual(g.next(), { value: 1, done: false }); assert.deepEqual(g.next(), { value: 2, done: false }); try { g.next(); assert.ok(false, "should have thrown an exception"); } catch (err) { assert.strictEqual(err, right); } }); it("should run finally after break within try", function() { function *gen() { try { yield 0; while (true) { yield 1; break; } } finally { yield 2; } yield 3; } check(gen(), [0, 1, 2, 3]); }); }); describe("try-catch-finally generator", function() { function *usingThrow() { yield 0; try { try { yield 1; throw 2; yield 3; } catch (x) { throw yield x; } finally { yield 5; } } catch (thrown) { yield thrown; } yield 6; } function *usingRaise() { yield 0; try { try { yield 1; raise(2); yield 3; } catch (x) { throw yield x; } finally { yield 5; } } catch (thrown) { yield thrown; } yield 6; } it("should statically catch and then finalize", function() { check(usingThrow(), [0, 1, 2, 5, 3, 6]); }); it("should dynamically catch and then finalize", function() { check(usingRaise(), [0, 1, 2, 5, 3, 6]); }); it("should execute catch and finally blocks at most once", function() { var error = new Error(); function *gen() { try { switch (1) { case 1: yield "a"; break; default: break; } throw error; } catch (e) { assert.strictEqual(e, error); yield "b"; do { do { yield "c"; break; } while (false); yield "d"; break; } while (false); yield "e"; } finally { yield "f"; } } check(gen(), ["a", "b", "c", "d", "e", "f"]); }); it("should handle backwards jumps in labeled loops", function() { function *gen() { var firstTime = true; outer: while (true) { yield 0; try { while (true) { yield 1; if (firstTime) { firstTime = false; yield 2; continue outer; } else { yield 3; break; } } yield 4; break; } finally { yield 5; } yield 6; } yield 7; } check(gen(), [0, 1, 2, 5, 0, 1, 3, 4, 5, 7]); }); it("should handle loop continue statements properly", function() { var error = new Error("thrown"); var markers = []; function *gen() { var c = 2; while (c > 0) { try { markers.push("try"); yield c; } catch (e) { assert.strictEqual(e, error); markers.push("catch"); continue; } finally { markers.push("finally"); } markers.push("decrement"); --c; } } var g = gen(); assert.deepEqual(g.next(), { value: 2, done: false }); assert.deepEqual(g.throw(error), { value: 2, done: false }); assert.deepEqual(g.next(), { value: 1, done: false }); assert.deepEqual(g.next(), { value: void 0, done: true }); assert.deepEqual(markers, [ "try", "catch", "finally", "try", "finally", "decrement", "try", "finally", "decrement" ]); }); }); describe("dynamic exception", function() { function *gen(x, fname) { try { return fns[fname](x); } catch (thrown) { yield thrown; } } var fns = { f: function(x) { throw x; }, g: function(x) { return x; } }; it("should be dispatched correctly", function() { check(gen("asdf", "f"), ["asdf"]); check(gen("asdf", "g"), [], "asdf"); }); }); describe("nested finally blocks", function() { function *usingThrow() { try { try { try { throw "thrown"; } finally { yield 1; } } catch (thrown) { yield thrown; } finally { yield 2; } } finally { yield 3; } } function *usingRaise() { try { try { try { raise("thrown"); } finally { yield 1; } } catch (thrown) { yield thrown; } finally { yield 2; } } finally { yield 3; } } it("should statically execute in order", function() { check(usingThrow(), [1, "thrown", 2, 3]); }); it("should dynamically execute in order", function() { check(usingRaise(), [1, "thrown", 2, 3]); }); }); describe("for-in loop generator", function() { it("should handle the simple case", function() { function *gen() { var count = 0; var obj = {foo: 1, bar: 2}; for (var key in obj) { assert(obj.hasOwnProperty(key), key + " must be own property"); yield [key, obj[key]]; count += 1; } return count; } check(gen(), [["foo", 1], ["bar", 2]], 2); }); it("should handle break in loop", function() { function *gen(obj) { var count = 0; for (var key in (yield "why not", obj)) { if (obj.hasOwnProperty(key)) { if (key === "skip") { break; } count += 1; yield [key, obj[key]]; } } return count; } check( gen({ a: 1, b: 2, skip: 3, c: 4 }), ["why not", ["a", 1], ["b", 2]], 2 ); }); it("should handle property deletion in loop", function() { function *gen() { var count = 0; var obj = {foo: 1, bar: 2}; for (var key in obj) { assert(obj.hasOwnProperty(key), key + " must be own property"); yield [key, obj[key]]; delete obj.bar; count += 1; } return count; } check(gen(), [["foo", 1]], 1); }); it("should loop over inherited properties", function() { function *gen() { var count = 0; function Foo() { this.baz = 1 } Foo.prototype.bar = 2; var foo = new Foo(); for (var key in foo) { yield [key, foo[key]]; count += 1; } return count; } check(gen(), [["baz", 1], ["bar", 2]], 2); }); it("should handle risky object expressions", function() { function a(sent) { assert.strictEqual(sent, 1); a.called = true; } function b(sent) { assert.strictEqual(sent, 2); b.called = true; return { callee: b }; } function *gen() { assert.ok(!a.called); assert.ok(!b.called); for (var key in a(yield 0), b(yield 1)) { assert.ok(a.called); assert.ok(b.called); assert.strictEqual(yield key, 3); } for (var key in a(1), { foo: "foo", bar: "bar" }) { yield key; } } check(gen(), [0, 1, "callee", "foo", "bar"]); }); it("should allow non-Identifier left-hand expressions", function() { var obj = {}; var baz = { a: 1, b: 2, c: 3 }; var markers = []; function foo() { markers.push("called foo"); return obj; } function *gen() { for (foo().bar in baz) { markers.push(obj.bar); yield obj.bar; } } check(gen(), ["a", "b", "c"]); assert.deepEqual(markers, [ "called foo", "a", "called foo", "b", "called foo", "c" ]); }); }); describe("yield chain", function() { function *gen(n) { return yield yield yield yield n; } it("should have correct associativity", function() { check(gen(5), [5, 1, 2, 3], 4); check(gen("asdf"), ["asdf", 1, 2, 3], 4); }); }); describe("object literal generator", function() { function *gen(a, b) { yield { a: a - (yield a), b: yield b }; } it("should yield the correct object", function() { check(gen(1, 2), [1, 2, { a: 0, b: 2 }]); check(gen(4, 2), [4, 2, { a: 3, b: 2 }]); }); }); describe("switch statement generator", function() { function *gen(a) { switch (yield a) { case (yield "x") - a: return "first case"; case (yield "y") - a: return "second case"; } } it("should jump to the correct cases", function() { check(gen(1), [1, "x"], "first case"); check(gen(2), [2, "x", "y"], "second case"); }); }); describe("infinite sequence generator", function() { function *gen(start, step) { step = step || 1; while (true) { yield start; start += step; } } function *limit(g, stop) { while (true) { var info = g.next(); if (info.done) { return; } else if (info.value < stop) { yield info.value; } else { return; } } } it("should generate a lot of plausible values", function() { var g = gen(10, 2); assert.deepEqual(g.next(), { value: 10, done: false }); assert.deepEqual(g.next(), { value: 12, done: false }); assert.deepEqual(g.next(), { value: 14, done: false }); assert.deepEqual(g.next(), { value: 16, done: false }); var sum = 10 + 12 + 14 + 16; for (var n = 0; n < 1000; ++n) { var info = g.next(); sum += info.value; assert.strictEqual(info.done, false); } assert.strictEqual(sum, 1017052); }); it("should allow limiting", function() { check(limit(gen(10, 3), 20), [10, 13, 16, 19]); }); }); describe("generator function expression", function() { it("should behave just like a declared generator", function() { check(function *(x, y) { yield x; yield y; yield x + y; return x * y; }(3, 7), [3, 7, 10], 21); }) }); describe("generator reentry attempt", function() { function *gen(x) { try { (yield x).next(x); } catch (err) { yield err; } return x + 1; } it("should complain with a TypeError", function() { var g = gen(3); assert.deepEqual(g.next(), { value: 3, done: false }); var complaint = g.next(g); // Sending the generator to itself. assert.ok(complaint.value instanceof Error); assert.strictEqual( complaint.value.message, "Generator is already running" ); assert.deepEqual(g.next(), { value: 4, done: true }); }); }); describe("completed generator", function() { function *gen() { return "ALL DONE"; } (runningInTranslation ? it : xit) ("should refuse to resume", function() { var g = gen(); assert.deepEqual(g.next(), { value: "ALL DONE", done: true }); assertAlreadyFinished(g); }); }); describe("delegated yield", function() { it("should delegate correctly", function() { function *gen(condition) { yield 0; if (condition) { yield 1; yield* gen(false); yield 2; } yield 3; } check(gen(true), [0, 1, 0, 3, 2, 3]); check(gen(false), [0, 3]); }); it("should cope with empty delegatees", function() { function *gen(condition) { if (condition) { yield 0; yield* gen(false); yield 1; } } check(gen(true), [0, 1]); check(gen(false), []); }); it("should support deeper nesting", function() { function *outer(n) { yield n; yield* middle(n - 1, inner(n + 10)); yield n + 1; } function *middle(n, plusTen) { yield n; yield* inner(n - 1); yield n + 1; yield* plusTen; } function *inner(n) { yield n; } check(outer(5), [5, 4, 3, 5, 15, 6]); }); it("should pass sent values through", function() { function *outer(n) { yield* inner(n << 1); yield "zxcv"; } function *inner(n) { return yield yield yield n; } var g = outer(3); assert.deepEqual(g.next(), { value: 6, done: false }); assert.deepEqual(g.next(1), { value: 1, done: false }); assert.deepEqual(g.next(2), { value: 2, done: false }); assert.deepEqual(g.next(4), { value: "zxcv", done: false }); assert.deepEqual(g.next(5), { value: void 0, done: true }); }); it("should be governed by enclosing try statements", function() { var error = new Error("thrown"); function *outer(n) { try { yield 0; yield* inner(n); yield 1; } catch (err) { yield err.message; } yield 4; } function *inner(n) { while (n --> 0) { try { if (n === 3) { raise(error); } } finally { yield n; } } } check(outer(3), [0, 2, 1, 0, 1, 4]); check(outer(5), [0, 4, 3, "thrown", 4]); }); it("should dispatch .thrown exceptions correctly", function() { var count = 0; function *gen() { yield* inner(); try { yield* inner(); } catch (err) { // pass } return yield* inner(); } function *inner() { return yield count++; } var g = gen(); assert.deepEqual(g.next(), { value: 0, done: false }); assert.deepEqual(g.next(), { value: 1, done: false }); assert.deepEqual(g.throw(new Error("lol")), { value: 2, done: false, }); assert.deepEqual(g.next("sent"), { value: "sent", done: true }); }); it("should call .return methods of delegate iterators", function() { var throwee = new Error("argument to gen.throw"); var thrownFromThrow = new Error("thrown from throw method"); var thrownFromReturn = new Error("thrown from return method"); function *gen(delegate) { try { return yield* delegate; } catch (err) { return err; } } function check(throwMethod, returnMethod) { var throwCalled = false; var returnCalled = false; var count = 0; var iterator = { next: function() { return { value: count++, done: false }; } }; iterator[iteratorSymbol] = function() { return this; }; if (throwMethod) { iterator["throw"] = function() { throwCalled = true; return throwMethod.apply(this, arguments); }; } if (returnMethod) { iterator["return"] = function() { returnCalled = true; return returnMethod.apply(this, arguments); }; } var g = gen(iterator); assert.deepEqual(g.next(), { value: 0, done: false }); assert.deepEqual(g.next(), { value: 1, done: false }); assert.deepEqual(g.next(), { value: 2, done: false }); assert.deepEqual(g.next(), { value: 3, done: false }); assert.strictEqual(throwCalled, false); assert.strictEqual(returnCalled, false); var result = {}; result.throwResult = g.throw(throwee); result.throwCalled = throwCalled; result.returnCalled = returnCalled; return result; } var checkResult = check(undefined, function() { throw thrownFromReturn; }); if (runningInTranslation) { // BUG: Node v0.11 and v0.12 neglect to call .return here. assert.strictEqual(checkResult.throwResult.value, thrownFromReturn); } else { // This is the TypeError that results from trying to call the // undefined .throw method of the iterator. assert.ok(checkResult.throwResult.value instanceof TypeError); } assert.strictEqual(checkResult.throwResult.done, true); assert.strictEqual(checkResult.throwCalled, false); // BUG: Node v0.11 and v0.12 neglect to call .return here. assert.strictEqual(checkResult.returnCalled, runningInTranslation); checkResult = check(undefined, function() { return { value: "from return", done: true }; }); assert.notStrictEqual(checkResult.throwResult.value, throwee); // This is the TypeError that results from trying to call the // undefined .throw method of the iterator. assert.ok(checkResult.throwResult.value instanceof TypeError); assert.strictEqual(checkResult.throwResult.done, true); assert.strictEqual(checkResult.throwCalled, false); // BUG: Node v0.11 and v0.12 neglect to call .return here. assert.strictEqual(checkResult.returnCalled, runningInTranslation); var checkResult = check(function(thrown) { return { value: "from throw", done: true }; }, function() { throw thrownFromReturn; }); assert.strictEqual(checkResult.throwResult.value, "from throw"); assert.strictEqual(checkResult.throwResult.done, true); assert.strictEqual(checkResult.throwCalled, true); assert.strictEqual(checkResult.returnCalled, false); var checkResult = check(function(thrown) { throw thrownFromThrow; }, function() { throw thrownFromReturn; }); assert.strictEqual(checkResult.throwResult.value, thrownFromThrow); assert.strictEqual(checkResult.throwResult.done, true); assert.strictEqual(checkResult.throwCalled, true); assert.strictEqual(checkResult.returnCalled, false); var checkResult = check(undefined, undefined); assert.notStrictEqual(checkResult.throwResult.value, throwee); // This is the TypeError that results from trying to call the // undefined .throw method of the iterator. assert.ok(checkResult.throwResult.value instanceof TypeError); assert.strictEqual(checkResult.throwResult.done, true); assert.strictEqual(checkResult.throwCalled, false); assert.strictEqual(checkResult.returnCalled, false); }); it("should not be required to have a .return method", function() { function *gen(delegate) { return yield* delegate; } var inner = range(5); var iterator = { next: inner.next.bind(inner) }; iterator[iteratorSymbol] = function() { return this; }; var g = gen(iterator); assert.deepEqual(g.next(), { value: 0, done: false }); assert.deepEqual(g.next(), { value: 1, done: false }); assert.deepEqual(g.next(), { value: 2, done: false }); if (typeof g.return === "function") { assert.deepEqual(g.return(-1), { value: -1, done: true }); assert.deepEqual(g.next(), { value: void 0, done: true }); } }); (runningInTranslation ? it : xit) ("should support any iterable argument", function() { function *gen() { yield 0; yield* [ yield "one", yield "two", yield "three" ]; yield 5; } check(gen(), [0, "one", "two", "three", 2, 3, 4, 5]); function *string() { return yield* "asdf"; } check(string(), ["a", "s", "d", "f"]); }); it("should evaluate to the return value of the delegate", function() { function *inner() { yield 1; return 2; } function *outer(delegate) { return yield* delegate; } check(outer(inner()), [1], 2); var arrayDelegate = [3, 4]; if (!runningInTranslation) { // Node v0.11 doesn't know how to turn arrays into iterators over // their elements without a little help. arrayDelegate = regeneratorRuntime.values(arrayDelegate); } check(outer(arrayDelegate), [3, 4], void 0); // See issue #143. if (!runningInTranslation) { return; } check(outer({ next: function() { return { value: "oyez", done: true }; } }), [], "oyez"); }); it("should work as a subexpression", function() { function *inner(arg) { return arg; } function *gen(delegate) { // Unfortunately these parentheses appear to be necessary. return 1 + (yield* delegate); } check(gen(inner(2)), [], 3); check(gen(inner(3)), [], 4); if (!runningInTranslation) { return; } check(gen({ next: function() { return { value: "foo", done: true }; } }), [], "1foo"); }); }); describe("function declaration hoisting", function() { it("should work even if the declarations are out of order", function() { function *gen(n) { yield increment(n); function increment(x) { return x + 1; } if (n % 2) { yield halve(decrement(n)); function halve(x) { return x >> 1; } function decrement(x) { return x - 1; } } else { // The behavior of function declarations nested inside conditional // blocks is notoriously underspecified, and in V8 it appears the // halve function is still defined when we take this branch, so // "undefine" it for consistency with regenerator semantics. halve = void 0; } yield typeof halve; yield increment(increment(n)); } check(gen(3), [4, 1, "function", 5]); check(gen(4), [5, "undefined", 6]); }); it("should work for nested generator function declarations", function() { function *outer(n) { yield 0; assert.ok(regeneratorRuntime.isGeneratorFunction(inner)); return yield* inner(n); // Note that this function declaration comes after everything else // in the outer function, but needs to be fully available above. function *inner(n) { yield n - 1; yield n; return yield n + 1; } } check(outer(2), [0, 1, 2, 3], 4); }); it("should not interfere with function rebinding", function() { function rebindTo(value) { var oldValue = toBeRebound; toBeRebound = value; return oldValue; } function *toBeRebound() { var originalValue = toBeRebound; yield toBeRebound; assert.strictEqual(rebindTo(42), originalValue); yield toBeRebound; assert.strictEqual(rebindTo("asdf"), 42); yield toBeRebound; } var original = toBeRebound; check(toBeRebound(), [original, 42, "asdf"]); function attemptToRebind(value) { var oldValue = safe; safe = value; return oldValue; } var safe = function *safe() { var originalValue = safe; yield safe; assert.strictEqual(attemptToRebind(42), originalValue); yield safe; assert.strictEqual(attemptToRebind("asdf"), 42); yield safe; } original = safe; check(safe(), [safe, safe, safe]); }); }); describe("the arguments object", function() { it("should work in simple variadic functions", function() { function *sum() { var result = 0; for (var i = 0; i < arguments.length; ++i) { yield result += arguments[i]; } return result; } check(sum(1, 2, 3), [1, 3, 6], 6); check(sum(9, -5, 3, 0, 2), [9, 4, 7, 7, 9], 9); }); it("should alias function parameters", function() { function *gen(x, y) { yield x; ++arguments[0]; yield x; yield y; --arguments[1]; yield y; var temp = y; y = x; x = temp; yield x; yield y; } check(gen(3, 7), [3, 4, 7, 6, 6, 4]); check(gen(10, -5), [10, 11, -5, -6, -6, 11]); }); it("should be shadowable by explicit declarations", function() { function *asParameter(x, arguments) { arguments = arguments + 1; yield x + arguments; } check(asParameter(4, 5), [10]); check(asParameter("asdf", "zxcv"), ["asdfzxcv1"]); function *asVariable(x) { // TODO References to arguments before the variable declaration // seem to see the object instead of the undefined value. var arguments = x + 1; yield arguments; } check(asVariable(4), [5]); check(asVariable("asdf"), ["asdf1"]); }); it("should not get confused by properties", function() { function *gen(args) { var obj = { arguments: args }; yield obj.arguments; obj.arguments = "oyez"; yield obj; } check(gen(42), [42, { arguments: "oyez" }]); }); it("supports .callee", function() { function *gen(doYield) { yield 1; if (doYield) { yield 2; } else { yield 3 yield* arguments.callee(true); yield 4 } yield 5; } check(gen(false), [1, 3, 1, 2, 5, 4, 5]); }); }); describe("catch parameter shadowing", function() { it("should leave outer variables unmodified", function() { function *gen(x) { var y = x + 1; try { throw x + 2; } catch (x) { yield x; x += 1; yield x; } yield x; try { throw x + 3; } catch (y) { yield y; y *= 2; yield y; } yield y; } check(gen(1), [3, 4, 1, 4, 8, 2]); check(gen(2), [4, 5, 2, 5, 10, 3]); }); it("should not replace variables defined in inner scopes", function() { function *gen(x) { try { throw x; } catch (x) { yield x; yield (function(x) { return x += 1; }(x + 1)); yield (function() { var x = arguments[0]; return x * 2; }(x + 2)); yield (function() { function notCalled(x) { throw x; } x >>= 1; return x; }()); yield x -= 1; } yield x; } check(gen(10), [10, 12, 24, 5, 4, 10]); check(gen(11), [11, 13, 26, 5, 4, 11]); }); it("should allow nested catch parameters of the same name", function() { function *gen() { try { raise("e1"); } catch (e) { yield e; try { raise("e2"); } catch (e) { yield e; } yield e; } } check(gen(), ["e1", "e2", "e1"]); }); it("should not interfere with non-referential identifiers", function() { function *gen() { try { yield 1; raise(new Error("oyez")); yield 2; } catch (e) { yield 3; e.e = "e.e"; e[e.message] = "e.oyez"; return { e: e, identity: function(x) { var e = x; return e; } }; } yield 4; } var g = gen(); assert.deepEqual(g.next(), { value: 1, done: false }); assert.deepEqual(g.next(), { value: 3, done: false }); var info = g.next(); assert.strictEqual(info.done, true); assert.strictEqual(info.value.e.message, "oyez"); assert.strictEqual(info.value.e.e, "e.e"); assert.strictEqual(info.value.e.oyez, "e.oyez"); assert.strictEqual(info.value.identity("same"), "same"); }); }); describe("empty while loops", function() { it("should be preserved in generated code", function() { function *gen(x) { while (x) { // empty while loop } do { // empty do-while loop } while (x); return gen.toString(); } var info = gen(false).next(); assert.strictEqual(info.done, true); assert.ok(/empty while loop/.test(info.value)); assert.ok(/empty do-while loop/.test(info.value)); }); }); describe("object literals with multiple yields", function() { it("should receive different sent values", function() { function *gen(fn) { return { a: yield "a", b: yield "b", c: fn(yield "c", yield "d"), d: [yield "e", yield "f"] }; } check(gen(function sum(x, y) { return x + y; }), ["a", "b", "c", "d", "e", "f"], { a: 1, b: 2, c: 3 + 4, d: [5, 6] }); }); }); describe("generator .throw method", function() { (runningInTranslation ? it : xit)("should complete generator", function() { function *gen(x) { yield 2; throw 1; } var u = gen(); u.next(); try { u.throw(2); } catch (err) { assert.strictEqual(err, 2); } assertAlreadyFinished(u); }); it("should work after the final call to .next", function() { function *gen() { yield 1; } var g = gen(); assert.deepEqual(g.next(), { value: 1, done: false }); var exception = new Error("unhandled exception"); try { g.throw(exception); assert.ok(false, "should have thrown an exception"); } catch (err) { assert.strictEqual(err, exception); } }); it("should immediately complete a new-born generator", function() { var began = false; function *gen() { began = true; yield 1; } var g = gen(); var exception = new Error("unhandled exception"); try { g.throw(exception); assert.ok(false, "should have thrown an exception"); } catch (err) { assert.strictEqual(err, exception); assert.strictEqual(began, false); } }); it("should not propagate errors handled inside a delegate", function() { function *outer() { try { yield* inner(); } catch (err) { return -1; } return 1; } function *inner() { try { yield void 0; } catch (e) { return; } } var g = outer(); g.next(); assert.equal(g.throw(new Error('foo')).value, 1); }); it("should propagate errors unhandled inside a delegate", function() { function *outer() { try { yield* inner(); } catch (err) { return -1; } return 1; } function *inner() { yield void 0; } var g = outer(); g.next(); assert.equal(g.throw(new Error('foo')).value, -1); }); }); describe("unqualified function calls", function() { it("should have a global `this` object", function() { function getThis() { return this; } // This is almost certainly the global object, but there's a chance it // might be null or undefined (in strict mode). var unqualifiedThis = getThis(); function *invoke() { // It seems like a bug in the ES6 spec that we have to yield an // argument instead of just calling (yield)(). return (yield "dummy")(); } var g = invoke(); var info = g.next(); assert.deepEqual(info, { value: "dummy", done: false }); info = g.next(getThis); // Avoid using assert.strictEqual when the arguments might equal the // global object, since JSON.stringify chokes on circular structures. assert.ok(info.value === unqualifiedThis); assert.strictEqual(info.done, true); }); }); describe("yield* expression results", function () { it("have correct values", function () { function* foo() { yield 0; return yield* bar(); } function* bar() { yield 1; return 2; } check(foo(), [0, 1], 2); }); it("can be used in complex expressions", function () { function pumpNumber(gen) { var n = 0; while (true) { var res = n > 0 ? gen.next(n) : gen.next(); n = res.value; if (res.done) { return n; } } } function* foo() { return (yield* bar()) + (yield* bar()); } function* bar() { return (yield 2) + (yield 3); } assert.strictEqual(pumpNumber(bar()), 5); assert.strictEqual(pumpNumber(foo()), 10); }); }); describe("isGeneratorFunction", function() { it("should work for function declarations", function() { // Do the assertions up here to make sure the generator function is // marked at the beginning of the block the function is declared in. assert.strictEqual( regeneratorRuntime.isGeneratorFunction(genFun), true ); assert.strictEqual( regeneratorRuntime.isGeneratorFunction(normalFun), false ); function normalFun() { return 0; } function *genFun() { yield 0; } }); it("should work for function expressions", function() { assert.strictEqual( regeneratorRuntime.isGeneratorFunction(function *genFun() { yield 0; }), true ); assert.strictEqual( regeneratorRuntime.isGeneratorFunction(function normalFun() { return 0; }), false ); }); }); describe("new expressions", function() { it("should be able to contain yield sub-expressions", function() { function A(first, second) { this.first = first; this.second = second; } function *gen() { return yield new (yield 0)(yield 1, yield 2); } var g = gen(); assert.deepEqual(g.next(), { value: 0, done: false }); assert.deepEqual(g.next(A), { value: 1, done: false }); assert.deepEqual(g.next("asdf"), { value: 2, done: false }); var info = g.next("zxcv"); assert.strictEqual(info.done, false); assert.ok(info.value instanceof A); assert.strictEqual(info.value.first, "asdf"); assert.strictEqual(info.value.second, "zxcv"); assert.deepEqual(g.next("qwer"), { value: "qwer", done: true }); }); }); describe("block binding", function() { it("should translate block binding correctly", function() { "use strict"; function *gen() { var a$0 = 0, a$1 = 1; let a = 3; { let a = 1; yield a + a$0; } { let a = 2; yield a - 1 + a$1; } yield a; } var g = gen(); assert.deepEqual(g.next(), { value: 1, done: false }); assert.deepEqual(g.next(), { value: 2, done: false }); assert.deepEqual(g.next(), { value: 3, done: false }); assert.deepEqual(g.next(), { value: void 0, done: true }); }); it("should translate block binding with iife correctly", function() { "use strict"; function *gen() { let arr = []; for (let x = 0; x < 3; x++) { let y = x; arr.push(function() { return y; }); } { let x; while( x = arr.pop() ) { yield x; } } } var g = gen(); assert.equal(g.next().value(), 2); assert.equal(g.next().value(), 1); assert.equal(g.next().value(), 0); assert.deepEqual(g.next(), { value: void 0, done: true }); }); }); describe("newborn generators", function() { it("should be able to yield* non-newborn generators", function() { function *inner() { return [yield 1, yield 2]; } function *outer(delegate) { return yield* delegate; } var n = inner(); assert.deepEqual(n.next(), { value: 1, done: false }); var g = outer(n); // I would really like to be able to pass 3 to g.next here, but V8 // ignores values sent to newborn generators, and SpiderMonkey throws // a TypeError. assert.deepEqual(g.next(), { value: 2, done: false }); assert.deepEqual(g.next(4), { value: [void 0, 4], done: true }); }); it("should support the ignore-initial-yield wrapper idiom", function() { var markers = []; function *inner() { markers.push(0); var sent1 = yield 1; markers.push(2); var sent2 = yield 2; markers.push(3); return [sent1, sent2]; } function wrapper(delegate) { var gen = (function*() { // This yield is the "initial yield" whose argument we ignore. var sent = yield "ignored", info; markers.push(1); while (!(info = delegate.next(sent)).done) { sent = yield info.value; } markers.push(4); return info.value; })(); // Ensure that gen is not newborn and that the next invocation of // gen.next(value) can send value to the initial yield expression. gen.next(); return gen; } var n = inner(); assert.deepEqual(n.next(), { value: 1, done: false }); var g = wrapper(n); // Unlike in the previous spec, it's fine to pass 3 to g.next here, // because g is not newborn, because g.next was already called once // before g was returned from the wrapper function. assert.deepEqual(g.next(3), { value: 2, done: false }); assert.deepEqual(g.next(4), { value: [3, 4], done: true }); // Ensure we encountered the marker points in the expected order. assert.deepEqual(markers, [0, 1, 2, 3, 4]); }); it("should allow chaining newborn and non-newborn generators", function() { function *range(n) { for (var i = 0; i < n; ++i) { yield i; } } function *chain(a, b) { yield* a; yield* b; } check(chain(range(3), range(5)), [0, 1, 2, 0, 1, 2, 3, 4]); function *y3(x) { return yield yield yield x; } function *y5(x) { return yield yield yield yield yield x; } check( chain(y3("foo"), y5("bar")), ["foo", 1, 2, "bar", 4, 5, 6, 7] ); var g3 = y3("three"); assert.deepEqual(g3.next(), { value: "three", done: false }); var g5 = y5("five"); assert.deepEqual(g5.next(), { value: "five", done: false }); var undef; // A little easier to read than void 0. check(chain(g3, g5), [undef, 1, undef, 3, 4, 5]); }); }); describe("labeled break and continue statements", function() { it("should be able to exit multiple try statements", function() { var e1 = "first"; var e2 = "second"; var e3 = "third"; var e4 = "fourth"; function *gen(n, which) { try { yield 0; raise(e1); } finally { yield 1; loop: for (var i = 0; i < n; ++i) { yield i; try { raise(e2); } finally { yield 2; try { raise(e3); } finally { yield 3; try { raise(e4); } finally { yield 4; if (which === "break") { yield "breaking"; break loop; } if (which === "continue") { yield "continuing"; continue loop; } yield 5; } } } } yield 6; } } try { check(gen(1, "break"), [ 0, 1, 0, 2, 3, 4, "breaking", 6 ]); assert.ok(false, "should have thrown an exception"); } catch (err) { assert.strictEqual(err, e1); } try { check(gen(3, "continue"), [ 0, 1, 0, 2, 3, 4, "continuing", 1, 2, 3, 4, "continuing", 2, 2, 3, 4, "continuing", 6 // Loop finished naturally. ]); assert.ok(false, "should have thrown an exception"); } catch (err) { assert.strictEqual(err, e1); } try { check(gen(3, "neither"), [ 0, 1, 0, 2, 3, 4, 5 ]); assert.ok(false, "should have thrown an exception"); } catch (err) { assert.strictEqual(err, e4); } }); it("should allow breaking from any labeled statement", function() { function* gen(limit) { yield 0; for (var i = 0; i < limit; ++i) { yield 1; label1: { yield 2; break label1; yield 3; } label2: if (limit === 3) label3: { yield 4; if (i === 0) break label2; yield 5; if (i === 1) break label3; label4: yield 6; // This should break from the for-loop. if (i === 2) xxx: break; yield 7; } // This should be a no-op. xxx: break xxx; yield 8 } yield 9; } check(gen(0), [0, 9]); check(gen(1), [0, 1, 2, 8, 9]); check(gen(2), [0, 1, 2, 8, 1, 2, 8, 9]); check(gen(3), [0, 1, 2, 4, 8, 1, 2, 4, 5, 8, 1, 2, 4, 5, 6, 9]); }); }); describe("for loop with var decl and no update expression", function() { // https://github.com/facebook/regenerator/issues/103 function *range() { for (var i = 0; false; ) { } } it("should compile and run", function() { check(range(), []); }); }); describe("generator function prototype", function() { function getProto(obj) { return Object.getPrototypeOf ? Object.getPrototypeOf(obj) : obj.__proto__; } it("should follow the expected object model", function() { var GeneratorFunctionPrototype = getProto(f); var GeneratorFunction = GeneratorFunctionPrototype.constructor; assert.strictEqual(GeneratorFunction.name, 'GeneratorFunction'); assert.strictEqual(GeneratorFunction.prototype, GeneratorFunctionPrototype); assert.strictEqual(GeneratorFunctionPrototype.prototype.constructor, GeneratorFunctionPrototype); assert.strictEqual(GeneratorFunctionPrototype.prototype, getProto(f.prototype)); assert.strictEqual(getProto(GeneratorFunctionPrototype), Function.prototype); if (typeof process === "undefined" || process.version.slice(1, 3) === "0.") { // Node version strings start with 0. assert.strictEqual(GeneratorFunctionPrototype.name, "GeneratorFunctionPrototype"); } else if (process.version.slice(1, 3) === "1.") { // iojs version strings start with 1., and iojs gets this .name // property wrong. TODO report this? assert.strictEqual(GeneratorFunctionPrototype.name, ""); } assert.strictEqual(typeof f2, "function"); assert.strictEqual(f2.constructor, GeneratorFunction); assert.ok(f2 instanceof GeneratorFunction); assert.strictEqual(f2.name, "f2"); var g = f(); assert.ok(g instanceof f); assert.strictEqual(getProto(g), f.prototype); assert.deepEqual([], Object.getOwnPropertyNames(f.prototype)); // assert.deepEqual([], Object.getOwnPropertyNames(g)); f.prototype.x = 42; var g2 = f(); assert.strictEqual(g2.x, 42); var g3 = new f(); assert.strictEqual(g3.x, 42); function* f2() { yield 1; } assert.strictEqual(getProto(f), getProto(f2)); assert.strictEqual(f.hasOwnProperty('constructor'), false); assert.strictEqual(getProto(f).constructor.name, 'GeneratorFunction'); // Intentionally at the end to test hoisting. function* f() { yield this; } function* f() { yield 1; } var f2 = f; f = 42; var g = f2(); assert.deepEqual(g.next(), { value: 1, done: false }); assert.deepEqual(g.next(), { value: void 0, done: true }); assert.ok(g instanceof f2); }); }); describe("for-of loops", function() { (runningInTranslation ? it : xit) ("should work for Arrays", function() { var sum = 0; for (var x of [1, 2].concat(3)) { sum += x; } assert.strictEqual(sum, 6); }); it("should work for generators", function() { var value, values = []; for (value of range(3)) values.push(value); assert.deepEqual(values, [0, 1, 2]); }); it("should work inside of generators", function() { function *yieldPermutations(list) { if (list.length < 2) { yield list; return 1; } var count = 0; var first = list.slice(0, 1); var genRest = yieldPermutations(list.slice(1)); for (var perm of genRest) { for (var i = 0; i < list.length; ++i) { var prefix = perm.slice(0, i); var suffix = perm.slice(i); yield prefix.concat(first, suffix); } count += i; } return count; } var count = 0; for (var perm of yieldPermutations([])) { assert.deepEqual(perm, []); ++count; } assert.strictEqual(count, 1); check(yieldPermutations([1]), [[1]], 1); check(yieldPermutations([2, 1]), [ [2, 1], [1, 2] ], 2); check(yieldPermutations([1,3,2]), [ [1, 3, 2], [3, 1, 2], [3, 2, 1], [1, 2, 3], [2, 1, 3], [2, 3, 1] ], 6); }); }); describe("generator return method", function() { if (!runningInTranslation) { // The return method has not been specified or implemented natively, // yet, so these tests need only pass in translation. return; } it("should work with newborn generators", function() { function *gen() { yield 0; } var g = gen(); assert.deepEqual(g.return("argument"), { value: "argument", done: true }); assertAlreadyFinished(g); }); it("should behave as if generator actually returned", function() { var executedFinally = false; function *gen() { try { yield 0; } catch (err) { assert.ok(false, "should not have executed the catch handler"); } finally { executedFinally = true; } } var g = gen(); assert.deepEqual(g.next(), { value: 0, done: false }); assert.deepEqual(g.return("argument"), { value: "argument", done: true }); assert.strictEqual(executedFinally, true); assertAlreadyFinished(g); }); it("should return both delegate and delegator", function() { var checkpoints = []; function* callee(errorToThrow) { try { yield 1; yield 2; } finally { checkpoints.push("callee finally"); if (errorToThrow) { throw errorToThrow; } } } function* caller(errorToThrow) { try { yield 0; yield* callee(errorToThrow); yield 3; } finally { checkpoints.push("caller finally"); } } var g1 = caller(); assert.deepEqual(g1.next(), { value: 0, done: false }); assert.deepEqual(g1.next(), { value: 1, done: false }); assert.deepEqual(g1.return(-1), { value: -1, done: true }); assert.deepEqual(checkpoints, [ "callee finally", "caller finally" ]); var error = new Error("thrown from callee"); var g2 = caller(error); assert.deepEqual(g2.next(), { value: 0, done: false }); assert.deepEqual(g2.next(), { value: 1, done: false }); try { g2.return(-1); assert.ok(false, "should have thrown an exception"); } catch (thrown) { assert.strictEqual(thrown, error); } assert.deepEqual(checkpoints, [ "callee finally", "caller finally", "callee finally", "caller finally" ]); }); }); describe("expressions containing yield subexpressions", function() { it("should evaluate all subexpressions before yielding", function() { function *gen(x) { return x * (yield (function(y) { x = y })); } var g = gen(2); var result = g.next(); assert.strictEqual(result.done, false); result.value(5); assert.deepEqual(g.next(5), { value: 10, done: true }); }); it("should work even with getter member expressions", function() { function *gen() { return a.b + (yield "asdf"); } var a = {}; var b = 0; Object.defineProperty(a, "b", { get: function() { return ++b; } }); var g = gen(); assert.strictEqual(a.b, 1); assert.deepEqual(g.next(), { value: "asdf", done: false }); assert.strictEqual(a.b, 3); assert.deepEqual(g.next(2), { value: 4, done: true }); }); it("should evaluate all array elements before yielding", function() { function *gen() { return [a, yield "asdf", a]; } var a = 1; var g = gen(); assert.deepEqual(g.next(), { value: "asdf", done: false }); a = 3; assert.deepEqual(g.next(2), { value: [1, 2, 3], done: true }); }); it("should handle callee member expressions correctly", function() { function *gen() { a = a.slice(0).concat(yield "asdf"); return a; } var a = []; var g = gen(); assert.deepEqual(g.next(), { value: "asdf", done: false }); a.push(1); assert.deepEqual(g.next(2), { value: [2], done: true }); }); it("should handle implicit stringification correctly", function() { function *gen() { return a + (yield "asdf"); } var a = [1, 2]; var g = gen(); assert.deepEqual(g.next(), { value: "asdf", done: false }); a = [4,5]; assert.deepEqual(g.next(",3"), { value: "1,2,3", done: true }); }); });