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