| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296 | 
							- /**
 
-  * @fileoverview Rule to enforce return statements in callbacks of array's methods
 
-  * @author Toru Nagashima
 
-  */
 
- "use strict";
 
- //------------------------------------------------------------------------------
 
- // Requirements
 
- //------------------------------------------------------------------------------
 
- const astUtils = require("./utils/ast-utils");
 
- //------------------------------------------------------------------------------
 
- // Helpers
 
- //------------------------------------------------------------------------------
 
- const TARGET_NODE_TYPE = /^(?:Arrow)?FunctionExpression$/u;
 
- const TARGET_METHODS = /^(?:every|filter|find(?:Index)?|flatMap|forEach|map|reduce(?:Right)?|some|sort)$/u;
 
- /**
 
-  * Checks a given code path segment is reachable.
 
-  * @param {CodePathSegment} segment A segment to check.
 
-  * @returns {boolean} `true` if the segment is reachable.
 
-  */
 
- function isReachable(segment) {
 
-     return segment.reachable;
 
- }
 
- /**
 
-  * Checks a given node is a member access which has the specified name's
 
-  * property.
 
-  * @param {ASTNode} node A node to check.
 
-  * @returns {boolean} `true` if the node is a member access which has
 
-  *      the specified name's property. The node may be a `(Chain|Member)Expression` node.
 
-  */
 
- function isTargetMethod(node) {
 
-     return astUtils.isSpecificMemberAccess(node, null, TARGET_METHODS);
 
- }
 
- /**
 
-  * Returns a human-legible description of an array method
 
-  * @param {string} arrayMethodName A method name to fully qualify
 
-  * @returns {string} the method name prefixed with `Array.` if it is a class method,
 
-  *      or else `Array.prototype.` if it is an instance method.
 
-  */
 
- function fullMethodName(arrayMethodName) {
 
-     if (["from", "of", "isArray"].includes(arrayMethodName)) {
 
-         return "Array.".concat(arrayMethodName);
 
-     }
 
-     return "Array.prototype.".concat(arrayMethodName);
 
- }
 
- /**
 
-  * Checks whether or not a given node is a function expression which is the
 
-  * callback of an array method, returning the method name.
 
-  * @param {ASTNode} node A node to check. This is one of
 
-  *      FunctionExpression or ArrowFunctionExpression.
 
-  * @returns {string} The method name if the node is a callback method,
 
-  *      null otherwise.
 
-  */
 
- function getArrayMethodName(node) {
 
-     let currentNode = node;
 
-     while (currentNode) {
 
-         const parent = currentNode.parent;
 
-         switch (parent.type) {
 
-             /*
 
-              * Looks up the destination. e.g.,
 
-              * foo.every(nativeFoo || function foo() { ... });
 
-              */
 
-             case "LogicalExpression":
 
-             case "ConditionalExpression":
 
-             case "ChainExpression":
 
-                 currentNode = parent;
 
-                 break;
 
-             /*
 
-              * If the upper function is IIFE, checks the destination of the return value.
 
-              * e.g.
 
-              *   foo.every((function() {
 
-              *     // setup...
 
-              *     return function callback() { ... };
 
-              *   })());
 
-              */
 
-             case "ReturnStatement": {
 
-                 const func = astUtils.getUpperFunction(parent);
 
-                 if (func === null || !astUtils.isCallee(func)) {
 
-                     return null;
 
-                 }
 
-                 currentNode = func.parent;
 
-                 break;
 
-             }
 
-             /*
 
-              * e.g.
 
-              *   Array.from([], function() {});
 
-              *   list.every(function() {});
 
-              */
 
-             case "CallExpression":
 
-                 if (astUtils.isArrayFromMethod(parent.callee)) {
 
-                     if (
 
-                         parent.arguments.length >= 2 &&
 
-                         parent.arguments[1] === currentNode
 
-                     ) {
 
-                         return "from";
 
-                     }
 
-                 }
 
-                 if (isTargetMethod(parent.callee)) {
 
-                     if (
 
-                         parent.arguments.length >= 1 &&
 
-                         parent.arguments[0] === currentNode
 
-                     ) {
 
-                         return astUtils.getStaticPropertyName(parent.callee);
 
-                     }
 
-                 }
 
-                 return null;
 
-             // Otherwise this node is not target.
 
-             default:
 
-                 return null;
 
-         }
 
-     }
 
-     /* istanbul ignore next: unreachable */
 
-     return null;
 
- }
 
- //------------------------------------------------------------------------------
 
- // Rule Definition
 
- //------------------------------------------------------------------------------
 
- module.exports = {
 
-     meta: {
 
-         type: "problem",
 
-         docs: {
 
-             description: "enforce `return` statements in callbacks of array methods",
 
-             category: "Best Practices",
 
-             recommended: false,
 
-             url: "https://eslint.org/docs/rules/array-callback-return"
 
-         },
 
-         schema: [
 
-             {
 
-                 type: "object",
 
-                 properties: {
 
-                     allowImplicit: {
 
-                         type: "boolean",
 
-                         default: false
 
-                     },
 
-                     checkForEach: {
 
-                         type: "boolean",
 
-                         default: false
 
-                     }
 
-                 },
 
-                 additionalProperties: false
 
-             }
 
-         ],
 
-         messages: {
 
-             expectedAtEnd: "{{arrayMethodName}}() expects a value to be returned at the end of {{name}}.",
 
-             expectedInside: "{{arrayMethodName}}() expects a return value from {{name}}.",
 
-             expectedReturnValue: "{{arrayMethodName}}() expects a return value from {{name}}.",
 
-             expectedNoReturnValue: "{{arrayMethodName}}() expects no useless return value from {{name}}."
 
-         }
 
-     },
 
-     create(context) {
 
-         const options = context.options[0] || { allowImplicit: false, checkForEach: false };
 
-         const sourceCode = context.getSourceCode();
 
-         let funcInfo = {
 
-             arrayMethodName: null,
 
-             upper: null,
 
-             codePath: null,
 
-             hasReturn: false,
 
-             shouldCheck: false,
 
-             node: null
 
-         };
 
-         /**
 
-          * Checks whether or not the last code path segment is reachable.
 
-          * Then reports this function if the segment is reachable.
 
-          *
 
-          * If the last code path segment is reachable, there are paths which are not
 
-          * returned or thrown.
 
-          * @param {ASTNode} node A node to check.
 
-          * @returns {void}
 
-          */
 
-         function checkLastSegment(node) {
 
-             if (!funcInfo.shouldCheck) {
 
-                 return;
 
-             }
 
-             let messageId = null;
 
-             if (funcInfo.arrayMethodName === "forEach") {
 
-                 if (options.checkForEach && node.type === "ArrowFunctionExpression" && node.expression) {
 
-                     messageId = "expectedNoReturnValue";
 
-                 }
 
-             } else {
 
-                 if (node.body.type === "BlockStatement" && funcInfo.codePath.currentSegments.some(isReachable)) {
 
-                     messageId = funcInfo.hasReturn ? "expectedAtEnd" : "expectedInside";
 
-                 }
 
-             }
 
-             if (messageId) {
 
-                 const name = astUtils.getFunctionNameWithKind(node);
 
-                 context.report({
 
-                     node,
 
-                     loc: astUtils.getFunctionHeadLoc(node, sourceCode),
 
-                     messageId,
 
-                     data: { name, arrayMethodName: fullMethodName(funcInfo.arrayMethodName) }
 
-                 });
 
-             }
 
-         }
 
-         return {
 
-             // Stacks this function's information.
 
-             onCodePathStart(codePath, node) {
 
-                 let methodName = null;
 
-                 if (TARGET_NODE_TYPE.test(node.type)) {
 
-                     methodName = getArrayMethodName(node);
 
-                 }
 
-                 funcInfo = {
 
-                     arrayMethodName: methodName,
 
-                     upper: funcInfo,
 
-                     codePath,
 
-                     hasReturn: false,
 
-                     shouldCheck:
 
-                         methodName &&
 
-                         !node.async &&
 
-                         !node.generator,
 
-                     node
 
-                 };
 
-             },
 
-             // Pops this function's information.
 
-             onCodePathEnd() {
 
-                 funcInfo = funcInfo.upper;
 
-             },
 
-             // Checks the return statement is valid.
 
-             ReturnStatement(node) {
 
-                 if (!funcInfo.shouldCheck) {
 
-                     return;
 
-                 }
 
-                 funcInfo.hasReturn = true;
 
-                 let messageId = null;
 
-                 if (funcInfo.arrayMethodName === "forEach") {
 
-                     // if checkForEach: true, returning a value at any path inside a forEach is not allowed
 
-                     if (options.checkForEach && node.argument) {
 
-                         messageId = "expectedNoReturnValue";
 
-                     }
 
-                 } else {
 
-                     // if allowImplicit: false, should also check node.argument
 
-                     if (!options.allowImplicit && !node.argument) {
 
-                         messageId = "expectedReturnValue";
 
-                     }
 
-                 }
 
-                 if (messageId) {
 
-                     context.report({
 
-                         node,
 
-                         messageId,
 
-                         data: {
 
-                             name: astUtils.getFunctionNameWithKind(funcInfo.node),
 
-                             arrayMethodName: fullMethodName(funcInfo.arrayMethodName)
 
-                         }
 
-                     });
 
-                 }
 
-             },
 
-             // Reports a given function if the last path is reachable.
 
-             "FunctionExpression:exit": checkLastSegment,
 
-             "ArrowFunctionExpression:exit": checkLastSegment
 
-         };
 
-     }
 
- };
 
 
  |