Skip to content

Commit 3d9187f

Browse files
committed
Fix name resolution in typedef and allow defaults for template tags
1 parent 339ad92 commit 3d9187f

12 files changed

+462
-6
lines changed

src/compiler/binder.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -3388,7 +3388,7 @@ namespace ts {
33883388

33893389
function bindTypeParameter(node: TypeParameterDeclaration) {
33903390
if (isJSDocTemplateTag(node.parent)) {
3391-
const container = find((node.parent.parent as JSDoc).tags!, isJSDocTypeAlias) || getHostSignatureFromJSDoc(node.parent); // TODO: GH#18217
3391+
const container = getEffectiveContainerForJSDocTemplateTag(node.parent);
33923392
if (container) {
33933393
if (!container.locals) {
33943394
container.locals = createSymbolTable();

src/compiler/checker.ts

+6-3
Original file line numberDiff line numberDiff line change
@@ -2045,7 +2045,9 @@ namespace ts {
20452045
lastSelfReferenceLocation = location;
20462046
}
20472047
lastLocation = location;
2048-
location = location.parent;
2048+
location = isJSDocTemplateTag(location) ?
2049+
getEffectiveContainerForJSDocTemplateTag(location) || location.parent :
2050+
location.parent;
20492051
}
20502052

20512053
// We just climbed up parents looking for the name, meaning that we started in a descendant node of `lastLocation`.
@@ -12836,7 +12838,7 @@ namespace ts {
1283612838

1283712839
function getParentSymbolOfTypeParameter(typeParameter: TypeParameter): Symbol | undefined {
1283812840
const tp = getDeclarationOfKind<TypeParameterDeclaration>(typeParameter.symbol, SyntaxKind.TypeParameter)!;
12839-
const host = isJSDocTemplateTag(tp.parent) ? getHostSignatureFromJSDoc(tp.parent) : tp.parent;
12841+
const host = isJSDocTemplateTag(tp.parent) ? getEffectiveContainerForJSDocTemplateTag(tp.parent) : tp.parent;
1284012842
return host && getSymbolOfNode(host);
1284112843
}
1284212844

@@ -33458,7 +33460,7 @@ namespace ts {
3345833460
}
3345933461
}
3346033462

33461-
checkTypeParameters(node.typeParameters);
33463+
checkTypeParameters(getEffectiveTypeParameterDeclarations(node));
3346233464

3346333465
forEach(node.parameters, checkParameter);
3346433466

@@ -35089,6 +35091,7 @@ namespace ts {
3508935091
checkTypeNameIsReserved(node.name, Diagnostics.Type_alias_name_cannot_be_0);
3509035092
}
3509135093
checkSourceElement(node.typeExpression);
35094+
checkTypeParameters(getEffectiveTypeParameterDeclarations(node));
3509235095
}
3509335096

3509435097
function checkJSDocTemplateTag(node: JSDocTemplateTag): void {

src/compiler/parser.ts

+18-2
Original file line numberDiff line numberDiff line change
@@ -8053,6 +8053,22 @@ namespace ts {
80538053
return token() === SyntaxKind.OpenBraceToken ? parseJSDocTypeExpression() : undefined;
80548054
}
80558055

8056+
function parseBracketNameInTemplateTag(): { name: Identifier, defaultType?: TypeNode } {
8057+
const isBracketed = parseOptionalJsdoc(SyntaxKind.OpenBracketToken);
8058+
if (isBracketed) {
8059+
skipWhitespace();
8060+
}
8061+
const name = parseJSDocIdentifierName(Diagnostics.Unexpected_token_A_type_parameter_name_was_expected_without_curly_braces);
8062+
let defaultType: TypeNode | undefined;
8063+
if (isBracketed) {
8064+
skipWhitespace();
8065+
parseExpected(SyntaxKind.EqualsToken);
8066+
defaultType = doInsideOfContext(NodeFlags.JSDoc, parseJSDocType);
8067+
parseExpected(SyntaxKind.CloseBracketToken);
8068+
}
8069+
return { name, defaultType };
8070+
}
8071+
80568072
function parseBracketNameInPropertyAndParamTag(): { name: EntityName, isBracketed: boolean } {
80578073
// Looking for something like '[foo]', 'foo', '[foo.bar]' or 'foo.bar'
80588074
const isBracketed = parseOptionalJsdoc(SyntaxKind.OpenBracketToken);
@@ -8441,11 +8457,11 @@ namespace ts {
84418457

84428458
function parseTemplateTagTypeParameter() {
84438459
const typeParameterPos = getNodePos();
8444-
const name = parseJSDocIdentifierName(Diagnostics.Unexpected_token_A_type_parameter_name_was_expected_without_curly_braces);
8460+
const { name, defaultType } = parseBracketNameInTemplateTag();
84458461
if (nodeIsMissing(name)) {
84468462
return undefined;
84478463
}
8448-
return finishNode(factory.createTypeParameterDeclaration(name, /*constraint*/ undefined, /*defaultType*/ undefined), typeParameterPos);
8464+
return finishNode(factory.createTypeParameterDeclaration(name, /*constraint*/ undefined, defaultType), typeParameterPos);
84498465
}
84508466

84518467
function parseTemplateTagTypeParameters() {

src/compiler/utilities.ts

+12
Original file line numberDiff line numberDiff line change
@@ -2703,6 +2703,18 @@ namespace ts {
27032703
return parameter && parameter.symbol;
27042704
}
27052705

2706+
export function getEffectiveContainerForJSDocTemplateTag(node: JSDocTemplateTag) {
2707+
if (isJSDoc(node.parent) && node.parent.tags) {
2708+
// A @template tag belongs to any @typedef, @callback, or @enum tags in the same comment block, if they exist.
2709+
const typeAlias = find(node.parent.tags, isJSDocTypeAlias);
2710+
if (typeAlias) {
2711+
return typeAlias;
2712+
}
2713+
}
2714+
// otherwise it belongs to the host it annotates
2715+
return getHostSignatureFromJSDoc(node);
2716+
}
2717+
27062718
export function getHostSignatureFromJSDoc(node: Node): SignatureDeclaration | undefined {
27072719
const host = getEffectiveJSDocHost(node);
27082720
return host && isFunctionLike(host) ? host : undefined;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
tests/cases/conformance/jsdoc/file.js(9,20): error TS2322: Type 'number' is not assignable to type 'string'.
2+
tests/cases/conformance/jsdoc/file.js(22,34): error TS1005: '=' expected.
3+
tests/cases/conformance/jsdoc/file.js(27,35): error TS1110: Type expected.
4+
tests/cases/conformance/jsdoc/file.js(33,14): error TS2706: Required type parameters may not follow optional type parameters.
5+
tests/cases/conformance/jsdoc/file.js(39,14): error TS2706: Required type parameters may not follow optional type parameters.
6+
tests/cases/conformance/jsdoc/file.js(44,17): error TS2744: Type parameter defaults can only reference previously declared type parameters.
7+
tests/cases/conformance/jsdoc/file.js(59,14): error TS2706: Required type parameters may not follow optional type parameters.
8+
tests/cases/conformance/jsdoc/file.js(66,17): error TS2744: Type parameter defaults can only reference previously declared type parameters.
9+
10+
11+
==== tests/cases/conformance/jsdoc/file.js (8 errors) ====
12+
/**
13+
* @template {string | number} [T=string] - ok: defaults are permitted
14+
* @typedef {[T]} A
15+
*/
16+
17+
/** @type {A} */ // ok, default for `T` in `A` is `string`
18+
const aDefault1 = [""];
19+
/** @type {A} */ // error: `number` is not assignable to string`
20+
const aDefault2 = [0];
21+
~
22+
!!! error TS2322: Type 'number' is not assignable to type 'string'.
23+
/** @type {A<string>} */ // ok, `T` is provided for `A`
24+
const aString = [""];
25+
/** @type {A<number>} */ // ok, `T` is provided for `A`
26+
const aNumber = [0];
27+
28+
/**
29+
* @template T
30+
* @template [U=T] - ok: default can reference earlier type parameter
31+
* @typedef {[T, U]} B
32+
*/
33+
34+
/**
35+
* @template {string | number} [T] - error: default requires an `=type`
36+
~
37+
!!! error TS1005: '=' expected.
38+
* @typedef {[T]} C
39+
*/
40+
41+
/**
42+
* @template {string | number} [T=] - error: default requires a `type`
43+
~
44+
!!! error TS1110: Type expected.
45+
* @typedef {[T]} D
46+
*/
47+
48+
/**
49+
* @template {string | number} [T=string]
50+
* @template U - error: Required type parameters cannot follow optional type parameters
51+
~
52+
!!! error TS2706: Required type parameters may not follow optional type parameters.
53+
* @typedef {[T, U]} E
54+
*/
55+
56+
/**
57+
* @template {string | number} [T=string]
58+
* @template U - error: Required type parameters cannot follow optional type parameters
59+
~
60+
!!! error TS2706: Required type parameters may not follow optional type parameters.
61+
* @typedef {[T, U]} F
62+
*/
63+
64+
/**
65+
* @template [T=U] - error: Type parameter defaults can only reference previously declared type parameters.
66+
~
67+
!!! error TS2744: Type parameter defaults can only reference previously declared type parameters.
68+
* @template [U=T]
69+
* @typedef {[T, U]} G
70+
*/
71+
72+
/**
73+
* @template T
74+
* @template [U=T] - ok: default can reference earlier type parameter
75+
* @param {T} a
76+
* @param {U} b
77+
*/
78+
function f1(a, b) {}
79+
80+
/**
81+
* @template {string | number} [T=string]
82+
* @template U - error: Required type parameters cannot follow optional type parameters
83+
~
84+
!!! error TS2706: Required type parameters may not follow optional type parameters.
85+
* @param {T} a
86+
* @param {U} b
87+
*/
88+
function f2(a, b) {}
89+
90+
/**
91+
* @template [T=U] - error: Type parameter defaults can only reference previously declared type parameters.
92+
~
93+
!!! error TS2744: Type parameter defaults can only reference previously declared type parameters.
94+
* @template [U=T]
95+
* @param {T} a
96+
* @param {U} b
97+
*/
98+
function f3(a, b) {}
99+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
=== tests/cases/conformance/jsdoc/file.js ===
2+
/**
3+
* @template {string | number} [T=string] - ok: defaults are permitted
4+
* @typedef {[T]} A
5+
*/
6+
7+
/** @type {A} */ // ok, default for `T` in `A` is `string`
8+
const aDefault1 = [""];
9+
>aDefault1 : Symbol(aDefault1, Decl(file.js, 6, 5))
10+
11+
/** @type {A} */ // error: `number` is not assignable to string`
12+
const aDefault2 = [0];
13+
>aDefault2 : Symbol(aDefault2, Decl(file.js, 8, 5))
14+
15+
/** @type {A<string>} */ // ok, `T` is provided for `A`
16+
const aString = [""];
17+
>aString : Symbol(aString, Decl(file.js, 10, 5))
18+
19+
/** @type {A<number>} */ // ok, `T` is provided for `A`
20+
const aNumber = [0];
21+
>aNumber : Symbol(aNumber, Decl(file.js, 12, 5))
22+
23+
/**
24+
* @template T
25+
* @template [U=T] - ok: default can reference earlier type parameter
26+
* @typedef {[T, U]} B
27+
*/
28+
29+
/**
30+
* @template {string | number} [T] - error: default requires an `=type`
31+
* @typedef {[T]} C
32+
*/
33+
34+
/**
35+
* @template {string | number} [T=] - error: default requires a `type`
36+
* @typedef {[T]} D
37+
*/
38+
39+
/**
40+
* @template {string | number} [T=string]
41+
* @template U - error: Required type parameters cannot follow optional type parameters
42+
* @typedef {[T, U]} E
43+
*/
44+
45+
/**
46+
* @template {string | number} [T=string]
47+
* @template U - error: Required type parameters cannot follow optional type parameters
48+
* @typedef {[T, U]} F
49+
*/
50+
51+
/**
52+
* @template [T=U] - error: Type parameter defaults can only reference previously declared type parameters.
53+
* @template [U=T]
54+
* @typedef {[T, U]} G
55+
*/
56+
57+
/**
58+
* @template T
59+
* @template [U=T] - ok: default can reference earlier type parameter
60+
* @param {T} a
61+
* @param {U} b
62+
*/
63+
function f1(a, b) {}
64+
>f1 : Symbol(f1, Decl(file.js, 12, 20))
65+
>a : Symbol(a, Decl(file.js, 54, 12))
66+
>b : Symbol(b, Decl(file.js, 54, 14))
67+
68+
/**
69+
* @template {string | number} [T=string]
70+
* @template U - error: Required type parameters cannot follow optional type parameters
71+
* @param {T} a
72+
* @param {U} b
73+
*/
74+
function f2(a, b) {}
75+
>f2 : Symbol(f2, Decl(file.js, 54, 20))
76+
>a : Symbol(a, Decl(file.js, 62, 12))
77+
>b : Symbol(b, Decl(file.js, 62, 14))
78+
79+
/**
80+
* @template [T=U] - error: Type parameter defaults can only reference previously declared type parameters.
81+
* @template [U=T]
82+
* @param {T} a
83+
* @param {U} b
84+
*/
85+
function f3(a, b) {}
86+
>f3 : Symbol(f3, Decl(file.js, 62, 20))
87+
>a : Symbol(a, Decl(file.js, 70, 12))
88+
>b : Symbol(b, Decl(file.js, 70, 14))
89+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
=== tests/cases/conformance/jsdoc/file.js ===
2+
/**
3+
* @template {string | number} [T=string] - ok: defaults are permitted
4+
* @typedef {[T]} A
5+
*/
6+
7+
/** @type {A} */ // ok, default for `T` in `A` is `string`
8+
const aDefault1 = [""];
9+
>aDefault1 : A<string>
10+
>[""] : [string]
11+
>"" : ""
12+
13+
/** @type {A} */ // error: `number` is not assignable to string`
14+
const aDefault2 = [0];
15+
>aDefault2 : A<string>
16+
>[0] : [number]
17+
>0 : 0
18+
19+
/** @type {A<string>} */ // ok, `T` is provided for `A`
20+
const aString = [""];
21+
>aString : A<string>
22+
>[""] : [string]
23+
>"" : ""
24+
25+
/** @type {A<number>} */ // ok, `T` is provided for `A`
26+
const aNumber = [0];
27+
>aNumber : A<number>
28+
>[0] : [number]
29+
>0 : 0
30+
31+
/**
32+
* @template T
33+
* @template [U=T] - ok: default can reference earlier type parameter
34+
* @typedef {[T, U]} B
35+
*/
36+
37+
/**
38+
* @template {string | number} [T] - error: default requires an `=type`
39+
* @typedef {[T]} C
40+
*/
41+
42+
/**
43+
* @template {string | number} [T=] - error: default requires a `type`
44+
* @typedef {[T]} D
45+
*/
46+
47+
/**
48+
* @template {string | number} [T=string]
49+
* @template U - error: Required type parameters cannot follow optional type parameters
50+
* @typedef {[T, U]} E
51+
*/
52+
53+
/**
54+
* @template {string | number} [T=string]
55+
* @template U - error: Required type parameters cannot follow optional type parameters
56+
* @typedef {[T, U]} F
57+
*/
58+
59+
/**
60+
* @template [T=U] - error: Type parameter defaults can only reference previously declared type parameters.
61+
* @template [U=T]
62+
* @typedef {[T, U]} G
63+
*/
64+
65+
/**
66+
* @template T
67+
* @template [U=T] - ok: default can reference earlier type parameter
68+
* @param {T} a
69+
* @param {U} b
70+
*/
71+
function f1(a, b) {}
72+
>f1 : <T, U = T>(a: T, b: U) => void
73+
>a : T
74+
>b : U
75+
76+
/**
77+
* @template {string | number} [T=string]
78+
* @template U - error: Required type parameters cannot follow optional type parameters
79+
* @param {T} a
80+
* @param {U} b
81+
*/
82+
function f2(a, b) {}
83+
>f2 : <T extends string | number = string, U>(a: T, b: U) => void
84+
>a : T
85+
>b : U
86+
87+
/**
88+
* @template [T=U] - error: Type parameter defaults can only reference previously declared type parameters.
89+
* @template [U=T]
90+
* @param {T} a
91+
* @param {U} b
92+
*/
93+
function f3(a, b) {}
94+
>f3 : <T = U, U = T>(a: T, b: U) => void
95+
>a : T
96+
>b : U
97+

0 commit comments

Comments
 (0)