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