.gitignore added
[dotfiles/.git] / .config / coc / extensions / node_modules / coc-prettier / node_modules / 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             category: "Best Practices",
71             recommended: false,
72             url: "https://eslint.org/docs/rules/no-useless-return"
73         },
74
75         fixable: "code",
76         schema: [],
77
78         messages: {
79             unnecessaryReturn: "Unnecessary return statement."
80         }
81     },
82
83     create(context) {
84         const segmentInfoMap = new WeakMap();
85         const usedUnreachableSegments = new WeakSet();
86         const sourceCode = context.getSourceCode();
87         let scopeInfo = null;
88
89         /**
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.
93          */
94         function isReturned(segment) {
95             const info = segmentInfoMap.get(segment);
96
97             return !info || info.returned;
98         }
99
100         /**
101          * Collects useless return statements from the given previous segments.
102          *
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.
110          *
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`.
117          */
118         function getUselessReturns(uselessReturns, prevSegments, providedTraversedSegments) {
119             const traversedSegments = providedTraversedSegments || new WeakSet();
120
121             for (const segment of prevSegments) {
122                 if (!segment.reachable) {
123                     if (!traversedSegments.has(segment)) {
124                         traversedSegments.add(segment);
125                         getUselessReturns(
126                             uselessReturns,
127                             segment.allPrevSegments.filter(isReturned),
128                             traversedSegments
129                         );
130                     }
131                     continue;
132                 }
133
134                 uselessReturns.push(...segmentInfoMap.get(segment).uselessReturns);
135             }
136
137             return uselessReturns;
138         }
139
140         /**
141          * Removes the return statements on the given segment from the useless return
142          * statement list.
143          *
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.
151          *
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.
155          * @returns {void}
156          */
157         function markReturnStatementsOnSegmentAsUsed(segment) {
158             if (!segment.reachable) {
159                 usedUnreachableSegments.add(segment);
160                 segment.allPrevSegments
161                     .filter(isReturned)
162                     .filter(prevSegment => !usedUnreachableSegments.has(prevSegment))
163                     .forEach(markReturnStatementsOnSegmentAsUsed);
164                 return;
165             }
166
167             const info = segmentInfoMap.get(segment);
168
169             for (const node of info.uselessReturns) {
170                 remove(scopeInfo.uselessReturns, node);
171             }
172             info.uselessReturns = [];
173         }
174
175         /**
176          * Removes the return statements on the current segments from the useless
177          * return statement list.
178          *
179          * This function will be called at every statement except FunctionDeclaration,
180          * BlockStatement, and BreakStatement.
181          *
182          * - FunctionDeclarations are always executed whether it's returned or not.
183          * - BlockStatements do nothing.
184          * - BreakStatements go the next merely.
185          * @returns {void}
186          */
187         function markReturnStatementsOnCurrentSegmentsAsUsed() {
188             scopeInfo
189                 .codePath
190                 .currentSegments
191                 .forEach(markReturnStatementsOnSegmentAsUsed);
192         }
193
194         //----------------------------------------------------------------------
195         // Public
196         //----------------------------------------------------------------------
197
198         return {
199
200             // Makes and pushs a new scope information.
201             onCodePathStart(codePath) {
202                 scopeInfo = {
203                     upper: scopeInfo,
204                     uselessReturns: [],
205                     codePath
206                 };
207             },
208
209             // Reports useless return statements if exist.
210             onCodePathEnd() {
211                 for (const node of scopeInfo.uselessReturns) {
212                     context.report({
213                         node,
214                         loc: node.loc,
215                         messageId: "unnecessaryReturn",
216                         fix(fixer) {
217                             if (isRemovable(node) && !sourceCode.getCommentsInside(node).length) {
218
219                                 /*
220                                  * Extend the replacement range to include the
221                                  * entire function to avoid conflicting with
222                                  * no-else-return.
223                                  * https://github.com/eslint/eslint/issues/8026
224                                  */
225                                 return new FixTracker(fixer, sourceCode)
226                                     .retainEnclosingFunction(node)
227                                     .remove(node);
228                             }
229                             return null;
230                         }
231                     });
232                 }
233
234                 scopeInfo = scopeInfo.upper;
235             },
236
237             /*
238              * Initializes segments.
239              * NOTE: This event is notified for only reachable segments.
240              */
241             onCodePathSegmentStart(segment) {
242                 const info = {
243                     uselessReturns: getUselessReturns([], segment.allPrevSegments),
244                     returned: false
245                 };
246
247                 // Stores the info.
248                 segmentInfoMap.set(segment, info);
249             },
250
251             // Adds ReturnStatement node to check whether it's useless or not.
252             ReturnStatement(node) {
253                 if (node.argument) {
254                     markReturnStatementsOnCurrentSegmentsAsUsed();
255                 }
256                 if (
257                     node.argument ||
258                     astUtils.isInLoop(node) ||
259                     isInFinally(node) ||
260
261                     // Ignore `return` statements in unreachable places (https://github.com/eslint/eslint/issues/11647).
262                     !scopeInfo.codePath.currentSegments.some(s => s.reachable)
263                 ) {
264                     return;
265                 }
266
267                 for (const segment of scopeInfo.codePath.currentSegments) {
268                     const info = segmentInfoMap.get(segment);
269
270                     if (info) {
271                         info.uselessReturns.push(node);
272                         info.returned = true;
273                     }
274                 }
275                 scopeInfo.uselessReturns.push(node);
276             },
277
278             /*
279              * Registers for all statement nodes except FunctionDeclaration, BlockStatement, BreakStatement.
280              * Removes return statements of the current segments from the useless return statement list.
281              */
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
303         };
304     }
305 };