]>
Commit | Line | Data |
---|---|---|
eb39fafa DC |
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 | ||
12 | const astUtils = require("./utils/ast-utils"); | |
13 | ||
14 | //------------------------------------------------------------------------------ | |
15 | // Helpers | |
16 | //------------------------------------------------------------------------------ | |
17 | const 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 | */ | |
24 | function isReachable(segment) { | |
25 | return segment.reachable; | |
26 | } | |
27 | ||
eb39fafa DC |
28 | //------------------------------------------------------------------------------ |
29 | // Rule Definition | |
30 | //------------------------------------------------------------------------------ | |
31 | ||
34eeec05 | 32 | /** @type {import('../shared/types').Rule} */ |
eb39fafa DC |
33 | module.exports = { |
34 | meta: { | |
35 | type: "problem", | |
36 | ||
37 | docs: { | |
8f9d1d4d | 38 | description: "Enforce `return` statements in getters", |
eb39fafa | 39 | recommended: true, |
f2a92ac6 | 40 | url: "https://eslint.org/docs/latest/rules/getter-return" |
eb39fafa DC |
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 }; | |
f2a92ac6 | 67 | const sourceCode = context.sourceCode; |
eb39fafa DC |
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, | |
56c4a2cb | 92 | loc: astUtils.getFunctionHeadLoc(node, sourceCode), |
eb39fafa DC |
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 | ||
f2a92ac6 DC |
115 | // Object.defineProperty() or Reflect.defineProperty() |
116 | if (parent.parent.parent.type === "CallExpression") { | |
117 | const callNode = parent.parent.parent.callee; | |
118 | ||
119 | if (astUtils.isSpecificMemberAccess(callNode, "Object", "defineProperty") || | |
120 | astUtils.isSpecificMemberAccess(callNode, "Reflect", "defineProperty")) { | |
121 | return true; | |
122 | } | |
eb39fafa DC |
123 | } |
124 | ||
f2a92ac6 | 125 | // Object.defineProperties() or Object.create() |
eb39fafa DC |
126 | if (parent.parent.parent.type === "Property" && |
127 | parent.parent.parent.parent.type === "ObjectExpression" && | |
f2a92ac6 DC |
128 | parent.parent.parent.parent.parent.type === "CallExpression") { |
129 | const callNode = parent.parent.parent.parent.parent.callee; | |
130 | ||
131 | return astUtils.isSpecificMemberAccess(callNode, "Object", "defineProperties") || | |
132 | astUtils.isSpecificMemberAccess(callNode, "Object", "create"); | |
eb39fafa DC |
133 | } |
134 | } | |
135 | } | |
136 | return false; | |
137 | } | |
138 | return { | |
139 | ||
140 | // Stacks this function's information. | |
141 | onCodePathStart(codePath, node) { | |
142 | funcInfo = { | |
143 | upper: funcInfo, | |
144 | codePath, | |
145 | hasReturn: false, | |
146 | shouldCheck: isGetter(node), | |
147 | node | |
148 | }; | |
149 | }, | |
150 | ||
151 | // Pops this function's information. | |
152 | onCodePathEnd() { | |
153 | funcInfo = funcInfo.upper; | |
154 | }, | |
155 | ||
156 | // Checks the return statement is valid. | |
157 | ReturnStatement(node) { | |
158 | if (funcInfo.shouldCheck) { | |
159 | funcInfo.hasReturn = true; | |
160 | ||
161 | // if allowImplicit: false, should also check node.argument | |
162 | if (!options.allowImplicit && !node.argument) { | |
163 | context.report({ | |
164 | node, | |
165 | messageId: "expected", | |
166 | data: { | |
167 | name: astUtils.getFunctionNameWithKind(funcInfo.node) | |
168 | } | |
169 | }); | |
170 | } | |
171 | } | |
172 | }, | |
173 | ||
174 | // Reports a given function if the last path is reachable. | |
175 | "FunctionExpression:exit": checkLastSegment, | |
176 | "ArrowFunctionExpression:exit": checkLastSegment | |
177 | }; | |
178 | } | |
179 | }; |