From c1d5dbfbd01e1d2483fc3b970bc2ab2d61580db6 Mon Sep 17 00:00:00 2001 From: Feross Aboukhadijeh Date: Thu, 29 Aug 2019 02:59:40 -0700 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20add=20no-callback-literal=20rule=20?= =?UTF-8?q?(#179)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/rules/no-callback-literal.md | 45 ++++++++++ lib/rules/no-callback-literal.js | 82 ++++++++++++++++++ tests/lib/rules/no-callback-literal.js | 113 +++++++++++++++++++++++++ 3 files changed, 240 insertions(+) create mode 100644 docs/rules/no-callback-literal.md create mode 100644 lib/rules/no-callback-literal.js create mode 100644 tests/lib/rules/no-callback-literal.js diff --git a/docs/rules/no-callback-literal.md b/docs/rules/no-callback-literal.md new file mode 100644 index 00000000..6281c313 --- /dev/null +++ b/docs/rules/no-callback-literal.md @@ -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) diff --git a/lib/rules/no-callback-literal.js b/lib/rules/no-callback-literal.js new file mode 100644 index 00000000..0db94233 --- /dev/null +++ b/lib/rules/no-callback-literal.js @@ -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 + } +} diff --git a/tests/lib/rules/no-callback-literal.js b/tests/lib/rules/no-callback-literal.js new file mode 100644 index 00000000..9548f921 --- /dev/null +++ b/tests/lib/rules/no-callback-literal.js @@ -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.", + }, + ], + }, + ], +})