diff --git a/docs/rules/no-use-before-define.md b/docs/rules/no-use-before-define.md index 09f4e5d6df9..8af260ea0ad 100644 --- a/docs/rules/no-use-before-define.md +++ b/docs/rules/no-use-before-define.md @@ -45,6 +45,14 @@ var b = 1; static x = C; } } + +{ + const C = class { + static { + C.x = "foo"; + } + } +} ``` Examples of **correct** code for this rule: @@ -86,6 +94,14 @@ function g() { x = C; } } + +{ + const C = class C { + static { + C.x = "foo"; + } + } +} ``` ## Options @@ -156,6 +172,15 @@ class A { [C.x]() {} } } + +{ + class C { + static { + new D(); + } + } + class D {} +} ``` Examples of **correct** code for the `{ "classes": false }` option: @@ -199,6 +224,15 @@ const g = function() {}; } const foo = 1; } + +{ + class C { + static { + this.x = foo; + } + } + const foo = 1; +} ``` Examples of **correct** code for the `{ "variables": false }` option: diff --git a/lib/rules/no-use-before-define.js b/lib/rules/no-use-before-define.js index 80ce3513af7..7f904f46bd5 100644 --- a/lib/rules/no-use-before-define.js +++ b/lib/rules/no-use-before-define.js @@ -45,25 +45,37 @@ function isInRange(node, location) { /** * Checks whether or not a given location is inside of the range of a class static initializer. + * Static initializers are static blocks and initializers of static fields. * @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 isInClassStaticInitializerRange(node, location) { return node.body.some(classMember => ( - classMember.type === "PropertyDefinition" && - classMember.static && - classMember.value && - isInRange(classMember.value, location) + ( + classMember.type === "StaticBlock" && + isInRange(classMember, location) + ) || + ( + classMember.type === "PropertyDefinition" && + classMember.static && + classMember.value && + isInRange(classMember.value, location) + ) )); } /** - * Checks whether a given scope is the scope of a static class field initializer. + * Checks whether a given scope is the scope of a a class static initializer. + * Static initializers are static blocks and initializers of static fields. * @param {eslint-scope.Scope} scope A scope to check. * @returns {boolean} `true` if the scope is a class static initializer scope. */ function isClassStaticInitializerScope(scope) { + if (scope.type === "class-static-block") { + return true; + } + if (scope.type === "class-field-initializer") { // `scope.block` is PropertyDefinition#value node @@ -82,7 +94,8 @@ function isClassStaticInitializerScope(scope) { * - top-level * - functions * - class field initializers (implicit functions) - * Static class field initializers are automatically run during the class definition evaluation, + * - class static blocks (implicit functions) + * Static class field initializers and class static blocks are automatically run during the class definition evaluation, * and therefore we'll consider them as a part of the parent execution context. * Example: * @@ -90,6 +103,7 @@ function isClassStaticInitializerScope(scope) { * * x; // returns `false` * () => x; // returns `true` + * * class C { * field = x; // returns `true` * static field = x; // returns `false` @@ -97,6 +111,14 @@ function isClassStaticInitializerScope(scope) { * method() { * x; // returns `true` * } + * + * static method() { + * x; // returns `true` + * } + * + * static { + * x; // returns `false` + * } * } * @param {eslint-scope.Reference} reference A reference to check. * @returns {boolean} `true` if the reference is from a separate execution context. @@ -127,8 +149,9 @@ function isFromSeparateExecutionContext(reference) { * var {a = a} = obj * for (var a in a) {} * for (var a of a) {} - * var C = class { [C]; } - * var C = class { static foo = C; } + * var C = class { [C]; }; + * var C = class { static foo = C; }; + * var C = class { static { foo = C; } }; * class C extends C {} * class C extends (class { static foo = C; }) {} * class C { [C]; } @@ -158,7 +181,7 @@ function isEvaluatedDuringInitialization(reference) { /* * Class binding is initialized before running static initializers. - * For example, `class C { static foo = C; }` is valid. + * For example, `class C { static foo = C; static { bar = C; } }` is valid. */ !isInClassStaticInitializerRange(classDefinition.body, location) ); diff --git a/tests/lib/rules/no-use-before-define.js b/tests/lib/rules/no-use-before-define.js index f9baf54edf1..ba80e4075c2 100644 --- a/tests/lib/rules/no-use-before-define.js +++ b/tests/lib/rules/no-use-before-define.js @@ -38,6 +38,8 @@ ruleTester.run("no-use-before-define", rule, { "var foo = function() { foo(); };", "var a; for (a in a) {}", { code: "var a; for (a of a) {}", parserOptions: { ecmaVersion: 6 } }, + { code: "let a; class C { static { a; } }", parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { static { let a; a; } }", parserOptions: { ecmaVersion: 2022 } }, // Block-level bindings { code: "\"use strict\"; a(); { function a() {} }", parserOptions: { ecmaVersion: 6 } }, @@ -60,6 +62,11 @@ ruleTester.run("no-use-before-define", rule, { options: [{ variables: false }], parserOptions: { ecmaVersion: 6 } }, + { + code: "class C { static { () => foo; let foo; } }", + options: [{ variables: false }], + parserOptions: { ecmaVersion: 2022 } + }, // Tests related to class definition evaluation. These are not TDZ errors. { code: "class C extends (class { method() { C; } }) {}", parserOptions: { ecmaVersion: 6 } }, @@ -156,6 +163,52 @@ ruleTester.run("no-use-before-define", rule, { code: "class C { static field = class { field = a; }; } let a;", options: [{ variables: false }], parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { static { C; } }", // `const C = class { static { C; } }` is TDZ error + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { static { C; } static {} static { C; } }", + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "(class C { static { C; } })", + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { static { class D extends C {} } }", + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { static { (class { static { 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 { static { () => D; } } class D {}", + options: [{ classes: false }], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { static { () => a; } } let a;", + options: [{ variables: false }], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "const C = class C { static { C.x; } }", + parserOptions: { ecmaVersion: 2022 } } ], invalid: [ @@ -975,6 +1028,69 @@ ruleTester.run("no-use-before-define", rule, { messageId: "usedBeforeDefined", data: { name: "a" } }] + }, + { + code: "const C = class { static { C; } };", + options: [{ variables: false }], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "usedBeforeDefined", + data: { name: "C" } + }] + }, + { + code: "const C = class { static { (class extends C {}); } };", + options: [{ variables: false }], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "usedBeforeDefined", + data: { name: "C" } + }] + }, + { + code: "class C { static { a; } } let a;", + options: [{ variables: false }], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "usedBeforeDefined", + data: { name: "a" } + }] + }, + { + code: "class C { static { D; } } class D {}", + options: [{ classes: false }], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "usedBeforeDefined", + data: { name: "D" } + }] + }, + { + code: "class C { static { (class extends D {}); } } class D {}", + options: [{ classes: false }], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "usedBeforeDefined", + data: { name: "D" } + }] + }, + { + code: "class C { static { (class { [a](){} }); } } let a;", + options: [{ variables: false }], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "usedBeforeDefined", + data: { name: "a" } + }] + }, + { + code: "class C { static { (class { static field = a; }); } } let a;", + options: [{ variables: false }], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "usedBeforeDefined", + data: { name: "a" } + }] } /*