blob: 6549555646e355384569d7c93ff9ffe3fda93e8b [file] [log] [blame]
/**
* @fileoverview Enforces that a return statement is present in property getters.
* @author Aladdin-ADD(hh_2013@foxmail.com)
*/
"use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const astUtils = require("./utils/ast-utils");
//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------
const TARGET_NODE_TYPE = /^(?:Arrow)?FunctionExpression$/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;
}
/**
* Gets a readable location.
*
* - FunctionExpression -> the function name or `function` keyword.
*
* @param {ASTNode} node - A function node to get.
* @returns {ASTNode|Token} The node or the token of a location.
*/
function getId(node) {
return node.id || node;
}
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
module.exports = {
meta: {
type: "problem",
docs: {
description: "enforce `return` statements in getters",
category: "Possible Errors",
recommended: true,
url: "https://eslint.org/docs/rules/getter-return"
},
fixable: null,
schema: [
{
type: "object",
properties: {
allowImplicit: {
type: "boolean",
default: false
}
},
additionalProperties: false
}
],
messages: {
expected: "Expected to return a value in {{name}}.",
expectedAlways: "Expected {{name}} to always return a value."
}
},
create(context) {
const options = context.options[0] || { allowImplicit: false };
let funcInfo = {
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 &&
funcInfo.codePath.currentSegments.some(isReachable)
) {
context.report({
node,
loc: getId(node).loc.start,
messageId: funcInfo.hasReturn ? "expectedAlways" : "expected",
data: {
name: astUtils.getFunctionNameWithKind(funcInfo.node)
}
});
}
}
/**
* Checks whether a node means a getter function.
* @param {ASTNode} node - a node to check.
* @returns {boolean} if node means a getter, return true; else return false.
*/
function isGetter(node) {
const parent = node.parent;
if (TARGET_NODE_TYPE.test(node.type) && node.body.type === "BlockStatement") {
if (parent.kind === "get") {
return true;
}
if (parent.type === "Property" && astUtils.getStaticPropertyName(parent) === "get" && parent.parent.type === "ObjectExpression") {
// Object.defineProperty()
if (parent.parent.parent.type === "CallExpression" &&
astUtils.getStaticPropertyName(parent.parent.parent.callee) === "defineProperty") {
return true;
}
// Object.defineProperties()
if (parent.parent.parent.type === "Property" &&
parent.parent.parent.parent.type === "ObjectExpression" &&
parent.parent.parent.parent.parent.type === "CallExpression" &&
astUtils.getStaticPropertyName(parent.parent.parent.parent.parent.callee) === "defineProperties") {
return true;
}
}
}
return false;
}
return {
// Stacks this function's information.
onCodePathStart(codePath, node) {
funcInfo = {
upper: funcInfo,
codePath,
hasReturn: false,
shouldCheck: isGetter(node),
node
};
},
// Pops this function's information.
onCodePathEnd() {
funcInfo = funcInfo.upper;
},
// Checks the return statement is valid.
ReturnStatement(node) {
if (funcInfo.shouldCheck) {
funcInfo.hasReturn = true;
// if allowImplicit: false, should also check node.argument
if (!options.allowImplicit && !node.argument) {
context.report({
node,
messageId: "expected",
data: {
name: astUtils.getFunctionNameWithKind(funcInfo.node)
}
});
}
}
},
// Reports a given function if the last path is reachable.
"FunctionExpression:exit": checkLastSegment,
"ArrowFunctionExpression:exit": checkLastSegment
};
}
};