Repository: eslint/eslint
Pull request #15134 (2021-10-04 05:03:27)

feat: report class evaluation TDZ errors in no-use-before-define
<!--
    Thank you for contributing!

    ESLint adheres to the [JS Foundation Code of Conduct](https://js.foundation/community/code-of-conduct).
-->

#### Prerequisites checklist

- [x] I have read the [contributing guidelines](https://github.com/eslint/eslint/blob/master/CONTRIBUTING.md).

#### What is the purpose of this pull request? (put an "X" next to an item)

<!--
    The following template is intentionally not a markdown checkbox list for the reasons
    explained in https://github.com/eslint/eslint/pull/12848#issuecomment-580302888
-->

[ ] Documentation update
[x] Bug fix ([template](https://raw.githubusercontent.com/eslint/eslint/master/templates/bug-report.md))
[ ] New rule ([template](https://raw.githubusercontent.com/eslint/eslint/master/templates/rule-proposal.md))
[ ] Changes an existing rule ([template](https://raw.githubusercontent.com/eslint/eslint/master/templates/rule-change-proposal.md))
[ ] Add autofixing to a rule
[ ] Add a CLI option
[ ] Add something to the core
[ ] Other, please explain:

This bug fix produces only more warnings.

<!--
    If the item you've checked above has a template, please paste the template questions below and answer them. (If this pull request is addressing an issue, you can just paste a link to the issue here instead.)
-->

**Tell us about your environment**

* **ESLint Version:** v8.0.0-rc.0
* **Node Version:** v12.22.4
* **npm Version:** v6.14.14

**What parser (default, `@babel/eslint-parser`, `@typescript-eslint/parser`, etc.) are you using?**

default

**Please show your full configuration:**

<details>
<summary>Configuration</summary>

<!-- Paste your configuration below: -->
```js
module.exports = {
    parserOptions: {
        ecmaVersion: 2022
    }
};
```

</details>

**What did you do? Please include the actual source code causing the issue.**

```js
/* eslint no-use-before-define: error */

class A extends A {} // TDZ error

class B {
    [B]() {} // TDZ error
}

const C = class {
    static foo = C; // TDZ error
}
```

```js
/* eslint no-use-before-define: ["error", { variables: false }] */

class D {
    static foo = x; // TDZ error
}

const x = 1;
```

**What did you expect to happen?**

`no-use-before-define` to report all references marked as "TDZ error" in the above examples.

**What actually happened? Please include the actual, raw output from ESLint.**

No errors.

<!--
    Please ensure your pull request is ready:

    - Read the pull request guide (https://eslint.org/docs/developer-guide/contributing/pull-requests)
    - Include tests for this change
    - Update documentation for this change (if appropriate)
-->

<!--
    The following is required for all pull requests:
-->

#### What changes did you make? (Give an overview)

Fixed the `no-use-before-define` rule for [ClassDefinitionEvaluation](https://tc39.es/ecma262/#sec-runtime-semantics-classdefinitionevaluation) steps. Class heritage and computed properties are evaluated before initializing the class binding (`A` and `B` create _classBinding_ in the class scope; they also create an outer variable but the references from the class definition are to the inner one, and the outer one is initialized even later). Static initializers are run after the class binding is initialized (if it exists), but still before initializing `C` in `const C = class ...` and before declarations that appear after the class (`const x`).

#### Is there anything you'd like reviewers to focus on?

Examples that don't require new syntax are reproducible as false negatives in v7.32.0, too ([demo link](https://eslint.org/demo#eyJ0ZXh0IjoiLyogZXNsaW50IG5vLXVzZS1iZWZvcmUtZGVmaW5lOiBlcnJvciAqL1xuXG5jbGFzcyBBIGV4dGVuZHMgQSB7fSAvLyBURFogZXJyb3JcblxuY2xhc3MgQiB7XG4gICAgW0JdKCkge30gLy8gVERaIGVycm9yXG59XG4iLCJvcHRpb25zIjp7InBhcnNlck9wdGlvbnMiOnsiZWNtYVZlcnNpb24iOjEyLCJzb3VyY2VUeXBlIjoic2NyaXB0IiwiZWNtYUZlYXR1cmVzIjp7fX0sInJ1bGVzIjp7fSwiZW52Ijp7fX19)). This is a bug fix regarding `extends` and computed properties, and a sort of enhancement regarding static field initializers since they're implicit functions. This rule doesn't track function calls, but in this case we can know when they will be called. 



Patch:
diff --git a/Makefile.js b/Makefile.js
index 61f034ad6..0a0846b92 100644
--- a/Makefile.js
+++ b/Makefile.js
@@ -648,6 +648,7 @@ target.gensite = function(prereleaseVersion) {
         if (test("-f", filename) && path.extname(filename) === ".md") {
 
             const rulesUrl = "https://github.com/eslint/eslint/tree/HEAD/lib/rules/",
+                testsUrl = "https://github.com/eslint/eslint/tree/HEAD/tests/lib/rules/",
                 docsUrl = "https://github.com/eslint/eslint/tree/HEAD/docs/rules/",
                 baseName = path.basename(filename),
                 sourceBaseName = `${path.basename(filename, ".md")}.js`,
@@ -731,6 +732,7 @@ target.gensite = function(prereleaseVersion) {
                 text += "\n## Resources\n\n";
                 if (!removed) {
                     text += `* [Rule source](${rulesUrl}${sourceBaseName})\n`;
+                    text += `* [Test source](${testsUrl}${sourceBaseName})\n`;
                 }
                 text += `* [Documentation source](${docsUrl}${baseName})\n`;
             }
diff --git a/README.md b/README.md
index 90eac6469..1f6df4ce7 100644
--- a/README.md
+++ b/README.md
@@ -298,7 +298,7 @@ The following companies, organizations, and individuals support ESLint's ongoing
 <p><a href="https://automattic.com"><img src="https://images.opencollective.com/photomatt/d0ef3e1/logo.png" alt="Automattic" height="undefined"></a></p><h3>Gold Sponsors</h3>
 <p><a href="https://nx.dev"><img src="https://images.opencollective.com/nx/0efbe42/logo.png" alt="Nx (by Nrwl)" height="96"></a> <a href="https://google.com/chrome"><img src="https://images.opencollective.com/chrome/dc55bd4/logo.png" alt="Chrome's Web Framework & Tools Performance Fund" height="96"></a> <a href="https://www.salesforce.com"><img src="https://images.opencollective.com/salesforce/ca8f997/logo.png" alt="Salesforce" height="96"></a> <a href="https://www.airbnb.com/"><img src="https://images.opencollective.com/airbnb/d327d66/logo.png" alt="Airbnb" height="96"></a> <a href="https://coinbase.com"><img src="https://avatars.githubusercontent.com/u/1885080?v=4" alt="Coinbase" height="96"></a> <a href="https://americanexpress.io"><img src="https://avatars.githubusercontent.com/u/3853301?v=4" alt="American Express" height="96"></a> <a href="https://substack.com/"><img src="https://avatars.githubusercontent.com/u/53023767?v=4" alt="Substack" height="96"></a></p><h3>Silver Sponsors</h3>
 <p><a href="https://liftoff.io/"><img src="https://images.opencollective.com/liftoff/5c4fa84/logo.png" alt="Liftoff" height="64"></a></p><h3>Bronze Sponsors</h3>
-<p><a href="https://launchdarkly.com"><img src="https://images.opencollective.com/launchdarkly/574bb9e/logo.png" alt="launchdarkly" height="32"></a> <a href="https://troypoint.com"><img src="https://images.opencollective.com/troypoint/080f96f/avatar.png" alt="TROYPOINT" height="32"></a> <a href="https://mobilen.nu"><img src="https://images.opencollective.com/mobilen/e19860d/logo.png" alt="Mobilen" height="32"></a> <a href="https://www.crosswordsolver.org/anagram-solver/"><img src="https://images.opencollective.com/anagram-solver/2666271/logo.png" alt="Anagram Solver" height="32"></a> <a href="null"><img src="https://images.opencollective.com/bugsnag-stability-monitoring/c2cef36/logo.png" alt="Bugsnag Stability Monitoring" height="32"></a> <a href="https://mixpanel.com"><img src="https://images.opencollective.com/mixpanel/cd682f7/logo.png" alt="Mixpanel" height="32"></a> <a href="https://www.vpsserver.com"><img src="https://images.opencollective.com/vpsservercom/logo.png" alt="VPS Server" height="32"></a> <a href="https://icons8.com"><img src="https://images.opencollective.com/icons8/7fa1641/logo.png" alt="Icons8: free icons, photos, illustrations, and music" height="32"></a> <a href="https://discord.com"><img src="https://images.opencollective.com/discordapp/f9645d9/logo.png" alt="Discord" height="32"></a> <a href="https://themeisle.com"><img src="https://images.opencollective.com/themeisle/d5592fe/logo.png" alt="ThemeIsle" height="32"></a> <a href="https://www.firesticktricks.com"><img src="https://images.opencollective.com/fire-stick-tricks/b8fbe2c/logo.png" alt="Fire Stick Tricks" height="32"></a> <a href="https://www.practiceignition.com"><img src="https://avatars.githubusercontent.com/u/5753491?v=4" alt="Practice Ignition" height="32"></a></p>
+<p><a href="https://launchdarkly.com"><img src="https://images.opencollective.com/launchdarkly/574bb9e/logo.png" alt="launchdarkly" height="32"></a> <a href="https://troypoint.com"><img src="https://images.opencollective.com/troypoint/080f96f/avatar.png" alt="TROYPOINT" height="32"></a> <a href="https://www.crosswordsolver.org/anagram-solver/"><img src="https://images.opencollective.com/anagram-solver/2666271/logo.png" alt="Anagram Solver" height="32"></a> <a href="https://www.vpsserver.com"><img src="https://images.opencollective.com/vpsservercom/logo.png" alt="VPS Server" height="32"></a> <a href="https://icons8.com"><img src="https://images.opencollective.com/icons8/7fa1641/logo.png" alt="Icons8: free icons, photos, illustrations, and music" height="32"></a> <a href="https://discord.com"><img src="https://images.opencollective.com/discordapp/f9645d9/logo.png" alt="Discord" height="32"></a> <a href="https://themeisle.com"><img src="https://images.opencollective.com/themeisle/d5592fe/logo.png" alt="ThemeIsle" height="32"></a> <a href="https://www.firesticktricks.com"><img src="https://images.opencollective.com/fire-stick-tricks/b8fbe2c/logo.png" alt="Fire Stick Tricks" height="32"></a> <a href="https://www.practiceignition.com"><img src="https://avatars.githubusercontent.com/u/5753491?v=4" alt="Practice Ignition" height="32"></a></p>
 <!--sponsorsend-->
 
 ## <a name="technology-sponsors"></a>Technology Sponsors
diff --git a/docs/developer-guide/contributing/pull-requests.md b/docs/developer-guide/contributing/pull-requests.md
index 876455a59..9c6f62fff 100644
--- a/docs/developer-guide/contributing/pull-requests.md
+++ b/docs/developer-guide/contributing/pull-requests.md
@@ -70,6 +70,8 @@ The `tag` is one of the following:
 * `build` - changes to build process only.
 * `refactor` - a change that doesn't affect APIs or user experience.
 * `test` - just changes to test files.
+* `ci` - changes to our CI configuration files and scripts.
+* `perf` - a code change that improves performance.
 
 Use the [labels of the issue you are working on](working-on-issues.md#issue-labels) to determine the best tag.
 
diff --git a/docs/developer-guide/working-with-rules.md b/docs/developer-guide/working-with-rules.md
index 269789e71..ee5a5606f 100644
--- a/docs/developer-guide/working-with-rules.md
+++ b/docs/developer-guide/working-with-rules.md
@@ -531,7 +531,7 @@ module.exports = {
 };
 ```
 
-Once you have an instance of `SourceCode`, you can use the methods on it to work with the code:
+Once you have an instance of `SourceCode`, you can use the following methods on it to work with the code:
 
 * `getText(node)` - returns the source code for the given node. Omit `node` to get the whole source.
 * `getAllComments()` - returns an array of all comments in the source.
diff --git a/docs/rules/no-use-before-define.md b/docs/rules/no-use-before-define.md
index 391c323ae..b4368427f 100644
--- a/docs/rules/no-use-before-define.md
+++ b/docs/rules/no-use-before-define.md
@@ -12,7 +12,6 @@ Examples of **incorrect** code for this rule:
 
 ```js
 /*eslint no-use-before-define: "error"*/
-/*eslint-env es6*/
 
 alert(a);
 var a = 10;
@@ -29,13 +28,29 @@ var b = 1;
     alert(c);
     let c = 1;
 }
+
+{
+    class C extends C {}
+}
+
+{
+    class C {
+        static x = "foo";
+        [C.x]() {}
+    }
+}
+
+{
+    const C = class {
+        static x = C;
+    }
+}
 ```
 
 Examples of **correct** code for this rule:
 
 ```js
 /*eslint no-use-before-define: "error"*/
-/*eslint-env es6*/
 
 var a;
 a = 10;
@@ -53,6 +68,24 @@ function g() {
     let c;
     c++;
 }
+
+{
+    class C {
+        static x = C;
+    }
+}
+
+{
+    const C = class C {
+        static x = C;
+    }
+}
+
+{
+    const C = class {
+        x = C;
+    }
+}
 ```
 
 ## Options
@@ -103,18 +136,32 @@ Examples of **incorrect** code for the `{ "classes": false }` option:
 
 ```js
 /*eslint no-use-before-define: ["error", { "classes": false }]*/
-/*eslint-env es6*/
 
 new A();
 class A {
 }
+
+{
+    class C extends C {}
+}
+
+{
+    class C extends D {}
+    class D {}
+}
+
+{
+    class C {
+        static x = "foo";
+        [C.x]() {}
+    }
+}
 ```
 
 Examples of **correct** code for the `{ "classes": false }` option:
 
 ```js
 /*eslint no-use-before-define: ["error", { "classes": false }]*/
-/*eslint-env es6*/
 
 function foo() {
     return new A();
@@ -139,6 +186,19 @@ const f = () => {};
 
 g();
 const g = function() {};
+
+{
+    const C = class {
+        static x = C;
+    }
+}
+
+{
+    const C = class {
+        static x = foo;
+    }
+    const foo = 1;
+}
 ```
 
 Examples of **correct** code for the `{ "variables": false }` option:
@@ -158,4 +218,11 @@ const f = () => {};
 
 const e = function() { return g(); }
 const g = function() {}
+
+{
+    const C = class {
+        x = foo;
+    }
+    const foo = 1;
+}
 ```
diff --git a/docs/user-guide/command-line-interface.md b/docs/user-guide/command-line-interface.md
index 34c03beb2..9ee7df474 100644
--- a/docs/user-guide/command-line-interface.md
+++ b/docs/user-guide/command-line-interface.md
@@ -211,9 +211,9 @@ If the rule is defined within a plugin, you have to prefix the rule ID with the
 
 Examples:
 
-    eslint --rule 'quotes: [2, double]'
-    eslint --rule 'guard-for-in: 2' --rule 'brace-style: [2, 1tbs]'
-    eslint --rule 'jquery/dollar-sign: 2'
+    eslint --rule 'quotes: [error, double]'
+    eslint --rule 'guard-for-in: error' --rule 'brace-style: [error, 1tbs]'
+    eslint --rule 'jquery/dollar-sign: error'
 
 ### Fixing problems
 
diff --git a/docs/user-guide/configuring/configuration-files.md b/docs/user-guide/configuring/configuration-files.md
index e900f4e7d..091d043b9 100644
--- a/docs/user-guide/configuring/configuration-files.md
+++ b/docs/user-guide/configuring/configuration-files.md
@@ -6,7 +6,7 @@
 * [Cascading and Hierarchy](#cascading-and-hierarchy)
 * [Extending Configuration Files](#extending-configuration-files)
 * [Configuration Based on Glob Patterns](#configuration-based-on-glob-patterns)
-* [Personal Configuration Files](#personal-configuration-files)
+* [Personal Configuration Files](#personal-configuration-files-deprecated)
 
 ## Configuration File Formats
 
diff --git a/lib/rules/key-spacing.js b/lib/rules/key-spacing.js
index cb176dd00..c09cebb51 100644
--- a/lib/rules/key-spacing.js
+++ b/lib/rules/key-spacing.js
@@ -427,19 +427,7 @@ module.exports = {
          * @returns {void}
          */
         function report(property, side, whitespace, expected, mode) {
-            const diff = whitespace.length - expected,
-                nextColon = getNextColon(property.key),
-                tokenBeforeColon = sourceCode.getTokenBefore(nextColon, { includeComments: true }),
-                tokenAfterColon = sourceCode.getTokenAfter(nextColon, { includeComments: true }),
-                isKeySide = side === "key",
-                isExtra = diff > 0,
-                diffAbs = Math.abs(diff),
-                spaces = Array(diffAbs + 1).join(" ");
-
-            const locStart = isKeySide ? tokenBeforeColon.loc.end : nextColon.loc.start;
-            const locEnd = isKeySide ? nextColon.loc.start : tokenAfterColon.loc.start;
-            const missingLoc = isKeySide ? tokenBeforeColon.loc : tokenAfterColon.loc;
-            const loc = isExtra ? { start: locStart, end: locEnd } : missingLoc;
+            const diff = whitespace.length - expected;
 
             if ((
                 diff && mode === "strict" ||
@@ -447,6 +435,19 @@ module.exports = {
                 diff > 0 && !expected && mode === "minimum") &&
                 !(expected && containsLineTerminator(whitespace))
             ) {
+                const nextColon = getNextColon(property.key),
+                    tokenBeforeColon = sourceCode.getTokenBefore(nextColon, { includeComments: true }),
+                    tokenAfterColon = sourceCode.getTokenAfter(nextColon, { includeComments: true }),
+                    isKeySide = side === "key",
+                    isExtra = diff > 0,
+                    diffAbs = Math.abs(diff),
+                    spaces = Array(diffAbs + 1).join(" ");
+
+                const locStart = isKeySide ? tokenBeforeColon.loc.end : nextColon.loc.start;
+                const locEnd = isKeySide ? nextColon.loc.start : tokenAfterColon.loc.start;
+                const missingLoc = isKeySide ? tokenBeforeColon.loc : tokenAfterColon.loc;
+                const loc = isExtra ? { start: locStart, end: locEnd } : missingLoc;
+
                 let fix;
 
                 if (isExtra) {
diff --git a/lib/rules/no-use-before-define.js b/lib/rules/no-use-before-define.js
index fd8864e5f..80ce3513a 100644
--- a/lib/rules/no-use-before-define.js
+++ b/lib/rules/no-use-before-define.js
@@ -34,52 +34,91 @@ function parseOptions(options) {
 }
 
 /**
- * Checks whether or not a given variable is a function declaration.
- * @param {eslint-scope.Variable} variable A variable to check.
- * @returns {boolean} `true` if the variable is a function declaration.
+ * Checks whether or not a given location is inside of the range of a given node.
+ * @param {ASTNode} node An node to check.
+ * @param {number} location A location to check.
+ * @returns {boolean} `true` if the location is inside of the range of the node.
  */
-function isFunction(variable) {
-    return variable.defs[0].type === "FunctionName";
+function isInRange(node, location) {
+    return node && node.range[0] <= location && location <= node.range[1];
 }
 
 /**
- * Checks whether or not a given variable is a class declaration in an upper function scope.
- * @param {eslint-scope.Variable} variable A variable to check.
- * @param {eslint-scope.Reference} reference A reference to check.
- * @returns {boolean} `true` if the variable is a class declaration.
+ * Checks whether or not a given location is inside of the range of a class static initializer.
+ * @param {ASTNode} node `ClassBody` node to check static initializers.
+ * @param {number} location A location to check.
+ * @returns {boolean} `true` if the location is inside of a class static initializer.
  */
-function isOuterClass(variable, reference) {
-    return (
-        variable.defs[0].type === "ClassName" &&
-        variable.scope.variableScope !== reference.from.variableScope
-    );
+function isInClassStaticInitializerRange(node, location) {
+    return node.body.some(classMember => (
+        classMember.type === "PropertyDefinition" &&
+        classMember.static &&
+        classMember.value &&
+        isInRange(classMember.value, location)
+    ));
 }
 
 /**
- * Checks whether or not a given variable is a variable declaration in an upper function scope.
- * @param {eslint-scope.Variable} variable A variable to check.
- * @param {eslint-scope.Reference} reference A reference to check.
- * @returns {boolean} `true` if the variable is a variable declaration.
+ * Checks whether a given scope is the scope of a static class field initializer.
+ * @param {eslint-scope.Scope} scope A scope to check.
+ * @returns {boolean} `true` if the scope is a class static initializer scope.
  */
-function isOuterVariable(variable, reference) {
-    return (
-        variable.defs[0].type === "Variable" &&
-        variable.scope.variableScope !== reference.from.variableScope
-    );
+function isClassStaticInitializerScope(scope) {
+    if (scope.type === "class-field-initializer") {
+
+        // `scope.block` is PropertyDefinition#value node
+        const propertyDefinition = scope.block.parent;
+
+        return propertyDefinition.static;
+    }
+
+    return false;
 }
 
 /**
- * Checks whether or not a given location is inside of the range of a given node.
- * @param {ASTNode} node An node to check.
- * @param {number} location A location to check.
- * @returns {boolean} `true` if the location is inside of the range of the node.
+ * Checks whether a given reference is evaluated in an execution context
+ * that isn't the one where the variable it refers to is defined.
+ * Execution contexts are:
+ * - top-level
+ * - functions
+ * - class field initializers (implicit functions)
+ * Static class field initializers are automatically run during the class definition evaluation,
+ * and therefore we'll consider them as a part of the parent execution context.
+ * Example:
+ *
+ *   const x = 1;
+ *
+ *   x; // returns `false`
+ *   () => x; // returns `true`
+ *   class C {
+ *       field = x; // returns `true`
+ *       static field = x; // returns `false`
+ *
+ *       method() {
+ *           x; // returns `true`
+ *       }
+ *   }
+ * @param {eslint-scope.Reference} reference A reference to check.
+ * @returns {boolean} `true` if the reference is from a separate execution context.
  */
-function isInRange(node, location) {
-    return node && node.range[0] <= location && location <= node.range[1];
+function isFromSeparateExecutionContext(reference) {
+    const variable = reference.resolved;
+    let scope = reference.from;
+
+    // Scope#variableScope represents execution context
+    while (variable.scope.variableScope !== scope.variableScope) {
+        if (isClassStaticInitializerScope(scope.variableScope)) {
+            scope = scope.variableScope.upper;
+        } else {
+            return true;
+        }
+    }
+
+    return false;
 }
 
 /**
- * Checks whether or not a given reference is inside of the initializers of a given variable.
+ * Checks whether or not a given reference is evaluated during the initialization of its variable.
  *
  * This returns `true` in the following cases:
  *
@@ -88,17 +127,44 @@ function isInRange(node, location) {
  *     var {a = a} = obj
  *     for (var a in a) {}
  *     for (var a of a) {}
- * @param {Variable} variable A variable to check.
+ *     var C = class { [C]; }
+ *     var C = class { static foo = C; }
+ *     class C extends C {}
+ *     class C extends (class { static foo = C; }) {}
+ *     class C { [C]; }
  * @param {Reference} reference A reference to check.
- * @returns {boolean} `true` if the reference is inside of the initializers.
+ * @returns {boolean} `true` if the reference is evaluated during the initialization.
  */
-function isInInitializer(variable, reference) {
-    if (variable.scope !== reference.from) {
+function isEvaluatedDuringInitialization(reference) {
+    if (isFromSeparateExecutionContext(reference)) {
+
+        /*
+         * Even if the reference appears in the initializer, it isn't evaluated during the initialization.
+         * For example, `const x = () => x;` is valid.
+         */
         return false;
     }
 
-    let node = variable.identifiers[0].parent;
     const location = reference.identifier.range[1];
+    const definition = reference.resolved.defs[0];
+
+    if (definition.type === "ClassName") {
+
+        // `ClassDeclaration` or `ClassExpression`
+        const classDefinition = definition.node;
+
+        return (
+            isInRange(classDefinition, location) &&
+
+            /*
+             * Class binding is initialized before running static initializers.
+             * For example, `class C { static foo = C; }` is valid.
+             */
+            !isInClassStaticInitializerRange(classDefinition.body, location)
+        );
+    }
+
+    let node = definition.name.parent;
 
     while (node) {
         if (node.type === "VariableDeclarator") {
@@ -167,65 +233,77 @@ module.exports = {
         const options = parseOptions(context.options[0]);
 
         /**
-         * Determines whether a given use-before-define case should be reported according to the options.
-         * @param {eslint-scope.Variable} variable The variable that gets used before being defined
-         * @param {eslint-scope.Reference} reference The reference to the variable
-         * @returns {boolean} `true` if the usage should be reported
+         * Determines whether a given reference should be checked.
+         *
+         * Returns `false` if the reference is:
+         * - initialization's (e.g., `let a = 1`).
+         * - referring to an undefined variable (i.e., if it's an unresolved reference).
+         * - referring to a variable that is defined, but not in the given source code
+         *   (e.g., global environment variable or `arguments` in functions).
+         * - allowed by options.
+         * @param {eslint-scope.Reference} reference The reference
+         * @returns {boolean} `true` if the reference should be checked
          */
-        function isForbidden(variable, reference) {
-            if (isFunction(variable)) {
-                return options.functions;
+        function shouldCheck(reference) {
+            if (reference.init) {
+                return false;
+            }
+
+            const variable = reference.resolved;
+
+            if (!variable || variable.defs.length === 0) {
+                return false;
             }
-            if (isOuterClass(variable, reference)) {
-                return options.classes;
+
+            const definitionType = variable.defs[0].type;
+
+            if (!options.functions && definitionType === "FunctionName") {
+                return false;
             }
-            if (isOuterVariable(variable, reference)) {
-                return options.variables;
+
+            if (
+                (
+                    !options.variables && definitionType === "Variable" ||
+                    !options.classes && definitionType === "ClassName"
+                ) &&
+
+                // don't skip checking the reference if it's in the same execution context, because of TDZ
+                isFromSeparateExecutionContext(reference)
+            ) {
+                return false;
             }
+
             return true;
         }
 
         /**
-         * Finds and validates all variables in a given scope.
-         * @param {Scope} scope The scope object.
+         * Finds and validates all references in a given scope and its child scopes.
+         * @param {eslint-scope.Scope} scope The scope object.
          * @returns {void}
-         * @private
          */
-        function findVariablesInScope(scope) {
-            scope.references.forEach(reference => {
+        function checkReferencesInScope(scope) {
+            scope.references.filter(shouldCheck).forEach(reference => {
                 const variable = reference.resolved;
+                const definitionIdentifier = variable.defs[0].name;
 
-                /*
-                 * Skips when the reference is:
-                 * - initialization's.
-                 * - referring to an undefined variable.
-                 * - referring to a global environment variable (there're no identifiers).
-                 * - located preceded by the variable (except in initializers).
-                 * - allowed by options.
-                 */
-                if (reference.init ||
-                    !variable ||
-                    variable.identifiers.length === 0 ||
-                    (variable.identifiers[0].range[1] < reference.identifier.range[1] && !isInInitializer(variable, reference)) ||
-                    !isForbidden(variable, reference)
+                if (
+                    reference.identifier.range[1] < definitionIdentifier.range[1] ||
+                    isEvaluatedDuringInitialization(reference)
                 ) {
-                    return;
+                    context.report({
+                        node: reference.identifier,
+                        messageId: "usedBeforeDefined",
+                        data: reference.identifier
+                    });
                 }
-
-                // Reports.
-                context.report({
-                    node: reference.identifier,
-                    messageId: "usedBeforeDefined",
-                    data: reference.identifier
-                });
             });
 
-            scope.childScopes.forEach(findVariablesInScope);
+            scope.childScopes.forEach(checkReferencesInScope);
         }
 
         return {
             Program() {
-                findVariablesInScope(context.getScope());
+                checkReferencesInScope(context.getScope());
             }
         };
     }
diff --git a/package.json b/package.json
index 095d7c52e..1a394f74c 100644
--- a/package.json
+++ b/package.json
@@ -81,7 +81,7 @@
     "progress": "^2.0.0",
     "regexpp": "^3.2.0",
     "semver": "^7.2.1",
-    "strip-ansi": "^6.0.0",
+    "strip-ansi": "^6.0.1",
     "strip-json-comments": "^3.1.0",
     "text-table": "^0.2.0",
     "v8-compile-cache": "^2.0.3"
@@ -103,7 +103,7 @@
     "eslint-plugin-internal-rules": "file:tools/internal-rules",
     "eslint-plugin-jsdoc": "^37.0.0",
     "eslint-plugin-node": "^11.1.0",
-    "eslint-release": "^3.1.2",
+    "eslint-release": "^3.2.0",
     "eslump": "^3.0.0",
     "esprima": "^4.0.1",
     "fs-teardown": "^0.1.3",


Test patch:
diff --git a/tests/lib/rules/no-use-before-define.js b/tests/lib/rules/no-use-before-define.js
index 152fa991b..f9baf54ed 100644
--- a/tests/lib/rules/no-use-before-define.js
+++ b/tests/lib/rules/no-use-before-define.js
@@ -20,6 +20,9 @@ const ruleTester = new RuleTester();
 
 ruleTester.run("no-use-before-define", rule, {
     valid: [
+        "unresolved",
+        "Array",
+        "function foo () { arguments; }",
         "var a=10; alert(a);",
         "function b(a) { alert(a); }",
         "Object.hasOwnProperty.call(a);",
@@ -56,6 +59,103 @@ ruleTester.run("no-use-before-define", rule, {
             code: "var foo = () => bar; var bar;",
             options: [{ variables: false }],
             parserOptions: { ecmaVersion: 6 }
+        },
+
+        // Tests related to class definition evaluation. These are not TDZ errors.
+        { code: "class C extends (class { method() { C; } }) {}", parserOptions: { ecmaVersion: 6 } },
+        { code: "(class extends (class { method() { C; } }) {});", parserOptions: { ecmaVersion: 6 } },
+        { code: "const C = (class extends (class { method() { C; } }) {});", parserOptions: { ecmaVersion: 6 } },
+        { code: "class C extends (class { field = C; }) {}", parserOptions: { ecmaVersion: 2022 } },
+        { code: "(class extends (class { field = C; }) {});", parserOptions: { ecmaVersion: 2022 } },
+        { code: "const C = (class extends (class { field = C; }) {});", parserOptions: { ecmaVersion: 2022 } },
+        { code: "class C { [() => C](){} }", parserOptions: { ecmaVersion: 6 } },
+        { code: "(class C { [() => C](){} });", parserOptions: { ecmaVersion: 6 } },
+        { code: "const C = class { [() => C](){} };", parserOptions: { ecmaVersion: 6 } },
+        { code: "class C { static [() => C](){} }", parserOptions: { ecmaVersion: 6 } },
+        { code: "(class C { static [() => C](){} });", parserOptions: { ecmaVersion: 6 } },
+        { code: "const C = class { static [() => C](){} };", parserOptions: { ecmaVersion: 6 } },
+        { code: "class C { [() => C]; }", parserOptions: { ecmaVersion: 2022 } },
+        { code: "(class C { [() => C]; });", parserOptions: { ecmaVersion: 2022 } },
+        { code: "const C = class { [() => C]; };", parserOptions: { ecmaVersion: 2022 } },
+        { code: "class C { static [() => C]; }", parserOptions: { ecmaVersion: 2022 } },
+        { code: "(class C { static [() => C]; });", parserOptions: { ecmaVersion: 2022 } },
+        { code: "const C = class { static [() => C]; };", parserOptions: { ecmaVersion: 2022 } },
+        { code: "class C { method() { C; } }", parserOptions: { ecmaVersion: 6 } },
+        { code: "(class C { method() { C; } });", parserOptions: { ecmaVersion: 6 } },
+        { code: "const C = class { method() { C; } };", parserOptions: { ecmaVersion: 6 } },
+        { code: "class C { static method() { C; } }", parserOptions: { ecmaVersion: 6 } },
+        { code: "(class C { static method() { C; } });", parserOptions: { ecmaVersion: 6 } },
+        { code: "const C = class { static method() { C; } };", parserOptions: { ecmaVersion: 6 } },
+        { code: "class C { field = C; }", parserOptions: { ecmaVersion: 2022 } },
+        { code: "(class C { field = C; });", parserOptions: { ecmaVersion: 2022 } },
+        { code: "const C = class { field = C; };", parserOptions: { ecmaVersion: 2022 } },
+        { code: "class C { static field = C; }", parserOptions: { ecmaVersion: 2022 } },
+        { code: "(class C { static field = C; });", parserOptions: { ecmaVersion: 2022 } }, // `const C = class { static field = C; };` is TDZ error
+        { code: "class C { static field = class { static field = C; }; }", parserOptions: { ecmaVersion: 2022 } },
+        { code: "(class C { static field = class { static field = C; }; });", parserOptions: { ecmaVersion: 2022 } },
+        { code: "class C { field = () => C; }", parserOptions: { ecmaVersion: 2022 } },
+        { code: "(class C { field = () => C; });", parserOptions: { ecmaVersion: 2022 } },
+        { code: "const C = class { field = () => C; };", parserOptions: { ecmaVersion: 2022 } },
+        { code: "class C { static field = () => C; }", parserOptions: { ecmaVersion: 2022 } },
+        { code: "(class C { static field = () => C; });", parserOptions: { ecmaVersion: 2022 } },
+        { code: "const C = class { static field = () => C; };", parserOptions: { ecmaVersion: 2022 } },
+        { code: "class C { field = class extends C {}; }", parserOptions: { ecmaVersion: 2022 } },
+        { code: "(class C { field = class extends C {}; });", parserOptions: { ecmaVersion: 2022 } },
+        { code: "const C = class { field = class extends C {}; }", parserOptions: { ecmaVersion: 2022 } },
+        { code: "class C { static field = class extends C {}; }", parserOptions: { ecmaVersion: 2022 } },
+        { code: "(class C { static field = class extends C {}; });", parserOptions: { ecmaVersion: 2022 } }, // `const C = class { static field = class extends C {}; };` is TDZ error
+        { code: "class C { static field = class { [C]; }; }", parserOptions: { ecmaVersion: 2022 } },
+        { code: "(class C { static field = class { [C]; }; });", parserOptions: { ecmaVersion: 2022 } }, // `const C = class { static field = class { [C]; } };` is TDZ error
+        { code: "const C = class { static field = class { field = C; }; };", parserOptions: { ecmaVersion: 2022 } },
+        {
+            code: "class C { method() { a; } } let a;",
+            options: [{ variables: false }],
+            parserOptions: { ecmaVersion: 6 }
+        },
+        {
+            code: "class C { static method() { a; } } let a;",
+            options: [{ variables: false }],
+            parserOptions: { ecmaVersion: 6 }
+        },
+        {
+            code: "class C { field = a; } let a;", // `class C { static field = a; } let a;` is TDZ error
+            options: [{ variables: false }],
+            parserOptions: { ecmaVersion: 2022 }
+        },
+        {
+            code: "class C { field = D; } class D {}", // `class C { static field = D; } class D {}` is TDZ error
+            options: [{ classes: false }],
+            parserOptions: { ecmaVersion: 2022 }
+        },
+        {
+            code: "class C { field = class extends D {}; } class D {}", // `class C { static field = class extends D {}; } class D {}` is TDZ error
+            options: [{ classes: false }],
+            parserOptions: { ecmaVersion: 2022 }
+        },
+        {
+            code: "class C { field = () => a; } let a;",
+            options: [{ variables: false }],
+            parserOptions: { ecmaVersion: 2022 }
+        },
+        {
+            code: "class C { static field = () => a; } let a;",
+            options: [{ variables: false }],
+            parserOptions: { ecmaVersion: 2022 }
+        },
+        {
+            code: "class C { field = () => D; } class D {}",
+            options: [{ classes: false }],
+            parserOptions: { ecmaVersion: 2022 }
+        },
+        {
+            code: "class C { static field = () => D; } class D {}",
+            options: [{ classes: false }],
+            parserOptions: { ecmaVersion: 2022 }
+        },
+        {
+            code: "class C { static field = class { field = a; }; } let a;",
+            options: [{ variables: false }],
+            parserOptions: { ecmaVersion: 2022 }
         }
     ],
     invalid: [
@@ -495,6 +595,417 @@ ruleTester.run("no-use-before-define", rule, {
                 messageId: "usedBeforeDefined",
                 data: { name: "x" }
             }]
+        },
+
+        // Tests related to class definition evaluation. These are TDZ errors.
+        {
+            code: "class C extends C {}",
+            options: [{ classes: false }],
+            parserOptions: { ecmaVersion: 6 },
+            errors: [{
+                messageId: "usedBeforeDefined",
+                data: { name: "C" }
+            }]
+        },
+        {
+            code: "const C = class extends C {};",
+            options: [{ variables: false }],
+            parserOptions: { ecmaVersion: 6 },
+            errors: [{
+                messageId: "usedBeforeDefined",
+                data: { name: "C" }
+            }]
+        },
+        {
+            code: "class C extends (class { [C](){} }) {}",
+            options: [{ classes: false }],
+            parserOptions: { ecmaVersion: 6 },
+            errors: [{
+                messageId: "usedBeforeDefined",
+                data: { name: "C" }
+            }]
+        },
+        {
+            code: "const C = class extends (class { [C](){} }) {};",
+            options: [{ variables: false }],
+            parserOptions: { ecmaVersion: 6 },
+            errors: [{
+                messageId: "usedBeforeDefined",
+                data: { name: "C" }
+            }]
+        },
+        {
+            code: "class C extends (class { static field = C; }) {}",
+            options: [{ classes: false }],
+            parserOptions: { ecmaVersion: 2022 },
+            errors: [{
+                messageId: "usedBeforeDefined",
+                data: { name: "C" }
+            }]
+        },
+        {
+            code: "const C = class extends (class { static field = C; }) {};",
+            options: [{ variables: false }],
+            parserOptions: { ecmaVersion: 2022 },
+            errors: [{
+                messageId: "usedBeforeDefined",
+                data: { name: "C" }
+            }]
+        },
+        {
+            code: "class C { [C](){} }",
+            options: [{ classes: false }],
+            parserOptions: { ecmaVersion: 6 },
+            errors: [{
+                messageId: "usedBeforeDefined",
+                data: { name: "C" }
+            }]
+        },
+        {
+            code: "(class C { [C](){} });",
+            options: [{ classes: false }],
+            parserOptions: { ecmaVersion: 6 },
+            errors: [{
+                messageId: "usedBeforeDefined",
+                data: { name: "C" }
+            }]
+        },
+        {
+            code: "const C = class { [C](){} };",
+            options: [{ variables: false }],
+            parserOptions: { ecmaVersion: 6 },
+            errors: [{
+                messageId: "usedBeforeDefined",
+                data: { name: "C" }
+            }]
+        },
+        {
+            code: "class C { static [C](){} }",
+            options: [{ classes: false }],
+            parserOptions: { ecmaVersion: 6 },
+            errors: [{
+                messageId: "usedBeforeDefined",
+                data: { name: "C" }
+            }]
+        },
+        {
+            code: "(class C { static [C](){} });",
+            options: [{ classes: false }],
+            parserOptions: { ecmaVersion: 6 },
+            errors: [{
+                messageId: "usedBeforeDefined",
+                data: { name: "C" }
+            }]
+        },
+        {
+            code: "const C = class { static [C](){} };",
+            options: [{ variables: false }],
+            parserOptions: { ecmaVersion: 6 },
+            errors: [{
+                messageId: "usedBeforeDefined",
+                data: { name: "C" }
+            }]
+        },
+        {
+            code: "class C { [C]; }",
+            options: [{ classes: false }],
+            parserOptions: { ecmaVersion: 2022 },
+            errors: [{
+                messageId: "usedBeforeDefined",
+                data: { name: "C" }
+            }]
+        },
+        {
+            code: "(class C { [C]; });",
+            options: [{ classes: false }],
+            parserOptions: { ecmaVersion: 2022 },
+            errors: [{
+                messageId: "usedBeforeDefined",
+                data: { name: "C" }
+            }]
+        },
+        {
+            code: "const C = class { [C]; };",
+            options: [{ variables: false }],
+            parserOptions: { ecmaVersion: 2022 },
+            errors: [{
+                messageId: "usedBeforeDefined",
+                data: { name: "C" }
+            }]
+        },
+        {
+            code: "class C { [C] = foo; }",
+            options: [{ classes: false }],
+            parserOptions: { ecmaVersion: 2022 },
+            errors: [{
+                messageId: "usedBeforeDefined",
+                data: { name: "C" }
+            }]
+        },
+        {
+            code: "(class C { [C] = foo; });",
+            options: [{ classes: false }],
+            parserOptions: { ecmaVersion: 2022 },
+            errors: [{
+                messageId: "usedBeforeDefined",
+                data: { name: "C" }
+            }]
+        },
+        {
+            code: "const C = class { [C] = foo; };",
+            options: [{ variables: false }],
+            parserOptions: { ecmaVersion: 2022 },
+            errors: [{
+                messageId: "usedBeforeDefined",
+                data: { name: "C" }
+            }]
+        },
+        {
+            code: "class C { static [C]; }",
+            options: [{ classes: false }],
+            parserOptions: { ecmaVersion: 2022 },
+            errors: [{
+                messageId: "usedBeforeDefined",
+                data: { name: "C" }
+            }]
+        },
+        {
+            code: "(class C { static [C]; });",
+            options: [{ classes: false }],
+            parserOptions: { ecmaVersion: 2022 },
+            errors: [{
+                messageId: "usedBeforeDefined",
+                data: { name: "C" }
+            }]
+        },
+        {
+            code: "const C = class { static [C]; };",
+            options: [{ variables: false }],
+            parserOptions: { ecmaVersion: 2022 },
+            errors: [{
+                messageId: "usedBeforeDefined",
+                data: { name: "C" }
+            }]
+        },
+        {
+            code: "class C { static [C] = foo; }",
+            options: [{ classes: false }],
+            parserOptions: { ecmaVersion: 2022 },
+            errors: [{
+                messageId: "usedBeforeDefined",
+                data: { name: "C" }
+            }]
+        },
+        {
+            code: "(class C { static [C] = foo; });",
+            options: [{ classes: false }],
+            parserOptions: { ecmaVersion: 2022 },
+            errors: [{
+                messageId: "usedBeforeDefined",
+                data: { name: "C" }
+            }]
+        },
+        {
+            code: "const C = class { static [C] = foo; };",
+            options: [{ variables: false }],
+            parserOptions: { ecmaVersion: 2022 },
+            errors: [{
+                messageId: "usedBeforeDefined",
+                data: { name: "C" }
+            }]
+        },
+        {
+            code: "const C = class { static field = C; };",
+            options: [{ variables: false }],
+            parserOptions: { ecmaVersion: 2022 },
+            errors: [{
+                messageId: "usedBeforeDefined",
+                data: { name: "C" }
+            }]
+        },
+        {
+            code: "const C = class { static field = class extends C {}; };",
+            options: [{ variables: false }],
+            parserOptions: { ecmaVersion: 2022 },
+            errors: [{
+                messageId: "usedBeforeDefined",
+                data: { name: "C" }
+            }]
+        },
+        {
+            code: "const C = class { static field = class { [C]; } };",
+            options: [{ variables: false }],
+            parserOptions: { ecmaVersion: 2022 },
+            errors: [{
+                messageId: "usedBeforeDefined",
+                data: { name: "C" }
+            }]
+        },
+        {
+            code: "const C = class { static field = class { static field = C; }; };",
+            options: [{ variables: false }],
+            parserOptions: { ecmaVersion: 2022 },
+            errors: [{
+                messageId: "usedBeforeDefined",
+                data: { name: "C" }
+            }]
+        },
+        {
+            code: "class C extends D {} class D {}",
+            options: [{ classes: false }],
+            parserOptions: { ecmaVersion: 6 },
+            errors: [{
+                messageId: "usedBeforeDefined",
+                data: { name: "D" }
+            }]
+        },
+        {
+            code: "class C extends (class { [a](){} }) {} let a;",
+            options: [{ variables: false }],
+            parserOptions: { ecmaVersion: 6 },
+            errors: [{
+                messageId: "usedBeforeDefined",
+                data: { name: "a" }
+            }]
+        },
+        {
+            code: "class C extends (class { static field = a; }) {} let a;",
+            options: [{ variables: false }],
+            parserOptions: { ecmaVersion: 2022 },
+            errors: [{
+                messageId: "usedBeforeDefined",
+                data: { name: "a" }
+            }]
+        },
+        {
+            code: "class C { [a]() {} } let a;",
+            options: [{ variables: false }],
+            parserOptions: { ecmaVersion: 6 },
+            errors: [{
+                messageId: "usedBeforeDefined",
+                data: { name: "a" }
+            }]
+        },
+        {
+            code: "class C { static [a]() {} } let a;",
+            options: [{ variables: false }],
+            parserOptions: { ecmaVersion: 6 },
+            errors: [{
+                messageId: "usedBeforeDefined",
+                data: { name: "a" }
+            }]
+        },
+        {
+            code: "class C { [a]; } let a;",
+            options: [{ variables: false }],
+            parserOptions: { ecmaVersion: 2022 },
+            errors: [{
+                messageId: "usedBeforeDefined",
+                data: { name: "a" }
+            }]
+        },
+        {
+            code: "class C { static [a]; } let a;",
+            options: [{ variables: false }],
+            parserOptions: { ecmaVersion: 2022 },
+            errors: [{
+                messageId: "usedBeforeDefined",
+                data: { name: "a" }
+            }]
+        },
+        {
+            code: "class C { [a] = foo; } let a;",
+            options: [{ variables: false }],
+            parserOptions: { ecmaVersion: 2022 },
+            errors: [{
+                messageId: "usedBeforeDefined",
+                data: { name: "a" }
+            }]
+        },
+        {
+            code: "class C { static [a] = foo; } let a;",
+            options: [{ variables: false }],
+            parserOptions: { ecmaVersion: 2022 },
+            errors: [{
+                messageId: "usedBeforeDefined",
+                data: { name: "a" }
+            }]
+        },
+        {
+            code: "class C { static field = a; } let a;",
+            options: [{ variables: false }],
+            parserOptions: { ecmaVersion: 2022 },
+            errors: [{
+                messageId: "usedBeforeDefined",
+                data: { name: "a" }
+            }]
+        },
+        {
+            code: "class C { static field = D; } class D {}",
+            options: [{ classes: false }],
+            parserOptions: { ecmaVersion: 2022 },
+            errors: [{
+                messageId: "usedBeforeDefined",
+                data: { name: "D" }
+            }]
+        },
+        {
+            code: "class C { static field = class extends D {}; } class D {}",
+            options: [{ classes: false }],
+            parserOptions: { ecmaVersion: 2022 },
+            errors: [{
+                messageId: "usedBeforeDefined",
+                data: { name: "D" }
+            }]
+        },
+        {
+            code: "class C { static field = class { [a](){} } } let a;",
+            options: [{ variables: false }],
+            parserOptions: { ecmaVersion: 2022 },
+            errors: [{
+                messageId: "usedBeforeDefined",
+                data: { name: "a" }
+            }]
+        },
+        {
+            code: "class C { static field = class { static field = a; }; } let a;",
+            options: [{ variables: false }],
+            parserOptions: { ecmaVersion: 2022 },
+            errors: [{
+                messageId: "usedBeforeDefined",
+                data: { name: "a" }
+            }]
         }
+
+        /*
+         * TODO(mdjermanovic): Add the following test cases once https://github.com/eslint/eslint-scope/issues/59 gets fixed:
+         * {
+         *  code: "(class C extends C {});",
+         *  options: [{ classes: false }],
+         *  parserOptions: { ecmaVersion: 6 },
+         *  errors: [{
+         *      messageId: "usedBeforeDefined",
+         *      data: { name: "C" }
+         *  }]
+         * },
+         * {
+         *  code: "(class C extends (class { [C](){} }) {});",
+         *  options: [{ classes: false }],
+         *  parserOptions: { ecmaVersion: 6 },
+         *  errors: [{
+         *      messageId: "usedBeforeDefined",
+         *      data: { name: "C" }
+         *  }]
+         * },
+         * {
+         *  code: "(class C extends (class { static field = C; }) {});",
+         *  options: [{ classes: false }],
+         *  parserOptions: { ecmaVersion: 2022 },
+         *  errors: [{
+         *      messageId: "usedBeforeDefined",
+         *      data: { name: "C" }
+         *  }]
+         * }
+         */
     ]
 });