Skip to content

Commit

Permalink
More control delegated for external link resolvers (#2066)
Browse files Browse the repository at this point in the history
* more control delegated for external resolve

* new exported types added to index

* code formatting

* ExternalResolveResult.caption was made optional and its value now used as default.

* addUnknownSymbolResolver signature was updated + naming update.

* Redundant ExternalResolveAttempt type was removed

* Prettier

* Prettier with newer version

* Revert bad test changes

* Always provide reflection to link resolver

Also swap argument order so that the possibly undefined parameter is last.

* Fix lint

---------

Co-authored-by: Gerrit Birkeland <gerrit@gerritbirkeland.com>
  • Loading branch information
captain-torch and Gerrit0 committed Feb 24, 2023
1 parent c565bc6 commit 020ebee
Show file tree
Hide file tree
Showing 5 changed files with 70 additions and 28 deletions.
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(
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

0 comments on commit 020ebee

Please sign in to comment.