no-alert.js 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  1. /**
  2. * @fileoverview Rule to flag use of alert, confirm, prompt
  3. * @author Nicholas C. Zakas
  4. */
  5. "use strict";
  6. //------------------------------------------------------------------------------
  7. // Requirements
  8. //------------------------------------------------------------------------------
  9. const {
  10. getStaticPropertyName: getPropertyName,
  11. getVariableByName,
  12. skipChainExpression,
  13. } = require("./utils/ast-utils");
  14. //------------------------------------------------------------------------------
  15. // Helpers
  16. //------------------------------------------------------------------------------
  17. /**
  18. * Checks if the given name is a prohibited identifier.
  19. * @param {string} name The name to check
  20. * @returns {boolean} Whether or not the name is prohibited.
  21. */
  22. function isProhibitedIdentifier(name) {
  23. return /^(?:alert|confirm|prompt)$/u.test(name);
  24. }
  25. /**
  26. * Finds the eslint-scope reference in the given scope.
  27. * @param {Object} scope The scope to search.
  28. * @param {ASTNode} node The identifier node.
  29. * @returns {Reference|null} Returns the found reference or null if none were found.
  30. */
  31. function findReference(scope, node) {
  32. const references = scope.references.filter(
  33. reference =>
  34. reference.identifier.range[0] === node.range[0] &&
  35. reference.identifier.range[1] === node.range[1],
  36. );
  37. if (references.length === 1) {
  38. return references[0];
  39. }
  40. return null;
  41. }
  42. /**
  43. * Checks if the given identifier node is shadowed in the given scope.
  44. * @param {Object} scope The current scope.
  45. * @param {string} node The identifier node to check
  46. * @returns {boolean} Whether or not the name is shadowed.
  47. */
  48. function isShadowed(scope, node) {
  49. const reference = findReference(scope, node);
  50. return (
  51. reference && reference.resolved && reference.resolved.defs.length > 0
  52. );
  53. }
  54. /**
  55. * Checks if the given identifier node is a ThisExpression in the global scope or the global window property.
  56. * @param {Object} scope The current scope.
  57. * @param {string} node The identifier node to check
  58. * @returns {boolean} Whether or not the node is a reference to the global object.
  59. */
  60. function isGlobalThisReferenceOrGlobalWindow(scope, node) {
  61. if (scope.type === "global" && node.type === "ThisExpression") {
  62. return true;
  63. }
  64. if (
  65. node.type === "Identifier" &&
  66. (node.name === "window" ||
  67. (node.name === "globalThis" &&
  68. getVariableByName(scope, "globalThis")))
  69. ) {
  70. return !isShadowed(scope, node);
  71. }
  72. return false;
  73. }
  74. //------------------------------------------------------------------------------
  75. // Rule Definition
  76. //------------------------------------------------------------------------------
  77. /** @type {import('../types').Rule.RuleModule} */
  78. module.exports = {
  79. meta: {
  80. type: "suggestion",
  81. docs: {
  82. description: "Disallow the use of `alert`, `confirm`, and `prompt`",
  83. recommended: false,
  84. url: "https://eslint.org/docs/latest/rules/no-alert",
  85. },
  86. schema: [],
  87. messages: {
  88. unexpected: "Unexpected {{name}}.",
  89. },
  90. },
  91. create(context) {
  92. const sourceCode = context.sourceCode;
  93. return {
  94. CallExpression(node) {
  95. const callee = skipChainExpression(node.callee),
  96. currentScope = sourceCode.getScope(node);
  97. // without window.
  98. if (callee.type === "Identifier") {
  99. const name = callee.name;
  100. if (
  101. !isShadowed(currentScope, callee) &&
  102. isProhibitedIdentifier(callee.name)
  103. ) {
  104. context.report({
  105. node,
  106. messageId: "unexpected",
  107. data: { name },
  108. });
  109. }
  110. } else if (
  111. callee.type === "MemberExpression" &&
  112. isGlobalThisReferenceOrGlobalWindow(
  113. currentScope,
  114. callee.object,
  115. )
  116. ) {
  117. const name = getPropertyName(callee);
  118. if (isProhibitedIdentifier(name)) {
  119. context.report({
  120. node,
  121. messageId: "unexpected",
  122. data: { name },
  123. });
  124. }
  125. }
  126. },
  127. };
  128. },
  129. };