typedoc-plugin-no-inherit
Version:
Typedoc plugin to exclude inherited members from a Typedoc class/interface
110 lines (109 loc) • 5.1 kB
JavaScript
import { Converter, DeclarationReflection, ParameterType, Reflection, ReflectionKind } from "typedoc";
export function load(app) {
app.options.addDeclaration({
name: "inheritNone",
defaultValue: false,
help: "[typedoc-plugin-no-inherit]: If true, no documentation will be inherited regardless of tags.",
type: ParameterType.Boolean,
});
const noInherit = [];
const inheritedReflections = [];
/**
* Builds the list of classes/interfaces that don't inherit docs and
* the list of reflections that are inherited that could end up being removed.
*/
app.converter.on(Converter.EVENT_CREATE_DECLARATION, (context, reflection) => {
if (reflection instanceof DeclarationReflection) {
// class or interface that won't inherit docs
if (reflection.kindOf(ReflectionKind.ClassOrInterface) && reflection.comment && reflection.comment.getTag("@noInheritDoc")) {
if (!app.options.getValue("inheritNone"))
noInherit.push(reflection);
reflection.comment.removeTags("@noInheritDoc");
}
// class or interface member inherited from a super
if (reflection.inheritedFrom &&
reflection.parent &&
reflection.parent.kindOf(ReflectionKind.ClassOrInterface) &&
(!reflection.overwrites || (reflection.overwrites && reflection.overwrites !== reflection.inheritedFrom))) {
if (app.options.getValue("inheritNone"))
context.project.removeReflection(reflection); // inheritNone? just remove it immediately
else
inheritedReflections.push(reflection);
}
}
}, -1100);
/**
* Goes over the list of inherited reflections and removes any that are down the hierarchy
* from a class that doesn't inherit docs.
*/
app.converter.on(Converter.EVENT_RESOLVE_BEGIN, (context) => {
/**
* Checks whether some DeclarationReflection is in the noInherit list.
* @param search The DeclarationReflection to search for in the list.
*/
function isNoInherit(search) {
if (noInherit.find((no) => no.id === search.id && no.name === search.name)) {
return true;
}
return false;
}
/**
* Checks whether some Reflection is in the inheritedReflections list.
* @param search The Reflection to search for in the list.
*/
function isInherited(search) {
if (inheritedReflections.find((inh) => inh.id === search.id && inh.name === search.name)) {
return true;
}
return false;
}
/**
* Checks whether some reflection's inheritance chain is broken by a class or interface that doesn't inherit docs.
* @param context The context object describing the current state the converter is in.
* @param current The current reflection being evaluated for non-inheritance.
* @param depth The current recursion depth, used for stopping on excessively long inheritance chains.
*/
function isNoInheritRecursive(context, current, depth) {
if (depth > 20) {
app.logger.warn(`Found inheritance chain with depth > 20, stopping no inherit check: ${current.getFullName()}`);
return false; // stop if we've recursed more than 20 times
}
// As we move up the chain, check if the reflection parent is in the noInherit list
const parent = current.parent;
if (!parent)
return false;
if (isNoInherit(parent) && (depth === 0 || isInherited(current))) {
return true;
}
const checkExtended = (type) => {
const extended = type?.reflection;
if (extended instanceof Reflection) {
const upLevel = extended.getChildByName(current.name);
if (upLevel && isNoInheritRecursive(context, upLevel, depth + 1)) {
return true;
}
}
return false;
};
if (parent.extendedTypes) {
if (parent.extendedTypes.some(checkExtended)) {
return true;
}
}
return false;
}
if (noInherit.length > 0 && inheritedReflections.length > 0) {
const project = context.project;
const removals = [];
for (const reflection of inheritedReflections) {
// Look through the inheritance chain for a reflection that is flagged as noInherit for this reflection
if (isNoInheritRecursive(context, reflection, 0)) {
removals.push(reflection);
}
}
for (const removal of removals) {
project.removeReflection(removal);
}
}
});
}