Skip to content

Commit dfedb24

Browse files
jameskeanesandersn
authored andcommitted
Jsdoc @constructor - in constructor properly infer this as class instance (microsoft#25980)
* Properly infer `this` in tagged `@constructor`s. `c.prototype.method = function() { this }` was already supported. This commit add support to two more kinds relying on the JSDoc `@constructor` tag. These are: 1. `/** @constructor */ function Example() { this }` 2. `/** @constructor */ var Example = function() { this }` * Update the baseline for js constructorFunctions. C3 and C4 `this` was set as `any`, now it is properly showing as the class type. * Fix lint errors * Add circular initialisers to constructo fn tests. * Error (`TS2348`) if calling tagged js constructors When calling a JS function explicitly tagged with either `@class` or `@constructor` the checker should throw a TS2348 not callable error. * Don't resolve jsdoc classes with construct sigs. This undoes the last commit that sought to change how js functions tagged with `@class` were inferred. For some reason, currently unknown, giving those functions construct signatures causes issues in property assignment/member resolution (as seen in the `typeFromPropertyAssignment12` test case). Instead of changing the signature resolution, the error is explicitly generated in `resolveCallExpression` for those functions.
1 parent 4821f81 commit dfedb24

File tree

5 files changed

+134
-4
lines changed

5 files changed

+134
-4
lines changed

src/compiler/checker.ts

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15404,8 +15404,8 @@ namespace ts {
1540415404
(!isInParameterInitializerBeforeContainingFunction(node) || getThisParameter(container))) {
1540515405
// Note: a parameter initializer should refer to class-this unless function-this is explicitly annotated.
1540615406

15407-
// If this is a function in a JS file, it might be a class method. Check if it's the RHS
15408-
// of a x.prototype.y = function [name]() { .... }
15407+
// If this is a function in a JS file, it might be a class method.
15408+
// Check if it's the RHS of a x.prototype.y = function [name]() { .... }
1540915409
if (container.kind === SyntaxKind.FunctionExpression &&
1541015410
container.parent.kind === SyntaxKind.BinaryExpression &&
1541115411
getSpecialPropertyAssignmentKind(container.parent as BinaryExpression) === SpecialPropertyAssignmentKind.PrototypeProperty) {
@@ -15419,6 +15419,17 @@ namespace ts {
1541915419
return getFlowTypeOfReference(node, getInferredClassType(classSymbol));
1542015420
}
1542115421
}
15422+
// Check if it's a constructor definition, can be either a variable decl or function decl
15423+
// i.e.
15424+
// * /** @constructor */ function [name]() { ... }
15425+
// * /** @constructor */ var x = function() { ... }
15426+
else if ((container.kind === SyntaxKind.FunctionExpression || container.kind === SyntaxKind.FunctionDeclaration) &&
15427+
getJSDocClassTag(container)) {
15428+
const classType = getJavaScriptClassType(container.symbol);
15429+
if (classType) {
15430+
return getFlowTypeOfReference(node, classType);
15431+
}
15432+
}
1542215433

1542315434
const thisType = getThisTypeOfDeclaration(container) || getContextualThisParameterType(container);
1542415435
if (thisType) {
@@ -19497,6 +19508,11 @@ namespace ts {
1949719508
}
1949819509
return resolveErrorCall(node);
1949919510
}
19511+
// If the function is explicitly marked with `@class`, then it must be constructed.
19512+
if (callSignatures.some(sig => isInJavaScriptFile(sig.declaration) && !!getJSDocClassTag(sig.declaration!))) {
19513+
error(node, Diagnostics.Value_of_type_0_is_not_callable_Did_you_mean_to_include_new, typeToString(funcType));
19514+
return resolveErrorCall(node);
19515+
}
1950019516
return resolveCall(node, callSignatures, candidatesOutArray, isForSignatureHelp);
1950119517
}
1950219518

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
tests/cases/conformance/salsa/index.js(22,15): error TS2348: Value of type 'typeof C3' is not callable. Did you mean to include 'new'?
2+
tests/cases/conformance/salsa/index.js(30,15): error TS2348: Value of type 'typeof C4' is not callable. Did you mean to include 'new'?
3+
4+
5+
==== tests/cases/conformance/salsa/index.js (2 errors) ====
6+
function C1() {
7+
if (!(this instanceof C1)) return new C1();
8+
this.x = 1;
9+
}
10+
11+
const c1_v1 = C1();
12+
const c1_v2 = new C1();
13+
14+
var C2 = function () {
15+
if (!(this instanceof C2)) return new C2();
16+
this.x = 1;
17+
};
18+
19+
const c2_v1 = C2();
20+
const c2_v2 = new C2();
21+
22+
/** @class */
23+
function C3() {
24+
if (!(this instanceof C3)) return new C3();
25+
};
26+
27+
const c3_v1 = C3();
28+
~~~~
29+
!!! error TS2348: Value of type 'typeof C3' is not callable. Did you mean to include 'new'?
30+
const c3_v2 = new C3();
31+
32+
/** @class */
33+
var C4 = function () {
34+
if (!(this instanceof C4)) return new C4();
35+
};
36+
37+
const c4_v1 = C4();
38+
~~~~
39+
!!! error TS2348: Value of type 'typeof C4' is not callable. Did you mean to include 'new'?
40+
const c4_v2 = new C4();
41+
42+
var c5_v1;
43+
c5_v1 = function f() { };
44+
new c5_v1();
45+
46+
var c5_v2;
47+
c5_v2 = class { };
48+
new c5_v2();
49+
50+
/** @class */
51+
function C6() {
52+
this.functions = [x => x, x => x + 1, x => x - 1]
53+
};
54+
55+
var c6_v1 = new C6();
56+

tests/baselines/reference/constructorFunctions.symbols

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ function C3() {
4343
>C3 : Symbol(C3, Decl(index.js, 14, 23))
4444

4545
if (!(this instanceof C3)) return new C3();
46+
>this : Symbol(C3, Decl(index.js, 14, 23))
4647
>C3 : Symbol(C3, Decl(index.js, 14, 23))
4748
>C3 : Symbol(C3, Decl(index.js, 14, 23))
4849

@@ -61,6 +62,7 @@ var C4 = function () {
6162
>C4 : Symbol(C4, Decl(index.js, 25, 3))
6263

6364
if (!(this instanceof C4)) return new C4();
65+
>this : Symbol(C4, Decl(index.js, 25, 8))
6466
>C4 : Symbol(C4, Decl(index.js, 25, 3))
6567
>C4 : Symbol(C4, Decl(index.js, 25, 3))
6668

@@ -93,4 +95,24 @@ c5_v2 = class { };
9395
new c5_v2();
9496
>c5_v2 : Symbol(c5_v2, Decl(index.js, 36, 3))
9597

98+
/** @class */
99+
function C6() {
100+
>C6 : Symbol(C6, Decl(index.js, 38, 12))
101+
102+
this.functions = [x => x, x => x + 1, x => x - 1]
103+
>this.functions : Symbol(C6.functions, Decl(index.js, 41, 15))
104+
>this : Symbol(C6, Decl(index.js, 38, 12))
105+
>functions : Symbol(C6.functions, Decl(index.js, 41, 15))
106+
>x : Symbol(x, Decl(index.js, 42, 20))
107+
>x : Symbol(x, Decl(index.js, 42, 20))
108+
>x : Symbol(x, Decl(index.js, 42, 27))
109+
>x : Symbol(x, Decl(index.js, 42, 27))
110+
>x : Symbol(x, Decl(index.js, 42, 39))
111+
>x : Symbol(x, Decl(index.js, 42, 39))
112+
113+
};
114+
115+
var c6_v1 = new C6();
116+
>c6_v1 : Symbol(c6_v1, Decl(index.js, 45, 3))
117+
>C6 : Symbol(C6, Decl(index.js, 38, 12))
96118

tests/baselines/reference/constructorFunctions.types

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ function C3() {
6969
>!(this instanceof C3) : boolean
7070
>(this instanceof C3) : boolean
7171
>this instanceof C3 : boolean
72-
>this : any
72+
>this : C3
7373
>C3 : typeof C3
7474
>new C3() : C3
7575
>C3 : typeof C3
@@ -95,7 +95,7 @@ var C4 = function () {
9595
>!(this instanceof C4) : boolean
9696
>(this instanceof C4) : boolean
9797
>this instanceof C4 : boolean
98-
>this : any
98+
>this : C4
9999
>C4 : typeof C4
100100
>new C4() : C4
101101
>C4 : typeof C4
@@ -137,4 +137,34 @@ new c5_v2();
137137
>new c5_v2() : c5_v2
138138
>c5_v2 : typeof c5_v2
139139

140+
/** @class */
141+
function C6() {
142+
>C6 : typeof C6
143+
144+
this.functions = [x => x, x => x + 1, x => x - 1]
145+
>this.functions = [x => x, x => x + 1, x => x - 1] : ((x: any) => any)[]
146+
>this.functions : ((x: any) => any)[]
147+
>this : C6
148+
>functions : ((x: any) => any)[]
149+
>[x => x, x => x + 1, x => x - 1] : ((x: any) => any)[]
150+
>x => x : (x: any) => any
151+
>x : any
152+
>x : any
153+
>x => x + 1 : (x: any) => any
154+
>x : any
155+
>x + 1 : any
156+
>x : any
157+
>1 : 1
158+
>x => x - 1 : (x: any) => number
159+
>x : any
160+
>x - 1 : number
161+
>x : any
162+
>1 : 1
163+
164+
};
165+
166+
var c6_v1 = new C6();
167+
>c6_v1 : C6
168+
>new C6() : C6
169+
>C6 : typeof C6
140170

tests/cases/conformance/salsa/constructorFunctions.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,9 @@ var c5_v2;
4343
c5_v2 = class { };
4444
new c5_v2();
4545

46+
/** @class */
47+
function C6() {
48+
this.functions = [x => x, x => x + 1, x => x - 1]
49+
};
50+
51+
var c6_v1 = new C6();

0 commit comments

Comments
 (0)