]> git.proxmox.com Git - pve-eslint.git/blob - eslint/lib/rules/no-useless-return.js
87f05892a943e22a080879a0f2157388c8389505
[pve-eslint.git] / eslint / lib / rules / no-useless-return.js
1 /**
2 * @fileoverview Disallow redundant return statements
3 * @author Teddy Katz
4 */
5 "use strict";
6
7 //------------------------------------------------------------------------------
8 // Requirements
9 //------------------------------------------------------------------------------
10
11 const astUtils = require("./utils/ast-utils"),
12 FixTracker = require("./utils/fix-tracker");
13
14 //------------------------------------------------------------------------------
15 // Helpers
16 //------------------------------------------------------------------------------
17
18 /**
19 * Removes the given element from the array.
20 * @param {Array} array The source array to remove.
21 * @param {any} element The target item to remove.
22 * @returns {void}
23 */
24 function remove(array, element) {
25 const index = array.indexOf(element);
26
27 if (index !== -1) {
28 array.splice(index, 1);
29 }
30 }
31
32 /**
33 * Checks whether it can remove the given return statement or not.
34 * @param {ASTNode} node The return statement node to check.
35 * @returns {boolean} `true` if the node is removable.
36 */
37 function isRemovable(node) {
38 return astUtils.STATEMENT_LIST_PARENTS.has(node.parent.type);
39 }
40
41 /**
42 * Checks whether the given return statement is in a `finally` block or not.
43 * @param {ASTNode} node The return statement node to check.
44 * @returns {boolean} `true` if the node is in a `finally` block.
45 */
46 function isInFinally(node) {
47 for (
48 let currentNode = node;
49 currentNode && currentNode.parent && !astUtils.isFunction(currentNode);
50 currentNode = currentNode.parent
51 ) {
52 if (currentNode.parent.type === "TryStatement" && currentNode.parent.finalizer === currentNode) {
53 return true;
54 }
55 }
56
57 return false;
58 }
59
60 //------------------------------------------------------------------------------
61 // Rule Definition
62 //------------------------------------------------------------------------------
63
64 module.exports = {
65 meta: {
66 type: "suggestion",
67
68 docs: {
69 description: "disallow redundant return statements",
70 recommended: false,
71 url: "https://eslint.org/docs/rules/no-useless-return"
72 },
73
74 fixable: "code",
75 schema: [],
76
77 messages: {
78 unnecessaryReturn: "Unnecessary return statement."
79 }
80 },
81
82 create(context) {
83 const segmentInfoMap = new WeakMap();
84 const usedUnreachableSegments = new WeakSet();
85 const sourceCode = context.getSourceCode();
86 let scopeInfo = null;
87
88 /**
89 * Checks whether the given segment is terminated by a return statement or not.
90 * @param {CodePathSegment} segment The segment to check.
91 * @returns {boolean} `true` if the segment is terminated by a return statement, or if it's still a part of unreachable.
92 */
93 function isReturned(segment) {
94 const info = segmentInfoMap.get(segment);
95
96 return !info || info.returned;
97 }
98
99 /**
100 * Collects useless return statements from the given previous segments.
101 *
102 * A previous segment may be an unreachable segment.
103 * In that case, the information object of the unreachable segment is not
104 * initialized because `onCodePathSegmentStart` event is not notified for
105 * unreachable segments.
106 * This goes to the previous segments of the unreachable segment recursively
107 * if the unreachable segment was generated by a return statement. Otherwise,
108 * this ignores the unreachable segment.
109 *
110 * This behavior would simulate code paths for the case that the return
111 * statement does not exist.
112 * @param {ASTNode[]} uselessReturns The collected return statements.
113 * @param {CodePathSegment[]} prevSegments The previous segments to traverse.
114 * @param {WeakSet<CodePathSegment>} [providedTraversedSegments] A set of segments that have already been traversed in this call
115 * @returns {ASTNode[]} `uselessReturns`.
116 */
117 function getUselessReturns(uselessReturns, prevSegments, providedTraversedSegments) {
118 const traversedSegments = providedTraversedSegments || new WeakSet();
119
120 for (const segment of prevSegments) {
121 if (!segment.reachable) {
122 if (!traversedSegments.has(segment)) {
123 traversedSegments.add(segment);
124 getUselessReturns(
125 uselessReturns,
126 segment.allPrevSegments.filter(isReturned),
127 traversedSegments
128 );
129 }
130 continue;
131 }
132
133 uselessReturns.push(...segmentInfoMap.get(segment).uselessReturns);
134 }
135
136 return uselessReturns;
137 }
138
139 /**
140 * Removes the return statements on the given segment from the useless return
141 * statement list.
142 *
143 * This segment may be an unreachable segment.
144 * In that case, the information object of the unreachable segment is not
145 * initialized because `onCodePathSegmentStart` event is not notified for
146 * unreachable segments.
147 * This goes to the previous segments of the unreachable segment recursively
148 * if the unreachable segment was generated by a return statement. Otherwise,
149 * this ignores the unreachable segment.
150 *
151 * This behavior would simulate code paths for the case that the return
152 * statement does not exist.
153 * @param {CodePathSegment} segment The segment to get return statements.
154 * @returns {void}
155 */
156 function markReturnStatementsOnSegmentAsUsed(segment) {
157 if (!segment.reachable) {
158 usedUnreachableSegments.add(segment);
159 segment.allPrevSegments
160 .filter(isReturned)
161 .filter(prevSegment => !usedUnreachableSegments.has(prevSegment))
162 .forEach(markReturnStatementsOnSegmentAsUsed);
163 return;
164 }
165
166 const info = segmentInfoMap.get(segment);
167
168 for (const node of info.uselessReturns) {
169 remove(scopeInfo.uselessReturns, node);
170 }
171 info.uselessReturns = [];
172 }
173
174 /**
175 * Removes the return statements on the current segments from the useless
176 * return statement list.
177 *
178 * This function will be called at every statement except FunctionDeclaration,
179 * BlockStatement, and BreakStatement.
180 *
181 * - FunctionDeclarations are always executed whether it's returned or not.
182 * - BlockStatements do nothing.
183 * - BreakStatements go the next merely.
184 * @returns {void}
185 */
186 function markReturnStatementsOnCurrentSegmentsAsUsed() {
187 scopeInfo
188 .codePath
189 .currentSegments
190 .forEach(markReturnStatementsOnSegmentAsUsed);
191 }
192
193 //----------------------------------------------------------------------
194 // Public
195 //----------------------------------------------------------------------
196
197 return {
198
199 // Makes and pushs a new scope information.
200 onCodePathStart(codePath) {
201 scopeInfo = {
202 upper: scopeInfo,
203 uselessReturns: [],
204 codePath
205 };
206 },
207
208 // Reports useless return statements if exist.
209 onCodePathEnd() {
210 for (const node of scopeInfo.uselessReturns) {
211 context.report({
212 node,
213 loc: node.loc,
214 messageId: "unnecessaryReturn",
215 fix(fixer) {
216 if (isRemovable(node) && !sourceCode.getCommentsInside(node).length) {
217
218 /*
219 * Extend the replacement range to include the
220 * entire function to avoid conflicting with
221 * no-else-return.
222 * https://github.com/eslint/eslint/issues/8026
223 */
224 return new FixTracker(fixer, sourceCode)
225 .retainEnclosingFunction(node)
226 .remove(node);
227 }
228 return null;
229 }
230 });
231 }
232
233 scopeInfo = scopeInfo.upper;
234 },
235
236 /*
237 * Initializes segments.
238 * NOTE: This event is notified for only reachable segments.
239 */
240 onCodePathSegmentStart(segment) {
241 const info = {
242 uselessReturns: getUselessReturns([], segment.allPrevSegments),
243 returned: false
244 };
245
246 // Stores the info.
247 segmentInfoMap.set(segment, info);
248 },
249
250 // Adds ReturnStatement node to check whether it's useless or not.
251 ReturnStatement(node) {
252 if (node.argument) {
253 markReturnStatementsOnCurrentSegmentsAsUsed();
254 }
255 if (
256 node.argument ||
257 astUtils.isInLoop(node) ||
258 isInFinally(node) ||
259
260 // Ignore `return` statements in unreachable places (https://github.com/eslint/eslint/issues/11647).
261 !scopeInfo.codePath.currentSegments.some(s => s.reachable)
262 ) {
263 return;
264 }
265
266 for (const segment of scopeInfo.codePath.currentSegments) {
267 const info = segmentInfoMap.get(segment);
268
269 if (info) {
270 info.uselessReturns.push(node);
271 info.returned = true;
272 }
273 }
274 scopeInfo.uselessReturns.push(node);
275 },
276
277 /*
278 * Registers for all statement nodes except FunctionDeclaration, BlockStatement, BreakStatement.
279 * Removes return statements of the current segments from the useless return statement list.
280 */
281 ClassDeclaration: markReturnStatementsOnCurrentSegmentsAsUsed,
282 ContinueStatement: markReturnStatementsOnCurrentSegmentsAsUsed,
283 DebuggerStatement: markReturnStatementsOnCurrentSegmentsAsUsed,
284 DoWhileStatement: markReturnStatementsOnCurrentSegmentsAsUsed,
285 EmptyStatement: markReturnStatementsOnCurrentSegmentsAsUsed,
286 ExpressionStatement: markReturnStatementsOnCurrentSegmentsAsUsed,
287 ForInStatement: markReturnStatementsOnCurrentSegmentsAsUsed,
288 ForOfStatement: markReturnStatementsOnCurrentSegmentsAsUsed,
289 ForStatement: markReturnStatementsOnCurrentSegmentsAsUsed,
290 IfStatement: markReturnStatementsOnCurrentSegmentsAsUsed,
291 ImportDeclaration: markReturnStatementsOnCurrentSegmentsAsUsed,
292 LabeledStatement: markReturnStatementsOnCurrentSegmentsAsUsed,
293 SwitchStatement: markReturnStatementsOnCurrentSegmentsAsUsed,
294 ThrowStatement: markReturnStatementsOnCurrentSegmentsAsUsed,
295 TryStatement: markReturnStatementsOnCurrentSegmentsAsUsed,
296 VariableDeclaration: markReturnStatementsOnCurrentSegmentsAsUsed,
297 WhileStatement: markReturnStatementsOnCurrentSegmentsAsUsed,
298 WithStatement: markReturnStatementsOnCurrentSegmentsAsUsed,
299 ExportNamedDeclaration: markReturnStatementsOnCurrentSegmentsAsUsed,
300 ExportDefaultDeclaration: markReturnStatementsOnCurrentSegmentsAsUsed,
301 ExportAllDeclaration: markReturnStatementsOnCurrentSegmentsAsUsed
302 };
303 }
304 };