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