.gitignore added
[dotfiles/.git] / .config / coc / extensions / node_modules / coc-prettier / node_modules / eslint / lib / rules / no-loop-func.js
1 /**
2  * @fileoverview Rule to flag creation of function inside a loop
3  * @author Ilya Volodin
4  */
5
6 "use strict";
7
8 //------------------------------------------------------------------------------
9 // Helpers
10 //------------------------------------------------------------------------------
11
12 /**
13  * Gets the containing loop node of a specified node.
14  *
15  * We don't need to check nested functions, so this ignores those.
16  * `Scope.through` contains references of nested functions.
17  * @param {ASTNode} node An AST node to get.
18  * @returns {ASTNode|null} The containing loop node of the specified node, or
19  *      `null`.
20  */
21 function getContainingLoopNode(node) {
22     for (let currentNode = node; currentNode.parent; currentNode = currentNode.parent) {
23         const parent = currentNode.parent;
24
25         switch (parent.type) {
26             case "WhileStatement":
27             case "DoWhileStatement":
28                 return parent;
29
30             case "ForStatement":
31
32                 // `init` is outside of the loop.
33                 if (parent.init !== currentNode) {
34                     return parent;
35                 }
36                 break;
37
38             case "ForInStatement":
39             case "ForOfStatement":
40
41                 // `right` is outside of the loop.
42                 if (parent.right !== currentNode) {
43                     return parent;
44                 }
45                 break;
46
47             case "ArrowFunctionExpression":
48             case "FunctionExpression":
49             case "FunctionDeclaration":
50
51                 // We don't need to check nested functions.
52                 return null;
53
54             default:
55                 break;
56         }
57     }
58
59     return null;
60 }
61
62 /**
63  * Gets the containing loop node of a given node.
64  * If the loop was nested, this returns the most outer loop.
65  * @param {ASTNode} node A node to get. This is a loop node.
66  * @param {ASTNode|null} excludedNode A node that the result node should not
67  *      include.
68  * @returns {ASTNode} The most outer loop node.
69  */
70 function getTopLoopNode(node, excludedNode) {
71     const border = excludedNode ? excludedNode.range[1] : 0;
72     let retv = node;
73     let containingLoopNode = node;
74
75     while (containingLoopNode && containingLoopNode.range[0] >= border) {
76         retv = containingLoopNode;
77         containingLoopNode = getContainingLoopNode(containingLoopNode);
78     }
79
80     return retv;
81 }
82
83 /**
84  * Checks whether a given reference which refers to an upper scope's variable is
85  * safe or not.
86  * @param {ASTNode} loopNode A containing loop node.
87  * @param {eslint-scope.Reference} reference A reference to check.
88  * @returns {boolean} `true` if the reference is safe or not.
89  */
90 function isSafe(loopNode, reference) {
91     const variable = reference.resolved;
92     const definition = variable && variable.defs[0];
93     const declaration = definition && definition.parent;
94     const kind = (declaration && declaration.type === "VariableDeclaration")
95         ? declaration.kind
96         : "";
97
98     // Variables which are declared by `const` is safe.
99     if (kind === "const") {
100         return true;
101     }
102
103     /*
104      * Variables which are declared by `let` in the loop is safe.
105      * It's a different instance from the next loop step's.
106      */
107     if (kind === "let" &&
108         declaration.range[0] > loopNode.range[0] &&
109         declaration.range[1] < loopNode.range[1]
110     ) {
111         return true;
112     }
113
114     /*
115      * WriteReferences which exist after this border are unsafe because those
116      * can modify the variable.
117      */
118     const border = getTopLoopNode(
119         loopNode,
120         (kind === "let") ? declaration : null
121     ).range[0];
122
123     /**
124      * Checks whether a given reference is safe or not.
125      * The reference is every reference of the upper scope's variable we are
126      * looking now.
127      *
128      * It's safeafe if the reference matches one of the following condition.
129      * - is readonly.
130      * - doesn't exist inside a local function and after the border.
131      * @param {eslint-scope.Reference} upperRef A reference to check.
132      * @returns {boolean} `true` if the reference is safe.
133      */
134     function isSafeReference(upperRef) {
135         const id = upperRef.identifier;
136
137         return (
138             !upperRef.isWrite() ||
139             variable.scope.variableScope === upperRef.from.variableScope &&
140             id.range[0] < border
141         );
142     }
143
144     return Boolean(variable) && variable.references.every(isSafeReference);
145 }
146
147 //------------------------------------------------------------------------------
148 // Rule Definition
149 //------------------------------------------------------------------------------
150
151 module.exports = {
152     meta: {
153         type: "suggestion",
154
155         docs: {
156             description: "disallow function declarations that contain unsafe references inside loop statements",
157             category: "Best Practices",
158             recommended: false,
159             url: "https://eslint.org/docs/rules/no-loop-func"
160         },
161
162         schema: [],
163
164         messages: {
165             unsafeRefs: "Function declared in a loop contains unsafe references to variable(s) {{ varNames }}."
166         }
167     },
168
169     create(context) {
170
171         /**
172          * Reports functions which match the following condition:
173          *
174          * - has a loop node in ancestors.
175          * - has any references which refers to an unsafe variable.
176          * @param {ASTNode} node The AST node to check.
177          * @returns {boolean} Whether or not the node is within a loop.
178          */
179         function checkForLoops(node) {
180             const loopNode = getContainingLoopNode(node);
181
182             if (!loopNode) {
183                 return;
184             }
185
186             const references = context.getScope().through;
187             const unsafeRefs = references.filter(r => !isSafe(loopNode, r)).map(r => r.identifier.name);
188
189             if (unsafeRefs.length > 0) {
190                 context.report({
191                     node,
192                     messageId: "unsafeRefs",
193                     data: { varNames: `'${unsafeRefs.join("', '")}'` }
194                 });
195             }
196         }
197
198         return {
199             ArrowFunctionExpression: checkForLoops,
200             FunctionExpression: checkForLoops,
201             FunctionDeclaration: checkForLoops
202         };
203     }
204 };