Skip to content

Commit aa5af56

Browse files
committed
Fix precedence of @this
Previously, both `@class` and `@this` in a jsdoc would cause the `@this` annotation to be ignored. This became a worse problem with this PR, because `this` is correctly typed even without the annotation. This commit makes sure that `@this` is checked first and used if present.
1 parent 21daffd commit aa5af56

7 files changed

+73
-19
lines changed

src/compiler/checker.ts

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -22436,21 +22436,23 @@ namespace ts {
2243622436
const isInJS = isInJSFile(node);
2243722437
if (isFunctionLike(container) &&
2243822438
(!isInParameterInitializerBeforeContainingFunction(node) || getThisParameter(container))) {
22439-
let thisType: Type | undefined;
22439+
let thisType = getThisTypeOfDeclaration(container) || isInJS && getTypeForThisExpressionFromJSDoc(container);
2244022440
// Note: a parameter initializer should refer to class-this unless function-this is explicitly annotated.
2244122441
// If this is a function in a JS file, it might be a class method.
22442-
const className = getClassNameFromPrototypeMethod(container);
22443-
if (isInJS && className) {
22444-
const classSymbol = checkExpression(className).symbol;
22445-
if (classSymbol && classSymbol.members && (classSymbol.flags & SymbolFlags.Function)) {
22446-
thisType = (getDeclaredTypeOfSymbol(classSymbol) as InterfaceType).thisType;
22442+
if (!thisType) {
22443+
const className = getClassNameFromPrototypeMethod(container);
22444+
if (isInJS && className) {
22445+
const classSymbol = checkExpression(className).symbol;
22446+
if (classSymbol && classSymbol.members && (classSymbol.flags & SymbolFlags.Function)) {
22447+
thisType = (getDeclaredTypeOfSymbol(classSymbol) as InterfaceType).thisType;
22448+
}
2244722449
}
22448-
}
22449-
else if (isJSConstructor(container)) {
22450-
thisType = (getDeclaredTypeOfSymbol(getMergedSymbol(container.symbol)) as InterfaceType).thisType;
22450+
else if (isJSConstructor(container)) {
22451+
thisType = (getDeclaredTypeOfSymbol(getMergedSymbol(container.symbol)) as InterfaceType).thisType;
22452+
}
22453+
thisType ||= getContextualThisParameterType(container);
2245122454
}
2245222455

22453-
thisType ||= getThisTypeOfDeclaration(container) || getContextualThisParameterType(container);
2245422456
if (thisType) {
2245522457
return getFlowTypeOfReference(node, thisType);
2245622458
}
@@ -22462,12 +22464,6 @@ namespace ts {
2246222464
return getFlowTypeOfReference(node, type);
2246322465
}
2246422466

22465-
if (isInJS) {
22466-
const type = getTypeForThisExpressionFromJSDoc(container);
22467-
if (type && type !== errorType) {
22468-
return getFlowTypeOfReference(node, type);
22469-
}
22470-
}
2247122467
if (isSourceFile(container)) {
2247222468
// look up in the source file's locals or exports
2247322469
if (container.commonJsModuleIndicator) {

tests/baselines/reference/assignmentToVoidZero2.errors.txt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
tests/cases/conformance/salsa/assignmentToVoidZero2.js(2,9): error TS2339: Property 'k' does not exist on type 'typeof import("tests/cases/conformance/salsa/assignmentToVoidZero2")'.
22
tests/cases/conformance/salsa/assignmentToVoidZero2.js(5,3): error TS2339: Property 'y' does not exist on type 'typeof o'.
33
tests/cases/conformance/salsa/assignmentToVoidZero2.js(6,9): error TS2339: Property 'y' does not exist on type 'typeof o'.
4+
tests/cases/conformance/salsa/assignmentToVoidZero2.js(10,10): error TS2339: Property 'q' does not exist on type 'C'.
45
tests/cases/conformance/salsa/assignmentToVoidZero2.js(13,9): error TS2339: Property 'q' does not exist on type 'C'.
56
tests/cases/conformance/salsa/importer.js(1,13): error TS2305: Module '"./assignmentToVoidZero2"' has no exported member 'k'.
67

78

8-
==== tests/cases/conformance/salsa/assignmentToVoidZero2.js (4 errors) ====
9+
==== tests/cases/conformance/salsa/assignmentToVoidZero2.js (5 errors) ====
910
exports.j = 1;
1011
exports.k = void 0;
1112
~
@@ -22,6 +23,8 @@ tests/cases/conformance/salsa/importer.js(1,13): error TS2305: Module '"./assign
2223
function C() {
2324
this.p = 1
2425
this.q = void 0
26+
~
27+
!!! error TS2339: Property 'q' does not exist on type 'C'.
2528
}
2629
var c = new C()
2730
c.p + c.q

tests/baselines/reference/assignmentToVoidZero2.symbols

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,12 @@ function C() {
2828
>C : Symbol(C, Decl(assignmentToVoidZero2.js, 5, 9))
2929

3030
this.p = 1
31+
>this.p : Symbol(C.p, Decl(assignmentToVoidZero2.js, 7, 14))
32+
>this : Symbol(C, Decl(assignmentToVoidZero2.js, 5, 9))
3133
>p : Symbol(C.p, Decl(assignmentToVoidZero2.js, 7, 14))
3234

3335
this.q = void 0
36+
>this : Symbol(C, Decl(assignmentToVoidZero2.js, 5, 9))
3437
}
3538
var c = new C()
3639
>c : Symbol(c, Decl(assignmentToVoidZero2.js, 11, 3))

tests/baselines/reference/assignmentToVoidZero2.types

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,14 +48,14 @@ function C() {
4848
this.p = 1
4949
>this.p = 1 : 1
5050
>this.p : any
51-
>this : any
51+
>this : this
5252
>p : any
5353
>1 : 1
5454

5555
this.q = void 0
5656
>this.q = void 0 : undefined
5757
>this.q : any
58-
>this : any
58+
>this : this
5959
>q : any
6060
>void 0 : undefined
6161
>0 : 0
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
=== tests/cases/conformance/jsdoc/classthisboth.js ===
2+
/**
3+
* @class
4+
* @this {{ e: number, m: number }}
5+
* this-tag should win, both 'e' and 'm' should be defined.
6+
*/
7+
function C() {
8+
>C : Symbol(C, Decl(classthisboth.js, 0, 0))
9+
10+
this.e = this.m + 1
11+
>this.e : Symbol(e, Decl(classthisboth.js, 2, 11))
12+
>this : Symbol(__type, Decl(classthisboth.js, 2, 10))
13+
>e : Symbol(C.e, Decl(classthisboth.js, 5, 14))
14+
>this.m : Symbol(m, Decl(classthisboth.js, 2, 22))
15+
>this : Symbol(__type, Decl(classthisboth.js, 2, 10))
16+
>m : Symbol(m, Decl(classthisboth.js, 2, 22))
17+
}
18+
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
=== tests/cases/conformance/jsdoc/classthisboth.js ===
2+
/**
3+
* @class
4+
* @this {{ e: number, m: number }}
5+
* this-tag should win, both 'e' and 'm' should be defined.
6+
*/
7+
function C() {
8+
>C : typeof C
9+
10+
this.e = this.m + 1
11+
>this.e = this.m + 1 : number
12+
>this.e : number
13+
>this : { e: number; m: number; }
14+
>e : number
15+
>this.m + 1 : number
16+
>this.m : number
17+
>this : { e: number; m: number; }
18+
>m : number
19+
>1 : 1
20+
}
21+
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// @allowJs: true
2+
// @noEmit: true
3+
// @checkJs: true
4+
// @Filename: classthisboth.js
5+
6+
/**
7+
* @class
8+
* @this {{ e: number, m: number }}
9+
* this-tag should win, both 'e' and 'm' should be defined.
10+
*/
11+
function C() {
12+
this.e = this.m + 1
13+
}

0 commit comments

Comments
 (0)