diff --git a/docs/rules/no-use-before-define.md b/docs/rules/no-use-before-define.md index 391c323aed0..b4368427f7a 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/lib/rules/no-use-before-define.js b/lib/rules/no-use-before-define.js index fd8864e5fa5..80ce3513af7 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/tests/lib/rules/no-use-before-define.js b/tests/lib/rules/no-use-before-define.js index 152fa991b9f..f9baf54edf1 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" } + * }] + * } + */ ] });