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