diff --git a/packages/babel-plugin-transform-regenerator/.gitignore b/packages/babel-plugin-transform-regenerator/.gitignore new file mode 100644 index 0000000000..51641efe5b --- /dev/null +++ b/packages/babel-plugin-transform-regenerator/.gitignore @@ -0,0 +1,11 @@ +npm-debug.log +node_modules + +test/mocha.js +test/mocha.css + +test/tests.es5.js +test/async.es5.js +test/tests.browser.js + +.idea diff --git a/packages/babel-plugin-transform-regenerator/.npmignore b/packages/babel-plugin-transform-regenerator/.npmignore new file mode 100644 index 0000000000..e216ae5e13 --- /dev/null +++ b/packages/babel-plugin-transform-regenerator/.npmignore @@ -0,0 +1,2 @@ +/node_modules +/test diff --git a/packages/babel-plugin-transform-regenerator/.travis.yml b/packages/babel-plugin-transform-regenerator/.travis.yml new file mode 100644 index 0000000000..928d48bb5c --- /dev/null +++ b/packages/babel-plugin-transform-regenerator/.travis.yml @@ -0,0 +1,11 @@ +language: node_js +node_js: + - "4.0" + - "iojs" + - "0.12" + - "0.11" + - "0.10" + - "0.8" +before_install: + - npm install -g npm@1.4.28 +sudo: false diff --git a/packages/babel-plugin-transform-regenerator/CONTRIBUTING.md b/packages/babel-plugin-transform-regenerator/CONTRIBUTING.md new file mode 100644 index 0000000000..a4ad349e65 --- /dev/null +++ b/packages/babel-plugin-transform-regenerator/CONTRIBUTING.md @@ -0,0 +1,80 @@ +# Contributing to Regenerator + +Regenerator uses GitHub as its sole source of truth. Everything happens +here. Facebook employees who contribute to Regenerator are expected to do +so in the same way as everyone else. In other words, this document applies +equally to all contributors. + +### `master` is unsafe + +We will do our best to keep `master` in good shape, with tests passing at +all times. But in order to move fast, we will make API changes that your +application might not be compatible with. We will do our best to +communicate these changes and always version appropriately so you can lock +into a specific version if need be. + +### Pull Requests + +In case you've never submitted a pull request (PR) via GitHub before, +please read [this short +tutorial](https://help.github.com/articles/creating-a-pull-request). If +you've submitted a PR before, there should be nothing surprising about our +procedures for Regenerator. + +*Before* submitting a pull request, please make sure the following is done… + +1. Fork the repo and create your branch from `master`. +2. If you've added code that should be tested, add tests! +3. Ensure the test suite passes (`npm test`). +4. If you haven't already, complete the CLA. +5. Submit a pull request via GitHub. +6. Check that Travis CI tests pass (pull request turns green). + +### Contributor License Agreement ("CLA") + +In order to accept your pull request, we need you to submit a CLA. You +only need to do this once, so if you've done this for another Facebook +open source project, you're good to go. If you are submitting a pull +request for the first time, just let us know that you have completed the +CLA and we can cross-check with your GitHub username. + +Complete your CLA here: + +## Bugs + +### Where to Find Known Issues + +We will be using GitHub Issues for all bugs. Before filing a new issue, +please try to make sure your problem doesn't already exist. If you think +your issue is more general than one that already exists, our preference is +still to modify the original issue to reflect the underlying problem more +faithfully. + +### Reporting New Issues + +The best way to get a bug fixed is to provide a reduced test case, and the +easiest way to reduce a testcase is to edit it in [the +sandbox](http://facebook.github.io/regenerator/) until you're satisfied +and then click the "report a bug" link (the new issue will be populated +automatically with your code). + +### Security Bugs + +Facebook has a [bounty program](https://www.facebook.com/whitehat/) for +the safe disclosure of security bugs. With that in mind, please do not +file public issues and go through the process outlined on that page. + +## Coding Style + +* Use semicolons; +* Commas last, +* 2 spaces for indentation (no tabs). +* Prefer `"` over `'` +* 80 character line length +* Match surrounding coding style. +* Less code is better code. + +## License + +By contributing to Regenerator, you agree that your contributions will be +licensed under the [BSD License](LICENSE). diff --git a/packages/babel-plugin-transform-regenerator/LICENSE b/packages/babel-plugin-transform-regenerator/LICENSE new file mode 100644 index 0000000000..187bfe283d --- /dev/null +++ b/packages/babel-plugin-transform-regenerator/LICENSE @@ -0,0 +1,14 @@ +BSD License + +For "regenerator" software + +Copyright (c) 2014, Facebook, Inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/packages/babel-plugin-transform-regenerator/PATENTS b/packages/babel-plugin-transform-regenerator/PATENTS new file mode 100644 index 0000000000..a2bd67d0db --- /dev/null +++ b/packages/babel-plugin-transform-regenerator/PATENTS @@ -0,0 +1,33 @@ +Additional Grant of Patent Rights Version 2 + +"Software" means the Regenerator software distributed by Facebook, Inc. + +Facebook, Inc. ("Facebook") hereby grants to each recipient of the Software +("you") a perpetual, worldwide, royalty-free, non-exclusive, irrevocable +(subject to the termination provision below) license under any Necessary +Claims, to make, have made, use, sell, offer to sell, import, and otherwise +transfer the Software. For avoidance of doubt, no license is granted under +Facebook's rights in any patent claims that are infringed by (i) modifications +to the Software made by you or any third party or (ii) the Software in +combination with any software or other technology. + +The license granted hereunder will terminate, automatically and without notice, +if you (or any of your subsidiaries, corporate affiliates or agents) initiate +directly or indirectly, or take a direct financial interest in, any Patent +Assertion: (i) against Facebook or any of its subsidiaries or corporate +affiliates, (ii) against any party if such Patent Assertion arises in whole or +in part from any software, technology, product or service of Facebook or any of +its subsidiaries or corporate affiliates, or (iii) against any party relating +to the Software. Notwithstanding the foregoing, if Facebook or any of its +subsidiaries or corporate affiliates files a lawsuit alleging patent +infringement against you in the first instance, and you respond by filing a +patent infringement counterclaim in that lawsuit against that party that is +unrelated to the Software, the license granted hereunder will not terminate +under section (i) of this paragraph due to such counterclaim. + +A "Necessary Claim" is a claim of a patent owned by Facebook that is +necessarily infringed by the Software standing alone. + +A "Patent Assertion" is any lawsuit or other action alleging direct, indirect, +or contributory infringement or inducement to infringe any patent, including a +cross-claim or counterclaim. diff --git a/packages/babel-plugin-transform-regenerator/README.md b/packages/babel-plugin-transform-regenerator/README.md new file mode 100644 index 0000000000..c220dba788 --- /dev/null +++ b/packages/babel-plugin-transform-regenerator/README.md @@ -0,0 +1,74 @@ +regenerator [![Build Status](https://travis-ci.org/facebook/regenerator.png?branch=master)](https://travis-ci.org/facebook/regenerator) +=== + +This package implements a fully-functional source transformation that +takes the proposed syntax for generators/`yield` from future versions of +JS ([ECMAScript6 or ES6](http://wiki.ecmascript.org/doku.php?id=harmony:specification_drafts), experimentally implemented in Node.js v0.11) and +spits out efficient JS-of-today (ES5) that behaves the same way. + +A small runtime library (less than 1KB compressed) is required to provide the +`wrapGenerator` function. You can install it either as a CommonJS module +or as a standalone .js file, whichever you prefer. + +Installation +--- + +From NPM: +```sh +npm install -g regenerator +``` + +From GitHub: +```sh +cd path/to/node_modules +git clone git://github.com/facebook/regenerator.git +cd regenerator +npm install . +npm test +``` + +Usage +--- + +You have several options for using this module. + +Simplest usage: +```sh +regenerator es6.js > es5.js # Just the transform. +regenerator --include-runtime es6.js > es5.js # Add the runtime too. +regenerator src lib # Transform every .js file in src and output to lib. +``` + +Programmatic usage: +```js +var es5Source = require("regenerator").compile(es6Source).code; +var es5SourceWithRuntime = require("regenerator").compile(es6Source, { + includeRuntime: true +}).code; +``` + +Babel plugin: +```js +var babel = require("babel-core"); +var code = babel.transform(es6Source, { + plugins: [require("generator")] +}).code; +``` + +How can you get involved? +--- + +The easiest way to get involved is to look for buggy examples using [the +sandbox](http://facebook.github.io/regenerator/), and when you find +something strange just click the "report a bug" link (the new issue form +will be populated automatically with the problematic code). + +Alternatively, you can +[fork](https://github.com/facebook/regenerator/fork) the repository, +create some failing tests cases in [test/tests.es6.js](test/tests.es6.js), +and send pull requests for me to fix. + +If you're feeling especially brave, you are more than welcome to dive into +the transformer code and fix the bug(s) yourself, but I must warn you that +the code could really benefit from [better implementation +comments](https://github.com/facebook/regenerator/issues/7). diff --git a/packages/babel-plugin-transform-regenerator/bin/regenerator b/packages/babel-plugin-transform-regenerator/bin/regenerator new file mode 100755 index 0000000000..88a4e27c12 --- /dev/null +++ b/packages/babel-plugin-transform-regenerator/bin/regenerator @@ -0,0 +1,17 @@ +#!/usr/bin/env node +// -*- mode: js -*- + +var compile = require("../main").compile; + +require("commoner").version( + require("../package.json").version +).resolve(function(id) { + return this.readModuleP(id); +}).option( + "-r, --include-runtime", + "Prepend the runtime to the output." +).process(function(id, source) { + return compile(source, { + includeRuntime: this.options.includeRuntime + }).code; +}); diff --git a/packages/babel-plugin-transform-regenerator/main.js b/packages/babel-plugin-transform-regenerator/main.js new file mode 100644 index 0000000000..2272f35859 --- /dev/null +++ b/packages/babel-plugin-transform-regenerator/main.js @@ -0,0 +1,34 @@ +/** + * 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 asyncFunctionSyntax = require("babel-plugin-syntax-async-functions"); +var blockScopingPlugin = require("babel-plugin-transform-es2015-block-scoping"); +var forOfPlugin = require("babel-plugin-transform-es2015-for-of"); +var babel = require("babel-core"); + +var regenerator = module.exports = function() { + return require("./lib/visit"); +}; + +regenerator.compile = function(code, opts) { + // todo: includeRuntime + return babel.transform(code, buildBabelOptions(opts)); +}; + +regenerator.transform = function (ast, opts) { + return babel.transformFromAst(ast, null, buildBabelOptions(opts)); +}; + +function buildBabelOptions(opts) { + return { + plugins: [regenerator, blockScopingPlugin, asyncFunctionSyntax, forOfPlugin], + sourceType: "script" + }; +} diff --git a/packages/babel-plugin-transform-regenerator/package.json b/packages/babel-plugin-transform-regenerator/package.json new file mode 100644 index 0000000000..a285cdf979 --- /dev/null +++ b/packages/babel-plugin-transform-regenerator/package.json @@ -0,0 +1,51 @@ +{ + "author": "Ben Newman ", + "name": "babel-plugin-regenerator", + "description": "Source transformer enabling ECMAScript 6 generator functions (yield) in JavaScript-of-today (ES5)", + "keywords": [ + "generator", + "yield", + "coroutine", + "rewriting", + "transformation", + "syntax", + "codegen", + "rewriting", + "refactoring", + "transpiler", + "desugaring", + "ES6" + ], + "version": "6.0.3", + "homepage": "http://github.com/facebook/regenerator", + "repository": { + "type": "git", + "url": "git://github.com/facebook/regenerator.git" + }, + "main": "main.js", + "bin": "bin/regenerator", + "scripts": { + "test": "node test/run.js" + }, + "dependencies": { + "commoner": "~0.10.3", + "babel-plugin-transform-es2015-block-scoping": "^5.0.0", + "babel-plugin-syntax-async-functions": "^5.0.0", + "babel-plugin-transform-es2015-for-of": "^5.0.0", + "babel-core": "^6.0.0", + "babel-traverse": "^6.0.0", + "babel-types": "^6.0.0", + "babylon": "^6.0.0", + "private": "~0.1.5", + "through": "~2.3.8" + }, + "devDependencies": { + "mocha": "~2.3.3", + "promise": "~7.0.4", + "semver": "~5.0.3" + }, + "license": "BSD", + "engines": { + "node": ">= 0.6" + } +} diff --git a/packages/babel-plugin-transform-regenerator/runtime-module.js b/packages/babel-plugin-transform-regenerator/runtime-module.js new file mode 100644 index 0000000000..8e7e2e4c1e --- /dev/null +++ b/packages/babel-plugin-transform-regenerator/runtime-module.js @@ -0,0 +1,31 @@ +// This method of obtaining a reference to the global object needs to be +// kept identical to the way it is obtained in runtime.js +var g = + typeof global === "object" ? global : + typeof window === "object" ? window : + typeof self === "object" ? self : this; + +// Use `getOwnPropertyNames` because not all browsers support calling +// `hasOwnProperty` on the global `self` object in a worker. See #183. +var hadRuntime = g.regeneratorRuntime && + Object.getOwnPropertyNames(g).indexOf("regeneratorRuntime") >= 0; + +// Save the old regeneratorRuntime in case it needs to be restored later. +var oldRuntime = hadRuntime && g.regeneratorRuntime; + +// Force reevalutation of runtime.js. +g.regeneratorRuntime = undefined; + +module.exports = require("./runtime"); + +if (hadRuntime) { + // Restore the original runtime. + g.regeneratorRuntime = oldRuntime; +} else { + // Remove the global property added by runtime.js. + try { + delete g.regeneratorRuntime; + } catch(e) { + g.regeneratorRuntime = undefined; + } +} diff --git a/packages/babel-plugin-transform-regenerator/runtime.js b/packages/babel-plugin-transform-regenerator/runtime.js new file mode 100644 index 0000000000..911a077b89 --- /dev/null +++ b/packages/babel-plugin-transform-regenerator/runtime.js @@ -0,0 +1,656 @@ +/** + * 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. + */ + +!(function(global) { + "use strict"; + + var hasOwn = Object.prototype.hasOwnProperty; + var undefined; // More compressible than void 0. + var iteratorSymbol = + typeof Symbol === "function" && Symbol.iterator || "@@iterator"; + + var inModule = typeof module === "object"; + var runtime = global.regeneratorRuntime; + if (runtime) { + if (inModule) { + // If regeneratorRuntime is defined globally and we're in a module, + // make the exports object identical to regeneratorRuntime. + module.exports = runtime; + } + // Don't bother evaluating the rest of this file if the runtime was + // already defined globally. + return; + } + + // Define the runtime globally (as expected by generated code) as either + // module.exports (if we're in a module) or a new, empty object. + runtime = global.regeneratorRuntime = inModule ? module.exports : {}; + + function wrap(innerFn, outerFn, self, tryLocsList) { + // If outerFn provided, then outerFn.prototype instanceof Generator. + var generator = Object.create((outerFn || Generator).prototype); + var context = new Context(tryLocsList || []); + + // The ._invoke method unifies the implementations of the .next, + // .throw, and .return methods. + generator._invoke = makeInvokeMethod(innerFn, self, context); + + return generator; + } + runtime.wrap = wrap; + + // Try/catch helper to minimize deoptimizations. Returns a completion + // record like context.tryEntries[i].completion. This interface could + // have been (and was previously) designed to take a closure to be + // invoked without arguments, but in all the cases we care about we + // already have an existing method we want to call, so there's no need + // to create a new function object. We can even get away with assuming + // the method takes exactly one argument, since that happens to be true + // in every case, so we don't have to touch the arguments object. The + // only additional allocation required is the completion record, which + // has a stable shape and so hopefully should be cheap to allocate. + function tryCatch(fn, obj, arg) { + try { + return { type: "normal", arg: fn.call(obj, arg) }; + } catch (err) { + return { type: "throw", arg: err }; + } + } + + var GenStateSuspendedStart = "suspendedStart"; + var GenStateSuspendedYield = "suspendedYield"; + var GenStateExecuting = "executing"; + var GenStateCompleted = "completed"; + + // Returning this object from the innerFn has the same effect as + // breaking out of the dispatch switch statement. + var ContinueSentinel = {}; + + // Dummy constructor functions that we use as the .constructor and + // .constructor.prototype properties for functions that return Generator + // objects. For full spec compliance, you may wish to configure your + // minifier not to mangle the names of these two functions. + function Generator() {} + function GeneratorFunction() {} + function GeneratorFunctionPrototype() {} + + var Gp = GeneratorFunctionPrototype.prototype = Generator.prototype; + GeneratorFunction.prototype = Gp.constructor = GeneratorFunctionPrototype; + GeneratorFunctionPrototype.constructor = GeneratorFunction; + GeneratorFunction.displayName = "GeneratorFunction"; + + // Helper for defining the .next, .throw, and .return methods of the + // Iterator interface in terms of a single ._invoke method. + function defineIteratorMethods(prototype) { + ["next", "throw", "return"].forEach(function(method) { + prototype[method] = function(arg) { + return this._invoke(method, arg); + }; + }); + } + + runtime.isGeneratorFunction = function(genFun) { + var ctor = typeof genFun === "function" && genFun.constructor; + return ctor + ? ctor === GeneratorFunction || + // For the native GeneratorFunction constructor, the best we can + // do is to check its .name property. + (ctor.displayName || ctor.name) === "GeneratorFunction" + : false; + }; + + runtime.mark = function(genFun) { + if (Object.setPrototypeOf) { + Object.setPrototypeOf(genFun, GeneratorFunctionPrototype); + } else { + genFun.__proto__ = GeneratorFunctionPrototype; + } + genFun.prototype = Object.create(Gp); + return genFun; + }; + + // Within the body of any async function, `await x` is transformed to + // `yield regeneratorRuntime.awrap(x)`, so that the runtime can test + // `value instanceof AwaitArgument` to determine if the yielded value is + // meant to be awaited. Some may consider the name of this method too + // cutesy, but they are curmudgeons. + runtime.awrap = function(arg) { + return new AwaitArgument(arg); + }; + + function AwaitArgument(arg) { + this.arg = arg; + } + + function AsyncIterator(generator) { + // This invoke function is written in a style that assumes some + // calling function (or Promise) will handle exceptions. + function invoke(method, arg) { + var result = generator[method](arg); + var value = result.value; + return value instanceof AwaitArgument + ? Promise.resolve(value.arg).then(invokeNext, invokeThrow) + : Promise.resolve(value).then(function(unwrapped) { + // When a yielded Promise is resolved, its final value becomes + // the .value of the Promise<{value,done}> result for the + // current iteration. If the Promise is rejected, however, the + // result for this iteration will be rejected with the same + // reason. Note that rejections of yielded Promises are not + // thrown back into the generator function, as is the case + // when an awaited Promise is rejected. This difference in + // behavior between yield and await is important, because it + // allows the consumer to decide what to do with the yielded + // rejection (swallow it and continue, manually .throw it back + // into the generator, abandon iteration, whatever). With + // await, by contrast, there is no opportunity to examine the + // rejection reason outside the generator function, so the + // only option is to throw it from the await expression, and + // let the generator function handle the exception. + result.value = unwrapped; + return result; + }); + } + + if (typeof process === "object" && process.domain) { + invoke = process.domain.bind(invoke); + } + + var invokeNext = invoke.bind(generator, "next"); + var invokeThrow = invoke.bind(generator, "throw"); + var invokeReturn = invoke.bind(generator, "return"); + var previousPromise; + + function enqueue(method, arg) { + function callInvokeWithMethodAndArg() { + return invoke(method, arg); + } + + return previousPromise = + // If enqueue has been called before, then we want to wait until + // all previous Promises have been resolved before calling invoke, + // so that results are always delivered in the correct order. If + // enqueue has not been called before, then it is important to + // call invoke immediately, without waiting on a callback to fire, + // so that the async generator function has the opportunity to do + // any necessary setup in a predictable way. This predictability + // is why the Promise constructor synchronously invokes its + // executor callback, and why async functions synchronously + // execute code before the first await. Since we implement simple + // async functions in terms of async generators, it is especially + // important to get this right, even though it requires care. + previousPromise ? previousPromise.then( + callInvokeWithMethodAndArg, + // Avoid propagating failures to Promises returned by later + // invocations of the iterator. + callInvokeWithMethodAndArg + ) : new Promise(function (resolve) { + resolve(callInvokeWithMethodAndArg()); + }); + } + + // Define the unified helper method that is used to implement .next, + // .throw, and .return (see defineIteratorMethods). + this._invoke = enqueue; + } + + defineIteratorMethods(AsyncIterator.prototype); + + // Note that simple async functions are implemented on top of + // AsyncIterator objects; they just return a Promise for the value of + // the final result produced by the iterator. + runtime.async = function(innerFn, outerFn, self, tryLocsList) { + var iter = new AsyncIterator( + wrap(innerFn, outerFn, self, tryLocsList) + ); + + return runtime.isGeneratorFunction(outerFn) + ? iter // If outerFn is a generator, return the full iterator. + : iter.next().then(function(result) { + return result.done ? result.value : iter.next(); + }); + }; + + function makeInvokeMethod(innerFn, self, context) { + var state = GenStateSuspendedStart; + + return function invoke(method, arg) { + if (state === GenStateExecuting) { + throw new Error("Generator is already running"); + } + + if (state === GenStateCompleted) { + if (method === "throw") { + throw arg; + } + + // Be forgiving, per 25.3.3.3.3 of the spec: + // https://people.mozilla.org/~jorendorff/es6-draft.html#sec-generatorresume + return doneResult(); + } + + while (true) { + var delegate = context.delegate; + if (delegate) { + if (method === "return" || + (method === "throw" && delegate.iterator[method] === undefined)) { + // A return or throw (when the delegate iterator has no throw + // method) always terminates the yield* loop. + context.delegate = null; + + // If the delegate iterator has a return method, give it a + // chance to clean up. + var returnMethod = delegate.iterator["return"]; + if (returnMethod) { + var record = tryCatch(returnMethod, delegate.iterator, arg); + if (record.type === "throw") { + // If the return method threw an exception, let that + // exception prevail over the original return or throw. + method = "throw"; + arg = record.arg; + continue; + } + } + + if (method === "return") { + // Continue with the outer return, now that the delegate + // iterator has been terminated. + continue; + } + } + + var record = tryCatch( + delegate.iterator[method], + delegate.iterator, + arg + ); + + if (record.type === "throw") { + context.delegate = null; + + // Like returning generator.throw(uncaught), but without the + // overhead of an extra function call. + method = "throw"; + arg = record.arg; + continue; + } + + // Delegate generator ran and handled its own exceptions so + // regardless of what the method was, we continue as if it is + // "next" with an undefined arg. + method = "next"; + arg = undefined; + + var info = record.arg; + if (info.done) { + context[delegate.resultName] = info.value; + context.next = delegate.nextLoc; + } else { + state = GenStateSuspendedYield; + return info; + } + + context.delegate = null; + } + + if (method === "next") { + if (state === GenStateSuspendedYield) { + context.sent = arg; + } else { + context.sent = undefined; + } + + } else if (method === "throw") { + if (state === GenStateSuspendedStart) { + state = GenStateCompleted; + throw arg; + } + + if (context.dispatchException(arg)) { + // If the dispatched exception was caught by a catch block, + // then let that catch block handle the exception normally. + method = "next"; + arg = undefined; + } + + } else if (method === "return") { + context.abrupt("return", arg); + } + + state = GenStateExecuting; + + var record = tryCatch(innerFn, self, context); + if (record.type === "normal") { + // If an exception is thrown from innerFn, we leave state === + // GenStateExecuting and loop back for another invocation. + state = context.done + ? GenStateCompleted + : GenStateSuspendedYield; + + var info = { + value: record.arg, + done: context.done + }; + + if (record.arg === ContinueSentinel) { + if (context.delegate && method === "next") { + // Deliberately forget the last sent value so that we don't + // accidentally pass it on to the delegate. + arg = undefined; + } + } else { + return info; + } + + } else if (record.type === "throw") { + state = GenStateCompleted; + // Dispatch the exception by looping back around to the + // context.dispatchException(arg) call above. + method = "throw"; + arg = record.arg; + } + } + }; + } + + // Define Generator.prototype.{next,throw,return} in terms of the + // unified ._invoke helper method. + defineIteratorMethods(Gp); + + Gp[iteratorSymbol] = function() { + return this; + }; + + Gp.toString = function() { + return "[object Generator]"; + }; + + function pushTryEntry(locs) { + var entry = { tryLoc: locs[0] }; + + if (1 in locs) { + entry.catchLoc = locs[1]; + } + + if (2 in locs) { + entry.finallyLoc = locs[2]; + entry.afterLoc = locs[3]; + } + + this.tryEntries.push(entry); + } + + function resetTryEntry(entry) { + var record = entry.completion || {}; + record.type = "normal"; + delete record.arg; + entry.completion = record; + } + + function Context(tryLocsList) { + // The root entry object (effectively a try statement without a catch + // or a finally block) gives us a place to store values thrown from + // locations where there is no enclosing try statement. + this.tryEntries = [{ tryLoc: "root" }]; + tryLocsList.forEach(pushTryEntry, this); + this.reset(true); + } + + runtime.keys = function(object) { + var keys = []; + for (var key in object) { + keys.push(key); + } + keys.reverse(); + + // Rather than returning an object with a next method, we keep + // things simple and return the next function itself. + return function next() { + while (keys.length) { + var key = keys.pop(); + if (key in object) { + next.value = key; + next.done = false; + return next; + } + } + + // To avoid creating an additional object, we just hang the .value + // and .done properties off the next function object itself. This + // also ensures that the minifier will not anonymize the function. + next.done = true; + return next; + }; + }; + + function values(iterable) { + if (iterable) { + var iteratorMethod = iterable[iteratorSymbol]; + if (iteratorMethod) { + return iteratorMethod.call(iterable); + } + + if (typeof iterable.next === "function") { + return iterable; + } + + if (!isNaN(iterable.length)) { + var i = -1, next = function next() { + while (++i < iterable.length) { + if (hasOwn.call(iterable, i)) { + next.value = iterable[i]; + next.done = false; + return next; + } + } + + next.value = undefined; + next.done = true; + + return next; + }; + + return next.next = next; + } + } + + // Return an iterator with no values. + return { next: doneResult }; + } + runtime.values = values; + + function doneResult() { + return { value: undefined, done: true }; + } + + Context.prototype = { + constructor: Context, + + reset: function(skipTempReset) { + this.prev = 0; + this.next = 0; + this.sent = undefined; + this.done = false; + this.delegate = null; + + this.tryEntries.forEach(resetTryEntry); + + if (!skipTempReset) { + for (var name in this) { + // Not sure about the optimal order of these conditions: + if (name.charAt(0) === "t" && + hasOwn.call(this, name) && + !isNaN(+name.slice(1))) { + this[name] = undefined; + } + } + } + }, + + stop: function() { + this.done = true; + + var rootEntry = this.tryEntries[0]; + var rootRecord = rootEntry.completion; + if (rootRecord.type === "throw") { + throw rootRecord.arg; + } + + return this.rval; + }, + + dispatchException: function(exception) { + if (this.done) { + throw exception; + } + + var context = this; + function handle(loc, caught) { + record.type = "throw"; + record.arg = exception; + context.next = loc; + return !!caught; + } + + for (var i = this.tryEntries.length - 1; i >= 0; --i) { + var entry = this.tryEntries[i]; + var record = entry.completion; + + if (entry.tryLoc === "root") { + // Exception thrown outside of any try block that could handle + // it, so set the completion value of the entire function to + // throw the exception. + return handle("end"); + } + + if (entry.tryLoc <= this.prev) { + var hasCatch = hasOwn.call(entry, "catchLoc"); + var hasFinally = hasOwn.call(entry, "finallyLoc"); + + if (hasCatch && hasFinally) { + if (this.prev < entry.catchLoc) { + return handle(entry.catchLoc, true); + } else if (this.prev < entry.finallyLoc) { + return handle(entry.finallyLoc); + } + + } else if (hasCatch) { + if (this.prev < entry.catchLoc) { + return handle(entry.catchLoc, true); + } + + } else if (hasFinally) { + if (this.prev < entry.finallyLoc) { + return handle(entry.finallyLoc); + } + + } else { + throw new Error("try statement without catch or finally"); + } + } + } + }, + + abrupt: function(type, arg) { + for (var i = this.tryEntries.length - 1; i >= 0; --i) { + var entry = this.tryEntries[i]; + if (entry.tryLoc <= this.prev && + hasOwn.call(entry, "finallyLoc") && + this.prev < entry.finallyLoc) { + var finallyEntry = entry; + break; + } + } + + if (finallyEntry && + (type === "break" || + type === "continue") && + finallyEntry.tryLoc <= arg && + arg <= finallyEntry.finallyLoc) { + // Ignore the finally entry if control is not jumping to a + // location outside the try/catch block. + finallyEntry = null; + } + + var record = finallyEntry ? finallyEntry.completion : {}; + record.type = type; + record.arg = arg; + + if (finallyEntry) { + this.next = finallyEntry.finallyLoc; + } else { + this.complete(record); + } + + return ContinueSentinel; + }, + + complete: function(record, afterLoc) { + if (record.type === "throw") { + throw record.arg; + } + + if (record.type === "break" || + record.type === "continue") { + this.next = record.arg; + } else if (record.type === "return") { + this.rval = record.arg; + this.next = "end"; + } else if (record.type === "normal" && afterLoc) { + this.next = afterLoc; + } + }, + + finish: function(finallyLoc) { + for (var i = this.tryEntries.length - 1; i >= 0; --i) { + var entry = this.tryEntries[i]; + if (entry.finallyLoc === finallyLoc) { + this.complete(entry.completion, entry.afterLoc); + resetTryEntry(entry); + return ContinueSentinel; + } + } + }, + + "catch": function(tryLoc) { + for (var i = this.tryEntries.length - 1; i >= 0; --i) { + var entry = this.tryEntries[i]; + if (entry.tryLoc === tryLoc) { + var record = entry.completion; + if (record.type === "throw") { + var thrown = record.arg; + resetTryEntry(entry); + } + return thrown; + } + } + + // The context.catch method must only be called with a location + // argument that corresponds to a known catch block. + throw new Error("illegal catch attempt"); + }, + + delegateYield: function(iterable, resultName, nextLoc) { + this.delegate = { + iterator: values(iterable), + resultName: resultName, + nextLoc: nextLoc + }; + + return ContinueSentinel; + } + }; +})( + // Among the various tricks for obtaining a reference to the global + // object, this seems to be the most reliable technique that does not + // use indirect eval (which violates Content Security Policy). + typeof global === "object" ? global : + typeof window === "object" ? window : + typeof self === "object" ? self : this +); diff --git a/packages/babel-plugin-transform-regenerator/test/async.es6.js b/packages/babel-plugin-transform-regenerator/test/async.es6.js new file mode 100644 index 0000000000..587e9b5f00 --- /dev/null +++ b/packages/babel-plugin-transform-regenerator/test/async.es6.js @@ -0,0 +1,493 @@ +/** + * 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"); + +describe("async functions and await expressions", function() { + Promise = require("promise"); + + 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"); + }); + }); + + describe("Promise", function() { + it("should be defined globally", function() { + var global = Function("return this")(); + assert.ok("Promise" in global); + assert.strictEqual(global.Promise, Promise); + }); + + it("should be a function", function() { + assert.strictEqual(typeof Promise, "function"); + }); + }); + + describe("no-await async function", function() { + it("should return a Promise", function(done) { + var called = false; + + async function noAwait(value) { + called = true; + return value; + } + + var promise = noAwait("asdf"); + assert.strictEqual(called, true); + + promise.then(function(value) { + assert.strictEqual(called, true); + assert.strictEqual(value, "asdf"); + done(); + }).catch(done); + }); + }); + + describe("one-await async function", function() { + it("should finish asynchronously", function(done) { + var flag1 = false; + var flag2 = false; + + async function oneAwait(value) { + flag1 = true; + var result = await value; + flag2 = true; + return result; + } + + var promise = oneAwait("asdf"); + assert.strictEqual(flag1, true); + assert.strictEqual(flag2, false); + + promise.then(function(value) { + assert.strictEqual(flag2, true); + assert.strictEqual(value, "asdf"); + done(); + }).catch(done); + }); + }); + + describe("nested async function calls", function() { + it("should evaluate in the right order", function(done) { + var markers = []; + + async function innerMost(marker) { + markers.push(marker); + return await marker; + } + + async function inner(marker) { + markers.push(marker); + + assert.strictEqual( + await innerMost(marker + 1), + marker + 1 + ); + + markers.push(marker + 2); + + assert.strictEqual( + await innerMost(marker + 3), + marker + 3 + ); + + markers.push(marker + 4); + } + + async function outer() { + markers.push(0); + await inner(1); + markers.push(6); + await inner(7); + markers.push(12); + } + + outer().then(function() { + var expected = []; + for (var i = 0; i <= 12; ++i) + expected.push(i); + assert.deepEqual(markers, expected); + done(); + }).catch(done); + }); + }); + + describe("dependent promises", function() { + it("should be awaitable out of order", function(done) { + async function outer(value) { + var resolved = false; + var p1 = new Promise(function(resolve) { + setTimeout(function() { + resolve(value + 1); + resolved = true; + }, 0); + }); + + assert.strictEqual(resolved, false); + + var v2 = await p1.then(function(value) { + return value + 1; + }); + + assert.strictEqual(resolved, true); + + var v1 = await p1; + + return [v1, v2]; + } + + outer(1).then(function(pair) { + assert.deepEqual(pair, [2, 3]); + done(); + }).catch(done); + }); + }); + + describe("rejected promises", function() { + it("should cause await expressions to throw", function(done) { + var error = new Error("rejected"); + + async function f(arg) { + try { + return await arg; + } catch (e) { + assert.strictEqual(e, error); + return "did throw"; + } + } + + Promise.all([ + f(Promise.reject(error)), + f(Promise.resolve("did not throw")) + ]).then(function(results) { + assert.deepEqual(results, [ + "did throw", + "did not throw" + ]); + done(); + }).catch(done); + }); + + it("should be returned by exceptional async functions", function(done) { + var error = new Error("rejected"); + + async function e(arg) { + if (arg) { + throw arg; + } + return "did not throw"; + } + + async function f(arg) { + return await e(arg); + } + + async function g(arg) { + return await f(arg); + } + + async function h(arg) { + return await Promise.all([ + g(arg), + Promise.resolve("dummy") + ]); + } + + Promise.all([ + h(error).then(function() { + done(new Error("should not have resolved")); + }, function(e) { + assert.strictEqual(e, error); + return "ok1"; + }), + h(null).then(function(result) { + assert.deepEqual(result, [ + "did not throw", + "dummy" + ]); + return "ok2"; + }) + ]).then(function(results) { + assert.deepEqual(results, ["ok1", "ok2"]); + done(); + }).catch(done); + }); + + it("should propagate failure when returned", function() { + var rejection = new Error("rejection"); + + async function f() { + return new Promise(function(resolve, reject) { + reject(rejection); + }); + } + + return f().then(function(result) { + assert.ok(false, "should have been rejected"); + }, function(error) { + assert.strictEqual(error, rejection); + }); + }); + }); + + describe("async function expressions", function() { + it("should be allowed", function(done) { + (async function(arg) { + return await arg; + })(Promise.resolve(1234)).then(function(value) { + assert.strictEqual(value, 1234); + done(); + }).catch(done); + }); + }); +}); + +describe("async generator functions", function() { + it("should return a working AsyncIterator", function() { + var markers = []; + + async function *gen(arg) { + markers.push(0); + var sent = yield arg; + markers.push(1); + var result = await sent; + markers.push(2); + assert.strictEqual(await (yield "second"), "sent after second"); + markers.push(3); + return result; + } + + var iter = gen("initial argument"); + assert.deepEqual(markers, []); + + var firstPromise = iter.next(); + assert.deepEqual(markers, [0]); + + return firstPromise.then(function(firstResult) { + assert.deepEqual(firstResult, { + value: "initial argument", + done: false + }); + + assert.deepEqual(markers, [0]); + + return iter.next(new Promise(function(resolve) { + setTimeout(resolve, 100); + }).then(function() { + assert.deepEqual(markers, [0, 1]); + return "will become final result"; + })); + + }).then(function(secondResult) { + assert.deepEqual(secondResult, { + value: "second", + done: false + }); + + assert.deepEqual(markers, [0, 1, 2]); + + return iter.next("sent after second"); + + }).then(function(finalResult) { + assert.deepEqual(markers, [0, 1, 2, 3]); + assert.deepEqual(finalResult, { + value: "will become final result", + done: true + }); + }); + }); + + it("should keep results in order", function() { + async function *range(limit) { + var before = []; + var after = []; + for (var i = 0; i < limit; ++i) { + before.push(i); + yield i; + after.push(i); + } + assert.deepEqual(before, after); + return before; + } + + var limit = 10; + var iter = range(limit); + var promises = []; + var results = []; + + for (var i = 0; i < limit; ++i) { + var promise = iter.next(); + promises.push(promise); + + promise.then(function(result) { + assert.strictEqual(result.done, false); + results.push(result); + }); + } + + assert.deepEqual(results, []); + + return Promise.all(promises).then(function(promiseResults) { + assert.deepEqual(results, promiseResults); + + return iter.next(); + + }).then(function(finalResult) { + assert.deepEqual(results.map(function(result) { + return result.value; + }), finalResult.value); + + assert.strictEqual(finalResult.done, true); + }); + }); + + it("should be able to handle many awaits", function() { + var awaitCount = 0; + + function countAwait(i) { + return Promise.resolve(i).then(function() { + ++awaitCount; + }); + } + + async function *gen(limit) { + await countAwait(0); + yield 1; + await countAwait(2); + await countAwait(3); + yield 4; + await countAwait(5); + await countAwait(6); + await countAwait(7); + yield 8; + for (var i = 0; i < limit; ++i) { + await countAwait(i); + } + return "done"; + } + + var iter = gen(100); + + return iter.next().then(function(result) { + assert.strictEqual(awaitCount, 1); + + assert.deepEqual(result, { + value: 1, + done: false + }); + + return iter.next(); + + }).then(function(result) { + assert.strictEqual(awaitCount, 3); + + assert.deepEqual(result, { + value: 4, + done: false + }); + + return iter.next(); + + }).then(function(result) { + assert.strictEqual(awaitCount, 6); + + assert.deepEqual(result, { + value: 8, + done: false + }); + + return iter.next(); + + }).then(function(result) { + assert.strictEqual(awaitCount, 6 + 100); + + assert.deepEqual(result, { + value: "done", + done: true + }); + + return iter.next(); + + }).then(function(result) { + assert.deepEqual(result, { + value: void 0, + done: true + }); + }); + }); + + it("should not propagate exceptions between iterations", function() { + async function *gen() { + yield 1; + yield 2; + } + + var iter = gen(); + + return iter.next().then(function(result) { + assert.deepEqual(result, { + value: 1, + done: false + }); + + return iter.throw(new Error("thrown from first yield")); + + }).then(function() { + throw new Error("should have thrown"); + + }, function(error) { + assert.strictEqual(error.message, "thrown from first yield"); + return iter.next(); + + }).then(function(result) { + assert.deepEqual(result, { + value: void 0, + done: true + }); + }); + }); + + it("should allow yielding a rejected Promise", function() { + var yielded = new Error("yielded rejection"); + var returned = new Error("returned rejection"); + + async function *gen() { + assert.strictEqual(yield Promise.reject(yielded), "first sent"); + assert.strictEqual(yield "middle", "second sent"); + return Promise.reject(returned); + } + + var iter = gen(); + + return iter.next().then(function(result) { + assert.ok(false, "should have yielded a rejected Promise"); + }, function(error) { + assert.strictEqual(error, yielded); + return iter.next("first sent"); + }).then(function(result) { + assert.deepEqual(result, { + value: "middle", + done: false + }); + return iter.next("second sent"); + }).then(function(result) { + assert.ok(false, "should have returned a rejected Promise"); + }, function(error) { + assert.strictEqual(error, returned); + }); + }); +}); diff --git a/packages/babel-plugin-transform-regenerator/test/index.html b/packages/babel-plugin-transform-regenerator/test/index.html new file mode 100644 index 0000000000..ace946a498 --- /dev/null +++ b/packages/babel-plugin-transform-regenerator/test/index.html @@ -0,0 +1,18 @@ + + + + Mocha + + + + + +
+ + + + + + diff --git a/packages/babel-plugin-transform-regenerator/test/nothing-to-transform.js b/packages/babel-plugin-transform-regenerator/test/nothing-to-transform.js new file mode 100644 index 0000000000..3ab38103d4 --- /dev/null +++ b/packages/babel-plugin-transform-regenerator/test/nothing-to-transform.js @@ -0,0 +1,3 @@ +function asdf() { + return "no generators or a-s-y-n-c functions to see here"; +} diff --git a/packages/babel-plugin-transform-regenerator/test/run.js b/packages/babel-plugin-transform-regenerator/test/run.js new file mode 100644 index 0000000000..a2c3419c19 --- /dev/null +++ b/packages/babel-plugin-transform-regenerator/test/run.js @@ -0,0 +1,154 @@ +/** + * 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 fs = require("fs"); +var path = require("path"); +var semver = require("semver"); +var spawn = require("child_process").spawn; +var regenerator = require("../main"); +var mochaDir = path.dirname(require.resolve("mocha")); + +function convert(es6File, es5File, callback) { + fs.readFile(es6File, "utf-8", function(err, es6) { + if (err) { + return callback(err); + } + + fs.writeFile(es5File, regenerator.compile(es6).code, callback); + }); +} + +function bundle(es5Files, browserFile, callback) { + var bundle = require("browserify")(); + es5Files.forEach(bundle.add, bundle); + bundle.bundle(function(err, src) { + if (err) { + return callback(err); + } + fs.writeFile(browserFile, src, callback); + }); +} + +var queue = []; +function enqueue(cmd, args, quiet) { + queue.push({ + cmd: cmd, + args: args || [], + quiet: !!quiet + }); +} + +function flush() { + var entry = queue.shift(); + if (entry) { + var cmd = entry.cmd; + if (typeof cmd === "function") { + cmd.apply(null, entry.args.concat(asyncCallback)); + } else { + spawn(cmd, entry.args, { + stdio: [ + process.stdin, + entry.quiet ? "ignore" : process.stdout, + process.stderr + ] + }).on("exit", asyncCallback); + } + } +} + +function asyncCallback(err) { + if (err) { + console.error("process exited abnormally:", err); + process.exit(typeof err === "number" ? err : -1); + } else { + process.nextTick(flush); + } +} + +function makeMochaCopyFunction(fileName) { + return function copy(callback) { + var src = path.join(mochaDir, fileName); + var dst = path.join(__dirname, fileName); + fs.unlink(dst, function() { + fs.symlink(src, dst, callback); + }); + }; +} + +if (semver.gte(process.version, "0.11.2")) { + enqueue("mocha", [ + "--harmony", + "--reporter", "spec", + "--require", "./runtime", + "./test/tests.es6.js" + ]); +} + +enqueue(convert, [ + "./test/tests.es6.js", + "./test/tests.es5.js" +]); + +enqueue(convert, [ + "./test/async.es6.js", + "./test/async.es5.js" +]); + +enqueue(makeMochaCopyFunction("mocha.js")); +enqueue(makeMochaCopyFunction("mocha.css")); + +// uglify-js does not work properly due to Node 0.11.7 bug. +// (https://github.com/joyent/node/issues/6235) +if (!semver.eq(process.version, "0.11.7")) { + try { + require.resolve("browserify"); // Throws if missing. + enqueue(bundle, [ + ["./runtime.js", + "./test/tests.es5.js", + "./test/async.es5.js"], + "./test/tests.browser.js" + ]); + } catch (ignored) { + console.error("browserify not installed; skipping bundle step"); + } +} + +enqueue("mocha", [ + "--reporter", "spec", + "--require", "./runtime", + "./test/tests.es5.js", + "./test/async.es5.js", + "./test/tests.transform.js" +]); + +// Run command-line tool with available options to make sure it works. + +enqueue("./bin/regenerator", [ + "./test/async.es5.js" +], true); + +enqueue("./bin/regenerator", [ + "--include-runtime", + "./test/async.es5.js" +], true); + +// Make sure we run the command-line tool on a file that does not need any +// transformation, too. + +enqueue("./bin/regenerator", [ + "./test/nothing-to-transform.js" +], true); + +enqueue("./bin/regenerator", [ + "--include-runtime", + "./test/nothing-to-transform.js" +], true); + +flush(); diff --git a/packages/babel-plugin-transform-regenerator/test/tests.es6.js b/packages/babel-plugin-transform-regenerator/test/tests.es6.js new file mode 100644 index 0000000000..3df59e76e9 --- /dev/null +++ b/packages/babel-plugin-transform-regenerator/test/tests.es6.js @@ -0,0 +1,2560 @@ +/** + * 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 + }); + }); +}); diff --git a/packages/babel-plugin-transform-regenerator/test/tests.transform.js b/packages/babel-plugin-transform-regenerator/test/tests.transform.js new file mode 100644 index 0000000000..e67e1d9569 --- /dev/null +++ b/packages/babel-plugin-transform-regenerator/test/tests.transform.js @@ -0,0 +1,47 @@ +var regenerator = require(".."); +var babylon = require("babylon"); +var assert = require("assert"); +var babel = require("babel-core"); +var t = require("babel-types"); + +describe("_blockHoist nodes", function() { + it("should be hoisted to the outer body", function() { + var foo; + var names = []; + var ast = babylon.parse([ + "function *foo(doNotHoistMe, hoistMe) {", + " var sent = yield doNotHoistMe();", + " hoistMe();", + " names.push(sent);", + " return 123;", + "}" + ].join("\n")); + + var hoistMeStmt = ast.program.body[0].body.body[1]; + t.assertExpressionStatement(hoistMeStmt); + t.assertCallExpression(hoistMeStmt.expression); + t.assertIdentifier(hoistMeStmt.expression.callee); + assert.strictEqual(hoistMeStmt.expression.callee.name, "hoistMe"); + + hoistMeStmt._blockHoist = 1; + + eval(babel.transformFromAst(ast, null, { plugins: [regenerator] }).code); + + assert.strictEqual(typeof foo, "function"); + assert.ok(regeneratorRuntime.isGeneratorFunction(foo)); + assert.strictEqual(names.length, 0); + + var g = foo(function doNotHoistMe() { + names.push("doNotHoistMe"); + return "yielded"; + }, function hoistMe() { + names.push("hoistMe"); + }); + + assert.deepEqual(names, ["hoistMe"]); + assert.deepEqual(g.next(), { value: "yielded", done: false }); + assert.deepEqual(names, ["hoistMe", "doNotHoistMe"]); + assert.deepEqual(g.next("oyez"), { value: 123, done: true }); + assert.deepEqual(names, ["hoistMe", "doNotHoistMe", "oyez"]); + }); +});