• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/**
2 * @fileoverview Rule to check for implicit global variables, functions and classes.
3 * @author Joshua Peek
4 */
5
6"use strict";
7
8//------------------------------------------------------------------------------
9// Rule Definition
10//------------------------------------------------------------------------------
11
12module.exports = {
13    meta: {
14        type: "suggestion",
15
16        docs: {
17            description: "disallow declarations in the global scope",
18            category: "Best Practices",
19            recommended: false,
20            url: "https://eslint.org/docs/rules/no-implicit-globals"
21        },
22
23        schema: [{
24            type: "object",
25            properties: {
26                lexicalBindings: {
27                    type: "boolean",
28                    default: false
29                }
30            },
31            additionalProperties: false
32        }],
33
34        messages: {
35            globalNonLexicalBinding: "Unexpected {{kind}} declaration in the global scope, wrap in an IIFE for a local variable, assign as global property for a global variable.",
36            globalLexicalBinding: "Unexpected {{kind}} declaration in the global scope, wrap in a block or in an IIFE.",
37            globalVariableLeak: "Global variable leak, declare the variable if it is intended to be local.",
38            assignmentToReadonlyGlobal: "Unexpected assignment to read-only global variable.",
39            redeclarationOfReadonlyGlobal: "Unexpected redeclaration of read-only global variable."
40        }
41    },
42
43    create(context) {
44
45        const checkLexicalBindings = context.options[0] && context.options[0].lexicalBindings === true;
46
47        /**
48         * Reports the node.
49         * @param {ASTNode} node Node to report.
50         * @param {string} messageId Id of the message to report.
51         * @param {string|undefined} kind Declaration kind, can be 'var', 'const', 'let', function or class.
52         * @returns {void}
53         */
54        function report(node, messageId, kind) {
55            context.report({
56                node,
57                messageId,
58                data: {
59                    kind
60                }
61            });
62        }
63
64        return {
65            Program() {
66                const scope = context.getScope();
67
68                scope.variables.forEach(variable => {
69
70                    // Only ESLint global variables have the `writable` key.
71                    const isReadonlyEslintGlobalVariable = variable.writeable === false;
72                    const isWritableEslintGlobalVariable = variable.writeable === true;
73
74                    if (isWritableEslintGlobalVariable) {
75
76                        // Everything is allowed with writable ESLint global variables.
77                        return;
78                    }
79
80                    variable.defs.forEach(def => {
81                        const defNode = def.node;
82
83                        if (def.type === "FunctionName" || (def.type === "Variable" && def.parent.kind === "var")) {
84                            if (isReadonlyEslintGlobalVariable) {
85                                report(defNode, "redeclarationOfReadonlyGlobal");
86                            } else {
87                                report(
88                                    defNode,
89                                    "globalNonLexicalBinding",
90                                    def.type === "FunctionName" ? "function" : `'${def.parent.kind}'`
91                                );
92                            }
93                        }
94
95                        if (checkLexicalBindings) {
96                            if (def.type === "ClassName" ||
97                                    (def.type === "Variable" && (def.parent.kind === "let" || def.parent.kind === "const"))) {
98                                if (isReadonlyEslintGlobalVariable) {
99                                    report(defNode, "redeclarationOfReadonlyGlobal");
100                                } else {
101                                    report(
102                                        defNode,
103                                        "globalLexicalBinding",
104                                        def.type === "ClassName" ? "class" : `'${def.parent.kind}'`
105                                    );
106                                }
107                            }
108                        }
109                    });
110                });
111
112                // Undeclared assigned variables.
113                scope.implicit.variables.forEach(variable => {
114                    const scopeVariable = scope.set.get(variable.name);
115                    let messageId;
116
117                    if (scopeVariable) {
118
119                        // ESLint global variable
120                        if (scopeVariable.writeable) {
121                            return;
122                        }
123                        messageId = "assignmentToReadonlyGlobal";
124
125                    } else {
126
127                        // Reference to an unknown variable, possible global leak.
128                        messageId = "globalVariableLeak";
129                    }
130
131                    // def.node is an AssignmentExpression, ForInStatement or ForOfStatement.
132                    variable.defs.forEach(def => {
133                        report(def.node, messageId);
134                    });
135                });
136            }
137        };
138
139    }
140};
141