├── .babelrc ├── .gitignore ├── .npmignore ├── test ├── fixtures │ ├── empty-func │ │ ├── actual.js │ │ ├── .babelrc │ │ └── expected.js │ ├── for-in │ │ ├── .babelrc │ │ ├── actual.js │ │ └── expected.js │ ├── guard │ │ ├── .babelrc │ │ ├── actual.js │ │ └── expected.js │ ├── switch │ │ ├── .babelrc │ │ ├── actual.js │ │ └── expected.js │ ├── for-loop │ │ ├── .babelrc │ │ ├── actual.js │ │ └── expected.js │ ├── if-flow │ │ ├── .babelrc │ │ ├── actual.js │ │ └── expected.js │ ├── lazy-op │ │ ├── .babelrc │ │ ├── actual.js │ │ └── expected.js │ ├── no-block │ │ ├── .babelrc │ │ ├── actual.js │ │ └── expected.js │ ├── normal-loop │ │ ├── .babelrc │ │ ├── actual.js │ │ └── expected.js │ ├── try-catch │ │ ├── .babelrc │ │ ├── this-arguments │ │ │ ├── .babelrc │ │ │ ├── actual.js │ │ │ └── expected.js │ │ ├── actual.js │ │ └── expected.js │ ├── try-finally │ │ ├── .babelrc │ │ ├── actual.js │ │ └── expected.js │ ├── while-loop │ │ ├── .babelrc │ │ ├── actual.js │ │ └── expected.js │ ├── await-func-call │ │ ├── .babelrc │ │ ├── actual.js │ │ └── expected.js │ ├── complex-loop │ │ ├── .babelrc │ │ ├── actual.js │ │ └── expected.js │ ├── complex-trycatch │ │ ├── .babelrc │ │ ├── actual.js │ │ └── expected.js │ ├── dowhile-loop │ │ ├── .babelrc │ │ ├── actual.js │ │ └── expected.js │ ├── loop-with-labels │ │ ├── .babelrc │ │ ├── actual.js │ │ └── expected.js │ ├── normal-nested-expr │ │ ├── actual.js │ │ ├── .babelrc │ │ └── expected.js │ ├── normal-sequence-expr │ │ ├── actual.js │ │ ├── .babelrc │ │ └── expected.js │ ├── normal-trycatch │ │ ├── .babelrc │ │ ├── actual.js │ │ └── expected.js │ ├── return-in-loop │ │ ├── .babelrc │ │ ├── actual.js │ │ └── expected.js │ ├── sequence-expr │ │ ├── .babelrc │ │ ├── actual.js │ │ └── expected.js │ ├── simple-nested-if │ │ ├── .babelrc │ │ ├── actual.js │ │ └── expected.js │ ├── simple-switch │ │ ├── .babelrc │ │ ├── actual.js │ │ └── expected.js │ ├── ternary-expr │ │ ├── .babelrc │ │ ├── actual.js │ │ └── expected.js │ ├── this-arguments │ │ ├── .babelrc │ │ ├── actual.js │ │ └── expected.js │ ├── array-expr-exec-order │ │ ├── .babelrc │ │ ├── actual.js │ │ └── expected.js │ ├── await-inside-await │ │ ├── .babelrc │ │ ├── actual.js │ │ └── expected.js │ ├── call-expr-exec-order │ │ ├── .babelrc │ │ ├── actual.js │ │ └── expected.js │ ├── nested-if-with-else │ │ ├── .babelrc │ │ ├── actual.js │ │ └── expected.js │ ├── nested-try-finally │ │ ├── .babelrc │ │ ├── actual.js │ │ └── expected.js │ ├── normal-conditional │ │ ├── .babelrc │ │ ├── actual.js │ │ └── expected.js │ ├── resp-name-reserved │ │ ├── .babelrc │ │ ├── actual.js │ │ └── expected.js │ ├── simple-conditional │ │ ├── .babelrc │ │ ├── actual.js │ │ └── expected.js │ ├── try-catch-finally │ │ ├── .babelrc │ │ ├── actual.js │ │ └── expected.js │ ├── binary-expr-exec-order │ │ ├── .babelrc │ │ ├── actual.js │ │ └── expected.js │ ├── for-in-with-external-var │ │ ├── .babelrc │ │ ├── actual.js │ │ └── expected.js │ ├── object-expr-exec-order │ │ ├── .babelrc │ │ ├── actual.js │ │ └── expected.js │ ├── pouchdb-auth │ │ ├── .babelrc │ │ ├── actual.js │ │ └── expected.js │ ├── keep-function-names-in-object-values │ │ ├── .babelrc │ │ ├── actual.js │ │ └── expected.js │ └── .eslintrc.json └── index.js ├── kneden ├── index.js ├── README.md └── package.json ├── .eslintrc.json ├── .travis.yml ├── LICENSE.txt ├── src ├── utils.js ├── index.js ├── promisechain.js ├── ifrefactor.js ├── looprefactor.js └── refactor.js ├── package.json └── README.md /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015"] 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | lib 3 | coverage 4 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | src 3 | coverage 4 | -------------------------------------------------------------------------------- /test/fixtures/empty-func/actual.js: -------------------------------------------------------------------------------- 1 | (async function () {}) 2 | -------------------------------------------------------------------------------- /kneden/index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('babel-plugin-async-to-promises'); 2 | -------------------------------------------------------------------------------- /test/fixtures/for-in/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | ["../../../src"] 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/guard/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | ["../../../src"] 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/switch/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | ["../../../src"] 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/empty-func/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | ["../../../src"] 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/empty-func/expected.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | return Promise.resolve(); 3 | }); 4 | -------------------------------------------------------------------------------- /test/fixtures/for-loop/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | ["../../../src"] 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/if-flow/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | ["../../../src"] 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/lazy-op/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | ["../../../src"] 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/no-block/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | ["../../../src"] 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/normal-loop/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | ["../../../src"] 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/try-catch/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | ["../../../src"] 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/try-finally/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | ["../../../src"] 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/while-loop/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | ["../../../src"] 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/await-func-call/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | ["../../../src"] 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/await-func-call/actual.js: -------------------------------------------------------------------------------- 1 | async function test() { 2 | await db.destroy(); 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/complex-loop/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | ["../../../src"] 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/complex-trycatch/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | ["../../../src"] 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/dowhile-loop/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | ["../../../src"] 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/lazy-op/actual.js: -------------------------------------------------------------------------------- 1 | async function test() { 2 | return (a() || await b()).ok; 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/loop-with-labels/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | ["../../../src"] 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/normal-nested-expr/actual.js: -------------------------------------------------------------------------------- 1 | async function a() { 2 | function b() { 3 | } 4 | } 5 | -------------------------------------------------------------------------------- /test/fixtures/normal-sequence-expr/actual.js: -------------------------------------------------------------------------------- 1 | async function test() { 2 | return a, b, c; 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/normal-trycatch/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | ["../../../src"] 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/return-in-loop/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | ["../../../src"] 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/sequence-expr/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | ["../../../src"] 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/simple-nested-if/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | ["../../../src"] 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/simple-switch/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | ["../../../src"] 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/ternary-expr/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | ["../../../src"] 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/this-arguments/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | ["../../../src"] 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/array-expr-exec-order/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | ["../../../src"] 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/await-inside-await/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | ["../../../src"] 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/call-expr-exec-order/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | ["../../../src"] 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/nested-if-with-else/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | ["../../../src"] 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/nested-try-finally/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | ["../../../src"] 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/normal-conditional/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | ["../../../src"] 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/normal-nested-expr/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | ["../../../src"] 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/normal-sequence-expr/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | ["../../../src"] 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/resp-name-reserved/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | ["../../../src"] 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/simple-conditional/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | ["../../../src"] 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/try-catch-finally/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | ["../../../src"] 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/binary-expr-exec-order/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | ["../../../src"] 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/for-in-with-external-var/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | ["../../../src"] 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/object-expr-exec-order/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | ["../../../src"] 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/pouchdb-auth/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015"], 3 | "plugins": ["../../../src"] 4 | } 5 | -------------------------------------------------------------------------------- /test/fixtures/try-catch/this-arguments/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | ["../../../src"] 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/keep-function-names-in-object-values/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | ["../../../src"] 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/normal-nested-expr/expected.js: -------------------------------------------------------------------------------- 1 | function a() { 2 | function b() {} 3 | return Promise.resolve(); 4 | } 5 | -------------------------------------------------------------------------------- /test/fixtures/sequence-expr/actual.js: -------------------------------------------------------------------------------- 1 | async function test() { 2 | test((await a(), await b(), c(), d(), await e())); 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/this-arguments/actual.js: -------------------------------------------------------------------------------- 1 | async function test() { 2 | console.log(this); 3 | console.log(arguments); 4 | } 5 | -------------------------------------------------------------------------------- /test/fixtures/array-expr-exec-order/actual.js: -------------------------------------------------------------------------------- 1 | async function test() { 2 | return [a(), await b(), c(), (await d()).ok]; 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/for-in/actual.js: -------------------------------------------------------------------------------- 1 | async function test(object) { 2 | for (var key in object) { 3 | await key; 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/call-expr-exec-order/actual.js: -------------------------------------------------------------------------------- 1 | async function test() { 2 | return test2(a(), await b(), c(), (await d()).ok); 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/try-catch/this-arguments/actual.js: -------------------------------------------------------------------------------- 1 | async function test() { 2 | console.log(this); 3 | console.log(arguments); 4 | } 5 | -------------------------------------------------------------------------------- /test/fixtures/guard/actual.js: -------------------------------------------------------------------------------- 1 | async function test(a, b) { 2 | if (a === b) { 3 | return; 4 | } 5 | 6 | await someOp((a + b) / 2); 7 | } 8 | -------------------------------------------------------------------------------- /test/fixtures/resp-name-reserved/actual.js: -------------------------------------------------------------------------------- 1 | async function test() { 2 | var _resp = 2; 3 | console.log(await x()); 4 | console.log(_resp); 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/for-in-with-external-var/actual.js: -------------------------------------------------------------------------------- 1 | async function test(object) { 2 | var key; 3 | for (key in object) { 4 | await key; 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /test/fixtures/no-block/actual.js: -------------------------------------------------------------------------------- 1 | async function test() { 2 | if (a()) (await b()); else if (c()) (await d()); 3 | 4 | while (c()) await d(); 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/normal-sequence-expr/expected.js: -------------------------------------------------------------------------------- 1 | function test() { 2 | return Promise.resolve().then(function () { 3 | return a, b, c; 4 | }); 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/ternary-expr/actual.js: -------------------------------------------------------------------------------- 1 | async function test() { 2 | var test = a() ? await b() : c(); 3 | return d() ? (await e()).ok : await f(); 4 | } 5 | -------------------------------------------------------------------------------- /test/fixtures/dowhile-loop/actual.js: -------------------------------------------------------------------------------- 1 | async function test() { 2 | var i = 0; 3 | do { 4 | await db.post({}); 5 | i++ 6 | } while (i < 11); 7 | } 8 | -------------------------------------------------------------------------------- /test/fixtures/normal-loop/actual.js: -------------------------------------------------------------------------------- 1 | async function test() { 2 | var i = 0; 3 | test: while (i < 10) { 4 | i++; 5 | continue test; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /test/fixtures/simple-nested-if/actual.js: -------------------------------------------------------------------------------- 1 | async function test() { 2 | if (a()) { 3 | if (b()) { 4 | return c(); 5 | } 6 | } 7 | return d(); 8 | } 9 | -------------------------------------------------------------------------------- /test/fixtures/try-finally/actual.js: -------------------------------------------------------------------------------- 1 | async function test() { 2 | try { 3 | console.log(await db.info()); 4 | } finally { 5 | await db.destroy(); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "eslint:recommended", 3 | "parser": "babel-eslint", 4 | "env": { 5 | "node": true, 6 | "es6": true 7 | } 8 | } 9 | 10 | -------------------------------------------------------------------------------- /test/fixtures/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../.eslintrc.json", 3 | "rules": { 4 | "no-unused-vars": 0, 5 | "no-undef": 0, 6 | "no-console": 0 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /test/fixtures/await-func-call/expected.js: -------------------------------------------------------------------------------- 1 | function test() { 2 | return Promise.resolve().then(function () { 3 | return db.destroy(); 4 | }).then(function () {}); 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/binary-expr-exec-order/actual.js: -------------------------------------------------------------------------------- 1 | /* eslint no-empty: 0 */ 2 | 3 | async function test() { 4 | if(c() === await d()) {} 5 | 6 | return a() + await b(); 7 | } 8 | -------------------------------------------------------------------------------- /test/fixtures/normal-trycatch/actual.js: -------------------------------------------------------------------------------- 1 | (async function () { 2 | try { 3 | a.b; 4 | } catch (err) { 5 | // reference error 6 | console.log(err); 7 | } 8 | }); 9 | -------------------------------------------------------------------------------- /test/fixtures/simple-conditional/actual.js: -------------------------------------------------------------------------------- 1 | async function test() { 2 | var a = await db.post({}); 3 | if (a) { 4 | await db.destroy(); 5 | } 6 | var b = 1 + 1; 7 | } 8 | -------------------------------------------------------------------------------- /test/fixtures/normal-conditional/actual.js: -------------------------------------------------------------------------------- 1 | async function test(a) { 2 | var b; 3 | if (a) { 4 | b = 3; 5 | } else { 6 | b = 4; 7 | } 8 | return b; 9 | } 10 | -------------------------------------------------------------------------------- /test/fixtures/object-expr-exec-order/actual.js: -------------------------------------------------------------------------------- 1 | async function test() { 2 | return { 3 | a: a(), 4 | b: await b(), 5 | c: c(), 6 | d: (await d()).ok 7 | }; 8 | } 9 | -------------------------------------------------------------------------------- /test/fixtures/await-inside-await/actual.js: -------------------------------------------------------------------------------- 1 | async function getDoc() { 2 | var doc = await db.get(await request('https://example.com/api/get-doc-id')); 3 | delete doc._rev; 4 | return doc; 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/try-catch/actual.js: -------------------------------------------------------------------------------- 1 | async function test() { 2 | try { 3 | going.to.fail; 4 | } catch (err) { 5 | await postErrorMessage('http://my.webservice/error', err); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /test/fixtures/lazy-op/expected.js: -------------------------------------------------------------------------------- 1 | function test() { 2 | return Promise.resolve().then(function () { 3 | return a() || b(); 4 | }).then(function (_resp) { 5 | return _resp.ok; 6 | }); 7 | } 8 | -------------------------------------------------------------------------------- /test/fixtures/guard/expected.js: -------------------------------------------------------------------------------- 1 | function test(a, b) { 2 | return Promise.resolve().then(function () { 3 | if (!(a === b)) { 4 | return someOp((a + b) / 2); 5 | } 6 | }).then(function () {}); 7 | } 8 | -------------------------------------------------------------------------------- /test/fixtures/return-in-loop/actual.js: -------------------------------------------------------------------------------- 1 | /* eslint no-constant-condition: 0 */ 2 | 3 | async function test() { 4 | while (true) { 5 | if (await a()) { 6 | return "now"; 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /test/fixtures/nested-if-with-else/actual.js: -------------------------------------------------------------------------------- 1 | async function test() { 2 | if (a) { 3 | if (b) { 4 | return c; 5 | } else { 6 | return d; 7 | } 8 | } else { 9 | return e; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /test/fixtures/simple-switch/actual.js: -------------------------------------------------------------------------------- 1 | async function test(n) { 2 | var a = 0; 3 | switch(n) { 4 | case 1: 5 | a = 2; 6 | break; 7 | case await getNum(): 8 | a = 3; 9 | } 10 | return a; 11 | } 12 | -------------------------------------------------------------------------------- /test/fixtures/for-loop/actual.js: -------------------------------------------------------------------------------- 1 | var request = require('request-promise'); 2 | async function test() { 3 | var pages = []; 4 | for (var i = 0; i < 10; i++) { 5 | pages.push(await request('https://example.com/page' + i)); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /test/fixtures/normal-loop/expected.js: -------------------------------------------------------------------------------- 1 | function test() { 2 | var i; 3 | return Promise.resolve().then(function () { 4 | i = 0; 5 | 6 | test: while (i < 10) { 7 | i++; 8 | continue test; 9 | } 10 | }); 11 | } 12 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - "0.10" 5 | - "stable" 6 | 7 | script: 8 | - npm run $COMMAND 9 | 10 | env: 11 | - COMMAND=test:lint 12 | - COMMAND=test:cov 13 | 14 | notifications: 15 | email: false 16 | 17 | -------------------------------------------------------------------------------- /test/fixtures/normal-conditional/expected.js: -------------------------------------------------------------------------------- 1 | function test(a) { 2 | var b; 3 | return Promise.resolve().then(function () { 4 | if (a) { 5 | b = 3; 6 | } else { 7 | b = 4; 8 | } 9 | return b; 10 | }); 11 | } 12 | -------------------------------------------------------------------------------- /test/fixtures/normal-trycatch/expected.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | return Promise.resolve().then(function () { 3 | try { 4 | a.b; 5 | } catch (err) { 6 | // reference error 7 | console.log(err); 8 | } 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /test/fixtures/this-arguments/expected.js: -------------------------------------------------------------------------------- 1 | function test() { 2 | var _this = this, 3 | _arguments = arguments; 4 | 5 | return Promise.resolve().then(function () { 6 | console.log(_this); 7 | console.log(_arguments); 8 | }); 9 | } 10 | -------------------------------------------------------------------------------- /test/fixtures/try-catch-finally/actual.js: -------------------------------------------------------------------------------- 1 | async function test() { 2 | try { 3 | await db.destroy(); 4 | } catch(err) { 5 | console.log(err); 6 | console.log(await db.post({})); 7 | } finally { 8 | await db.info(); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /test/fixtures/while-loop/actual.js: -------------------------------------------------------------------------------- 1 | var PouchDB = require('pouchdb'); 2 | 3 | async function test() { 4 | var db = new PouchDB('test'); 5 | while (i < 10) { 6 | i++; 7 | await db.put({_id: i}); 8 | } 9 | return await db.allDocs(); 10 | } 11 | -------------------------------------------------------------------------------- /test/fixtures/try-catch/this-arguments/expected.js: -------------------------------------------------------------------------------- 1 | function test() { 2 | var _this = this, 3 | _arguments = arguments; 4 | 5 | return Promise.resolve().then(function () { 6 | console.log(_this); 7 | console.log(_arguments); 8 | }); 9 | } 10 | -------------------------------------------------------------------------------- /test/fixtures/keep-function-names-in-object-values/actual.js: -------------------------------------------------------------------------------- 1 | async function fn() { 2 | var o1 = { 3 | a: function a() { 4 | console.log('o1.a'); 5 | } 6 | }; 7 | var o2 = { 8 | a: function a() { 9 | console.log('o2.a') 10 | } 11 | }; 12 | } 13 | -------------------------------------------------------------------------------- /test/fixtures/simple-nested-if/expected.js: -------------------------------------------------------------------------------- 1 | function test() { 2 | var _test; 3 | 4 | return Promise.resolve().then(function () { 5 | _test = a(); 6 | 7 | if (_test && b()) { 8 | return c(); 9 | } else { 10 | return d(); 11 | } 12 | }); 13 | } 14 | -------------------------------------------------------------------------------- /test/fixtures/resp-name-reserved/expected.js: -------------------------------------------------------------------------------- 1 | function test() { 2 | var _resp; 3 | 4 | return Promise.resolve().then(function () { 5 | _resp = 2; 6 | return x(); 7 | }).then(function (_resp2) { 8 | console.log(_resp2); 9 | console.log(_resp); 10 | }); 11 | } 12 | -------------------------------------------------------------------------------- /test/fixtures/complex-loop/actual.js: -------------------------------------------------------------------------------- 1 | async function test() { 2 | var i = 0; 3 | while (i < 10) { 4 | i++; 5 | if ((await db.put({_id: i})).ok) { 6 | continue; 7 | } 8 | if (i === 2) { 9 | break; 10 | } 11 | var a = await db.destroy(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /test/fixtures/array-expr-exec-order/expected.js: -------------------------------------------------------------------------------- 1 | function test() { 2 | return Promise.resolve().then(function () { 3 | return Promise.all([a(), b(), c(), Promise.resolve().then(function () { 4 | return d(); 5 | }).then(function (_resp) { 6 | return _resp.ok; 7 | })]); 8 | }); 9 | } 10 | -------------------------------------------------------------------------------- /test/fixtures/complex-trycatch/actual.js: -------------------------------------------------------------------------------- 1 | async function test() { 2 | var a = await a(); 3 | try { 4 | var b = new PouchDB('test2'); 5 | var c = await db.destroy(); 6 | } catch (err) { 7 | var d = await new PouchDB('test').destroy(); 8 | } 9 | var e = await b(); 10 | 11 | return a + b + c + d + e + 2; 12 | } 13 | -------------------------------------------------------------------------------- /test/fixtures/try-catch/expected.js: -------------------------------------------------------------------------------- 1 | function test() { 2 | return Promise.resolve().then(function () { 3 | return Promise.resolve().then(function () { 4 | going.to.fail; 5 | }).catch(function (err) { 6 | return postErrorMessage('http://my.webservice/error', err); 7 | }); 8 | }).then(function () {}); 9 | } 10 | -------------------------------------------------------------------------------- /test/fixtures/simple-conditional/expected.js: -------------------------------------------------------------------------------- 1 | function test() { 2 | var a, b; 3 | return Promise.resolve().then(function () { 4 | return db.post({}); 5 | }).then(function (_resp) { 6 | a = _resp; 7 | 8 | if (a) { 9 | return db.destroy(); 10 | } 11 | }).then(function () { 12 | b = 1 + 1; 13 | }); 14 | } 15 | -------------------------------------------------------------------------------- /test/fixtures/nested-if-with-else/expected.js: -------------------------------------------------------------------------------- 1 | function test() { 2 | var _test; 3 | 4 | return Promise.resolve().then(function () { 5 | _test = a; 6 | 7 | if (_test && b) { 8 | return c; 9 | } else { 10 | if (_test) { 11 | return d; 12 | } else { 13 | return e; 14 | } 15 | } 16 | }); 17 | } 18 | -------------------------------------------------------------------------------- /test/fixtures/if-flow/actual.js: -------------------------------------------------------------------------------- 1 | async function test() { 2 | if (a()) { 3 | if (b()) { 4 | c(); 5 | return; 6 | } 7 | if (await d()) { 8 | await e(); 9 | if (f()) { 10 | return await g(); 11 | } else { 12 | return h(); 13 | } 14 | } 15 | return i(); 16 | } 17 | return await j(); 18 | } 19 | -------------------------------------------------------------------------------- /test/fixtures/keep-function-names-in-object-values/expected.js: -------------------------------------------------------------------------------- 1 | function fn() { 2 | var o1, o2; 3 | return Promise.resolve().then(function () { 4 | o1 = { 5 | a: function a() { 6 | console.log('o1.a'); 7 | } 8 | }; 9 | o2 = { 10 | a: function a() { 11 | console.log('o2.a'); 12 | } 13 | }; 14 | }); 15 | } 16 | -------------------------------------------------------------------------------- /test/fixtures/await-inside-await/expected.js: -------------------------------------------------------------------------------- 1 | function getDoc() { 2 | var doc; 3 | return Promise.resolve().then(function () { 4 | return request('https://example.com/api/get-doc-id'); 5 | }).then(function (_resp) { 6 | return db.get(_resp); 7 | }).then(function (_resp) { 8 | doc = _resp; 9 | 10 | delete doc._rev; 11 | return doc; 12 | }); 13 | } 14 | -------------------------------------------------------------------------------- /test/fixtures/ternary-expr/expected.js: -------------------------------------------------------------------------------- 1 | function test() { 2 | var test; 3 | return Promise.resolve().then(function () { 4 | return a() ? b() : c(); 5 | }).then(function (_resp) { 6 | test = _resp; 7 | 8 | return d() ? Promise.resolve().then(function () { 9 | return e(); 10 | }).then(function (_resp) { 11 | return _resp.ok; 12 | }) : f(); 13 | }); 14 | } 15 | -------------------------------------------------------------------------------- /test/fixtures/sequence-expr/expected.js: -------------------------------------------------------------------------------- 1 | function test() { 2 | return Promise.resolve().then(function () { 3 | return Promise.resolve().then(function () { 4 | return a(); 5 | }).then(function () { 6 | return b(); 7 | }).then(function () { 8 | c(); 9 | d(); 10 | return e(); 11 | }); 12 | }).then(function (_resp) { 13 | test(_resp); 14 | }); 15 | } 16 | -------------------------------------------------------------------------------- /test/fixtures/switch/actual.js: -------------------------------------------------------------------------------- 1 | async function test() { 2 | switch (a()) { 3 | case 2: 4 | await b(); 5 | // FIXME: handle return 6 | return; 7 | case 3: 8 | case 4: 9 | console.log(4); 10 | break; 11 | default: 12 | console.log('default'); 13 | // falls through 14 | case 5: 15 | await d(); 16 | } 17 | console.log('done!'); 18 | } 19 | -------------------------------------------------------------------------------- /test/fixtures/nested-try-finally/actual.js: -------------------------------------------------------------------------------- 1 | async function test() { 2 | try { 3 | try { 4 | await a(); 5 | } finally { 6 | await b(); 7 | } 8 | } finally { 9 | await c(); 10 | } 11 | } 12 | 13 | async function test2() { 14 | try { 15 | await a(); 16 | } finally { 17 | try { 18 | await b(); 19 | } finally { 20 | await c(); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /test/fixtures/object-expr-exec-order/expected.js: -------------------------------------------------------------------------------- 1 | function test() { 2 | var _temp; 3 | 4 | return Promise.resolve().then(function () { 5 | _temp = {}; 6 | _temp.a = a(); 7 | return b(); 8 | }).then(function (_resp) { 9 | _temp.b = _resp; 10 | _temp.c = c(); 11 | return d(); 12 | }).then(function (_resp) { 13 | _temp.d = _resp.ok; 14 | 15 | return _temp; 16 | }); 17 | } 18 | -------------------------------------------------------------------------------- /kneden/README.md: -------------------------------------------------------------------------------- 1 | Kneden (babel-plugin-async-to-promises) 2 | ======================================= 3 | 4 | > Transpile ES7 async/await to vanilla ES6 Promise chains 5 | 6 | This package name is being phased out. Install [babel-plugin-async-to-promises](https://www.npmjs.com/package/babel-plugin-async-to-promises) 7 | instead. For more information see 8 | [issue #22](https://github.com/marten-de-vries/kneden/issues/22) 9 | -------------------------------------------------------------------------------- /test/fixtures/call-expr-exec-order/expected.js: -------------------------------------------------------------------------------- 1 | function test() { 2 | var _temp; 3 | 4 | return Promise.resolve().then(function () { 5 | return Promise.all([a(), b(), c(), Promise.resolve().then(function () { 6 | return d(); 7 | }).then(function (_resp) { 8 | return _resp.ok; 9 | })]); 10 | }).then(function (_resp) { 11 | _temp = _resp; 12 | 13 | return test2(_temp[0], _temp[1], _temp[2], _temp[3]); 14 | }); 15 | } 16 | -------------------------------------------------------------------------------- /test/fixtures/dowhile-loop/expected.js: -------------------------------------------------------------------------------- 1 | function test() { 2 | function _recursive() { 3 | return Promise.resolve().then(function () { 4 | return db.post({}); 5 | }).then(function () { 6 | i++; 7 | 8 | if (i < 11) { 9 | return _recursive(); 10 | } 11 | }); 12 | } 13 | 14 | var i; 15 | return Promise.resolve().then(function () { 16 | i = 0; 17 | return _recursive(); 18 | }).then(function () {}); 19 | } 20 | -------------------------------------------------------------------------------- /test/fixtures/binary-expr-exec-order/expected.js: -------------------------------------------------------------------------------- 1 | /* eslint no-empty: 0 */ 2 | 3 | function test() { 4 | var _temp, _temp2; 5 | 6 | return Promise.resolve().then(function () { 7 | return Promise.resolve().then(function () { 8 | _temp = c(); 9 | return d(); 10 | }).then(function (_resp) { 11 | return _temp === _resp; 12 | }); 13 | }).then(function (_resp) { 14 | if (_resp) {} 15 | 16 | _temp2 = a(); 17 | return b(); 18 | }).then(function (_resp) { 19 | return _temp2 + _resp; 20 | }); 21 | } 22 | -------------------------------------------------------------------------------- /test/fixtures/try-finally/expected.js: -------------------------------------------------------------------------------- 1 | function test() { 2 | return Promise.resolve().then(function () { 3 | return Promise.resolve().then(function () { 4 | return db.info(); 5 | }).then(function (_resp) { 6 | console.log(_resp); 7 | }).then(function () { 8 | return db.destroy(); 9 | }, function (_err) { 10 | return Promise.resolve().then(function () { 11 | return db.destroy(); 12 | }).then(function () { 13 | throw _err; 14 | }); 15 | }); 16 | }).then(function () {}); 17 | } 18 | -------------------------------------------------------------------------------- /test/fixtures/no-block/expected.js: -------------------------------------------------------------------------------- 1 | function test() { 2 | function _recursive() { 3 | if (c()) { 4 | return Promise.resolve().then(function () { 5 | return d(); 6 | }).then(function () { 7 | return _recursive(); 8 | }); 9 | } 10 | } 11 | 12 | return Promise.resolve().then(function () { 13 | if (a()) { 14 | return b(); 15 | } else { 16 | if (c()) { 17 | return d(); 18 | } 19 | } 20 | }).then(function () { 21 | return _recursive(); 22 | }).then(function () {}); 23 | } 24 | -------------------------------------------------------------------------------- /test/fixtures/loop-with-labels/actual.js: -------------------------------------------------------------------------------- 1 | function a() { 2 | console.log('test'); 3 | } 4 | 5 | async function test() { 6 | var i, j; 7 | i = 0; 8 | j = 0; 9 | 10 | outer: 11 | while (i < 10) { 12 | i++; 13 | inner: 14 | while (j < 10) { 15 | await a(); 16 | console.log(i, j); 17 | j++; 18 | if (i === 8) { 19 | break outer; 20 | } 21 | if (i === 1) { 22 | continue outer; 23 | } 24 | if (i === j) { 25 | break inner; 26 | } 27 | } 28 | } 29 | } 30 | 31 | test(); 32 | -------------------------------------------------------------------------------- /test/fixtures/while-loop/expected.js: -------------------------------------------------------------------------------- 1 | var PouchDB = require('pouchdb'); 2 | 3 | function test() { 4 | function _recursive() { 5 | if (i < 10) { 6 | return Promise.resolve().then(function () { 7 | i++; 8 | return db.put({ _id: i }); 9 | }).then(function () { 10 | return _recursive(); 11 | }); 12 | } 13 | } 14 | 15 | var db; 16 | return Promise.resolve().then(function () { 17 | db = new PouchDB('test'); 18 | return _recursive(); 19 | }).then(function () { 20 | return db.allDocs(); 21 | }); 22 | } 23 | -------------------------------------------------------------------------------- /test/fixtures/for-loop/expected.js: -------------------------------------------------------------------------------- 1 | var request = require('request-promise'); 2 | function test() { 3 | function _recursive() { 4 | if (i < 10) { 5 | return Promise.resolve().then(function () { 6 | return request('https://example.com/page' + i); 7 | }).then(function (_resp) { 8 | pages.push(_resp); 9 | i++; 10 | return _recursive(); 11 | }); 12 | } 13 | } 14 | 15 | var pages, i; 16 | return Promise.resolve().then(function () { 17 | pages = []; 18 | i = 0; 19 | return _recursive(); 20 | }).then(function () {}); 21 | } 22 | -------------------------------------------------------------------------------- /kneden/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "kneden", 3 | "version": "1.0.4", 4 | "description": " Transpile ES7 async/await to vanilla ES6 Promise chains", 5 | "repository": "marten-de-vries/kneden", 6 | "author": "Marten de Vries", 7 | "main": "index.js", 8 | "keywords": [ 9 | "es", 10 | "7", 11 | "6", 12 | "babel", 13 | "promise", 14 | "async", 15 | "await", 16 | "promises", 17 | "function", 18 | "functions", 19 | "plugin", 20 | "babel-plugin" 21 | ], 22 | "dependencies": { 23 | "babel-plugin-async-to-promises": "1.0.4" 24 | }, 25 | "license": "ISC" 26 | } 27 | -------------------------------------------------------------------------------- /test/fixtures/try-catch-finally/expected.js: -------------------------------------------------------------------------------- 1 | function test() { 2 | return Promise.resolve().then(function () { 3 | return Promise.resolve().then(function () { 4 | return db.destroy(); 5 | }).catch(function (err) { 6 | return Promise.resolve().then(function () { 7 | console.log(err); 8 | return db.post({}); 9 | }).then(function (_resp) { 10 | console.log(_resp); 11 | }); 12 | }).then(function () { 13 | return db.info(); 14 | }, function (_err) { 15 | return Promise.resolve().then(function () { 16 | return db.info(); 17 | }).then(function () { 18 | throw _err; 19 | }); 20 | }); 21 | }).then(function () {}); 22 | } 23 | -------------------------------------------------------------------------------- /test/fixtures/return-in-loop/expected.js: -------------------------------------------------------------------------------- 1 | /* eslint no-constant-condition: 0 */ 2 | 3 | function test() { 4 | function _recursive() { 5 | var _test; 6 | 7 | return Promise.resolve().then(function () { 8 | _test = true; 9 | return _test && a(); 10 | }).then(function (_resp) { 11 | if (_resp) { 12 | return "now"; 13 | } else { 14 | if (_test) { 15 | return _recursive(); 16 | } 17 | } 18 | }); 19 | } 20 | 21 | var _temp; 22 | 23 | return Promise.resolve().then(function () { 24 | return _recursive(); 25 | }).then(function (_resp) { 26 | _temp = _resp; 27 | 28 | if (_temp !== _recursive) { 29 | return _temp; 30 | } 31 | }); 32 | } 33 | -------------------------------------------------------------------------------- /test/fixtures/simple-switch/expected.js: -------------------------------------------------------------------------------- 1 | function test(n) { 2 | var a, _discriminant, _match, _brokenOut; 3 | 4 | return Promise.resolve().then(function () { 5 | a = 0; 6 | _discriminant = n; 7 | _match = false; 8 | _brokenOut = false; 9 | 10 | if (!_brokenOut && (_match || 1 === _discriminant)) { 11 | a = 2; 12 | _brokenOut = true; 13 | _match = true; 14 | } 15 | 16 | return !_brokenOut && (_match || Promise.resolve().then(function () { 17 | return getNum(); 18 | }).then(function (_resp) { 19 | return _resp === _discriminant; 20 | })); 21 | }).then(function (_resp) { 22 | if (_resp) { 23 | a = 3; 24 | _match = true; 25 | } 26 | 27 | return a; 28 | }); 29 | } 30 | -------------------------------------------------------------------------------- /test/fixtures/complex-trycatch/expected.js: -------------------------------------------------------------------------------- 1 | function test() { 2 | var a, b, c, d, e; 3 | return Promise.resolve().then(function () { 4 | return a(); 5 | }).then(function (_resp) { 6 | a = _resp; 7 | return Promise.resolve().then(function () { 8 | b = new PouchDB('test2'); 9 | return db.destroy(); 10 | }).then(function (_resp) { 11 | c = _resp; 12 | }).catch(function (err) { 13 | return Promise.resolve().then(function () { 14 | return new PouchDB('test').destroy(); 15 | }).then(function (_resp) { 16 | d = _resp; 17 | }); 18 | }); 19 | }).then(function () { 20 | return b(); 21 | }).then(function (_resp) { 22 | e = _resp; 23 | 24 | 25 | return a + b + c + d + e + 2; 26 | }); 27 | } 28 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | ISC License 2 | 3 | Copyright (c) 2016 Marten de Vries 4 | 5 | Permission to use, copy, modify, and distribute this software for any 6 | purpose with or without fee is hereby granted, provided that the above 7 | copyright notice and this permission notice appear in all copies. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | -------------------------------------------------------------------------------- /test/fixtures/if-flow/expected.js: -------------------------------------------------------------------------------- 1 | function test() { 2 | var _test, _test2; 3 | 4 | return Promise.resolve().then(function () { 5 | _test = a(); 6 | 7 | if (_test && b()) { 8 | c(); 9 | } else { 10 | return Promise.resolve().then(function () { 11 | return _test && d(); 12 | }).then(function (_resp) { 13 | _test2 = _resp; 14 | 15 | if (_test2) { 16 | return e(); 17 | } 18 | }).then(function () { 19 | if (_test2 && f()) { 20 | return g(); 21 | } else { 22 | if (_test2) { 23 | return h(); 24 | } else { 25 | if (_test) { 26 | return i(); 27 | } else { 28 | return j(); 29 | } 30 | } 31 | } 32 | }); 33 | } 34 | }).then(function () {}); 35 | } 36 | -------------------------------------------------------------------------------- /test/fixtures/for-in/expected.js: -------------------------------------------------------------------------------- 1 | function test(object) { 2 | function _recursive() { 3 | var _test; 4 | 5 | return Promise.resolve().then(function () { 6 | _test = _keys.length; 7 | 8 | if (_test) { 9 | key = _keys.pop(); 10 | } 11 | 12 | if (_test && key in _object) { 13 | return Promise.resolve().then(function () { 14 | return key; 15 | }).then(function () {}); 16 | } 17 | }).then(function () { 18 | if (_test) { 19 | return _recursive(); 20 | } 21 | }); 22 | } 23 | 24 | var key, _keys, _object; 25 | 26 | return Promise.resolve().then(function () { 27 | _object = object; 28 | _keys = []; 29 | 30 | for (var _key in _object) { 31 | _keys.push(_key); 32 | } 33 | 34 | _keys.reverse(); 35 | 36 | return _recursive(); 37 | }).then(function () {}); 38 | } 39 | -------------------------------------------------------------------------------- /test/fixtures/for-in-with-external-var/expected.js: -------------------------------------------------------------------------------- 1 | function test(object) { 2 | function _recursive() { 3 | var _test; 4 | 5 | return Promise.resolve().then(function () { 6 | _test = _keys.length; 7 | 8 | if (_test) { 9 | key = _keys.pop(); 10 | } 11 | 12 | if (_test && key in _object) { 13 | return Promise.resolve().then(function () { 14 | return key; 15 | }).then(function () {}); 16 | } 17 | }).then(function () { 18 | if (_test) { 19 | return _recursive(); 20 | } 21 | }); 22 | } 23 | 24 | var key, _keys, _object; 25 | 26 | return Promise.resolve().then(function () { 27 | _object = object; 28 | _keys = []; 29 | 30 | for (var _key in _object) { 31 | _keys.push(_key); 32 | } 33 | 34 | _keys.reverse(); 35 | 36 | return _recursive(); 37 | }).then(function () {}); 38 | } 39 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | 3 | import path from 'path'; 4 | import fs from 'fs'; 5 | import assert from 'assert'; 6 | import {transformFileSync} from 'babel-core'; 7 | 8 | function trim(str) { 9 | return str.replace(/^\s+|\s+$/, ''); 10 | } 11 | 12 | describe('Transpile ES7 async/await to vanilla ES6 Promise chains -', function () { 13 | // sometimes 2000 isn't enough when starting up in coverage mode. 14 | this.timeout(5000); 15 | 16 | const fixturesDir = path.join(__dirname, 'fixtures'); 17 | fs.readdirSync(fixturesDir).forEach(caseName => { 18 | const fixtureDir = path.join(fixturesDir, caseName); 19 | const actualPath = path.join(fixtureDir, 'actual.js'); 20 | if (!fs.statSync(fixtureDir).isDirectory()) { 21 | return; 22 | } 23 | it(caseName.split('-').join(' '), () => { 24 | const actual = transformFileSync(actualPath).code; 25 | 26 | const expected = fs.readFileSync( 27 | path.join(fixtureDir, 'expected.js') 28 | ).toString(); 29 | 30 | assert.equal(trim(actual), trim(expected)); 31 | }); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /test/fixtures/complex-loop/expected.js: -------------------------------------------------------------------------------- 1 | function test() { 2 | function _recursive() { 3 | var _test; 4 | 5 | _test = i < 10; 6 | 7 | if (_test) { 8 | i++; 9 | } 10 | 11 | return Promise.resolve().then(function () { 12 | return _test && Promise.resolve().then(function () { 13 | return db.put({ _id: i }); 14 | }).then(function (_resp) { 15 | return _resp.ok; 16 | }); 17 | }).then(function (_resp) { 18 | if (_resp) { 19 | return _recursive(); 20 | } else { 21 | if (_test && i === 2) { 22 | return _recursive; 23 | } else { 24 | if (_test) { 25 | return Promise.resolve().then(function () { 26 | return db.destroy(); 27 | }).then(function (_resp) { 28 | a = _resp; 29 | return _recursive(); 30 | }); 31 | } 32 | } 33 | } 34 | }); 35 | } 36 | 37 | var i, a; 38 | return Promise.resolve().then(function () { 39 | i = 0; 40 | return _recursive(); 41 | }).then(function () {}); 42 | } 43 | -------------------------------------------------------------------------------- /test/fixtures/switch/expected.js: -------------------------------------------------------------------------------- 1 | function test() { 2 | var _discriminant, _match, _brokenOut, _test; 3 | 4 | return Promise.resolve().then(function () { 5 | _discriminant = a(); 6 | _match = false; 7 | _brokenOut = false; 8 | 9 | if (!_brokenOut && (_match || 2 === _discriminant)) { 10 | return Promise.resolve().then(function () { 11 | return b(); 12 | }).then(function () { 13 | // FIXME: handle return 14 | return; 15 | }); 16 | } 17 | }).then(function () { 18 | if (!_brokenOut && (_match || 3 === _discriminant)) { 19 | _match = true; 20 | } 21 | 22 | if (!_brokenOut && (_match || 4 === _discriminant)) { 23 | console.log(4); 24 | _brokenOut = true; 25 | _match = true; 26 | } 27 | 28 | if (!_brokenOut && (_match || 5 === _discriminant)) { 29 | return Promise.resolve().then(function () { 30 | return d(); 31 | }).then(function () { 32 | _match = true; 33 | }); 34 | } 35 | }).then(function () { 36 | _test = !_brokenOut && !_match; 37 | 38 | if (_test) { 39 | console.log('default'); 40 | } 41 | 42 | if (_test && !_brokenOut) { 43 | return d(); 44 | } 45 | }).then(function () { 46 | console.log('done!'); 47 | }); 48 | } 49 | -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | import { 2 | assignmentExpression, 3 | awaitExpression, 4 | callExpression, 5 | expressionStatement, 6 | functionExpression 7 | } from 'babel-types'; 8 | import {extend} from 'js-extend'; 9 | 10 | export const NoSubFunctionsVisitor = { 11 | Function(path) { 12 | path.skip(); 13 | } 14 | } 15 | 16 | export const containsAwait = matcher(['AwaitExpression'], NoSubFunctionsVisitor); 17 | 18 | export function matcher(types, base) { 19 | const MatchVisitor = extend({}, base); 20 | types.forEach(type => { 21 | MatchVisitor[type] = function (path) { 22 | this.match.found = true; 23 | path.stop(); 24 | }; 25 | }); 26 | return function (path) { 27 | if (!path.node) { 28 | return false; 29 | } 30 | if (types.indexOf(path.node.type) !== -1) { 31 | return true; 32 | } 33 | const match = {} 34 | path.traverse(MatchVisitor, {match}); 35 | return match.found; 36 | } 37 | } 38 | 39 | export function wrapFunction(body) { 40 | const func = functionExpression(null, [], body, false, true); 41 | func.dirtyAllowed = true; 42 | return callExpression(func, []); 43 | } 44 | 45 | export const awaitStatement = arg => expressionStatement(awaitExpression(arg)); 46 | 47 | export const assign = (a, b) => 48 | expressionStatement(assignmentExpression('=', a, b)); 49 | -------------------------------------------------------------------------------- /test/fixtures/loop-with-labels/expected.js: -------------------------------------------------------------------------------- 1 | function a() { 2 | console.log('test'); 3 | } 4 | 5 | function test() { 6 | function outer() { 7 | function inner() { 8 | var _test; 9 | 10 | return Promise.resolve().then(function () { 11 | _test = j < 10; 12 | 13 | if (_test) { 14 | return Promise.resolve().then(function () { 15 | return a(); 16 | }).then(function () { 17 | console.log(i, j); 18 | j++; 19 | }); 20 | } 21 | }).then(function () { 22 | if (_test && i === 8) { 23 | return outer; 24 | } else { 25 | if (_test && i === 1) { 26 | return outer(); 27 | } else { 28 | if (_test && i === j) { 29 | return inner; 30 | } else { 31 | if (_test) { 32 | return inner(); 33 | } 34 | } 35 | } 36 | } 37 | }); 38 | } 39 | 40 | var _temp; 41 | 42 | if (i < 10) { 43 | return Promise.resolve().then(function () { 44 | i++; 45 | return inner(); 46 | }).then(function (_resp) { 47 | _temp = _resp; 48 | 49 | if (_temp !== inner) { 50 | return _temp; 51 | } else { 52 | return outer(); 53 | } 54 | }); 55 | } 56 | } 57 | 58 | var i, j; 59 | return Promise.resolve().then(function () { 60 | i = 0; 61 | j = 0; 62 | 63 | return outer(); 64 | }).then(function () {}); 65 | } 66 | 67 | test(); 68 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "babel-plugin-async-to-promises", 3 | "version": "1.0.4", 4 | "description": "Transpile ES7 async/await to vanilla ES6 Promise chains", 5 | "repository": "marten-de-vries/kneden", 6 | "author": "Marten de Vries", 7 | "main": "lib/index.js", 8 | "devDependencies": { 9 | "babel-cli": "^6.4.5", 10 | "babel-core": "^6.3.17", 11 | "babel-eslint": "^6.0.0-beta.6", 12 | "babel-istanbul": "^0.8.0", 13 | "babel-preset-es2015": "^6.3.13", 14 | "babel-register": "^6.4.3", 15 | "eslint": "^2.0.0", 16 | "mocha": "^2.2.5" 17 | }, 18 | "scripts": { 19 | "clean": "rm -rf lib", 20 | "build": "babel src -d lib", 21 | "test": "npm run test:lint && npm run test:cov", 22 | "test:cov": "babel-node node_modules/.bin/babel-istanbul cover _mocha && babel-istanbul check-coverage --statements 100 --functions 100 --branches 100 --lines 100", 23 | "test:js": "mocha --compilers js:babel-register", 24 | "test:lint": "eslint src/*.js test/index.js && eslint test/fixtures/*/*.js", 25 | "test:watch": "npm run test:js -- --watch", 26 | "prepublish": "npm run clean && npm run build" 27 | }, 28 | "keywords": [ 29 | "es", 30 | "7", 31 | "6", 32 | "babel", 33 | "promise", 34 | "async", 35 | "await", 36 | "promises", 37 | "function", 38 | "functions", 39 | "plugin", 40 | "babel-plugin" 41 | ], 42 | "dependencies": { 43 | "babel-helper-hoist-variables": "^6.5.0", 44 | "babel-template": "^6.3.13", 45 | "babel-types": "^6.5.2", 46 | "js-extend": "^1.0.1" 47 | }, 48 | "license": "ISC" 49 | } 50 | -------------------------------------------------------------------------------- /test/fixtures/nested-try-finally/expected.js: -------------------------------------------------------------------------------- 1 | function test() { 2 | return Promise.resolve().then(function () { 3 | return Promise.resolve().then(function () { 4 | return Promise.resolve().then(function () { 5 | return a(); 6 | }).then(function () { 7 | return b(); 8 | }, function (_err) { 9 | return Promise.resolve().then(function () { 10 | return b(); 11 | }).then(function () { 12 | throw _err; 13 | }); 14 | }); 15 | }).then(function () { 16 | return c(); 17 | }, function (_err) { 18 | return Promise.resolve().then(function () { 19 | return c(); 20 | }).then(function () { 21 | throw _err; 22 | }); 23 | }); 24 | }).then(function () {}); 25 | } 26 | 27 | function test2() { 28 | return Promise.resolve().then(function () { 29 | return Promise.resolve().then(function () { 30 | return a(); 31 | }).then(function () { 32 | return Promise.resolve().then(function () { 33 | return b(); 34 | }).then(function () { 35 | return c(); 36 | }, function (_err) { 37 | return Promise.resolve().then(function () { 38 | return c(); 39 | }).then(function () { 40 | throw _err; 41 | }); 42 | }); 43 | }, function (_err) { 44 | return Promise.resolve().then(function () { 45 | return Promise.resolve().then(function () { 46 | return b(); 47 | }).then(function () { 48 | return c(); 49 | }, function (_err) { 50 | return Promise.resolve().then(function () { 51 | return c(); 52 | }).then(function () { 53 | throw _err; 54 | }); 55 | }); 56 | }).then(function () { 57 | throw _err; 58 | }); 59 | }); 60 | }).then(function () {}); 61 | } 62 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import hoistVariables from 'babel-helper-hoist-variables'; 2 | 3 | import { 4 | blockStatement, 5 | identifier, 6 | isBlockStatement, 7 | isCallExpression, 8 | isFunctionExpression, 9 | isReturnStatement, 10 | returnStatement, 11 | thisExpression, 12 | variableDeclaration, 13 | variableDeclarator 14 | } from 'babel-types'; 15 | 16 | import {RefactorVisitor, IfRefactorVisitor} from './refactor'; 17 | import PromiseChain from './promisechain'; 18 | 19 | module.exports = () => ({ 20 | visitor: WrapperVisitor, 21 | manipulateOptions(opts, parserOpts) { 22 | parserOpts.plugins.push('asyncFunctions'); 23 | } 24 | }); 25 | 26 | let depth = 0; 27 | let respID, errID; 28 | 29 | const WrapperVisitor = { 30 | // Because only ES5 is really supported, force this plugin to run as late as 31 | // possible. At least the normal (es2015 preset) transforms have happened by 32 | // then. 33 | Program: { 34 | exit(path) { 35 | respID = path.scope.generateUid('resp'); 36 | errID = path.scope.generateUid('err'); 37 | path.traverse(MainVisitor); 38 | // inline functions 39 | path.traverse(InliningVisitor); 40 | } 41 | } 42 | }; 43 | 44 | const MainVisitor = { 45 | Function: { 46 | enter(path) { 47 | depth++; 48 | const {node} = path; 49 | if (node.async) { 50 | const decls = []; 51 | const addVarDecl = id => decls.push(variableDeclarator(id)); 52 | // hoist variables 53 | hoistVariables(path, addVarDecl); 54 | 55 | // info gathering for this/arguments during the refactoring 56 | const thisID = identifier(path.scope.generateUid('this')); 57 | const argumentsID = identifier(path.scope.generateUid('arguments')); 58 | const used = {thisID: false, argumentsID: false}; 59 | 60 | const newBody = []; 61 | const addFunctionDecl = func => newBody.push(func); 62 | 63 | // refactor code 64 | const args = {thisID, argumentsID, used, addVarDecl, addFunctionDecl, respID, errID}; 65 | path.traverse(RefactorVisitor, args); 66 | // add this/arguments vars if necessary 67 | if (used.thisID) { 68 | decls.push(variableDeclarator(thisID, thisExpression())); 69 | } 70 | if (used.argumentsID) { 71 | decls.push(variableDeclarator(argumentsID, identifier('arguments'))); 72 | } 73 | if (decls.length) { 74 | newBody.push(variableDeclaration('var', decls)); 75 | } 76 | 77 | // transformations that can only be done after all others. 78 | path.traverse(IfRefactorVisitor); 79 | 80 | // build the promise chain 81 | const chain = new PromiseChain(depth > 1, node.dirtyAllowed, respID, errID); 82 | chain.add(path.get('body.body')); 83 | newBody.push(returnStatement(chain.toAST())); 84 | 85 | // combine all the newly generated stuff. 86 | node.body = blockStatement(newBody); 87 | node.async = false; 88 | } 89 | }, 90 | exit() { 91 | depth--; 92 | } 93 | } 94 | }; 95 | 96 | const InliningVisitor = { 97 | BlockStatement(path) { 98 | // inline blocks. Included because babel-template otherwise creates empty 99 | // blocks. 100 | if (isBlockStatement(path.parent)) { 101 | path.replaceWithMultiple(path.node.body); 102 | } 103 | }, 104 | ReturnStatement(path) { 105 | // return function () { ...body... }() becomes: ...body... 106 | const call = path.node.argument; 107 | const inlineable = ( 108 | isCallExpression(call) && 109 | !call.arguments.length && 110 | isFunctionExpression(call.callee) && 111 | !call.callee.id && 112 | !call.callee.params.length && 113 | isBlockStatement(call.callee.body) && 114 | !Object.keys(path.get('argument.callee').scope.bindings).length 115 | ); 116 | if (inlineable) { 117 | path.replaceWithMultiple(call.callee.body.body); 118 | } 119 | }, 120 | CallExpression(path) { 121 | // function () { return x; }() becomes x 122 | const inlineable = ( 123 | !path.node.arguments.length && 124 | isFunctionExpression(path.node.callee) && 125 | !path.node.callee.id && 126 | !path.node.callee.params.length && 127 | isBlockStatement(path.node.callee.body) && 128 | path.node.callee.body.body.length === 1 && 129 | isReturnStatement(path.node.callee.body.body[0]) && 130 | path.node.callee.body.body[0].argument 131 | ); 132 | if (inlineable) { 133 | path.replaceWith(path.node.callee.body.body[0].argument); 134 | } 135 | } 136 | }; 137 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Kneden (babel-plugin-async-to-promises) 2 | ======================================= 3 | 4 | **This project is currently unmaintained. If you want to take over, feel free to fork the repo. If such a fork gets maintained or contains useful improvements, I'd be willing to merge back and give repo+npm access.** 5 | 6 | [![Build Status](https://travis-ci.org/marten-de-vries/kneden.svg?branch=master)](https://travis-ci.org/marten-de-vries/kneden) 7 | [![Dependency Status](https://david-dm.org/marten-de-vries/kneden.svg)](https://david-dm.org/marten-de-vries/kneden) 8 | [![devDependency Status](https://david-dm.org/marten-de-vries/kneden/dev-status.svg)](https://david-dm.org/marten-de-vries/kneden#info=devDependencies) 9 | 10 | > Transpile ES7 async/await to vanilla ES6 Promise chains 11 | 12 | **WARNING: Kneden 13 | [is usable](https://github.com/pouchdb/pouchdb-plugin-helper/pull/9), but it's 14 | also [not complete yet](https://github.com/marten-de-vries/kneden/issues/13).** 15 | 16 | Do you want an ES7 async/await transpiling [Babel](https://babeljs.io/) plugin, 17 | that: 18 | 19 | - produces readable code - even when generator functions are not available? 20 | - doesn't come with a runtime your users have to download? 21 | 22 | Then look no further! **Kneden (babel-plugin-async-to-promises)** can help you. 23 | 24 | ## Example 25 | 26 | **In** 27 | 28 | ```js 29 | async function test() { 30 | await db.destroy(); 31 | } 32 | ``` 33 | 34 | **Out** 35 | 36 | ```js 37 | function test() { 38 | return Promise.resolve().then(function () { 39 | return db.destroy(); 40 | }).then(function () {}); 41 | } 42 | ``` 43 | 44 | (The last .then() might seem superfluous at first, but the first function 45 | doesn't actually resolve to anything so it's necessary to make a valid 46 | translation.) 47 | 48 | **Kneden** tries to translate ES7 async/await to promises in a manner similar to 49 | how a human would do so. Loops are converted to recursive functions, and your 50 | code is modified in such a way that a return won't just drop you in the next 51 | part of the promise chain, but actually does what you expect it to do. 52 | 53 | For more examples, see the 54 | [test/fixtures directory](https://github.com/marten-de-vries/kneden/tree/master/test/fixtures) 55 | for both the input and output **Kneden** takes/produces. 56 | 57 | ## Installation 58 | 59 | ```sh 60 | $ npm install babel-plugin-async-to-promises 61 | ``` 62 | 63 | ## Usage 64 | 65 | Note: Kneden only supports transpiling ES5 with the addition of async/await. If 66 | you're using other ES6 features (like arrow functions, let/const, classes, 67 | etc.), make sure you transpile them down to valid ES5 code first using the 68 | [babel es2015 preset](https://www.npmjs.com/package/babel-preset-es2015). See 69 | [#19](https://github.com/marten-de-vries/kneden/issues/19) for more information. 70 | 71 | ### Via `.babelrc` (Recommended) 72 | 73 | **.babelrc** 74 | 75 | ```json 76 | { 77 | "plugins": ["async-to-promises"] 78 | } 79 | ``` 80 | 81 | ### Via CLI 82 | 83 | ```sh 84 | $ babel --plugins async-to-promises script.js 85 | ``` 86 | 87 | ### Via Node API 88 | 89 | ```javascript 90 | require("babel-core").transform("code", { 91 | plugins: ["async-to-promises"] 92 | }); 93 | ``` 94 | 95 | You can also use the plug-in in [Browserify](http://browserify.org/) using 96 | [babelify](https://github.com/babel/babelify), in [Rollup](http://rollupjs.org/) 97 | by using it in conjunction with 98 | [rollup-plugin-babel](https://github.com/rollup/rollup-plugin-babel), and in 99 | [Webpack](https://webpack.github.io/) using 100 | [babel-loader](https://github.com/babel/babel-loader). 101 | 102 | Unsupported 103 | ----------- 104 | 105 | - Return statements aren't properly supported in switch and try/catch/finally 106 | statements yet ([#13](https://github.com/marten-de-vries/kneden/issues/13)) 107 | - No ``eval()``; but that's true for other Babel plugins/presets as well. 108 | 109 | Contributing 110 | ------------ 111 | 112 | There are a couple of ways to contribute, for example by: 113 | 114 | - Reporting test results with your code base 115 | - Fixing bugs, for a nice starting task see the ones labeled '[good first bug](https://github.com/marten-de-vries/kneden/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+bug%22)'. 116 | 117 | Contributions are very welcome! Just open an issue or PR. 118 | 119 | What's up with the name? 120 | ------------------------ 121 | 122 | It's Dutch for 'to knead'/'to mold' - the program molds ES7 async/await 123 | constructs into promises. It seemed applicable. [Pronounciation](https://upload.wikimedia.org/wikipedia/commons/0/0e/Nl-kneden.ogg). 124 | 125 | The npm package name is a more descriptive one as explained in 126 | [issue #22](https://github.com/marten-de-vries/kneden/issues/22). 127 | 128 | License 129 | ------- 130 | 131 | ISC 132 | 133 | --- 134 | 135 | **Kneden** is a project by [Marten de Vries](https://ma.rtendevri.es/). 136 | -------------------------------------------------------------------------------- /src/promisechain.js: -------------------------------------------------------------------------------- 1 | import { 2 | blockStatement, 3 | callExpression, 4 | cloneDeep, 5 | functionExpression, 6 | identifier, 7 | isExpressionStatement, 8 | memberExpression, 9 | returnStatement, 10 | throwStatement 11 | } from 'babel-types'; 12 | import {extend} from 'js-extend'; 13 | import {NoSubFunctionsVisitor} from './utils'; 14 | 15 | export default class PromiseChain { 16 | // add, addCatch and addFinally were designed to be called only one time each 17 | // at most. Call them more at your own risk. 18 | // 19 | // addCatch() and addFinally() are not guaranteed to handle return values 20 | // correctly. FIXME. 21 | constructor(inner, dirtyAllowed, respName, errName) { 22 | this._inner = inner; 23 | this._dirtyAllowed = dirtyAllowed; 24 | this._respName = respName; 25 | this._errName = errName; 26 | 27 | this._ast = callExpression(memberExpression(identifier('Promise'), identifier('resolve')), []); 28 | } 29 | add(block) { 30 | if (!block.length) { 31 | return; 32 | } 33 | let current = this._addLink('then', []); 34 | block.forEach(path => { 35 | const awaitInfos = []; 36 | path.traverse(PromisifyPrepVisitor, {awaitInfos, respName: this._respName}); 37 | 38 | awaitInfos.forEach(awaitInfo => { 39 | current.body.push(returnStatement(awaitInfo.arg)); 40 | const params = awaitInfo.passID ? [identifier(this._respName)] : []; 41 | current = this._addLink('then', params); 42 | }); 43 | if (path.node) { 44 | current.body.push(path.node); 45 | } 46 | }); 47 | } 48 | _addLink(type, params, secondParams) { 49 | this._cleanup(); 50 | 51 | const current = {body: []}; 52 | const handlerBody = blockStatement(current.body); 53 | const handlers = [functionExpression(null, params, handlerBody)]; 54 | 55 | if (secondParams) { 56 | current.secondBody = []; 57 | const secondHandlerBody = blockStatement(current.secondBody); 58 | handlers.push(functionExpression(null, secondParams, secondHandlerBody)); 59 | } 60 | 61 | const method = memberExpression(this._ast, identifier(type)); 62 | this._ast = callExpression(method, handlers); 63 | 64 | return current; 65 | } 66 | _cleanup() { 67 | // if resolving to non-undefined when there is no return is allowed, and 68 | // the last part of the chain is .then(function () {}), then chop off that 69 | // part 70 | const chopOff = ( 71 | this._dirtyAllowed && 72 | this._ast.callee.property.name === 'then' && 73 | this._ast.arguments.length === 1 && 74 | !this._ast.arguments[0].body.body.length 75 | ); 76 | if (chopOff) { 77 | this._ast = this._ast.callee.object; 78 | } 79 | } 80 | addCatch(block, errID) { 81 | const current = this._addLink('catch', [errID]); 82 | const catchChain = this._subChain(); 83 | catchChain.add(block); 84 | current.body.push(returnStatement(catchChain.toAST())); 85 | } 86 | _subChain() { 87 | return new PromiseChain(true, true, this._respName, this._errName); 88 | } 89 | addFinally(block) { 90 | const errID = identifier(this._errName); 91 | const current = this._addLink('then', [], [errID]); 92 | 93 | const finallyChain = this._subChain(); 94 | 95 | // disable optimalizations 96 | finallyChain._inner = false; 97 | finallyChain._dirtyAllowed = false; 98 | finallyChain.add(block); 99 | const secondAST = cloneDeep(finallyChain.toAST()); 100 | // smuggle in the throw statement 101 | secondAST.arguments[0].body.body.push(throwStatement(errID)); 102 | current.secondBody.push(returnStatement(secondAST)); 103 | 104 | // re-enable optimalizations 105 | finallyChain._inner = true; 106 | finallyChain._dirtyAllowed = true; 107 | const ast = returnStatement(finallyChain.toAST()); 108 | current.body.push(ast); 109 | } 110 | toAST() { 111 | this._cleanup(); 112 | 113 | const callee = this._ast.callee.object.callee; 114 | if (this._inner && callee && callee.object.name === 'Promise') { 115 | // only one handler to the promise - because we're in an inner function 116 | // there's no reason to wrap the handler in promise code. Convenienly, 117 | // such a handler is inlineable later on. 118 | // 119 | // Summary: 120 | // ``Promise.resolve().then(function () {...})`` 121 | // becomes 122 | // ``function () {...}()`` 123 | return callExpression(this._ast.arguments[0], []); 124 | } 125 | return this._ast; 126 | } 127 | } 128 | 129 | const PromisifyPrepVisitor = extend({ 130 | AwaitExpression: { 131 | exit(path) { 132 | // exit so awaits are evaluated inside out if there are multiple in 133 | // the expression 134 | const info = {arg: path.node.argument}; 135 | if (isExpressionStatement(path.parent)) { 136 | path.remove(); 137 | } else { 138 | info.passID = true; 139 | path.replaceWith(identifier(this.respName)); 140 | } 141 | this.awaitInfos.push(info); 142 | } 143 | } 144 | }, NoSubFunctionsVisitor); 145 | -------------------------------------------------------------------------------- /src/ifrefactor.js: -------------------------------------------------------------------------------- 1 | import { 2 | awaitExpression, 3 | blockStatement, 4 | ensureBlock, 5 | identifier, 6 | ifStatement, 7 | isIfStatement, 8 | isReturnStatement, 9 | logicalExpression, 10 | returnStatement, 11 | unaryExpression 12 | } from 'babel-types'; 13 | 14 | import { 15 | assign, 16 | containsAwait, 17 | matcher, 18 | NoSubFunctionsVisitor, 19 | wrapFunction 20 | } from './utils'; 21 | 22 | import {extend} from 'js-extend'; 23 | 24 | export const FirstPassIfVisitor = { 25 | IfStatement(path) { 26 | const {node} = path; 27 | ensureBlock(node, 'consequent'); 28 | if (node.alternate) { 29 | ensureBlock(node, 'alternate'); 30 | } 31 | if (node.consequent.body.some(isIfStatement) && containsReturnOrAwait(path)) { 32 | // flatten if statements. There are two ways to reach d() in the below. 33 | // if a() && !b(), and if !a() && !b(). That's problematic during the 34 | // promise conversion. 35 | // 36 | // if (a()) { 37 | // if (b()) { 38 | // return c(); 39 | // } 40 | // } 41 | // return d(); 42 | // 43 | // this becomes instead: 44 | // 45 | // var _test = a(); 46 | // if (_test && b()) { 47 | // return c(); 48 | // } 49 | // return d(); 50 | // 51 | // which is better, but not quite the result we want yet. See for that 52 | // the BlockStatement handler in the other IfRefactorVisitor below. 53 | 54 | const testID = identifier(path.scope.generateUid('test')); 55 | this.addVarDecl(testID); 56 | const block = [assign(testID, node.test)]; 57 | 58 | let stillToAdd = []; 59 | const clearQueue = () => { 60 | if (stillToAdd.length) { 61 | block.push(ifStatement(testID, blockStatement(stillToAdd))); 62 | stillToAdd = []; 63 | } 64 | } 65 | node.consequent.body.forEach(stmt => { 66 | if (isIfStatement(stmt)) { 67 | clearQueue(); 68 | stmt.test = logicalExpression('&&', testID, stmt.test); 69 | if (stmt.alternate) { 70 | stmt.alternate = blockStatement([ifStatement(testID, stmt.alternate)]); 71 | } 72 | block.push(stmt); 73 | } else { 74 | stillToAdd.push(stmt); 75 | } 76 | }); 77 | clearQueue(); 78 | extendElse(block[block.length - 1], (node.alternate || {}).body || []); 79 | path.replaceWithMultiple(block); 80 | } 81 | } 82 | }; 83 | 84 | const containsReturnOrAwait = matcher(['ReturnStatement', 'AwaitExpression'], NoSubFunctionsVisitor); 85 | 86 | export const SecondPassIfVisitor = extend({ 87 | IfStatement(path) { 88 | const alt = path.node.alternate; 89 | if (!path.node.consequent.body.length && alt && alt.body.length) { 90 | path.node.consequent = path.node.alternate; 91 | path.node.alternate = null; 92 | path.node.test = unaryExpression('!', path.node.test); 93 | } 94 | const ifContainsAwait = containsAwait(path.get('consequent')); 95 | const elseContainsAwait = containsAwait(path.get('alternate')); 96 | 97 | const {node} = path; 98 | if (ifContainsAwait) { 99 | node.consequent = wrapIfBranch(node.consequent); 100 | } 101 | if (elseContainsAwait) { 102 | node.alternate = wrapIfBranch(node.alternate); 103 | } 104 | if (ifContainsAwait || elseContainsAwait) { 105 | path.replaceWith(awaitExpression(wrapFunction(blockStatement([node])))); 106 | } 107 | }, 108 | BlockStatement(path) { 109 | // Converts 110 | // 111 | // var _test = a(); 112 | // if (_test && b()) { 113 | // return c(); 114 | // } 115 | // return d(); 116 | // 117 | // into: 118 | // 119 | // var _test = a(); 120 | // if (_test && b()) { 121 | // return c(); 122 | // } else { 123 | // return d(); 124 | // } 125 | // 126 | // ... which has at every point in time only two choices: returning 127 | // directly out of the function, or continueing on. That's what's required 128 | // for a nice conversion to Promise chains. 129 | for (var i = 0; i < path.node.body.length; i++) { 130 | const subNode = path.node.body[i]; 131 | if (isReturnStatement(subNode)) { 132 | // remove everything in the block after the return - it's never going 133 | // to be executed anyway. 134 | path.node.body.splice(i + 1); 135 | } 136 | if (!isIfStatement(subNode)) { 137 | continue; 138 | } 139 | const lastStmt = subNode.consequent.body[subNode.consequent.body.length - 1]; 140 | if (!isReturnStatement(lastStmt)) { 141 | continue; 142 | } 143 | const remainder = path.node.body.splice(i + 1); 144 | if (!lastStmt.argument) { 145 | // chop off the soon to be useless return statement 146 | subNode.consequent.body.splice(-1); 147 | } 148 | extendElse(subNode, remainder); 149 | } 150 | } 151 | }, NoSubFunctionsVisitor) 152 | 153 | const wrapIfBranch = 154 | branch => blockStatement([returnStatement(wrapFunction(branch))]); 155 | 156 | function extendElse(ifStmt, extraBody) { 157 | const body = ((ifStmt.alternate || {}).body || []).concat(extraBody); 158 | if (body.length) { 159 | ifStmt.alternate = blockStatement(body); 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /src/looprefactor.js: -------------------------------------------------------------------------------- 1 | import { 2 | awaitExpression, 3 | blockStatement, 4 | callExpression, 5 | ensureBlock, 6 | expressionStatement, 7 | identifier, 8 | ifStatement, 9 | returnStatement, 10 | whileStatement 11 | } from 'babel-types'; 12 | import template from 'babel-template'; 13 | import {extend} from 'js-extend'; 14 | 15 | import { 16 | awaitStatement, 17 | containsAwait, 18 | NoSubFunctionsVisitor, 19 | matcher, 20 | wrapFunction 21 | } from './utils'; 22 | 23 | export default { 24 | LabeledStatement: { 25 | // Babel seems to auto-remove labels from the AST if they don't make sense 26 | // in a position. That makes it hard to keep track of if you're in a loop 27 | // with label. So we move the label onto the node itself, and handle it 28 | // manually (at least, if we're touching the loop, i.e. if it has an await 29 | // somewhere inside). 30 | enter(path) { 31 | if (containsAwait(path)) { 32 | path.node.body.loopLabel = path.node.label; 33 | } 34 | } 35 | }, 36 | DoWhileStatement(path) { 37 | // converts 38 | // 39 | // do { 40 | // newBody; 41 | // } while (node.test) 42 | // 43 | // into: 44 | // 45 | // await async function _recursive() { 46 | // newBody; 47 | // if (node.test) { 48 | // return await _recursive(); 49 | // } 50 | // }() 51 | 52 | refactorLoop(path, false, this.addVarDecl, functionID => { 53 | const continueBlock = blockStatement([continueStatementEquiv(functionID)]) 54 | path.node.body.body.push(ifStatement(path.node.test, continueBlock)); 55 | path.replaceWith(recursiveWrapFunction(functionID, path.node.body)); 56 | }); 57 | }, 58 | WhileStatement(path) { 59 | // converts 60 | // 61 | // while (node.test) { 62 | // newBody; 63 | // } 64 | // 65 | // into: 66 | // 67 | // await async function _recursive() { 68 | // if (node.test) { 69 | // newBody; 70 | // return await _recursive(); 71 | // } 72 | // }() 73 | 74 | refactorLoop(path, false, this.addVarDecl, functionID => { 75 | path.node.body.body.push(continueStatementEquiv(functionID)); 76 | const body = blockStatement([ifStatement(path.node.test, path.node.body)]); 77 | 78 | path.replaceWith(recursiveWrapFunction(functionID, body)); 79 | }); 80 | }, 81 | ForStatement(path) { 82 | // converts 83 | // 84 | // for(node.init, node.test, node.update) { 85 | // newBody; 86 | // } 87 | // 88 | // into: 89 | // 90 | // { 91 | // node.init; 92 | // await async function _recursive() { 93 | // if (node.test) { 94 | // newBody; 95 | // node.update; 96 | // return await _recursive(); 97 | // } 98 | // }() 99 | // } 100 | ifShouldRefactorLoop(path, containsAwait(path.get('update')), () => { 101 | path.node.body.body.push(expressionStatement(path.node.update)); 102 | path.replaceWithMultiple([ 103 | expressionStatement(path.node.init), 104 | whileStatement(path.node.test, path.node.body) 105 | ]); 106 | }); 107 | }, 108 | ForInStatement(path) { 109 | // converts 110 | // for (node.left in node.right) { 111 | // newBody; 112 | // } 113 | // 114 | // info: 115 | // 116 | // var _items = []; 117 | // for (var _item in node.right) { 118 | // _items.push(_item); 119 | // } 120 | // _items.reverse(); 121 | // await async function _recursive() { 122 | // if (_items.length) { 123 | // node.left = _items.pop(); 124 | // node.body; 125 | // return await _recursive(); 126 | // } 127 | // } 128 | 129 | ifShouldRefactorLoop(path, false, () => { 130 | var KEYS = identifier(path.scope.generateUid('keys')); 131 | var OBJECT = identifier(path.scope.generateUid('object')); 132 | this.addVarDecl(KEYS); 133 | this.addVarDecl(OBJECT); 134 | path.replaceWithMultiple(forInEquiv({ 135 | KEYS, OBJECT, 136 | KEY: identifier(path.scope.generateUid('key')), 137 | LEFT: path.node.left, 138 | RIGHT: path.node.right, 139 | BODY: path.node.body 140 | })); 141 | }); 142 | } 143 | }; 144 | 145 | const forInEquiv = template(` 146 | OBJECT = RIGHT; 147 | KEYS = []; 148 | for (var KEY in OBJECT) { 149 | KEYS.push(KEY); 150 | } 151 | KEYS.reverse(); 152 | while(KEYS.length) { 153 | LEFT = KEYS.pop(); 154 | if (LEFT in OBJECT) { 155 | BODY; 156 | } 157 | } 158 | `); 159 | 160 | function recursiveWrapFunction(functionID, body) { 161 | const func = wrapFunction(body); 162 | func.callee.id = functionID; 163 | 164 | return awaitStatement(func); 165 | } 166 | 167 | function insideAwaitContainingLabel(path) { 168 | // walks the path tree to check if inside a label that also contains an await 169 | // statement. (See also the LabeledStatement visitor.) 170 | do { 171 | if (path.node.loopLabel) { 172 | return true; 173 | } 174 | } while ((path = path.parentPath)); 175 | 176 | // no such label found 177 | return false; 178 | } 179 | 180 | function ifShouldRefactorLoop(path, extraCheck, handler) { 181 | // ensureBlock here is convenient, but has nothing to do with the method name 182 | ensureBlock(path.node); 183 | 184 | if (extraCheck || insideAwaitContainingLabel(path) || loopContainsAwait(path.get('body'))) { 185 | handler(); 186 | } 187 | } 188 | 189 | const NoSubLoopsVisitor = { 190 | Loop(path) { 191 | path.skip(); 192 | } 193 | }; 194 | 195 | // does the current loop (no subloops) contain an await statement? 196 | const loopContainsAwait = matcher( 197 | ['AwaitExpression'], 198 | extend({}, NoSubFunctionsVisitor, NoSubLoopsVisitor) 199 | ); 200 | 201 | function refactorLoop(path, extraCheck, addVarDecl, handler) { 202 | ifShouldRefactorLoop(path, extraCheck, () => { 203 | // gather info about the function & fix up its body (break + continue 204 | // statements) 205 | const label = path.node.loopLabel; 206 | const functionID = label || identifier(path.scope.generateUid('recursive')); 207 | const info = {functionID}; 208 | path.get('body').traverse(BreakContinueReplacementVisitor, info); 209 | // actual conversion 210 | handler(functionID); 211 | 212 | // if containing a return *or* a break statement that doesn't control the 213 | // own loop (references a label of another loop), add: 214 | // 215 | // .then(function (_resp) { 216 | // _temp = _resp; 217 | // if (_temp !== _recursive) { 218 | // return _temp; 219 | // } 220 | // }); 221 | if (info.addReturnHandler) { 222 | var tmp = identifier(path.scope.generateUid('temp')); 223 | addVarDecl(tmp); 224 | path.node.loopLabel = label; 225 | path.replaceWithMultiple(loopReturnHandler({TMP: tmp, BASE: path.node, FUNC: functionID})); 226 | } 227 | }); 228 | } 229 | 230 | const loopReturnHandler = template(` 231 | TMP = BASE 232 | if (_temp !== FUNC) { 233 | return _temp; 234 | } 235 | `); 236 | 237 | const continueStatementEquiv = funcID => { 238 | // continue label; -> return await label(); 239 | const stmt = returnStatement(awaitExpression(callExpression(funcID, []))) 240 | // not a 'real' return 241 | stmt.noHandlerRequired = true; 242 | return stmt; 243 | }; 244 | 245 | const BreakContinueReplacementVisitor = extend({ 246 | ReturnStatement(path) { 247 | if (!path.node.noHandlerRequired && path.node.argument) { 248 | // if a return statement added by the user - and actually returning 249 | // something, we need to add a return handler later. 250 | this.addReturnHandler = true; 251 | } 252 | }, 253 | // replace continue/break with their recursive equivalents 254 | BreakStatement(path) { 255 | // a break statement is replaced by returning the name of the loop function 256 | // that should be broken. It's a convenient unique value. 257 | // 258 | // So: break; becomes return _recursive; 259 | // 260 | // and break myLabel; becomes return myLabel; 261 | 262 | const label = getLabel(path, this.functionID); 263 | 264 | const returnStmt = returnStatement(getLabel(path, this.functionID)); 265 | if (label === this.functionID) { 266 | // only if this controls the current loop, a return handler is unnecessary 267 | returnStmt.noHandlerRequired = true; 268 | } 269 | path.replaceWith(returnStmt); 270 | }, 271 | ContinueStatement(path) { 272 | // see break, with the difference that the function is called (and thus) 273 | // executed next 274 | path.replaceWith(continueStatementEquiv(getLabel(path, this.functionID))); 275 | } 276 | }, NoSubFunctionsVisitor, NoSubLoopsVisitor); 277 | 278 | const getLabel = (path, functionID) => path.node.label || functionID; 279 | -------------------------------------------------------------------------------- /test/fixtures/pouchdb-auth/actual.js: -------------------------------------------------------------------------------- 1 | import {setup, teardown, should, shouldThrowError} from './utils'; 2 | import extend from 'extend'; 3 | 4 | let db; 5 | 6 | function shouldBeAdminParty(session) { 7 | session.info.should.eql({ 8 | "authentication_handlers": ["api"], 9 | "authentication_db": "test" 10 | }); 11 | session.userCtx.should.eql({ 12 | "name": null, 13 | "roles": ["_admin"] 14 | }); 15 | session.ok.should.be.ok; 16 | } 17 | 18 | function shouldNotBeLoggedIn(session) { 19 | session.info.should.eql({ 20 | authentication_handlers: ["api"], 21 | authentication_db: "test" 22 | }); 23 | session.userCtx.should.eql({ 24 | name: null, 25 | roles: [] 26 | }); 27 | session.ok.should.be.ok; 28 | } 29 | 30 | function shouldBeSuccesfulLogIn(data, roles) { 31 | var copy = extend({}, data); 32 | // irrelevant 33 | delete copy.sessionID; 34 | copy.should.eql({ 35 | "ok": true, 36 | "name": "username", 37 | "roles": roles 38 | }); 39 | } 40 | 41 | function shouldBeLoggedIn(session, roles) { 42 | session.userCtx.should.eql({ 43 | "name": "username", 44 | "roles": roles 45 | }); 46 | session.info.authenticated.should.equal("api"); 47 | } 48 | 49 | describe('SyncAuthTests', () => { 50 | beforeEach(async () => { 51 | db = setup() 52 | should.not.exist(await db.useAsAuthenticationDB({isOnlineAuthDB: false})); 53 | }); 54 | afterEach(teardown); 55 | 56 | it('should test the daemon', () => { 57 | // handled by beforeEach and afterEach 58 | }); 59 | 60 | it('should not allow stopping usage as an auth db twice', async () => { 61 | await db.stopUsingAsAuthenticationDB(); 62 | await shouldThrowError(async () => 63 | await db.stopUsingAsAuthenticationDB() 64 | ); 65 | // startup for afterEach 66 | await db.useAsAuthenticationDB(); 67 | }); 68 | 69 | it('should not allow using a db as an auth db twice', async () => { 70 | //1: beforeEach() 71 | //2: see below: 72 | await shouldThrowError(async () => 73 | await db.useAsAuthenticationDB() 74 | ); 75 | }); 76 | 77 | it('should have working db methods', async () => { 78 | const signUpData = await db.signUp("username", "password", {roles: ["test"]}); 79 | signUpData.rev.indexOf("1-").should.equal(0); 80 | signUpData.ok.should.be.ok; 81 | signUpData.id.should.equal("org.couchdb.user:username"); 82 | 83 | const doc = await db.get("org.couchdb.user:username"); 84 | doc._rev.indexOf("1-").should.equal(0); 85 | doc.should.have.property("derived_key"); 86 | doc.iterations.should.equal(10); 87 | doc.name.should.equal("username"); 88 | doc.password_scheme.should.equal("pbkdf2"); 89 | doc.roles.should.eql(["test"]); 90 | doc.should.have.property("salt"); 91 | doc.type.should.equal("user"); 92 | 93 | doc.should.not.have.property("password"); 94 | 95 | const session = await db.session(); 96 | shouldBeAdminParty(session); 97 | 98 | const logInData = await db.logIn("username", "password"); 99 | shouldBeSuccesfulLogIn(logInData, ["test"]); 100 | 101 | const session2 = await db.session(); 102 | shouldBeLoggedIn(session2, ["test"]); 103 | 104 | const session3 = await db.multiUserSession(); 105 | shouldBeAdminParty(session3); 106 | 107 | const logOutData = await db.logOut(); 108 | logOutData.ok.should.be.ok; 109 | const session4 = await db.session(); 110 | shouldBeAdminParty(session4); 111 | 112 | //should also give a {ok: true} when not logged in. 113 | const logOutData2 = await db.logOut(); 114 | logOutData2.ok.should.be.ok; 115 | 116 | const error = await shouldThrowError(async () => 117 | await db.logIn("username", "wrongPassword") 118 | ); 119 | error.status.should.equal(401); 120 | error.name.should.equal("unauthorized"); 121 | error.message.should.equal("Name or password is incorrect."); 122 | }); 123 | 124 | it('should support sign up without roles', async () => { 125 | const result = await db.signUp("username", "password"); 126 | result.ok.should.be.ok; 127 | 128 | const resp2 = await db.get("org.couchdb.user:username"); 129 | resp2.roles.should.eql([]); 130 | }); 131 | 132 | it('should validate docs', async () => { 133 | const error = await shouldThrowError(async () => 134 | await db.post({}) 135 | ); 136 | error.status.should.equal(403); 137 | 138 | const resp = await db.bulkDocs([{}]); 139 | resp[0].status.should.equal(403); 140 | }); 141 | 142 | it('should handle conflicting logins', async () => { 143 | const doc1 = { 144 | _id: "org.couchdb.user:test", 145 | _rev: "1-blabla", 146 | type: "user", 147 | name: "test", 148 | roles: [] 149 | }; 150 | const doc2 = extend({}, doc1); 151 | doc2._rev = "2-something"; 152 | //generate conflict 153 | await db.bulkDocs([doc1, doc2], {new_edits: false}); 154 | 155 | const error = await shouldThrowError(async () => 156 | await db.logIn("test", "unimportant") 157 | ); 158 | 159 | error.status.should.equal(401); 160 | error.name.should.equal("unauthorized"); 161 | error.message.should.contain("conflict"); 162 | }); 163 | 164 | it('should not accept invalid session ids', async () => { 165 | const err = await shouldThrowError(async () => { 166 | await db.multiUserSession('invalid-session-id'); 167 | }); 168 | err.status.should.equal(400); 169 | err.name.should.equal('bad_request'); 170 | err.message.should.contain('Malformed'); 171 | }); 172 | 173 | afterEach(async () => { 174 | should.not.exist(await db.stopUsingAsAuthenticationDB()); 175 | }); 176 | }); 177 | 178 | describe('AsyncAuthTests', () => { 179 | beforeEach(async () => { 180 | db = setup(); 181 | }); 182 | afterEach(teardown); 183 | it('should suport the basics', done => { 184 | function cb(err) { 185 | db.stopUsingAsAuthenticationDB(); 186 | done(err); 187 | } 188 | db.useAsAuthenticationDB(cb); 189 | }); 190 | }); 191 | 192 | describe('AsyncAuthTestsWithoutDaemon', () => { 193 | beforeEach(async () => { 194 | db = setup() 195 | }); 196 | afterEach(teardown); 197 | 198 | it('should be impossible to use the various exposed methods', () => { 199 | should.not.exist(db.signUp); 200 | should.not.exist(db.session); 201 | should.not.exist(db.logIn); 202 | should.not.exist(db.logOut); 203 | }); 204 | 205 | it('should hash admin passwords', async () => { 206 | const admins = { 207 | test: "-pbkdf2-0abe2dcd23e0b6efc39004749e8d242ddefe46d1,16a1031881b31991f21a619112b1191fb1c41401be1f31d5,10", 208 | test2: "test" 209 | }; 210 | const resp = await db.hashAdminPasswords(admins); 211 | resp.test.should.equal(admins.test); 212 | //10 is the default amount of iterations 213 | resp.test2.indexOf("-pbkdf2-").should.equal(0); 214 | resp.test2.lastIndexOf(",10").should.equal(resp.test2.length - 3); 215 | }); 216 | 217 | it('should support changing admin passwords hash iterations', async () => { 218 | const resp = await db.hashAdminPasswords({ 219 | abc: "test" 220 | }, {iterations: 11}); 221 | resp.abc.indexOf("-pbkdf2-").should.equal(0); 222 | resp.abc.lastIndexOf(",11").should.equal(resp.abc.length - 3); 223 | }); 224 | }); 225 | 226 | describe('No automated test setup', () => { 227 | beforeEach(() => { 228 | db = setup(); 229 | }); 230 | afterEach(teardown); 231 | 232 | it('should support admin logins', async () => { 233 | const opts = { 234 | admins: { 235 | username: '-pbkdf2-37508a1f1c5c19f38779fbe029ae99ee32988293,885e6e9e9031e391d5ef12abbb6c6aef,10' 236 | }, 237 | secret: db.generateSecret() 238 | }; 239 | await db.useAsAuthenticationDB(opts); 240 | 241 | shouldNotBeLoggedIn(await db.multiUserSession()); 242 | const logInData = await db.multiUserLogIn('username', 'test'); 243 | shouldBeSuccesfulLogIn(logInData, ['_admin']); 244 | 245 | db.stopUsingAsAuthenticationDB(); 246 | await db.useAsAuthenticationDB({/* no admins */}); 247 | 248 | //if admins not supplied, there's no session (admin party!) 249 | shouldBeAdminParty(await db.multiUserSession(logInData.sessionID)); 250 | 251 | db.stopUsingAsAuthenticationDB(); 252 | await db.useAsAuthenticationDB(opts); 253 | 254 | //otherwise there is 255 | const sessionData = await db.multiUserSession(logInData.sessionID); 256 | shouldBeLoggedIn(sessionData, ["_admin"]); 257 | 258 | //check if logout works (i.e. forgetting the session id.) 259 | shouldNotBeLoggedIn(await db.multiUserSession()); 260 | }); 261 | 262 | it('should handle invalid admins field on login', async () => { 263 | const admins = { 264 | username: "-pbkdf2-37508a1f1c5c19f38779fbe029ae99ee32988293,885e6e9e9031e391d5ef12abbb6c6aef,10", 265 | username2: 'this-is-no-hash' 266 | }; 267 | await db.useAsAuthenticationDB({admins: admins}); 268 | 269 | shouldNotBeLoggedIn(await db.session()); 270 | const error = await shouldThrowError(async () => 271 | await db.logIn("username2", "test") 272 | ); 273 | error.status.should.equal(401); 274 | shouldNotBeLoggedIn(await db.session()); 275 | }); 276 | 277 | it('should not accept timed out sessions', async () => { 278 | // example stolen from calculate-couchdb-session-id's test suite. That 279 | // session timed out quite a bit ago. 280 | 281 | await db.useAsAuthenticationDB({ 282 | secret: '4ed13457964f05535fbb54c0e9f77a83', 283 | timeout: 3600, 284 | admins: { 285 | // password 'test' 286 | 'jan': '-pbkdf2-2be978bc2be874f755d8899cfddad18ed78e3c09,d5513283df4f649c72757a91aa30bdde,10' 287 | } 288 | }) 289 | 290 | var sessionID = 'amFuOjU2Njg4MkI5OkEK3-1SRseo6yNRHfk-mmk6zOxm'; 291 | shouldNotBeLoggedIn(await db.multiUserSession(sessionID)); 292 | }); 293 | }); 294 | -------------------------------------------------------------------------------- /src/refactor.js: -------------------------------------------------------------------------------- 1 | import { 2 | arrayExpression, 3 | awaitExpression, 4 | binaryExpression, 5 | blockStatement, 6 | booleanLiteral, 7 | callExpression, 8 | expressionStatement, 9 | identifier, 10 | ifStatement, 11 | isExpressionStatement, 12 | isReturnStatement, 13 | logicalExpression, 14 | memberExpression, 15 | numericLiteral, 16 | objectExpression, 17 | returnStatement, 18 | unaryExpression 19 | } from 'babel-types'; 20 | import {extend} from 'js-extend'; 21 | 22 | import PromiseChain from './promisechain'; 23 | import { 24 | assign, 25 | awaitStatement, 26 | containsAwait, 27 | NoSubFunctionsVisitor, 28 | wrapFunction 29 | } from './utils'; 30 | import {FirstPassIfVisitor, SecondPassIfVisitor} from './ifrefactor'; 31 | import PartialLoopRefactorVisitor from './looprefactor'; 32 | 33 | export const IfRefactorVisitor = SecondPassIfVisitor; 34 | 35 | export const RefactorVisitor = extend({ 36 | AwaitExpression(path) { 37 | // ``return await x`` becomes just ``return x`` 38 | if (isReturnStatement(path.parent)) { 39 | path.replaceWith(path.node.argument); 40 | } 41 | }, 42 | BinaryExpression(path) { 43 | // a() + await b 44 | // 45 | // -> 46 | // 47 | // _temp = a(), _temp + await b 48 | // 49 | // to make sure the execution order is correct. This provides a nice trick: 50 | // if you don't care about evaluation order and have one await-ed item in 51 | // your binary expression, put it on the left side of the operator. 52 | 53 | if (containsAwait(path.get('right')) && !path.node.left.isTemp) { 54 | const tmp = identifier(path.scope.generateUid('temp')); 55 | tmp.isTemp = true; 56 | this.addVarDecl(tmp); 57 | const assignment = assign(tmp, path.node.left); 58 | path.node.left = tmp; 59 | insertBefore(path, assignment); 60 | } 61 | }, 62 | ArrayExpression(path) { 63 | // [a(), await b()] 64 | // 65 | // -> 66 | // 67 | // await Promise.all([ 68 | // function () {return a();}(), 69 | // function () {return await b();}() 70 | // ]) 71 | // 72 | // (which is optimized away to:) 73 | // 74 | // await Promise.all([a(), b()]) 75 | 76 | if (path.get('elements').slice(1).some(containsAwait)) { 77 | const elements = path.node.elements.map(element => { 78 | return wrapFunction(blockStatement([returnStatement(element)])); 79 | }); 80 | const promiseAll = memberExpression(identifier('Promise'), identifier('all')); 81 | path.replaceWith(awaitExpression(callExpression(promiseAll, [arrayExpression(elements)]))); 82 | } 83 | }, 84 | CallExpression(path) { 85 | // call(a(), await b()) 86 | // 87 | // -> 88 | // 89 | // _temp = [a(), await b()], call(_temp[0], _temp[1]) 90 | 91 | if (path.get('arguments').slice(1).some(containsAwait)) { 92 | const tmp = identifier(path.scope.generateUid('temp')); 93 | this.addVarDecl(tmp); 94 | const assignment = assign(tmp, arrayExpression(path.node.arguments)); 95 | path.node.arguments = path.node.arguments.map((_, i) => { 96 | return memberExpression(tmp, numericLiteral(i), true); 97 | }) 98 | insertBefore(path, assignment); 99 | } 100 | }, 101 | ObjectExpression(path) { 102 | // {a: a(), b: await b()} 103 | // 104 | // -> 105 | // 106 | // _temp = {}, _temp.a = a(), _temp.b = await b(), _temp 107 | 108 | if (path.get('properties').slice(1).some(containsAwait)) { 109 | const tmp = identifier(path.scope.generateUid('temp')); 110 | this.addVarDecl(tmp); 111 | const assignments = [assign(tmp, objectExpression([]))]; 112 | path.node.properties.forEach(property => { 113 | const member = memberExpression(tmp, property.key); 114 | assignments.push(assign(member, property.value)); 115 | }); 116 | path.replaceWith(tmp); 117 | insertBefore(path, assignments); 118 | } 119 | }, 120 | TryStatement: { 121 | exit(path) { 122 | // changes a try/catch that contains an await in a promise chain that uses 123 | // .catch() 124 | // 125 | // uses exit() to make sure nested try/catch-es are converted correctly 126 | // too. 127 | 128 | if (containsAwait(path)) { 129 | const subChain = new PromiseChain(true, true, this.respID, this.errID); 130 | subChain.add(path.get('block.body')); 131 | if(path.node.handler) { 132 | subChain.addCatch(path.get('handler.body.body'), path.node.handler.param); 133 | } 134 | if (path.node.finalizer) { 135 | subChain.addFinally(path.get('finalizer.body')); 136 | } 137 | path.replaceWith(awaitStatement(subChain.toAST())); 138 | } 139 | } 140 | }, 141 | ConditionalExpression(path) { 142 | const {node} = path; 143 | const leftHasAwait = containsAwait(path.get('consequent')); 144 | const rightHasAwait = containsAwait(path.get('alternate')); 145 | if (leftHasAwait) { 146 | node.consequent = wrapAwaitContaining(node.consequent); 147 | } 148 | if (rightHasAwait) { 149 | node.alternate = wrapAwaitContaining(node.alternate); 150 | } 151 | if (leftHasAwait || rightHasAwait) { 152 | path.replaceWith(awaitExpression(path.node)); 153 | } 154 | }, 155 | LogicalExpression(path) { 156 | // a && (await b) becomes: 157 | // await a && async function () { 158 | // return await b(); 159 | // }() 160 | if (containsAwait(path.get('right'))) { 161 | path.node.right = wrapAwaitContaining(path.node.right); 162 | path.replaceWith(awaitExpression(path.node)); 163 | } 164 | }, 165 | SequenceExpression(path) { 166 | // a, await b, await c becomes: 167 | // await async function() { 168 | // a; 169 | // await b; 170 | // return await c; 171 | // } 172 | 173 | if (containsAwait(path)) { 174 | // don't include the last item yet 175 | const exprs = path.node.expressions; 176 | const body = exprs.slice(0, exprs.length - 1).map( 177 | expr => expressionStatement(expr) 178 | ); 179 | // because that one gets a return statement 180 | body.push(returnStatement(exprs[exprs.length - 1])); 181 | path.replaceWith(awaitExpression(wrapFunction(blockStatement(body)))); 182 | } 183 | }, 184 | ThisExpression(path) { 185 | path.replaceWith(this.thisID); 186 | this.used.thisID = true; 187 | }, 188 | Identifier(path) { 189 | if (path.node.name === 'arguments' && !path.scope.hasOwnBinding('arguments')) { 190 | path.replaceWith(this.argumentsID); 191 | this.used.argumentsID = true; 192 | } 193 | }, 194 | SwitchStatement(path) { 195 | // converts a switch statement in a bunch of if statements that compare the 196 | // discriminant to each test. Falling through is handled by a 'match' 197 | // variable, and the break statement is handled by a variable 'brokenOut'. 198 | // Cases after the default case are repeated so the default case can fall 199 | // through (but in such a way that they won't match again if the default 200 | // isn't falling through) 201 | 202 | const discrID = identifier(path.scope.generateUid('discriminant')); 203 | const matchID = identifier(path.scope.generateUid('match')); 204 | const brokenID = identifier(path.scope.generateUid('brokenOut')); 205 | this.addVarDecl(discrID); 206 | this.addVarDecl(matchID); 207 | this.addVarDecl(brokenID); 208 | 209 | // replace break statements with assignment expressions 210 | path.traverse(SwitchBreakReplacementVisitor, {brokenID}); 211 | 212 | const stmts = []; 213 | const notBroken = unaryExpression('!', brokenID); 214 | let defaultIdx; 215 | path.node.cases.forEach((caseNode, i) => { 216 | // add normal checks 217 | if (!caseNode.test) { 218 | defaultIdx = i; 219 | return; 220 | } 221 | 222 | // Seems like a weird order? Maybe, but it does prevent the 223 | // BinaryExpression refactorer to make too much of a mess for the sake of 224 | // strict execution order correctness. 225 | const isOwnMatch = binaryExpression('===', caseNode.test, discrID); 226 | const isMatch = logicalExpression('||', matchID, isOwnMatch); 227 | const test = logicalExpression('&&', notBroken, isMatch); 228 | stmts.push(ifStatement(test, blockStatement(caseNode.consequent.concat([ 229 | assign(matchID, booleanLiteral(true)) 230 | ])))); 231 | }); 232 | 233 | if (typeof defaultIdx !== 'undefined') { 234 | // add default case 235 | const notMatch = unaryExpression('!', matchID); 236 | const defaultTest = logicalExpression('&&', notBroken, notMatch); 237 | const body = path.node.cases[defaultIdx].consequent; 238 | path.node.cases.slice(defaultIdx + 1).forEach(caseNode => { 239 | // add fall through cases after default - still guarded by the default 240 | // check 241 | body.push(ifStatement(notBroken, blockStatement(caseNode.consequent))); 242 | }); 243 | stmts.push(ifStatement(defaultTest, blockStatement(body))); 244 | } 245 | 246 | path.replaceWithMultiple([ 247 | assign(discrID, path.node.discriminant), 248 | assign(matchID, booleanLiteral(false)), 249 | assign(brokenID, booleanLiteral(false)) 250 | ].concat(stmts)); 251 | }, 252 | FunctionDeclaration(path) { 253 | this.addFunctionDecl(path.node); 254 | path.remove(); 255 | }, 256 | FunctionExpression(path) { 257 | if (path.node.id && path.parent.type !== 'ObjectProperty') { 258 | path.node.type = 'FunctionDeclaration'; 259 | this.addFunctionDecl(path.node) 260 | path.replaceWith(path.node.id); 261 | } 262 | } 263 | }, FirstPassIfVisitor, PartialLoopRefactorVisitor, NoSubFunctionsVisitor); 264 | 265 | function insertBefore(path, node) { 266 | // prevent unnecessary sequence expressions. In normal JS they might be 267 | // elegant and thus nice for Babel, but their async wrapper is ugly. 268 | if (isExpressionStatement(path.parent) || isReturnStatement(path.parent)) { 269 | path.parentPath.insertBefore(node); 270 | } else { 271 | path.insertBefore(node); 272 | } 273 | } 274 | 275 | const SwitchBreakReplacementVisitor = extend({ 276 | BreakStatement(path) { 277 | // TODO: don't execute any code after the break assignment 278 | path.replaceWith(assign(this.brokenID, booleanLiteral(true))); 279 | } 280 | // TODO: don't touch sub switch statements. Enabling the following should be a 281 | // start. 282 | //SwitchStatement(path) { 283 | // path.skip(); 284 | //} 285 | }, NoSubFunctionsVisitor); 286 | 287 | const wrapAwaitContaining = 288 | node => wrapFunction(blockStatement([returnStatement(node)])); 289 | -------------------------------------------------------------------------------- /test/fixtures/pouchdb-auth/expected.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _utils = require('./utils'); 4 | 5 | var _extend = require('extend'); 6 | 7 | var _extend2 = _interopRequireDefault(_extend); 8 | 9 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 10 | 11 | var db = void 0; 12 | 13 | function shouldBeAdminParty(session) { 14 | session.info.should.eql({ 15 | "authentication_handlers": ["api"], 16 | "authentication_db": "test" 17 | }); 18 | session.userCtx.should.eql({ 19 | "name": null, 20 | "roles": ["_admin"] 21 | }); 22 | session.ok.should.be.ok; 23 | } 24 | 25 | function shouldNotBeLoggedIn(session) { 26 | session.info.should.eql({ 27 | authentication_handlers: ["api"], 28 | authentication_db: "test" 29 | }); 30 | session.userCtx.should.eql({ 31 | name: null, 32 | roles: [] 33 | }); 34 | session.ok.should.be.ok; 35 | } 36 | 37 | function shouldBeSuccesfulLogIn(data, roles) { 38 | var copy = (0, _extend2.default)({}, data); 39 | // irrelevant 40 | delete copy.sessionID; 41 | copy.should.eql({ 42 | "ok": true, 43 | "name": "username", 44 | "roles": roles 45 | }); 46 | } 47 | 48 | function shouldBeLoggedIn(session, roles) { 49 | session.userCtx.should.eql({ 50 | "name": "username", 51 | "roles": roles 52 | }); 53 | session.info.authenticated.should.equal("api"); 54 | } 55 | 56 | describe('SyncAuthTests', function () { 57 | beforeEach(function () { 58 | return Promise.resolve().then(function () { 59 | db = (0, _utils.setup)(); 60 | return db.useAsAuthenticationDB({ isOnlineAuthDB: false }); 61 | }).then(function (_resp) { 62 | _utils.should.not.exist(_resp); 63 | }); 64 | }); 65 | afterEach(_utils.teardown); 66 | 67 | it('should test the daemon', function () { 68 | // handled by beforeEach and afterEach 69 | }); 70 | 71 | it('should not allow stopping usage as an auth db twice', function () { 72 | return Promise.resolve().then(function () { 73 | return db.stopUsingAsAuthenticationDB(); 74 | }).then(function () { 75 | return (0, _utils.shouldThrowError)(function () { 76 | return db.stopUsingAsAuthenticationDB(); 77 | }); 78 | }).then(function () { 79 | return db.useAsAuthenticationDB(); 80 | }).then(function () {}); 81 | }); 82 | 83 | it('should not allow using a db as an auth db twice', function () { 84 | return Promise.resolve().then(function () { 85 | return (0, _utils.shouldThrowError)(function () { 86 | return db.useAsAuthenticationDB(); 87 | }); 88 | }).then(function () {}); 89 | }); 90 | 91 | it('should have working db methods', function () { 92 | var signUpData, doc, session, logInData, session2, session3, logOutData, session4, logOutData2, error; 93 | return Promise.resolve().then(function () { 94 | return db.signUp("username", "password", { roles: ["test"] }); 95 | }).then(function (_resp) { 96 | signUpData = _resp; 97 | 98 | signUpData.rev.indexOf("1-").should.equal(0); 99 | signUpData.ok.should.be.ok; 100 | signUpData.id.should.equal("org.couchdb.user:username"); 101 | 102 | return db.get("org.couchdb.user:username"); 103 | }).then(function (_resp) { 104 | doc = _resp; 105 | 106 | doc._rev.indexOf("1-").should.equal(0); 107 | doc.should.have.property("derived_key"); 108 | doc.iterations.should.equal(10); 109 | doc.name.should.equal("username"); 110 | doc.password_scheme.should.equal("pbkdf2"); 111 | doc.roles.should.eql(["test"]); 112 | doc.should.have.property("salt"); 113 | doc.type.should.equal("user"); 114 | 115 | doc.should.not.have.property("password"); 116 | 117 | return db.session(); 118 | }).then(function (_resp) { 119 | session = _resp; 120 | 121 | shouldBeAdminParty(session); 122 | 123 | return db.logIn("username", "password"); 124 | }).then(function (_resp) { 125 | logInData = _resp; 126 | 127 | shouldBeSuccesfulLogIn(logInData, ["test"]); 128 | 129 | return db.session(); 130 | }).then(function (_resp) { 131 | session2 = _resp; 132 | 133 | shouldBeLoggedIn(session2, ["test"]); 134 | 135 | return db.multiUserSession(); 136 | }).then(function (_resp) { 137 | session3 = _resp; 138 | 139 | shouldBeAdminParty(session3); 140 | 141 | return db.logOut(); 142 | }).then(function (_resp) { 143 | logOutData = _resp; 144 | 145 | logOutData.ok.should.be.ok; 146 | return db.session(); 147 | }).then(function (_resp) { 148 | session4 = _resp; 149 | 150 | shouldBeAdminParty(session4); 151 | 152 | //should also give a {ok: true} when not logged in. 153 | return db.logOut(); 154 | }).then(function (_resp) { 155 | logOutData2 = _resp; 156 | 157 | logOutData2.ok.should.be.ok; 158 | 159 | return (0, _utils.shouldThrowError)(function () { 160 | return db.logIn("username", "wrongPassword"); 161 | }); 162 | }).then(function (_resp) { 163 | error = _resp; 164 | 165 | error.status.should.equal(401); 166 | error.name.should.equal("unauthorized"); 167 | error.message.should.equal("Name or password is incorrect."); 168 | }); 169 | }); 170 | 171 | it('should support sign up without roles', function () { 172 | var result, resp2; 173 | return Promise.resolve().then(function () { 174 | return db.signUp("username", "password"); 175 | }).then(function (_resp) { 176 | result = _resp; 177 | 178 | result.ok.should.be.ok; 179 | 180 | return db.get("org.couchdb.user:username"); 181 | }).then(function (_resp) { 182 | resp2 = _resp; 183 | 184 | resp2.roles.should.eql([]); 185 | }); 186 | }); 187 | 188 | it('should validate docs', function () { 189 | var error, resp; 190 | return Promise.resolve().then(function () { 191 | return (0, _utils.shouldThrowError)(function () { 192 | return db.post({}); 193 | }); 194 | }).then(function (_resp) { 195 | error = _resp; 196 | 197 | error.status.should.equal(403); 198 | 199 | return db.bulkDocs([{}]); 200 | }).then(function (_resp) { 201 | resp = _resp; 202 | 203 | resp[0].status.should.equal(403); 204 | }); 205 | }); 206 | 207 | it('should handle conflicting logins', function () { 208 | var doc1, doc2, error; 209 | return Promise.resolve().then(function () { 210 | doc1 = { 211 | _id: "org.couchdb.user:test", 212 | _rev: "1-blabla", 213 | type: "user", 214 | name: "test", 215 | roles: [] 216 | }; 217 | doc2 = (0, _extend2.default)({}, doc1); 218 | 219 | doc2._rev = "2-something"; 220 | //generate conflict 221 | return db.bulkDocs([doc1, doc2], { new_edits: false }); 222 | }).then(function () { 223 | return (0, _utils.shouldThrowError)(function () { 224 | return db.logIn("test", "unimportant"); 225 | }); 226 | }).then(function (_resp) { 227 | error = _resp; 228 | 229 | 230 | error.status.should.equal(401); 231 | error.name.should.equal("unauthorized"); 232 | error.message.should.contain("conflict"); 233 | }); 234 | }); 235 | 236 | it('should not accept invalid session ids', function () { 237 | var err; 238 | return Promise.resolve().then(function () { 239 | return (0, _utils.shouldThrowError)(function () { 240 | return Promise.resolve().then(function () { 241 | return db.multiUserSession('invalid-session-id'); 242 | }).then(function () {}); 243 | }); 244 | }).then(function (_resp) { 245 | err = _resp; 246 | 247 | err.status.should.equal(400); 248 | err.name.should.equal('bad_request'); 249 | err.message.should.contain('Malformed'); 250 | }); 251 | }); 252 | 253 | afterEach(function () { 254 | return Promise.resolve().then(function () { 255 | return db.stopUsingAsAuthenticationDB(); 256 | }).then(function (_resp) { 257 | _utils.should.not.exist(_resp); 258 | }); 259 | }); 260 | }); 261 | 262 | describe('AsyncAuthTests', function () { 263 | beforeEach(function () { 264 | db = (0, _utils.setup)(); 265 | }); 266 | afterEach(_utils.teardown); 267 | it('should suport the basics', function (done) { 268 | function cb(err) { 269 | db.stopUsingAsAuthenticationDB(); 270 | done(err); 271 | } 272 | db.useAsAuthenticationDB(cb); 273 | }); 274 | }); 275 | 276 | describe('AsyncAuthTestsWithoutDaemon', function () { 277 | beforeEach(function () { 278 | db = (0, _utils.setup)(); 279 | }); 280 | afterEach(_utils.teardown); 281 | 282 | it('should be impossible to use the various exposed methods', function () { 283 | _utils.should.not.exist(db.signUp); 284 | _utils.should.not.exist(db.session); 285 | _utils.should.not.exist(db.logIn); 286 | _utils.should.not.exist(db.logOut); 287 | }); 288 | 289 | it('should hash admin passwords', function () { 290 | var admins, resp; 291 | return Promise.resolve().then(function () { 292 | admins = { 293 | test: "-pbkdf2-0abe2dcd23e0b6efc39004749e8d242ddefe46d1,16a1031881b31991f21a619112b1191fb1c41401be1f31d5,10", 294 | test2: "test" 295 | }; 296 | return db.hashAdminPasswords(admins); 297 | }).then(function (_resp) { 298 | resp = _resp; 299 | 300 | resp.test.should.equal(admins.test); 301 | //10 is the default amount of iterations 302 | resp.test2.indexOf("-pbkdf2-").should.equal(0); 303 | resp.test2.lastIndexOf(",10").should.equal(resp.test2.length - 3); 304 | }); 305 | }); 306 | 307 | it('should support changing admin passwords hash iterations', function () { 308 | var resp; 309 | return Promise.resolve().then(function () { 310 | return db.hashAdminPasswords({ 311 | abc: "test" 312 | }, { iterations: 11 }); 313 | }).then(function (_resp) { 314 | resp = _resp; 315 | 316 | resp.abc.indexOf("-pbkdf2-").should.equal(0); 317 | resp.abc.lastIndexOf(",11").should.equal(resp.abc.length - 3); 318 | }); 319 | }); 320 | }); 321 | 322 | describe('No automated test setup', function () { 323 | beforeEach(function () { 324 | db = (0, _utils.setup)(); 325 | }); 326 | afterEach(_utils.teardown); 327 | 328 | it('should support admin logins', function () { 329 | var opts, logInData, sessionData; 330 | return Promise.resolve().then(function () { 331 | opts = { 332 | admins: { 333 | username: '-pbkdf2-37508a1f1c5c19f38779fbe029ae99ee32988293,885e6e9e9031e391d5ef12abbb6c6aef,10' 334 | }, 335 | secret: db.generateSecret() 336 | }; 337 | return db.useAsAuthenticationDB(opts); 338 | }).then(function () { 339 | return db.multiUserSession(); 340 | }).then(function (_resp) { 341 | 342 | shouldNotBeLoggedIn(_resp); 343 | return db.multiUserLogIn('username', 'test'); 344 | }).then(function (_resp) { 345 | logInData = _resp; 346 | 347 | shouldBeSuccesfulLogIn(logInData, ['_admin']); 348 | 349 | db.stopUsingAsAuthenticationDB(); 350 | return db.useAsAuthenticationDB({/* no admins */}); 351 | }).then(function () { 352 | return db.multiUserSession(logInData.sessionID); 353 | }).then(function (_resp) { 354 | 355 | //if admins not supplied, there's no session (admin party!) 356 | shouldBeAdminParty(_resp); 357 | 358 | db.stopUsingAsAuthenticationDB(); 359 | return db.useAsAuthenticationDB(opts); 360 | }).then(function () { 361 | return db.multiUserSession(logInData.sessionID); 362 | }).then(function (_resp) { 363 | 364 | //otherwise there is 365 | sessionData = _resp; 366 | 367 | shouldBeLoggedIn(sessionData, ["_admin"]); 368 | 369 | //check if logout works (i.e. forgetting the session id.) 370 | return db.multiUserSession(); 371 | }).then(function (_resp) { 372 | shouldNotBeLoggedIn(_resp); 373 | }); 374 | }); 375 | 376 | it('should handle invalid admins field on login', function () { 377 | var admins, error; 378 | return Promise.resolve().then(function () { 379 | admins = { 380 | username: "-pbkdf2-37508a1f1c5c19f38779fbe029ae99ee32988293,885e6e9e9031e391d5ef12abbb6c6aef,10", 381 | username2: 'this-is-no-hash' 382 | }; 383 | return db.useAsAuthenticationDB({ admins: admins }); 384 | }).then(function () { 385 | return db.session(); 386 | }).then(function (_resp) { 387 | 388 | shouldNotBeLoggedIn(_resp); 389 | return (0, _utils.shouldThrowError)(function () { 390 | return db.logIn("username2", "test"); 391 | }); 392 | }).then(function (_resp) { 393 | error = _resp; 394 | 395 | error.status.should.equal(401); 396 | return db.session(); 397 | }).then(function (_resp) { 398 | shouldNotBeLoggedIn(_resp); 399 | }); 400 | }); 401 | 402 | it('should not accept timed out sessions', function () { 403 | var sessionID; 404 | return Promise.resolve().then(function () { 405 | return db.useAsAuthenticationDB({ 406 | secret: '4ed13457964f05535fbb54c0e9f77a83', 407 | timeout: 3600, 408 | admins: { 409 | // password 'test' 410 | 'jan': '-pbkdf2-2be978bc2be874f755d8899cfddad18ed78e3c09,d5513283df4f649c72757a91aa30bdde,10' 411 | } 412 | }); 413 | }).then(function () { 414 | sessionID = 'amFuOjU2Njg4MkI5OkEK3-1SRseo6yNRHfk-mmk6zOxm'; 415 | // example stolen from calculate-couchdb-session-id's test suite. That 416 | // session timed out quite a bit ago. 417 | 418 | return db.multiUserSession(sessionID); 419 | }).then(function (_resp) { 420 | shouldNotBeLoggedIn(_resp); 421 | }); 422 | }); 423 | }); 424 | --------------------------------------------------------------------------------