Skip to content

Commit bc54be8

Browse files
committed
Discover parent types through mapped types
Resolves #2978
1 parent b418f6b commit bc54be8

File tree

8 files changed

+88
-28
lines changed

8 files changed

+88
-28
lines changed

.vscode/settings.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
},
66

77
"typescript.tsdk": "./node_modules/typescript/lib",
8+
"dprint.path": "./node_modules/dprint/dprint",
89

910
// Automatically run the formatter when certain files are saved.
1011
"[javascript]": {

CHANGELOG.md

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

1515
- Relative links in `<img srcset>` will now be discovered by TypeDoc, #2975.
1616
- Relative links in `<source src>` and `<source srcset>` elements will now be discovered by TypeDoc, #2975.
17+
- Improved inherited from/overwrites link discovery to point to parent properties in more cases, #2978
1718

1819
### Thanks!
1920

scripts/testcase.js

100644100755
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
#!/usr/bin/env node
12
// @ts-check
23
import md from "markdown-it";
34
import cp from "child_process";

src/lib/converter/plugins/ImplementsPlugin.ts

Lines changed: 58 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -110,8 +110,10 @@ export class ImplementsPlugin extends ConverterComponent {
110110
project: ProjectReflection,
111111
reflection: DeclarationReflection,
112112
) {
113+
if (!reflection.extendedTypes) return;
114+
113115
const extendedTypes = filterMap(
114-
reflection.extendedTypes ?? [],
116+
reflection.extendedTypes,
115117
(type) => {
116118
return type instanceof ReferenceType &&
117119
type.reflection instanceof DeclarationReflection
@@ -139,23 +141,54 @@ export class ImplementsPlugin extends ConverterComponent {
139141
parentMember.signatures ?? [],
140142
)
141143
) {
142-
childSig[key] = ReferenceType.createResolvedReference(
144+
// If we're already pointing at something because TS said we should reference
145+
// it, then don't overwrite the reference.
146+
if (!childSig[key]?.reflection) {
147+
childSig[key] = ReferenceType.createResolvedReference(
148+
`${parent.name}.${parentMember.name}`,
149+
parentSig,
150+
project,
151+
);
152+
}
153+
}
154+
155+
if (!child[key]?.reflection) {
156+
child[key] = ReferenceType.createResolvedReference(
143157
`${parent.name}.${parentMember.name}`,
144-
parentSig,
158+
parentMember,
145159
project,
146160
);
147161
}
148162

149-
child[key] = ReferenceType.createResolvedReference(
150-
`${parent.name}.${parentMember.name}`,
151-
parentMember,
152-
project,
153-
);
154-
155163
this.handleInheritedComments(child, parentMember);
156164
}
157165
}
158166
}
167+
168+
// #2978, this is very unfortunate. If a child's parent links are broken at this point,
169+
// we replace them with an intentionally broken link so that they won't ever be resolved.
170+
// This is done because if we don't do it then we run into issues where we have a link which
171+
// points to some ReflectionSymbolId which might not exist now, but once we've gone through
172+
// serialization/deserialization, might point to an unexpected location. (See the mixin
173+
// converter tests, I suspect this might actually be an indication of something else slightly
174+
// broken there, but don't want to spend more time with this right now.)
175+
for (const child of reflection.children || []) {
176+
if (child.inheritedFrom && !child.inheritedFrom.reflection) {
177+
child.inheritedFrom = ReferenceType.createBrokenReference(child.inheritedFrom.name, project);
178+
}
179+
if (child.overwrites && !child.overwrites.reflection) {
180+
child.overwrites = ReferenceType.createBrokenReference(child.overwrites.name, project);
181+
}
182+
183+
for (const childSig of child.getAllSignatures()) {
184+
if (childSig.inheritedFrom && !childSig.inheritedFrom.reflection) {
185+
childSig.inheritedFrom = ReferenceType.createBrokenReference(childSig.inheritedFrom.name, project);
186+
}
187+
if (childSig.overwrites && !childSig.overwrites.reflection) {
188+
childSig.overwrites = ReferenceType.createBrokenReference(childSig.overwrites.name, project);
189+
}
190+
}
191+
}
159192
}
160193

161194
private onResolveEnd(context: Context) {
@@ -522,9 +555,21 @@ function createLink(
522555
symbol: ts.Symbol,
523556
isInherit: boolean,
524557
) {
525-
const project = context.project;
526558
const name = `${expr.expression.getText()}.${getHumanName(symbol.name)}`;
527559

560+
// We should always have rootSymbols, but check just in case. We use the first
561+
// symbol here as TypeDoc's models don't have multiple symbols for the parent
562+
// reference. This is technically wrong because symbols might be declared in
563+
// multiple locations (interface declaration merging), but that's an uncommon
564+
// enough use case that it doesn't seem worthwhile to complicate the rest of the
565+
// world to deal with it.
566+
// Note that we also need to check that the root symbol isn't this symbol.
567+
// This seems to happen sometimes when dealing with interface inheritance.
568+
const rootSymbols = context.checker.getRootSymbols(symbol);
569+
const ref = rootSymbols.length && rootSymbols[0] != symbol
570+
? context.createSymbolReference(rootSymbols[0], context, name)
571+
: ReferenceType.createBrokenReference(name, context.project);
572+
528573
link(reflection);
529574
link(reflection.getSignature);
530575
link(reflection.setSignature);
@@ -535,34 +580,21 @@ function createLink(
535580
link(sig);
536581
}
537582

538-
// Intentionally create broken links here. These will be replaced with real links during
539-
// resolution if we can do so. We create broken links rather than real links because in the
540-
// case of an inherited symbol, we'll end up referencing a single symbol ID rather than one
541-
// for each class.
542583
function link(
543584
target: DeclarationReflection | SignatureReflection | undefined,
544585
) {
545586
if (!target) return;
546587

547588
if (clause.token === ts.SyntaxKind.ImplementsKeyword) {
548-
target.implementationOf ??= ReferenceType.createBrokenReference(
549-
name,
550-
project,
551-
);
589+
target.implementationOf ??= ref;
552590
return;
553591
}
554592

555593
if (isInherit) {
556594
target.setFlag(ReflectionFlag.Inherited);
557-
target.inheritedFrom ??= ReferenceType.createBrokenReference(
558-
name,
559-
project,
560-
);
595+
target.inheritedFrom ??= ref;
561596
} else {
562-
target.overwrites ??= ReferenceType.createBrokenReference(
563-
name,
564-
project,
565-
);
597+
target.overwrites ??= ref;
566598
}
567599
}
568600
}

src/lib/serialization/schema.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ type _ModelToObject<T> =
5858
T extends M.CommentDisplayPart ? CommentDisplayPart :
5959
T extends M.SourceReference ? SourceReference :
6060
T extends M.FileRegistry ? FileRegistry :
61+
T extends M.ReflectionSymbolId ? ReflectionSymbolId :
6162
never;
6263

6364
type Primitive = string | number | undefined | null | boolean;

src/test/converter/interface/specs.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2546,8 +2546,8 @@
25462546
},
25472547
"inheritedFrom": {
25482548
"type": "reference",
2549-
"target": 106,
2550-
"name": "Base.base"
2549+
"target": 112,
2550+
"name": "Child.base"
25512551
}
25522552
},
25532553
{
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
export interface Parent {
2+
prop: string;
3+
}
4+
5+
export interface Child extends Partial<Parent> {}
6+
7+
export type Tricky<T> = Omit<T, "x"> & { x: number };
8+
9+
export interface HasX {
10+
x: string;
11+
}
12+
13+
export interface InheritsX extends Tricky<HasX> {
14+
// InheritsX.x should *not* be linked to HasX.x
15+
}

src/test/issues.c2.test.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2135,4 +2135,13 @@ describe("Issue Tests", () => {
21352135
["Var", "Comment"],
21362136
]);
21372137
});
2138+
2139+
it("#2978 handles parent properties through mapped types", () => {
2140+
const project = convert();
2141+
const prop = query(project, "Child.prop");
2142+
equal(prop.inheritedFrom?.reflection?.getFullName(), "Parent.prop");
2143+
const x = query(project, "InheritsX.x");
2144+
equal(x.inheritedFrom?.reflection?.getFullName(), undefined);
2145+
equal(x.inheritedFrom?.name, "Tricky.x");
2146+
});
21382147
});

0 commit comments

Comments
 (0)