Skip to content

Commit 45959de

Browse files
committed
Support linking to type alias properties
Resolves #2524
1 parent 9819646 commit 45959de

File tree

8 files changed

+105
-18
lines changed

8 files changed

+105
-18
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ title: Changelog
7575
overloads if present, #2718.
7676
- Fixed handling of `@enum` if the type was declared before the variable, #2719.
7777
- Fixed empty top level modules page in packages mode, #2753.
78+
- TypeDoc can now link to type alias properties, #2524.
7879
- Fixed an issue where properties were not properly marked optional in some
7980
cases. This primarily affected destructured parameters.
8081
- Added `yaml` to the highlight languages supported by default.

src/lib/models/reflections/abstract.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import type {
1818
} from "../../internationalization/index.js";
1919
import type { ParameterReflection } from "./parameter.js";
2020
import { createNormalizedUrl } from "../../utils/html.js";
21+
import type { ReferenceReflection } from "./reference.js";
2122

2223
/**
2324
* Current reflection id.
@@ -486,6 +487,9 @@ export abstract class Reflection {
486487
isDocument(): this is DocumentReflection {
487488
return false;
488489
}
490+
isReference(): this is ReferenceReflection {
491+
return this.variant === "reference";
492+
}
489493

490494
/**
491495
* Check if this reflection or any of its parents have been marked with the `@deprecated` tag.

src/lib/models/reflections/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,4 @@ export {
2424
export { SignatureReflection } from "./signature.js";
2525
export { TypeParameterReflection, VarianceModifier } from "./type-parameter.js";
2626
export { splitUnquotedString } from "./utils.js";
27-
export type { ReflectionVariant } from "./variant.js";
27+
export type { ReflectionVariant, SomeReflection } from "./variant.js";

src/lib/models/reflections/variant.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,5 @@ export interface ReflectionVariant {
1919
typeParam: TypeParameterReflection;
2020
document: DocumentReflection;
2121
}
22+
23+
export type SomeReflection = ReflectionVariant[keyof ReflectionVariant];

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

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ export function typeDeclaration(context: DefaultThemeRenderContext, type: SomeTy
3737
return (
3838
<div class="tsd-type-declaration">
3939
<h4>{context.i18n.theme_type_declaration()}</h4>
40-
{context.typeDetails(type)}
40+
{context.typeDetails(type, true)}
4141
</div>
4242
);
4343
}
@@ -57,21 +57,22 @@ function shouldExpandReference(reference: ReferenceType) {
5757
return expanded.has(target) === false;
5858
}
5959

60-
export function typeDetails(context: DefaultThemeRenderContext, type: SomeType): JSX.Children {
61-
return typeDetailsImpl(context, type);
60+
export function typeDetails(context: DefaultThemeRenderContext, type: SomeType, renderAnchors: boolean): JSX.Children {
61+
return typeDetailsImpl(context, type, renderAnchors);
6262
}
6363

6464
export function typeDetailsImpl(
6565
context: DefaultThemeRenderContext,
6666
type: SomeType,
67+
renderAnchors: boolean,
6768
highlighted?: Map<string, CommentDisplayPart[]>,
6869
): JSX.Children {
6970
const result = type.visit<JSX.Children>({
7071
array(type) {
71-
return context.typeDetails(type.elementType);
72+
return context.typeDetails(type.elementType, renderAnchors);
7273
},
7374
intersection(type) {
74-
return type.types.map(context.typeDetails);
75+
return type.types.map((t) => context.typeDetails(t, renderAnchors));
7576
},
7677
union(type) {
7778
const result: JSX.Children = [];
@@ -89,9 +90,9 @@ export function typeDetailsImpl(
8990
reflection(type) {
9091
const declaration = type.declaration;
9192
if (highlighted) {
92-
return highlightedDeclarationDetails(context, declaration, highlighted);
93+
return highlightedDeclarationDetails(context, declaration, renderAnchors, highlighted);
9394
}
94-
return declarationDetails(context, declaration);
95+
return declarationDetails(context, declaration, renderAnchors);
9596
},
9697
reference(reference) {
9798
if (shouldExpandReference(reference)) {
@@ -103,8 +104,8 @@ export function typeDetailsImpl(
103104
// Ensure we don't go into an infinite loop here
104105
expanded.add(target);
105106
const details = target.type
106-
? typeDetailsImpl(context, target.type)
107-
: declarationDetails(context, target);
107+
? context.typeDetails(target.type, renderAnchors)
108+
: declarationDetails(context, target, renderAnchors);
108109
expanded.delete(target);
109110
return details;
110111
}
@@ -121,7 +122,7 @@ export function typeDetailsImpl(
121122

122123
export function typeDetailsIfUseful(context: DefaultThemeRenderContext, type: SomeType | undefined): JSX.Children {
123124
if (type && renderingTypeDetailsIsUseful(type)) {
124-
return context.typeDetails(type);
125+
return context.typeDetails(type, false);
125126
}
126127
}
127128

@@ -150,6 +151,7 @@ function highlightedPropertyDetails(
150151
function highlightedDeclarationDetails(
151152
context: DefaultThemeRenderContext,
152153
declaration: DeclarationReflection,
154+
renderAnchors: boolean,
153155
highlightedProperties?: Map<string, CommentDisplayPart[]>,
154156
) {
155157
return (
@@ -159,13 +161,17 @@ function highlightedDeclarationDetails(
159161
?.map(
160162
(child) =>
161163
highlightedProperties?.has(child.name) &&
162-
renderChild(context, child, highlightedProperties.get(child.name)),
164+
renderChild(context, child, renderAnchors, highlightedProperties.get(child.name)),
163165
)}
164166
</ul>
165167
);
166168
}
167169

168-
function declarationDetails(context: DefaultThemeRenderContext, declaration: DeclarationReflection): JSX.Children {
170+
function declarationDetails(
171+
context: DefaultThemeRenderContext,
172+
declaration: DeclarationReflection,
173+
renderAnchors: boolean,
174+
): JSX.Children {
169175
return (
170176
<>
171177
{context.commentSummary(declaration)}
@@ -191,7 +197,7 @@ function declarationDetails(context: DefaultThemeRenderContext, declaration: Dec
191197
</li>
192198
)}
193199
{declaration.indexSignatures?.map((index) => renderIndexSignature(context, index))}
194-
{declaration.getProperties()?.map((child) => renderChild(context, child))}
200+
{declaration.getProperties()?.map((child) => renderChild(context, child, renderAnchors))}
195201
</ul>
196202
</>
197203
);
@@ -200,6 +206,7 @@ function declarationDetails(context: DefaultThemeRenderContext, declaration: Dec
200206
function renderChild(
201207
context: DefaultThemeRenderContext,
202208
child: DeclarationReflection,
209+
renderAnchors: boolean,
203210
highlight?: CommentDisplayPart[],
204211
) {
205212
if (child.signatures) {
@@ -208,6 +215,7 @@ function renderChild(
208215
<h5>
209216
{!!child.flags.isRest && <span class="tsd-signature-symbol">...</span>}
210217
<span class={getKindClass(child)}>{child.name}</span>
218+
<a id={child.anchor} class="tsd-anchor"></a>
211219
<span class="tsd-signature-symbol">{!!child.flags.isOptional && "?"}:</span>
212220
function
213221
</h5>
@@ -237,6 +245,7 @@ function renderChild(
237245
{context.reflectionFlags(child)}
238246
{!!child.flags.isRest && <span class="tsd-signature-symbol">...</span>}
239247
<span class={getKindClass(child)}>{child.name}</span>
248+
<a id={child.anchor} class="tsd-anchor"></a>
240249
<span class="tsd-signature-symbol">
241250
{!!child.flags.isOptional && "?"}
242251
{": "}
@@ -245,7 +254,9 @@ function renderChild(
245254
</h5>
246255
{highlightOrComment(child)}
247256
{child.getProperties().some(renderingChildIsUseful) && (
248-
<ul class="tsd-parameters">{child.getProperties().map((c) => renderChild(context, c))}</ul>
257+
<ul class="tsd-parameters">
258+
{child.getProperties().map((c) => renderChild(context, c, renderAnchors))}
259+
</ul>
249260
)}
250261
</li>
251262
);
@@ -260,6 +271,7 @@ function renderChild(
260271
{context.reflectionFlags(child.getSignature)}
261272
<span class="tsd-signature-keyword">get </span>
262273
<span class={getKindClass(child)}>{child.name}</span>
274+
<a id={child.anchor} class="tsd-anchor"></a>
263275
<span class="tsd-signature-symbol">(): </span>
264276
{context.type(child.getSignature.type)}
265277
</h5>
@@ -273,6 +285,7 @@ function renderChild(
273285
{context.reflectionFlags(child.setSignature)}
274286
<span class="tsd-signature-keyword">set </span>
275287
<span class={getKindClass(child)}>{child.name}</span>
288+
{!child.getSignature && <a id={child.anchor} class="tsd-anchor"></a>}
276289
<span class="tsd-signature-symbol">(</span>
277290
{child.setSignature.parameters?.map((item) => (
278291
<>
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export type Alias = {
2+
/** {@link Alias.other} {@link other} */
3+
readonly default: string;
4+
/** {@link Alias.default} {@link default} */
5+
readonly other: string;
6+
};

src/test/issues.c2.test.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1471,6 +1471,37 @@ describe("Issue Tests", () => {
14711471
equal(getSigComment(project, "fooWithComment", 1), "Overload 2");
14721472
});
14731473

1474+
it("#2524 Handles links to type alias properties", () => {
1475+
const project = convert();
1476+
app.options.setValue("validation", false);
1477+
app.options.setValue("validation", { invalidLink: true });
1478+
app.validate(project);
1479+
1480+
const def = query(project, "Alias.default");
1481+
equal(getLinks(def), [
1482+
{
1483+
display: "other",
1484+
target: [ReflectionKind.Property, "Alias.__type.other"],
1485+
},
1486+
{
1487+
display: "other",
1488+
target: [ReflectionKind.Property, "Alias.__type.other"],
1489+
},
1490+
]);
1491+
1492+
const other = query(project, "Alias.other");
1493+
equal(getLinks(other), [
1494+
{
1495+
display: "default",
1496+
target: [ReflectionKind.Property, "Alias.__type.default"],
1497+
},
1498+
{
1499+
display: "default",
1500+
target: [ReflectionKind.Property, "Alias.__type.default"],
1501+
},
1502+
]);
1503+
});
1504+
14741505
it("#2545 discovers comments from non-exported 'parent' methods", () => {
14751506
const project = convert();
14761507

src/test/utils.ts

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,44 @@ import {
99
} from "../index.js";
1010
import { filterMap } from "../lib/utils/index.js";
1111
import { equal } from "assert/strict";
12+
import type { SomeReflection } from "../lib/models/reflections/variant.js";
1213

1314
export function query(
1415
project: ProjectReflection,
1516
name: string,
1617
): DeclarationReflection {
17-
const reflection = project.getChildByName(name);
18-
ok(reflection instanceof DeclarationReflection, `Failed to find ${name}`);
19-
return reflection;
18+
let refl: SomeReflection | undefined = project;
19+
const parts = name.split(".");
20+
21+
for (let i = 0; i < parts.length; ++i) {
22+
if (!refl) break;
23+
if (refl.isReference()) {
24+
refl = refl.getTargetReflectionDeep() as SomeReflection;
25+
}
26+
27+
if (refl.isDocument()) {
28+
throw new Error("Found document");
29+
}
30+
31+
if (refl.isProject()) {
32+
refl = refl.getChildByName([parts[i]]) as
33+
| SomeReflection
34+
| undefined;
35+
continue;
36+
}
37+
38+
if (refl.type?.type === "reflection") {
39+
refl = refl.type.declaration.getChildByName([parts[i]]) as
40+
| SomeReflection
41+
| undefined;
42+
continue;
43+
}
44+
45+
refl = refl.getChildByName([parts[i]]) as SomeReflection | undefined;
46+
}
47+
48+
ok(refl instanceof DeclarationReflection, `Failed to find ${name}`);
49+
return refl;
2050
}
2151

2252
export function querySig(

0 commit comments

Comments
 (0)