From 3caf3c52021c994397f6305cfa3e99276deff4b0 Mon Sep 17 00:00:00 2001 From: Zzzen Date: Sat, 1 Jan 2022 13:44:05 +0800 Subject: [PATCH 01/13] 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; +} From ae251685e35aa52d08b9f46caf90352f8d86d78d Mon Sep 17 00:00:00 2001 From: Zzzen Date: Wed, 5 Jan 2022 00:01:53 +0800 Subject: [PATCH 02/13] fix: add ThisExpression to EntityName --- packages/ast-spec/src/unions/EntityName.ts | 3 +- .../tests/eslint-rules/no-undef.test.ts | 3 +- .../src/referencer/ClassVisitor.ts | 6 +++- .../src/referencer/TypeVisitor.ts | 30 +++++++++---------- 4 files changed, 23 insertions(+), 19 deletions(-) diff --git a/packages/ast-spec/src/unions/EntityName.ts b/packages/ast-spec/src/unions/EntityName.ts index 82f7b1c9b0c..8a7364bef6b 100644 --- a/packages/ast-spec/src/unions/EntityName.ts +++ b/packages/ast-spec/src/unions/EntityName.ts @@ -1,4 +1,5 @@ import type { Identifier } from '../expression/Identifier/spec'; +import type { ThisExpression } from '../expression/ThisExpression/spec'; import type { TSQualifiedName } from '../type/TSQualifiedName/spec'; -export type EntityName = Identifier | TSQualifiedName; +export type EntityName = Identifier | ThisExpression | TSQualifiedName; 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 53dd3be8d0d..3335e04e741 100644 --- a/packages/eslint-plugin/tests/eslint-rules/no-undef.test.ts +++ b/packages/eslint-plugin/tests/eslint-rules/no-undef.test.ts @@ -249,11 +249,12 @@ class Foo {} export type AppState = typeof import('./src/store/reducers').default; `, ` +let self: typeof this; +let foo: typeof this.foo; const obj = { foo: '', bar() { let self: typeof this; - let foo: typeof this.foo; }, }; `, diff --git a/packages/scope-manager/src/referencer/ClassVisitor.ts b/packages/scope-manager/src/referencer/ClassVisitor.ts index 6f505044213..306f064dc8a 100644 --- a/packages/scope-manager/src/referencer/ClassVisitor.ts +++ b/packages/scope-manager/src/referencer/ClassVisitor.ts @@ -293,7 +293,7 @@ class ClassVisitor extends Visitor { node.typeAnnotation.type === AST_NODE_TYPES.TSTypeReference && this.#emitDecoratorMetadata ) { - let identifier: TSESTree.Identifier; + let identifier: TSESTree.Identifier | TSESTree.ThisExpression; if ( node.typeAnnotation.typeName.type === AST_NODE_TYPES.TSQualifiedName ) { @@ -306,6 +306,10 @@ class ClassVisitor extends Visitor { identifier = node.typeAnnotation.typeName; } + if (identifier.type === AST_NODE_TYPES.ThisExpression) { + return; + } + if (withDecorators) { this.#referencer.currentScope().referenceDualValueType(identifier); diff --git a/packages/scope-manager/src/referencer/TypeVisitor.ts b/packages/scope-manager/src/referencer/TypeVisitor.ts index bbfbab23020..672058dfae0 100644 --- a/packages/scope-manager/src/referencer/TypeVisitor.ts +++ b/packages/scope-manager/src/referencer/TypeVisitor.ts @@ -256,24 +256,22 @@ class TypeVisitor extends Visitor { // a type query `typeof foo` is a special case that references a _non-type_ variable, 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 === TSESTree.AST_NODE_TYPES.TSQualifiedName) { - expr = expr.left; + let identifier: TSESTree.Identifier | TSESTree.ThisExpression; + if (node.exprName.type === AST_NODE_TYPES.TSQualifiedName) { + let iter = node.exprName; + while (iter.left.type === AST_NODE_TYPES.TSQualifiedName) { + iter = iter.left; } - if ((expr.type as unknown) === AST_NODE_TYPES.ThisExpression) { - return; - } - this.#referencer.currentScope().referenceValue(expr); + identifier = iter.left; + } else { + identifier = node.exprName; } + + if (identifier.type === AST_NODE_TYPES.ThisExpression) { + return; + } + + this.#referencer.currentScope().referenceValue(identifier); } protected TSTypeAnnotation(node: TSESTree.TSTypeAnnotation): void { From 1a6ef433493e846e74f6acc9f4120fb82157b400 Mon Sep 17 00:00:00 2001 From: Zzzen Date: Mon, 25 Apr 2022 22:33:56 +0800 Subject: [PATCH 03/13] fix: add parser tests --- .../typescript/types/typeof-this.src.ts | 2 + .../typescript/types/typeof-this.src.ts.shot | 528 ++++++++++++++++++ 2 files changed, 530 insertions(+) create mode 100644 packages/shared-fixtures/fixtures/typescript/types/typeof-this.src.ts create mode 100644 packages/typescript-estree/tests/snapshots/typescript/types/typeof-this.src.ts.shot diff --git a/packages/shared-fixtures/fixtures/typescript/types/typeof-this.src.ts b/packages/shared-fixtures/fixtures/typescript/types/typeof-this.src.ts new file mode 100644 index 00000000000..6cad5147a8a --- /dev/null +++ b/packages/shared-fixtures/fixtures/typescript/types/typeof-this.src.ts @@ -0,0 +1,2 @@ +let self: typeof this; +let foo: typeof this.foo; diff --git a/packages/typescript-estree/tests/snapshots/typescript/types/typeof-this.src.ts.shot b/packages/typescript-estree/tests/snapshots/typescript/types/typeof-this.src.ts.shot new file mode 100644 index 00000000000..b1779d9d972 --- /dev/null +++ b/packages/typescript-estree/tests/snapshots/typescript/types/typeof-this.src.ts.shot @@ -0,0 +1,528 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`typescript types typeof-this.src 1`] = ` +Object { + "body": Array [ + Object { + "declarations": Array [ + Object { + "id": Object { + "loc": Object { + "end": Object { + "column": 21, + "line": 1, + }, + "start": Object { + "column": 4, + "line": 1, + }, + }, + "name": "self", + "range": Array [ + 4, + 21, + ], + "type": "Identifier", + "typeAnnotation": Object { + "loc": Object { + "end": Object { + "column": 21, + "line": 1, + }, + "start": Object { + "column": 8, + "line": 1, + }, + }, + "range": Array [ + 8, + 21, + ], + "type": "TSTypeAnnotation", + "typeAnnotation": Object { + "exprName": Object { + "loc": Object { + "end": Object { + "column": 21, + "line": 1, + }, + "start": Object { + "column": 17, + "line": 1, + }, + }, + "range": Array [ + 17, + 21, + ], + "type": "ThisExpression", + }, + "loc": Object { + "end": Object { + "column": 21, + "line": 1, + }, + "start": Object { + "column": 10, + "line": 1, + }, + }, + "range": Array [ + 10, + 21, + ], + "type": "TSTypeQuery", + }, + }, + }, + "init": null, + "loc": Object { + "end": Object { + "column": 21, + "line": 1, + }, + "start": Object { + "column": 4, + "line": 1, + }, + }, + "range": Array [ + 4, + 21, + ], + "type": "VariableDeclarator", + }, + ], + "kind": "let", + "loc": Object { + "end": Object { + "column": 22, + "line": 1, + }, + "start": Object { + "column": 0, + "line": 1, + }, + }, + "range": Array [ + 0, + 22, + ], + "type": "VariableDeclaration", + }, + Object { + "declarations": Array [ + Object { + "id": Object { + "loc": Object { + "end": Object { + "column": 24, + "line": 2, + }, + "start": Object { + "column": 4, + "line": 2, + }, + }, + "name": "foo", + "range": Array [ + 27, + 47, + ], + "type": "Identifier", + "typeAnnotation": Object { + "loc": Object { + "end": Object { + "column": 24, + "line": 2, + }, + "start": Object { + "column": 7, + "line": 2, + }, + }, + "range": Array [ + 30, + 47, + ], + "type": "TSTypeAnnotation", + "typeAnnotation": Object { + "exprName": Object { + "left": Object { + "loc": Object { + "end": Object { + "column": 20, + "line": 2, + }, + "start": Object { + "column": 16, + "line": 2, + }, + }, + "range": Array [ + 39, + 43, + ], + "type": "ThisExpression", + }, + "loc": Object { + "end": Object { + "column": 24, + "line": 2, + }, + "start": Object { + "column": 16, + "line": 2, + }, + }, + "range": Array [ + 39, + 47, + ], + "right": Object { + "loc": Object { + "end": Object { + "column": 24, + "line": 2, + }, + "start": Object { + "column": 21, + "line": 2, + }, + }, + "name": "foo", + "range": Array [ + 44, + 47, + ], + "type": "Identifier", + }, + "type": "TSQualifiedName", + }, + "loc": Object { + "end": Object { + "column": 24, + "line": 2, + }, + "start": Object { + "column": 9, + "line": 2, + }, + }, + "range": Array [ + 32, + 47, + ], + "type": "TSTypeQuery", + }, + }, + }, + "init": null, + "loc": Object { + "end": Object { + "column": 24, + "line": 2, + }, + "start": Object { + "column": 4, + "line": 2, + }, + }, + "range": Array [ + 27, + 47, + ], + "type": "VariableDeclarator", + }, + ], + "kind": "let", + "loc": Object { + "end": Object { + "column": 25, + "line": 2, + }, + "start": Object { + "column": 0, + "line": 2, + }, + }, + "range": Array [ + 23, + 48, + ], + "type": "VariableDeclaration", + }, + ], + "comments": Array [], + "loc": Object { + "end": Object { + "column": 0, + "line": 3, + }, + "start": Object { + "column": 0, + "line": 1, + }, + }, + "range": Array [ + 0, + 49, + ], + "sourceType": "script", + "tokens": Array [ + Object { + "loc": Object { + "end": Object { + "column": 3, + "line": 1, + }, + "start": Object { + "column": 0, + "line": 1, + }, + }, + "range": Array [ + 0, + 3, + ], + "type": "Keyword", + "value": "let", + }, + Object { + "loc": Object { + "end": Object { + "column": 8, + "line": 1, + }, + "start": Object { + "column": 4, + "line": 1, + }, + }, + "range": Array [ + 4, + 8, + ], + "type": "Identifier", + "value": "self", + }, + Object { + "loc": Object { + "end": Object { + "column": 9, + "line": 1, + }, + "start": Object { + "column": 8, + "line": 1, + }, + }, + "range": Array [ + 8, + 9, + ], + "type": "Punctuator", + "value": ":", + }, + Object { + "loc": Object { + "end": Object { + "column": 16, + "line": 1, + }, + "start": Object { + "column": 10, + "line": 1, + }, + }, + "range": Array [ + 10, + 16, + ], + "type": "Keyword", + "value": "typeof", + }, + Object { + "loc": Object { + "end": Object { + "column": 21, + "line": 1, + }, + "start": Object { + "column": 17, + "line": 1, + }, + }, + "range": Array [ + 17, + 21, + ], + "type": "Keyword", + "value": "this", + }, + Object { + "loc": Object { + "end": Object { + "column": 22, + "line": 1, + }, + "start": Object { + "column": 21, + "line": 1, + }, + }, + "range": Array [ + 21, + 22, + ], + "type": "Punctuator", + "value": ";", + }, + Object { + "loc": Object { + "end": Object { + "column": 3, + "line": 2, + }, + "start": Object { + "column": 0, + "line": 2, + }, + }, + "range": Array [ + 23, + 26, + ], + "type": "Keyword", + "value": "let", + }, + Object { + "loc": Object { + "end": Object { + "column": 7, + "line": 2, + }, + "start": Object { + "column": 4, + "line": 2, + }, + }, + "range": Array [ + 27, + 30, + ], + "type": "Identifier", + "value": "foo", + }, + Object { + "loc": Object { + "end": Object { + "column": 8, + "line": 2, + }, + "start": Object { + "column": 7, + "line": 2, + }, + }, + "range": Array [ + 30, + 31, + ], + "type": "Punctuator", + "value": ":", + }, + Object { + "loc": Object { + "end": Object { + "column": 15, + "line": 2, + }, + "start": Object { + "column": 9, + "line": 2, + }, + }, + "range": Array [ + 32, + 38, + ], + "type": "Keyword", + "value": "typeof", + }, + Object { + "loc": Object { + "end": Object { + "column": 20, + "line": 2, + }, + "start": Object { + "column": 16, + "line": 2, + }, + }, + "range": Array [ + 39, + 43, + ], + "type": "Keyword", + "value": "this", + }, + Object { + "loc": Object { + "end": Object { + "column": 21, + "line": 2, + }, + "start": Object { + "column": 20, + "line": 2, + }, + }, + "range": Array [ + 43, + 44, + ], + "type": "Punctuator", + "value": ".", + }, + Object { + "loc": Object { + "end": Object { + "column": 24, + "line": 2, + }, + "start": Object { + "column": 21, + "line": 2, + }, + }, + "range": Array [ + 44, + 47, + ], + "type": "Identifier", + "value": "foo", + }, + Object { + "loc": Object { + "end": Object { + "column": 25, + "line": 2, + }, + "start": Object { + "column": 24, + "line": 2, + }, + }, + "range": Array [ + 47, + 48, + ], + "type": "Punctuator", + "value": ";", + }, + ], + "type": "Program", +} +`; From 1680e541749566709e623169dc15170ea9c7a908 Mon Sep 17 00:00:00 2001 From: Zzzen Date: Mon, 25 Apr 2022 23:28:04 +0800 Subject: [PATCH 04/13] fix: prefer type predicate --- packages/typescript-estree/src/node-utils.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/typescript-estree/src/node-utils.ts b/packages/typescript-estree/src/node-utils.ts index 8bca0930cb7..12b2811e1ca 100644 --- a/packages/typescript-estree/src/node-utils.ts +++ b/packages/typescript-estree/src/node-utils.ts @@ -668,7 +668,9 @@ export function identifierIsThisKeyword(id: ts.Identifier): boolean { return id.originalKeywordKind === SyntaxKind.ThisKeyword; } -export function isThisIdentifier(node: ts.Node | undefined): boolean { +export function isThisIdentifier( + node: ts.Node | undefined, +): node is ts.Identifier { return ( !!node && node.kind === SyntaxKind.Identifier && From fdbc897d1998ed4f122eedac5343151ea9eb1525 Mon Sep 17 00:00:00 2001 From: Zzzen Date: Tue, 26 Apr 2022 00:48:06 +0800 Subject: [PATCH 05/13] fix: update snapshot --- .../__snapshots__/semantic-diagnostics-enabled.test.ts.snap | 2 ++ .../snapshots/typescript/types/typeof-this.src.ts.shot | 6 ++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/typescript-estree/tests/lib/__snapshots__/semantic-diagnostics-enabled.test.ts.snap b/packages/typescript-estree/tests/lib/__snapshots__/semantic-diagnostics-enabled.test.ts.snap index 03f030ad731..c130b176aaa 100644 --- a/packages/typescript-estree/tests/lib/__snapshots__/semantic-diagnostics-enabled.test.ts.snap +++ b/packages/typescript-estree/tests/lib/__snapshots__/semantic-diagnostics-enabled.test.ts.snap @@ -2774,6 +2774,8 @@ exports[`Parse all fixtures with "errorOnTypeScriptSyntacticAndSemanticIssues" e exports[`Parse all fixtures with "errorOnTypeScriptSyntacticAndSemanticIssues" enabled fixtures/typescript/types/typeof.src 1`] = `"TEST OUTPUT: No semantic or syntactic issues found"`; +exports[`Parse all fixtures with "errorOnTypeScriptSyntacticAndSemanticIssues" enabled fixtures/typescript/types/typeof-this.src 1`] = `"TEST OUTPUT: No semantic or syntactic issues found"`; + exports[`Parse all fixtures with "errorOnTypeScriptSyntacticAndSemanticIssues" enabled fixtures/typescript/types/union-intersection.src 1`] = `"TEST OUTPUT: No semantic or syntactic issues found"`; exports[`Parse all fixtures with "errorOnTypeScriptSyntacticAndSemanticIssues" enabled fixtures/typescript/types/union-type.src 1`] = `"TEST OUTPUT: No semantic or syntactic issues found"`; diff --git a/packages/typescript-estree/tests/snapshots/typescript/types/typeof-this.src.ts.shot b/packages/typescript-estree/tests/snapshots/typescript/types/typeof-this.src.ts.shot index b1779d9d972..6ea007aff11 100644 --- a/packages/typescript-estree/tests/snapshots/typescript/types/typeof-this.src.ts.shot +++ b/packages/typescript-estree/tests/snapshots/typescript/types/typeof-this.src.ts.shot @@ -51,11 +51,12 @@ Object { "line": 1, }, }, + "name": "this", "range": Array [ 17, 21, ], - "type": "ThisExpression", + "type": "Identifier", }, "loc": Object { "end": Object { @@ -159,11 +160,12 @@ Object { "line": 2, }, }, + "name": "this", "range": Array [ 39, 43, ], - "type": "ThisExpression", + "type": "Identifier", }, "loc": Object { "end": Object { From c924ee36b57ab6c931d72787ab04729bace3eb18 Mon Sep 17 00:00:00 2001 From: Zzzen Date: Tue, 26 Apr 2022 22:13:48 +0800 Subject: [PATCH 06/13] fix: update baseline --- .../snapshots/typescript/types/typeof-this.src.ts.shot | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/typescript-estree/tests/snapshots/typescript/types/typeof-this.src.ts.shot b/packages/typescript-estree/tests/snapshots/typescript/types/typeof-this.src.ts.shot index 6ea007aff11..b1779d9d972 100644 --- a/packages/typescript-estree/tests/snapshots/typescript/types/typeof-this.src.ts.shot +++ b/packages/typescript-estree/tests/snapshots/typescript/types/typeof-this.src.ts.shot @@ -51,12 +51,11 @@ Object { "line": 1, }, }, - "name": "this", "range": Array [ 17, 21, ], - "type": "Identifier", + "type": "ThisExpression", }, "loc": Object { "end": Object { @@ -160,12 +159,11 @@ Object { "line": 2, }, }, - "name": "this", "range": Array [ 39, 43, ], - "type": "Identifier", + "type": "ThisExpression", }, "loc": Object { "end": Object { From 3d09e4d83475b070afa0bfad78e62e1f4e5a24ee Mon Sep 17 00:00:00 2001 From: Zzzen Date: Sat, 28 May 2022 23:09:56 +0800 Subject: [PATCH 07/13] test: transform babel ast --- .../tests/ast-alignment/utils.ts | 23 ++++++++++++++++++- .../typescript/types/typeof-this.src.ts.shot | 2 ++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/packages/typescript-estree/tests/ast-alignment/utils.ts b/packages/typescript-estree/tests/ast-alignment/utils.ts index e74d1daf3ff..3188d1509a3 100644 --- a/packages/typescript-estree/tests/ast-alignment/utils.ts +++ b/packages/typescript-estree/tests/ast-alignment/utils.ts @@ -1,6 +1,6 @@ // babel types are something we don't really care about /* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/restrict-plus-operands */ -import type { File, Program } from '@babel/types'; +import type { File, Identifier, Program, TSTypeQuery } from '@babel/types'; import { AST_NODE_TYPES, TSESTree } from '../../src/ts-estree'; import { deeplyCopy, omitDeep } from '../../tools/test-utils'; @@ -295,6 +295,27 @@ export function preprocessBabylonAST(ast: File): any { delete node.loc.start.index; } }, + TSTypeQuery(node: TSTypeQuery) { + const { exprName } = node; + const processIdentifier = (identifier: Identifier) => { + if (identifier.name === 'this') { + (identifier.type as string) = 'ThisExpression'; + delete (identifier as { name?: string }).name; + } + }; + if (exprName.type === 'Identifier') { + processIdentifier(exprName); + } else if (exprName.type === 'TSQualifiedName') { + let qualifiedName = exprName; + while (true) { + if (qualifiedName.left.type === 'Identifier') { + processIdentifier(qualifiedName.left); + return; + } + qualifiedName = qualifiedName.left; + } + } + }, }, ); } diff --git a/packages/typescript-estree/tests/snapshots/typescript/types/typeof-this.src.ts.shot b/packages/typescript-estree/tests/snapshots/typescript/types/typeof-this.src.ts.shot index b1779d9d972..c708076ab55 100644 --- a/packages/typescript-estree/tests/snapshots/typescript/types/typeof-this.src.ts.shot +++ b/packages/typescript-estree/tests/snapshots/typescript/types/typeof-this.src.ts.shot @@ -72,6 +72,7 @@ Object { 21, ], "type": "TSTypeQuery", + "typeParameters": undefined, }, }, }, @@ -214,6 +215,7 @@ Object { 47, ], "type": "TSTypeQuery", + "typeParameters": undefined, }, }, }, From b03c226a0ba77656b318c46f4ae6e8c9c503c73f Mon Sep 17 00:00:00 2001 From: Zzzen Date: Sun, 5 Jun 2022 16:14:44 +0800 Subject: [PATCH 08/13] test: fix CI error --- packages/typescript-estree/tests/ast-alignment/utils.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/typescript-estree/tests/ast-alignment/utils.ts b/packages/typescript-estree/tests/ast-alignment/utils.ts index 3188d1509a3..880214f04f5 100644 --- a/packages/typescript-estree/tests/ast-alignment/utils.ts +++ b/packages/typescript-estree/tests/ast-alignment/utils.ts @@ -295,8 +295,12 @@ export function preprocessBabylonAST(ast: File): any { delete node.loc.start.index; } }, - TSTypeQuery(node: TSTypeQuery) { - const { exprName } = node; + /** + * ts-estree: `this` in `typeof this` has been converted from `Identifier` to `ThisExpression` + * @see https://github.com/typescript-eslint/typescript-eslint/pull/4382 + */ + TSTypeQuery(node: any) { + const { exprName } = node as TSTypeQuery; const processIdentifier = (identifier: Identifier) => { if (identifier.name === 'this') { (identifier.type as string) = 'ThisExpression'; From fcbaa5d3a45a05ee792e7d231be7612e16bbec84 Mon Sep 17 00:00:00 2001 From: Zzzen Date: Sun, 5 Jun 2022 16:54:18 +0800 Subject: [PATCH 09/13] test: refactor --- .../tests/ast-alignment/utils.ts | 31 +++++++++---------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/packages/typescript-estree/tests/ast-alignment/utils.ts b/packages/typescript-estree/tests/ast-alignment/utils.ts index 880214f04f5..f8c43458708 100644 --- a/packages/typescript-estree/tests/ast-alignment/utils.ts +++ b/packages/typescript-estree/tests/ast-alignment/utils.ts @@ -301,23 +301,22 @@ export function preprocessBabylonAST(ast: File): any { */ TSTypeQuery(node: any) { const { exprName } = node as TSTypeQuery; - const processIdentifier = (identifier: Identifier) => { - if (identifier.name === 'this') { - (identifier.type as string) = 'ThisExpression'; - delete (identifier as { name?: string }).name; - } - }; - if (exprName.type === 'Identifier') { - processIdentifier(exprName); - } else if (exprName.type === 'TSQualifiedName') { - let qualifiedName = exprName; - while (true) { - if (qualifiedName.left.type === 'Identifier') { - processIdentifier(qualifiedName.left); - return; - } - qualifiedName = qualifiedName.left; + let identifier: Identifier; + if (exprName.type === AST_NODE_TYPES.TSImportType) { + return; + } else if (exprName.type === AST_NODE_TYPES.TSQualifiedName) { + let iter = exprName; + while (iter.left.type === AST_NODE_TYPES.TSQualifiedName) { + iter = iter.left; } + identifier = iter.left; + } else { + identifier = exprName; + } + + if (identifier.name === 'this') { + (identifier.type as string) = AST_NODE_TYPES.ThisExpression; + delete (identifier as { name?: string }).name; } }, }, From df847c1e8d88b82455076041170f98711296a4f2 Mon Sep 17 00:00:00 2001 From: Zzzen Date: Fri, 17 Jun 2022 22:30:58 +0800 Subject: [PATCH 10/13] fix merge error --- packages/scope-manager/src/referencer/TypeVisitor.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/scope-manager/src/referencer/TypeVisitor.ts b/packages/scope-manager/src/referencer/TypeVisitor.ts index bdb30fdbd5b..f3c4dd920d3 100644 --- a/packages/scope-manager/src/referencer/TypeVisitor.ts +++ b/packages/scope-manager/src/referencer/TypeVisitor.ts @@ -266,6 +266,10 @@ class TypeVisitor extends Visitor { } else { identifier = node.exprName; } + if (identifier.type === AST_NODE_TYPES.ThisExpression) { + return; + } + this.#referencer.currentScope().referenceValue(identifier); this.visit(node.typeParameters); } From a60ceeed95e5b2885ccc96fb887e143e0595962e Mon Sep 17 00:00:00 2001 From: Zzzen Date: Sat, 18 Jun 2022 00:18:10 +0800 Subject: [PATCH 11/13] refactor --- packages/scope-manager/src/referencer/TypeVisitor.ts | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/packages/scope-manager/src/referencer/TypeVisitor.ts b/packages/scope-manager/src/referencer/TypeVisitor.ts index f3c4dd920d3..4b39676850d 100644 --- a/packages/scope-manager/src/referencer/TypeVisitor.ts +++ b/packages/scope-manager/src/referencer/TypeVisitor.ts @@ -256,20 +256,19 @@ class TypeVisitor extends Visitor { // a type query `typeof foo` is a special case that references a _non-type_ variable, protected TSTypeQuery(node: TSESTree.TSTypeQuery): void { - let identifier: TSESTree.Identifier | TSESTree.ThisExpression; + let entityName: TSESTree.Identifier | TSESTree.ThisExpression; if (node.exprName.type === AST_NODE_TYPES.TSQualifiedName) { let iter = node.exprName; while (iter.left.type === AST_NODE_TYPES.TSQualifiedName) { iter = iter.left; } - identifier = iter.left; + entityName = iter.left; } else { - identifier = node.exprName; + entityName = node.exprName; } - if (identifier.type === AST_NODE_TYPES.ThisExpression) { - return; + if (entityName.type === AST_NODE_TYPES.Identifier) { + this.#referencer.currentScope().referenceValue(entityName); } - this.#referencer.currentScope().referenceValue(identifier); this.visit(node.typeParameters); } From b9f12b9c2805114c7782f48665ac61fbfb9295e7 Mon Sep 17 00:00:00 2001 From: Zzzen Date: Sat, 18 Jun 2022 12:03:15 +0800 Subject: [PATCH 12/13] refactor --- .../src/referencer/ClassVisitor.ts | 14 +- .../tests/fixtures/decorators/typeof-this.ts | 5 + .../fixtures/decorators/typeof-this.ts.shot | 142 ++++++++++++++++++ 3 files changed, 153 insertions(+), 8 deletions(-) create mode 100644 packages/scope-manager/tests/fixtures/decorators/typeof-this.ts create mode 100644 packages/scope-manager/tests/fixtures/decorators/typeof-this.ts.shot diff --git a/packages/scope-manager/src/referencer/ClassVisitor.ts b/packages/scope-manager/src/referencer/ClassVisitor.ts index 306f064dc8a..92dab53ca90 100644 --- a/packages/scope-manager/src/referencer/ClassVisitor.ts +++ b/packages/scope-manager/src/referencer/ClassVisitor.ts @@ -293,7 +293,7 @@ class ClassVisitor extends Visitor { node.typeAnnotation.type === AST_NODE_TYPES.TSTypeReference && this.#emitDecoratorMetadata ) { - let identifier: TSESTree.Identifier | TSESTree.ThisExpression; + let entityName: TSESTree.Identifier | TSESTree.ThisExpression; if ( node.typeAnnotation.typeName.type === AST_NODE_TYPES.TSQualifiedName ) { @@ -301,17 +301,15 @@ class ClassVisitor extends Visitor { while (iter.left.type === AST_NODE_TYPES.TSQualifiedName) { iter = iter.left; } - identifier = iter.left; + entityName = iter.left; } else { - identifier = node.typeAnnotation.typeName; - } - - if (identifier.type === AST_NODE_TYPES.ThisExpression) { - return; + entityName = node.typeAnnotation.typeName; } if (withDecorators) { - this.#referencer.currentScope().referenceDualValueType(identifier); + if (entityName.type === AST_NODE_TYPES.Identifier) { + this.#referencer.currentScope().referenceDualValueType(entityName); + } if (node.typeAnnotation.typeParameters) { this.visitType(node.typeAnnotation.typeParameters); diff --git a/packages/scope-manager/tests/fixtures/decorators/typeof-this.ts b/packages/scope-manager/tests/fixtures/decorators/typeof-this.ts new file mode 100644 index 00000000000..1b68d92331b --- /dev/null +++ b/packages/scope-manager/tests/fixtures/decorators/typeof-this.ts @@ -0,0 +1,5 @@ +function decorator() {} +@decorator +class Foo { + bar(baz: typeof this) {} +} diff --git a/packages/scope-manager/tests/fixtures/decorators/typeof-this.ts.shot b/packages/scope-manager/tests/fixtures/decorators/typeof-this.ts.shot new file mode 100644 index 00000000000..39c4468e53c --- /dev/null +++ b/packages/scope-manager/tests/fixtures/decorators/typeof-this.ts.shot @@ -0,0 +1,142 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`decorators typeof-this 1`] = ` +ScopeManager { + variables: Array [ + ImplicitGlobalConstTypeVariable, + Variable$2 { + defs: Array [ + FunctionNameDefinition$1 { + name: Identifier<"decorator">, + node: FunctionDeclaration$1, + }, + ], + name: "decorator", + references: Array [ + Reference$1 { + identifier: Identifier<"decorator">, + isRead: true, + isTypeReference: false, + isValueReference: true, + isWrite: false, + resolved: Variable$2, + }, + ], + isValueVariable: true, + isTypeVariable: false, + }, + Variable$3 { + defs: Array [], + name: "arguments", + references: Array [], + isValueVariable: true, + isTypeVariable: true, + }, + Variable$4 { + defs: Array [ + ClassNameDefinition$2 { + name: Identifier<"Foo">, + node: ClassDeclaration$2, + }, + ], + name: "Foo", + references: Array [], + isValueVariable: true, + isTypeVariable: true, + }, + Variable$5 { + defs: Array [ + ClassNameDefinition$3 { + name: Identifier<"Foo">, + node: ClassDeclaration$2, + }, + ], + name: "Foo", + references: Array [], + isValueVariable: true, + isTypeVariable: true, + }, + Variable$6 { + defs: Array [], + name: "arguments", + references: Array [], + isValueVariable: true, + isTypeVariable: true, + }, + Variable$7 { + defs: Array [ + ParameterDefinition$4 { + name: Identifier<"baz">, + node: FunctionExpression$3, + }, + ], + name: "baz", + references: Array [], + isValueVariable: true, + isTypeVariable: false, + }, + ], + scopes: Array [ + GlobalScope$1 { + block: Program$4, + isStrict: false, + references: Array [ + Reference$1, + ], + set: Map { + "const" => ImplicitGlobalConstTypeVariable, + "decorator" => Variable$2, + "Foo" => Variable$4, + }, + type: "global", + upper: null, + variables: Array [ + ImplicitGlobalConstTypeVariable, + Variable$2, + Variable$4, + ], + }, + FunctionScope$2 { + block: FunctionDeclaration$1, + isStrict: false, + references: Array [], + set: Map { + "arguments" => Variable$3, + }, + type: "function", + upper: GlobalScope$1, + variables: Array [ + Variable$3, + ], + }, + ClassScope$3 { + block: ClassDeclaration$2, + isStrict: true, + references: Array [], + set: Map { + "Foo" => Variable$5, + }, + type: "class", + upper: GlobalScope$1, + variables: Array [ + Variable$5, + ], + }, + FunctionScope$4 { + block: FunctionExpression$3, + isStrict: true, + references: Array [], + set: Map { + "arguments" => Variable$6, + "baz" => Variable$7, + }, + type: "function", + upper: ClassScope$3, + variables: Array [ + Variable$6, + Variable$7, + ], + }, + ], +} +`; From d42ecff5073a10c84d9833c24eb31057681b83cb Mon Sep 17 00:00:00 2001 From: Brad Zacher Date: Sun, 26 Jun 2022 13:24:37 -0700 Subject: [PATCH 13/13] apply armano's suggested node type change --- packages/typescript-estree/src/convert.ts | 11 +++++------ .../src/ts-estree/estree-to-ts-node-types.ts | 5 ++++- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/packages/typescript-estree/src/convert.ts b/packages/typescript-estree/src/convert.ts index ea4c753fe99..be09a2b387d 100644 --- a/packages/typescript-estree/src/convert.ts +++ b/packages/typescript-estree/src/convert.ts @@ -801,12 +801,11 @@ export class Converter { case SyntaxKind.Identifier: { if (isThisInTypeQuery(node)) { - return this.createNode( - node as unknown as ts.ThisExpression, - { - type: AST_NODE_TYPES.ThisExpression, - }, - ); + // special case for `typeof this.foo` - TS emits an Identifier for `this` + // but we want to treat it as a ThisExpression for consistency + return this.createNode(node, { + type: AST_NODE_TYPES.ThisExpression, + }); } return this.createNode(node, { type: AST_NODE_TYPES.Identifier, diff --git a/packages/typescript-estree/src/ts-estree/estree-to-ts-node-types.ts b/packages/typescript-estree/src/ts-estree/estree-to-ts-node-types.ts index 666909b5a71..82d5ace2c2a 100644 --- a/packages/typescript-estree/src/ts-estree/estree-to-ts-node-types.ts +++ b/packages/typescript-estree/src/ts-estree/estree-to-ts-node-types.ts @@ -149,7 +149,10 @@ export interface EstreeToTsNodeTypes { [AST_NODE_TYPES.TemplateLiteral]: | ts.NoSubstitutionTemplateLiteral | ts.TemplateExpression; - [AST_NODE_TYPES.ThisExpression]: ts.ThisExpression | ts.KeywordTypeNode; + [AST_NODE_TYPES.ThisExpression]: + | ts.ThisExpression + | ts.KeywordTypeNode + | ts.Identifier; [AST_NODE_TYPES.ThrowStatement]: ts.ThrowStatement; [AST_NODE_TYPES.TryStatement]: ts.TryStatement; [AST_NODE_TYPES.TSAbstractPropertyDefinition]: ts.PropertyDeclaration;