Skip to content

Commit c80916a

Browse files
authored
Merge pull request #15 from xjamundx/no-callback-literal
New Rule: no-callback-literal (fixes #12)
2 parents 30bee36 + f18d810 commit c80916a

File tree

4 files changed

+168
-2
lines changed

4 files changed

+168
-2
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ ESlint Rules for the Standard Linter
1313
'standard/object-curly-even-spacing': [2, "either"]
1414
'standard/array-bracket-even-spacing': [2, "either"],
1515
'standard/computed-property-even-spacing': [2, "even"]
16+
'standard/no-callback-literal': [2, ["cb", "callback", "next"]]
1617
}
1718
}
1819
```
@@ -24,4 +25,5 @@ There are several rules that were created specifically for the `standard` linter
2425
- `object-curly-even-spacing` - Like `object-curly-spacing` from ESLint except it has an `either` option which lets you have 1 or 0 spaces padding.
2526
- `array-bracket-even-spacing` - Like `array-bracket-even-spacing` from ESLint except it has an `either` option which lets you have 1 or 0 spacing padding.
2627
- `computed-property-even-spacing` - Like `computed-property-spacing` around ESLint except is has an `even` option which lets you have 1 or 0 spacing padding.
28+
- `no-callback-literal` - Ensures that we strictly follow the callback pattern with `undefined`, `false`, `null` or an error object in the first position of a callback.
2729

index.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,13 @@ module.exports = {
44
rules: {
55
'array-bracket-even-spacing': require('./rules/array-bracket-even-spacing.js'),
66
'computed-property-even-spacing': require('./rules/computed-property-even-spacing.js'),
7-
'object-curly-even-spacing': require('./rules/object-curly-even-spacing.js')
7+
'object-curly-even-spacing': require('./rules/object-curly-even-spacing.js'),
8+
'no-callback-literal': require('./rules/no-callback-literal.js')
89
},
910
rulesConfig: {
1011
'object-curly-even-spacing': 0,
1112
'array-bracket-even-spacing': 0,
12-
'computed-property-even-spacing': 0
13+
'computed-property-even-spacing': 0,
14+
'no-callback-literal': 0
1315
}
1416
}

rules/no-callback-literal.js

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/**
2+
* Ensures that the callback pattern is followed properly
3+
* with an Error object (or undefined or null) in the first position.
4+
*/
5+
6+
'use strict'
7+
8+
// ------------------------------------------------------------------------------
9+
// Helpers
10+
// ------------------------------------------------------------------------------
11+
12+
/**
13+
* Determine if a node has a possiblity to be an Error object
14+
* @param {ASTNode} node ASTNode to check
15+
* @returns {boolean} True if there is a chance it contains an Error obj
16+
*/
17+
function couldBeError (node) {
18+
switch (node.type) {
19+
case 'Identifier':
20+
case 'CallExpression':
21+
case 'NewExpression':
22+
case 'MemberExpression':
23+
case 'TaggedTemplateExpression':
24+
case 'YieldExpression':
25+
return true // possibly an error object.
26+
27+
case 'AssignmentExpression':
28+
return couldBeError(node.right)
29+
30+
case 'SequenceExpression':
31+
var exprs = node.expressions
32+
return exprs.length !== 0 && couldBeError(exprs[exprs.length - 1])
33+
34+
case 'LogicalExpression':
35+
return couldBeError(node.left) || couldBeError(node.right)
36+
37+
case 'ConditionalExpression':
38+
return couldBeError(node.consequent) || couldBeError(node.alternate)
39+
40+
default:
41+
return node.value === null || node.value === false
42+
}
43+
}
44+
45+
// ------------------------------------------------------------------------------
46+
// Rule Definition
47+
// ------------------------------------------------------------------------------
48+
49+
module.exports = {
50+
meta: {
51+
docs: {}
52+
},
53+
54+
create: function (context) {
55+
var callbackNames = context.options[0] || ['callback', 'next', 'cb']
56+
57+
function isCallback (name) {
58+
return callbackNames.indexOf(name) > -1
59+
}
60+
61+
return {
62+
63+
CallExpression: function (node) {
64+
var errorArg = node.arguments[0]
65+
if (errorArg && isCallback(node.callee.name)) {
66+
if (!couldBeError(errorArg)) {
67+
context.report(node, 'Unexpected literal in error position of callback.')
68+
} else if (node.arguments.length > 1 && errorArg.type === 'Identifier') {
69+
if (errorArg.name === 'undefined') {
70+
context.report(node, 'Expected null instead found undefined.')
71+
}
72+
}
73+
}
74+
}
75+
}
76+
}
77+
}

tests/no-callback-literal.js

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/**
2+
* @fileoverview Tests for the no-callback-literal rule
3+
* @author Jamund Ferguson
4+
* @copyright 2016 Jamund Ferguson. All rights reserved.
5+
*/
6+
'use strict'
7+
8+
// ------------------------------------------------------------------------------
9+
// Requirements
10+
// ------------------------------------------------------------------------------
11+
12+
var RuleTester = require('eslint').RuleTester
13+
var rule = require('../rules/no-callback-literal')
14+
15+
// ------------------------------------------------------------------------------
16+
// Tests
17+
// ------------------------------------------------------------------------------
18+
19+
var ruleTester = new RuleTester()
20+
ruleTester.run('no-callback-literal', rule, {
21+
valid: [
22+
23+
// random stuff
24+
'horse()',
25+
'sort(null)',
26+
'require("zyx")',
27+
'require("zyx", data)',
28+
29+
// callback()
30+
'callback()',
31+
'callback(undefined)',
32+
'callback(null)',
33+
'callback(x)',
34+
'callback(false)',
35+
'callback(new Error("error"))',
36+
'callback(friendly, data)',
37+
'callback(null, data)',
38+
'callback(x, data)',
39+
'callback(new Error("error"), data)',
40+
41+
// cb()
42+
'cb()',
43+
'cb(false)',
44+
'cb(undefined)',
45+
'cb(null)',
46+
'cb(null, "super")',
47+
48+
// next()
49+
'next()',
50+
'next(undefined)',
51+
'next(null)',
52+
'next(null, "super")',
53+
'next(false, "super")',
54+
55+
// custom callback
56+
{
57+
code: 'callback(44); next("55"); power(new Error("super thing")); power(null);',
58+
options: [['power']]
59+
}
60+
],
61+
62+
invalid: [
63+
// callback
64+
{ code: 'callback(undefined, "snork")', errors: [{ message: 'Expected null instead found undefined.' }] },
65+
{ code: 'callback("help")', errors: [{ message: 'Unexpected literal in error position of callback.' }] },
66+
{ code: 'callback("help", data)', errors: [{ message: 'Unexpected literal in error position of callback.' }] },
67+
68+
// cb
69+
{ code: 'cb(undefined, "snork")', errors: [{ message: 'Expected null instead found undefined.' }] },
70+
{ code: 'cb("help")', errors: [{ message: 'Unexpected literal in error position of callback.' }] },
71+
{ code: 'cb("help", data)', errors: [{ message: 'Unexpected literal in error position of callback.' }] },
72+
73+
// next
74+
{ code: 'next(undefined, "snork")', errors: [{ message: 'Expected null instead found undefined.' }] },
75+
{ code: 'next("help")', errors: [{ message: 'Unexpected literal in error position of callback.' }] },
76+
{ code: 'next("help", data)', errors: [{ message: 'Unexpected literal in error position of callback.' }] },
77+
78+
// custom callback name
79+
{
80+
code: 'nexty(44)',
81+
options: [['nexty']],
82+
errors: [{ message: 'Unexpected literal in error position of callback.' }]
83+
}
84+
]
85+
})

0 commit comments

Comments
 (0)