]>
Commit | Line | Data |
---|---|---|
eb39fafa DC |
1 | /** |
2 | * @fileoverview Enforce return after a callback. | |
3 | * @author Jamund Ferguson | |
609c276f | 4 | * @deprecated in ESLint v7.0.0 |
eb39fafa DC |
5 | */ |
6 | "use strict"; | |
7 | ||
8 | //------------------------------------------------------------------------------ | |
9 | // Rule Definition | |
10 | //------------------------------------------------------------------------------ | |
11 | ||
12 | module.exports = { | |
13 | meta: { | |
56c4a2cb DC |
14 | deprecated: true, |
15 | ||
ebb53d86 | 16 | replacedBy: [], |
56c4a2cb | 17 | |
eb39fafa DC |
18 | type: "suggestion", |
19 | ||
20 | docs: { | |
21 | description: "require `return` statements after callbacks", | |
eb39fafa DC |
22 | recommended: false, |
23 | url: "https://eslint.org/docs/rules/callback-return" | |
24 | }, | |
25 | ||
26 | schema: [{ | |
27 | type: "array", | |
28 | items: { type: "string" } | |
29 | }], | |
30 | ||
31 | messages: { | |
32 | missingReturn: "Expected return with your callback function." | |
33 | } | |
34 | }, | |
35 | ||
36 | create(context) { | |
37 | ||
38 | const callbacks = context.options[0] || ["callback", "cb", "next"], | |
39 | sourceCode = context.getSourceCode(); | |
40 | ||
41 | //-------------------------------------------------------------------------- | |
42 | // Helpers | |
43 | //-------------------------------------------------------------------------- | |
44 | ||
45 | /** | |
46 | * Find the closest parent matching a list of types. | |
47 | * @param {ASTNode} node The node whose parents we are searching | |
48 | * @param {Array} types The node types to match | |
49 | * @returns {ASTNode} The matched node or undefined. | |
50 | */ | |
51 | function findClosestParentOfType(node, types) { | |
52 | if (!node.parent) { | |
53 | return null; | |
54 | } | |
55 | if (types.indexOf(node.parent.type) === -1) { | |
56 | return findClosestParentOfType(node.parent, types); | |
57 | } | |
58 | return node.parent; | |
59 | } | |
60 | ||
61 | /** | |
456be15e | 62 | * Check to see if a node contains only identifiers |
eb39fafa | 63 | * @param {ASTNode} node The node to check |
456be15e | 64 | * @returns {boolean} Whether or not the node contains only identifiers |
eb39fafa DC |
65 | */ |
66 | function containsOnlyIdentifiers(node) { | |
67 | if (node.type === "Identifier") { | |
68 | return true; | |
69 | } | |
70 | ||
71 | if (node.type === "MemberExpression") { | |
72 | if (node.object.type === "Identifier") { | |
73 | return true; | |
74 | } | |
75 | if (node.object.type === "MemberExpression") { | |
76 | return containsOnlyIdentifiers(node.object); | |
77 | } | |
78 | } | |
79 | ||
80 | return false; | |
81 | } | |
82 | ||
83 | /** | |
84 | * Check to see if a CallExpression is in our callback list. | |
85 | * @param {ASTNode} node The node to check against our callback names list. | |
86 | * @returns {boolean} Whether or not this function matches our callback name. | |
87 | */ | |
88 | function isCallback(node) { | |
89 | return containsOnlyIdentifiers(node.callee) && callbacks.indexOf(sourceCode.getText(node.callee)) > -1; | |
90 | } | |
91 | ||
92 | /** | |
93 | * Determines whether or not the callback is part of a callback expression. | |
94 | * @param {ASTNode} node The callback node | |
95 | * @param {ASTNode} parentNode The expression node | |
96 | * @returns {boolean} Whether or not this is part of a callback expression | |
97 | */ | |
98 | function isCallbackExpression(node, parentNode) { | |
99 | ||
100 | // ensure the parent node exists and is an expression | |
101 | if (!parentNode || parentNode.type !== "ExpressionStatement") { | |
102 | return false; | |
103 | } | |
104 | ||
105 | // cb() | |
106 | if (parentNode.expression === node) { | |
107 | return true; | |
108 | } | |
109 | ||
110 | // special case for cb && cb() and similar | |
111 | if (parentNode.expression.type === "BinaryExpression" || parentNode.expression.type === "LogicalExpression") { | |
112 | if (parentNode.expression.right === node) { | |
113 | return true; | |
114 | } | |
115 | } | |
116 | ||
117 | return false; | |
118 | } | |
119 | ||
120 | //-------------------------------------------------------------------------- | |
121 | // Public | |
122 | //-------------------------------------------------------------------------- | |
123 | ||
124 | return { | |
125 | CallExpression(node) { | |
126 | ||
127 | // if we're not a callback we can return | |
128 | if (!isCallback(node)) { | |
129 | return; | |
130 | } | |
131 | ||
132 | // find the closest block, return or loop | |
133 | const closestBlock = findClosestParentOfType(node, ["BlockStatement", "ReturnStatement", "ArrowFunctionExpression"]) || {}; | |
134 | ||
135 | // if our parent is a return we know we're ok | |
136 | if (closestBlock.type === "ReturnStatement") { | |
137 | return; | |
138 | } | |
139 | ||
140 | // arrow functions don't always have blocks and implicitly return | |
141 | if (closestBlock.type === "ArrowFunctionExpression") { | |
142 | return; | |
143 | } | |
144 | ||
145 | // block statements are part of functions and most if statements | |
146 | if (closestBlock.type === "BlockStatement") { | |
147 | ||
148 | // find the last item in the block | |
149 | const lastItem = closestBlock.body[closestBlock.body.length - 1]; | |
150 | ||
151 | // if the callback is the last thing in a block that might be ok | |
152 | if (isCallbackExpression(node, lastItem)) { | |
153 | ||
154 | const parentType = closestBlock.parent.type; | |
155 | ||
156 | // but only if the block is part of a function | |
157 | if (parentType === "FunctionExpression" || | |
158 | parentType === "FunctionDeclaration" || | |
159 | parentType === "ArrowFunctionExpression" | |
160 | ) { | |
161 | return; | |
162 | } | |
163 | ||
164 | } | |
165 | ||
166 | // ending a block with a return is also ok | |
167 | if (lastItem.type === "ReturnStatement") { | |
168 | ||
169 | // but only if the callback is immediately before | |
170 | if (isCallbackExpression(node, closestBlock.body[closestBlock.body.length - 2])) { | |
171 | return; | |
172 | } | |
173 | } | |
174 | ||
175 | } | |
176 | ||
177 | // as long as you're the child of a function at this point you should be asked to return | |
178 | if (findClosestParentOfType(node, ["FunctionDeclaration", "FunctionExpression", "ArrowFunctionExpression"])) { | |
179 | context.report({ node, messageId: "missingReturn" }); | |
180 | } | |
181 | ||
182 | } | |
183 | ||
184 | }; | |
185 | } | |
186 | }; |