UNPKG

5 kBJavaScriptView Raw
1/**
2 * @fileoverview Enforce return after a callback.
3 * @author Jamund Ferguson
4 * @copyright 2015 Jamund Ferguson. All rights reserved.
5 */
6"use strict";
7
8//------------------------------------------------------------------------------
9// Rule Definition
10//------------------------------------------------------------------------------
11
12module.exports = function(context) {
13
14 var callbacks = context.options[0] || ["callback", "cb", "next"];
15
16 //--------------------------------------------------------------------------
17 // Helpers
18 //--------------------------------------------------------------------------
19
20 /**
21 * Find the closest parent matching a list of types.
22 * @param {ASTNode} node The node whose parents we are searching
23 * @param {Array} types The node types to match
24 * @returns {ASTNode} The matched node or undefined.
25 */
26 function findClosestParentOfType(node, types) {
27 if (!node.parent) {
28 return null;
29 }
30 if (types.indexOf(node.parent.type) === -1) {
31 return findClosestParentOfType(node.parent, types);
32 }
33 return node.parent;
34 }
35
36 /**
37 * Check to see if a CallExpression is in our callback list.
38 * @param {ASTNode} node The node to check against our callback names list.
39 * @returns {Boolean} Whether or not this function matches our callback name.
40 */
41 function isCallback(node) {
42 return node.callee.type === "Identifier" && callbacks.indexOf(node.callee.name) > -1;
43 }
44
45 /**
46 * Determines whether or not the callback is part of a callback expression.
47 * @param {ASTNode} node The callback node
48 * @param {ASTNode} parentNode The expression node
49 * @returns {boolean} Whether or not this is part of a callback expression
50 */
51 function isCallbackExpression(node, parentNode) {
52 // ensure the parent node exists and is an expression
53 if (!parentNode || parentNode.type !== "ExpressionStatement") {
54 return false;
55 }
56
57 // cb()
58 if (parentNode.expression === node) {
59 return true;
60 }
61
62 // special case for cb && cb() and similar
63 if (parentNode.expression.type === "BinaryExpression" || parentNode.expression.type === "LogicalExpression") {
64 if (parentNode.expression.right === node) {
65 return true;
66 }
67 }
68
69 return false;
70 }
71
72 //--------------------------------------------------------------------------
73 // Public
74 //--------------------------------------------------------------------------
75
76 return {
77 "CallExpression": function(node) {
78
79 // if we"re not a callback we can return
80 if (!isCallback(node)) {
81 return;
82 }
83
84 // find the closest block, return or loop
85 var closestBlock = findClosestParentOfType(node, ["BlockStatement", "ReturnStatement", "ArrowFunctionExpression"]) || {},
86 lastItem, parentType;
87
88 // if our parent is a return we know we're ok
89 if (closestBlock.type === "ReturnStatement" ) {
90 return;
91 }
92
93 // arrow functions don't always have blocks and implicitly return
94 if (closestBlock.type === "ArrowFunctionExpression") {
95 return;
96 }
97
98 // block statements are part of functions and most if statements
99 if (closestBlock.type === "BlockStatement") {
100
101 // find the last item in the block
102 lastItem = closestBlock.body[closestBlock.body.length - 1];
103
104 // if the callback is the last thing in a block that might be ok
105 if (isCallbackExpression(node, lastItem)) {
106
107 parentType = closestBlock.parent.type;
108
109 // but only if the block is part of a function
110 if (parentType === "FunctionExpression" ||
111 parentType === "FunctionDeclaration" ||
112 parentType === "ArrowFunctionExpression"
113 ) {
114 return;
115 }
116
117 }
118
119 // ending a block with a return is also ok
120 if (lastItem.type === "ReturnStatement") {
121
122 // but only if the callback is immediately before
123 if (isCallbackExpression(node, closestBlock.body[closestBlock.body.length - 2])) {
124 return;
125 }
126 }
127
128 }
129
130 // as long as you're the child of a function at this point you should be asked to return
131 if (findClosestParentOfType(node, ["FunctionDeclaration", "FunctionExpression", "ArrowFunctionExpression"])) {
132 context.report(node, "Expected return with your callback function.");
133 }
134
135 }
136
137 };
138};
139
140module.exports.schema = [{
141 type: "array",
142 items: { type: "string" }
143}];