diff --git a/packages/eslint-plugin/src/util/collectUnusedVariables.ts b/packages/eslint-plugin/src/util/collectUnusedVariables.ts index 983eb1472c14..734eeac690de 100644 --- a/packages/eslint-plugin/src/util/collectUnusedVariables.ts +++ b/packages/eslint-plugin/src/util/collectUnusedVariables.ts @@ -1,6 +1,7 @@ import type { ScopeManager, ScopeVariable, + Variable, } from '@typescript-eslint/scope-manager'; import type { TSESTree } from '@typescript-eslint/utils'; @@ -188,7 +189,7 @@ class UnusedVarsVisitor extends Visitor { // basic exported variables isExported(variable) || // variables implicitly exported via a merged declaration - isMergableExported(variable) || + isMergeableExported(variable) || // used variables isUsedVariable(variable) ) { @@ -415,6 +416,22 @@ function isSelfReference( return false; } +/** + * @param variable the variable to check + * @param node the node from a some def of variable + * @returns true if variable is type/value duality and declaration is type declaration + */ +function isMergedTypeDeclaration( + variable: Variable, + node: TSESTree.Node, +): boolean { + return ( + node.type === AST_NODE_TYPES.TSTypeAliasDeclaration && + variable.isTypeVariable && + variable.isValueVariable + ); +} + const MERGABLE_TYPES = new Set([ AST_NODE_TYPES.ClassDeclaration, AST_NODE_TYPES.FunctionDeclaration, @@ -426,7 +443,7 @@ const MERGABLE_TYPES = new Set([ * Determine if the variable is directly exported * @param variable the variable to check */ -function isMergableExported(variable: ScopeVariable): boolean { +function isMergeableExported(variable: Variable): boolean { // If all of the merged things are of the same type, TS will error if not all of them are exported - so we only need to find one for (const def of variable.defs) { // parameters can never be exported. @@ -441,7 +458,7 @@ function isMergableExported(variable: ScopeVariable): boolean { def.node.parent?.type === AST_NODE_TYPES.ExportNamedDeclaration) || def.node.parent?.type === AST_NODE_TYPES.ExportDefaultDeclaration ) { - return true; + return !isMergedTypeDeclaration(variable, def.node); } } @@ -453,7 +470,7 @@ function isMergableExported(variable: ScopeVariable): boolean { * @param variable eslint-scope variable object. * @returns True if the variable is exported, false if not. */ -function isExported(variable: ScopeVariable): boolean { +function isExported(variable: Variable): boolean { return variable.defs.some(definition => { let node = definition.node; @@ -465,7 +482,9 @@ function isExported(variable: ScopeVariable): boolean { } // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - return node.parent!.type.startsWith('Export'); + const isExportedFlag = node.parent!.type.startsWith('Export'); + + return isExportedFlag && !isMergedTypeDeclaration(variable, node); }); } diff --git a/packages/eslint-plugin/tests/rules/no-unused-vars/no-unused-vars.test.ts b/packages/eslint-plugin/tests/rules/no-unused-vars/no-unused-vars.test.ts index 5f40bbcdc3cc..517baaefc6aa 100644 --- a/packages/eslint-plugin/tests/rules/no-unused-vars/no-unused-vars.test.ts +++ b/packages/eslint-plugin/tests/rules/no-unused-vars/no-unused-vars.test.ts @@ -1715,6 +1715,44 @@ export {}; ], filename: 'foo.d.ts', }, + // https://github.com/typescript-eslint/typescript-eslint/issues/10658 + { + code: ` +const A = 0; +export type A = typeof A; + `, + errors: [ + { + data: { + action: 'assigned a value', + additional: '', + varName: 'A', + }, + line: 2, + messageId: 'usedOnlyAsType', + }, + ], + }, + { + code: ` +function A() {} +namespace A { + export const prop = 1; +} +export type A = typeof A; + `, + errors: [ + { + data: { + action: 'defined', + additional: '', + varName: 'A', + }, + line: 2, + messageId: 'usedOnlyAsType', + }, + ], + }, ], valid: [ @@ -3018,5 +3056,18 @@ declare class Bar {} `, filename: 'foo.d.ts', }, + { + code: ` +const A = 0; +type A = typeof A; +export { A }; + `, + }, + { + code: ` +class A {} +export type B = A; + `, + }, ], });
Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.
Alternative Proxies: