alecxe/eslint-plugin-protractor

View on GitHub
lib/rules/array-callback-return.js

Summary

Maintainability
B
4 hrs
Test Coverage
'use strict'
 
/**
* @fileoverview Rule to enforce return statements in callbacks of ElementArrayFinder's methods
* @author Alexander Afanasyev (based on Toru Nagashima's work)
*/
 
var isElementArrayFinder = require('../is-element-array-finder')
 
var ANY_FUNCTION_PATTERN = /^(?:Function(?:Declaration|Expression)|ArrowFunctionExpression)$/
var TARGET_NODE_TYPE = /^(?:Arrow)?FunctionExpression$/
var TARGET_METHODS = /^(?:filter|map|reduce)$/
 
/**
* 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
}
 
/**
* Gets a readable location.
*
* - FunctionExpression -> the function name or `function` keyword.
* - ArrowFunctionExpression -> `=>` token.
*
* @param {ASTNode} node - A function node to get.
* @param {SourceCode} sourceCode - A source code to get tokens.
* @returns {ASTNode|Token} The node or the token of a location.
*/
function getLocation (node, sourceCode) {
if (node.type === 'ArrowFunctionExpression') {
return sourceCode.getTokenBefore(node.body)
}
return node.id || node
}
 
/**
* Finds a function node from ancestors of a node.
* @param {ASTNode} node - A start node to find.
* @returns {Node|null} A found function node.
*/
function getUpperFunction (node) {
for (var currentNode = node; currentNode; currentNode = currentNode.parent) {
if (ANY_FUNCTION_PATTERN.test(currentNode.type)) {
return currentNode
}
}
return null
}
 
/**
* Checks whether or not a node is callee.
* @param {ASTNode} node - A node to check.
* @returns {boolean} Whether or not the node is callee.
*/
function isCallee (node) {
return node.parent.type === 'CallExpression' && node.parent.callee === node
}
 
/**
* Checks a given node is a MemberExpression node which has the specified name's
* property.
*
* @param {ASTNode} node - A node to check.
* @returns {boolean} `true` if the node is a MemberExpression node which has
* the specified name's property
*/
function isTargetMethod (node) {
return (node.object && isElementArrayFinder(node.object) &&
node.type === 'MemberExpression' &&
node.property &&
TARGET_METHODS.test(node.property.name)
)
}
 
/**
* Checks whether or not a given node is a function expression which is the
* callback of an array method.
*
* @param {ASTNode} node - A node to check. This is one of
* FunctionExpression or ArrowFunctionExpression.
* @returns {boolean} `true` if the node is the callback of an array method.
*/
Function `isCallbackOfArrayMethod` has a Cognitive Complexity of 11 (exceeds 5 allowed). Consider refactoring.
function isCallbackOfArrayMethod (node) {
while (node) {
var parent = node.parent
 
switch (parent.type) {
case 'LogicalExpression':
case 'ConditionalExpression':
node = parent
break
 
case 'ReturnStatement':
var func = getUpperFunction(parent)
 
if (func === null || !isCallee(func)) {
return false
}
node = func.parent
break
 
case 'CallExpression':
if (isTargetMethod(parent.callee)) {
return (parent.arguments.length >= 1 && parent.arguments[0] === node)
}
return false
 
// Otherwise this node is not target.
/* istanbul ignore next: unreachable */
default:
return false
}
}
 
/* istanbul ignore next: unreachable */
return false
}
 
module.exports = {
meta: {
schema: []
},
 
Function `create` has 47 lines of code (exceeds 25 allowed). Consider refactoring.
Function `create` has a Cognitive Complexity of 10 (exceeds 5 allowed). Consider refactoring.
create: function (context) {
var funcInfo = {
upper: null,
codePath: null,
hasReturn: false,
shouldCheck: false
}
 
/**
* 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 &&
funcInfo.codePath.currentSegments.some(isReachable)
) {
context.report({
node: node,
loc: getLocation(node, context.getSourceCode()).loc.start,
message: funcInfo.hasReturn
? 'Expected to return a value at the end of this function'
: 'Expected to return a value in this function'
})
}
}
 
return {
// Stacks this function's information.
'onCodePathStart': function (codePath, node) {
funcInfo = {
upper: funcInfo,
codePath: codePath,
hasReturn: false,
shouldCheck: TARGET_NODE_TYPE.test(node.type) &&
node.body.type === 'BlockStatement' &&
isCallbackOfArrayMethod(node)
}
},
 
// Pops this function's information.
'onCodePathEnd': function () {
funcInfo = funcInfo.upper
},
 
// Checks the return statement is valid.
'ReturnStatement': function (node) {
if (funcInfo.shouldCheck) {
funcInfo.hasReturn = true
 
if (!node.argument) {
context.report({
node: node,
message: 'Expected a return value'
})
}
}
},
 
// Reports a given function if the last path is reachable.
'FunctionExpression:exit': checkLastSegment,
'ArrowFunctionExpression:exit': checkLastSegment
}
}
}