diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 38888e5e582ed..46f54d9ca22af 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2045,6 +2045,13 @@ namespace ts { lastSelfReferenceLocation = location; } lastLocation = location; + if (isJSDocTemplateTag(location)) { + const container = location.parent.tags && find(location.parent.tags, isJSDocTypeAlias) || getHostSignatureFromJSDoc(location); + if (container) { + location = container; + continue; + } + } location = location.parent; } @@ -33458,7 +33465,7 @@ namespace ts { } } - checkTypeParameters(node.typeParameters); + checkTypeParameters(getEffectiveTypeParameterDeclarations(node)); forEach(node.parameters, checkParameter); @@ -35089,6 +35096,7 @@ namespace ts { checkTypeNameIsReserved(node.name, Diagnostics.Type_alias_name_cannot_be_0); } checkSourceElement(node.typeExpression); + checkTypeParameters(getEffectiveTypeParameterDeclarations(node)); } function checkJSDocTemplateTag(node: JSDocTemplateTag): void { diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 0aa0a8fb0957f..10c6233d9a08e 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -8053,6 +8053,22 @@ namespace ts { return token() === SyntaxKind.OpenBraceToken ? parseJSDocTypeExpression() : undefined; } + function parseBracketNameInTemplateTag(): { name: Identifier, defaultType?: TypeNode } { + const isBracketed = parseOptionalJsdoc(SyntaxKind.OpenBracketToken); + if (isBracketed) { + skipWhitespace(); + } + const name = parseJSDocIdentifierName(Diagnostics.Unexpected_token_A_type_parameter_name_was_expected_without_curly_braces); + let defaultType: TypeNode | undefined; + if (isBracketed) { + skipWhitespace(); + parseExpected(SyntaxKind.EqualsToken); + defaultType = doInsideOfContext(NodeFlags.JSDoc, parseJSDocType); + parseExpected(SyntaxKind.CloseBracketToken); + } + return { name, defaultType }; + } + function parseBracketNameInPropertyAndParamTag(): { name: EntityName, isBracketed: boolean } { // Looking for something like '[foo]', 'foo', '[foo.bar]' or 'foo.bar' const isBracketed = parseOptionalJsdoc(SyntaxKind.OpenBracketToken); @@ -8441,11 +8457,11 @@ namespace ts { function parseTemplateTagTypeParameter() { const typeParameterPos = getNodePos(); - const name = parseJSDocIdentifierName(Diagnostics.Unexpected_token_A_type_parameter_name_was_expected_without_curly_braces); + const { name, defaultType } = parseBracketNameInTemplateTag(); if (nodeIsMissing(name)) { return undefined; } - return finishNode(factory.createTypeParameterDeclaration(name, /*constraint*/ undefined, /*defaultType*/ undefined), typeParameterPos); + return finishNode(factory.createTypeParameterDeclaration(name, /*constraint*/ undefined, defaultType), typeParameterPos); } function parseTemplateTagTypeParameters() { diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 6194f41a20d22..105cf3b353cda 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -3304,6 +3304,7 @@ namespace ts { export interface JSDocTemplateTag extends JSDocTag { readonly kind: SyntaxKind.JSDocTemplateTag; + readonly parent: JSDoc; readonly constraint: JSDocTypeExpression | undefined; readonly typeParameters: NodeArray; } diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 803d3163ef209..1cb2a08636151 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -1847,6 +1847,7 @@ declare namespace ts { } export interface JSDocTemplateTag extends JSDocTag { readonly kind: SyntaxKind.JSDocTemplateTag; + readonly parent: JSDoc; readonly constraint: JSDocTypeExpression | undefined; readonly typeParameters: NodeArray; } @@ -10993,7 +10994,7 @@ declare namespace ts { /** @deprecated Use `factory.createJSDocTemplateTag` or the factory supplied by your transformation context instead. */ const createJSDocTemplateTag: (tagName: Identifier | undefined, constraint: JSDocTypeExpression | undefined, typeParameters: readonly TypeParameterDeclaration[], comment?: string | NodeArray | undefined) => JSDocTemplateTag; /** @deprecated Use `factory.createJSDocTypedefTag` or the factory supplied by your transformation context instead. */ - const createJSDocTypedefTag: (tagName: Identifier | undefined, typeExpression?: JSDocTypeLiteral | JSDocTypeExpression | undefined, fullName?: Identifier | JSDocNamespaceDeclaration | undefined, comment?: string | NodeArray | undefined) => JSDocTypedefTag; + const createJSDocTypedefTag: (tagName: Identifier | undefined, typeExpression?: JSDocTypeExpression | JSDocTypeLiteral | undefined, fullName?: Identifier | JSDocNamespaceDeclaration | undefined, comment?: string | NodeArray | undefined) => JSDocTypedefTag; /** @deprecated Use `factory.createJSDocCallbackTag` or the factory supplied by your transformation context instead. */ const createJSDocCallbackTag: (tagName: Identifier | undefined, typeExpression: JSDocSignature, fullName?: Identifier | JSDocNamespaceDeclaration | undefined, comment?: string | NodeArray | undefined) => JSDocCallbackTag; /** @deprecated Use `factory.createJSDocSignature` or the factory supplied by your transformation context instead. */ diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 94d77a0c42dd1..850de9c338db9 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -1847,6 +1847,7 @@ declare namespace ts { } export interface JSDocTemplateTag extends JSDocTag { readonly kind: SyntaxKind.JSDocTemplateTag; + readonly parent: JSDoc; readonly constraint: JSDocTypeExpression | undefined; readonly typeParameters: NodeArray; } @@ -7189,7 +7190,7 @@ declare namespace ts { /** @deprecated Use `factory.createJSDocTemplateTag` or the factory supplied by your transformation context instead. */ const createJSDocTemplateTag: (tagName: Identifier | undefined, constraint: JSDocTypeExpression | undefined, typeParameters: readonly TypeParameterDeclaration[], comment?: string | NodeArray | undefined) => JSDocTemplateTag; /** @deprecated Use `factory.createJSDocTypedefTag` or the factory supplied by your transformation context instead. */ - const createJSDocTypedefTag: (tagName: Identifier | undefined, typeExpression?: JSDocTypeLiteral | JSDocTypeExpression | undefined, fullName?: Identifier | JSDocNamespaceDeclaration | undefined, comment?: string | NodeArray | undefined) => JSDocTypedefTag; + const createJSDocTypedefTag: (tagName: Identifier | undefined, typeExpression?: JSDocTypeExpression | JSDocTypeLiteral | undefined, fullName?: Identifier | JSDocNamespaceDeclaration | undefined, comment?: string | NodeArray | undefined) => JSDocTypedefTag; /** @deprecated Use `factory.createJSDocCallbackTag` or the factory supplied by your transformation context instead. */ const createJSDocCallbackTag: (tagName: Identifier | undefined, typeExpression: JSDocSignature, fullName?: Identifier | JSDocNamespaceDeclaration | undefined, comment?: string | NodeArray | undefined) => JSDocCallbackTag; /** @deprecated Use `factory.createJSDocSignature` or the factory supplied by your transformation context instead. */ diff --git a/tests/baselines/reference/jsdocTemplateTagDefault.errors.txt b/tests/baselines/reference/jsdocTemplateTagDefault.errors.txt new file mode 100644 index 0000000000000..7b3f3d1356936 --- /dev/null +++ b/tests/baselines/reference/jsdocTemplateTagDefault.errors.txt @@ -0,0 +1,99 @@ +tests/cases/conformance/jsdoc/file.js(9,20): error TS2322: Type 'number' is not assignable to type 'string'. +tests/cases/conformance/jsdoc/file.js(22,34): error TS1005: '=' expected. +tests/cases/conformance/jsdoc/file.js(27,35): error TS1110: Type expected. +tests/cases/conformance/jsdoc/file.js(33,14): error TS2706: Required type parameters may not follow optional type parameters. +tests/cases/conformance/jsdoc/file.js(39,14): error TS2706: Required type parameters may not follow optional type parameters. +tests/cases/conformance/jsdoc/file.js(44,17): error TS2744: Type parameter defaults can only reference previously declared type parameters. +tests/cases/conformance/jsdoc/file.js(59,14): error TS2706: Required type parameters may not follow optional type parameters. +tests/cases/conformance/jsdoc/file.js(66,17): error TS2744: Type parameter defaults can only reference previously declared type parameters. + + +==== tests/cases/conformance/jsdoc/file.js (8 errors) ==== + /** + * @template {string | number} [T=string] - ok: defaults are permitted + * @typedef {[T]} A + */ + + /** @type {A} */ // ok, default for `T` in `A` is `string` + const aDefault1 = [""]; + /** @type {A} */ // error: `number` is not assignable to string` + const aDefault2 = [0]; + ~ +!!! error TS2322: Type 'number' is not assignable to type 'string'. + /** @type {A} */ // ok, `T` is provided for `A` + const aString = [""]; + /** @type {A} */ // ok, `T` is provided for `A` + const aNumber = [0]; + + /** + * @template T + * @template [U=T] - ok: default can reference earlier type parameter + * @typedef {[T, U]} B + */ + + /** + * @template {string | number} [T] - error: default requires an `=type` + ~ +!!! error TS1005: '=' expected. + * @typedef {[T]} C + */ + + /** + * @template {string | number} [T=] - error: default requires a `type` + ~ +!!! error TS1110: Type expected. + * @typedef {[T]} D + */ + + /** + * @template {string | number} [T=string] + * @template U - error: Required type parameters cannot follow optional type parameters + ~ +!!! error TS2706: Required type parameters may not follow optional type parameters. + * @typedef {[T, U]} E + */ + + /** + * @template {string | number} [T=string] + * @template U - error: Required type parameters cannot follow optional type parameters + ~ +!!! error TS2706: Required type parameters may not follow optional type parameters. + * @typedef {[T, U]} F + */ + + /** + * @template [T=U] - error: Type parameter defaults can only reference previously declared type parameters. + ~ +!!! error TS2744: Type parameter defaults can only reference previously declared type parameters. + * @template [U=T] + * @typedef {[T, U]} G + */ + + /** + * @template T + * @template [U=T] - ok: default can reference earlier type parameter + * @param {T} a + * @param {U} b + */ + function f1(a, b) {} + + /** + * @template {string | number} [T=string] + * @template U - error: Required type parameters cannot follow optional type parameters + ~ +!!! error TS2706: Required type parameters may not follow optional type parameters. + * @param {T} a + * @param {U} b + */ + function f2(a, b) {} + + /** + * @template [T=U] - error: Type parameter defaults can only reference previously declared type parameters. + ~ +!!! error TS2744: Type parameter defaults can only reference previously declared type parameters. + * @template [U=T] + * @param {T} a + * @param {U} b + */ + function f3(a, b) {} + \ No newline at end of file diff --git a/tests/baselines/reference/jsdocTemplateTagDefault.symbols b/tests/baselines/reference/jsdocTemplateTagDefault.symbols new file mode 100644 index 0000000000000..c984d8e00163b --- /dev/null +++ b/tests/baselines/reference/jsdocTemplateTagDefault.symbols @@ -0,0 +1,89 @@ +=== tests/cases/conformance/jsdoc/file.js === +/** + * @template {string | number} [T=string] - ok: defaults are permitted + * @typedef {[T]} A + */ + +/** @type {A} */ // ok, default for `T` in `A` is `string` +const aDefault1 = [""]; +>aDefault1 : Symbol(aDefault1, Decl(file.js, 6, 5)) + +/** @type {A} */ // error: `number` is not assignable to string` +const aDefault2 = [0]; +>aDefault2 : Symbol(aDefault2, Decl(file.js, 8, 5)) + +/** @type {A} */ // ok, `T` is provided for `A` +const aString = [""]; +>aString : Symbol(aString, Decl(file.js, 10, 5)) + +/** @type {A} */ // ok, `T` is provided for `A` +const aNumber = [0]; +>aNumber : Symbol(aNumber, Decl(file.js, 12, 5)) + +/** + * @template T + * @template [U=T] - ok: default can reference earlier type parameter + * @typedef {[T, U]} B + */ + +/** + * @template {string | number} [T] - error: default requires an `=type` + * @typedef {[T]} C + */ + +/** + * @template {string | number} [T=] - error: default requires a `type` + * @typedef {[T]} D + */ + +/** + * @template {string | number} [T=string] + * @template U - error: Required type parameters cannot follow optional type parameters + * @typedef {[T, U]} E + */ + +/** + * @template {string | number} [T=string] + * @template U - error: Required type parameters cannot follow optional type parameters + * @typedef {[T, U]} F + */ + +/** + * @template [T=U] - error: Type parameter defaults can only reference previously declared type parameters. + * @template [U=T] + * @typedef {[T, U]} G + */ + +/** + * @template T + * @template [U=T] - ok: default can reference earlier type parameter + * @param {T} a + * @param {U} b + */ +function f1(a, b) {} +>f1 : Symbol(f1, Decl(file.js, 12, 20)) +>a : Symbol(a, Decl(file.js, 54, 12)) +>b : Symbol(b, Decl(file.js, 54, 14)) + + /** + * @template {string | number} [T=string] + * @template U - error: Required type parameters cannot follow optional type parameters + * @param {T} a + * @param {U} b + */ +function f2(a, b) {} +>f2 : Symbol(f2, Decl(file.js, 54, 20)) +>a : Symbol(a, Decl(file.js, 62, 12)) +>b : Symbol(b, Decl(file.js, 62, 14)) + +/** + * @template [T=U] - error: Type parameter defaults can only reference previously declared type parameters. + * @template [U=T] + * @param {T} a + * @param {U} b + */ +function f3(a, b) {} +>f3 : Symbol(f3, Decl(file.js, 62, 20)) +>a : Symbol(a, Decl(file.js, 70, 12)) +>b : Symbol(b, Decl(file.js, 70, 14)) + diff --git a/tests/baselines/reference/jsdocTemplateTagDefault.types b/tests/baselines/reference/jsdocTemplateTagDefault.types new file mode 100644 index 0000000000000..db806cc2b77d2 --- /dev/null +++ b/tests/baselines/reference/jsdocTemplateTagDefault.types @@ -0,0 +1,97 @@ +=== tests/cases/conformance/jsdoc/file.js === +/** + * @template {string | number} [T=string] - ok: defaults are permitted + * @typedef {[T]} A + */ + +/** @type {A} */ // ok, default for `T` in `A` is `string` +const aDefault1 = [""]; +>aDefault1 : A +>[""] : [string] +>"" : "" + +/** @type {A} */ // error: `number` is not assignable to string` +const aDefault2 = [0]; +>aDefault2 : A +>[0] : [number] +>0 : 0 + +/** @type {A} */ // ok, `T` is provided for `A` +const aString = [""]; +>aString : A +>[""] : [string] +>"" : "" + +/** @type {A} */ // ok, `T` is provided for `A` +const aNumber = [0]; +>aNumber : A +>[0] : [number] +>0 : 0 + +/** + * @template T + * @template [U=T] - ok: default can reference earlier type parameter + * @typedef {[T, U]} B + */ + +/** + * @template {string | number} [T] - error: default requires an `=type` + * @typedef {[T]} C + */ + +/** + * @template {string | number} [T=] - error: default requires a `type` + * @typedef {[T]} D + */ + +/** + * @template {string | number} [T=string] + * @template U - error: Required type parameters cannot follow optional type parameters + * @typedef {[T, U]} E + */ + +/** + * @template {string | number} [T=string] + * @template U - error: Required type parameters cannot follow optional type parameters + * @typedef {[T, U]} F + */ + +/** + * @template [T=U] - error: Type parameter defaults can only reference previously declared type parameters. + * @template [U=T] + * @typedef {[T, U]} G + */ + +/** + * @template T + * @template [U=T] - ok: default can reference earlier type parameter + * @param {T} a + * @param {U} b + */ +function f1(a, b) {} +>f1 : (a: T, b: U) => void +>a : T +>b : U + + /** + * @template {string | number} [T=string] + * @template U - error: Required type parameters cannot follow optional type parameters + * @param {T} a + * @param {U} b + */ +function f2(a, b) {} +>f2 : (a: T, b: U) => void +>a : T +>b : U + +/** + * @template [T=U] - error: Type parameter defaults can only reference previously declared type parameters. + * @template [U=T] + * @param {T} a + * @param {U} b + */ +function f3(a, b) {} +>f3 : (a: T, b: U) => void +>a : T +>b : U + diff --git a/tests/baselines/reference/jsdocTemplateTagNameResolution.errors.txt b/tests/baselines/reference/jsdocTemplateTagNameResolution.errors.txt new file mode 100644 index 0000000000000..7c23e7abacad7 --- /dev/null +++ b/tests/baselines/reference/jsdocTemplateTagNameResolution.errors.txt @@ -0,0 +1,16 @@ +tests/cases/conformance/jsdoc/file.js(10,7): error TS2322: Type 'string' is not assignable to type 'number'. + + +==== tests/cases/conformance/jsdoc/file.js (1 errors) ==== + /** + * @template T + * @template {keyof T} K + * @typedef {T[K]} Foo + */ + + const x = { a: 1 }; + + /** @type {Foo} */ + const y = "a"; + ~ +!!! error TS2322: Type 'string' is not assignable to type 'number'. \ No newline at end of file diff --git a/tests/baselines/reference/jsdocTemplateTagNameResolution.symbols b/tests/baselines/reference/jsdocTemplateTagNameResolution.symbols new file mode 100644 index 0000000000000..3252fee63ba24 --- /dev/null +++ b/tests/baselines/reference/jsdocTemplateTagNameResolution.symbols @@ -0,0 +1,15 @@ +=== tests/cases/conformance/jsdoc/file.js === +/** + * @template T + * @template {keyof T} K + * @typedef {T[K]} Foo + */ + +const x = { a: 1 }; +>x : Symbol(x, Decl(file.js, 6, 5)) +>a : Symbol(a, Decl(file.js, 6, 11)) + +/** @type {Foo} */ +const y = "a"; +>y : Symbol(y, Decl(file.js, 9, 5)) + diff --git a/tests/baselines/reference/jsdocTemplateTagNameResolution.types b/tests/baselines/reference/jsdocTemplateTagNameResolution.types new file mode 100644 index 0000000000000..ea9ec471e13ad --- /dev/null +++ b/tests/baselines/reference/jsdocTemplateTagNameResolution.types @@ -0,0 +1,18 @@ +=== tests/cases/conformance/jsdoc/file.js === +/** + * @template T + * @template {keyof T} K + * @typedef {T[K]} Foo + */ + +const x = { a: 1 }; +>x : { a: number; } +>{ a: 1 } : { a: number; } +>a : number +>1 : 1 + +/** @type {Foo} */ +const y = "a"; +>y : number +>"a" : "a" + diff --git a/tests/cases/conformance/jsdoc/jsdocTemplateTagDefault.ts b/tests/cases/conformance/jsdoc/jsdocTemplateTagDefault.ts new file mode 100644 index 0000000000000..8a532cde27895 --- /dev/null +++ b/tests/cases/conformance/jsdoc/jsdocTemplateTagDefault.ts @@ -0,0 +1,76 @@ +// @allowJs: true +// @checkJs: true +// @noEmit: true +// @Filename: file.js + +/** + * @template {string | number} [T=string] - ok: defaults are permitted + * @typedef {[T]} A + */ + +/** @type {A} */ // ok, default for `T` in `A` is `string` +const aDefault1 = [""]; +/** @type {A} */ // error: `number` is not assignable to string` +const aDefault2 = [0]; +/** @type {A} */ // ok, `T` is provided for `A` +const aString = [""]; +/** @type {A} */ // ok, `T` is provided for `A` +const aNumber = [0]; + +/** + * @template T + * @template [U=T] - ok: default can reference earlier type parameter + * @typedef {[T, U]} B + */ + +/** + * @template {string | number} [T] - error: default requires an `=type` + * @typedef {[T]} C + */ + +/** + * @template {string | number} [T=] - error: default requires a `type` + * @typedef {[T]} D + */ + +/** + * @template {string | number} [T=string] + * @template U - error: Required type parameters cannot follow optional type parameters + * @typedef {[T, U]} E + */ + +/** + * @template {string | number} [T=string] + * @template U - error: Required type parameters cannot follow optional type parameters + * @typedef {[T, U]} F + */ + +/** + * @template [T=U] - error: Type parameter defaults can only reference previously declared type parameters. + * @template [U=T] + * @typedef {[T, U]} G + */ + +/** + * @template T + * @template [U=T] - ok: default can reference earlier type parameter + * @param {T} a + * @param {U} b + */ +function f1(a, b) {} + + /** + * @template {string | number} [T=string] + * @template U - error: Required type parameters cannot follow optional type parameters + * @param {T} a + * @param {U} b + */ +function f2(a, b) {} + +/** + * @template [T=U] - error: Type parameter defaults can only reference previously declared type parameters. + * @template [U=T] + * @param {T} a + * @param {U} b + */ +function f3(a, b) {} diff --git a/tests/cases/conformance/jsdoc/jsdocTemplateTagNameResolution.ts b/tests/cases/conformance/jsdoc/jsdocTemplateTagNameResolution.ts new file mode 100644 index 0000000000000..e529a7be4250e --- /dev/null +++ b/tests/cases/conformance/jsdoc/jsdocTemplateTagNameResolution.ts @@ -0,0 +1,15 @@ +// @allowJs: true +// @checkJs: true +// @noEmit: true +// @Filename: file.js + +/** + * @template T + * @template {keyof T} K + * @typedef {T[K]} Foo + */ + +const x = { a: 1 }; + +/** @type {Foo} */ +const y = "a"; \ No newline at end of file