From f7d55fd8c8fca1eed5bf6086781c932e35b7d9b8 Mon Sep 17 00:00:00 2001 From: nayounsang Date: Tue, 17 Jun 2025 19:47:35 +0900 Subject: [PATCH 1/9] test: add test case --- .../no-unused-vars/no-unused-vars.test.ts | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) 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..c9a5f4a1c122 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,24 @@ 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', + }, + ], + }, ], valid: [ @@ -3018,5 +3036,13 @@ declare class Bar {} `, filename: 'foo.d.ts', }, + { + code: ` +const A = 0; + +type A = typeof A; +export { A }; + `, + }, ], }); From bc379fce5697f29911282dea9ee9194084c578f3 Mon Sep 17 00:00:00 2001 From: nayounsang Date: Fri, 20 Jun 2025 16:29:31 +0900 Subject: [PATCH 2/9] fix: strict checks on exports with same type and variable name --- .../src/util/collectUnusedVariables.ts | 39 ++++++++++++++++++- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/packages/eslint-plugin/src/util/collectUnusedVariables.ts b/packages/eslint-plugin/src/util/collectUnusedVariables.ts index 983eb1472c14..314dfdcd2806 100644 --- a/packages/eslint-plugin/src/util/collectUnusedVariables.ts +++ b/packages/eslint-plugin/src/util/collectUnusedVariables.ts @@ -5,6 +5,8 @@ import type { import type { TSESTree } from '@typescript-eslint/utils'; import { + DefinitionType, + ESLintScopeVariable, ImplicitLibVariable, ScopeType, Visitor, @@ -15,6 +17,7 @@ import { ESLintUtils, TSESLint, } from '@typescript-eslint/utils'; +import { Node } from 'typescript'; import { isTypeImport } from './isTypeImport'; import { referenceContainsTypeQuery } from './referenceContainsTypeQuery'; @@ -415,6 +418,29 @@ function isSelfReference( return false; } +const exportExceptDefTypes: DefinitionType[] = [ + DefinitionType.Variable, + DefinitionType.Type, +]; +/** + * In edge cases, the existing used logic does not work. + * When the type and variable name of the variable are the same + * @ref https://github.com/typescript-eslint/typescript-eslint/issues/10658 + * @param variable the variable to check + * @returns true if it is an edge case + */ +function isSafeUnusedExportCondition(variable: ScopeVariable): boolean { + if (variable instanceof ESLintScopeVariable) { + return true; + } + if (variable.isTypeVariable && variable.isValueVariable) { + return !variable.defs + .map(d => d.type) + .every(t => exportExceptDefTypes.includes(t)); + } + return true; +} + const MERGABLE_TYPES = new Set([ AST_NODE_TYPES.ClassDeclaration, AST_NODE_TYPES.FunctionDeclaration, @@ -427,6 +453,7 @@ const MERGABLE_TYPES = new Set([ * @param variable the variable to check */ function isMergableExported(variable: ScopeVariable): boolean { + const safeFlag = isSafeUnusedExportCondition(variable); // 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 +468,9 @@ function isMergableExported(variable: ScopeVariable): boolean { def.node.parent?.type === AST_NODE_TYPES.ExportNamedDeclaration) || def.node.parent?.type === AST_NODE_TYPES.ExportDefaultDeclaration ) { - return true; + return ( + safeFlag || def.node.type !== AST_NODE_TYPES.TSTypeAliasDeclaration + ); } } @@ -454,6 +483,7 @@ function isMergableExported(variable: ScopeVariable): boolean { * @returns True if the variable is exported, false if not. */ function isExported(variable: ScopeVariable): boolean { + const safeFlag = isSafeUnusedExportCondition(variable); return variable.defs.some(definition => { let node = definition.node; @@ -465,7 +495,12 @@ 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 && + (safeFlag || node.type !== AST_NODE_TYPES.TSTypeAliasDeclaration) + ); }); } From 2f474b3f902606e151b01f31e05ae16899a6946c Mon Sep 17 00:00:00 2001 From: nayounsang Date: Fri, 20 Jun 2025 21:42:33 +0900 Subject: [PATCH 3/9] refactor: resolve self-code-review --- packages/eslint-plugin/src/util/collectUnusedVariables.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/eslint-plugin/src/util/collectUnusedVariables.ts b/packages/eslint-plugin/src/util/collectUnusedVariables.ts index 314dfdcd2806..376efa900bc8 100644 --- a/packages/eslint-plugin/src/util/collectUnusedVariables.ts +++ b/packages/eslint-plugin/src/util/collectUnusedVariables.ts @@ -17,7 +17,6 @@ import { ESLintUtils, TSESLint, } from '@typescript-eslint/utils'; -import { Node } from 'typescript'; import { isTypeImport } from './isTypeImport'; import { referenceContainsTypeQuery } from './referenceContainsTypeQuery'; @@ -191,7 +190,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) ) { @@ -452,7 +451,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: ScopeVariable): boolean { const safeFlag = isSafeUnusedExportCondition(variable); // 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) { From 52eede8005e79a1808633ec8bd87a43831db3bca Mon Sep 17 00:00:00 2001 From: nayounsang Date: Tue, 1 Jul 2025 17:24:46 +0900 Subject: [PATCH 4/9] refactor: change ScopeVariable to Variable --- .../eslint-plugin/src/util/collectUnusedVariables.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/packages/eslint-plugin/src/util/collectUnusedVariables.ts b/packages/eslint-plugin/src/util/collectUnusedVariables.ts index 376efa900bc8..7b0b47de8c81 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'; @@ -428,10 +429,7 @@ const exportExceptDefTypes: DefinitionType[] = [ * @param variable the variable to check * @returns true if it is an edge case */ -function isSafeUnusedExportCondition(variable: ScopeVariable): boolean { - if (variable instanceof ESLintScopeVariable) { - return true; - } +function isSafeUnusedExportCondition(variable: Variable): boolean { if (variable.isTypeVariable && variable.isValueVariable) { return !variable.defs .map(d => d.type) @@ -451,7 +449,7 @@ const MERGABLE_TYPES = new Set([ * Determine if the variable is directly exported * @param variable the variable to check */ -function isMergeableExported(variable: ScopeVariable): boolean { +function isMergeableExported(variable: Variable): boolean { const safeFlag = isSafeUnusedExportCondition(variable); // 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) { @@ -481,7 +479,7 @@ function isMergeableExported(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 { const safeFlag = isSafeUnusedExportCondition(variable); return variable.defs.some(definition => { let node = definition.node; From df466a478b90bdc9b7093970e7606658c468fd75 Mon Sep 17 00:00:00 2001 From: nayounsang Date: Tue, 1 Jul 2025 17:36:21 +0900 Subject: [PATCH 5/9] refactor: improve perf when call isSafeUnusedExportCondition --- packages/eslint-plugin/src/util/collectUnusedVariables.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/eslint-plugin/src/util/collectUnusedVariables.ts b/packages/eslint-plugin/src/util/collectUnusedVariables.ts index 7b0b47de8c81..62deb7f28dd7 100644 --- a/packages/eslint-plugin/src/util/collectUnusedVariables.ts +++ b/packages/eslint-plugin/src/util/collectUnusedVariables.ts @@ -450,7 +450,6 @@ const MERGABLE_TYPES = new Set([ * @param variable the variable to check */ function isMergeableExported(variable: Variable): boolean { - const safeFlag = isSafeUnusedExportCondition(variable); // 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. @@ -466,7 +465,8 @@ function isMergeableExported(variable: Variable): boolean { def.node.parent?.type === AST_NODE_TYPES.ExportDefaultDeclaration ) { return ( - safeFlag || def.node.type !== AST_NODE_TYPES.TSTypeAliasDeclaration + def.node.type !== AST_NODE_TYPES.TSTypeAliasDeclaration || + isSafeUnusedExportCondition(variable) ); } } @@ -480,7 +480,6 @@ function isMergeableExported(variable: Variable): boolean { * @returns True if the variable is exported, false if not. */ function isExported(variable: Variable): boolean { - const safeFlag = isSafeUnusedExportCondition(variable); return variable.defs.some(definition => { let node = definition.node; @@ -496,7 +495,8 @@ function isExported(variable: Variable): boolean { return ( isExportedFlag && - (safeFlag || node.type !== AST_NODE_TYPES.TSTypeAliasDeclaration) + (node.type !== AST_NODE_TYPES.TSTypeAliasDeclaration || + isSafeUnusedExportCondition(variable)) ); }); } From 0d5801d17acb373a7bce62194493fdb260134057 Mon Sep 17 00:00:00 2001 From: nayounsang Date: Wed, 2 Jul 2025 17:29:50 +0900 Subject: [PATCH 6/9] test: add test case --- .../no-unused-vars/no-unused-vars.test.ts | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) 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 c9a5f4a1c122..2fd9fdf69626 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 @@ -1733,6 +1733,26 @@ export type A = typeof A; }, ], }, + { + 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: [ @@ -3039,10 +3059,15 @@ declare class Bar {} { code: ` const A = 0; - type A = typeof A; export { A }; `, }, + { + code: ` +class A { } +export type B = A; + `, + }, ], }); From ed639ea3deb69c72c6df67caff648001433c541c Mon Sep 17 00:00:00 2001 From: nayounsang Date: Wed, 2 Jul 2025 17:35:22 +0900 Subject: [PATCH 7/9] refactor: validate logic --- .../src/util/collectUnusedVariables.ts | 32 ++++++------------- 1 file changed, 10 insertions(+), 22 deletions(-) diff --git a/packages/eslint-plugin/src/util/collectUnusedVariables.ts b/packages/eslint-plugin/src/util/collectUnusedVariables.ts index 62deb7f28dd7..34da070722ad 100644 --- a/packages/eslint-plugin/src/util/collectUnusedVariables.ts +++ b/packages/eslint-plugin/src/util/collectUnusedVariables.ts @@ -6,8 +6,6 @@ import type { import type { TSESTree } from '@typescript-eslint/utils'; import { - DefinitionType, - ESLintScopeVariable, ImplicitLibVariable, ScopeType, Visitor, @@ -418,24 +416,12 @@ function isSelfReference( return false; } -const exportExceptDefTypes: DefinitionType[] = [ - DefinitionType.Variable, - DefinitionType.Type, -]; /** - * In edge cases, the existing used logic does not work. - * When the type and variable name of the variable are the same - * @ref https://github.com/typescript-eslint/typescript-eslint/issues/10658 * @param variable the variable to check - * @returns true if it is an edge case + * @returns true if it is `isTypeVariable` and `isValueVariable` */ -function isSafeUnusedExportCondition(variable: Variable): boolean { - if (variable.isTypeVariable && variable.isValueVariable) { - return !variable.defs - .map(d => d.type) - .every(t => exportExceptDefTypes.includes(t)); - } - return true; +function isDualPurposeVariable(variable: Variable): boolean { + return variable.isTypeVariable && variable.isValueVariable; } const MERGABLE_TYPES = new Set([ @@ -464,9 +450,9 @@ function isMergeableExported(variable: Variable): boolean { def.node.parent?.type === AST_NODE_TYPES.ExportNamedDeclaration) || def.node.parent?.type === AST_NODE_TYPES.ExportDefaultDeclaration ) { - return ( - def.node.type !== AST_NODE_TYPES.TSTypeAliasDeclaration || - isSafeUnusedExportCondition(variable) + return !( + def.node.type === AST_NODE_TYPES.TSTypeAliasDeclaration && + isDualPurposeVariable(variable) ); } } @@ -495,8 +481,10 @@ function isExported(variable: Variable): boolean { return ( isExportedFlag && - (node.type !== AST_NODE_TYPES.TSTypeAliasDeclaration || - isSafeUnusedExportCondition(variable)) + !( + node.type === AST_NODE_TYPES.TSTypeAliasDeclaration && + isDualPurposeVariable(variable) + ) ); }); } From 85d49d7427d913f3aee57c84d5fd65b9e18a72e7 Mon Sep 17 00:00:00 2001 From: nayounsang Date: Wed, 2 Jul 2025 18:05:09 +0900 Subject: [PATCH 8/9] fix: lint test file --- .../tests/rules/no-unused-vars/no-unused-vars.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 2fd9fdf69626..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 @@ -1735,7 +1735,7 @@ export type A = typeof A; }, { code: ` -function A() { } +function A() {} namespace A { export const prop = 1; } @@ -3065,7 +3065,7 @@ export { A }; }, { code: ` -class A { } +class A {} export type B = A; `, }, From 8ac0777ac1c27eda0897661c64f7969dc1f80057 Mon Sep 17 00:00:00 2001 From: nayounsang Date: Sat, 5 Jul 2025 23:46:50 +0900 Subject: [PATCH 9/9] refactor: dupe logic abstract --- .../src/util/collectUnusedVariables.ts | 27 +++++++++---------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/packages/eslint-plugin/src/util/collectUnusedVariables.ts b/packages/eslint-plugin/src/util/collectUnusedVariables.ts index 34da070722ad..734eeac690de 100644 --- a/packages/eslint-plugin/src/util/collectUnusedVariables.ts +++ b/packages/eslint-plugin/src/util/collectUnusedVariables.ts @@ -418,10 +418,18 @@ function isSelfReference( /** * @param variable the variable to check - * @returns true if it is `isTypeVariable` and `isValueVariable` + * @param node the node from a some def of variable + * @returns true if variable is type/value duality and declaration is type declaration */ -function isDualPurposeVariable(variable: Variable): boolean { - return variable.isTypeVariable && variable.isValueVariable; +function isMergedTypeDeclaration( + variable: Variable, + node: TSESTree.Node, +): boolean { + return ( + node.type === AST_NODE_TYPES.TSTypeAliasDeclaration && + variable.isTypeVariable && + variable.isValueVariable + ); } const MERGABLE_TYPES = new Set([ @@ -450,10 +458,7 @@ function isMergeableExported(variable: Variable): boolean { def.node.parent?.type === AST_NODE_TYPES.ExportNamedDeclaration) || def.node.parent?.type === AST_NODE_TYPES.ExportDefaultDeclaration ) { - return !( - def.node.type === AST_NODE_TYPES.TSTypeAliasDeclaration && - isDualPurposeVariable(variable) - ); + return !isMergedTypeDeclaration(variable, def.node); } } @@ -479,13 +484,7 @@ function isExported(variable: Variable): boolean { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const isExportedFlag = node.parent!.type.startsWith('Export'); - return ( - isExportedFlag && - !( - node.type === AST_NODE_TYPES.TSTypeAliasDeclaration && - isDualPurposeVariable(variable) - ) - ); + return isExportedFlag && !isMergedTypeDeclaration(variable, node); }); } pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

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:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy