diff options
Diffstat (limited to 'tools/eslint/lib/rules/no-alert.js')
-rw-r--r-- | tools/eslint/lib/rules/no-alert.js | 133 |
1 files changed, 116 insertions, 17 deletions
diff --git a/tools/eslint/lib/rules/no-alert.js b/tools/eslint/lib/rules/no-alert.js index 1f14b533d71..7d041eaf748 100644 --- a/tools/eslint/lib/rules/no-alert.js +++ b/tools/eslint/lib/rules/no-alert.js @@ -1,6 +1,8 @@ /** * @fileoverview Rule to flag use of alert, confirm, prompt * @author Nicholas C. Zakas + * @copyright 2015 Mathias Schreck + * @copyright 2013 Nicholas C. Zakas */ "use strict"; @@ -8,47 +10,144 @@ // Helpers //------------------------------------------------------------------------------ -function matchProhibited(name) { - return name.match(/^(alert|confirm|prompt)$/); +/** + * 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)$/.test(name); } -function report(context, node, result) { - context.report(node, "Unexpected {{name}}.", { name: result[1] }); +/** + * Reports the given node and identifier name. + * @param {RuleContext} context The ESLint rule context. + * @param {ASTNode} node The node to report on. + * @param {string} identifierName The name of the identifier. + * @returns {void} + */ +function report(context, node, identifierName) { + context.report(node, "Unexpected {{name}}.", { name: identifierName }); } +/** + * Returns the property name of a MemberExpression. + * @param {ASTNode} memberExpressionNode The MemberExpression node. + * @returns {string|undefined} Returns the property name if available, undefined else. + */ +function getPropertyName(memberExpressionNode) { + if (memberExpressionNode.computed) { + if (memberExpressionNode.property.type === "Literal") { + return memberExpressionNode.property.value; + } + } else { + return memberExpressionNode.property.name; + } +} + +/** + * Finds the escope reference in the given scope. + * @param {Object} scope The scope to search. + * @param {ASTNode} node The identifier node. + * @returns {Reference|undefined} Returns the found reference or undefined if none were found. + */ +function findReference(scope, node) { + var references = scope.references.filter(function (reference) { + return reference.identifier.range[0] === node.range[0] && + reference.identifier.range[1] === node.range[1]; + }); + + if (references.length === 1) { + return references[0]; + } +} + +/** + * Checks if the given identifier name is shadowed in the given global scope. + * @param {Object} globalScope The global scope. + * @param {string} identifierName The identifier name to check + * @returns {boolean} Whether or not the name is shadowed globally. + */ +function isGloballyShadowed(globalScope, identifierName) { + return globalScope.variables.some(function (variable) { + return variable.name === identifierName && variable.defs.length > 0; + }); +} + +/** + * Checks if the given identifier node is shadowed in the given scope. + * @param {Object} scope The current scope. + * @param {Object} globalScope The global scope. + * @param {string} node The identifier node to check + * @returns {boolean} Whether or not the name is shadowed. + */ +function isShadowed(scope, globalScope, node) { + var reference = findReference(scope, node), + identifierName = node.name; + + if (reference) { + if (reference.resolved || isGloballyShadowed(globalScope, identifierName)) { + return true; + } + } + + return false; +} + +/** + * 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 {Object} globalScope The global 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, globalScope, node) { + if (scope.type === "global" && node.type === "ThisExpression") { + return true; + } else if (node.name === "window") { + return !isShadowed(scope, globalScope, node); + } + + return false; +} //------------------------------------------------------------------------------ // Rule Definition //------------------------------------------------------------------------------ module.exports = function(context) { + var globalScope; return { - "CallExpression": function(node) { + "Program": function () { + globalScope = context.getScope(); + }, - var result; + "CallExpression": function(node) { + var callee = node.callee, + identifierName, + currentScope = context.getScope(); // without window. - if (node.callee.type === "Identifier") { + if (callee.type === "Identifier") { + identifierName = callee.name; - result = matchProhibited(node.callee.name); - - if (result) { - report(context, node, result); + if (!isShadowed(currentScope, globalScope, callee) && isProhibitedIdentifier(callee.name)) { + report(context, node, identifierName); } - } else if (node.callee.type === "MemberExpression" && node.callee.property.type === "Identifier") { - - result = matchProhibited(node.callee.property.name); + } else if (callee.type === "MemberExpression" && isGlobalThisReferenceOrGlobalWindow(currentScope, globalScope, callee.object)) { + identifierName = getPropertyName(callee); - if (result && node.callee.object.name === "window") { - report(context, node, result); + if (isProhibitedIdentifier(identifierName)) { + report(context, node, identifierName); } - } } }; }; + +module.exports.schema = []; |