Skip to content

Commit

Permalink
Added async/await rules
Browse files Browse the repository at this point in the history
  • Loading branch information
xjamundx committed Oct 18, 2016
1 parent 4b59f4c commit 3c077fa
Show file tree
Hide file tree
Showing 9 changed files with 198 additions and 28 deletions.
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,18 @@ Then configure the rules you want to use under the rules section.

## Rules

### Promise Rules

- `catch-or-return` Enforces the use of `catch` on un-returned promises.
- `no-return-wrap` Avoid wrapping values in `Promise.resolve` or `Promise.reject` when not needed.
- `param-names` Enforce consistent param names when creating new promises.
- `always-return` Return inside each `then` to create readable and reusable Promise chains.
- `no-native` In an ES5 environment, make sure to create a `Promise` constructor before using.

### Async/Await Rules

- `prefer-await-to-then` Prefer `await` to `then()` for reading Promise values
- `prefer-await-to-callbacks` Prefer async/await to the callback pattern

### Rule: `catch-or-return`

Expand Down
2 changes: 2 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ module.exports = {
'no-return-wrap': require('./rules/no-return-wrap'),
'always-return': require('./rules/always-return'),
'catch-or-return': require('./rules/catch-or-return'),
'prefer-await-to-callbacks': require('./rules/prefer-await-to-callbacks'),
'prefer-await-to-then': require('./rules/prefer-await-to-then'),
'no-native': require('./rules/no-native')
},
rulesConfig: {
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"test": "mocha test"
},
"devDependencies": {
"eslint": "^2.10 || ^3.0",
"eslint": "^3.0",
"mocha": "^2.3.4",
"standard": "^7.1.2"
},
Expand Down
18 changes: 9 additions & 9 deletions rules/always-return.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,25 +10,25 @@ function isFunctionWithBlockStatement (node) {

function isThenCallExpression (node) {
return (
node.type === 'CallExpression' &&
node.callee.type === 'MemberExpression' &&
node.callee.property.name === 'then'
node.type === 'CallExpression' &&
node.callee.type === 'MemberExpression' &&
node.callee.property.name === 'then'
)
}

function isFirstArgument (node) {
return (
node.parent &&
node.parent.arguments &&
node.parent.arguments[0] === node
node.parent &&
node.parent.arguments &&
node.parent.arguments[0] === node
)
}

function isInlineThenFunctionExpression (node) {
return (
isFunctionWithBlockStatement(node) &&
isThenCallExpression(node.parent) &&
isFirstArgument(node)
isFunctionWithBlockStatement(node) &&
isThenCallExpression(node.parent) &&
isFirstArgument(node)
)
}

Expand Down
36 changes: 18 additions & 18 deletions rules/lib/is-promise.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,24 +11,24 @@ var STATIC_METHODS = [

function isPromise (expression) {
return ( // hello.then()
expression.type === 'CallExpression' &&
expression.callee.type === 'MemberExpression' &&
expression.callee.property.name === 'then'
) || ( // hello.catch()
expression.type === 'CallExpression' &&
expression.callee.type === 'MemberExpression' &&
expression.callee.property.name === 'catch'
) || ( // somePromise.ANYTHING()
expression.type === 'CallExpression' &&
expression.callee.type === 'MemberExpression' &&
isPromise(expression.callee.object)
) || ( // Promise.STATIC_METHOD()
expression.type === 'CallExpression' &&
expression.callee.type === 'MemberExpression' &&
expression.callee.object.type === 'Identifier' &&
expression.callee.object.name === 'Promise' &&
STATIC_METHODS.indexOf(expression.callee.property.name) !== -1
)
expression.type === 'CallExpression' &&
expression.callee.type === 'MemberExpression' &&
expression.callee.property.name === 'then'
) || ( // hello.catch()
expression.type === 'CallExpression' &&
expression.callee.type === 'MemberExpression' &&
expression.callee.property.name === 'catch'
) || ( // somePromise.ANYTHING()
expression.type === 'CallExpression' &&
expression.callee.type === 'MemberExpression' &&
isPromise(expression.callee.object)
) || ( // Promise.STATIC_METHOD()
expression.type === 'CallExpression' &&
expression.callee.type === 'MemberExpression' &&
expression.callee.object.type === 'Identifier' &&
expression.callee.object.name === 'Promise' &&
STATIC_METHODS.indexOf(expression.callee.property.name) !== -1
)
}

module.exports = isPromise
45 changes: 45 additions & 0 deletions rules/prefer-await-to-callbacks.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/**
* Rule: prefer-await-to-callbacks
* Discourage using then() and instead use async/await.
*/

var errorMessage = 'Avoid callbacks. Prefer Async/Await.'

module.exports = function (context) {
function checkLastParamsForCallback (node) {
var len = node.params.length - 1
var lastParam = node.params[len]
if (lastParam && (lastParam.name === 'callback' || lastParam.name === 'cb')) {
context.report(lastParam, errorMessage)
}
}
function isInsideYieldOrAwait () {
return context.getAncestors().some(function (parent) {
return parent.type === 'AwaitExpression' || parent.type === 'YieldExpression'
})
}
return {
CallExpression: function (node) {
// callbacks aren't allowed
if (node.callee.name === 'cb' || node.callee.name === 'callback') {
context.report(node, errorMessage)
return
}

// thennables aren't allowed either
var args = node.arguments
var num = args.length - 1
var arg = num > -1 && node.arguments && node.arguments[num]
if (arg && arg.type === 'FunctionExpression' || arg.type === 'ArrowFunctionExpression') {
if (arg.params && arg.params[0] && arg.params[0].name === 'err') {
if (!isInsideYieldOrAwait()) {
context.report(arg, errorMessage)
}
}
}
},
FunctionDeclaration: checkLastParamsForCallback,
FunctionExpression: checkLastParamsForCallback,
ArrowFunctionExpression: checkLastParamsForCallback
}
}
22 changes: 22 additions & 0 deletions rules/prefer-await-to-then.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/**
* Rule: prefer-await-to-then
* Discourage using then() and instead use async/await.
*/

module.exports = function (context) {
return {
MemberExpression: function (node) {
// you can then() if you are inside of a yield or await
if (context.getAncestors().some(function (parent) {
return parent.type === 'AwaitExpression' || parent.type === 'YieldExpression'
})) {
return
}

// if you're a then expression then you're probably a promise
if (node.property && node.property.name === 'then') {
context.report(node.property, 'Prefer await to then().')
}
}
}
}
58 changes: 58 additions & 0 deletions test/prefer-await-to-callbacks.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
'use strict'

var rule = require('../rules/prefer-await-to-callbacks')
var RuleTester = require('eslint').RuleTester
var message = 'Avoid callbacks. Prefer Async/Await.'
var parserOptions = { ecmaVersion: 8 }
var ruleTester = new RuleTester()

ruleTester.run('prefer-await-to-callbacks', rule, {
valid: [
{ code: 'async function hi() { await thing().catch(err => console.log(err)) }', parserOptions: parserOptions },
{ code: 'async function hi() { await thing().then() }', parserOptions: parserOptions },
{ code: 'async function hi() { await thing().catch() }', parserOptions: parserOptions }
],

invalid: [
{
code: 'heart(function(err) {})',
parserOptions: parserOptions,
errors: [ { message: message } ]
},
{
code: 'heart(err => {})',
parserOptions: parserOptions,
errors: [ { message: message } ]
},
{
code: 'heart("ball", function(err) {})',
parserOptions: parserOptions,
errors: [ { message: message } ]
},
{
code: 'function getData(id, callback) {}',
parserOptions: parserOptions,
errors: [ { message: message } ]
},
{
code: 'const getData = (cb) => {}',
parserOptions: parserOptions,
errors: [ { message: message } ]
},
{
code: 'var x = function (x, cb) {}',
parserOptions: parserOptions,
errors: [ { message: message } ]
},
{
code: 'cb()',
parserOptions: parserOptions,
errors: [ { message: message } ]
},
{
code: 'callback()',
parserOptions: parserOptions,
errors: [ { message: message } ]
}
]
})
37 changes: 37 additions & 0 deletions test/prefer-await-to-then.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
'use strict'

var rule = require('../rules/prefer-await-to-then')
var RuleTester = require('eslint').RuleTester
var message = 'Prefer await to then().'
var parserOptions = { ecmaVersion: 8 }
var ruleTester = new RuleTester()
ruleTester.run('prefer-await-to-then', rule, {
valid: [
{ code: 'async function hi() { await thing() }', parserOptions: parserOptions },
{ code: 'async function hi() { await thing().then() }', parserOptions: parserOptions },
{ code: 'async function hi() { await thing().catch() }', parserOptions: parserOptions }
],

invalid: [
{
code: 'hey.then(x => {})',
parserOptions: parserOptions,
errors: [ { message: message } ]
},
{
code: 'hey.then(function() { }).then()',
parserOptions: parserOptions,
errors: [ { message: message }, { message: message } ]
},
{
code: 'hey.then(function() { }).then(x).catch()',
parserOptions: parserOptions,
errors: [ { message: message }, { message: message } ]
},
{
code: 'async function a() { hey.then(function() { }).then(function() { }) }',
parserOptions: parserOptions,
errors: [ { message: message }, { message: message } ]
}
]
})

0 comments on commit 3c077fa

Please sign in to comment.