From cf787e9bcf46b83e13e7fcc6f6b301934debcdae Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Wed, 8 Sep 2021 17:05:07 -0700 Subject: [PATCH] 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 --- src/compiler/binder.ts | 2 +- src/compiler/checker.ts | 9 +- src/compiler/parser.ts | 15 +- src/compiler/utilities.ts | 12 ++ .../jsdocTemplateTagDefault.errors.txt | 90 +++++++++ .../reference/jsdocTemplateTagDefault.js | 186 ++++++++++++++++++ .../reference/jsdocTemplateTagDefault.symbols | 83 ++++++++ .../reference/jsdocTemplateTagDefault.types | 91 +++++++++ .../jsdocTemplateTagNameResolution.errors.txt | 16 ++ .../jsdocTemplateTagNameResolution.js | 30 +++ .../jsdocTemplateTagNameResolution.symbols | 15 ++ .../jsdocTemplateTagNameResolution.types | 18 ++ .../jsdoc/jsdocTemplateTagDefault.ts | 71 +++++++ .../jsdoc/jsdocTemplateTagNameResolution.ts | 16 ++ 14 files changed, 649 insertions(+), 5 deletions(-) create mode 100644 tests/baselines/reference/jsdocTemplateTagDefault.errors.txt create mode 100644 tests/baselines/reference/jsdocTemplateTagDefault.js create mode 100644 tests/baselines/reference/jsdocTemplateTagDefault.symbols create mode 100644 tests/baselines/reference/jsdocTemplateTagDefault.types create mode 100644 tests/baselines/reference/jsdocTemplateTagNameResolution.errors.txt create mode 100644 tests/baselines/reference/jsdocTemplateTagNameResolution.js create mode 100644 tests/baselines/reference/jsdocTemplateTagNameResolution.symbols create mode 100644 tests/baselines/reference/jsdocTemplateTagNameResolution.types create mode 100644 tests/cases/conformance/jsdoc/jsdocTemplateTagDefault.ts create mode 100644 tests/cases/conformance/jsdoc/jsdocTemplateTagNameResolution.ts diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 96a35d5a4c77d..9087823617bca 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -3388,7 +3388,7 @@ namespace ts { function bindTypeParameter(node: TypeParameterDeclaration) { if (isJSDocTemplateTag(node.parent)) { - const container = find((node.parent.parent as JSDoc).tags!, isJSDocTypeAlias) || getHostSignatureFromJSDoc(node.parent); // TODO: GH#18217 + const container = getEffectiveContainerForJSDocTemplateTag(node.parent); if (container) { if (!container.locals) { container.locals = createSymbolTable(); diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index d402546ade20b..d69fc6fb81bd9 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2052,7 +2052,9 @@ namespace ts { lastSelfReferenceLocation = location; } lastLocation = location; - location = location.parent; + location = isJSDocTemplateTag(location) ? + getEffectiveContainerForJSDocTemplateTag(location) || location.parent : + location.parent; } // 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 { function getParentSymbolOfTypeParameter(typeParameter: TypeParameter): Symbol | undefined { const tp = getDeclarationOfKind(typeParameter.symbol, SyntaxKind.TypeParameter)!; - const host = isJSDocTemplateTag(tp.parent) ? getHostSignatureFromJSDoc(tp.parent) : tp.parent; + const host = isJSDocTemplateTag(tp.parent) ? getEffectiveContainerForJSDocTemplateTag(tp.parent) : tp.parent; return host && getSymbolOfNode(host); } @@ -33673,7 +33675,7 @@ namespace ts { } } - checkTypeParameters(node.typeParameters); + checkTypeParameters(getEffectiveTypeParameterDeclarations(node)); forEach(node.parameters, checkParameter); @@ -35306,6 +35308,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..dbe5394908d48 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -8441,11 +8441,24 @@ namespace ts { function parseTemplateTagTypeParameter() { const typeParameterPos = getNodePos(); + 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); + } + 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/utilities.ts b/src/compiler/utilities.ts index 19784af87c0ff..64f822a842263 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -2723,6 +2723,18 @@ namespace ts { return parameter && parameter.symbol; } + export function getEffectiveContainerForJSDocTemplateTag(node: JSDocTemplateTag) { + if (isJSDoc(node.parent) && node.parent.tags) { + // A @template tag belongs to any @typedef, @callback, or @enum tags in the same comment block, if they exist. + const typeAlias = find(node.parent.tags, isJSDocTypeAlias); + if (typeAlias) { + return typeAlias; + } + } + // otherwise it belongs to the host it annotates + return getHostSignatureFromJSDoc(node); + } + export function getHostSignatureFromJSDoc(node: Node): SignatureDeclaration | undefined { const host = getEffectiveJSDocHost(node); return host && isFunctionLike(host) ? host : undefined; diff --git a/tests/baselines/reference/jsdocTemplateTagDefault.errors.txt b/tests/baselines/reference/jsdocTemplateTagDefault.errors.txt new file mode 100644 index 0000000000000..d21f546ed2dd4 --- /dev/null +++ b/tests/baselines/reference/jsdocTemplateTagDefault.errors.txt @@ -0,0 +1,90 @@ +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(38,17): error TS2744: Type parameter defaults can only reference previously declared type parameters. +tests/cases/conformance/jsdoc/file.js(53,14): error TS2706: Required type parameters may not follow optional type parameters. +tests/cases/conformance/jsdoc/file.js(60,17): error TS2744: Type parameter defaults can only reference previously declared type parameters. + + +==== tests/cases/conformance/jsdoc/file.js (7 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 [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.js b/tests/baselines/reference/jsdocTemplateTagDefault.js new file mode 100644 index 0000000000000..4c65f8f78d6ec --- /dev/null +++ b/tests/baselines/reference/jsdocTemplateTagDefault.js @@ -0,0 +1,186 @@ +//// [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 [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) {} + + +//// [file.js] +/** + * @template {string | number} [T=string] - ok: defaults are permitted + * @typedef {[T]} A + */ +/** @type {A} */ // ok, default for `T` in `A` is `string` +var aDefault1 = [""]; +/** @type {A} */ // error: `number` is not assignable to string` +var aDefault2 = [0]; +/** @type {A} */ // ok, `T` is provided for `A` +var aString = [""]; +/** @type {A} */ // ok, `T` is provided for `A` +var 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 [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) { } + + +//// [file.d.ts] +/** + * @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 [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 + */ +declare function f1(a: T, b: U): void; +/** +* @template {string | number} [T=string] +* @template U - error: Required type parameters cannot follow optional type parameters +* @param {T} a +* @param {U} b +*/ +declare function f2(a: T, b: U): void; +/** + * @template [T=U] - error: Type parameter defaults can only reference previously declared type parameters. + * @template [U=T] + * @param {T} a + * @param {U} b + */ +declare function f3(a: T, b: U): void; +/** + * @template {string | number} [T=string] - ok: defaults are permitted + * @typedef {[T]} A + */ +/** @type {A} */ declare const aDefault1: A; +/** @type {A} */ declare const aDefault2: A; +/** @type {A} */ declare const aString: A; +/** @type {A} */ declare const aNumber: A; +type B = [T, U]; +type C = [T]; +type D = [T]; +type E = [T, U]; +type G = [T, U]; +type A = [T]; diff --git a/tests/baselines/reference/jsdocTemplateTagDefault.symbols b/tests/baselines/reference/jsdocTemplateTagDefault.symbols new file mode 100644 index 0000000000000..19cba3a6a0474 --- /dev/null +++ b/tests/baselines/reference/jsdocTemplateTagDefault.symbols @@ -0,0 +1,83 @@ +=== 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 [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, 48, 12)) +>b : Symbol(b, Decl(file.js, 48, 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, 48, 20)) +>a : Symbol(a, Decl(file.js, 56, 12)) +>b : Symbol(b, Decl(file.js, 56, 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, 56, 20)) +>a : Symbol(a, Decl(file.js, 64, 12)) +>b : Symbol(b, Decl(file.js, 64, 14)) + diff --git a/tests/baselines/reference/jsdocTemplateTagDefault.types b/tests/baselines/reference/jsdocTemplateTagDefault.types new file mode 100644 index 0000000000000..7b00202e84268 --- /dev/null +++ b/tests/baselines/reference/jsdocTemplateTagDefault.types @@ -0,0 +1,91 @@ +=== 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 [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.js b/tests/baselines/reference/jsdocTemplateTagNameResolution.js new file mode 100644 index 0000000000000..0db2a96eb5505 --- /dev/null +++ b/tests/baselines/reference/jsdocTemplateTagNameResolution.js @@ -0,0 +1,30 @@ +//// [file.js] +/** + * @template T + * @template {keyof T} K + * @typedef {T[K]} Foo + */ + +const x = { a: 1 }; + +/** @type {Foo} */ +const y = "a"; + +//// [file.js] +/** + * @template T + * @template {keyof T} K + * @typedef {T[K]} Foo + */ +var x = { a: 1 }; +/** @type {Foo} */ +var y = "a"; + + +//// [file.d.ts] +declare namespace x { + const a: number; +} +/** @type {Foo} */ +declare const y: Foo; +type Foo = T[K]; 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..c93359a7a6eb3 --- /dev/null +++ b/tests/cases/conformance/jsdoc/jsdocTemplateTagDefault.ts @@ -0,0 +1,71 @@ +// @allowJs: true +// @checkJs: true +// @declaration: true +// @outDir: out +// @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 [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..a5b4d052ad2de --- /dev/null +++ b/tests/cases/conformance/jsdoc/jsdocTemplateTagNameResolution.ts @@ -0,0 +1,16 @@ +// @allowJs: true +// @checkJs: true +// @outDir: out +// @declaration: 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