Actualizacion maquina principal
[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 lodash = require("lodash");
13
14 const astUtils = require("./utils/ast-utils");
15
16 //------------------------------------------------------------------------------
17 // Helpers
18 //------------------------------------------------------------------------------
19
20 const TARGET_NODE_TYPE = /^(?:Arrow)?FunctionExpression$/u;
21 const TARGET_METHODS = /^(?:every|filter|find(?:Index)?|map|reduce(?:Right)?|some|sort)$/u;
22
23 /**
24  * Checks a given code path segment is reachable.
25  * @param {CodePathSegment} segment A segment to check.
26  * @returns {boolean} `true` if the segment is reachable.
27  */
28 function isReachable(segment) {
29     return segment.reachable;
30 }
31
32 /**
33  * Gets a readable location.
34  *
35  * - FunctionExpression -> the function name or `function` keyword.
36  * - ArrowFunctionExpression -> `=>` token.
37  * @param {ASTNode} node A function node to get.
38  * @param {SourceCode} sourceCode A source code to get tokens.
39  * @returns {ASTNode|Token} The node or the token of a location.
40  */
41 function getLocation(node, sourceCode) {
42     if (node.type === "ArrowFunctionExpression") {
43         return sourceCode.getTokenBefore(node.body);
44     }
45     return node.id || node;
46 }
47
48 /**
49  * Checks a given node is a MemberExpression node which has the specified name's
50  * property.
51  * @param {ASTNode} node A node to check.
52  * @returns {boolean} `true` if the node is a MemberExpression node which has
53  *      the specified name's property
54  */
55 function isTargetMethod(node) {
56     return (
57         node.type === "MemberExpression" &&
58         TARGET_METHODS.test(astUtils.getStaticPropertyName(node) || "")
59     );
60 }
61
62 /**
63  * Checks whether or not a given node is a function expression which is the
64  * callback of an array method.
65  * @param {ASTNode} node A node to check. This is one of
66  *      FunctionExpression or ArrowFunctionExpression.
67  * @returns {boolean} `true` if the node is the callback of an array method.
68  */
69 function isCallbackOfArrayMethod(node) {
70     let currentNode = node;
71
72     while (currentNode) {
73         const parent = currentNode.parent;
74
75         switch (parent.type) {
76
77             /*
78              * Looks up the destination. e.g.,
79              * foo.every(nativeFoo || function foo() { ... });
80              */
81             case "LogicalExpression":
82             case "ConditionalExpression":
83                 currentNode = parent;
84                 break;
85
86             /*
87              * If the upper function is IIFE, checks the destination of the return value.
88              * e.g.
89              *   foo.every((function() {
90              *     // setup...
91              *     return function callback() { ... };
92              *   })());
93              */
94             case "ReturnStatement": {
95                 const func = astUtils.getUpperFunction(parent);
96
97                 if (func === null || !astUtils.isCallee(func)) {
98                     return false;
99                 }
100                 currentNode = func.parent;
101                 break;
102             }
103
104             /*
105              * e.g.
106              *   Array.from([], function() {});
107              *   list.every(function() {});
108              */
109             case "CallExpression":
110                 if (astUtils.isArrayFromMethod(parent.callee)) {
111                     return (
112                         parent.arguments.length >= 2 &&
113                         parent.arguments[1] === currentNode
114                     );
115                 }
116                 if (isTargetMethod(parent.callee)) {
117                     return (
118                         parent.arguments.length >= 1 &&
119                         parent.arguments[0] === currentNode
120                     );
121                 }
122                 return false;
123
124             // Otherwise this node is not target.
125             default:
126                 return false;
127         }
128     }
129
130     /* istanbul ignore next: unreachable */
131     return false;
132 }
133
134 //------------------------------------------------------------------------------
135 // Rule Definition
136 //------------------------------------------------------------------------------
137
138 module.exports = {
139     meta: {
140         type: "problem",
141
142         docs: {
143             description: "enforce `return` statements in callbacks of array methods",
144             category: "Best Practices",
145             recommended: false,
146             url: "https://eslint.org/docs/rules/array-callback-return"
147         },
148
149         schema: [
150             {
151                 type: "object",
152                 properties: {
153                     allowImplicit: {
154                         type: "boolean",
155                         default: false
156                     }
157                 },
158                 additionalProperties: false
159             }
160         ],
161
162         messages: {
163             expectedAtEnd: "Expected to return a value at the end of {{name}}.",
164             expectedInside: "Expected to return a value in {{name}}.",
165             expectedReturnValue: "{{name}} expected a return value."
166         }
167     },
168
169     create(context) {
170
171         const options = context.options[0] || { allowImplicit: false };
172
173         let funcInfo = {
174             upper: null,
175             codePath: null,
176             hasReturn: false,
177             shouldCheck: false,
178             node: null
179         };
180
181         /**
182          * Checks whether or not the last code path segment is reachable.
183          * Then reports this function if the segment is reachable.
184          *
185          * If the last code path segment is reachable, there are paths which are not
186          * returned or thrown.
187          * @param {ASTNode} node A node to check.
188          * @returns {void}
189          */
190         function checkLastSegment(node) {
191             if (funcInfo.shouldCheck &&
192                 funcInfo.codePath.currentSegments.some(isReachable)
193             ) {
194                 context.report({
195                     node,
196                     loc: getLocation(node, context.getSourceCode()).loc.start,
197                     messageId: funcInfo.hasReturn
198                         ? "expectedAtEnd"
199                         : "expectedInside",
200                     data: {
201                         name: astUtils.getFunctionNameWithKind(funcInfo.node)
202                     }
203                 });
204             }
205         }
206
207         return {
208
209             // Stacks this function's information.
210             onCodePathStart(codePath, node) {
211                 funcInfo = {
212                     upper: funcInfo,
213                     codePath,
214                     hasReturn: false,
215                     shouldCheck:
216                         TARGET_NODE_TYPE.test(node.type) &&
217                         node.body.type === "BlockStatement" &&
218                         isCallbackOfArrayMethod(node) &&
219                         !node.async &&
220                         !node.generator,
221                     node
222                 };
223             },
224
225             // Pops this function's information.
226             onCodePathEnd() {
227                 funcInfo = funcInfo.upper;
228             },
229
230             // Checks the return statement is valid.
231             ReturnStatement(node) {
232                 if (funcInfo.shouldCheck) {
233                     funcInfo.hasReturn = true;
234
235                     // if allowImplicit: false, should also check node.argument
236                     if (!options.allowImplicit && !node.argument) {
237                         context.report({
238                             node,
239                             messageId: "expectedReturnValue",
240                             data: {
241                                 name: lodash.upperFirst(astUtils.getFunctionNameWithKind(funcInfo.node))
242                             }
243                         });
244                     }
245                 }
246             },
247
248             // Reports a given function if the last path is reachable.
249             "FunctionExpression:exit": checkLastSegment,
250             "ArrowFunctionExpression:exit": checkLastSegment
251         };
252     }
253 };