Skip to content

Commit 5d44275

Browse files
committed
Add tests for declaration reference resolution
1 parent 4dfefe8 commit 5d44275

File tree

8 files changed

+261
-62
lines changed

8 files changed

+261
-62
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
- The `gaSite` option has been removed since Google Analytics now infers the site automatically, updated Google Analytics script to latest version, #1846.
1616
- Comments on export declarations will only overrides comments for references and namespaces, #1901.
1717
- The deprecated `listInvalidSymbolLinks` option has been removed. Use `validation.invalidLink` instead.
18-
- The deprecated `true` and `false` values have been removed from `--emit`, to migrate replace `true` with `"both"` and `false` with `"docs"`.
18+
- The deprecated `true` and `false` values have been removed from `--emit`, to migrate replace `true` with `"both"` and `false` with `"docs"` (the default).
1919
- Links are no longer be resolved against a global list of all symbols. See [the documentation](https://typedoc.org/guides/link-resolution/) for details on link resolution.
2020
- The `validation.invalidLink` option is now on by default.
2121
- `reflection.decorates`, `reflection.decorators`, and their corresponding interfaces have been removed as no code in TypeDoc used them.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@
9494
".tsx"
9595
],
9696
"reporter": [
97-
"html",
97+
"html-spa",
9898
"text-summary"
9999
],
100100
"exclude": [

src/lib/converter/comments/declarationReference.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
* @module
77
*/
88

9+
import type { Chars } from "../../utils";
10+
911
export const MeaningKeywords = [
1012
"class", // SymbolFlags.Class
1113
"interface", // SymbolFlags.Interface
@@ -51,10 +53,6 @@ export interface ComponentPath {
5153
path: string;
5254
}
5355

54-
type Chars<T extends string> = T extends `${infer C}${infer R}`
55-
? C | Chars<R>
56-
: never;
57-
5856
// <TAB> <VT> <FF> <SP> <NBSP> <ZWNBSP> <USP>
5957
const WhiteSpace = /[\t\u2B7F\u240C \u00A0\uFEFF\p{White_Space}]/u;
6058
const LineTerminator = "\r\n\u2028\u2029";

src/lib/converter/plugins/LinkResolverPlugin.ts

Lines changed: 33 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,14 @@ export class LinkResolverPlugin extends ConverterComponent {
6565
const { target, caption } =
6666
LinkResolverPlugin.splitLinkText(content);
6767

68-
if (!urlPrefix.test(target)) {
68+
if (urlPrefix.test(target)) {
69+
parts.push({
70+
kind: "inline-tag",
71+
tag: "@link",
72+
text: caption,
73+
target,
74+
});
75+
} else {
6976
const targetRefl = reflection.findReflectionByName(target);
7077
if (targetRefl) {
7178
parts.push({
@@ -75,22 +82,17 @@ export class LinkResolverPlugin extends ConverterComponent {
7582
target: targetRefl,
7683
});
7784
} else {
78-
this.application.logger.warn(
79-
"Failed to find target: " + content
80-
);
85+
if (this.validation.invalidLink) {
86+
this.application.logger.warn(
87+
"Failed to find target: " + content
88+
);
89+
}
8190
parts.push({
8291
kind: "inline-tag",
8392
tag: "@link",
8493
text: content,
8594
});
8695
}
87-
} else {
88-
parts.push({
89-
kind: "inline-tag",
90-
tag: "@link",
91-
text: caption,
92-
target,
93-
});
9496
}
9597
}
9698
parts.push({
@@ -219,11 +221,6 @@ function resolveLinkTag(
219221
let pos = 0;
220222
const end = part.text.length;
221223

222-
// Skip any leading white space, which isn't allowed in a declaration reference.
223-
while (pos < end && ts.isWhiteSpaceLike(part.text.charCodeAt(pos))) {
224-
pos++;
225-
}
226-
227224
// Try to parse one
228225
const declRef = parseDeclarationReference(part.text, pos, end);
229226

@@ -237,12 +234,15 @@ function resolveLinkTag(
237234
// If resolution via a declaration reference failed, revert to the legacy "split and check"
238235
// method... this should go away in 0.24, once people have had a chance to migrate any failing links.
239236
if (!target) {
240-
warn(
241-
`Failed to resolve {@link ${
242-
part.text
243-
}} in ${reflection.getFriendlyFullName()} with declaration references. This link will break in v0.24.`
244-
);
245-
return legacyResolveLinkTag(reflection, part);
237+
const resolved = legacyResolveLinkTag(reflection, part);
238+
if (resolved) {
239+
warn(
240+
`Failed to resolve {@link ${
241+
part.text
242+
}} in ${reflection.getFriendlyFullName()} with declaration references. This link will break in v0.24.`
243+
);
244+
}
245+
return resolved;
246246
}
247247

248248
// Remaining text after an optional pipe is the link text, so advance
@@ -369,7 +369,9 @@ function resolveKeyword(
369369
): Reflection[] | undefined {
370370
switch (kw) {
371371
case undefined:
372-
return [refl];
372+
return refl instanceof DeclarationReflection && refl.signatures
373+
? refl.signatures
374+
: [refl];
373375
case "class":
374376
if (refl.kindOf(ReflectionKind.Class)) return [refl];
375377
break;
@@ -396,22 +398,25 @@ function resolveKeyword(
396398

397399
case "new":
398400
case "constructor":
399-
if (refl.kindOf(ReflectionKind.Class)) {
401+
if (
402+
refl.kindOf(
403+
ReflectionKind.ClassOrInterface | ReflectionKind.TypeLiteral
404+
)
405+
) {
400406
const ctor = (refl as ContainerReflection).children?.find((c) =>
401407
c.kindOf(ReflectionKind.Constructor)
402408
);
403409
return (ctor as DeclarationReflection)?.signatures;
404410
}
405-
if (refl.kindOf(ReflectionKind.Constructor)) {
406-
return (refl as DeclarationReflection).signatures;
407-
}
408411
break;
409412

410413
case "member":
411414
if (refl.kindOf(ReflectionKind.SomeMember)) return [refl];
412415
break;
413416
case "event":
414-
if (refl.comment?.hasModifier("@event")) return [refl];
417+
// Never resolve. Nobody should use this.
418+
// It's required by the grammar, but is not documented by TypeDoc
419+
// nor by the comments in the grammar.
415420
break;
416421
case "call":
417422
return (refl as DeclarationReflection).signatures;

src/lib/utils/general.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,13 @@ export type IfInternal<T, F> = InternalOnly extends true ? T : F;
3535
*/
3636
export type NeverIfInternal<T> = IfInternal<never, T>;
3737

38+
/**
39+
* Resolves a string type into a union of characters, `"ab"` turns into `"a" | "b"`.
40+
*/
41+
export type Chars<T extends string> = T extends `${infer C}${infer R}`
42+
? C | Chars<R>
43+
: never;
44+
3845
/**
3946
* Utility to help type checking ensure that there is no uncovered case.
4047
*/

src/lib/utils/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export {
1717
writeFile,
1818
writeFileSync,
1919
} from "./fs";
20-
export type { IfInternal, NeverIfInternal } from "./general";
20+
export type { IfInternal, NeverIfInternal, Chars } from "./general";
2121
export { assertNever } from "./general";
2222
export { CallbackLogger, ConsoleLogger, Logger, LogLevel } from "./loggers";
2323
export { DefaultMap } from "./map";

src/test/behaviorTests.ts

Lines changed: 93 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,10 @@ import {
88
Comment,
99
CommentDisplayPart,
1010
CommentTag,
11+
Reflection,
12+
SignatureReflection,
1113
} from "../lib/models";
14+
import { Chars, filterMap } from "../lib/utils";
1215
import { CommentStyle } from "../lib/utils/options/declaration";
1316
import type { TestLogger } from "./TestLogger";
1417

@@ -18,33 +21,7 @@ function query(project: ProjectReflection, name: string) {
1821
return reflection;
1922
}
2023

21-
type Letters =
22-
| "a"
23-
| "b"
24-
| "c"
25-
| "d"
26-
| "e"
27-
| "f"
28-
| "g"
29-
| "h"
30-
| "i"
31-
| "j"
32-
| "k"
33-
| "l"
34-
| "m"
35-
| "n"
36-
| "o"
37-
| "p"
38-
| "q"
39-
| "r"
40-
| "s"
41-
| "t"
42-
| "u"
43-
| "v"
44-
| "w"
45-
| "x"
46-
| "y"
47-
| "z";
24+
type Letters = Chars<"abcdefghijklmnopqrstuvwxyz">;
4825

4926
export const behaviorTests: {
5027
[issue: `_${string}`]: (app: Application) => void;
@@ -364,6 +341,95 @@ export const behaviorTests: {
364341
equal(Comment.combineDisplayParts(c.comment?.summary), "");
365342
},
366343

344+
_linkResolution(app) {
345+
app.options.setValue("sort", ["source-order"]);
346+
},
347+
linkResolution(project) {
348+
function getLinks(refl: Reflection) {
349+
ok(refl.comment);
350+
return filterMap(refl.comment.summary, (p) => {
351+
if (p.kind === "inline-tag" && p.tag === "@link") {
352+
if (typeof p.target === "string") {
353+
return p.target;
354+
}
355+
if (p.target instanceof SignatureReflection) {
356+
return [
357+
p.target.getFullName(),
358+
p.target.parent.signatures?.indexOf(p.target),
359+
];
360+
}
361+
return [p.target?.kind, p.target?.getFullName()];
362+
}
363+
});
364+
}
365+
366+
for (const [refl, target] of [
367+
["Scoping.abc", "Scoping.abc"],
368+
["Scoping.Foo", "Scoping.Foo.abc"],
369+
["Scoping.Foo.abc", "Scoping.Foo.abc"],
370+
["Scoping.Bar", "Scoping.abc"],
371+
["Scoping.Bar.abc", "Scoping.abc"],
372+
] as const) {
373+
equal(
374+
getLinks(query(project, refl)).map((x) => x[1]),
375+
[query(project, target).getFullName()]
376+
);
377+
}
378+
379+
const links = getLinks(query(project, "Meanings"));
380+
equal(links, [
381+
[ReflectionKind.Enum, "Meanings.A"],
382+
[ReflectionKind.Namespace, "Meanings.A"],
383+
[ReflectionKind.Enum, "Meanings.A"],
384+
385+
[undefined, undefined],
386+
[ReflectionKind.Class, "Meanings.B"],
387+
388+
[ReflectionKind.Interface, "Meanings.C"],
389+
[ReflectionKind.TypeAlias, "Meanings.D"],
390+
["Meanings.E.E", 0],
391+
[ReflectionKind.Variable, "Meanings.F"],
392+
393+
["Meanings.B.constructor.new B", 0],
394+
["Meanings.B.constructor.new B", 0],
395+
["Meanings.B.constructor.new B", 1],
396+
397+
[ReflectionKind.EnumMember, "Meanings.A.A"],
398+
[undefined, undefined],
399+
400+
["Meanings.E.E", 0],
401+
["Meanings.E.E", 1],
402+
403+
["Meanings.B.constructor.new B", 0],
404+
["Meanings.B.constructor.new B", 1],
405+
406+
["Meanings.B.__index", undefined],
407+
[ReflectionKind.Interface, "Meanings.G"],
408+
409+
["Meanings.E.E", 1],
410+
[ReflectionKind.Class, "Meanings.B"],
411+
]);
412+
413+
equal(getLinks(query(project, "URLS")), [
414+
"https://example.com",
415+
"ftp://example.com",
416+
]);
417+
418+
equal(
419+
getLinks(query(project, "Globals.A")).map((x) => x[1]),
420+
["URLS", "A", "Globals.A"]
421+
);
422+
423+
equal(getLinks(query(project, "Navigation")), [
424+
[ReflectionKind.Method, "Navigation.Child.foo"],
425+
[ReflectionKind.Property, "Navigation.Child.foo"],
426+
[undefined, undefined],
427+
]);
428+
429+
const foo = query(project, "Navigation.Child.foo").signatures![0];
430+
equal(getLinks(foo), [[ReflectionKind.Method, "Navigation.Child.foo"]]);
431+
},
432+
367433
mergedDeclarations(project, logger) {
368434
const a = query(project, "SingleCommentMultiDeclaration");
369435
equal(

0 commit comments

Comments
 (0)