Skip to content

Commit 471e680

Browse files
committed
Better types from jsdoc param tags
1 parent 70c1c57 commit 471e680

File tree

4 files changed

+538
-34
lines changed

4 files changed

+538
-34
lines changed

src/compiler/checker.ts

Lines changed: 39 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -6814,37 +6814,53 @@ namespace ts {
68146814
return undefined;
68156815
}
68166816

6817-
function resolveTypeReferenceName(typeReferenceName: EntityNameExpression | EntityName) {
6817+
function resolveTypeReferenceName(node: TypeReferenceType, typeReferenceName: EntityNameExpression | EntityName) {
68186818
if (!typeReferenceName) {
68196819
return unknownSymbol;
68206820
}
68216821

6822-
return resolveEntityName(typeReferenceName, SymbolFlags.Type) || unknownSymbol;
6822+
const meaning = node.kind === SyntaxKind.JSDocTypeReference
6823+
? SymbolFlags.Type | SymbolFlags.Value
6824+
: SymbolFlags.Type;
6825+
6826+
return resolveEntityName(typeReferenceName, meaning) || unknownSymbol;
68236827
}
68246828

68256829
function getTypeReferenceType(node: TypeReferenceType, symbol: Symbol) {
68266830
const typeArguments = typeArgumentsFromTypeReferenceNode(node); // Do unconditionally so we mark type arguments as referenced.
6831+
let fallbackType: Type = unknownType;
6832+
while (true) {
6833+
if (symbol === unknownSymbol) {
6834+
return fallbackType;
6835+
}
68276836

6828-
if (symbol === unknownSymbol) {
6829-
return unknownType;
6830-
}
6837+
if (symbol.flags & (SymbolFlags.Class | SymbolFlags.Interface)) {
6838+
return getTypeFromClassOrInterfaceReference(node, symbol, typeArguments);
6839+
}
68316840

6832-
if (symbol.flags & (SymbolFlags.Class | SymbolFlags.Interface)) {
6833-
return getTypeFromClassOrInterfaceReference(node, symbol, typeArguments);
6834-
}
6841+
if (symbol.flags & SymbolFlags.TypeAlias) {
6842+
return getTypeFromTypeAliasReference(node, symbol, typeArguments);
6843+
}
68356844

6836-
if (symbol.flags & SymbolFlags.TypeAlias) {
6837-
return getTypeFromTypeAliasReference(node, symbol, typeArguments);
6838-
}
6845+
if (symbol.flags & SymbolFlags.Value && node.kind === SyntaxKind.JSDocTypeReference) {
6846+
// A JSDocTypeReference may have resolved to a value (as opposed to a type). If
6847+
// the value has a construct signature, we use the return type of the construct
6848+
// signature as the type; otherwise, the type of this reference is just the type
6849+
// of the value we resolved to.
6850+
if (symbol.flags & SymbolFlags.Function && (symbol.members || getJSDocClassTag(symbol.valueDeclaration))) {
6851+
return getInferredClassType(symbol);
6852+
}
68396853

6840-
if (symbol.flags & SymbolFlags.Value && node.kind === SyntaxKind.JSDocTypeReference) {
6841-
// A JSDocTypeReference may have resolved to a value (as opposed to a type). In
6842-
// that case, the type of this reference is just the type of the value we resolved
6843-
// to.
6844-
return getTypeOfSymbol(symbol);
6845-
}
6854+
fallbackType = getTypeOfSymbol(symbol);
6855+
6856+
// Try to use the symbol of the type (if present) to get a better type on the
6857+
// next pass.
6858+
symbol = fallbackType.symbol || unknownSymbol;
6859+
continue;
6860+
}
68466861

6847-
return getTypeFromNonGenericTypeReference(node, symbol);
6862+
return getTypeFromNonGenericTypeReference(node, symbol);
6863+
}
68486864
}
68496865

68506866
function getPrimitiveTypeFromJSDocTypeReference(node: JSDocTypeReference): Type {
@@ -6888,21 +6904,10 @@ namespace ts {
68886904
let symbol: Symbol;
68896905
let type: Type;
68906906
if (node.kind === SyntaxKind.JSDocTypeReference) {
6891-
type = getPrimitiveTypeFromJSDocTypeReference(<JSDocTypeReference>node);
6892-
if (!type) {
6893-
const typeReferenceName = getTypeReferenceName(node);
6894-
symbol = resolveTypeReferenceName(typeReferenceName);
6895-
type = getTypeReferenceType(node, symbol);
6896-
}
6907+
type = getPrimitiveTypeFromJSDocTypeReference(node);
68976908
}
6898-
else {
6899-
// We only support expressions that are simple qualified names. For other expressions this produces undefined.
6900-
const typeNameOrExpression: EntityNameOrEntityNameExpression = node.kind === SyntaxKind.TypeReference
6901-
? (<TypeReferenceNode>node).typeName
6902-
: isEntityNameExpression((<ExpressionWithTypeArguments>node).expression)
6903-
? <EntityNameExpression>(<ExpressionWithTypeArguments>node).expression
6904-
: undefined;
6905-
symbol = typeNameOrExpression && resolveEntityName(typeNameOrExpression, SymbolFlags.Type) || unknownSymbol;
6909+
if (!type) {
6910+
symbol = resolveTypeReferenceName(node, getTypeReferenceName(node));
69066911
type = getTypeReferenceType(node, symbol);
69076912
}
69086913
// Cache both the resolved symbol and the resolved type. The resolved symbol is needed in when we check the
@@ -19367,8 +19372,8 @@ namespace ts {
1936719372

1936819373
function checkFunctionDeclaration(node: FunctionDeclaration): void {
1936919374
if (produceDiagnostics) {
19370-
checkFunctionOrMethodDeclaration(node) || checkGrammarForGenerator(node);
19371-
19375+
checkFunctionOrMethodDeclaration(node);
19376+
checkGrammarForGenerator(node);
1937219377
checkCollisionWithCapturedSuperVariable(node, node.name);
1937319378
checkCollisionWithCapturedThisVariable(node, node.name);
1937419379
checkCollisionWithCapturedNewTargetVariable(node, node.name);
Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
=== tests/cases/conformance/salsa/node.d.ts ===
2+
declare function require(id: string): any;
3+
>require : Symbol(require, Decl(node.d.ts, 0, 0))
4+
>id : Symbol(id, Decl(node.d.ts, 0, 25))
5+
6+
declare var module: any, exports: any;
7+
>module : Symbol(module, Decl(node.d.ts, 1, 11))
8+
>exports : Symbol(exports, Decl(node.d.ts, 1, 24))
9+
10+
=== tests/cases/conformance/salsa/a-ext.js ===
11+
exports.A = function () {
12+
>exports : Symbol(A, Decl(a-ext.js, 0, 0))
13+
>A : Symbol(A, Decl(a-ext.js, 0, 0))
14+
15+
this.x = 1;
16+
>x : Symbol((Anonymous function).x, Decl(a-ext.js, 0, 25))
17+
18+
};
19+
20+
=== tests/cases/conformance/salsa/a.js ===
21+
const { A } = require("./a-ext");
22+
>A : Symbol(A, Decl(a.js, 0, 7))
23+
>require : Symbol(require, Decl(node.d.ts, 0, 0))
24+
>"./a-ext" : Symbol("tests/cases/conformance/salsa/a-ext", Decl(a-ext.js, 0, 0))
25+
26+
/** @param {A} p */
27+
function a(p) { p.x; }
28+
>a : Symbol(a, Decl(a.js, 0, 33))
29+
>p : Symbol(p, Decl(a.js, 3, 11))
30+
>p.x : Symbol((Anonymous function).x, Decl(a-ext.js, 0, 25))
31+
>p : Symbol(p, Decl(a.js, 3, 11))
32+
>x : Symbol((Anonymous function).x, Decl(a-ext.js, 0, 25))
33+
34+
=== tests/cases/conformance/salsa/b-ext.js ===
35+
exports.B = class {
36+
>exports : Symbol(B, Decl(b-ext.js, 0, 0))
37+
>B : Symbol(B, Decl(b-ext.js, 0, 0))
38+
39+
constructor() {
40+
this.x = 1;
41+
>this.x : Symbol((Anonymous class).x, Decl(b-ext.js, 1, 19))
42+
>this : Symbol((Anonymous class), Decl(b-ext.js, 0, 11))
43+
>x : Symbol((Anonymous class).x, Decl(b-ext.js, 1, 19))
44+
}
45+
};
46+
47+
=== tests/cases/conformance/salsa/b.js ===
48+
const { B } = require("./b-ext");
49+
>B : Symbol(B, Decl(b.js, 0, 7))
50+
>require : Symbol(require, Decl(node.d.ts, 0, 0))
51+
>"./b-ext" : Symbol("tests/cases/conformance/salsa/b-ext", Decl(b-ext.js, 0, 0))
52+
53+
/** @param {B} p */
54+
function b(p) { p.x; }
55+
>b : Symbol(b, Decl(b.js, 0, 33))
56+
>p : Symbol(p, Decl(b.js, 3, 11))
57+
>p.x : Symbol((Anonymous class).x, Decl(b-ext.js, 1, 19))
58+
>p : Symbol(p, Decl(b.js, 3, 11))
59+
>x : Symbol((Anonymous class).x, Decl(b-ext.js, 1, 19))
60+
61+
=== tests/cases/conformance/salsa/c-ext.js ===
62+
export function C() {
63+
>C : Symbol(C, Decl(c-ext.js, 0, 0))
64+
65+
this.x = 1;
66+
>x : Symbol(C.x, Decl(c-ext.js, 0, 21))
67+
}
68+
69+
=== tests/cases/conformance/salsa/c.js ===
70+
const { C } = require("./c-ext");
71+
>C : Symbol(C, Decl(c.js, 0, 7))
72+
>require : Symbol(require, Decl(node.d.ts, 0, 0))
73+
>"./c-ext" : Symbol("tests/cases/conformance/salsa/c-ext", Decl(c-ext.js, 0, 0))
74+
75+
/** @param {C} p */
76+
function c(p) { p.x; }
77+
>c : Symbol(c, Decl(c.js, 0, 33))
78+
>p : Symbol(p, Decl(c.js, 3, 11))
79+
>p.x : Symbol(C.x, Decl(c-ext.js, 0, 21))
80+
>p : Symbol(p, Decl(c.js, 3, 11))
81+
>x : Symbol(C.x, Decl(c-ext.js, 0, 21))
82+
83+
=== tests/cases/conformance/salsa/d-ext.js ===
84+
export var D = function() {
85+
>D : Symbol(D, Decl(d-ext.js, 0, 10))
86+
87+
this.x = 1;
88+
>x : Symbol(D.x, Decl(d-ext.js, 0, 27))
89+
90+
};
91+
92+
=== tests/cases/conformance/salsa/d.js ===
93+
const { D } = require("./d-ext");
94+
>D : Symbol(D, Decl(d.js, 0, 7))
95+
>require : Symbol(require, Decl(node.d.ts, 0, 0))
96+
>"./d-ext" : Symbol("tests/cases/conformance/salsa/d-ext", Decl(d-ext.js, 0, 0))
97+
98+
/** @param {D} p */
99+
function d(p) { p.x; }
100+
>d : Symbol(d, Decl(d.js, 0, 33))
101+
>p : Symbol(p, Decl(d.js, 3, 11))
102+
>p.x : Symbol(D.x, Decl(d-ext.js, 0, 27))
103+
>p : Symbol(p, Decl(d.js, 3, 11))
104+
>x : Symbol(D.x, Decl(d-ext.js, 0, 27))
105+
106+
=== tests/cases/conformance/salsa/e-ext.js ===
107+
export class E {
108+
>E : Symbol(E, Decl(e-ext.js, 0, 0))
109+
110+
constructor() {
111+
this.x = 1;
112+
>this.x : Symbol(E.x, Decl(e-ext.js, 1, 19))
113+
>this : Symbol(E, Decl(e-ext.js, 0, 0))
114+
>x : Symbol(E.x, Decl(e-ext.js, 1, 19))
115+
}
116+
}
117+
118+
=== tests/cases/conformance/salsa/e.js ===
119+
const { E } = require("./e-ext");
120+
>E : Symbol(E, Decl(e.js, 0, 7))
121+
>require : Symbol(require, Decl(node.d.ts, 0, 0))
122+
>"./e-ext" : Symbol("tests/cases/conformance/salsa/e-ext", Decl(e-ext.js, 0, 0))
123+
124+
/** @param {E} p */
125+
function e(p) { p.x; }
126+
>e : Symbol(e, Decl(e.js, 0, 33))
127+
>p : Symbol(p, Decl(e.js, 3, 11))
128+
>p.x : Symbol(E.x, Decl(e-ext.js, 1, 19))
129+
>p : Symbol(p, Decl(e.js, 3, 11))
130+
>x : Symbol(E.x, Decl(e-ext.js, 1, 19))
131+
132+
=== tests/cases/conformance/salsa/f.js ===
133+
var F = function () {
134+
>F : Symbol(F, Decl(f.js, 0, 3))
135+
136+
this.x = 1;
137+
>x : Symbol(F.x, Decl(f.js, 0, 21))
138+
139+
};
140+
141+
/** @param {F} p */
142+
function f(p) { p.x; }
143+
>f : Symbol(f, Decl(f.js, 2, 2))
144+
>p : Symbol(p, Decl(f.js, 5, 11))
145+
>p.x : Symbol(F.x, Decl(f.js, 0, 21))
146+
>p : Symbol(p, Decl(f.js, 5, 11))
147+
>x : Symbol(F.x, Decl(f.js, 0, 21))
148+
149+
=== tests/cases/conformance/salsa/g.js ===
150+
function G() {
151+
>G : Symbol(G, Decl(g.js, 0, 0))
152+
153+
this.x = 1;
154+
>x : Symbol(G.x, Decl(g.js, 0, 14))
155+
}
156+
157+
/** @param {G} p */
158+
function g(p) { p.x; }
159+
>g : Symbol(g, Decl(g.js, 2, 1))
160+
>p : Symbol(p, Decl(g.js, 5, 11))
161+
>p.x : Symbol(G.x, Decl(g.js, 0, 14))
162+
>p : Symbol(p, Decl(g.js, 5, 11))
163+
>x : Symbol(G.x, Decl(g.js, 0, 14))
164+
165+
=== tests/cases/conformance/salsa/h.js ===
166+
class H {
167+
>H : Symbol(H, Decl(h.js, 0, 0))
168+
169+
constructor() {
170+
this.x = 1;
171+
>this.x : Symbol(H.x, Decl(h.js, 1, 19))
172+
>this : Symbol(H, Decl(h.js, 0, 0))
173+
>x : Symbol(H.x, Decl(h.js, 1, 19))
174+
}
175+
}
176+
177+
/** @param {H} p */
178+
function h(p) { p.x; }
179+
>h : Symbol(h, Decl(h.js, 4, 1))
180+
>p : Symbol(p, Decl(h.js, 7, 11))
181+
>p.x : Symbol(H.x, Decl(h.js, 1, 19))
182+
>p : Symbol(p, Decl(h.js, 7, 11))
183+
>x : Symbol(H.x, Decl(h.js, 1, 19))
184+

0 commit comments

Comments
 (0)