typedoc-plugin-merge-modules
Version:
Plugin for TypeDoc that merges the content of modules.
215 lines (214 loc) • 9.69 kB
JavaScript
import { Comment, DeclarationReflection, DocumentReflection, ReflectionKind } from "typedoc";
import { getNameFromDescriptionTag, moveReflectionToTarget, removeReflectionFromParent, removeTagFromCommentsOf, } from "../utils.js";
/**
* Name of the comment tag that can be used to mark a module as the target module within the bundle.
* The target module is the one into which all the modules of the bundle are merged.
*/
const targetModuleCommentTag = "@mergeTarget";
/**
* Class representing a group of modules.
*/
export class ModuleBundle {
/** The project in which all modules are in. */
project;
/** The modules of the bundle. */
modules = new Array();
/**
* Creates a new module bundle instance.
* @param project The project in which all modules are in.
*/
constructor(project) {
this.project = project;
}
/**
* Adds a module to the bundle.
* @param module The module to add.
*/
add(module) {
this.modules.push(module);
}
/**
* Merges the modules of the bundle into one module (or the given target override).
* @param categorizationHasAlreadyHappened Defines if TypeDoc has already categorized the
* reflections in the modules of the bundle.
* @param targetOverride A module to target, taking precendence over the bundle's search.
*/
merge(categorizationHasAlreadyHappened, targetOverride) {
// get target module
const mergeTarget = targetOverride ?? this.getTargetModule();
removeTagFromCommentsOf(mergeTarget, targetModuleCommentTag);
this.moveChildrenAndDocumentsIntoTarget(mergeTarget);
if (categorizationHasAlreadyHappened) {
// In this case TypeDoc has already categorized and grouped the reflections.
// Therefore we must copy the content of all categories and groups into the target.
this.moveCategoriesIntoTarget(mergeTarget);
this.moveGroupsIntoTarget(mergeTarget);
}
else {
// In this case we must copy the category descriptions into the target because TypeDoc will look
// for them there when it categorizes and groups the reflections.
// If we don't do this then the category and group descriptions will be missing in the docs.
this.copyCategoryDescriptionTagsIntoTarget(mergeTarget);
this.copyGroupDescriptionTagsIntoTarget(mergeTarget);
}
// remove rest modules
this.modules.forEach((module) => {
if (module !== mergeTarget) {
removeReflectionFromParent(module);
}
});
}
/**
* Returns the module from the bundle that should be used as the target module.
* @returns The target module.
*/
getTargetModule() {
// 1. search for the first module which is marked with a specific tag
const firstModuleWithTargetTag = this.modules.find((module) => module.comment && module.comment.blockTags.findIndex((ct) => ct.tag === targetModuleCommentTag) !== -1);
if (firstModuleWithTargetTag) {
return firstModuleWithTargetTag;
}
// 2. search for the first module with a comment
const firstModuleWithComment = this.modules.find((module) => (module.comment?.summary.length ?? 0) > 0);
if (firstModuleWithComment) {
return firstModuleWithComment;
}
// 3. default: pick the first module
return this.modules[0];
}
/**
* Moves all children and documents into the given target.
* @param target The target into which the children and documents should be moved.
*/
moveChildrenAndDocumentsIntoTarget(target) {
for (const mod of this.modules) {
// Here we create a copy because the next loop modifies the collection
const reflections = [...(mod.childrenIncludingDocuments ?? [])];
for (const ref of reflections) {
// Drop aliases (= ReflectionKind.Reference)
if ((ref instanceof DeclarationReflection && !ref.kindOf(ReflectionKind.Reference)) ||
ref instanceof DocumentReflection) {
moveReflectionToTarget(ref, target);
}
}
}
}
/**
* Moves the children from all modules' categories into the corresponding category of the given target.
* @param target The target into whose categories the children should be moved.
*/
moveCategoriesIntoTarget(target) {
// move categories
this.modules.forEach((module) => {
if (module !== target) {
module.categories?.forEach((category) => {
const existingTargetCategory = target.categories?.find((c) => c.title === category.title);
if (!existingTargetCategory) {
target.categories = [...(target.categories ?? []), category];
}
else {
existingTargetCategory.children = existingTargetCategory.children.concat(category.children);
if (existingTargetCategory.description?.length === 0 && category.description) {
existingTargetCategory.description = category.description;
}
}
});
}
});
// sort categories
target.categories?.forEach((category) => {
category.children.sort((a, b) => {
if (a.name > b.name) {
return 1;
}
else if (a.name === b.name) {
return 0;
}
return -1;
});
});
}
/**
* Copies the category description comment tags into the the given target.
* @param target The target into which the category descriptions are copied.
*/
copyCategoryDescriptionTagsIntoTarget(target) {
this.modules.forEach((module) => {
if (module !== target) {
const categoryDescriptionsOfModule = module.comment?.blockTags.filter((bt) => bt.tag === "@categoryDescription") ?? [];
if (categoryDescriptionsOfModule.length === 0) {
return; // nothing to copy
}
if (!target.comment) {
target.comment = new Comment([], []);
}
categoryDescriptionsOfModule.forEach((categoryDescription) => {
const targetModuleAlreadyHasThisCategoryDescriptionsTag = target.comment?.blockTags.find((bt) => bt.tag === "@categoryDescription" &&
getNameFromDescriptionTag(bt) === getNameFromDescriptionTag(categoryDescription));
if (!targetModuleAlreadyHasThisCategoryDescriptionsTag) {
target.comment?.blockTags.push(categoryDescription);
}
});
}
});
}
/**
* Moves the children from all modules' groups into the corresponding group of the given target.
* @param target The target into whose groups the children should be moved.
*/
moveGroupsIntoTarget(target) {
// move groups
this.modules.forEach((module) => {
if (module !== target) {
module.groups?.forEach((group) => {
const existingTargetGroup = target.groups?.find((g) => g.title === group.title);
if (!existingTargetGroup) {
target.groups = [...(target.groups ?? []), group];
}
else {
existingTargetGroup.children = existingTargetGroup.children.concat(group.children);
if (existingTargetGroup.description?.length === 0 && group.description) {
existingTargetGroup.description = group.description;
}
}
});
}
});
// sort groups
target.groups?.forEach((group) => {
group.children.sort((a, b) => {
if (a.name > b.name) {
return 1;
}
else if (a.name === b.name) {
return 0;
}
return -1;
});
});
}
/**
* Copies the group description comment tags into the the given target.
* @param target The target into which the group descriptions are copied.
*/
copyGroupDescriptionTagsIntoTarget(target) {
this.modules.forEach((module) => {
if (module !== target) {
const groupDescriptionsOfModule = module.comment?.blockTags.filter((bt) => bt.tag === "@groupDescription") ?? [];
if (groupDescriptionsOfModule.length === 0) {
return; // nothing to copy
}
if (!target.comment) {
target.comment = new Comment([], []);
}
groupDescriptionsOfModule.forEach((groupDescription) => {
const targetModuleAlreadyHasThisGroupDescriptionsTag = target.comment?.blockTags.find((bt) => bt.tag === "@groupDescription" &&
getNameFromDescriptionTag(bt) === getNameFromDescriptionTag(groupDescription));
if (!targetModuleAlreadyHasThisGroupDescriptionsTag) {
target.comment?.blockTags.push(groupDescription);
}
});
}
});
}
}