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