Skip to content

Commit 80fa780

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

13 files changed

+457
-5
lines changed

src/compiler/checker.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2045,6 +2045,13 @@ namespace ts {
20452045
lastSelfReferenceLocation = location;
20462046
}
20472047
lastLocation = location;
2048+
if (isJSDocTemplateTag(location)) {
2049+
const container = location.parent.tags && find(location.parent.tags, isJSDocTypeAlias) || getHostSignatureFromJSDoc(location);
2050+
if (container) {
2051+
location = container;
2052+
continue;
2053+
}
2054+
}
20482055
location = location.parent;
20492056
}
20502057

@@ -33458,7 +33465,7 @@ namespace ts {
3345833465
}
3345933466
}
3346033467

33461-
checkTypeParameters(node.typeParameters);
33468+
checkTypeParameters(getEffectiveTypeParameterDeclarations(node));
3346233469

3346333470
forEach(node.parameters, checkParameter);
3346433471

@@ -35089,6 +35096,7 @@ namespace ts {
3508935096
checkTypeNameIsReserved(node.name, Diagnostics.Type_alias_name_cannot_be_0);
3509035097
}
3509135098
checkSourceElement(node.typeExpression);
35099+
checkTypeParameters(getEffectiveTypeParameterDeclarations(node));
3509235100
}
3509335101

3509435102
function checkJSDocTemplateTag(node: JSDocTemplateTag): void {

src/compiler/parser.ts

Lines changed: 18 additions & 2 deletions
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/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3304,6 +3304,7 @@ namespace ts {
33043304

33053305
export interface JSDocTemplateTag extends JSDocTag {
33063306
readonly kind: SyntaxKind.JSDocTemplateTag;
3307+
readonly parent: JSDoc;
33073308
readonly constraint: JSDocTypeExpression | undefined;
33083309
readonly typeParameters: NodeArray<TypeParameterDeclaration>;
33093310
}

tests/baselines/reference/api/tsserverlibrary.d.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1847,6 +1847,7 @@ declare namespace ts {
18471847
}
18481848
export interface JSDocTemplateTag extends JSDocTag {
18491849
readonly kind: SyntaxKind.JSDocTemplateTag;
1850+
readonly parent: JSDoc;
18501851
readonly constraint: JSDocTypeExpression | undefined;
18511852
readonly typeParameters: NodeArray<TypeParameterDeclaration>;
18521853
}
@@ -10993,7 +10994,7 @@ declare namespace ts {
1099310994
/** @deprecated Use `factory.createJSDocTemplateTag` or the factory supplied by your transformation context instead. */
1099410995
const createJSDocTemplateTag: (tagName: Identifier | undefined, constraint: JSDocTypeExpression | undefined, typeParameters: readonly TypeParameterDeclaration[], comment?: string | NodeArray<JSDocComment> | undefined) => JSDocTemplateTag;
1099510996
/** @deprecated Use `factory.createJSDocTypedefTag` or the factory supplied by your transformation context instead. */
10996-
const createJSDocTypedefTag: (tagName: Identifier | undefined, typeExpression?: JSDocTypeLiteral | JSDocTypeExpression | undefined, fullName?: Identifier | JSDocNamespaceDeclaration | undefined, comment?: string | NodeArray<JSDocComment> | undefined) => JSDocTypedefTag;
10997+
const createJSDocTypedefTag: (tagName: Identifier | undefined, typeExpression?: JSDocTypeExpression | JSDocTypeLiteral | undefined, fullName?: Identifier | JSDocNamespaceDeclaration | undefined, comment?: string | NodeArray<JSDocComment> | undefined) => JSDocTypedefTag;
1099710998
/** @deprecated Use `factory.createJSDocCallbackTag` or the factory supplied by your transformation context instead. */
1099810999
const createJSDocCallbackTag: (tagName: Identifier | undefined, typeExpression: JSDocSignature, fullName?: Identifier | JSDocNamespaceDeclaration | undefined, comment?: string | NodeArray<JSDocComment> | undefined) => JSDocCallbackTag;
1099911000
/** @deprecated Use `factory.createJSDocSignature` or the factory supplied by your transformation context instead. */

tests/baselines/reference/api/typescript.d.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1847,6 +1847,7 @@ declare namespace ts {
18471847
}
18481848
export interface JSDocTemplateTag extends JSDocTag {
18491849
readonly kind: SyntaxKind.JSDocTemplateTag;
1850+
readonly parent: JSDoc;
18501851
readonly constraint: JSDocTypeExpression | undefined;
18511852
readonly typeParameters: NodeArray<TypeParameterDeclaration>;
18521853
}
@@ -7189,7 +7190,7 @@ declare namespace ts {
71897190
/** @deprecated Use `factory.createJSDocTemplateTag` or the factory supplied by your transformation context instead. */
71907191
const createJSDocTemplateTag: (tagName: Identifier | undefined, constraint: JSDocTypeExpression | undefined, typeParameters: readonly TypeParameterDeclaration[], comment?: string | NodeArray<JSDocComment> | undefined) => JSDocTemplateTag;
71917192
/** @deprecated Use `factory.createJSDocTypedefTag` or the factory supplied by your transformation context instead. */
7192-
const createJSDocTypedefTag: (tagName: Identifier | undefined, typeExpression?: JSDocTypeLiteral | JSDocTypeExpression | undefined, fullName?: Identifier | JSDocNamespaceDeclaration | undefined, comment?: string | NodeArray<JSDocComment> | undefined) => JSDocTypedefTag;
7193+
const createJSDocTypedefTag: (tagName: Identifier | undefined, typeExpression?: JSDocTypeExpression | JSDocTypeLiteral | undefined, fullName?: Identifier | JSDocNamespaceDeclaration | undefined, comment?: string | NodeArray<JSDocComment> | undefined) => JSDocTypedefTag;
71937194
/** @deprecated Use `factory.createJSDocCallbackTag` or the factory supplied by your transformation context instead. */
71947195
const createJSDocCallbackTag: (tagName: Identifier | undefined, typeExpression: JSDocSignature, fullName?: Identifier | JSDocNamespaceDeclaration | undefined, comment?: string | NodeArray<JSDocComment> | undefined) => JSDocCallbackTag;
71957196
/** @deprecated Use `factory.createJSDocSignature` or the factory supplied by your transformation context instead. */
Lines changed: 99 additions & 0 deletions
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+
Lines changed: 89 additions & 0 deletions
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+
Lines changed: 97 additions & 0 deletions
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)