diff --git a/packages/eslint-plugin/src/rules/no-shadow.ts b/packages/eslint-plugin/src/rules/no-shadow.ts index 1d1f894b651..6eb2ef38f7c 100644 --- a/packages/eslint-plugin/src/rules/no-shadow.ts +++ b/packages/eslint-plugin/src/rules/no-shadow.ts @@ -126,6 +126,84 @@ export default util.createRule({ return util.isFunctionType(id.parent); } + function isGenericOfStaticMethod( + variable: TSESLint.Scope.Variable, + ): boolean { + if (!('isTypeVariable' in variable)) { + // this shouldn't happen... + return false; + } + + if (!variable.isTypeVariable) { + return false; + } + + if (variable.identifiers.length === 0) { + return false; + } + + const typeParameter = variable.identifiers[0].parent; + if (typeParameter?.type !== AST_NODE_TYPES.TSTypeParameter) { + return false; + } + const typeParameterDecl = typeParameter.parent; + if ( + typeParameterDecl?.type !== AST_NODE_TYPES.TSTypeParameterDeclaration + ) { + return false; + } + const functionExpr = typeParameterDecl.parent; + if ( + !functionExpr || + (functionExpr.type !== AST_NODE_TYPES.FunctionExpression && + functionExpr.type !== AST_NODE_TYPES.TSEmptyBodyFunctionExpression) + ) { + return false; + } + const methodDefinition = functionExpr.parent; + if (methodDefinition?.type !== AST_NODE_TYPES.MethodDefinition) { + return false; + } + return methodDefinition.static; + } + + function isGenericOfClassDecl(variable: TSESLint.Scope.Variable): boolean { + if (!('isTypeVariable' in variable)) { + // this shouldn't happen... + return false; + } + + if (!variable.isTypeVariable) { + return false; + } + + if (variable.identifiers.length === 0) { + return false; + } + + const typeParameter = variable.identifiers[0].parent; + if (typeParameter?.type !== AST_NODE_TYPES.TSTypeParameter) { + return false; + } + const typeParameterDecl = typeParameter.parent; + if ( + typeParameterDecl?.type !== AST_NODE_TYPES.TSTypeParameterDeclaration + ) { + return false; + } + const classDecl = typeParameterDecl.parent; + return classDecl?.type === AST_NODE_TYPES.ClassDeclaration; + } + + function isGenericOfAStaticMethodShadow( + variable: TSESLint.Scope.Variable, + shadowed: TSESLint.Scope.Variable, + ): boolean { + return ( + isGenericOfStaticMethod(variable) && isGenericOfClassDecl(shadowed) + ); + } + /** * Check if variable name is allowed. * @param variable The variable to check. @@ -321,6 +399,13 @@ export default util.createRule({ continue; } + // ignore static class method generic shadowing class generic + // this is impossible for the scope analyser to understand + // so we have to handle this manually in this rule + if (isGenericOfAStaticMethodShadow(variable, shadowed)) { + continue; + } + const isESLintGlobal = 'writeable' in shadowed; if ( (shadowed.identifiers.length > 0 || diff --git a/packages/eslint-plugin/tests/rules/no-shadow.test.ts b/packages/eslint-plugin/tests/rules/no-shadow.test.ts index abc78ee5f3e..f48adfc1b57 100644 --- a/packages/eslint-plugin/tests/rules/no-shadow.test.ts +++ b/packages/eslint-plugin/tests/rules/no-shadow.test.ts @@ -159,6 +159,19 @@ type Fn = (Foo: string) => typeof Foo; `, options: [{ ignoreFunctionTypeParameterNameValueShadow: false }], }, + ` +export class Wrapper { + private constructor(private readonly wrapped: Wrapped) {} + + unwrap(): Wrapped { + return this.wrapped; + } + + static create(wrapped: Wrapped) { + return new Wrapper(wrapped); + } +} + `, ], invalid: [ {