UNPKG

typedoc

Version:

Create api documentation for TypeScript projects.

150 lines (149 loc) 6.7 kB
import ts from "typescript"; import { DeclarationReflection, Reflection, ReflectionKind, ReflectionSymbolId, } from "../../models/index.js"; import { parseDeclarationReference, } from "./declarationReference.js"; import { resolveDeclarationReference } from "./declarationReferenceResolver.js"; import { maxElementByScore } from "../../utils/array.js"; const urlPrefix = /^(http|ftp)s?:\/\//; export function resolveLinks(comment, reflection, externalResolver, options) { comment.summary = resolvePartLinks(reflection, comment.summary, externalResolver, options); for (const tag of comment.blockTags) { tag.content = resolvePartLinks(reflection, tag.content, externalResolver, options); } if (reflection instanceof DeclarationReflection && reflection.readme) { reflection.readme = resolvePartLinks(reflection, reflection.readme, externalResolver, options); } if (reflection.isDocument()) { reflection.content = resolvePartLinks(reflection, reflection.content, externalResolver, options); } } export function resolvePartLinks(reflection, parts, externalResolver, options) { return parts.flatMap((part) => processPart(reflection, part, externalResolver, options)); } function processPart(reflection, part, externalResolver, options) { if (part.kind === "inline-tag") { if (part.tag === "@link" || part.tag === "@linkcode" || part.tag === "@linkplain") { return resolveLinkTag(reflection, part, externalResolver, options); } } return part; } function resolveLinkTag(reflection, part, externalResolver, options) { // This tag may have already been resolved to if we are running in packages mode // or when reading in a JSON file. #2680. if (typeof part.target === "string" || part.target instanceof Reflection) { return part; } let defaultDisplayText = ""; let pos = 0; const end = part.text.length; while (pos < end && ts.isWhiteSpaceLike(part.text.charCodeAt(pos))) { pos++; } let target; // Try to parse a declaration reference if we didn't use the TS symbol for resolution const declRef = parseDeclarationReference(part.text, pos, end); // Might already know where it should go if useTsLinkResolution is turned on if (part.target instanceof ReflectionSymbolId) { const tsTargets = reflection.project.getReflectionsFromSymbolId(part.target); if (tsTargets.length) { // Find the target most likely to have a real url in the generated documentation // 1. A direct export (class, interface, variable) // 2. A property of a direct export (class/interface property) // 3. A property of a type of an export (property on type alias) // 4. Whatever the first symbol found was target = maxElementByScore(tsTargets, (r) => { if (r.kindOf(ReflectionKind.SomeExport)) { return 4; } if (r.kindOf(ReflectionKind.SomeMember) && r.parent?.kindOf(ReflectionKind.SomeExport)) { return 3; } if (r.kindOf(ReflectionKind.SomeMember) && r.parent?.kindOf(ReflectionKind.TypeLiteral) && r.parent.parent?.kindOf(ReflectionKind.TypeAlias | ReflectionKind.Variable)) { return 2; } return 1; }); pos = end; defaultDisplayText = part.tsLinkText || (options.preserveLinkText ? part.text : target.name); } else { // If we didn't find a target, we might be pointing to a symbol in another project that will be merged in // or some external symbol, so ask external resolvers to try resolution. Don't use regular declaration ref // resolution in case it matches something that would have been merged in later. if (declRef) { pos = declRef[1]; } const externalResolveResult = externalResolver(declRef?.[0] ?? part.target.toDeclarationReference(), reflection, part, part.target); defaultDisplayText = part.tsLinkText || (options.preserveLinkText ? part.text : part.text.substring(0, pos)); switch (typeof externalResolveResult) { case "string": target = externalResolveResult; break; case "object": target = externalResolveResult.target; defaultDisplayText = externalResolveResult.caption || defaultDisplayText; } } } if (!target && declRef) { // Got one, great! Try to resolve the link target = resolveDeclarationReference(reflection, declRef[0]); pos = declRef[1]; if (target) { defaultDisplayText = options.preserveLinkText ? part.text : target.name; } else { // If we didn't find a link, it might be a @link tag to an external symbol, check that next. const externalResolveResult = externalResolver(declRef[0], reflection, part, part.target instanceof ReflectionSymbolId ? part.target : undefined); defaultDisplayText = options.preserveLinkText ? part.text : part.text.substring(0, pos); switch (typeof externalResolveResult) { case "string": target = externalResolveResult; break; case "object": target = externalResolveResult.target; defaultDisplayText = externalResolveResult.caption || defaultDisplayText; } } } if (!target && urlPrefix.test(part.text)) { const wsIndex = part.text.search(/\s/); target = wsIndex === -1 ? part.text : part.text.substring(0, wsIndex); pos = target.length; defaultDisplayText = target; } // Remaining text after an optional pipe is the link text, so advance // until that's consumed. while (pos < end && ts.isWhiteSpaceLike(part.text.charCodeAt(pos))) { pos++; } if (pos < end && part.text[pos] === "|") { pos++; } if (!target) { return part; } part.target = target; part.text = part.text.substring(pos).trim() || defaultDisplayText || part.text; return part; }