Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

More control delegated for external link resolvers #2066

Merged
merged 13 commits into from Feb 24, 2023
1 change: 1 addition & 0 deletions src/index.ts
Expand Up @@ -13,6 +13,7 @@ export {
type ComponentPath,
type Meaning,
type MeaningKeyword,
type ExternalResolveResult,
} from "./lib/converter";

export {
Expand Down
44 changes: 32 additions & 12 deletions src/lib/converter/comments/linkResolver.ts
Expand Up @@ -16,12 +16,19 @@ import { resolveDeclarationReference } from "./declarationReferenceResolver";
const urlPrefix = /^(http|ftp)s?:\/\//;
const brackets = /\[\[(?!include:)([^\]]+)\]\]/g;

export type ExternalResolveResult = { target: string; caption?: string };
export type ExternalSymbolResolver = (
ref: DeclarationReference,
refl: Reflection,
part: Readonly<CommentDisplayPart> | undefined
) => ExternalResolveResult | string | undefined;

export function resolveLinks(
comment: Comment,
reflection: Reflection,
validation: ValidationOptions,
logger: Logger,
attemptExternalResolve: (ref: DeclarationReference) => string | undefined
externalResolver: ExternalSymbolResolver
) {
let warned = false;
const warn = () => {
Expand All @@ -39,7 +46,7 @@ export function resolveLinks(
warn,
validation,
logger,
attemptExternalResolve
externalResolver
);
for (const tag of comment.blockTags) {
tag.content = resolvePartLinks(
Expand All @@ -48,7 +55,7 @@ export function resolveLinks(
warn,
validation,
logger,
attemptExternalResolve
externalResolver
);
}

Expand All @@ -59,7 +66,7 @@ export function resolveLinks(
warn,
validation,
logger,
attemptExternalResolve
externalResolver
);
}
}
Expand All @@ -70,7 +77,7 @@ export function resolvePartLinks(
warn: () => void,
validation: ValidationOptions,
logger: Logger,
attemptExternalResolve: (ref: DeclarationReference) => string | undefined
externalResolver: ExternalSymbolResolver
): CommentDisplayPart[] {
return parts.flatMap((part) =>
processPart(
Expand All @@ -79,7 +86,7 @@ export function resolvePartLinks(
warn,
validation,
logger,
attemptExternalResolve
externalResolver
)
);
}
Expand All @@ -90,7 +97,7 @@ function processPart(
warn: () => void,
validation: ValidationOptions,
logger: Logger,
attemptExternalResolve: (ref: DeclarationReference) => string | undefined
externalResolver: ExternalSymbolResolver
): CommentDisplayPart | CommentDisplayPart[] {
if (part.kind === "text" && brackets.test(part.text)) {
warn();
Expand All @@ -106,7 +113,7 @@ function processPart(
return resolveLinkTag(
reflection,
part,
attemptExternalResolve,
externalResolver,
(msg: string) => {
if (validation.invalidLink) {
logger.warn(msg);
Expand All @@ -122,7 +129,7 @@ function processPart(
function resolveLinkTag(
reflection: Reflection,
part: InlineTagDisplayPart,
attemptExternalResolve: (ref: DeclarationReference) => string | undefined,
externalResolver: ExternalSymbolResolver,
warn: (message: string) => void
) {
let pos = 0;
Expand All @@ -146,9 +153,22 @@ function resolveLinkTag(
defaultDisplayText = target.name;
} else {
// If we didn't find a link, it might be a @link tag to an external symbol, check that next.
target = attemptExternalResolve(declRef[0]);
if (target) {
defaultDisplayText = part.text.substring(0, pos);
const externalResolveResult = externalResolver(
declRef[0],
reflection,
part
);

defaultDisplayText = part.text.substring(0, pos);

switch (typeof externalResolveResult) {
case "string":
target = externalResolveResult;
break;
case "object":
target = externalResolveResult.target;
defaultDisplayText =
externalResolveResult.caption || defaultDisplayText;
}
}
}
Expand Down
30 changes: 18 additions & 12 deletions src/lib/converter/converter.ts
Expand Up @@ -27,7 +27,12 @@ import type {
} from "../utils/options/declaration";
import { parseComment } from "./comments/parser";
import { lexCommentString } from "./comments/rawLexer";
import { resolvePartLinks, resolveLinks } from "./comments/linkResolver";
import {
resolvePartLinks,
resolveLinks,
ExternalSymbolResolver,
ExternalResolveResult,
} from "./comments/linkResolver";
import type { DeclarationReference } from "./comments/declarationReference";

/**
Expand Down Expand Up @@ -77,9 +82,7 @@ export class Converter extends ChildableComponent<
externalSymbolLinkMappings!: Record<string, Record<string, string>>;

private _config?: CommentParserConfig;
private _externalSymbolResolvers: Array<
(ref: DeclarationReference) => string | undefined
> = [];
private _externalSymbolResolvers: Array<ExternalSymbolResolver> = [];

get config(): CommentParserConfig {
return this._config || this._buildCommentParserConfig();
Expand Down Expand Up @@ -268,19 +271,22 @@ export class Converter extends ChildableComponent<
*
* Note: This will be used for both references to types declared in node_modules (in which case the
* reference passed will have the `moduleSource` set and the `symbolReference` will navigate via `.`)
* and user defined \{\@link\} tags which cannot be resolved.
* and user defined \{\@link\} tags which cannot be resolved. If the link being resolved is inferred
* from a type, then no `part` will be passed to the resolver function.
* @since 0.22.14
*/
addUnknownSymbolResolver(
resolver: (ref: DeclarationReference) => string | undefined
): void {
addUnknownSymbolResolver(resolver: ExternalSymbolResolver): void {
this._externalSymbolResolvers.push(resolver);
}

/** @internal */
resolveExternalLink(ref: DeclarationReference): string | undefined {
resolveExternalLink(
Gerrit0 marked this conversation as resolved.
Show resolved Hide resolved
ref: DeclarationReference,
refl: Reflection,
part?: CommentDisplayPart
): ExternalResolveResult | string | undefined {
for (const resolver of this._externalSymbolResolvers) {
const resolved = resolver(ref);
const resolved = resolver(ref, refl, part);
if (resolved) return resolved;
}
}
Expand All @@ -300,7 +306,7 @@ export class Converter extends ChildableComponent<
owner,
this.validation,
this.owner.logger,
(ref) => this.resolveExternalLink(ref)
(ref, part, refl) => this.resolveExternalLink(ref, part, refl)
);
} else {
let warned = false;
Expand All @@ -319,7 +325,7 @@ export class Converter extends ChildableComponent<
warn,
this.validation,
this.owner.logger,
(ref) => this.resolveExternalLink(ref)
(ref, part, refl) => this.resolveExternalLink(ref, part, refl)
);
}
}
Expand Down
4 changes: 4 additions & 0 deletions src/lib/converter/index.ts
Expand Up @@ -9,5 +9,9 @@ export type {
Meaning,
MeaningKeyword,
} from "./comments/declarationReference";
export type {
ExternalSymbolResolver,
ExternalResolveResult,
} from "./comments/linkResolver";

import "./plugins/index";
19 changes: 15 additions & 4 deletions src/lib/converter/plugins/LinkResolverPlugin.ts
@@ -1,5 +1,5 @@
import { Component, ConverterComponent } from "../components";
import type { Context } from "../../converter";
import type { Context, ExternalResolveResult } from "../../converter";
import { ConverterEvents } from "../converter-events";
import { BindOption, ValidationOptions } from "../../utils";
import { DeclarationReflection } from "../../models";
Expand Down Expand Up @@ -45,14 +45,25 @@ export class LinkResolverPlugin extends ConverterComponent {
);
}

for (const { type } of discoverAllReferenceTypes(
for (const { type, owner } of discoverAllReferenceTypes(
context.project,
false
)) {
if (!type.reflection) {
type.externalUrl = context.converter.resolveExternalLink(
type.toDeclarationReference()
const resolveResult = context.converter.resolveExternalLink(
type.toDeclarationReference(),
owner
);
switch (typeof resolveResult) {
case "string":
type.externalUrl = resolveResult as string;
break;
case "object":
type.externalUrl = (
resolveResult as ExternalResolveResult
).target;
break;
}
}
}
}
Expand Down