]>
Commit | Line | Data |
---|---|---|
eb39fafa DC |
1 | /** |
2 | * @fileoverview Rule to enforce var declarations are only at the top of a function. | |
3 | * @author Danny Fritz | |
4 | * @author Gyandeep Singh | |
5 | */ | |
6 | "use strict"; | |
7 | ||
8 | //------------------------------------------------------------------------------ | |
9 | // Rule Definition | |
10 | //------------------------------------------------------------------------------ | |
11 | ||
34eeec05 | 12 | /** @type {import('../shared/types').Rule} */ |
eb39fafa DC |
13 | module.exports = { |
14 | meta: { | |
15 | type: "suggestion", | |
16 | ||
17 | docs: { | |
8f9d1d4d | 18 | description: "Require `var` declarations be placed at the top of their containing scope", |
eb39fafa DC |
19 | recommended: false, |
20 | url: "https://eslint.org/docs/rules/vars-on-top" | |
21 | }, | |
22 | ||
23 | schema: [], | |
24 | messages: { | |
25 | top: "All 'var' declarations must be at the top of the function scope." | |
26 | } | |
27 | }, | |
28 | ||
29 | create(context) { | |
30 | ||
31 | //-------------------------------------------------------------------------- | |
32 | // Helpers | |
33 | //-------------------------------------------------------------------------- | |
34 | ||
eb39fafa | 35 | /** |
609c276f | 36 | * Has AST suggesting a directive. |
eb39fafa DC |
37 | * @param {ASTNode} node any node |
38 | * @returns {boolean} whether the given node structurally represents a directive | |
39 | */ | |
40 | function looksLikeDirective(node) { | |
41 | return node.type === "ExpressionStatement" && | |
42 | node.expression.type === "Literal" && typeof node.expression.value === "string"; | |
43 | } | |
44 | ||
45 | /** | |
46 | * Check to see if its a ES6 import declaration | |
47 | * @param {ASTNode} node any node | |
48 | * @returns {boolean} whether the given node represents a import declaration | |
49 | */ | |
50 | function looksLikeImport(node) { | |
51 | return node.type === "ImportDeclaration" || node.type === "ImportSpecifier" || | |
52 | node.type === "ImportDefaultSpecifier" || node.type === "ImportNamespaceSpecifier"; | |
53 | } | |
54 | ||
55 | /** | |
56 | * Checks whether a given node is a variable declaration or not. | |
57 | * @param {ASTNode} node any node | |
58 | * @returns {boolean} `true` if the node is a variable declaration. | |
59 | */ | |
60 | function isVariableDeclaration(node) { | |
61 | return ( | |
62 | node.type === "VariableDeclaration" || | |
63 | ( | |
64 | node.type === "ExportNamedDeclaration" && | |
65 | node.declaration && | |
66 | node.declaration.type === "VariableDeclaration" | |
67 | ) | |
68 | ); | |
69 | } | |
70 | ||
71 | /** | |
72 | * Checks whether this variable is on top of the block body | |
73 | * @param {ASTNode} node The node to check | |
74 | * @param {ASTNode[]} statements collection of ASTNodes for the parent node block | |
75 | * @returns {boolean} True if var is on top otherwise false | |
76 | */ | |
77 | function isVarOnTop(node, statements) { | |
78 | const l = statements.length; | |
79 | let i = 0; | |
80 | ||
609c276f TL |
81 | // Skip over directives and imports. Static blocks don't have either. |
82 | if (node.parent.type !== "StaticBlock") { | |
83 | for (; i < l; ++i) { | |
84 | if (!looksLikeDirective(statements[i]) && !looksLikeImport(statements[i])) { | |
85 | break; | |
86 | } | |
eb39fafa DC |
87 | } |
88 | } | |
89 | ||
90 | for (; i < l; ++i) { | |
91 | if (!isVariableDeclaration(statements[i])) { | |
92 | return false; | |
93 | } | |
94 | if (statements[i] === node) { | |
95 | return true; | |
96 | } | |
97 | } | |
98 | ||
99 | return false; | |
100 | } | |
101 | ||
102 | /** | |
103 | * Checks whether variable is on top at the global level | |
104 | * @param {ASTNode} node The node to check | |
105 | * @param {ASTNode} parent Parent of the node | |
106 | * @returns {void} | |
107 | */ | |
108 | function globalVarCheck(node, parent) { | |
109 | if (!isVarOnTop(node, parent.body)) { | |
110 | context.report({ node, messageId: "top" }); | |
111 | } | |
112 | } | |
113 | ||
114 | /** | |
115 | * Checks whether variable is on top at functional block scope level | |
116 | * @param {ASTNode} node The node to check | |
eb39fafa DC |
117 | * @returns {void} |
118 | */ | |
609c276f TL |
119 | function blockScopeVarCheck(node) { |
120 | const { parent } = node; | |
121 | ||
122 | if ( | |
123 | parent.type === "BlockStatement" && | |
124 | /Function/u.test(parent.parent.type) && | |
125 | isVarOnTop(node, parent.body) | |
126 | ) { | |
127 | return; | |
eb39fafa | 128 | } |
609c276f TL |
129 | |
130 | if ( | |
131 | parent.type === "StaticBlock" && | |
132 | isVarOnTop(node, parent.body) | |
133 | ) { | |
134 | return; | |
135 | } | |
136 | ||
137 | context.report({ node, messageId: "top" }); | |
eb39fafa DC |
138 | } |
139 | ||
140 | //-------------------------------------------------------------------------- | |
141 | // Public API | |
142 | //-------------------------------------------------------------------------- | |
143 | ||
144 | return { | |
145 | "VariableDeclaration[kind='var']"(node) { | |
146 | if (node.parent.type === "ExportNamedDeclaration") { | |
147 | globalVarCheck(node.parent, node.parent.parent); | |
148 | } else if (node.parent.type === "Program") { | |
149 | globalVarCheck(node, node.parent); | |
150 | } else { | |
609c276f | 151 | blockScopeVarCheck(node); |
eb39fafa DC |
152 | } |
153 | } | |
154 | }; | |
155 | ||
156 | } | |
157 | }; |