Skip to content

Commit 61c2911

Browse files
committed
Improve support for @class
Resolves multiple bugs reported in #2914
1 parent 6376273 commit 61c2911

File tree

6 files changed

+95
-4
lines changed

6 files changed

+95
-4
lines changed

CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ title: Changelog
66

77
### Bug Fixes
88

9+
- Variables using `@class` now correctly handle `@category`, #2914.
10+
- Variables using `@class` now include constructor parameters, #2914.
11+
- Variables using `@class` with a generic first constructor function now adopt
12+
that function's type parameters as the class type parameters, #2914.
913
- Inlining types can now handle more type variants, #2920.
1014
- API: `toString` on types containing index signatures now behave correctly, #2917.
1115

site/tags/class.md

+6
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@ will result in all "dynamic" properties being expanded to real properties.
1212
TypeDoc will also ignore types/interfaces declared with the same name as
1313
variables annotated with `@class`.
1414

15+
If the constructor function has more than one overload, TypeDoc will use
16+
the return type for the first overload to determine the shape of the class.
17+
18+
If the constructor function is generic, the type parameters will be lifted
19+
up from the constructor function to become class type parameters.
20+
1521
## Example
1622

1723
```ts

src/lib/converter/factories/signature.ts

+21-3
Original file line numberDiff line numberDiff line change
@@ -159,13 +159,31 @@ export function createConstructSignatureWithType(
159159

160160
if (declaration) {
161161
sigRef.comment = context.getSignatureComment(declaration);
162+
if (sigRef.comment?.discoveryId === context.scope.parent?.comment?.discoveryId) {
163+
delete sigRef.comment;
164+
}
162165
}
163166

164-
sigRef.typeParameters = convertTypeParameters(
167+
const parameterSymbols: Array<ts.Symbol & { type?: ts.Type }> = signature.thisParameter
168+
? [signature.thisParameter, ...signature.parameters]
169+
: [...signature.parameters];
170+
171+
// Prevent a `this` parameter from appearing on constructor signature
172+
// as TS disallows them on regular classes.
173+
if (parameterSymbols[0]?.name === "this") {
174+
parameterSymbols.shift();
175+
}
176+
177+
sigRef.parameters = convertParameters(
165178
sigRefCtx,
166179
sigRef,
167-
signature.typeParameters,
180+
parameterSymbols,
181+
declaration?.parameters,
168182
);
183+
sigRef.parameters = convertParameters(sigRefCtx, sigRef, parameterSymbols, undefined);
184+
185+
// Do NOT convert type parameters here, they go on the class itself in the @class case
186+
// See #2914.
169187

170188
sigRef.type = ReferenceType.createResolvedReference(
171189
context.scope.parent!.name,
@@ -368,7 +386,7 @@ function checkForDestructuredParameterDefaults(
368386
}
369387
}
370388

371-
function convertTypeParameters(
389+
export function convertTypeParameters(
372390
context: Context,
373391
parent: Reflection,
374392
parameters: readonly ts.TypeParameter[] | undefined,

src/lib/converter/symbols.ts

+10-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,12 @@ import { getEnumFlags, hasAllFlags, hasAnyFlag, i18n, removeFlag } from "#utils"
1414
import type { Context } from "./context.js";
1515
import { convertDefaultValue } from "./convert-expression.js";
1616
import { convertIndexSignatures } from "./factories/index-signature.js";
17-
import { createConstructSignatureWithType, createSignature, createTypeParamReflection } from "./factories/signature.js";
17+
import {
18+
convertTypeParameters,
19+
createConstructSignatureWithType,
20+
createSignature,
21+
createTypeParamReflection,
22+
} from "./factories/signature.js";
1823
import { convertJsDocAlias, convertJsDocCallback } from "./jsdoc.js";
1924
import { getHeritageTypes } from "./utils/nodes.js";
2025
import { removeUndefined } from "./utils/reflections.js";
@@ -1277,6 +1282,10 @@ function convertSymbolAsClass(
12771282
createConstructSignatureWithType(constructContext, sig, reflection);
12781283
}
12791284

1285+
// Take the type parameters from the first constructor signature and use
1286+
// them as the type parameters for the class, #2914
1287+
reflection.typeParameters = convertTypeParameters(rc, reflection, ctors[0].getTypeParameters());
1288+
12801289
const instType = ctors[0].getReturnType();
12811290
convertSymbols(rc, instType.getProperties());
12821291

src/test/converter2/issues/gh2914.ts

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/**
2+
* Description
3+
* @class
4+
* @category Bug
5+
*/
6+
export declare const Bug1: new () => { x: string };
7+
8+
/** @class */
9+
export declare const Bug2: new (x: string) => {};
10+
11+
/** @class */
12+
export declare const Bug3: new <T extends string>() => {
13+
x: T;
14+
};
15+
16+
/** @class */
17+
export declare const Bug4: new <T extends () => U, U extends string>(x: T) => T;

src/test/issues.c2.test.ts

+37
Original file line numberDiff line numberDiff line change
@@ -2043,6 +2043,43 @@ describe("Issue Tests", () => {
20432043
equal(exp.type?.toString(), "never[]");
20442044
});
20452045

2046+
it("#2914 does not categorize @class constructor if class is categorized", () => {
2047+
app.options.setValue("categorizeByGroup", false);
2048+
const project = convert();
2049+
const Bug1 = query(project, "Bug1");
2050+
equal(Bug1.children?.map(c => c.name), ["constructor", "x"]);
2051+
equal(Bug1.categories === undefined, true, "Should not have categories");
2052+
2053+
equal(project.categories?.length, 2);
2054+
equal(project.categories[0].title, "Bug");
2055+
equal(project.categories[1].title, "Other");
2056+
});
2057+
2058+
it("#2914 includes constructor parameters", () => {
2059+
app.options.setValue("categorizeByGroup", false);
2060+
const project = convert();
2061+
const ctor = querySig(project, "Bug2.constructor");
2062+
equal(ctor.parameters?.length, 1);
2063+
equal(ctor.parameters[0].name, "x");
2064+
equal(ctor.parameters[0].type?.toString(), "string");
2065+
});
2066+
2067+
it("#2914 converts constructor type parameters as class type parameters", () => {
2068+
const project = convert();
2069+
const Bug3 = query(project, "Bug3");
2070+
equal(Bug3.typeParameters?.length, 1);
2071+
equal(Bug3.typeParameters[0].type?.toString(), "string");
2072+
const ctor = querySig(project, "Bug3.constructor");
2073+
equal(ctor.typeParameters, undefined);
2074+
});
2075+
2076+
it("#2914 includes call signatures on the class type", () => {
2077+
const project = convert();
2078+
const Bug4 = query(project, "Bug4");
2079+
equal(Bug4.signatures?.length, 1);
2080+
equal(Bug4.signatures[0].type?.toString(), "U");
2081+
});
2082+
20462083
it("#2917 stringifies index signatures", () => {
20472084
const project = convert();
20482085
const data = query(project, "Foo.data");

0 commit comments

Comments
 (0)