205 lines
6.9 KiB
Plaintext
205 lines
6.9 KiB
Plaintext
|
/**
|
||
|
* @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 all segments in a set and returns true if any are reachable.
|
||
|
* @param {Set<CodePathSegment>} segments The segments to check.
|
||
|
* @returns {boolean} True if any segment is reachable; false otherwise.
|
||
|
*/
|
||
|
function isAnySegmentReachable(segments) {
|
||
|
|
||
|
for (const segment of segments) {
|
||
|
if (segment.reachable) {
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
//------------------------------------------------------------------------------
|
||
|
// Rule Definition
|
||
|
//------------------------------------------------------------------------------
|
||
|
|
||
|
/** @type {import('../shared/types').Rule} */
|
||
|
module.exports = {
|
||
|
meta: {
|
||
|
type: "problem",
|
||
|
|
||
|
docs: {
|
||
|
description: "Enforce `return` statements in getters",
|
||
|
recommended: true,
|
||
|
url: "https://eslint.org/docs/latest/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 };
|
||
|
const sourceCode = context.sourceCode;
|
||
|
|
||
|
let funcInfo = {
|
||
|
upper: null,
|
||
|
codePath: null,
|
||
|
hasReturn: false,
|
||
|
shouldCheck: false,
|
||
|
node: null,
|
||
|
currentSegments: []
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* 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 &&
|
||
|
isAnySegmentReachable(funcInfo.currentSegments)
|
||
|
) {
|
||
|
context.report({
|
||
|
node,
|
||
|
loc: astUtils.getFunctionHeadLoc(node, sourceCode),
|
||
|
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() or Reflect.defineProperty()
|
||
|
if (parent.parent.parent.type === "CallExpression") {
|
||
|
const callNode = parent.parent.parent.callee;
|
||
|
|
||
|
if (astUtils.isSpecificMemberAccess(callNode, "Object", "defineProperty") ||
|
||
|
astUtils.isSpecificMemberAccess(callNode, "Reflect", "defineProperty")) {
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Object.defineProperties() or Object.create()
|
||
|
if (parent.parent.parent.type === "Property" &&
|
||
|
parent.parent.parent.parent.type === "ObjectExpression" &&
|
||
|
parent.parent.parent.parent.parent.type === "CallExpression") {
|
||
|
const callNode = parent.parent.parent.parent.parent.callee;
|
||
|
|
||
|
return astUtils.isSpecificMemberAccess(callNode, "Object", "defineProperties") ||
|
||
|
astUtils.isSpecificMemberAccess(callNode, "Object", "create");
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
return {
|
||
|
|
||
|
// Stacks this function's information.
|
||
|
onCodePathStart(codePath, node) {
|
||
|
funcInfo = {
|
||
|
upper: funcInfo,
|
||
|
codePath,
|
||
|
hasReturn: false,
|
||
|
shouldCheck: isGetter(node),
|
||
|
node,
|
||
|
currentSegments: new Set()
|
||
|
};
|
||
|
},
|
||
|
|
||
|
// Pops this function's information.
|
||
|
onCodePathEnd() {
|
||
|
funcInfo = funcInfo.upper;
|
||
|
},
|
||
|
onUnreachableCodePathSegmentStart(segment) {
|
||
|
funcInfo.currentSegments.add(segment);
|
||
|
},
|
||
|
|
||
|
onUnreachableCodePathSegmentEnd(segment) {
|
||
|
funcInfo.currentSegments.delete(segment);
|
||
|
},
|
||
|
|
||
|
onCodePathSegmentStart(segment) {
|
||
|
funcInfo.currentSegments.add(segment);
|
||
|
},
|
||
|
|
||
|
onCodePathSegmentEnd(segment) {
|
||
|
funcInfo.currentSegments.delete(segment);
|
||
|
},
|
||
|
|
||
|
// 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
|
||
|
};
|
||
|
}
|
||
|
};
|