UNPKG

5.4 kBJavaScriptView Raw
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
12module.exports = {
13 meta: {
14 docs: {
15 description: "require `var` declarations be placed at the top of their containing scope",
16 category: "Best Practices",
17 recommended: false,
18 url: "https://eslint.org/docs/rules/vars-on-top"
19 },
20
21 schema: []
22 },
23
24 create(context) {
25 const errorMessage = "All 'var' declarations must be at the top of the function scope.";
26
27 //--------------------------------------------------------------------------
28 // Helpers
29 //--------------------------------------------------------------------------
30
31 /**
32 * @param {ASTNode} node - any node
33 * @returns {boolean} whether the given node structurally represents a directive
34 */
35 function looksLikeDirective(node) {
36 return node.type === "ExpressionStatement" &&
37 node.expression.type === "Literal" && typeof node.expression.value === "string";
38 }
39
40 /**
41 * Check to see if its a ES6 import declaration
42 * @param {ASTNode} node - any node
43 * @returns {boolean} whether the given node represents a import declaration
44 */
45 function looksLikeImport(node) {
46 return node.type === "ImportDeclaration" || node.type === "ImportSpecifier" ||
47 node.type === "ImportDefaultSpecifier" || node.type === "ImportNamespaceSpecifier";
48 }
49
50 /**
51 * Checks whether a given node is a variable declaration or not.
52 *
53 * @param {ASTNode} node - any node
54 * @returns {boolean} `true` if the node is a variable declaration.
55 */
56 function isVariableDeclaration(node) {
57 return (
58 node.type === "VariableDeclaration" ||
59 (
60 node.type === "ExportNamedDeclaration" &&
61 node.declaration &&
62 node.declaration.type === "VariableDeclaration"
63 )
64 );
65 }
66
67 /**
68 * Checks whether this variable is on top of the block body
69 * @param {ASTNode} node - The node to check
70 * @param {ASTNode[]} statements - collection of ASTNodes for the parent node block
71 * @returns {boolean} True if var is on top otherwise false
72 */
73 function isVarOnTop(node, statements) {
74 const l = statements.length;
75 let i = 0;
76
77 // skip over directives
78 for (; i < l; ++i) {
79 if (!looksLikeDirective(statements[i]) && !looksLikeImport(statements[i])) {
80 break;
81 }
82 }
83
84 for (; i < l; ++i) {
85 if (!isVariableDeclaration(statements[i])) {
86 return false;
87 }
88 if (statements[i] === node) {
89 return true;
90 }
91 }
92
93 return false;
94 }
95
96 /**
97 * Checks whether variable is on top at the global level
98 * @param {ASTNode} node - The node to check
99 * @param {ASTNode} parent - Parent of the node
100 * @returns {void}
101 */
102 function globalVarCheck(node, parent) {
103 if (!isVarOnTop(node, parent.body)) {
104 context.report({ node, message: errorMessage });
105 }
106 }
107
108 /**
109 * Checks whether variable is on top at functional block scope level
110 * @param {ASTNode} node - The node to check
111 * @param {ASTNode} parent - Parent of the node
112 * @param {ASTNode} grandParent - Parent of the node's parent
113 * @returns {void}
114 */
115 function blockScopeVarCheck(node, parent, grandParent) {
116 if (!(/Function/.test(grandParent.type) &&
117 parent.type === "BlockStatement" &&
118 isVarOnTop(node, parent.body))) {
119 context.report({ node, message: errorMessage });
120 }
121 }
122
123 //--------------------------------------------------------------------------
124 // Public API
125 //--------------------------------------------------------------------------
126
127 return {
128 VariableDeclaration(node) {
129 const ancestors = context.getAncestors();
130 let parent = ancestors.pop();
131 let grandParent = ancestors.pop();
132
133 if (node.kind === "var") { // check variable is `var` type and not `let` or `const`
134 if (parent.type === "ExportNamedDeclaration") {
135 node = parent;
136 parent = grandParent;
137 grandParent = ancestors.pop();
138 }
139
140 if (parent.type === "Program") { // That means its a global variable
141 globalVarCheck(node, parent);
142 } else {
143 blockScopeVarCheck(node, parent, grandParent);
144 }
145 }
146 }
147 };
148
149 }
150};