2 * @fileoverview Rule to enforce return statements in callbacks of array's methods
3 * @author Toru Nagashima
8 //------------------------------------------------------------------------------
10 //------------------------------------------------------------------------------
12 const lodash = require("lodash");
14 const astUtils = require("./utils/ast-utils");
16 //------------------------------------------------------------------------------
18 //------------------------------------------------------------------------------
20 const TARGET_NODE_TYPE = /^(?:Arrow)?FunctionExpression$/u;
21 const TARGET_METHODS = /^(?:every|filter|find(?:Index)?|map|reduce(?:Right)?|some|sort)$/u;
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.
28 function isReachable(segment) {
29 return segment.reachable;
33 * Gets a readable location.
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.
41 function getLocation(node, sourceCode) {
42 if (node.type === "ArrowFunctionExpression") {
43 return sourceCode.getTokenBefore(node.body);
45 return node.id || node;
49 * Checks a given node is a MemberExpression node which has the specified name's
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
55 function isTargetMethod(node) {
57 node.type === "MemberExpression" &&
58 TARGET_METHODS.test(astUtils.getStaticPropertyName(node) || "")
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.
69 function isCallbackOfArrayMethod(node) {
70 let currentNode = node;
73 const parent = currentNode.parent;
75 switch (parent.type) {
78 * Looks up the destination. e.g.,
79 * foo.every(nativeFoo || function foo() { ... });
81 case "LogicalExpression":
82 case "ConditionalExpression":
87 * If the upper function is IIFE, checks the destination of the return value.
89 * foo.every((function() {
91 * return function callback() { ... };
94 case "ReturnStatement": {
95 const func = astUtils.getUpperFunction(parent);
97 if (func === null || !astUtils.isCallee(func)) {
100 currentNode = func.parent;
106 * Array.from([], function() {});
107 * list.every(function() {});
109 case "CallExpression":
110 if (astUtils.isArrayFromMethod(parent.callee)) {
112 parent.arguments.length >= 2 &&
113 parent.arguments[1] === currentNode
116 if (isTargetMethod(parent.callee)) {
118 parent.arguments.length >= 1 &&
119 parent.arguments[0] === currentNode
124 // Otherwise this node is not target.
130 /* istanbul ignore next: unreachable */
134 //------------------------------------------------------------------------------
136 //------------------------------------------------------------------------------
143 description: "enforce `return` statements in callbacks of array methods",
144 category: "Best Practices",
146 url: "https://eslint.org/docs/rules/array-callback-return"
158 additionalProperties: false
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."
171 const options = context.options[0] || { allowImplicit: false };
182 * Checks whether or not the last code path segment is reachable.
183 * Then reports this function if the segment is reachable.
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.
190 function checkLastSegment(node) {
191 if (funcInfo.shouldCheck &&
192 funcInfo.codePath.currentSegments.some(isReachable)
196 loc: getLocation(node, context.getSourceCode()).loc.start,
197 messageId: funcInfo.hasReturn
201 name: astUtils.getFunctionNameWithKind(funcInfo.node)
209 // Stacks this function's information.
210 onCodePathStart(codePath, node) {
216 TARGET_NODE_TYPE.test(node.type) &&
217 node.body.type === "BlockStatement" &&
218 isCallbackOfArrayMethod(node) &&
225 // Pops this function's information.
227 funcInfo = funcInfo.upper;
230 // Checks the return statement is valid.
231 ReturnStatement(node) {
232 if (funcInfo.shouldCheck) {
233 funcInfo.hasReturn = true;
235 // if allowImplicit: false, should also check node.argument
236 if (!options.allowImplicit && !node.argument) {
239 messageId: "expectedReturnValue",
241 name: lodash.upperFirst(astUtils.getFunctionNameWithKind(funcInfo.node))
248 // Reports a given function if the last path is reachable.
249 "FunctionExpression:exit": checkLastSegment,
250 "ArrowFunctionExpression:exit": checkLastSegment