Skip to content

Commit 1f58143

Browse files
committed
Correctly handle @link in documents
Resolves #2629
1 parent d04ea32 commit 1f58143

File tree

8 files changed

+118
-74
lines changed

8 files changed

+118
-74
lines changed

Diff for: CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
- The page navigation sidebar no longer incorrectly includes re-exports if the same member is exported with multiple names #2625.
66
- Page navigation now ensures the current page is visible when the page is first loaded, #2626.
77
- If a relative linked image is referenced multiple times, TypeDoc will no longer sometimes produce invalid links to the image #2627.
8+
- `@link` tags will now be validated in referenced markdown documents, #2629.
9+
- `@link` tags are now resolved in project documents, #2629.
810
- Comments on re-exports are now rendered.
911

1012
### Thanks!

Diff for: internal-docs/plugins.md

+4-8
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,14 @@
22
title: Plugins
33
children:
44
- ./components-and-events.md
5+
- ./third-party-symbols.md
56
---
67

78
# Writing a TypeDoc Plugin
89

910
TypeDoc supports plugins which can modify how projects are converted, how converted symbols
1011
are organized, and how they are displayed, among other things. Plugins are Node modules which
11-
export a single `load` function that will be called by TypeDoc with the [Application] instance
12+
export a single `load` function that will be called by TypeDoc with the {@link Application} instance
1213
which they are to be attached to. Plugins should assume that they may be loaded multiple times
1314
for different applications, and that a single load of an application class may be used to convert
1415
multiple projects.
@@ -26,8 +27,8 @@ export function load(app) {
2627
```
2728

2829
Plugins affect TypeDoc's execution by attaching event listeners to one or many events that will be
29-
fired during conversion and rendering. Events are available on the [Application], [Converter],
30-
[Renderer], and [Serializer]/[Deserializer] classes. There are static `EVENT_*` properties on those
30+
fired during conversion and rendering. Events are available on the {@link Application}, {@link Converter},
31+
{@link Renderer}, and {@link Serializer}/{@link Deserializer} classes. There are static `EVENT_*` properties on those
3132
classes which describe the available events.
3233

3334
The best way to learn what's available to plugins is to browse the docs, or look at the source code
@@ -45,10 +46,5 @@ TypeDoc works. The [development page](https://typedoc.org/guides/development/) o
4546
If you have specific questions regarding plugin development, please open an issue or ask in the
4647
[TypeScript Discord] #typedoc channel.
4748

48-
[Application]: https://typedoc.org/api/classes/Application.html
49-
[Converter]: https://typedoc.org/api/classes/Converter.html
50-
[Renderer]: https://typedoc.org/api/classes/Renderer.html
51-
[Serializer]: https://typedoc.org/api/classes/Serializer.html
52-
[Deserializer]: https://typedoc.org/api/classes/Deserializer.html
5349
[typedoc-plugin-mdn-links]: https://github.com/Gerrit0/typedoc-plugin-mdn-links/blob/main/src/index.ts
5450
[TypeScript Discord]: https://discord.gg/typescript

Diff for: internal-docs/third-party-symbols.md

+10-7
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
---
2+
title: Third Party Symbols
3+
---
4+
15
# Third Party Symbols
26

37
TypeDoc 0.22 added support for linking to third party sites by associating a symbol name with npm packages.
@@ -47,7 +51,8 @@ A wildcard can be used to provide a fallback link to any unmapped type.
4751
}
4852
```
4953

50-
Plugins can add support for linking to third party sites by calling [`app.converter.addUnknownSymbolResolver`][addUnknownSymbolResolver].
54+
Plugins can add support for linking to third party sites by calling
55+
{@link Converter.addUnknownSymbolResolver | `app.converter.addUnknownSymbolResolver`}
5156

5257
If the given symbol is unknown, or does not appear in the documentation site, the resolver may return `undefined`
5358
and no link will be rendered unless provided by another resolver.
@@ -150,12 +155,10 @@ export function load(app: Application) {
150155
```
151156

152157
The unknown symbol resolver will also be passed the reflection containing the link
153-
and, if the link was defined by the user, the [CommentDisplayPart] which was parsed into the [DeclarationReference] provided as the first argument.
158+
and, if the link was defined by the user, the {@link Models.CommentDisplayPart} which was parsed into the
159+
{@link DeclarationReference} provided as the first argument.
154160

155-
If `--useTsLinkResolution` is on (the default), it may also be passed a [ReflectionSymbolId] referencing the symbol that TypeScript resolves the link to.
161+
If `--useTsLinkResolution` is on (the default), it may also be passed a {@link Models.ReflectionSymbolId}
162+
referencing the symbol that TypeScript resolves the link to.
156163

157164
[externalSymbolLinkMappings]: https://typedoc.org/options/comments/#externalsymbollinkmappings
158-
[CommentDisplayPart]: https://typedoc.org/api/types/CommentDisplayPart.html
159-
[DeclarationReference]: https://typedoc.org/api/interfaces/DeclarationReference.html
160-
[ReflectionSymbolId]: https://typedoc.org/api/classes/Application.html
161-
[addUnknownSymbolResolver]: https://typedoc.org/api/classes/Converter.html#addUnknownSymbolResolver

Diff for: src/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ export type {
4949
RendererHooks,
5050
NavigationElement,
5151
RendererEvents,
52+
PageHeading,
5253
} from "./lib/output";
5354

5455
export {

Diff for: src/lib/converter/plugins/LinkResolverPlugin.ts

+10-7
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import { ConverterEvents } from "../converter-events";
44
import { Option, type ValidationOptions } from "../../utils";
55
import {
66
ContainerReflection,
7-
DeclarationReflection,
87
makeRecursiveVisitor,
98
type ProjectReflection,
109
type Reflection,
@@ -57,14 +56,18 @@ export class LinkResolverPlugin extends ConverterComponent {
5756
},
5857
}),
5958
);
59+
60+
if (reflection.readme) {
61+
reflection.readme = this.owner.resolveLinks(
62+
reflection.readme,
63+
reflection,
64+
);
65+
}
6066
}
6167

62-
if (
63-
reflection instanceof DeclarationReflection &&
64-
reflection.readme
65-
) {
66-
reflection.readme = this.owner.resolveLinks(
67-
reflection.readme,
68+
if (reflection.isDocument()) {
69+
reflection.content = this.owner.resolveLinks(
70+
reflection.content,
6871
reflection,
6972
);
7073
}

Diff for: src/lib/internationalization/translatable.ts

+2
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,8 @@ export const translatable = {
103103
failed_to_resolve_link_to_0_in_comment_for_1_may_have_meant_2: `Failed to resolve link to "{0}" in comment for {1}. You may have wanted "{2}"`,
104104
failed_to_resolve_link_to_0_in_readme_for_1: `Failed to resolve link to "{0}" in readme for {1}`,
105105
failed_to_resolve_link_to_0_in_readme_for_1_may_have_meant_2: `Failed to resolve link to "{0}" in readme for {1}. You may have wanted "{2}"`,
106+
failed_to_resolve_link_to_0_in_document_1: `Failed to resolve link to "{0}" in document {1}`,
107+
failed_to_resolve_link_to_0_in_document_1_may_have_meant_2: `Failed to resolve link to "{0}" in document {1}. You may have wanted "{2}"`,
106108
type_0_defined_in_1_is_referenced_by_2_but_not_included_in_docs: `{0}, defined in {1}, is referenced by {2} but not included in the documentation`,
107109
reflection_0_kind_1_defined_in_2_does_not_have_any_documentation: `{0} ({1}), defined in {2}, does not have any documentation`,
108110
invalid_intentionally_not_exported_symbols_0:

Diff for: src/lib/output/index.ts

+7-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1-
export { PageEvent, RendererEvent, MarkdownEvent, IndexEvent } from "./events";
1+
export {
2+
PageEvent,
3+
RendererEvent,
4+
MarkdownEvent,
5+
IndexEvent,
6+
type PageHeading,
7+
} from "./events";
28
export { UrlMapping } from "./models/UrlMapping";
39
export type { RenderTemplate } from "./models/UrlMapping";
410
export { Renderer, type RendererEvents } from "./renderer";

Diff for: src/lib/validation/links.ts

+82-51
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import {
2+
type Reflection,
23
ReflectionKind,
34
type Comment,
45
type CommentDisplayPart,
@@ -17,7 +18,7 @@ function getBrokenPartLinks(parts: readonly CommentDisplayPart[]) {
1718
linkTags.includes(part.tag) &&
1819
!part.target
1920
) {
20-
links.push(part.text);
21+
links.push(part.text.trim());
2122
}
2223
}
2324

@@ -40,79 +41,109 @@ export function validateLinks(
4041
logger: Logger,
4142
): void {
4243
for (const id in project.reflections) {
43-
const reflection = project.reflections[id];
44+
checkReflection(project.reflections[id], logger);
45+
}
46+
47+
if (!(project.id in project.reflections)) {
48+
checkReflection(project, logger);
49+
}
50+
}
4451

45-
if (reflection.isProject() || reflection.isDeclaration()) {
46-
for (const broken of getBrokenPartLinks(reflection.readme || [])) {
47-
// #2360, "@" is a future reserved character in TSDoc component paths
48-
// If a link starts with it, and doesn't include a module source indicator "!"
49-
// then the user probably is trying to link to a package containing "@" with an absolute link.
50-
if (broken.startsWith("@") && !broken.includes("!")) {
51-
logger.warn(
52-
logger.i18n.failed_to_resolve_link_to_0_in_readme_for_1_may_have_meant_2(
53-
broken,
54-
reflection.getFriendlyFullName(),
55-
broken.replace(/[.#~]/, "!"),
56-
),
57-
);
58-
} else {
59-
logger.warn(
60-
logger.i18n.failed_to_resolve_link_to_0_in_readme_for_1(
61-
broken,
62-
reflection.getFriendlyFullName(),
63-
),
64-
);
65-
}
52+
function checkReflection(reflection: Reflection, logger: Logger) {
53+
if (reflection.isProject() || reflection.isDeclaration()) {
54+
for (const broken of getBrokenPartLinks(reflection.readme || [])) {
55+
// #2360, "@" is a future reserved character in TSDoc component paths
56+
// If a link starts with it, and doesn't include a module source indicator "!"
57+
// then the user probably is trying to link to a package containing "@" with an absolute link.
58+
if (broken.startsWith("@") && !broken.includes("!")) {
59+
logger.warn(
60+
logger.i18n.failed_to_resolve_link_to_0_in_readme_for_1_may_have_meant_2(
61+
broken,
62+
reflection.getFriendlyFullName(),
63+
broken.replace(/[.#~]/, "!"),
64+
),
65+
);
66+
} else {
67+
logger.warn(
68+
logger.i18n.failed_to_resolve_link_to_0_in_readme_for_1(
69+
broken,
70+
reflection.getFriendlyFullName(),
71+
),
72+
);
6673
}
6774
}
75+
}
6876

69-
for (const broken of getBrokenLinks(reflection.comment)) {
77+
if (reflection.isDocument()) {
78+
for (const broken of getBrokenPartLinks(reflection.content)) {
7079
// #2360, "@" is a future reserved character in TSDoc component paths
7180
// If a link starts with it, and doesn't include a module source indicator "!"
7281
// then the user probably is trying to link to a package containing "@" with an absolute link.
7382
if (broken.startsWith("@") && !broken.includes("!")) {
7483
logger.warn(
75-
logger.i18n.failed_to_resolve_link_to_0_in_comment_for_1_may_have_meant_2(
84+
logger.i18n.failed_to_resolve_link_to_0_in_document_1_may_have_meant_2(
7685
broken,
7786
reflection.getFriendlyFullName(),
7887
broken.replace(/[.#~]/, "!"),
7988
),
8089
);
8190
} else {
8291
logger.warn(
83-
logger.i18n.failed_to_resolve_link_to_0_in_comment_for_1(
92+
logger.i18n.failed_to_resolve_link_to_0_in_document_1(
8493
broken,
8594
reflection.getFriendlyFullName(),
8695
),
8796
);
8897
}
8998
}
99+
}
90100

91-
if (
92-
reflection.isDeclaration() &&
93-
reflection.kindOf(ReflectionKind.TypeAlias) &&
94-
reflection.type?.type === "union" &&
95-
reflection.type.elementSummaries
96-
) {
97-
for (const broken of reflection.type.elementSummaries.flatMap(
98-
getBrokenPartLinks,
99-
)) {
100-
if (broken.startsWith("@") && !broken.includes("!")) {
101-
logger.warn(
102-
logger.i18n.failed_to_resolve_link_to_0_in_comment_for_1_may_have_meant_2(
103-
broken,
104-
reflection.getFriendlyFullName(),
105-
broken.replace(/[.#~]/, "!"),
106-
),
107-
);
108-
} else {
109-
logger.warn(
110-
logger.i18n.failed_to_resolve_link_to_0_in_comment_for_1(
111-
broken,
112-
reflection.getFriendlyFullName(),
113-
),
114-
);
115-
}
101+
for (const broken of getBrokenLinks(reflection.comment)) {
102+
// #2360, "@" is a future reserved character in TSDoc component paths
103+
// If a link starts with it, and doesn't include a module source indicator "!"
104+
// then the user probably is trying to link to a package containing "@" with an absolute link.
105+
if (broken.startsWith("@") && !broken.includes("!")) {
106+
logger.warn(
107+
logger.i18n.failed_to_resolve_link_to_0_in_comment_for_1_may_have_meant_2(
108+
broken,
109+
reflection.getFriendlyFullName(),
110+
broken.replace(/[.#~]/, "!"),
111+
),
112+
);
113+
} else {
114+
logger.warn(
115+
logger.i18n.failed_to_resolve_link_to_0_in_comment_for_1(
116+
broken,
117+
reflection.getFriendlyFullName(),
118+
),
119+
);
120+
}
121+
}
122+
123+
if (
124+
reflection.isDeclaration() &&
125+
reflection.kindOf(ReflectionKind.TypeAlias) &&
126+
reflection.type?.type === "union" &&
127+
reflection.type.elementSummaries
128+
) {
129+
for (const broken of reflection.type.elementSummaries.flatMap(
130+
getBrokenPartLinks,
131+
)) {
132+
if (broken.startsWith("@") && !broken.includes("!")) {
133+
logger.warn(
134+
logger.i18n.failed_to_resolve_link_to_0_in_comment_for_1_may_have_meant_2(
135+
broken,
136+
reflection.getFriendlyFullName(),
137+
broken.replace(/[.#~]/, "!"),
138+
),
139+
);
140+
} else {
141+
logger.warn(
142+
logger.i18n.failed_to_resolve_link_to_0_in_comment_for_1(
143+
broken,
144+
reflection.getFriendlyFullName(),
145+
),
146+
);
116147
}
117148
}
118149
}

0 commit comments

Comments
 (0)