Skip to content

Commit c1d5dbf

Browse files
ferossmysticatea
authored andcommittedAug 29, 2019
✨ add no-callback-literal rule (#179)
1 parent 72de3a3 commit c1d5dbf

File tree

3 files changed

+240
-0
lines changed

3 files changed

+240
-0
lines changed
 

‎docs/rules/no-callback-literal.md

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# node/no-callback-literal
2+
3+
> Ensures the Node.js error-first callback pattern is followed
4+
5+
When invoking a callback function which uses the Node.js error-first callback pattern, all of your errors should either use the `Error` class or a subclass of it. It is also acceptable to use `undefined` or `null` if there is no error.
6+
7+
## 📖 Rule Details
8+
9+
When a function is named `cb` or `callback`, then it must be invoked with a first argument that is `undefined`, `null`, an `Error` class, or a subclass or `Error`.
10+
11+
Examples of :-1: **incorrect** code for this rule:
12+
13+
```js
14+
/*eslint node/no-callback-literal: "error" */
15+
16+
cb('this is an error string');
17+
cb({ a: 1 });
18+
callback(0);
19+
```
20+
21+
Examples of :+1: **correct** code for this rule:
22+
23+
```js
24+
/*eslint node/no-callback-literal: "error" */
25+
26+
cb(undefined);
27+
cb(null, 5);
28+
callback(new Error('some error'));
29+
callback(someVariable);
30+
```
31+
32+
### Options
33+
34+
```json
35+
{
36+
"rules": {
37+
"node/no-callback-literal": "error"
38+
}
39+
}
40+
```
41+
42+
## 🔎 Implementation
43+
44+
- [Rule source](../../lib/rules/no-callback-literal.js)
45+
- [Test source](../../tests/lib/rules/no-callback-literal.js)

‎lib/rules/no-callback-literal.js

+82
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/**
2+
* @author Jamund Ferguson
3+
* See LICENSE file in root directory for full license.
4+
*/
5+
"use strict"
6+
7+
module.exports = {
8+
meta: {
9+
docs: {
10+
description:
11+
"ensure Node.js-style error-first callback pattern is followed",
12+
category: "Possible Errors",
13+
recommended: false,
14+
url:
15+
"https://github.com/mysticatea/eslint-plugin-node/blob/v9.1.0/docs/rules/no-callback-literal.md",
16+
},
17+
type: "problem",
18+
fixable: null,
19+
schema: [],
20+
},
21+
22+
create(context) {
23+
const callbackNames = ["callback", "cb"]
24+
25+
function isCallback(name) {
26+
return callbackNames.indexOf(name) > -1
27+
}
28+
29+
return {
30+
CallExpression(node) {
31+
const errorArg = node.arguments[0]
32+
const calleeName = node.callee.name
33+
34+
if (
35+
errorArg &&
36+
!couldBeError(errorArg) &&
37+
isCallback(calleeName)
38+
) {
39+
context.report({
40+
node,
41+
message:
42+
"Unexpected literal in error position of callback.",
43+
})
44+
}
45+
},
46+
}
47+
},
48+
}
49+
50+
/**
51+
* Determine if a node has a possiblity to be an Error object
52+
* @param {ASTNode} node ASTNode to check
53+
* @returns {boolean} True if there is a chance it contains an Error obj
54+
*/
55+
function couldBeError(node) {
56+
switch (node.type) {
57+
case "Identifier":
58+
case "CallExpression":
59+
case "NewExpression":
60+
case "MemberExpression":
61+
case "TaggedTemplateExpression":
62+
case "YieldExpression":
63+
return true // possibly an error object.
64+
65+
case "AssignmentExpression":
66+
return couldBeError(node.right)
67+
68+
case "SequenceExpression": {
69+
const exprs = node.expressions
70+
return exprs.length !== 0 && couldBeError(exprs[exprs.length - 1])
71+
}
72+
73+
case "LogicalExpression":
74+
return couldBeError(node.left) || couldBeError(node.right)
75+
76+
case "ConditionalExpression":
77+
return couldBeError(node.consequent) || couldBeError(node.alternate)
78+
79+
default:
80+
return node.value === null
81+
}
82+
}
+113
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
/**
2+
* @author Jamund Ferguson
3+
* See LICENSE file in root directory for full license.
4+
*/
5+
"use strict"
6+
7+
const RuleTester = require("eslint").RuleTester
8+
const rule = require("../../../lib/rules/no-callback-literal")
9+
10+
const ruleTester = new RuleTester()
11+
ruleTester.run("no-callback-literal", rule, {
12+
valid: [
13+
// random stuff
14+
"horse()",
15+
"sort(null)",
16+
'require("zyx")',
17+
'require("zyx", data)',
18+
19+
// callback()
20+
"callback()",
21+
"callback(undefined)",
22+
"callback(null)",
23+
"callback(x)",
24+
'callback(new Error("error"))',
25+
"callback(friendly, data)",
26+
"callback(undefined, data)",
27+
"callback(null, data)",
28+
"callback(x, data)",
29+
'callback(new Error("error"), data)',
30+
"callback(x = obj, data)",
31+
"callback((1, a), data)",
32+
"callback(a || b, data)",
33+
"callback(a ? b : c, data)",
34+
"callback(a ? 1 : c, data)",
35+
"callback(a ? b : 1, data)",
36+
37+
// cb()
38+
"cb()",
39+
"cb(undefined)",
40+
"cb(null)",
41+
'cb(undefined, "super")',
42+
'cb(null, "super")',
43+
],
44+
45+
invalid: [
46+
// callback
47+
{
48+
code: 'callback(false, "snork")',
49+
errors: [
50+
{
51+
message:
52+
"Unexpected literal in error position of callback.",
53+
},
54+
],
55+
},
56+
{
57+
code: 'callback("help")',
58+
errors: [
59+
{
60+
message:
61+
"Unexpected literal in error position of callback.",
62+
},
63+
],
64+
},
65+
{
66+
code: 'callback("help", data)',
67+
errors: [
68+
{
69+
message:
70+
"Unexpected literal in error position of callback.",
71+
},
72+
],
73+
},
74+
75+
// cb
76+
{
77+
code: "cb(false)",
78+
errors: [
79+
{
80+
message:
81+
"Unexpected literal in error position of callback.",
82+
},
83+
],
84+
},
85+
{
86+
code: 'cb("help")',
87+
errors: [
88+
{
89+
message:
90+
"Unexpected literal in error position of callback.",
91+
},
92+
],
93+
},
94+
{
95+
code: 'cb("help", data)',
96+
errors: [
97+
{
98+
message:
99+
"Unexpected literal in error position of callback.",
100+
},
101+
],
102+
},
103+
{
104+
code: "callback((a, 1), data)",
105+
errors: [
106+
{
107+
message:
108+
"Unexpected literal in error position of callback.",
109+
},
110+
],
111+
},
112+
],
113+
})

0 commit comments

Comments
 (0)
Please sign in to comment.