diff --git a/lib/rules/no-undefined.js b/lib/rules/no-undefined.js index 18e1d98641d..d29ac1e720f 100644 --- a/lib/rules/no-undefined.js +++ b/lib/rules/no-undefined.js @@ -21,15 +21,54 @@ module.exports = { create(context) { + /** + * Report an invalid "undefined" identifier node. + * @param {ASTNode} node The node to report. + * @returns {void} + */ + function report(node) { + context.report({ + node, + message: "Unexpected use of undefined." + }); + } + + /** + * Checks the given scope for references to `undefined` and reports + * all references found. + * @param {escope.Scope} scope The scope to check. + * @returns {void} + */ + function checkScope(scope) { + const undefinedVar = scope.set.get("undefined"); + + if (!undefinedVar) { + return; + } + + const references = undefinedVar.references; + + const defs = undefinedVar.defs; + + // Report non-initializing references (those are covered in defs below) + references + .filter(ref => !ref.init) + .forEach(ref => report(ref.identifier)); + + defs.forEach(def => report(def.name)); + } + return { + "Program:exit"() { + const globalScope = context.getScope(); + + const stack = [globalScope]; - Identifier(node) { - if (node.name === "undefined") { - const parent = context.getAncestors().pop(); + while (stack.length) { + const scope = stack.pop(); - if (!parent || parent.type !== "MemberExpression" || node !== parent.property || parent.computed) { - context.report({ node, message: "Unexpected use of undefined." }); - } + stack.push.apply(stack, scope.childScopes); + checkScope(scope); } } }; diff --git a/tests/lib/rules/no-undefined.js b/tests/lib/rules/no-undefined.js index 18d8d335aee..bc570685416 100644 --- a/tests/lib/rules/no-undefined.js +++ b/tests/lib/rules/no-undefined.js @@ -12,13 +12,20 @@ const rule = require("../../../lib/rules/no-undefined"), RuleTester = require("../../../lib/testers/rule-tester"); +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +const ES6_SCRIPT = { ecmaVersion: 6 }; +const ES6_MODULE = { ecmaVersion: 6, sourceType: "module" }; + //------------------------------------------------------------------------------ // Tests //------------------------------------------------------------------------------ const errors = [{ message: "Unexpected use of undefined.", type: "Identifier" }]; -const ruleTester = new RuleTester(); +const ruleTester = new RuleTester({ parserOptions: ES6_SCRIPT }); ruleTester.run("no-undefined", rule, { valid: [ @@ -31,7 +38,18 @@ ruleTester.run("no-undefined", rule, { "ndefined", "a.undefined", "this.undefined", - "global['undefined']" + "global['undefined']", + + // https://github.com/eslint/eslint/issues/7964 + "({ undefined: bar })", + "({ undefined: bar } = foo)", + "({ undefined() {} })", + "class Foo { undefined() {} }", + "(class { undefined() {} })", + { code: "import { undefined as a } from 'foo'", parserOptions: ES6_MODULE }, + { code: "export { undefined } from 'foo'", parserOptions: ES6_MODULE }, + { code: "export { undefined as a } from 'foo'", parserOptions: ES6_MODULE }, + { code: "export { a as undefined } from 'foo'", parserOptions: ES6_MODULE } ], invalid: [ { code: "undefined", errors }, @@ -40,10 +58,66 @@ ruleTester.run("no-undefined", rule, { { code: "undefined[0]", errors }, { code: "f(undefined)", errors }, { code: "function f(undefined) {}", errors }, + { code: "function f() { var undefined; }", errors }, + { code: "function f() { undefined = true; }", errors }, { code: "var undefined;", errors }, { code: "try {} catch(undefined) {}", errors }, + { code: "function undefined() {}", errors }, { code: "(function undefined(){}())", errors }, + { code: "var foo = function undefined() {}", errors }, + { code: "foo = function undefined() {}", errors }, { code: "undefined = true", errors }, - { code: "var undefined = true", errors } + { code: "var undefined = true", errors }, + { code: "({ undefined })", errors }, + { code: "({ [undefined]: foo })", errors }, + { code: "({ bar: undefined })", errors }, + { code: "({ bar: undefined } = foo)", errors }, + { code: "var { undefined } = foo", errors }, + { code: "var { bar: undefined } = foo", errors }, + { + code: "({ undefined: function undefined() {} })", + errors: [Object.assign({}, errors[0], { column: 24 })] + }, + { code: "({ foo: function undefined() {} })", errors }, + { code: "class Foo { [undefined]() {} }", errors }, + { code: "(class { [undefined]() {} })", errors }, + { + code: "var undefined = true; undefined = false;", + errors: [{ + message: "Unexpected use of undefined.", + column: 5 + }, { + message: "Unexpected use of undefined.", + column: 23 + }] + }, + { + code: "import undefined from 'foo'", + parserOptions: ES6_MODULE, + errors + }, + { + code: "import * as undefined from 'foo'", + parserOptions: ES6_MODULE, + errors + }, + { + code: "import { undefined } from 'foo'", + parserOptions: ES6_MODULE, + errors + }, + { + code: "import { a as undefined } from 'foo'", + parserOptions: ES6_MODULE, + errors + }, + { + code: "export { undefined }", + parserOptions: ES6_MODULE, + errors + }, + { code: "let a = [b, ...undefined]", errors }, + { code: "[a, ...undefined] = b", errors }, + { code: "[a = undefined] = b", errors } ] });