summaryrefslogtreecommitdiff
path: root/tools/node_modules/eslint/lib/rules/no-implicit-globals.js
blob: d4bfa3af82fc3bc9e221fc9a135a822083c85c74 (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
/**
 * @fileoverview Rule to check for implicit global variables, functions and classes.
 * @author Joshua Peek
 */

"use strict";

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

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

        docs: {
            description: "disallow declarations in the global scope",
            category: "Best Practices",
            recommended: false,
            url: "https://eslint.org/docs/rules/no-implicit-globals"
        },

        schema: [{
            type: "object",
            properties: {
                lexicalBindings: {
                    type: "boolean",
                    default: false
                }
            },
            additionalProperties: false
        }],

        messages: {
            globalNonLexicalBinding: "Unexpected {{kind}} declaration in the global scope, wrap in an IIFE for a local variable, assign as global property for a global variable.",
            globalLexicalBinding: "Unexpected {{kind}} declaration in the global scope, wrap in a block or in an IIFE.",
            globalVariableLeak: "Global variable leak, declare the variable if it is intended to be local.",
            assignmentToReadonlyGlobal: "Unexpected assignment to read-only global variable.",
            redeclarationOfReadonlyGlobal: "Unexpected redeclaration of read-only global variable."
        }
    },

    create(context) {

        const checkLexicalBindings = context.options[0] && context.options[0].lexicalBindings === true;

        /**
         * Reports the node.
         * @param {ASTNode} node Node to report.
         * @param {string} messageId Id of the message to report.
         * @param {string|undefined} kind Declaration kind, can be 'var', 'const', 'let', function or class.
         * @returns {void}
         */
        function report(node, messageId, kind) {
            context.report({
                node,
                messageId,
                data: {
                    kind
                }
            });
        }

        return {
            Program() {
                const scope = context.getScope();

                scope.variables.forEach(variable => {

                    // Only ESLint global variables have the `writable` key.
                    const isReadonlyEslintGlobalVariable = variable.writeable === false;
                    const isWritableEslintGlobalVariable = variable.writeable === true;

                    if (isWritableEslintGlobalVariable) {

                        // Everything is allowed with writable ESLint global variables.
                        return;
                    }

                    variable.defs.forEach(def => {
                        const defNode = def.node;

                        if (def.type === "FunctionName" || (def.type === "Variable" && def.parent.kind === "var")) {
                            if (isReadonlyEslintGlobalVariable) {
                                report(defNode, "redeclarationOfReadonlyGlobal");
                            } else {
                                report(
                                    defNode,
                                    "globalNonLexicalBinding",
                                    def.type === "FunctionName" ? "function" : `'${def.parent.kind}'`
                                );
                            }
                        }

                        if (checkLexicalBindings) {
                            if (def.type === "ClassName" ||
                                    (def.type === "Variable" && (def.parent.kind === "let" || def.parent.kind === "const"))) {
                                if (isReadonlyEslintGlobalVariable) {
                                    report(defNode, "redeclarationOfReadonlyGlobal");
                                } else {
                                    report(
                                        defNode,
                                        "globalLexicalBinding",
                                        def.type === "ClassName" ? "class" : `'${def.parent.kind}'`
                                    );
                                }
                            }
                        }
                    });
                });

                // Undeclared assigned variables.
                scope.implicit.variables.forEach(variable => {
                    const scopeVariable = scope.set.get(variable.name);
                    let messageId;

                    if (scopeVariable) {

                        // ESLint global variable
                        if (scopeVariable.writeable) {
                            return;
                        }
                        messageId = "assignmentToReadonlyGlobal";

                    } else {

                        // Reference to an unknown variable, possible global leak.
                        messageId = "globalVariableLeak";
                    }

                    // def.node is an AssignmentExpression, ForInStatement or ForOfStatement.
                    variable.defs.forEach(def => {
                        report(def.node, messageId);
                    });
                });
            }
        };

    }
};