Skip to content

Commit 031f7a4

Browse files
committed
Handle constraints on type params in JS classes
resolves #2929
1 parent 8775e56 commit 031f7a4

File tree

5 files changed

+49
-7
lines changed

5 files changed

+49
-7
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ title: Changelog
2121
- Inlining types can now handle more type variants, #2920.
2222
- Fixed behavior of `externalSymbolLinkMappings` option when URL is set to `#`, #2921.
2323
- Fixed broken links within module pages when structure-dir router was used, #2928.
24+
- Type parameters on JS classes defined with `@typedef` now correctly handle the constraint, #2929.
2425
- API: `toString` on types containing index signatures now behave correctly, #2917.
2526

2627
## v0.28.1 (2025-03-20)

src/lib/converter/comments/index.ts

+9-4
Original file line numberDiff line numberDiff line change
@@ -374,10 +374,15 @@ export function getJsDocComment(
374374
const tag = comment.getIdentifiedTag(name, `@${declaration.tagName.text}`);
375375

376376
if (!tag) {
377-
logger.error(
378-
i18n.failed_to_find_jsdoc_tag_for_name_0(name),
379-
declaration,
380-
);
377+
// If this is a template tag with multiple declarations, we warned already if there
378+
// was a comment attached. If there wasn't, then don't error about failing to find
379+
// a tag because this is unsupported.
380+
if (!ts.isJSDocTemplateTag(declaration)) {
381+
logger.error(
382+
i18n.failed_to_find_jsdoc_tag_for_name_0(name),
383+
declaration,
384+
);
385+
}
381386
} else {
382387
const result = new Comment(Comment.cloneDisplayParts(tag.content));
383388
result.sourcePath = comment.sourcePath;

src/lib/converter/factories/signature.ts

+14-3
Original file line numberDiff line numberDiff line change
@@ -453,12 +453,23 @@ export function createTypeParamReflection(
453453
getVariance(param.modifiers),
454454
);
455455
const paramScope = context.withScope(paramRefl);
456-
paramRefl.type = param.constraint
457-
? context.converter.convertType(paramScope, param.constraint)
458-
: void 0;
456+
457+
if (ts.isJSDocTemplateTag(param.parent)) {
458+
// With a @template tag, the constraint applies only to the
459+
// first type parameter declared.
460+
if (param.parent.typeParameters[0].name.text === param.name.text && param.parent.constraint) {
461+
paramRefl.type = context.converter.convertType(paramScope, param.parent.constraint);
462+
}
463+
} else {
464+
paramRefl.type = param.constraint
465+
? context.converter.convertType(paramScope, param.constraint)
466+
: void 0;
467+
}
468+
459469
paramRefl.default = param.default
460470
? context.converter.convertType(paramScope, param.default)
461471
: void 0;
472+
462473
if (param.modifiers?.some((m) => m.kind === ts.SyntaxKind.ConstKeyword)) {
463474
paramRefl.flags.setFlag(ReflectionFlag.Const, true);
464475
}

src/test/converter2/issues/gh2929.js

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/**
2+
* @template {number} [T=1]
3+
*/
4+
export class NumberManager {
5+
/**
6+
* @param {T[]} nums
7+
*/
8+
constructor(nums) {}
9+
}
10+
11+
/**
12+
* @template {number} A, B
13+
*/
14+
export class EdgeCases {
15+
}

src/test/issues.c2.test.ts

+10
Original file line numberDiff line numberDiff line change
@@ -2099,4 +2099,14 @@ describe("Issue Tests", () => {
20992099
const test3 = query(project, "InlineArray");
21002100
equal(test3.type?.toString(), "string[]");
21012101
});
2102+
2103+
it("#2929 handles type parameters on JS classes", () => {
2104+
const project = convert();
2105+
const NumberManager = query(project, "NumberManager");
2106+
equal(NumberManager.typeParameters?.map(t => t.type?.toString()), ["number"]);
2107+
equal(NumberManager.typeParameters?.map(t => t.default?.toString()), ["1"]);
2108+
2109+
const EdgeCases = query(project, "EdgeCases");
2110+
equal(EdgeCases.typeParameters?.map(t => t.type?.toString()), ["number", undefined]);
2111+
});
21022112
});

0 commit comments

Comments
 (0)