Skip to content

Commit cf787e9

Browse files
authored
Fix name resolution in typedef and allow defaults for template tags (#45483)
* Fix name resolution in typedef and allow defaults for template tags * Inline parseBracketNameInTemplateTag * Update baselines * Add js declaration emit tests
1 parent 8610ff5 commit cf787e9

14 files changed

+649
-5
lines changed

Diff for: 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();

Diff for: src/compiler/checker.ts

+6-3
Original file line numberDiff line numberDiff line change
@@ -2052,7 +2052,9 @@ namespace ts {
20522052
lastSelfReferenceLocation = location;
20532053
}
20542054
lastLocation = location;
2055-
location = location.parent;
2055+
location = isJSDocTemplateTag(location) ?
2056+
getEffectiveContainerForJSDocTemplateTag(location) || location.parent :
2057+
location.parent;
20562058
}
20572059

20582060
// We just climbed up parents looking for the name, meaning that we started in a descendant node of `lastLocation`.
@@ -12901,7 +12903,7 @@ namespace ts {
1290112903

1290212904
function getParentSymbolOfTypeParameter(typeParameter: TypeParameter): Symbol | undefined {
1290312905
const tp = getDeclarationOfKind<TypeParameterDeclaration>(typeParameter.symbol, SyntaxKind.TypeParameter)!;
12904-
const host = isJSDocTemplateTag(tp.parent) ? getHostSignatureFromJSDoc(tp.parent) : tp.parent;
12906+
const host = isJSDocTemplateTag(tp.parent) ? getEffectiveContainerForJSDocTemplateTag(tp.parent) : tp.parent;
1290512907
return host && getSymbolOfNode(host);
1290612908
}
1290712909

@@ -33673,7 +33675,7 @@ namespace ts {
3367333675
}
3367433676
}
3367533677

33676-
checkTypeParameters(node.typeParameters);
33678+
checkTypeParameters(getEffectiveTypeParameterDeclarations(node));
3367733679

3367833680
forEach(node.parameters, checkParameter);
3367933681

@@ -35306,6 +35308,7 @@ namespace ts {
3530635308
checkTypeNameIsReserved(node.name, Diagnostics.Type_alias_name_cannot_be_0);
3530735309
}
3530835310
checkSourceElement(node.typeExpression);
35311+
checkTypeParameters(getEffectiveTypeParameterDeclarations(node));
3530935312
}
3531035313

3531135314
function checkJSDocTemplateTag(node: JSDocTemplateTag): void {

Diff for: src/compiler/parser.ts

+14-1
Original file line numberDiff line numberDiff line change
@@ -8441,11 +8441,24 @@ namespace ts {
84418441

84428442
function parseTemplateTagTypeParameter() {
84438443
const typeParameterPos = getNodePos();
8444+
const isBracketed = parseOptionalJsdoc(SyntaxKind.OpenBracketToken);
8445+
if (isBracketed) {
8446+
skipWhitespace();
8447+
}
84448448
const name = parseJSDocIdentifierName(Diagnostics.Unexpected_token_A_type_parameter_name_was_expected_without_curly_braces);
8449+
8450+
let defaultType: TypeNode | undefined;
8451+
if (isBracketed) {
8452+
skipWhitespace();
8453+
parseExpected(SyntaxKind.EqualsToken);
8454+
defaultType = doInsideOfContext(NodeFlags.JSDoc, parseJSDocType);
8455+
parseExpected(SyntaxKind.CloseBracketToken);
8456+
}
8457+
84458458
if (nodeIsMissing(name)) {
84468459
return undefined;
84478460
}
8448-
return finishNode(factory.createTypeParameterDeclaration(name, /*constraint*/ undefined, /*defaultType*/ undefined), typeParameterPos);
8461+
return finishNode(factory.createTypeParameterDeclaration(name, /*constraint*/ undefined, defaultType), typeParameterPos);
84498462
}
84508463

84518464
function parseTemplateTagTypeParameters() {

Diff for: src/compiler/utilities.ts

+12
Original file line numberDiff line numberDiff line change
@@ -2723,6 +2723,18 @@ namespace ts {
27232723
return parameter && parameter.symbol;
27242724
}
27252725

2726+
export function getEffectiveContainerForJSDocTemplateTag(node: JSDocTemplateTag) {
2727+
if (isJSDoc(node.parent) && node.parent.tags) {
2728+
// A @template tag belongs to any @typedef, @callback, or @enum tags in the same comment block, if they exist.
2729+
const typeAlias = find(node.parent.tags, isJSDocTypeAlias);
2730+
if (typeAlias) {
2731+
return typeAlias;
2732+
}
2733+
}
2734+
// otherwise it belongs to the host it annotates
2735+
return getHostSignatureFromJSDoc(node);
2736+
}
2737+
27262738
export function getHostSignatureFromJSDoc(node: Node): SignatureDeclaration | undefined {
27272739
const host = getEffectiveJSDocHost(node);
27282740
return host && isFunctionLike(host) ? host : undefined;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
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(38,17): error TS2744: Type parameter defaults can only reference previously declared type parameters.
6+
tests/cases/conformance/jsdoc/file.js(53,14): error TS2706: Required type parameters may not follow optional type parameters.
7+
tests/cases/conformance/jsdoc/file.js(60,17): error TS2744: Type parameter defaults can only reference previously declared type parameters.
8+
9+
10+
==== tests/cases/conformance/jsdoc/file.js (7 errors) ====
11+
/**
12+
* @template {string | number} [T=string] - ok: defaults are permitted
13+
* @typedef {[T]} A
14+
*/
15+
16+
/** @type {A} */ // ok, default for `T` in `A` is `string`
17+
const aDefault1 = [""];
18+
/** @type {A} */ // error: `number` is not assignable to string`
19+
const aDefault2 = [0];
20+
~
21+
!!! error TS2322: Type 'number' is not assignable to type 'string'.
22+
/** @type {A<string>} */ // ok, `T` is provided for `A`
23+
const aString = [""];
24+
/** @type {A<number>} */ // ok, `T` is provided for `A`
25+
const aNumber = [0];
26+
27+
/**
28+
* @template T
29+
* @template [U=T] - ok: default can reference earlier type parameter
30+
* @typedef {[T, U]} B
31+
*/
32+
33+
/**
34+
* @template {string | number} [T] - error: default requires an `=type`
35+
~
36+
!!! error TS1005: '=' expected.
37+
* @typedef {[T]} C
38+
*/
39+
40+
/**
41+
* @template {string | number} [T=] - error: default requires a `type`
42+
~
43+
!!! error TS1110: Type expected.
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+
~
51+
!!! error TS2706: Required type parameters may not follow optional type parameters.
52+
* @typedef {[T, U]} E
53+
*/
54+
55+
/**
56+
* @template [T=U] - error: Type parameter defaults can only reference previously declared type parameters.
57+
~
58+
!!! error TS2744: Type parameter defaults can only reference previously declared type parameters.
59+
* @template [U=T]
60+
* @typedef {[T, U]} G
61+
*/
62+
63+
/**
64+
* @template T
65+
* @template [U=T] - ok: default can reference earlier type parameter
66+
* @param {T} a
67+
* @param {U} b
68+
*/
69+
function f1(a, b) {}
70+
71+
/**
72+
* @template {string | number} [T=string]
73+
* @template U - error: Required type parameters cannot follow optional type parameters
74+
~
75+
!!! error TS2706: Required type parameters may not follow optional type parameters.
76+
* @param {T} a
77+
* @param {U} b
78+
*/
79+
function f2(a, b) {}
80+
81+
/**
82+
* @template [T=U] - error: Type parameter defaults can only reference previously declared type parameters.
83+
~
84+
!!! error TS2744: Type parameter defaults can only reference previously declared type parameters.
85+
* @template [U=T]
86+
* @param {T} a
87+
* @param {U} b
88+
*/
89+
function f3(a, b) {}
90+

Diff for: tests/baselines/reference/jsdocTemplateTagDefault.js

+186
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
//// [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+
/** @type {A} */ // error: `number` is not assignable to string`
10+
const aDefault2 = [0];
11+
/** @type {A<string>} */ // ok, `T` is provided for `A`
12+
const aString = [""];
13+
/** @type {A<number>} */ // ok, `T` is provided for `A`
14+
const aNumber = [0];
15+
16+
/**
17+
* @template T
18+
* @template [U=T] - ok: default can reference earlier type parameter
19+
* @typedef {[T, U]} B
20+
*/
21+
22+
/**
23+
* @template {string | number} [T] - error: default requires an `=type`
24+
* @typedef {[T]} C
25+
*/
26+
27+
/**
28+
* @template {string | number} [T=] - error: default requires a `type`
29+
* @typedef {[T]} D
30+
*/
31+
32+
/**
33+
* @template {string | number} [T=string]
34+
* @template U - error: Required type parameters cannot follow optional type parameters
35+
* @typedef {[T, U]} E
36+
*/
37+
38+
/**
39+
* @template [T=U] - error: Type parameter defaults can only reference previously declared type parameters.
40+
* @template [U=T]
41+
* @typedef {[T, U]} G
42+
*/
43+
44+
/**
45+
* @template T
46+
* @template [U=T] - ok: default can reference earlier type parameter
47+
* @param {T} a
48+
* @param {U} b
49+
*/
50+
function f1(a, b) {}
51+
52+
/**
53+
* @template {string | number} [T=string]
54+
* @template U - error: Required type parameters cannot follow optional type parameters
55+
* @param {T} a
56+
* @param {U} b
57+
*/
58+
function f2(a, b) {}
59+
60+
/**
61+
* @template [T=U] - error: Type parameter defaults can only reference previously declared type parameters.
62+
* @template [U=T]
63+
* @param {T} a
64+
* @param {U} b
65+
*/
66+
function f3(a, b) {}
67+
68+
69+
//// [file.js]
70+
/**
71+
* @template {string | number} [T=string] - ok: defaults are permitted
72+
* @typedef {[T]} A
73+
*/
74+
/** @type {A} */ // ok, default for `T` in `A` is `string`
75+
var aDefault1 = [""];
76+
/** @type {A} */ // error: `number` is not assignable to string`
77+
var aDefault2 = [0];
78+
/** @type {A<string>} */ // ok, `T` is provided for `A`
79+
var aString = [""];
80+
/** @type {A<number>} */ // ok, `T` is provided for `A`
81+
var aNumber = [0];
82+
/**
83+
* @template T
84+
* @template [U=T] - ok: default can reference earlier type parameter
85+
* @typedef {[T, U]} B
86+
*/
87+
/**
88+
* @template {string | number} [T] - error: default requires an `=type`
89+
* @typedef {[T]} C
90+
*/
91+
/**
92+
* @template {string | number} [T=] - error: default requires a `type`
93+
* @typedef {[T]} D
94+
*/
95+
/**
96+
* @template {string | number} [T=string]
97+
* @template U - error: Required type parameters cannot follow optional type parameters
98+
* @typedef {[T, U]} E
99+
*/
100+
/**
101+
* @template [T=U] - error: Type parameter defaults can only reference previously declared type parameters.
102+
* @template [U=T]
103+
* @typedef {[T, U]} G
104+
*/
105+
/**
106+
* @template T
107+
* @template [U=T] - ok: default can reference earlier type parameter
108+
* @param {T} a
109+
* @param {U} b
110+
*/
111+
function f1(a, b) { }
112+
/**
113+
* @template {string | number} [T=string]
114+
* @template U - error: Required type parameters cannot follow optional type parameters
115+
* @param {T} a
116+
* @param {U} b
117+
*/
118+
function f2(a, b) { }
119+
/**
120+
* @template [T=U] - error: Type parameter defaults can only reference previously declared type parameters.
121+
* @template [U=T]
122+
* @param {T} a
123+
* @param {U} b
124+
*/
125+
function f3(a, b) { }
126+
127+
128+
//// [file.d.ts]
129+
/**
130+
* @template T
131+
* @template [U=T] - ok: default can reference earlier type parameter
132+
* @typedef {[T, U]} B
133+
*/
134+
/**
135+
* @template {string | number} [T] - error: default requires an `=type`
136+
* @typedef {[T]} C
137+
*/
138+
/**
139+
* @template {string | number} [T=] - error: default requires a `type`
140+
* @typedef {[T]} D
141+
*/
142+
/**
143+
* @template {string | number} [T=string]
144+
* @template U - error: Required type parameters cannot follow optional type parameters
145+
* @typedef {[T, U]} E
146+
*/
147+
/**
148+
* @template [T=U] - error: Type parameter defaults can only reference previously declared type parameters.
149+
* @template [U=T]
150+
* @typedef {[T, U]} G
151+
*/
152+
/**
153+
* @template T
154+
* @template [U=T] - ok: default can reference earlier type parameter
155+
* @param {T} a
156+
* @param {U} b
157+
*/
158+
declare function f1<T, U = T>(a: T, b: U): void;
159+
/**
160+
* @template {string | number} [T=string]
161+
* @template U - error: Required type parameters cannot follow optional type parameters
162+
* @param {T} a
163+
* @param {U} b
164+
*/
165+
declare function f2<T extends string | number = string, U>(a: T, b: U): void;
166+
/**
167+
* @template [T=U] - error: Type parameter defaults can only reference previously declared type parameters.
168+
* @template [U=T]
169+
* @param {T} a
170+
* @param {U} b
171+
*/
172+
declare function f3<T = U, U = T>(a: T, b: U): void;
173+
/**
174+
* @template {string | number} [T=string] - ok: defaults are permitted
175+
* @typedef {[T]} A
176+
*/
177+
/** @type {A} */ declare const aDefault1: A<string>;
178+
/** @type {A} */ declare const aDefault2: A<string>;
179+
/** @type {A<string>} */ declare const aString: A<string>;
180+
/** @type {A<number>} */ declare const aNumber: A<number>;
181+
type B<T, U = T> = [T, U];
182+
type C<T extends string | number = any> = [T];
183+
type D<T extends string | number = any> = [T];
184+
type E<T extends string | number = string, U> = [T, U];
185+
type G<T = U, U = T> = [T, U];
186+
type A<T extends string | number = string> = [T];

0 commit comments

Comments
 (0)