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 type: "suggestion", 15 16 docs: { 17 description: "require `var` declarations be placed at the top of their containing scope", 18 category: "Best Practices", 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 35 // eslint-disable-next-line jsdoc/require-description 36 /** 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 81 // skip over directives 82 for (; i < l; ++i) { 83 if (!looksLikeDirective(statements[i]) && !looksLikeImport(statements[i])) { 84 break; 85 } 86 } 87 88 for (; i < l; ++i) { 89 if (!isVariableDeclaration(statements[i])) { 90 return false; 91 } 92 if (statements[i] === node) { 93 return true; 94 } 95 } 96 97 return false; 98 } 99 100 /** 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 104 * @returns {void} 105 */ 106 function globalVarCheck(node, parent) { 107 if (!isVarOnTop(node, parent.body)) { 108 context.report({ node, messageId: "top" }); 109 } 110 } 111 112 /** 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 117 * @returns {void} 118 */ 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" }); 124 } 125 } 126 127 //-------------------------------------------------------------------------- 128 // Public API 129 //-------------------------------------------------------------------------- 130 131 return { 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); 137 } else { 138 blockScopeVarCheck(node, node.parent, node.parent.parent); 139 } 140 } 141 }; 142 143 } 144}; 145