Skip to content

Commit

Permalink
Correctly handle @link in documents
Browse files Browse the repository at this point in the history
Resolves #2629
  • Loading branch information
Gerrit0 committed Jul 10, 2024
1 parent d04ea32 commit 1f58143
Show file tree
Hide file tree
Showing 8 changed files with 118 additions and 74 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
- The page navigation sidebar no longer incorrectly includes re-exports if the same member is exported with multiple names #2625.
- Page navigation now ensures the current page is visible when the page is first loaded, #2626.
- If a relative linked image is referenced multiple times, TypeDoc will no longer sometimes produce invalid links to the image #2627.
- `@link` tags will now be validated in referenced markdown documents, #2629.
- `@link` tags are now resolved in project documents, #2629.
- Comments on re-exports are now rendered.

### Thanks!
Expand Down
12 changes: 4 additions & 8 deletions internal-docs/plugins.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@
title: Plugins
children:
- ./components-and-events.md
- ./third-party-symbols.md
---

# Writing a TypeDoc Plugin

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

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

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

[Application]: https://typedoc.org/api/classes/Application.html
[Converter]: https://typedoc.org/api/classes/Converter.html
[Renderer]: https://typedoc.org/api/classes/Renderer.html
[Serializer]: https://typedoc.org/api/classes/Serializer.html
[Deserializer]: https://typedoc.org/api/classes/Deserializer.html
[typedoc-plugin-mdn-links]: https://github.com/Gerrit0/typedoc-plugin-mdn-links/blob/main/src/index.ts
[TypeScript Discord]: https://discord.gg/typescript
17 changes: 10 additions & 7 deletions internal-docs/third-party-symbols.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
---
title: Third Party Symbols
---

# Third Party Symbols

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

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

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

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

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

[externalSymbolLinkMappings]: https://typedoc.org/options/comments/#externalsymbollinkmappings
[CommentDisplayPart]: https://typedoc.org/api/types/CommentDisplayPart.html
[DeclarationReference]: https://typedoc.org/api/interfaces/DeclarationReference.html
[ReflectionSymbolId]: https://typedoc.org/api/classes/Application.html
[addUnknownSymbolResolver]: https://typedoc.org/api/classes/Converter.html#addUnknownSymbolResolver
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ export type {
RendererHooks,
NavigationElement,
RendererEvents,
PageHeading,
} from "./lib/output";

export {
Expand Down
17 changes: 10 additions & 7 deletions src/lib/converter/plugins/LinkResolverPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { ConverterEvents } from "../converter-events";
import { Option, type ValidationOptions } from "../../utils";
import {
ContainerReflection,
DeclarationReflection,
makeRecursiveVisitor,
type ProjectReflection,
type Reflection,
Expand Down Expand Up @@ -57,14 +56,18 @@ export class LinkResolverPlugin extends ConverterComponent {
},
}),
);

if (reflection.readme) {
reflection.readme = this.owner.resolveLinks(
reflection.readme,
reflection,
);
}
}

if (
reflection instanceof DeclarationReflection &&
reflection.readme
) {
reflection.readme = this.owner.resolveLinks(
reflection.readme,
if (reflection.isDocument()) {
reflection.content = this.owner.resolveLinks(
reflection.content,
reflection,
);
}
Expand Down
2 changes: 2 additions & 0 deletions src/lib/internationalization/translatable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,8 @@ export const translatable = {
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}"`,
failed_to_resolve_link_to_0_in_readme_for_1: `Failed to resolve link to "{0}" in readme for {1}`,
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}"`,
failed_to_resolve_link_to_0_in_document_1: `Failed to resolve link to "{0}" in document {1}`,
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}"`,
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`,
reflection_0_kind_1_defined_in_2_does_not_have_any_documentation: `{0} ({1}), defined in {2}, does not have any documentation`,
invalid_intentionally_not_exported_symbols_0:
Expand Down
8 changes: 7 additions & 1 deletion src/lib/output/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
export { PageEvent, RendererEvent, MarkdownEvent, IndexEvent } from "./events";
export {
PageEvent,
RendererEvent,
MarkdownEvent,
IndexEvent,
type PageHeading,
} from "./events";
export { UrlMapping } from "./models/UrlMapping";
export type { RenderTemplate } from "./models/UrlMapping";
export { Renderer, type RendererEvents } from "./renderer";
Expand Down
133 changes: 82 additions & 51 deletions src/lib/validation/links.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {
type Reflection,
ReflectionKind,
type Comment,
type CommentDisplayPart,
Expand All @@ -17,7 +18,7 @@ function getBrokenPartLinks(parts: readonly CommentDisplayPart[]) {
linkTags.includes(part.tag) &&
!part.target
) {
links.push(part.text);
links.push(part.text.trim());
}
}

Expand All @@ -40,79 +41,109 @@ export function validateLinks(
logger: Logger,
): void {
for (const id in project.reflections) {
const reflection = project.reflections[id];
checkReflection(project.reflections[id], logger);
}

if (!(project.id in project.reflections)) {
checkReflection(project, logger);
}
}

if (reflection.isProject() || reflection.isDeclaration()) {
for (const broken of getBrokenPartLinks(reflection.readme || [])) {
// #2360, "@" is a future reserved character in TSDoc component paths
// If a link starts with it, and doesn't include a module source indicator "!"
// then the user probably is trying to link to a package containing "@" with an absolute link.
if (broken.startsWith("@") && !broken.includes("!")) {
logger.warn(
logger.i18n.failed_to_resolve_link_to_0_in_readme_for_1_may_have_meant_2(
broken,
reflection.getFriendlyFullName(),
broken.replace(/[.#~]/, "!"),
),
);
} else {
logger.warn(
logger.i18n.failed_to_resolve_link_to_0_in_readme_for_1(
broken,
reflection.getFriendlyFullName(),
),
);
}
function checkReflection(reflection: Reflection, logger: Logger) {
if (reflection.isProject() || reflection.isDeclaration()) {
for (const broken of getBrokenPartLinks(reflection.readme || [])) {
// #2360, "@" is a future reserved character in TSDoc component paths
// If a link starts with it, and doesn't include a module source indicator "!"
// then the user probably is trying to link to a package containing "@" with an absolute link.
if (broken.startsWith("@") && !broken.includes("!")) {
logger.warn(
logger.i18n.failed_to_resolve_link_to_0_in_readme_for_1_may_have_meant_2(
broken,
reflection.getFriendlyFullName(),
broken.replace(/[.#~]/, "!"),
),
);
} else {
logger.warn(
logger.i18n.failed_to_resolve_link_to_0_in_readme_for_1(
broken,
reflection.getFriendlyFullName(),
),
);
}
}
}

for (const broken of getBrokenLinks(reflection.comment)) {
if (reflection.isDocument()) {
for (const broken of getBrokenPartLinks(reflection.content)) {
// #2360, "@" is a future reserved character in TSDoc component paths
// If a link starts with it, and doesn't include a module source indicator "!"
// then the user probably is trying to link to a package containing "@" with an absolute link.
if (broken.startsWith("@") && !broken.includes("!")) {
logger.warn(
logger.i18n.failed_to_resolve_link_to_0_in_comment_for_1_may_have_meant_2(
logger.i18n.failed_to_resolve_link_to_0_in_document_1_may_have_meant_2(
broken,
reflection.getFriendlyFullName(),
broken.replace(/[.#~]/, "!"),
),
);
} else {
logger.warn(
logger.i18n.failed_to_resolve_link_to_0_in_comment_for_1(
logger.i18n.failed_to_resolve_link_to_0_in_document_1(
broken,
reflection.getFriendlyFullName(),
),
);
}
}
}

if (
reflection.isDeclaration() &&
reflection.kindOf(ReflectionKind.TypeAlias) &&
reflection.type?.type === "union" &&
reflection.type.elementSummaries
) {
for (const broken of reflection.type.elementSummaries.flatMap(
getBrokenPartLinks,
)) {
if (broken.startsWith("@") && !broken.includes("!")) {
logger.warn(
logger.i18n.failed_to_resolve_link_to_0_in_comment_for_1_may_have_meant_2(
broken,
reflection.getFriendlyFullName(),
broken.replace(/[.#~]/, "!"),
),
);
} else {
logger.warn(
logger.i18n.failed_to_resolve_link_to_0_in_comment_for_1(
broken,
reflection.getFriendlyFullName(),
),
);
}
for (const broken of getBrokenLinks(reflection.comment)) {
// #2360, "@" is a future reserved character in TSDoc component paths
// If a link starts with it, and doesn't include a module source indicator "!"
// then the user probably is trying to link to a package containing "@" with an absolute link.
if (broken.startsWith("@") && !broken.includes("!")) {
logger.warn(
logger.i18n.failed_to_resolve_link_to_0_in_comment_for_1_may_have_meant_2(
broken,
reflection.getFriendlyFullName(),
broken.replace(/[.#~]/, "!"),
),
);
} else {
logger.warn(
logger.i18n.failed_to_resolve_link_to_0_in_comment_for_1(
broken,
reflection.getFriendlyFullName(),
),
);
}
}

if (
reflection.isDeclaration() &&
reflection.kindOf(ReflectionKind.TypeAlias) &&
reflection.type?.type === "union" &&
reflection.type.elementSummaries
) {
for (const broken of reflection.type.elementSummaries.flatMap(
getBrokenPartLinks,
)) {
if (broken.startsWith("@") && !broken.includes("!")) {
logger.warn(
logger.i18n.failed_to_resolve_link_to_0_in_comment_for_1_may_have_meant_2(
broken,
reflection.getFriendlyFullName(),
broken.replace(/[.#~]/, "!"),
),
);
} else {
logger.warn(
logger.i18n.failed_to_resolve_link_to_0_in_comment_for_1(
broken,
reflection.getFriendlyFullName(),
),
);
}
}
}
Expand Down

0 comments on commit 1f58143

Please sign in to comment.