2 * @fileoverview Disallow redundant return statements
7 //------------------------------------------------------------------------------
9 //------------------------------------------------------------------------------
11 const astUtils = require("./utils/ast-utils"),
12 FixTracker = require("./utils/fix-tracker");
14 //------------------------------------------------------------------------------
16 //------------------------------------------------------------------------------
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.
24 function remove(array, element) {
25 const index = array.indexOf(element);
28 array.splice(index, 1);
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.
37 function isRemovable(node) {
38 return astUtils.STATEMENT_LIST_PARENTS.has(node.parent.type);
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.
46 function isInFinally(node) {
48 let currentNode = node;
49 currentNode && currentNode.parent && !astUtils.isFunction(currentNode);
50 currentNode = currentNode.parent
52 if (currentNode.parent.type === "TryStatement" && currentNode.parent.finalizer === currentNode) {
60 //------------------------------------------------------------------------------
62 //------------------------------------------------------------------------------
69 description: "disallow redundant return statements",
70 category: "Best Practices",
72 url: "https://eslint.org/docs/rules/no-useless-return"
79 unnecessaryReturn: "Unnecessary return statement."
84 const segmentInfoMap = new WeakMap();
85 const usedUnreachableSegments = new WeakSet();
86 const sourceCode = context.getSourceCode();
90 * Checks whether the given segment is terminated by a return statement or not.
91 * @param {CodePathSegment} segment The segment to check.
92 * @returns {boolean} `true` if the segment is terminated by a return statement, or if it's still a part of unreachable.
94 function isReturned(segment) {
95 const info = segmentInfoMap.get(segment);
97 return !info || info.returned;
101 * Collects useless return statements from the given previous segments.
103 * A previous segment may be an unreachable segment.
104 * In that case, the information object of the unreachable segment is not
105 * initialized because `onCodePathSegmentStart` event is not notified for
106 * unreachable segments.
107 * This goes to the previous segments of the unreachable segment recursively
108 * if the unreachable segment was generated by a return statement. Otherwise,
109 * this ignores the unreachable segment.
111 * This behavior would simulate code paths for the case that the return
112 * statement does not exist.
113 * @param {ASTNode[]} uselessReturns The collected return statements.
114 * @param {CodePathSegment[]} prevSegments The previous segments to traverse.
115 * @param {WeakSet<CodePathSegment>} [providedTraversedSegments] A set of segments that have already been traversed in this call
116 * @returns {ASTNode[]} `uselessReturns`.
118 function getUselessReturns(uselessReturns, prevSegments, providedTraversedSegments) {
119 const traversedSegments = providedTraversedSegments || new WeakSet();
121 for (const segment of prevSegments) {
122 if (!segment.reachable) {
123 if (!traversedSegments.has(segment)) {
124 traversedSegments.add(segment);
127 segment.allPrevSegments.filter(isReturned),
134 uselessReturns.push(...segmentInfoMap.get(segment).uselessReturns);
137 return uselessReturns;
141 * Removes the return statements on the given segment from the useless return
144 * This segment may be an unreachable segment.
145 * In that case, the information object of the unreachable segment is not
146 * initialized because `onCodePathSegmentStart` event is not notified for
147 * unreachable segments.
148 * This goes to the previous segments of the unreachable segment recursively
149 * if the unreachable segment was generated by a return statement. Otherwise,
150 * this ignores the unreachable segment.
152 * This behavior would simulate code paths for the case that the return
153 * statement does not exist.
154 * @param {CodePathSegment} segment The segment to get return statements.
157 function markReturnStatementsOnSegmentAsUsed(segment) {
158 if (!segment.reachable) {
159 usedUnreachableSegments.add(segment);
160 segment.allPrevSegments
162 .filter(prevSegment => !usedUnreachableSegments.has(prevSegment))
163 .forEach(markReturnStatementsOnSegmentAsUsed);
167 const info = segmentInfoMap.get(segment);
169 for (const node of info.uselessReturns) {
170 remove(scopeInfo.uselessReturns, node);
172 info.uselessReturns = [];
176 * Removes the return statements on the current segments from the useless
177 * return statement list.
179 * This function will be called at every statement except FunctionDeclaration,
180 * BlockStatement, and BreakStatement.
182 * - FunctionDeclarations are always executed whether it's returned or not.
183 * - BlockStatements do nothing.
184 * - BreakStatements go the next merely.
187 function markReturnStatementsOnCurrentSegmentsAsUsed() {
191 .forEach(markReturnStatementsOnSegmentAsUsed);
194 //----------------------------------------------------------------------
196 //----------------------------------------------------------------------
200 // Makes and pushs a new scope information.
201 onCodePathStart(codePath) {
209 // Reports useless return statements if exist.
211 for (const node of scopeInfo.uselessReturns) {
215 messageId: "unnecessaryReturn",
217 if (isRemovable(node) && !sourceCode.getCommentsInside(node).length) {
220 * Extend the replacement range to include the
221 * entire function to avoid conflicting with
223 * https://github.com/eslint/eslint/issues/8026
225 return new FixTracker(fixer, sourceCode)
226 .retainEnclosingFunction(node)
234 scopeInfo = scopeInfo.upper;
238 * Initializes segments.
239 * NOTE: This event is notified for only reachable segments.
241 onCodePathSegmentStart(segment) {
243 uselessReturns: getUselessReturns([], segment.allPrevSegments),
248 segmentInfoMap.set(segment, info);
251 // Adds ReturnStatement node to check whether it's useless or not.
252 ReturnStatement(node) {
254 markReturnStatementsOnCurrentSegmentsAsUsed();
258 astUtils.isInLoop(node) ||
261 // Ignore `return` statements in unreachable places (https://github.com/eslint/eslint/issues/11647).
262 !scopeInfo.codePath.currentSegments.some(s => s.reachable)
267 for (const segment of scopeInfo.codePath.currentSegments) {
268 const info = segmentInfoMap.get(segment);
271 info.uselessReturns.push(node);
272 info.returned = true;
275 scopeInfo.uselessReturns.push(node);
279 * Registers for all statement nodes except FunctionDeclaration, BlockStatement, BreakStatement.
280 * Removes return statements of the current segments from the useless return statement list.
282 ClassDeclaration: markReturnStatementsOnCurrentSegmentsAsUsed,
283 ContinueStatement: markReturnStatementsOnCurrentSegmentsAsUsed,
284 DebuggerStatement: markReturnStatementsOnCurrentSegmentsAsUsed,
285 DoWhileStatement: markReturnStatementsOnCurrentSegmentsAsUsed,
286 EmptyStatement: markReturnStatementsOnCurrentSegmentsAsUsed,
287 ExpressionStatement: markReturnStatementsOnCurrentSegmentsAsUsed,
288 ForInStatement: markReturnStatementsOnCurrentSegmentsAsUsed,
289 ForOfStatement: markReturnStatementsOnCurrentSegmentsAsUsed,
290 ForStatement: markReturnStatementsOnCurrentSegmentsAsUsed,
291 IfStatement: markReturnStatementsOnCurrentSegmentsAsUsed,
292 ImportDeclaration: markReturnStatementsOnCurrentSegmentsAsUsed,
293 LabeledStatement: markReturnStatementsOnCurrentSegmentsAsUsed,
294 SwitchStatement: markReturnStatementsOnCurrentSegmentsAsUsed,
295 ThrowStatement: markReturnStatementsOnCurrentSegmentsAsUsed,
296 TryStatement: markReturnStatementsOnCurrentSegmentsAsUsed,
297 VariableDeclaration: markReturnStatementsOnCurrentSegmentsAsUsed,
298 WhileStatement: markReturnStatementsOnCurrentSegmentsAsUsed,
299 WithStatement: markReturnStatementsOnCurrentSegmentsAsUsed,
300 ExportNamedDeclaration: markReturnStatementsOnCurrentSegmentsAsUsed,
301 ExportDefaultDeclaration: markReturnStatementsOnCurrentSegmentsAsUsed,
302 ExportAllDeclaration: markReturnStatementsOnCurrentSegmentsAsUsed