.gitignore added
[dotfiles/.git] / .config / coc / extensions / node_modules / coc-prettier / node_modules / eslint / lib / rules / no-unreachable-loop.js
1 /**
2  * @fileoverview Rule to disallow loops with a body that allows only one iteration
3  * @author Milos Djermanovic
4  */
5
6 "use strict";
7
8 //------------------------------------------------------------------------------
9 // Helpers
10 //------------------------------------------------------------------------------
11
12 const allLoopTypes = ["WhileStatement", "DoWhileStatement", "ForStatement", "ForInStatement", "ForOfStatement"];
13
14 /**
15  * Determines whether the given node is the first node in the code path to which a loop statement
16  * 'loops' for the next iteration.
17  * @param {ASTNode} node The node to check.
18  * @returns {boolean} `true` if the node is a looping target.
19  */
20 function isLoopingTarget(node) {
21     const parent = node.parent;
22
23     if (parent) {
24         switch (parent.type) {
25             case "WhileStatement":
26                 return node === parent.test;
27             case "DoWhileStatement":
28                 return node === parent.body;
29             case "ForStatement":
30                 return node === (parent.update || parent.test || parent.body);
31             case "ForInStatement":
32             case "ForOfStatement":
33                 return node === parent.left;
34
35             // no default
36         }
37     }
38
39     return false;
40 }
41
42 /**
43  * Creates an array with elements from the first given array that are not included in the second given array.
44  * @param {Array} arrA The array to compare from.
45  * @param {Array} arrB The array to compare against.
46  * @returns {Array} a new array that represents `arrA \ arrB`.
47  */
48 function getDifference(arrA, arrB) {
49     return arrA.filter(a => !arrB.includes(a));
50 }
51
52 //------------------------------------------------------------------------------
53 // Rule Definition
54 //------------------------------------------------------------------------------
55
56 module.exports = {
57     meta: {
58         type: "problem",
59
60         docs: {
61             description: "disallow loops with a body that allows only one iteration",
62             category: "Possible Errors",
63             recommended: false,
64             url: "https://eslint.org/docs/rules/no-unreachable-loop"
65         },
66
67         schema: [{
68             type: "object",
69             properties: {
70                 ignore: {
71                     type: "array",
72                     items: {
73                         enum: allLoopTypes
74                     },
75                     uniqueItems: true
76                 }
77             },
78             additionalProperties: false
79         }],
80
81         messages: {
82             invalid: "Invalid loop. Its body allows only one iteration."
83         }
84     },
85
86     create(context) {
87         const ignoredLoopTypes = context.options[0] && context.options[0].ignore || [],
88             loopTypesToCheck = getDifference(allLoopTypes, ignoredLoopTypes),
89             loopSelector = loopTypesToCheck.join(","),
90             loopsByTargetSegments = new Map(),
91             loopsToReport = new Set();
92
93         let currentCodePath = null;
94
95         return {
96             onCodePathStart(codePath) {
97                 currentCodePath = codePath;
98             },
99
100             onCodePathEnd() {
101                 currentCodePath = currentCodePath.upper;
102             },
103
104             [loopSelector](node) {
105
106                 /**
107                  * Ignore unreachable loop statements to avoid unnecessary complexity in the implementation, or false positives otherwise.
108                  * For unreachable segments, the code path analysis does not raise events required for this implementation.
109                  */
110                 if (currentCodePath.currentSegments.some(segment => segment.reachable)) {
111                     loopsToReport.add(node);
112                 }
113             },
114
115             onCodePathSegmentStart(segment, node) {
116                 if (isLoopingTarget(node)) {
117                     const loop = node.parent;
118
119                     loopsByTargetSegments.set(segment, loop);
120                 }
121             },
122
123             onCodePathSegmentLoop(_, toSegment, node) {
124                 const loop = loopsByTargetSegments.get(toSegment);
125
126                 /**
127                  * The second iteration is reachable, meaning that the loop is valid by the logic of this rule,
128                  * only if there is at least one loop event with the appropriate target (which has been already
129                  * determined in the `loopsByTargetSegments` map), raised from either:
130                  *
131                  * - the end of the loop's body (in which case `node === loop`)
132                  * - a `continue` statement
133                  *
134                  * This condition skips loop events raised from `ForInStatement > .right` and `ForOfStatement > .right` nodes.
135                  */
136                 if (node === loop || node.type === "ContinueStatement") {
137
138                     // Removes loop if it exists in the set. Otherwise, `Set#delete` has no effect and doesn't throw.
139                     loopsToReport.delete(loop);
140                 }
141             },
142
143             "Program:exit"() {
144                 loopsToReport.forEach(
145                     node => context.report({ node, messageId: "invalid" })
146                 );
147             }
148         };
149     }
150 };