• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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