/** * @fileoverview Rule to flag use of alert, confirm, prompt * @author Nicholas C. Zakas */ 'use strict'; //------------------------------------------------------------------------------ // Requirements //------------------------------------------------------------------------------ const { getStaticPropertyName: getPropertyName, getVariableByName, skipChainExpression, } = require('./utils/ast-utils'); //------------------------------------------------------------------------------ // Helpers //------------------------------------------------------------------------------ /** * Checks if the given name is a prohibited identifier. * @param {string} name The name to check * @returns {boolean} Whether or not the name is prohibited. */ function isProhibitedIdentifier(name) { return /^(alert|confirm|prompt)$/u.test(name); } /** * Finds the eslint-scope reference in the given scope. * @param {Object} scope The scope to search. * @param {ASTNode} node The identifier node. * @returns {Reference|null} Returns the found reference or null if none were found. */ function findReference(scope, node) { const references = scope.references.filter( (reference) => reference.identifier.range[0] === node.range[0] && reference.identifier.range[1] === node.range[1] ); if (references.length === 1) { return references[0]; } return null; } /** * Checks if the given identifier node is shadowed in the given scope. * @param {Object} scope The current scope. * @param {string} node The identifier node to check * @returns {boolean} Whether or not the name is shadowed. */ function isShadowed(scope, node) { const reference = findReference(scope, node); return reference && reference.resolved && reference.resolved.defs.length > 0; } /** * Checks if the given identifier node is a ThisExpression in the global scope or the global window property. * @param {Object} scope The current scope. * @param {string} node The identifier node to check * @returns {boolean} Whether or not the node is a reference to the global object. */ function isGlobalThisReferenceOrGlobalWindow(scope, node) { if (scope.type === 'global' && node.type === 'ThisExpression') { return true; } if ( node.type === 'Identifier' && (node.name === 'window' || (node.name === 'globalThis' && getVariableByName(scope, 'globalThis'))) ) { return !isShadowed(scope, node); } return false; } //------------------------------------------------------------------------------ // Rule Definition //------------------------------------------------------------------------------ /** @type {import('../types').Rule.RuleModule} */ module.exports = { meta: { type: 'suggestion', docs: { description: 'Disallow the use of `alert`, `confirm`, and `prompt`', recommended: false, url: 'https://eslint.org/docs/latest/rules/no-alert', }, schema: [], messages: { unexpected: 'Unexpected {{name}}.', }, }, create(context) { const sourceCode = context.sourceCode; return { CallExpression(node) { const callee = skipChainExpression(node.callee), currentScope = sourceCode.getScope(node); // without window. if (callee.type === 'Identifier') { const name = callee.name; if ( !isShadowed(currentScope, callee) && isProhibitedIdentifier(callee.name) ) { context.report({ node, messageId: 'unexpected', data: { name }, }); } } else if ( callee.type === 'MemberExpression' && isGlobalThisReferenceOrGlobalWindow(currentScope, callee.object) ) { const name = getPropertyName(callee); if (isProhibitedIdentifier(name)) { context.report({ node, messageId: 'unexpected', data: { name }, }); } } }, }; }, };