Skip to content

fix(eslint-plugin): [no-unused-vars] no is assigned a value but only used as a type error when it has a same name #11322

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
30 changes: 25 additions & 5 deletions packages/eslint-plugin/src/util/collectUnusedVariables.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type {
ScopeManager,
ScopeVariable,
Variable,
} from '@typescript-eslint/scope-manager';
import type { TSESTree } from '@typescript-eslint/utils';

Expand Down Expand Up @@ -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)
) {
Expand Down Expand Up @@ -415,6 +416,14 @@ function isSelfReference(
return false;
}

/**
* @param variable the variable to check
* @returns true if it is `isTypeVariable` and `isValueVariable`
*/
function isDualPurposeVariable(variable: Variable): boolean {
return variable.isTypeVariable && variable.isValueVariable;
}

const MERGABLE_TYPES = new Set([
AST_NODE_TYPES.ClassDeclaration,
AST_NODE_TYPES.FunctionDeclaration,
Expand All @@ -426,7 +435,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.
Expand All @@ -441,7 +450,10 @@ function isMergableExported(variable: ScopeVariable): boolean {
def.node.parent?.type === AST_NODE_TYPES.ExportNamedDeclaration) ||
def.node.parent?.type === AST_NODE_TYPES.ExportDefaultDeclaration
) {
return true;
return !(
def.node.type === AST_NODE_TYPES.TSTypeAliasDeclaration &&
isDualPurposeVariable(variable)
);
}
}

Expand All @@ -453,7 +465,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;

Expand All @@ -465,7 +477,15 @@ 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 &&
!(
node.type === AST_NODE_TYPES.TSTypeAliasDeclaration &&
isDualPurposeVariable(variable)
)
);
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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: [
Expand Down Expand Up @@ -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;
`,
},
],
});