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 |
|
---|
12 | /** @type {import('../shared/types').Rule} */
|
---|
13 | module.exports = {
|
---|
14 | meta: {
|
---|
15 | type: "suggestion",
|
---|
16 |
|
---|
17 | docs: {
|
---|
18 | description: "Disallow declarations in the global scope",
|
---|
19 | recommended: false,
|
---|
20 | url: "https://eslint.org/docs/latest/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 | const sourceCode = context.sourceCode;
|
---|
47 |
|
---|
48 | /**
|
---|
49 | * Reports the node.
|
---|
50 | * @param {ASTNode} node Node to report.
|
---|
51 | * @param {string} messageId Id of the message to report.
|
---|
52 | * @param {string|undefined} kind Declaration kind, can be 'var', 'const', 'let', function or class.
|
---|
53 | * @returns {void}
|
---|
54 | */
|
---|
55 | function report(node, messageId, kind) {
|
---|
56 | context.report({
|
---|
57 | node,
|
---|
58 | messageId,
|
---|
59 | data: {
|
---|
60 | kind
|
---|
61 | }
|
---|
62 | });
|
---|
63 | }
|
---|
64 |
|
---|
65 | return {
|
---|
66 | Program(node) {
|
---|
67 | const scope = sourceCode.getScope(node);
|
---|
68 |
|
---|
69 | scope.variables.forEach(variable => {
|
---|
70 |
|
---|
71 | // Only ESLint global variables have the `writable` key.
|
---|
72 | const isReadonlyEslintGlobalVariable = variable.writeable === false;
|
---|
73 | const isWritableEslintGlobalVariable = variable.writeable === true;
|
---|
74 |
|
---|
75 | if (isWritableEslintGlobalVariable) {
|
---|
76 |
|
---|
77 | // Everything is allowed with writable ESLint global variables.
|
---|
78 | return;
|
---|
79 | }
|
---|
80 |
|
---|
81 | // Variables exported by "exported" block comments
|
---|
82 | if (variable.eslintExported) {
|
---|
83 | return;
|
---|
84 | }
|
---|
85 |
|
---|
86 | variable.defs.forEach(def => {
|
---|
87 | const defNode = def.node;
|
---|
88 |
|
---|
89 | if (def.type === "FunctionName" || (def.type === "Variable" && def.parent.kind === "var")) {
|
---|
90 | if (isReadonlyEslintGlobalVariable) {
|
---|
91 | report(defNode, "redeclarationOfReadonlyGlobal");
|
---|
92 | } else {
|
---|
93 | report(
|
---|
94 | defNode,
|
---|
95 | "globalNonLexicalBinding",
|
---|
96 | def.type === "FunctionName" ? "function" : `'${def.parent.kind}'`
|
---|
97 | );
|
---|
98 | }
|
---|
99 | }
|
---|
100 |
|
---|
101 | if (checkLexicalBindings) {
|
---|
102 | if (def.type === "ClassName" ||
|
---|
103 | (def.type === "Variable" && (def.parent.kind === "let" || def.parent.kind === "const"))) {
|
---|
104 | if (isReadonlyEslintGlobalVariable) {
|
---|
105 | report(defNode, "redeclarationOfReadonlyGlobal");
|
---|
106 | } else {
|
---|
107 | report(
|
---|
108 | defNode,
|
---|
109 | "globalLexicalBinding",
|
---|
110 | def.type === "ClassName" ? "class" : `'${def.parent.kind}'`
|
---|
111 | );
|
---|
112 | }
|
---|
113 | }
|
---|
114 | }
|
---|
115 | });
|
---|
116 | });
|
---|
117 |
|
---|
118 | // Undeclared assigned variables.
|
---|
119 | scope.implicit.variables.forEach(variable => {
|
---|
120 | const scopeVariable = scope.set.get(variable.name);
|
---|
121 | let messageId;
|
---|
122 |
|
---|
123 | if (scopeVariable) {
|
---|
124 |
|
---|
125 | // ESLint global variable
|
---|
126 | if (scopeVariable.writeable) {
|
---|
127 | return;
|
---|
128 | }
|
---|
129 | messageId = "assignmentToReadonlyGlobal";
|
---|
130 |
|
---|
131 | } else {
|
---|
132 |
|
---|
133 | // Reference to an unknown variable, possible global leak.
|
---|
134 | messageId = "globalVariableLeak";
|
---|
135 | }
|
---|
136 |
|
---|
137 | // def.node is an AssignmentExpression, ForInStatement or ForOfStatement.
|
---|
138 | variable.defs.forEach(def => {
|
---|
139 | report(def.node, messageId);
|
---|
140 | });
|
---|
141 | });
|
---|
142 | }
|
---|
143 | };
|
---|
144 |
|
---|
145 | }
|
---|
146 | };
|
---|