Skip to content

Commit eefb5ba

Browse files
committed
Handle nested object @param comments
Resolves #2555
1 parent 74f5b6e commit eefb5ba

File tree

8 files changed

+130
-31
lines changed

8 files changed

+130
-31
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ title: Changelog
2929

3030
- TypeDoc will now discover entry points from `package.json` exports if they
3131
are not provided manually, #1937.
32+
- Improved support for `@param` comments with nested object types, #2555.
3233
- Improved support for `@param` comments which reference a type
3334
alias/interface. Important properties on the referenced type can now be
3435
highlighted with `@param options.foo`, which will result in the additional

src/lib/converter/plugins/CommentPlugin.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -722,7 +722,8 @@ function moveNestedParamTags(
722722
for (const tag of tags) {
723723
const path = tag.name!.split(".");
724724
path.shift();
725-
const child = target.declaration.getChildByName(path);
725+
const child =
726+
target.declaration.getChildOrTypePropertyByName(path);
726727

727728
if (child && !child.comment) {
728729
child.comment = new Comment(

src/lib/models/reflections/declaration.ts

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,6 @@ export class DeclarationReflection extends ContainerReflection {
192192
return result;
193193
}
194194

195-
/** @internal */
196195
getNonIndexSignatures(): SignatureReflection[] {
197196
return ([] as SignatureReflection[]).concat(
198197
this.signatures ?? [],
@@ -201,6 +200,43 @@ export class DeclarationReflection extends ContainerReflection {
201200
);
202201
}
203202

203+
getProperties(): DeclarationReflection[] {
204+
if (this.children?.length) {
205+
return this.children;
206+
}
207+
208+
if (this.type?.type === "reflection") {
209+
return this.type.declaration.children ?? [];
210+
}
211+
return [];
212+
}
213+
214+
getChildOrTypePropertyByName(
215+
path: string[],
216+
): DeclarationReflection | undefined {
217+
if (this.type?.type === "reflection") {
218+
for (const child of this.type.declaration.children || []) {
219+
if (path[0] === child.name) {
220+
if (path.length === 1) {
221+
return child;
222+
}
223+
return child.getChildOrTypePropertyByName(path.slice(1));
224+
}
225+
}
226+
}
227+
228+
for (const child of this.children || []) {
229+
if (path[0] === child.name) {
230+
if (path.length === 1) {
231+
return child;
232+
}
233+
return child.getChildOrTypePropertyByName(path.slice(1));
234+
}
235+
}
236+
237+
return undefined;
238+
}
239+
204240
override traverse(callback: TraverseCallback) {
205241
for (const parameter of this.typeParameters?.slice() || []) {
206242
if (callback(parameter, TraverseProperty.TypeParameter) === false) {

src/lib/output/formatter.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -714,7 +714,7 @@ export class FormattedCodeBuilder {
714714
options: { topLevelLinks: boolean },
715715
): FormatterNode {
716716
const members: FormatterNode[] = [];
717-
const children = reflection.children || [];
717+
const children = reflection.getProperties();
718718

719719
for (const item of children) {
720720
this.member(members, item, options);

src/lib/output/themes/default/partials/typeDetails.tsx

Lines changed: 31 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,10 @@ export function typeDetailsImpl(
8888
},
8989
reflection(type) {
9090
const declaration = type.declaration;
91-
return declarationDetails(context, declaration, highlighted);
91+
if (highlighted) {
92+
return highlightedDeclarationDetails(context, declaration, highlighted);
93+
}
94+
return declarationDetails(context, declaration);
9295
},
9396
reference(reference) {
9497
if (shouldExpandReference(reference)) {
@@ -100,8 +103,8 @@ export function typeDetailsImpl(
100103
// Ensure we don't go into an infinite loop here
101104
expanded.add(target);
102105
const details = target.type
103-
? typeDetailsImpl(context, target.type, reference.highlightedProperties)
104-
: declarationDetails(context, target, reference.highlightedProperties);
106+
? typeDetailsImpl(context, target.type)
107+
: declarationDetails(context, target);
105108
expanded.delete(target);
106109
return details;
107110
}
@@ -144,23 +147,25 @@ function highlightedPropertyDetails(
144147
);
145148
}
146149

147-
function declarationDetails(
150+
function highlightedDeclarationDetails(
148151
context: DefaultThemeRenderContext,
149152
declaration: DeclarationReflection,
150153
highlightedProperties?: Map<string, CommentDisplayPart[]>,
151-
): JSX.Children {
152-
if (!declaration.comment?.hasModifier("@expand")) {
153-
return (
154-
<ul class="tsd-parameters">
155-
{declaration.children?.map(
154+
) {
155+
return (
156+
<ul class="tsd-parameters">
157+
{declaration
158+
.getProperties()
159+
?.map(
156160
(child) =>
157161
highlightedProperties?.has(child.name) &&
158162
renderChild(context, child, highlightedProperties.get(child.name)),
159163
)}
160-
</ul>
161-
);
162-
}
164+
</ul>
165+
);
166+
}
163167

168+
function declarationDetails(context: DefaultThemeRenderContext, declaration: DeclarationReflection): JSX.Children {
164169
return (
165170
<>
166171
{context.commentSummary(declaration)}
@@ -186,9 +191,7 @@ function declarationDetails(
186191
</li>
187192
)}
188193
{declaration.indexSignatures?.map((index) => renderIndexSignature(context, index))}
189-
{declaration.children?.map((child) =>
190-
renderChild(context, child, highlightedProperties?.get(child.name)),
191-
)}
194+
{declaration.getProperties()?.map((child) => renderChild(context, child))}
192195
</ul>
193196
</>
194197
);
@@ -241,8 +244,8 @@ function renderChild(
241244
{context.type(child.type)}
242245
</h5>
243246
{highlightOrComment(child)}
244-
{child.children?.some(renderingChildIsUseful) && (
245-
<ul class="tsd-parameters">{child.children.map((c) => renderChild(context, c))}</ul>
247+
{child.getProperties().some(renderingChildIsUseful) && (
248+
<ul class="tsd-parameters">{child.getProperties().map((c) => renderChild(context, c))}</ul>
246249
)}
247250
</li>
248251
);
@@ -312,19 +315,20 @@ function renderIndexSignature(context: DefaultThemeRenderContext, index: Signatu
312315
}
313316

314317
function renderingChildIsUseful(refl: DeclarationReflection) {
315-
if (refl.hasComment()) return true;
316-
if (
317-
refl.children?.some((child) => {
318-
if (child.hasComment()) return true;
319-
if (child.type) {
320-
return renderingTypeDetailsIsUseful(child.type);
321-
}
322-
})
323-
) {
318+
if (renderingThisChildIsUseful(refl)) {
324319
return true;
325320
}
326321

327-
return refl.getAllSignatures().some((sig) => {
322+
return refl.getProperties().some(renderingThisChildIsUseful);
323+
}
324+
325+
function renderingThisChildIsUseful(refl: DeclarationReflection) {
326+
if (refl.hasComment()) return true;
327+
328+
const declaration = refl.type?.type === "reflection" ? refl.type.declaration : refl;
329+
if (declaration.hasComment()) return true;
330+
331+
return declaration.getAllSignatures().some((sig) => {
328332
return sig.hasComment() || sig.parameters?.some((p) => p.hasComment());
329333
});
330334
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/**
2+
* @param props - Component properties.
3+
* @param props.title - Title.
4+
* @param props.options - Options.
5+
* @param props.options.featureA - Turn on or off featureA.
6+
* @param props.options.featureB - Turn on or off featureB.
7+
*/
8+
export function ComponentWithOptions({
9+
title,
10+
options,
11+
}: {
12+
title: string;
13+
options: { featureA: boolean; featureB: boolean };
14+
}) {}

src/test/converter2/typedoc.json

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,14 @@
11
// This is here so we can point to it with doc:c2 and avoid warnings caused by TypeDoc's actual configuration.
22
{
3-
"logLevel": "Verbose"
3+
"logLevel": "Verbose",
4+
"outputs": [
5+
{
6+
"name": "html",
7+
"path": "../../../docs"
8+
},
9+
{
10+
"name": "json",
11+
"path": "../../../docs/docs.json"
12+
}
13+
]
414
}

src/test/issues.c2.test.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1509,6 +1509,39 @@ describe("Issue Tests", () => {
15091509
logger.expectNoOtherMessages();
15101510
});
15111511

1512+
it("#2555 allows nested @param comments", () => {
1513+
const project = convert();
1514+
const sig = querySig(project, "ComponentWithOptions");
1515+
const param = sig.parameters?.[0];
1516+
equal(param?.type?.type, "reflection");
1517+
const title = param.type.declaration.getChildOrTypePropertyByName([
1518+
"title",
1519+
]);
1520+
const options = param.type.declaration.getChildOrTypePropertyByName([
1521+
"options",
1522+
]);
1523+
const featureA = param.type.declaration.getChildOrTypePropertyByName([
1524+
"options",
1525+
"featureA",
1526+
]);
1527+
const featureB = param.type.declaration.getChildOrTypePropertyByName([
1528+
"options",
1529+
"featureB",
1530+
]);
1531+
1532+
const comments = [param, title, options, featureA, featureB].map((d) =>
1533+
Comment.combineDisplayParts(d?.comment?.summary),
1534+
);
1535+
1536+
equal(comments, [
1537+
"Component properties.",
1538+
"Title.",
1539+
"Options.",
1540+
"Turn on or off featureA.",
1541+
"Turn on or off featureB.",
1542+
]);
1543+
});
1544+
15121545
it("#2574 default export", () => {
15131546
const project = convert();
15141547
const sig = querySig(project, "usesDefaultExport");

0 commit comments

Comments
 (0)