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",
71 url
: "https://eslint.org/docs/rules/no-useless-return"
78 unnecessaryReturn
: "Unnecessary return statement."
83 const segmentInfoMap
= new WeakMap();
84 const usedUnreachableSegments
= new WeakSet();
85 const sourceCode
= context
.getSourceCode();
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.
93 function isReturned(segment
) {
94 const info
= segmentInfoMap
.get(segment
);
96 return !info
|| info
.returned
;
100 * Collects useless return statements from the given previous segments.
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.
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`.
117 function getUselessReturns(uselessReturns
, prevSegments
, providedTraversedSegments
) {
118 const traversedSegments
= providedTraversedSegments
|| new WeakSet();
120 for (const segment
of prevSegments
) {
121 if (!segment
.reachable
) {
122 if (!traversedSegments
.has(segment
)) {
123 traversedSegments
.add(segment
);
126 segment
.allPrevSegments
.filter(isReturned
),
133 uselessReturns
.push(...segmentInfoMap
.get(segment
).uselessReturns
);
136 return uselessReturns
;
140 * Removes the return statements on the given segment from the useless return
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.
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.
156 function markReturnStatementsOnSegmentAsUsed(segment
) {
157 if (!segment
.reachable
) {
158 usedUnreachableSegments
.add(segment
);
159 segment
.allPrevSegments
161 .filter(prevSegment
=> !usedUnreachableSegments
.has(prevSegment
))
162 .forEach(markReturnStatementsOnSegmentAsUsed
);
166 const info
= segmentInfoMap
.get(segment
);
168 for (const node
of info
.uselessReturns
) {
169 remove(scopeInfo
.uselessReturns
, node
);
171 info
.uselessReturns
= [];
175 * Removes the return statements on the current segments from the useless
176 * return statement list.
178 * This function will be called at every statement except FunctionDeclaration,
179 * BlockStatement, and BreakStatement.
181 * - FunctionDeclarations are always executed whether it's returned or not.
182 * - BlockStatements do nothing.
183 * - BreakStatements go the next merely.
186 function markReturnStatementsOnCurrentSegmentsAsUsed() {
190 .forEach(markReturnStatementsOnSegmentAsUsed
);
193 //----------------------------------------------------------------------
195 //----------------------------------------------------------------------
199 // Makes and pushs a new scope information.
200 onCodePathStart(codePath
) {
208 // Reports useless return statements if exist.
210 for (const node
of scopeInfo
.uselessReturns
) {
214 messageId
: "unnecessaryReturn",
216 if (isRemovable(node
) && !sourceCode
.getCommentsInside(node
).length
) {
219 * Extend the replacement range to include the
220 * entire function to avoid conflicting with
222 * https://github.com/eslint/eslint/issues/8026
224 return new FixTracker(fixer
, sourceCode
)
225 .retainEnclosingFunction(node
)
233 scopeInfo
= scopeInfo
.upper
;
237 * Initializes segments.
238 * NOTE: This event is notified for only reachable segments.
240 onCodePathSegmentStart(segment
) {
242 uselessReturns
: getUselessReturns([], segment
.allPrevSegments
),
247 segmentInfoMap
.set(segment
, info
);
250 // Adds ReturnStatement node to check whether it's useless or not.
251 ReturnStatement(node
) {
253 markReturnStatementsOnCurrentSegmentsAsUsed();
257 astUtils
.isInLoop(node
) ||
260 // Ignore `return` statements in unreachable places (https://github.com/eslint/eslint/issues/11647).
261 !scopeInfo
.codePath
.currentSegments
.some(s
=> s
.reachable
)
266 for (const segment
of scopeInfo
.codePath
.currentSegments
) {
267 const info
= segmentInfoMap
.get(segment
);
270 info
.uselessReturns
.push(node
);
271 info
.returned
= true;
274 scopeInfo
.uselessReturns
.push(node
);
278 * Registers for all statement nodes except FunctionDeclaration, BlockStatement, BreakStatement.
279 * Removes return statements of the current segments from the useless return statement list.
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