From 130ba480233c4e052551f76289589c1150f1bd27 Mon Sep 17 00:00:00 2001 From: Gerrit Birkeland Date: Sat, 7 Oct 2023 18:16:05 -0600 Subject: [PATCH] Handle `@template` constraints correctly Resolves #2389 --- CHANGELOG.md | 1 + src/lib/converter/factories/signature.ts | 48 ++++++++++++++++++++++++ src/lib/converter/jsdoc.ts | 10 +---- src/test/converter2/issues/gh2389.js | 6 +++ src/test/issues.c2.test.ts | 11 ++++++ 5 files changed, 67 insertions(+), 9 deletions(-) create mode 100644 src/test/converter2/issues/gh2389.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 485c15b01..864405692 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ ### Bug Fixes +- Fixed conversion of `@template` constraints on JSDoc defined type parameters, #2389. - Invalid link validation is now correctly suppressed before all projects have been converted in packages mode, #2403. - Fixed tsconfig handling for projects using a solution-style tsconfig, #2406. - Fixed broken settings icons caused by icon caching introduced in 0.25.1, #2408. diff --git a/src/lib/converter/factories/signature.ts b/src/lib/converter/factories/signature.ts index d295aab80..e9bd83492 100644 --- a/src/lib/converter/factories/signature.ts +++ b/src/lib/converter/factories/signature.ts @@ -340,6 +340,54 @@ export function createTypeParamReflection( return paramRefl; } +export function convertTemplateParameterNodes( + context: Context, + nodes: readonly ts.JSDocTemplateTag[] | undefined, +) { + return nodes?.flatMap((node) => { + return node.typeParameters.map((param, index) => { + const paramRefl = new TypeParameterReflection( + param.name.text, + context.scope, + getVariance(param.modifiers), + ); + const paramScope = context.withScope(paramRefl); + paramRefl.type = + index || !node.constraint + ? void 0 + : context.converter.convertType( + paramScope, + node.constraint.type, + ); + paramRefl.default = param.default + ? context.converter.convertType(paramScope, param.default) + : void 0; + if ( + param.modifiers?.some( + (m) => m.kind === ts.SyntaxKind.ConstKeyword, + ) + ) { + paramRefl.flags.setFlag(ReflectionFlag.Const, true); + } + + context.registerReflection(paramRefl, param.symbol); + + if (ts.isJSDocTemplateTag(param.parent)) { + paramRefl.comment = context.getJsDocComment(param.parent); + } + + context.trigger( + ConverterEvents.CREATE_TYPE_PARAMETER, + paramRefl, + param, + ); + return paramRefl; + }); + }); + const params = (nodes ?? []).flatMap((tag) => tag.typeParameters); + return convertTypeParameterNodes(context, params); +} + function getVariance( modifiers: ts.ModifiersArray | undefined, ): VarianceModifier | undefined { diff --git a/src/lib/converter/jsdoc.ts b/src/lib/converter/jsdoc.ts index 8b4347245..da5b38c8a 100644 --- a/src/lib/converter/jsdoc.ts +++ b/src/lib/converter/jsdoc.ts @@ -16,7 +16,7 @@ import type { Context } from "./context"; import { ConverterEvents } from "./converter-events"; import { convertParameterNodes, - convertTypeParameterNodes, + convertTemplateParameterNodes, } from "./factories/signature"; export function convertJsDocAlias( @@ -163,14 +163,6 @@ function convertTemplateParameters(context: Context, node: ts.JSDoc) { ); } -function convertTemplateParameterNodes( - context: Context, - nodes: readonly ts.JSDocTemplateTag[] | undefined, -) { - const params = (nodes ?? []).flatMap((tag) => tag.typeParameters); - return convertTypeParameterNodes(context, params); -} - function getTypedefReExportTarget( context: Context, declaration: ts.JSDocTypedefTag | ts.JSDocEnumTag, diff --git a/src/test/converter2/issues/gh2389.js b/src/test/converter2/issues/gh2389.js new file mode 100644 index 000000000..4814a0706 --- /dev/null +++ b/src/test/converter2/issues/gh2389.js @@ -0,0 +1,6 @@ +/** + * @template {string} T, U + * @param {T} x + * @param {U} y + */ +export function foo(x, y) {} diff --git a/src/test/issues.c2.test.ts b/src/test/issues.c2.test.ts index a2b0059cd..d91355fb7 100644 --- a/src/test/issues.c2.test.ts +++ b/src/test/issues.c2.test.ts @@ -1163,6 +1163,17 @@ describe("Issue Tests", () => { ); }); + it("Handles @template parameter constraints correctly, #2389", () => { + const project = convert(); + const foo = query(project, "foo"); + equal(foo.signatures?.length, 1); + equal(foo.signatures[0].typeParameters?.length, 2); + + const [T, U] = foo.signatures[0].typeParameters; + equal(T.type?.toString(), "string"); + equal(U.type?.toString(), undefined); + }); + // This is rather unfortunate, we need to do this so that files which include only // a single declare module can still have a comment on them, but it looks really // weird and wrong if there are multiple declare module statements in a file...