diff --git a/packages/eslint-plugin/src/rules/no-use-before-define.ts b/packages/eslint-plugin/src/rules/no-use-before-define.ts index 18525a138ce..b67f5ef09c3 100644 --- a/packages/eslint-plugin/src/rules/no-use-before-define.ts +++ b/packages/eslint-plugin/src/rules/no-use-before-define.ts @@ -92,6 +92,37 @@ function isOuterVariable( ); } +/** + * Recursively checks whether or not a given reference has a type query declaration among it's parents + */ +function referenceContainsTypeQuery(node: TSESTree.Node): boolean { + switch (node.type) { + case AST_NODE_TYPES.TSTypeQuery: + return true; + + case AST_NODE_TYPES.TSQualifiedName: + case AST_NODE_TYPES.Identifier: + if (!node.parent) { + return false; + } + return referenceContainsTypeQuery(node.parent); + + default: + // if we find a different node, there's no chance that we're in a TSTypeQuery + return false; + } +} + +/** + * Checks whether or not a given reference is a type reference. + */ +function isTypeReference(reference: TSESLint.Scope.Reference): boolean { + return ( + reference.isTypeReference || + referenceContainsTypeQuery(reference.identifier) + ); +} + /** * Checks whether or not a given location is inside of the range of a given node. */ @@ -219,7 +250,7 @@ export default util.createRule({ variable: TSESLint.Scope.Variable, reference: TSESLint.Scope.Reference, ): boolean { - if (reference.isTypeReference && options.ignoreTypeReferences) { + if (options.ignoreTypeReferences && isTypeReference(reference)) { return false; } if (isFunction(variable)) { diff --git a/packages/eslint-plugin/tests/rules/no-use-before-define.test.ts b/packages/eslint-plugin/tests/rules/no-use-before-define.test.ts index 23f2abb0473..77e434d3382 100644 --- a/packages/eslint-plugin/tests/rules/no-use-before-define.test.ts +++ b/packages/eslint-plugin/tests/rules/no-use-before-define.test.ts @@ -219,7 +219,43 @@ type Foo = string | number; `, options: [{ typedefs: false }], }, + // https://github.com/typescript-eslint/typescript-eslint/issues/2572 + { + code: ` +interface Bar { + type: typeof Foo; +} + +const Foo = 2; + `, + options: [{ ignoreTypeReferences: true }], + }, + { + code: ` +interface Bar { + type: typeof Foo.FOO; +} + +class Foo { + public static readonly FOO = ''; +} + `, + options: [{ ignoreTypeReferences: true }], + }, + { + code: ` +interface Bar { + type: typeof Foo.Bar.Baz; +} +const Foo = { + Bar: { + Baz: 1, + }, +}; + `, + options: [{ ignoreTypeReferences: true }], + }, // https://github.com/bradzacher/eslint-plugin-typescript/issues/141 { code: ` @@ -875,6 +911,65 @@ for (var a of a) { ], }, + // "ignoreTypeReferences" option + { + code: ` +interface Bar { + type: typeof Foo; +} + +const Foo = 2; + `, + options: [{ ignoreTypeReferences: false }], + errors: [ + { + messageId: 'noUseBeforeDefine', + data: { name: 'Foo' }, + type: AST_NODE_TYPES.Identifier, + }, + ], + }, + { + code: ` +interface Bar { + type: typeof Foo.FOO; +} + +class Foo { + public static readonly FOO = ''; +} + `, + options: [{ ignoreTypeReferences: false }], + errors: [ + { + messageId: 'noUseBeforeDefine', + data: { name: 'Foo' }, + type: AST_NODE_TYPES.Identifier, + }, + ], + }, + { + code: ` +interface Bar { + type: typeof Foo.Bar.Baz; +} + +const Foo = { + Bar: { + Baz: 1, + }, +}; + `, + options: [{ ignoreTypeReferences: false }], + errors: [ + { + messageId: 'noUseBeforeDefine', + data: { name: 'Foo' }, + type: AST_NODE_TYPES.Identifier, + }, + ], + }, + // "variables" option { code: `