From 3caf3c52021c994397f6305cfa3e99276deff4b0 Mon Sep 17 00:00:00 2001 From: Zzzen Date: Sat, 1 Jan 2022 13:44:05 +0800 Subject: [PATCH] fix: support `typeof this` --- .../tests/eslint-rules/no-undef.test.ts | 9 +++++++ .../src/referencer/TypeVisitor.ts | 11 ++++++++- packages/typescript-estree/src/convert.ts | 9 +++++++ packages/typescript-estree/src/node-utils.ts | 24 +++++++++++++++++++ 4 files changed, 52 insertions(+), 1 deletion(-) diff --git a/packages/eslint-plugin/tests/eslint-rules/no-undef.test.ts b/packages/eslint-plugin/tests/eslint-rules/no-undef.test.ts index 9cff63994d3..53dd3be8d0d 100644 --- a/packages/eslint-plugin/tests/eslint-rules/no-undef.test.ts +++ b/packages/eslint-plugin/tests/eslint-rules/no-undef.test.ts @@ -248,6 +248,15 @@ class Foo {} ` export type AppState = typeof import('./src/store/reducers').default; `, + ` +const obj = { + foo: '', + bar() { + let self: typeof this; + let foo: typeof this.foo; + }, +}; + `, ], invalid: [ { diff --git a/packages/scope-manager/src/referencer/TypeVisitor.ts b/packages/scope-manager/src/referencer/TypeVisitor.ts index e9abb40f4f4..bbfbab23020 100644 --- a/packages/scope-manager/src/referencer/TypeVisitor.ts +++ b/packages/scope-manager/src/referencer/TypeVisitor.ts @@ -258,11 +258,20 @@ class TypeVisitor extends Visitor { protected TSTypeQuery(node: TSESTree.TSTypeQuery): void { if (node.exprName.type === AST_NODE_TYPES.Identifier) { this.#referencer.currentScope().referenceValue(node.exprName); + } else if ( + (node.exprName.type as unknown) === AST_NODE_TYPES.ThisExpression + ) { + // technically exprName is either Identifier or QualifiedName, but eslint does not recognize `typeof this`, + // so we have translated it to ThisExpression. + return; } else { let expr = node.exprName.left; - while (expr.type !== AST_NODE_TYPES.Identifier) { + while (expr.type === TSESTree.AST_NODE_TYPES.TSQualifiedName) { expr = expr.left; } + if ((expr.type as unknown) === AST_NODE_TYPES.ThisExpression) { + return; + } this.#referencer.currentScope().referenceValue(expr); } } diff --git a/packages/typescript-estree/src/convert.ts b/packages/typescript-estree/src/convert.ts index f0fb7d3cf58..99453a9f409 100644 --- a/packages/typescript-estree/src/convert.ts +++ b/packages/typescript-estree/src/convert.ts @@ -20,6 +20,7 @@ import { isComputedProperty, isESTreeClassMember, isOptional, + isThisInTypeQuery, TSError, unescapeStringLiteralText, } from './node-utils'; @@ -787,6 +788,14 @@ export class Converter { } case SyntaxKind.Identifier: { + if (isThisInTypeQuery(node)) { + return this.createNode( + node as unknown as ts.ThisExpression, + { + type: AST_NODE_TYPES.ThisExpression, + }, + ); + } return this.createNode(node, { type: AST_NODE_TYPES.Identifier, name: node.text, diff --git a/packages/typescript-estree/src/node-utils.ts b/packages/typescript-estree/src/node-utils.ts index d8182aa5655..8bca0930cb7 100644 --- a/packages/typescript-estree/src/node-utils.ts +++ b/packages/typescript-estree/src/node-utils.ts @@ -663,3 +663,27 @@ export function firstDefined( } return undefined; } + +export function identifierIsThisKeyword(id: ts.Identifier): boolean { + return id.originalKeywordKind === SyntaxKind.ThisKeyword; +} + +export function isThisIdentifier(node: ts.Node | undefined): boolean { + return ( + !!node && + node.kind === SyntaxKind.Identifier && + identifierIsThisKeyword(node as ts.Identifier) + ); +} + +export function isThisInTypeQuery(node: ts.Node): boolean { + if (!isThisIdentifier(node)) { + return false; + } + + while (ts.isQualifiedName(node.parent) && node.parent.left === node) { + node = node.parent; + } + + return node.parent.kind === SyntaxKind.TypeQuery; +}