UNPKG

3.69 kBJavaScriptView Raw
1/**
2 * @fileoverview Rule to flag creation of function inside a loop
3 * @author Ilya Volodin
4 * @copyright 2013 Ilya Volodin. All rights reserved.
5 */
6
7"use strict";
8
9//------------------------------------------------------------------------------
10// Helpers
11//------------------------------------------------------------------------------
12
13/**
14 * Gets the containing loop node of a specified node.
15 *
16 * We don't need to check nested functions, so this ignores those.
17 * `Scope.through` contains references of nested functions.
18 *
19 * @param {ASTNode} node - An AST node to get.
20 * @returns {ASTNode|null} The containing loop node of the specified node, or `null`.
21 */
22function getContainingLoopNode(node) {
23 var parent = node.parent;
24 while (parent) {
25 switch (parent.type) {
26 case "WhileStatement":
27 case "DoWhileStatement":
28 return parent;
29
30 case "ForStatement":
31 // `init` is outside of the loop.
32 if (parent.init !== node) {
33 return parent;
34 }
35 break;
36
37 case "ForInStatement":
38 case "ForOfStatement":
39 // `right` is outside of the loop.
40 if (parent.right !== node) {
41 return parent;
42 }
43 break;
44
45 case "ArrowFunctionExpression":
46 case "FunctionExpression":
47 case "FunctionDeclaration":
48 // We don't need to check nested functions.
49 return null;
50
51 default:
52 break;
53 }
54
55 node = parent;
56 parent = node.parent;
57 }
58
59 return null;
60}
61
62/**
63 * Checks whether or not a reference refers to a variable that is block-binding in the loop.
64 * @param {ASTNode} loopNode - A containing loop node.
65 * @param {escope.Reference} reference - A reference to check.
66 * @returns {boolean} Whether or not a reference refers to a variable that is block-binding in the loop.
67 */
68function isBlockBindingsInLoop(loopNode, reference) {
69 var variable = reference.resolved;
70 var definition = variable && variable.defs[0];
71 var declaration = definition && definition.parent;
72
73 return (
74 // Checks whether this is `let`/`const`.
75 declaration &&
76 declaration.type === "VariableDeclaration" &&
77 (declaration.kind === "let" || declaration.kind === "const") &&
78 // Checks whether this is in the loop.
79 declaration.range[0] > loopNode.range[0] &&
80 declaration.range[1] < loopNode.range[1]
81 );
82}
83
84//------------------------------------------------------------------------------
85// Rule Definition
86//------------------------------------------------------------------------------
87
88module.exports = function(context) {
89 /**
90 * Reports such functions:
91 *
92 * - has an ancestor node which is a loop.
93 * - has a reference that refers to a variable that is block-binding in the loop.
94 *
95 * @param {ASTNode} node The AST node to check.
96 * @returns {boolean} Whether or not the node is within a loop.
97 */
98 function checkForLoops(node) {
99 var loopNode = getContainingLoopNode(node);
100 if (!loopNode) {
101 return;
102 }
103
104 var references = context.getScope().through;
105 if (references.length > 0 && !references.every(isBlockBindingsInLoop.bind(null, loopNode))) {
106 context.report(node, "Don't make functions within a loop");
107 }
108 }
109
110 return {
111 "ArrowFunctionExpression": checkForLoops,
112 "FunctionExpression": checkForLoops,
113 "FunctionDeclaration": checkForLoops
114 };
115};
116
117module.exports.schema = [];