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 removeable.
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"
80 const segmentInfoMap = new WeakMap();
81 const usedUnreachableSegments = new WeakSet();
82 const sourceCode = context.getSourceCode();
86 * Checks whether the given segment is terminated by a return statement or not.
87 * @param {CodePathSegment} segment The segment to check.
88 * @returns {boolean} `true` if the segment is terminated by a return statement, or if it's still a part of unreachable.
90 function isReturned(segment) {
91 const info = segmentInfoMap.get(segment);
93 return !info || info.returned;
97 * Collects useless return statements from the given previous segments.
99 * A previous segment may be an unreachable segment.
100 * In that case, the information object of the unreachable segment is not
101 * initialized because `onCodePathSegmentStart` event is not notified for
102 * unreachable segments.
103 * This goes to the previous segments of the unreachable segment recursively
104 * if the unreachable segment was generated by a return statement. Otherwise,
105 * this ignores the unreachable segment.
107 * This behavior would simulate code paths for the case that the return
108 * statement does not exist.
109 * @param {ASTNode[]} uselessReturns The collected return statements.
110 * @param {CodePathSegment[]} prevSegments The previous segments to traverse.
111 * @param {WeakSet<CodePathSegment>} [providedTraversedSegments] A set of segments that have already been traversed in this call
112 * @returns {ASTNode[]} `uselessReturns`.
114 function getUselessReturns(uselessReturns, prevSegments, providedTraversedSegments) {
115 const traversedSegments = providedTraversedSegments || new WeakSet();
117 for (const segment of prevSegments) {
118 if (!segment.reachable) {
119 if (!traversedSegments.has(segment)) {
120 traversedSegments.add(segment);
123 segment.allPrevSegments.filter(isReturned),
130 uselessReturns.push(...segmentInfoMap.get(segment).uselessReturns);
133 return uselessReturns;
137 * Removes the return statements on the given segment from the useless return
140 * This segment may be an unreachable segment.
141 * In that case, the information object of the unreachable segment is not
142 * initialized because `onCodePathSegmentStart` event is not notified for
143 * unreachable segments.
144 * This goes to the previous segments of the unreachable segment recursively
145 * if the unreachable segment was generated by a return statement. Otherwise,
146 * this ignores the unreachable segment.
148 * This behavior would simulate code paths for the case that the return
149 * statement does not exist.
150 * @param {CodePathSegment} segment The segment to get return statements.
153 function markReturnStatementsOnSegmentAsUsed(segment) {
154 if (!segment.reachable) {
155 usedUnreachableSegments.add(segment);
156 segment.allPrevSegments
158 .filter(prevSegment => !usedUnreachableSegments.has(prevSegment))
159 .forEach(markReturnStatementsOnSegmentAsUsed);
163 const info = segmentInfoMap.get(segment);
165 for (const node of info.uselessReturns) {
166 remove(scopeInfo.uselessReturns, node);
168 info.uselessReturns = [];
172 * Removes the return statements on the current segments from the useless
173 * return statement list.
175 * This function will be called at every statement except FunctionDeclaration,
176 * BlockStatement, and BreakStatement.
178 * - FunctionDeclarations are always executed whether it's returned or not.
179 * - BlockStatements do nothing.
180 * - BreakStatements go the next merely.
183 function markReturnStatementsOnCurrentSegmentsAsUsed() {
187 .forEach(markReturnStatementsOnSegmentAsUsed);
190 //----------------------------------------------------------------------
192 //----------------------------------------------------------------------
196 // Makes and pushs a new scope information.
197 onCodePathStart(codePath) {
205 // Reports useless return statements if exist.
207 for (const node of scopeInfo.uselessReturns) {
211 message: "Unnecessary return statement.",
213 if (isRemovable(node) && !sourceCode.getCommentsInside(node).length) {
216 * Extend the replacement range to include the
217 * entire function to avoid conflicting with
219 * https://github.com/eslint/eslint/issues/8026
221 return new FixTracker(fixer, sourceCode)
222 .retainEnclosingFunction(node)
230 scopeInfo = scopeInfo.upper;
234 * Initializes segments.
235 * NOTE: This event is notified for only reachable segments.
237 onCodePathSegmentStart(segment) {
239 uselessReturns: getUselessReturns([], segment.allPrevSegments),
244 segmentInfoMap.set(segment, info);
247 // Adds ReturnStatement node to check whether it's useless or not.
248 ReturnStatement(node) {
250 markReturnStatementsOnCurrentSegmentsAsUsed();
254 astUtils.isInLoop(node) ||
257 // Ignore `return` statements in unreachable places (https://github.com/eslint/eslint/issues/11647).
258 !scopeInfo.codePath.currentSegments.some(s => s.reachable)
263 for (const segment of scopeInfo.codePath.currentSegments) {
264 const info = segmentInfoMap.get(segment);
267 info.uselessReturns.push(node);
268 info.returned = true;
271 scopeInfo.uselessReturns.push(node);
275 * Registers for all statement nodes except FunctionDeclaration, BlockStatement, BreakStatement.
276 * Removes return statements of the current segments from the useless return statement list.
278 ClassDeclaration: markReturnStatementsOnCurrentSegmentsAsUsed,
279 ContinueStatement: markReturnStatementsOnCurrentSegmentsAsUsed,
280 DebuggerStatement: markReturnStatementsOnCurrentSegmentsAsUsed,
281 DoWhileStatement: markReturnStatementsOnCurrentSegmentsAsUsed,
282 EmptyStatement: markReturnStatementsOnCurrentSegmentsAsUsed,
283 ExpressionStatement: markReturnStatementsOnCurrentSegmentsAsUsed,
284 ForInStatement: markReturnStatementsOnCurrentSegmentsAsUsed,
285 ForOfStatement: markReturnStatementsOnCurrentSegmentsAsUsed,
286 ForStatement: markReturnStatementsOnCurrentSegmentsAsUsed,
287 IfStatement: markReturnStatementsOnCurrentSegmentsAsUsed,
288 ImportDeclaration: markReturnStatementsOnCurrentSegmentsAsUsed,
289 LabeledStatement: markReturnStatementsOnCurrentSegmentsAsUsed,
290 SwitchStatement: markReturnStatementsOnCurrentSegmentsAsUsed,
291 ThrowStatement: markReturnStatementsOnCurrentSegmentsAsUsed,
292 TryStatement: markReturnStatementsOnCurrentSegmentsAsUsed,
293 VariableDeclaration: markReturnStatementsOnCurrentSegmentsAsUsed,
294 WhileStatement: markReturnStatementsOnCurrentSegmentsAsUsed,
295 WithStatement: markReturnStatementsOnCurrentSegmentsAsUsed,
296 ExportNamedDeclaration: markReturnStatementsOnCurrentSegmentsAsUsed,
297 ExportDefaultDeclaration: markReturnStatementsOnCurrentSegmentsAsUsed,
298 ExportAllDeclaration: markReturnStatementsOnCurrentSegmentsAsUsed