summaryrefslogtreecommitdiff
path: root/tools/node_modules/eslint/lib/rules/vars-on-top.js
blob: 28ddae442b517a43f66a709dfa9d8e104c6db70f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
/**
 * @fileoverview Rule to enforce var declarations are only at the top of a function.
 * @author Danny Fritz
 * @author Gyandeep Singh
 */
"use strict";

//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------

module.exports = {
    meta: {
        type: "suggestion",

        docs: {
            description: "require `var` declarations be placed at the top of their containing scope",
            category: "Best Practices",
            recommended: false,
            url: "https://eslint.org/docs/rules/vars-on-top"
        },

        schema: [],
        messages: {
            top: "All 'var' declarations must be at the top of the function scope."
        }
    },

    create(context) {

        //--------------------------------------------------------------------------
        // Helpers
        //--------------------------------------------------------------------------

        // eslint-disable-next-line jsdoc/require-description
        /**
         * @param {ASTNode} node any node
         * @returns {boolean} whether the given node structurally represents a directive
         */
        function looksLikeDirective(node) {
            return node.type === "ExpressionStatement" &&
                node.expression.type === "Literal" && typeof node.expression.value === "string";
        }

        /**
         * Check to see if its a ES6 import declaration
         * @param {ASTNode} node any node
         * @returns {boolean} whether the given node represents a import declaration
         */
        function looksLikeImport(node) {
            return node.type === "ImportDeclaration" || node.type === "ImportSpecifier" ||
                node.type === "ImportDefaultSpecifier" || node.type === "ImportNamespaceSpecifier";
        }

        /**
         * Checks whether a given node is a variable declaration or not.
         * @param {ASTNode} node any node
         * @returns {boolean} `true` if the node is a variable declaration.
         */
        function isVariableDeclaration(node) {
            return (
                node.type === "VariableDeclaration" ||
                (
                    node.type === "ExportNamedDeclaration" &&
                    node.declaration &&
                    node.declaration.type === "VariableDeclaration"
                )
            );
        }

        /**
         * Checks whether this variable is on top of the block body
         * @param {ASTNode} node The node to check
         * @param {ASTNode[]} statements collection of ASTNodes for the parent node block
         * @returns {boolean} True if var is on top otherwise false
         */
        function isVarOnTop(node, statements) {
            const l = statements.length;
            let i = 0;

            // skip over directives
            for (; i < l; ++i) {
                if (!looksLikeDirective(statements[i]) && !looksLikeImport(statements[i])) {
                    break;
                }
            }

            for (; i < l; ++i) {
                if (!isVariableDeclaration(statements[i])) {
                    return false;
                }
                if (statements[i] === node) {
                    return true;
                }
            }

            return false;
        }

        /**
         * Checks whether variable is on top at the global level
         * @param {ASTNode} node The node to check
         * @param {ASTNode} parent Parent of the node
         * @returns {void}
         */
        function globalVarCheck(node, parent) {
            if (!isVarOnTop(node, parent.body)) {
                context.report({ node, messageId: "top" });
            }
        }

        /**
         * Checks whether variable is on top at functional block scope level
         * @param {ASTNode} node The node to check
         * @param {ASTNode} parent Parent of the node
         * @param {ASTNode} grandParent Parent of the node's parent
         * @returns {void}
         */
        function blockScopeVarCheck(node, parent, grandParent) {
            if (!(/Function/u.test(grandParent.type) &&
                    parent.type === "BlockStatement" &&
                    isVarOnTop(node, parent.body))) {
                context.report({ node, messageId: "top" });
            }
        }

        //--------------------------------------------------------------------------
        // Public API
        //--------------------------------------------------------------------------

        return {
            "VariableDeclaration[kind='var']"(node) {
                if (node.parent.type === "ExportNamedDeclaration") {
                    globalVarCheck(node.parent, node.parent.parent);
                } else if (node.parent.type === "Program") {
                    globalVarCheck(node, node.parent);
                } else {
                    blockScopeVarCheck(node, node.parent, node.parent.parent);
                }
            }
        };

    }
};