.gitignore added
[dotfiles/.git] / .config / coc / extensions / node_modules / coc-prettier / node_modules / eslint / lib / rules / array-callback-return.js
1 /**
2  * @fileoverview Rule to enforce return statements in callbacks of array's methods
3  * @author Toru Nagashima
4  */
5
6 "use strict";
7
8 //------------------------------------------------------------------------------
9 // Requirements
10 //------------------------------------------------------------------------------
11
12 const astUtils = require("./utils/ast-utils");
13
14 //------------------------------------------------------------------------------
15 // Helpers
16 //------------------------------------------------------------------------------
17
18 const TARGET_NODE_TYPE = /^(?:Arrow)?FunctionExpression$/u;
19 const TARGET_METHODS = /^(?:every|filter|find(?:Index)?|flatMap|forEach|map|reduce(?:Right)?|some|sort)$/u;
20
21 /**
22  * Checks a given code path segment is reachable.
23  * @param {CodePathSegment} segment A segment to check.
24  * @returns {boolean} `true` if the segment is reachable.
25  */
26 function isReachable(segment) {
27     return segment.reachable;
28 }
29
30 /**
31  * Checks a given node is a member access which has the specified name's
32  * property.
33  * @param {ASTNode} node A node to check.
34  * @returns {boolean} `true` if the node is a member access which has
35  *      the specified name's property. The node may be a `(Chain|Member)Expression` node.
36  */
37 function isTargetMethod(node) {
38     return astUtils.isSpecificMemberAccess(node, null, TARGET_METHODS);
39 }
40
41 /**
42  * Returns a human-legible description of an array method
43  * @param {string} arrayMethodName A method name to fully qualify
44  * @returns {string} the method name prefixed with `Array.` if it is a class method,
45  *      or else `Array.prototype.` if it is an instance method.
46  */
47 function fullMethodName(arrayMethodName) {
48     if (["from", "of", "isArray"].includes(arrayMethodName)) {
49         return "Array.".concat(arrayMethodName);
50     }
51     return "Array.prototype.".concat(arrayMethodName);
52 }
53
54 /**
55  * Checks whether or not a given node is a function expression which is the
56  * callback of an array method, returning the method name.
57  * @param {ASTNode} node A node to check. This is one of
58  *      FunctionExpression or ArrowFunctionExpression.
59  * @returns {string} The method name if the node is a callback method,
60  *      null otherwise.
61  */
62 function getArrayMethodName(node) {
63     let currentNode = node;
64
65     while (currentNode) {
66         const parent = currentNode.parent;
67
68         switch (parent.type) {
69
70             /*
71              * Looks up the destination. e.g.,
72              * foo.every(nativeFoo || function foo() { ... });
73              */
74             case "LogicalExpression":
75             case "ConditionalExpression":
76             case "ChainExpression":
77                 currentNode = parent;
78                 break;
79
80             /*
81              * If the upper function is IIFE, checks the destination of the return value.
82              * e.g.
83              *   foo.every((function() {
84              *     // setup...
85              *     return function callback() { ... };
86              *   })());
87              */
88             case "ReturnStatement": {
89                 const func = astUtils.getUpperFunction(parent);
90
91                 if (func === null || !astUtils.isCallee(func)) {
92                     return null;
93                 }
94                 currentNode = func.parent;
95                 break;
96             }
97
98             /*
99              * e.g.
100              *   Array.from([], function() {});
101              *   list.every(function() {});
102              */
103             case "CallExpression":
104                 if (astUtils.isArrayFromMethod(parent.callee)) {
105                     if (
106                         parent.arguments.length >= 2 &&
107                         parent.arguments[1] === currentNode
108                     ) {
109                         return "from";
110                     }
111                 }
112                 if (isTargetMethod(parent.callee)) {
113                     if (
114                         parent.arguments.length >= 1 &&
115                         parent.arguments[0] === currentNode
116                     ) {
117                         return astUtils.getStaticPropertyName(parent.callee);
118                     }
119                 }
120                 return null;
121
122             // Otherwise this node is not target.
123             default:
124                 return null;
125         }
126     }
127
128     /* istanbul ignore next: unreachable */
129     return null;
130 }
131
132 //------------------------------------------------------------------------------
133 // Rule Definition
134 //------------------------------------------------------------------------------
135
136 module.exports = {
137     meta: {
138         type: "problem",
139
140         docs: {
141             description: "enforce `return` statements in callbacks of array methods",
142             category: "Best Practices",
143             recommended: false,
144             url: "https://eslint.org/docs/rules/array-callback-return"
145         },
146
147         schema: [
148             {
149                 type: "object",
150                 properties: {
151                     allowImplicit: {
152                         type: "boolean",
153                         default: false
154                     },
155                     checkForEach: {
156                         type: "boolean",
157                         default: false
158                     }
159                 },
160                 additionalProperties: false
161             }
162         ],
163
164         messages: {
165             expectedAtEnd: "{{arrayMethodName}}() expects a value to be returned at the end of {{name}}.",
166             expectedInside: "{{arrayMethodName}}() expects a return value from {{name}}.",
167             expectedReturnValue: "{{arrayMethodName}}() expects a return value from {{name}}.",
168             expectedNoReturnValue: "{{arrayMethodName}}() expects no useless return value from {{name}}."
169         }
170     },
171
172     create(context) {
173
174         const options = context.options[0] || { allowImplicit: false, checkForEach: false };
175         const sourceCode = context.getSourceCode();
176
177         let funcInfo = {
178             arrayMethodName: null,
179             upper: null,
180             codePath: null,
181             hasReturn: false,
182             shouldCheck: false,
183             node: null
184         };
185
186         /**
187          * Checks whether or not the last code path segment is reachable.
188          * Then reports this function if the segment is reachable.
189          *
190          * If the last code path segment is reachable, there are paths which are not
191          * returned or thrown.
192          * @param {ASTNode} node A node to check.
193          * @returns {void}
194          */
195         function checkLastSegment(node) {
196
197             if (!funcInfo.shouldCheck) {
198                 return;
199             }
200
201             let messageId = null;
202
203             if (funcInfo.arrayMethodName === "forEach") {
204                 if (options.checkForEach && node.type === "ArrowFunctionExpression" && node.expression) {
205                     messageId = "expectedNoReturnValue";
206                 }
207             } else {
208                 if (node.body.type === "BlockStatement" && funcInfo.codePath.currentSegments.some(isReachable)) {
209                     messageId = funcInfo.hasReturn ? "expectedAtEnd" : "expectedInside";
210                 }
211             }
212
213             if (messageId) {
214                 const name = astUtils.getFunctionNameWithKind(node);
215
216                 context.report({
217                     node,
218                     loc: astUtils.getFunctionHeadLoc(node, sourceCode),
219                     messageId,
220                     data: { name, arrayMethodName: fullMethodName(funcInfo.arrayMethodName) }
221                 });
222             }
223         }
224
225         return {
226
227             // Stacks this function's information.
228             onCodePathStart(codePath, node) {
229
230                 let methodName = null;
231
232                 if (TARGET_NODE_TYPE.test(node.type)) {
233                     methodName = getArrayMethodName(node);
234                 }
235
236                 funcInfo = {
237                     arrayMethodName: methodName,
238                     upper: funcInfo,
239                     codePath,
240                     hasReturn: false,
241                     shouldCheck:
242                         methodName &&
243                         !node.async &&
244                         !node.generator,
245                     node
246                 };
247             },
248
249             // Pops this function's information.
250             onCodePathEnd() {
251                 funcInfo = funcInfo.upper;
252             },
253
254             // Checks the return statement is valid.
255             ReturnStatement(node) {
256
257                 if (!funcInfo.shouldCheck) {
258                     return;
259                 }
260
261                 funcInfo.hasReturn = true;
262
263                 let messageId = null;
264
265                 if (funcInfo.arrayMethodName === "forEach") {
266
267                     // if checkForEach: true, returning a value at any path inside a forEach is not allowed
268                     if (options.checkForEach && node.argument) {
269                         messageId = "expectedNoReturnValue";
270                     }
271                 } else {
272
273                     // if allowImplicit: false, should also check node.argument
274                     if (!options.allowImplicit && !node.argument) {
275                         messageId = "expectedReturnValue";
276                     }
277                 }
278
279                 if (messageId) {
280                     context.report({
281                         node,
282                         messageId,
283                         data: {
284                             name: astUtils.getFunctionNameWithKind(funcInfo.node),
285                             arrayMethodName: fullMethodName(funcInfo.arrayMethodName)
286                         }
287                     });
288                 }
289             },
290
291             // Reports a given function if the last path is reachable.
292             "FunctionExpression:exit": checkLastSegment,
293             "ArrowFunctionExpression:exit": checkLastSegment
294         };
295     }
296 };