2 * @fileoverview Rule to enforce var declarations are only at the top of a function.
4 * @author Gyandeep Singh
8 //------------------------------------------------------------------------------
10 //------------------------------------------------------------------------------
17 description: "require `var` declarations be placed at the top of their containing scope",
18 category: "Best Practices",
20 url: "https://eslint.org/docs/rules/vars-on-top"
25 top: "All 'var' declarations must be at the top of the function scope."
31 //--------------------------------------------------------------------------
33 //--------------------------------------------------------------------------
35 // eslint-disable-next-line jsdoc/require-description
37 * @param {ASTNode} node any node
38 * @returns {boolean} whether the given node structurally represents a directive
40 function looksLikeDirective(node) {
41 return node.type === "ExpressionStatement" &&
42 node.expression.type === "Literal" && typeof node.expression.value === "string";
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
50 function looksLikeImport(node) {
51 return node.type === "ImportDeclaration" || node.type === "ImportSpecifier" ||
52 node.type === "ImportDefaultSpecifier" || node.type === "ImportNamespaceSpecifier";
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.
60 function isVariableDeclaration(node) {
62 node.type === "VariableDeclaration" ||
64 node.type === "ExportNamedDeclaration" &&
66 node.declaration.type === "VariableDeclaration"
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
77 function isVarOnTop(node, statements) {
78 const l = statements.length;
81 // skip over directives
83 if (!looksLikeDirective(statements[i]) && !looksLikeImport(statements[i])) {
89 if (!isVariableDeclaration(statements[i])) {
92 if (statements[i] === node) {
101 * Checks whether variable is on top at the global level
102 * @param {ASTNode} node The node to check
103 * @param {ASTNode} parent Parent of the node
106 function globalVarCheck(node, parent) {
107 if (!isVarOnTop(node, parent.body)) {
108 context.report({ node, messageId: "top" });
113 * Checks whether variable is on top at functional block scope level
114 * @param {ASTNode} node The node to check
115 * @param {ASTNode} parent Parent of the node
116 * @param {ASTNode} grandParent Parent of the node's parent
119 function blockScopeVarCheck(node, parent, grandParent) {
120 if (!(/Function/u.test(grandParent.type) &&
121 parent.type === "BlockStatement" &&
122 isVarOnTop(node, parent.body))) {
123 context.report({ node, messageId: "top" });
127 //--------------------------------------------------------------------------
129 //--------------------------------------------------------------------------
132 "VariableDeclaration[kind='var']"(node) {
133 if (node.parent.type === "ExportNamedDeclaration") {
134 globalVarCheck(node.parent, node.parent.parent);
135 } else if (node.parent.type === "Program") {
136 globalVarCheck(node, node.parent);
138 blockScopeVarCheck(node, node.parent, node.parent.parent);