UNPKG

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