UNPKG

5.06 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 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 /**
36 * @param {ASTNode} node - any node
37 * @returns {boolean} whether the given node structurally represents a directive
38 */
39 function looksLikeDirective(node) {
40 return node.type === "ExpressionStatement" &&
41 node.expression.type === "Literal" && typeof node.expression.value === "string";
42 }
43
44 /**
45 * Check to see if its a ES6 import declaration
46 * @param {ASTNode} node - any node
47 * @returns {boolean} whether the given node represents a import declaration
48 */
49 function looksLikeImport(node) {
50 return node.type === "ImportDeclaration" || node.type === "ImportSpecifier" ||
51 node.type === "ImportDefaultSpecifier" || node.type === "ImportNamespaceSpecifier";
52 }
53
54 /**
55 * Checks whether a given node is a variable declaration or not.
56 *
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};