UNPKG

5.94 kBJavaScriptView Raw
1/**
2 * @fileoverview Enforces that a return statement is present in property getters.
3 * @author Aladdin-ADD(hh_2013@foxmail.com)
4 */
5
6"use strict";
7
8//------------------------------------------------------------------------------
9// Requirements
10//------------------------------------------------------------------------------
11
12const astUtils = require("./utils/ast-utils");
13
14//------------------------------------------------------------------------------
15// Helpers
16//------------------------------------------------------------------------------
17const TARGET_NODE_TYPE = /^(?:Arrow)?FunctionExpression$/u;
18
19/**
20 * Checks a given code path segment is reachable.
21 * @param {CodePathSegment} segment A segment to check.
22 * @returns {boolean} `true` if the segment is reachable.
23 */
24function isReachable(segment) {
25 return segment.reachable;
26}
27
28//------------------------------------------------------------------------------
29// Rule Definition
30//------------------------------------------------------------------------------
31
32module.exports = {
33 meta: {
34 type: "problem",
35
36 docs: {
37 description: "enforce `return` statements in getters",
38 category: "Possible Errors",
39 recommended: true,
40 url: "https://eslint.org/docs/rules/getter-return"
41 },
42
43 fixable: null,
44
45 schema: [
46 {
47 type: "object",
48 properties: {
49 allowImplicit: {
50 type: "boolean",
51 default: false
52 }
53 },
54 additionalProperties: false
55 }
56 ],
57
58 messages: {
59 expected: "Expected to return a value in {{name}}.",
60 expectedAlways: "Expected {{name}} to always return a value."
61 }
62 },
63
64 create(context) {
65
66 const options = context.options[0] || { allowImplicit: false };
67 const sourceCode = context.getSourceCode();
68
69 let funcInfo = {
70 upper: null,
71 codePath: null,
72 hasReturn: false,
73 shouldCheck: false,
74 node: null
75 };
76
77 /**
78 * Checks whether or not the last code path segment is reachable.
79 * Then reports this function if the segment is reachable.
80 *
81 * If the last code path segment is reachable, there are paths which are not
82 * returned or thrown.
83 * @param {ASTNode} node A node to check.
84 * @returns {void}
85 */
86 function checkLastSegment(node) {
87 if (funcInfo.shouldCheck &&
88 funcInfo.codePath.currentSegments.some(isReachable)
89 ) {
90 context.report({
91 node,
92 loc: astUtils.getFunctionHeadLoc(node, sourceCode),
93 messageId: funcInfo.hasReturn ? "expectedAlways" : "expected",
94 data: {
95 name: astUtils.getFunctionNameWithKind(funcInfo.node)
96 }
97 });
98 }
99 }
100
101 /**
102 * Checks whether a node means a getter function.
103 * @param {ASTNode} node a node to check.
104 * @returns {boolean} if node means a getter, return true; else return false.
105 */
106 function isGetter(node) {
107 const parent = node.parent;
108
109 if (TARGET_NODE_TYPE.test(node.type) && node.body.type === "BlockStatement") {
110 if (parent.kind === "get") {
111 return true;
112 }
113 if (parent.type === "Property" && astUtils.getStaticPropertyName(parent) === "get" && parent.parent.type === "ObjectExpression") {
114
115 // Object.defineProperty()
116 if (parent.parent.parent.type === "CallExpression" &&
117 astUtils.getStaticPropertyName(parent.parent.parent.callee) === "defineProperty") {
118 return true;
119 }
120
121 // Object.defineProperties()
122 if (parent.parent.parent.type === "Property" &&
123 parent.parent.parent.parent.type === "ObjectExpression" &&
124 parent.parent.parent.parent.parent.type === "CallExpression" &&
125 astUtils.getStaticPropertyName(parent.parent.parent.parent.parent.callee) === "defineProperties") {
126 return true;
127 }
128 }
129 }
130 return false;
131 }
132 return {
133
134 // Stacks this function's information.
135 onCodePathStart(codePath, node) {
136 funcInfo = {
137 upper: funcInfo,
138 codePath,
139 hasReturn: false,
140 shouldCheck: isGetter(node),
141 node
142 };
143 },
144
145 // Pops this function's information.
146 onCodePathEnd() {
147 funcInfo = funcInfo.upper;
148 },
149
150 // Checks the return statement is valid.
151 ReturnStatement(node) {
152 if (funcInfo.shouldCheck) {
153 funcInfo.hasReturn = true;
154
155 // if allowImplicit: false, should also check node.argument
156 if (!options.allowImplicit && !node.argument) {
157 context.report({
158 node,
159 messageId: "expected",
160 data: {
161 name: astUtils.getFunctionNameWithKind(funcInfo.node)
162 }
163 });
164 }
165 }
166 },
167
168 // Reports a given function if the last path is reachable.
169 "FunctionExpression:exit": checkLastSegment,
170 "ArrowFunctionExpression:exit": checkLastSegment
171 };
172 }
173};