typedoc
Version:
Create api documentation for TypeScript projects.
218 lines (217 loc) • 8.32 kB
JavaScript
import { ok } from "assert";
import { ContainerReflection, DeclarationReflection, ReferenceReflection, ReflectionKind, } from "../../models/index.js";
import { assertNever, filterMap } from "../../utils/index.js";
function resolveReferenceReflection(ref) {
if (ref instanceof ReferenceReflection) {
return ref.getTargetReflectionDeep();
}
return ref;
}
export function resolveDeclarationReference(reflection, ref) {
let high = [];
let low = [];
if (ref.moduleSource) {
high =
reflection.project.children?.filter((c) => c.kindOf(ReflectionKind.SomeModule) &&
c.name === ref.moduleSource) || [];
}
else if (ref.resolutionStart === "global") {
high.push(reflection.project);
if (reflection.project.children?.length === 1) {
high.push(reflection.project.children[0]);
}
}
else {
// Work around no-unnecessary-condition, should be unnecessary... want a trap if it ever becomes false.
ok(ref.resolutionStart.startsWith("local") &&
ref.resolutionStart.length === 5);
// TypeScript's behavior is to first try to resolve links via variable scope, and then
// if the link still hasn't been found, check either siblings (if comment belongs to a member)
// or otherwise children.
let refl = reflection;
if (refl.kindOf(ReflectionKind.ExportContainer)) {
high.push(refl);
}
while (refl.parent) {
refl = refl.parent;
if (refl.kindOf(ReflectionKind.ExportContainer)) {
high.push(refl);
}
else {
low.push(refl);
}
if (refl.kindOf(ReflectionKind.Project) &&
refl.children?.length === 1) {
high.push(refl.children[0]);
}
}
if (reflection.kindOf(ReflectionKind.SomeMember)) {
high.push(reflection.parent);
}
else if (reflection.kindOf(ReflectionKind.SomeSignature) &&
reflection.parent.kindOf(ReflectionKind.SomeMember)) {
high.push(reflection.parent.parent);
}
else if (high[0] !== reflection) {
if (reflection.parent instanceof ContainerReflection) {
high.push(...(reflection.parent.childrenIncludingDocuments?.filter((c) => c.name === reflection.name) || []));
}
else {
high.push(reflection);
}
}
}
if (ref.symbolReference) {
for (const part of ref.symbolReference.path || []) {
const high2 = high;
high = [];
const low2 = low;
low = [];
for (const refl of high2) {
const resolved = resolveSymbolReferencePart(refl, part);
high.push(...resolved.high.map(resolveReferenceReflection));
low.push(...resolved.low.map(resolveReferenceReflection));
}
for (const refl of low2) {
const resolved = resolveSymbolReferencePart(refl, part);
low.push(...resolved.high.map(resolveReferenceReflection));
low.push(...resolved.low.map(resolveReferenceReflection));
}
}
if (ref.symbolReference.meaning) {
high = filterMapByMeaning(high, ref.symbolReference.meaning);
low = filterMapByMeaning(low, ref.symbolReference.meaning);
}
}
return high[0] || low[0];
}
function filterMapByMeaning(reflections, meaning) {
return filterMap(reflections, (refl) => {
const kwResolved = resolveKeyword(refl, meaning.keyword) || [];
if (meaning.label) {
return kwResolved.find((r) => r.comment?.label === meaning.label);
}
return kwResolved[meaning.index || 0];
});
}
function resolveKeyword(refl, kw) {
switch (kw) {
case undefined:
return refl instanceof DeclarationReflection && refl.signatures
? refl.signatures
: [refl];
case "class":
if (refl.kindOf(ReflectionKind.Class))
return [refl];
break;
case "interface":
if (refl.kindOf(ReflectionKind.Interface))
return [refl];
break;
case "type":
if (refl.kindOf(ReflectionKind.SomeType))
return [refl];
break;
case "enum":
if (refl.kindOf(ReflectionKind.Enum))
return [refl];
break;
case "namespace":
if (refl.kindOf(ReflectionKind.SomeModule))
return [refl];
break;
case "function":
if (refl.kindOf(ReflectionKind.FunctionOrMethod)) {
return refl.signatures;
}
break;
case "var":
if (refl.kindOf(ReflectionKind.Variable))
return [refl];
break;
case "new":
case "constructor":
if (refl.kindOf(ReflectionKind.ClassOrInterface |
ReflectionKind.TypeLiteral)) {
const ctor = refl.children?.find((c) => c.kindOf(ReflectionKind.Constructor));
return ctor.signatures;
}
break;
case "member":
if (refl.kindOf(ReflectionKind.SomeMember))
return [refl];
break;
case "event":
// Never resolve. Nobody should use this.
// It's required by the grammar, but is not documented by TypeDoc
// nor by the comments in the grammar.
break;
case "call":
return refl.signatures;
case "index":
if (refl.indexSignatures) {
return refl.indexSignatures;
}
break;
case "complex":
if (refl.kindOf(ReflectionKind.SomeType))
return [refl];
break;
case "getter":
if (refl.getSignature) {
return [refl.getSignature];
}
break;
case "setter":
if (refl.setSignature) {
return [refl.setSignature];
}
break;
default:
assertNever(kw);
}
}
function resolveSymbolReferencePart(refl, path) {
let high = [];
let low = [];
let children;
if (refl instanceof ContainerReflection) {
children = refl.childrenIncludingDocuments;
}
if (!children && refl.isDeclaration() && refl.type?.type === "reflection") {
children = refl.type.declaration.childrenIncludingDocuments;
}
if (!children) {
return { high, low };
}
switch (path.navigation) {
// Grammar says resolve via "exports"... as always, reality is more complicated.
// Check exports first, but also allow this as a general purpose "some child" operator
// so that resolution doesn't behave very poorly with projects using JSDoc style resolution.
// Also is more consistent with how TypeScript resolves link tags.
case ".":
high = children.filter((r) => r.name === path.path &&
(r.kindOf(ReflectionKind.SomeExport) || r.flags.isStatic));
low = children.filter((r) => r.name === path.path &&
(!r.kindOf(ReflectionKind.SomeExport) || !r.flags.isStatic));
break;
// Resolve via "members", interface children, class instance properties/accessors/methods,
// enum members, type literal properties
case "#":
high =
children?.filter((r) => {
return (r.name === path.path &&
r.kindOf(ReflectionKind.SomeMember) &&
!r.flags.isStatic);
}) || [];
break;
// Resolve via "locals"... treat this as a stricter `.` which only supports traversing
// module/namespace exports since TypeDoc doesn't support documenting locals.
case "~":
if (refl.kindOf(ReflectionKind.SomeModule | ReflectionKind.Project)) {
high = children?.filter((r) => r.name === path.path) || [];
}
break;
}
return { high, low };
}