| /** |
| * @fileoverview Enforces that a return statement is present in property getters. |
| * @author Aladdin-ADD(hh_2013@foxmail.com) |
| */ |
| |
| "use strict"; |
| |
| //------------------------------------------------------------------------------ |
| // Requirements |
| //------------------------------------------------------------------------------ |
| |
| const astUtils = require("../ast-utils"); |
| |
| //------------------------------------------------------------------------------ |
| // Helpers |
| //------------------------------------------------------------------------------ |
| const TARGET_NODE_TYPE = /^(?:Arrow)?FunctionExpression$/; |
| |
| /** |
| * 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: { |
| docs: { |
| description: "enforce `return` statements in getters", |
| category: "Possible Errors", |
| recommended: false, |
| url: "https://eslint.org/docs/rules/getter-return" |
| }, |
| fixable: null, |
| schema: [ |
| { |
| type: "object", |
| properties: { |
| allowImplicit: { |
| type: "boolean" |
| } |
| }, |
| additionalProperties: false |
| } |
| ] |
| }, |
| |
| 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, |
| message: funcInfo.hasReturn |
| ? "Expected {{name}} to always return a value." |
| : "Expected to return a value in {{name}}.", |
| 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, |
| message: "Expected to return a value in {{name}}.", |
| data: { |
| name: astUtils.getFunctionNameWithKind(funcInfo.node) |
| } |
| }); |
| } |
| } |
| }, |
| |
| // Reports a given function if the last path is reachable. |
| "FunctionExpression:exit": checkLastSegment, |
| "ArrowFunctionExpression:exit": checkLastSegment |
| }; |
| } |
| }; |