diff --git a/docs/rules/no-shadow-restricted-names.md b/docs/rules/no-shadow-restricted-names.md index 0624a82aa14..9dfc0371906 100644 --- a/docs/rules/no-shadow-restricted-names.md +++ b/docs/rules/no-shadow-restricted-names.md @@ -19,7 +19,7 @@ function NaN(){} !function(Infinity){}; -var undefined; +var undefined = 5; try {} catch(eval){} ``` @@ -32,6 +32,9 @@ Examples of **correct** code for this rule: var Object; function f(a, b){} + +// Exception: `undefined` may be shadowed if the variable is never assigned a value. +var undefined; ``` ## Further Reading diff --git a/lib/rules/no-shadow-restricted-names.js b/lib/rules/no-shadow-restricted-names.js index 9bdd5086804..f09f3767da4 100644 --- a/lib/rules/no-shadow-restricted-names.js +++ b/lib/rules/no-shadow-restricted-names.js @@ -4,6 +4,19 @@ */ "use strict"; +/** + * Determines if a variable safely shadows undefined. + * This is the case when a variable named `undefined` is never assigned to a value (i.e. it always shares the same value + * as the global). + * @param {eslintScope.Variable} variable The variable to check + * @returns {boolean} true if this variable safely shadows `undefined` + */ +function safelyShadowsUndefined(variable) { + return variable.name === "undefined" && + variable.references.every(ref => !ref.isWrite()) && + variable.defs.every(def => def.node.type === "VariableDeclarator" && def.node.init === null); +} + //------------------------------------------------------------------------------ // Rule Definition //------------------------------------------------------------------------------ @@ -24,12 +37,13 @@ module.exports = { create(context) { - const RESTRICTED = ["undefined", "NaN", "Infinity", "arguments", "eval"]; + + const RESTRICTED = new Set(["undefined", "NaN", "Infinity", "arguments", "eval"]); return { "VariableDeclaration, :function, CatchClause"(node) { for (const variable of context.getDeclaredVariables(node)) { - if (variable.defs.length > 0 && RESTRICTED.includes(variable.name)) { + if (variable.defs.length > 0 && RESTRICTED.has(variable.name) && !safelyShadowsUndefined(variable)) { context.report({ node: variable.defs[0].name, message: "Shadowing of global property '{{idName}}'.", diff --git a/tests/lib/rules/no-shadow-restricted-names.js b/tests/lib/rules/no-shadow-restricted-names.js index 3c014a70f5a..cfc40a82cfd 100644 --- a/tests/lib/rules/no-shadow-restricted-names.js +++ b/tests/lib/rules/no-shadow-restricted-names.js @@ -24,6 +24,13 @@ ruleTester.run("no-shadow-restricted-names", rule, { { code: "try {} catch {}", parserOptions: { ecmaVersion: 2019 } + }, + "var undefined;", + "var undefined; doSomething(undefined);", + "var undefined; var undefined;", + { + code: "let undefined", + parserOptions: { ecmaVersion: 2015 } } ], invalid: [ @@ -39,13 +46,12 @@ ruleTester.run("no-shadow-restricted-names", rule, { ] }, { - code: "function undefined(undefined) { var undefined; !function undefined(undefined) { try {} catch(undefined) {} }; }", + code: "function undefined(undefined) { !function undefined(undefined) { try {} catch(undefined) {} }; }", errors: [ { message: "Shadowing of global property 'undefined'.", type: "Identifier" }, { message: "Shadowing of global property 'undefined'.", type: "Identifier" }, { message: "Shadowing of global property 'undefined'.", type: "Identifier" }, { message: "Shadowing of global property 'undefined'.", type: "Identifier" }, - { message: "Shadowing of global property 'undefined'.", type: "Identifier" }, { message: "Shadowing of global property 'undefined'.", type: "Identifier" } ] }, @@ -110,6 +116,12 @@ ruleTester.run("no-shadow-restricted-names", rule, { { message: "Shadowing of global property 'undefined'.", type: "Identifier" }, { message: "Shadowing of global property 'undefined'.", type: "Identifier" } ] + }, + { + code: "var undefined; undefined = 5;", + errors: [ + { message: "Shadowing of global property 'undefined'.", type: "Identifier" } + ] } ] });