From df40c7ae883983f522b33be4a7dd87e2ecafac09 Mon Sep 17 00:00:00 2001 From: Oleksandr T Date: Tue, 27 Jun 2023 01:00:10 +0300 Subject: [PATCH] fix(51868): this within function typed as any when annotated with JSDoc (#54516) --- src/compiler/checker.ts | 15 ++-- .../reference/jsdocThisType.errors.txt | 58 ++++++++++++++ .../baselines/reference/jsdocThisType.symbols | 62 ++++++++++++++ tests/baselines/reference/jsdocThisType.types | 80 +++++++++++++++++++ .../cases/conformance/jsdoc/jsdocThisType.ts | 41 ++++++++++ 5 files changed, 246 insertions(+), 10 deletions(-) create mode 100644 tests/baselines/reference/jsdocThisType.errors.txt create mode 100644 tests/baselines/reference/jsdocThisType.symbols create mode 100644 tests/baselines/reference/jsdocThisType.types create mode 100644 tests/cases/conformance/jsdoc/jsdocThisType.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 4ffc5dc978b..d50ecd9b789 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -28473,20 +28473,15 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } } - function getTypeForThisExpressionFromJSDoc(node: Node) { - const jsdocType = getJSDocType(node); - if (jsdocType && jsdocType.kind === SyntaxKind.JSDocFunctionType) { - const jsDocFunctionType = jsdocType as JSDocFunctionType; - if (jsDocFunctionType.parameters.length > 0 && - jsDocFunctionType.parameters[0].name && - (jsDocFunctionType.parameters[0].name as Identifier).escapedText === InternalSymbolName.This) { - return getTypeFromTypeNode(jsDocFunctionType.parameters[0].type!); - } - } + function getTypeForThisExpressionFromJSDoc(node: SignatureDeclaration) { const thisTag = getJSDocThisTag(node); if (thisTag && thisTag.typeExpression) { return getTypeFromTypeNode(thisTag.typeExpression); } + const signature = getSignatureOfTypeTag(node); + if (signature) { + return getThisTypeOfSignature(signature); + } } function isInConstructorArgumentInitializer(node: Node, constructorDecl: Node): boolean { diff --git a/tests/baselines/reference/jsdocThisType.errors.txt b/tests/baselines/reference/jsdocThisType.errors.txt new file mode 100644 index 00000000000..bf0285b62b7 --- /dev/null +++ b/tests/baselines/reference/jsdocThisType.errors.txt @@ -0,0 +1,58 @@ +/a.js(3,10): error TS2339: Property 'test' does not exist on type 'Foo'. +/a.js(8,10): error TS2339: Property 'test' does not exist on type 'Foo'. +/a.js(13,10): error TS2339: Property 'test' does not exist on type 'Foo'. +/a.js(18,10): error TS2339: Property 'test' does not exist on type 'Foo'. +/a.js(23,10): error TS2339: Property 'test' does not exist on type 'Foo'. +/a.js(28,10): error TS2339: Property 'test' does not exist on type 'Foo'. + + +==== /types.d.ts (0 errors) ==== + export interface Foo { + foo: () => void; + } + + export type M = (this: Foo) => void; + +==== /a.js (6 errors) ==== + /** @type {import('./types').M} */ + export const f1 = function() { + this.test(); + ~~~~ +!!! error TS2339: Property 'test' does not exist on type 'Foo'. + } + + /** @type {import('./types').M} */ + export function f2() { + this.test(); + ~~~~ +!!! error TS2339: Property 'test' does not exist on type 'Foo'. + } + + /** @type {(this: import('./types').Foo) => void} */ + export const f3 = function() { + this.test(); + ~~~~ +!!! error TS2339: Property 'test' does not exist on type 'Foo'. + } + + /** @type {(this: import('./types').Foo) => void} */ + export function f4() { + this.test(); + ~~~~ +!!! error TS2339: Property 'test' does not exist on type 'Foo'. + } + + /** @type {function(this: import('./types').Foo): void} */ + export const f5 = function() { + this.test(); + ~~~~ +!!! error TS2339: Property 'test' does not exist on type 'Foo'. + } + + /** @type {function(this: import('./types').Foo): void} */ + export function f6() { + this.test(); + ~~~~ +!!! error TS2339: Property 'test' does not exist on type 'Foo'. + } + \ No newline at end of file diff --git a/tests/baselines/reference/jsdocThisType.symbols b/tests/baselines/reference/jsdocThisType.symbols new file mode 100644 index 00000000000..23f0a9f2e5c --- /dev/null +++ b/tests/baselines/reference/jsdocThisType.symbols @@ -0,0 +1,62 @@ +=== /types.d.ts === +export interface Foo { +>Foo : Symbol(Foo, Decl(types.d.ts, 0, 0)) + + foo: () => void; +>foo : Symbol(Foo.foo, Decl(types.d.ts, 0, 22)) +} + +export type M = (this: Foo) => void; +>M : Symbol(M, Decl(types.d.ts, 2, 1)) +>this : Symbol(this, Decl(types.d.ts, 4, 17)) +>Foo : Symbol(Foo, Decl(types.d.ts, 0, 0)) + +=== /a.js === +/** @type {import('./types').M} */ +export const f1 = function() { +>f1 : Symbol(f1, Decl(a.js, 1, 12)) + + this.test(); +>this : Symbol(this, Decl(types.d.ts, 4, 17)) +} + +/** @type {import('./types').M} */ +export function f2() { +>f2 : Symbol(f2, Decl(a.js, 3, 1)) + + this.test(); +>this : Symbol(Foo, Decl(types.d.ts, 0, 0)) +} + +/** @type {(this: import('./types').Foo) => void} */ +export const f3 = function() { +>f3 : Symbol(f3, Decl(a.js, 11, 12)) + + this.test(); +>this : Symbol(this, Decl(a.js, 10, 12)) +} + +/** @type {(this: import('./types').Foo) => void} */ +export function f4() { +>f4 : Symbol(f4, Decl(a.js, 13, 1)) + + this.test(); +>this : Symbol(Foo, Decl(types.d.ts, 0, 0)) +} + +/** @type {function(this: import('./types').Foo): void} */ +export const f5 = function() { +>f5 : Symbol(f5, Decl(a.js, 21, 12)) + + this.test(); +>this : Symbol(this, Decl(a.js, 20, 20)) +} + +/** @type {function(this: import('./types').Foo): void} */ +export function f6() { +>f6 : Symbol(f6, Decl(a.js, 23, 1)) + + this.test(); +>this : Symbol(Foo, Decl(types.d.ts, 0, 0)) +} + diff --git a/tests/baselines/reference/jsdocThisType.types b/tests/baselines/reference/jsdocThisType.types new file mode 100644 index 00000000000..27df6d6c6b0 --- /dev/null +++ b/tests/baselines/reference/jsdocThisType.types @@ -0,0 +1,80 @@ +=== /types.d.ts === +export interface Foo { + foo: () => void; +>foo : () => void +} + +export type M = (this: Foo) => void; +>M : (this: Foo) => void +>this : Foo + +=== /a.js === +/** @type {import('./types').M} */ +export const f1 = function() { +>f1 : import("/types").M +>function() { this.test();} : (this: import("/types").Foo) => void + + this.test(); +>this.test() : any +>this.test : any +>this : import("/types").Foo +>test : any +} + +/** @type {import('./types').M} */ +export function f2() { +>f2 : (this: import("/types").Foo) => void + + this.test(); +>this.test() : any +>this.test : any +>this : import("/types").Foo +>test : any +} + +/** @type {(this: import('./types').Foo) => void} */ +export const f3 = function() { +>f3 : (this: import("/types").Foo) => void +>function() { this.test();} : (this: import("/types").Foo) => void + + this.test(); +>this.test() : any +>this.test : any +>this : import("/types").Foo +>test : any +} + +/** @type {(this: import('./types').Foo) => void} */ +export function f4() { +>f4 : (this: import('./types').Foo) => void + + this.test(); +>this.test() : any +>this.test : any +>this : import("/types").Foo +>test : any +} + +/** @type {function(this: import('./types').Foo): void} */ +export const f5 = function() { +>f5 : (this: import("/types").Foo) => void +>function() { this.test();} : (this: import("/types").Foo) => void + + this.test(); +>this.test() : any +>this.test : any +>this : import("/types").Foo +>test : any +} + +/** @type {function(this: import('./types').Foo): void} */ +export function f6() { +>f6 : (this: import('./types').Foo) => void + + this.test(); +>this.test() : any +>this.test : any +>this : import("/types").Foo +>test : any +} + diff --git a/tests/cases/conformance/jsdoc/jsdocThisType.ts b/tests/cases/conformance/jsdoc/jsdocThisType.ts new file mode 100644 index 00000000000..16e49b1fef8 --- /dev/null +++ b/tests/cases/conformance/jsdoc/jsdocThisType.ts @@ -0,0 +1,41 @@ +// @allowJs: true +// @checkJs: true +// @noEmit: true + +// @filename: /types.d.ts +export interface Foo { + foo: () => void; +} + +export type M = (this: Foo) => void; + +// @filename: /a.js +/** @type {import('./types').M} */ +export const f1 = function() { + this.test(); +} + +/** @type {import('./types').M} */ +export function f2() { + this.test(); +} + +/** @type {(this: import('./types').Foo) => void} */ +export const f3 = function() { + this.test(); +} + +/** @type {(this: import('./types').Foo) => void} */ +export function f4() { + this.test(); +} + +/** @type {function(this: import('./types').Foo): void} */ +export const f5 = function() { + this.test(); +} + +/** @type {function(this: import('./types').Foo): void} */ +export function f6() { + this.test(); +}