no-implicit-globals.js 4.5 KB

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