-
-
Notifications
You must be signed in to change notification settings - Fork 171
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
✨ add no-callback-literal rule (#179)
- Loading branch information
1 parent
72de3a3
commit c1d5dbf
Showing
3 changed files
with
240 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
# node/no-callback-literal | ||
|
||
> Ensures the Node.js error-first callback pattern is followed | ||
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. | ||
|
||
## 📖 Rule Details | ||
|
||
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`. | ||
|
||
Examples of :-1: **incorrect** code for this rule: | ||
|
||
```js | ||
/*eslint node/no-callback-literal: "error" */ | ||
|
||
cb('this is an error string'); | ||
cb({ a: 1 }); | ||
callback(0); | ||
``` | ||
|
||
Examples of :+1: **correct** code for this rule: | ||
|
||
```js | ||
/*eslint node/no-callback-literal: "error" */ | ||
|
||
cb(undefined); | ||
cb(null, 5); | ||
callback(new Error('some error')); | ||
callback(someVariable); | ||
``` | ||
|
||
### Options | ||
|
||
```json | ||
{ | ||
"rules": { | ||
"node/no-callback-literal": "error" | ||
} | ||
} | ||
``` | ||
|
||
## 🔎 Implementation | ||
|
||
- [Rule source](../../lib/rules/no-callback-literal.js) | ||
- [Test source](../../tests/lib/rules/no-callback-literal.js) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
/** | ||
* @author Jamund Ferguson | ||
* See LICENSE file in root directory for full license. | ||
*/ | ||
"use strict" | ||
|
||
module.exports = { | ||
meta: { | ||
docs: { | ||
description: | ||
"ensure Node.js-style error-first callback pattern is followed", | ||
category: "Possible Errors", | ||
recommended: false, | ||
url: | ||
"https://github.com/mysticatea/eslint-plugin-node/blob/v9.1.0/docs/rules/no-callback-literal.md", | ||
}, | ||
type: "problem", | ||
fixable: null, | ||
schema: [], | ||
}, | ||
|
||
create(context) { | ||
const callbackNames = ["callback", "cb"] | ||
|
||
function isCallback(name) { | ||
return callbackNames.indexOf(name) > -1 | ||
} | ||
|
||
return { | ||
CallExpression(node) { | ||
const errorArg = node.arguments[0] | ||
const calleeName = node.callee.name | ||
|
||
if ( | ||
errorArg && | ||
!couldBeError(errorArg) && | ||
isCallback(calleeName) | ||
) { | ||
context.report({ | ||
node, | ||
message: | ||
"Unexpected literal in error position of callback.", | ||
}) | ||
} | ||
}, | ||
} | ||
}, | ||
} | ||
|
||
/** | ||
* Determine if a node has a possiblity to be an Error object | ||
* @param {ASTNode} node ASTNode to check | ||
* @returns {boolean} True if there is a chance it contains an Error obj | ||
*/ | ||
function couldBeError(node) { | ||
switch (node.type) { | ||
case "Identifier": | ||
case "CallExpression": | ||
case "NewExpression": | ||
case "MemberExpression": | ||
case "TaggedTemplateExpression": | ||
case "YieldExpression": | ||
return true // possibly an error object. | ||
|
||
case "AssignmentExpression": | ||
return couldBeError(node.right) | ||
|
||
case "SequenceExpression": { | ||
const exprs = node.expressions | ||
return exprs.length !== 0 && couldBeError(exprs[exprs.length - 1]) | ||
} | ||
|
||
case "LogicalExpression": | ||
return couldBeError(node.left) || couldBeError(node.right) | ||
|
||
case "ConditionalExpression": | ||
return couldBeError(node.consequent) || couldBeError(node.alternate) | ||
|
||
default: | ||
return node.value === null | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
/** | ||
* @author Jamund Ferguson | ||
* See LICENSE file in root directory for full license. | ||
*/ | ||
"use strict" | ||
|
||
const RuleTester = require("eslint").RuleTester | ||
const rule = require("../../../lib/rules/no-callback-literal") | ||
|
||
const ruleTester = new RuleTester() | ||
ruleTester.run("no-callback-literal", rule, { | ||
valid: [ | ||
// random stuff | ||
"horse()", | ||
"sort(null)", | ||
'require("zyx")', | ||
'require("zyx", data)', | ||
|
||
// callback() | ||
"callback()", | ||
"callback(undefined)", | ||
"callback(null)", | ||
"callback(x)", | ||
'callback(new Error("error"))', | ||
"callback(friendly, data)", | ||
"callback(undefined, data)", | ||
"callback(null, data)", | ||
"callback(x, data)", | ||
'callback(new Error("error"), data)', | ||
"callback(x = obj, data)", | ||
"callback((1, a), data)", | ||
"callback(a || b, data)", | ||
"callback(a ? b : c, data)", | ||
"callback(a ? 1 : c, data)", | ||
"callback(a ? b : 1, data)", | ||
|
||
// cb() | ||
"cb()", | ||
"cb(undefined)", | ||
"cb(null)", | ||
'cb(undefined, "super")', | ||
'cb(null, "super")', | ||
], | ||
|
||
invalid: [ | ||
// callback | ||
{ | ||
code: 'callback(false, "snork")', | ||
errors: [ | ||
{ | ||
message: | ||
"Unexpected literal in error position of callback.", | ||
}, | ||
], | ||
}, | ||
{ | ||
code: 'callback("help")', | ||
errors: [ | ||
{ | ||
message: | ||
"Unexpected literal in error position of callback.", | ||
}, | ||
], | ||
}, | ||
{ | ||
code: 'callback("help", data)', | ||
errors: [ | ||
{ | ||
message: | ||
"Unexpected literal in error position of callback.", | ||
}, | ||
], | ||
}, | ||
|
||
// cb | ||
{ | ||
code: "cb(false)", | ||
errors: [ | ||
{ | ||
message: | ||
"Unexpected literal in error position of callback.", | ||
}, | ||
], | ||
}, | ||
{ | ||
code: 'cb("help")', | ||
errors: [ | ||
{ | ||
message: | ||
"Unexpected literal in error position of callback.", | ||
}, | ||
], | ||
}, | ||
{ | ||
code: 'cb("help", data)', | ||
errors: [ | ||
{ | ||
message: | ||
"Unexpected literal in error position of callback.", | ||
}, | ||
], | ||
}, | ||
{ | ||
code: "callback((a, 1), data)", | ||
errors: [ | ||
{ | ||
message: | ||
"Unexpected literal in error position of callback.", | ||
}, | ||
], | ||
}, | ||
], | ||
}) |