UNPKG

5.02 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[kind='var']"(node) {
129 if (node.parent.type === "ExportNamedDeclaration") {
130 globalVarCheck(node.parent, node.parent.parent);
131 } else if (node.parent.type === "Program") {
132 globalVarCheck(node, node.parent);
133 } else {
134 blockScopeVarCheck(node, node.parent, node.parent.parent);
135 }
136 }
137 };
138
139 }
140};