diff --git a/CHANGELOG.md b/CHANGELOG.md index 95e19a0..cd974c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [6.0.3] - 2024-10-25 +### Fixed +- Category descriptions and group descriptions were missing when merging into the project root. + ## [6.0.2] - 2024-10-13 ### Fixed - Category descriptions and group descriptions were missing in the generated documentation when TypeDoc was run @@ -88,7 +92,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 First release -[unreleased]: https://github.com/krisztianb/typedoc-plugin-merge-modules/compare/v6.0.2...HEAD +[unreleased]: https://github.com/krisztianb/typedoc-plugin-merge-modules/compare/v6.0.3...HEAD +[6.0.3]: https://github.com/krisztianb/typedoc-plugin-merge-modules/releases/tag/v6.0.3 [6.0.2]: https://github.com/krisztianb/typedoc-plugin-merge-modules/releases/tag/v6.0.2 [6.0.1]: https://github.com/krisztianb/typedoc-plugin-merge-modules/releases/tag/v6.0.1 [6.0.0]: https://github.com/krisztianb/typedoc-plugin-merge-modules/releases/tag/v6.0.0 diff --git a/package.json b/package.json index cf03ebc..b2798b9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "typedoc-plugin-merge-modules", - "version": "6.0.2", + "version": "6.0.3", "description": "Plugin for TypeDoc that merges the content of modules.", "author": { "name": "KrisztiƔn Balla", diff --git a/src/merger/module_bundle.ts b/src/merger/module_bundle.ts index 71c1fb0..24182e0 100644 --- a/src/merger/module_bundle.ts +++ b/src/merger/module_bundle.ts @@ -1,11 +1,8 @@ -/** @module merger */ import { Comment, DeclarationReflection, DocumentReflection, ProjectReflection, ReflectionKind } from "typedoc"; import { - addDeclarationReflectionToTarget, - addDocumentReflectionToTarget, getNameFromDescriptionTag, - removeDeclarationReflectionFromModule, - removeDocumentReflectionFromModule, + moveDeclarationReflectionToTarget, + moveDocumentReflectionToTarget, removeTagFromCommentsOf, } from "../utils"; @@ -42,33 +39,37 @@ export class ModuleBundle { } /** - * Merges the modules of the bundle into one 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. */ - public merge(categorizationHasAlreadyHappened: boolean): void { + public merge( + categorizationHasAlreadyHappened: boolean, + targetOverride?: DeclarationReflection | ProjectReflection, + ): void { // get target module - const targetModule = this.getTargetModule(); - removeTagFromCommentsOf(targetModule, targetModuleCommentTag); + const mergeTarget = targetOverride ?? this.getTargetModule(); + removeTagFromCommentsOf(mergeTarget, targetModuleCommentTag); - this.mergeChildrenAndDocumentsIntoTargetModule(targetModule); + this.moveChildrenAndDocumentsIntoTarget(mergeTarget); if (categorizationHasAlreadyHappened) { // In this case TypeDoc has already categorized and grouped the reflections. - // Therefore we must merge the content all categories and groups into the target module. - this.mergeCategoriesIntoTargetModule(targetModule); - this.mergeGroupsIntoTargetModule(targetModule); + // 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 module because TypeDoc will look + // 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 would be missing in the docs. - this.copyCategoryDescriptionTagsIntoTargetModule(targetModule); - this.copyGroupDescriptionTagsIntoTargetModule(targetModule); + // 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 !== targetModule) { + if (module !== mergeTarget) { delete module.children; this.project.removeReflection(module); } @@ -101,7 +102,11 @@ export class ModuleBundle { return this.modules[0]; } - private mergeChildrenAndDocumentsIntoTargetModule(targetModule: DeclarationReflection): void { + /** + * Moves all children and documents into the given target. + * @param target The target into which the children and documents should be moved. + */ + private moveChildrenAndDocumentsIntoTarget(target: DeclarationReflection | ProjectReflection): void { for (const mod of this.modules) { // Here we create a copy because the next loop modifies the collection const reflections = [...(mod.childrenIncludingDocuments ?? [])]; @@ -109,53 +114,27 @@ export class ModuleBundle { for (const ref of reflections) { // Drop aliases (= ReflectionKind.Reference) if (ref instanceof DeclarationReflection && !ref.kindOf(ReflectionKind.Reference)) { - this.moveDeclarationReflectionToTargetModule(ref, targetModule); + moveDeclarationReflectionToTarget(ref, target); } else if (ref instanceof DocumentReflection) { - this.moveDocumentReflectionToTargetModule(ref, targetModule); + moveDocumentReflectionToTarget(ref, target); } } } } /** - * Moves a declaration reflection to the given target module. - * @param ref The declaration reflection that should be moved. - * @param targetModule The target module into which the declaration reflection should be moved. - */ - // eslint-disable-next-line class-methods-use-this - private moveDeclarationReflectionToTargetModule( - ref: DeclarationReflection, - targetModule: DeclarationReflection, - ): void { - removeDeclarationReflectionFromModule(ref); - addDeclarationReflectionToTarget(ref, targetModule); - } - - /** - * Moves a document reflection to the given target module. - * @param ref The document reflection that should be moved. - * @param targetModule The target module into which the document reflection should be moved. - * @throws {Error} If the given reflection is not within a module. - */ - // eslint-disable-next-line class-methods-use-this - private moveDocumentReflectionToTargetModule(ref: DocumentReflection, targetModule: DeclarationReflection): void { - removeDocumentReflectionFromModule(ref); - addDocumentReflectionToTarget(ref, targetModule); - } - - /** - * Merges the children from all modules' categories into the corresponding category of the given target module. - * @param targetModule The target module into whoes categories the children should be merged. + * 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. */ - private mergeCategoriesIntoTargetModule(targetModule: DeclarationReflection): void { - // merge categories + private moveCategoriesIntoTarget(target: DeclarationReflection | ProjectReflection): void { + // move categories this.modules.forEach((module) => { - if (module !== targetModule) { + if (module !== target) { module.categories?.forEach((category) => { - const existingTargetCategory = targetModule.categories?.find((c) => c.title === category.title); + const existingTargetCategory = target.categories?.find((c) => c.title === category.title); if (!existingTargetCategory) { - targetModule.categories = [...(targetModule.categories ?? []), category]; + target.categories = [...(target.categories ?? []), category]; } else { existingTargetCategory.children = existingTargetCategory.children.concat(category.children); @@ -168,7 +147,7 @@ export class ModuleBundle { }); // sort categories - targetModule.categories?.forEach((category) => { + target.categories?.forEach((category) => { category.children.sort((a, b) => { if (a.name > b.name) { return 1; @@ -180,12 +159,12 @@ export class ModuleBundle { }); } /** - * Copies the category description comment tags into the the given target module. - * @param targetModule The target module into which the category descriptions are merged. + * Copies the category description comment tags into the the given target. + * @param target The target into which the category descriptions are copied. */ - private copyCategoryDescriptionTagsIntoTargetModule(targetModule: DeclarationReflection): void { + private copyCategoryDescriptionTagsIntoTarget(target: DeclarationReflection | ProjectReflection): void { this.modules.forEach((module) => { - if (module !== targetModule) { + if (module !== target) { const categoryDescriptionsOfModule = module.comment?.blockTags.filter((bt) => bt.tag === "@categoryDescription") ?? []; @@ -193,19 +172,19 @@ export class ModuleBundle { return; // nothing to copy } - if (!targetModule.comment) { - targetModule.comment = new Comment([], []); + if (!target.comment) { + target.comment = new Comment([], []); } categoryDescriptionsOfModule.forEach((categoryDescription) => { - const targetModuleAlreadyHasThisCategoryDescriptionsTag = targetModule.comment?.blockTags.find( + const targetModuleAlreadyHasThisCategoryDescriptionsTag = target.comment?.blockTags.find( (bt) => bt.tag === "@categoryDescription" && getNameFromDescriptionTag(bt) === getNameFromDescriptionTag(categoryDescription), ); if (!targetModuleAlreadyHasThisCategoryDescriptionsTag) { - targetModule.comment?.blockTags.push(categoryDescription); + target.comment?.blockTags.push(categoryDescription); } }); } @@ -213,18 +192,18 @@ export class ModuleBundle { } /** - * Merges the children from all modules' groups into the corresponding group of the given target module. - * @param targetModule The target module into whoes groups the children should be merged. + * 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. */ - private mergeGroupsIntoTargetModule(targetModule: DeclarationReflection): void { - // merge groups + private moveGroupsIntoTarget(target: DeclarationReflection | ProjectReflection): void { + // move groups this.modules.forEach((module) => { - if (module !== targetModule) { + if (module !== target) { module.groups?.forEach((group) => { - const existingTargetGroup = targetModule.groups?.find((g) => g.title === group.title); + const existingTargetGroup = target.groups?.find((g) => g.title === group.title); if (!existingTargetGroup) { - targetModule.groups = [...(targetModule.groups ?? []), group]; + target.groups = [...(target.groups ?? []), group]; } else { existingTargetGroup.children = existingTargetGroup.children.concat(group.children); @@ -237,7 +216,7 @@ export class ModuleBundle { }); // sort groups - targetModule.groups?.forEach((group) => { + target.groups?.forEach((group) => { group.children.sort((a, b) => { if (a.name > b.name) { return 1; @@ -250,12 +229,12 @@ export class ModuleBundle { } /** - * Copies the group description comment tags into the the given target module. - * @param targetModule The target module into which the group descriptions are merged. + * Copies the group description comment tags into the the given target. + * @param target The target into which the group descriptions are copied. */ - private copyGroupDescriptionTagsIntoTargetModule(targetModule: DeclarationReflection): void { + private copyGroupDescriptionTagsIntoTarget(target: DeclarationReflection | ProjectReflection): void { this.modules.forEach((module) => { - if (module !== targetModule) { + if (module !== target) { const groupDescriptionsOfModule = module.comment?.blockTags.filter((bt) => bt.tag === "@groupDescription") ?? []; @@ -263,19 +242,19 @@ export class ModuleBundle { return; // nothing to copy } - if (!targetModule.comment) { - targetModule.comment = new Comment([], []); + if (!target.comment) { + target.comment = new Comment([], []); } groupDescriptionsOfModule.forEach((groupDescription) => { - const targetModuleAlreadyHasThisGroupDescriptionsTag = targetModule.comment?.blockTags.find( + const targetModuleAlreadyHasThisGroupDescriptionsTag = target.comment?.blockTags.find( (bt) => bt.tag === "@groupDescription" && getNameFromDescriptionTag(bt) === getNameFromDescriptionTag(groupDescription), ); if (!targetModuleAlreadyHasThisGroupDescriptionsTag) { - targetModule.comment?.blockTags.push(groupDescription); + target.comment?.blockTags.push(groupDescription); } }); } diff --git a/src/merger/module_category_merger.ts b/src/merger/module_category_merger.ts index d7960c6..3ea3893 100644 --- a/src/merger/module_category_merger.ts +++ b/src/merger/module_category_merger.ts @@ -1,4 +1,3 @@ -/** @module merger */ import { DeclarationReflection } from "typedoc"; import { ModuleMerger } from "./module_merger"; diff --git a/src/merger/project_merger.ts b/src/merger/project_merger.ts index 9557a3c..31c3e0d 100644 --- a/src/merger/project_merger.ts +++ b/src/merger/project_merger.ts @@ -1,11 +1,7 @@ -import { DeclarationReflection, DocumentReflection, ProjectReflection, ReflectionKind } from "typedoc"; -import { - addDeclarationReflectionToTarget, - addDocumentReflectionToTarget, - getModulesFrom, - removeDeclarationReflectionFromModule, - removeDocumentReflectionFromModule, -} from "../utils"; +import { ProjectReflection } from "typedoc"; +import { Plugin } from "../plugin"; +import { getModulesFrom } from "../utils"; +import { ModuleBundle } from "./module_bundle"; /** * Merger that moves the content of all modules into the project root. @@ -14,12 +10,17 @@ export class ProjectMerger { /** The project whose modules are merged. */ private readonly project: ProjectReflection; + /** The plugin which is using this merger. */ + private readonly plugin: Plugin; + /** * Creates a new merger instance. * @param project The project whose modules are merged. + * @param plugin The plugin which is using this merger. */ - public constructor(project: ProjectReflection) { + public constructor(project: ProjectReflection, plugin: Plugin) { this.project = project; + this.plugin = plugin; } /** @@ -29,56 +30,11 @@ export class ProjectMerger { // In monorepo project each project is also a module => Recursively collect all modules const allModules = getModulesFrom(this.project); - if (allModules.length > 0) { - this.removeModulesFromProject(); - - for (const module of allModules) { - // Here we create a copy because the next loop modifies the collection - const reflections = [...(module.childrenIncludingDocuments ?? [])]; - - for (const ref of reflections) { - // Drop aliases (= ReflectionKind.Reference) and modules - if ( - ref instanceof DeclarationReflection && - !ref.kindOf([ReflectionKind.Reference, ReflectionKind.Module]) - ) { - this.moveDeclarationReflectionToProject(ref); - } else if (ref instanceof DocumentReflection) { - this.moveDocumentReflectionFromToProject(ref); - } - } - } - } - } - - /** - * Removes all modules from the project reflection. Doesn't touch the project documents. - */ - private removeModulesFromProject(): void { - this.project.children = []; - this.project.children.forEach((child) => this.project.removeReflection(child)); + // Create a module bundle for all the modules + const bundle = new ModuleBundle(this.project); + allModules.forEach((module) => bundle.add(module)); - // keep project documents that are included with the TypeDoc config parameter "projectDocuments" - this.project.childrenIncludingDocuments = - this.project.childrenIncludingDocuments?.filter((item) => item instanceof DocumentReflection) ?? []; - } - - /** - * Moves a declaration reflection to the project root. - * @param ref The declaration reflection that should be moved. - */ - private moveDeclarationReflectionToProject(ref: DeclarationReflection): void { - removeDeclarationReflectionFromModule(ref); - addDeclarationReflectionToTarget(ref, this.project); - } - - /** - * Moves a document reflection to the project root. - * @param ref The document reflection that should be moved. - * @throws {Error} If the given reflection is not within a module. - */ - private moveDocumentReflectionFromToProject(ref: DocumentReflection): void { - removeDocumentReflectionFromModule(ref); - addDocumentReflectionToTarget(ref, this.project); + // Merge the bundle into the project + bundle.merge(this.plugin.runsAfterCategorization, this.project); } } diff --git a/src/plugin.ts b/src/plugin.ts index b63ac79..4f5a27c 100644 --- a/src/plugin.ts +++ b/src/plugin.ts @@ -144,7 +144,7 @@ export class Plugin { */ private createMerger(project: ProjectReflection): ProjectMerger | ModuleMerger | undefined { if (this._options.mode === "project") { - return new ProjectMerger(project); + return new ProjectMerger(project, this); } else if (this._options.mode === "module") { return new ModuleMerger(project, this); } else if (this._options.mode === "module-category") { diff --git a/src/utils.ts b/src/utils.ts index 3af0588..69e70ca 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -50,7 +50,10 @@ export function tryGetOriginalReflectionName( * @param reflection The reflection from which the tag should be removed. * @param tagToRemove Name of the tag to be removed. */ -export function removeTagFromCommentsOf(reflection: DeclarationReflection, tagToRemove: string): void { +export function removeTagFromCommentsOf( + reflection: DeclarationReflection | ProjectReflection, + tagToRemove: string, +): void { const tagIndex = reflection.comment?.blockTags.findIndex((ct) => ct.tag === tagToRemove) ?? -1; if (tagIndex !== -1) { @@ -132,6 +135,32 @@ export function removeDocumentReflectionFromModule(ref: DocumentReflection): voi } } +/** + * Moves a declaration reflection to the given target. + * @param ref The declaration reflection that should be moved. + * @param target The target into which the declaration reflection should be moved. + */ +export function moveDeclarationReflectionToTarget( + ref: DeclarationReflection, + target: DeclarationReflection | ProjectReflection, +): void { + removeDeclarationReflectionFromModule(ref); + addDeclarationReflectionToTarget(ref, target); +} + +/** + * Moves a document reflection to the given target. + * @param ref The document reflection that should be moved. + * @param target The target into which the document reflection should be moved. + */ +export function moveDocumentReflectionToTarget( + ref: DocumentReflection, + target: DeclarationReflection | ProjectReflection, +): void { + removeDocumentReflectionFromModule(ref); + addDocumentReflectionToTarget(ref, target); +} + /** * Returns the modules within the given module parent. Searches recursively. * @param moduleParent The element in which to search for modules. diff --git a/test/merge-project-monorepo/input/project1/a.ts b/test/merge-project-monorepo/input/project1/a.ts index 1e14df5..98936de 100644 --- a/test/merge-project-monorepo/input/project1/a.ts +++ b/test/merge-project-monorepo/input/project1/a.ts @@ -1 +1,2 @@ +/** @category Alpha */ export class A {} diff --git a/test/merge-project-monorepo/input/project1/b.ts b/test/merge-project-monorepo/input/project1/b.ts index 1aa41a5..5b379cf 100644 --- a/test/merge-project-monorepo/input/project1/b.ts +++ b/test/merge-project-monorepo/input/project1/b.ts @@ -1 +1,2 @@ +/** @category Beta */ export class B {} diff --git a/test/merge-project-monorepo/input/project1/index.ts b/test/merge-project-monorepo/input/project1/index.ts index bfd87cc..8f43144 100644 --- a/test/merge-project-monorepo/input/project1/index.ts +++ b/test/merge-project-monorepo/input/project1/index.ts @@ -1,2 +1,9 @@ +/** + * @module + * @categoryDescription Alpha + * Category description from the file of A. + * @categoryDescription Beta + * Category description from the file of B. + */ export { A } from "./a"; export { B } from "./b"; diff --git a/test/merge-project-monorepo/input/project2/c.ts b/test/merge-project-monorepo/input/project2/c.ts index 1ec0ebf..baead1c 100644 --- a/test/merge-project-monorepo/input/project2/c.ts +++ b/test/merge-project-monorepo/input/project2/c.ts @@ -1 +1,2 @@ +/** @category Gamma */ export class C {} diff --git a/test/merge-project-monorepo/input/project2/index.ts b/test/merge-project-monorepo/input/project2/index.ts index 5e7c6a6..c730208 100644 --- a/test/merge-project-monorepo/input/project2/index.ts +++ b/test/merge-project-monorepo/input/project2/index.ts @@ -1 +1,6 @@ +/** + * @module + * @categoryDescription Gamma + * Category description from the file of C. + */ export { C } from "./c"; diff --git a/test/merge-project-monorepo/test.cy.ts b/test/merge-project-monorepo/test.cy.ts index bd314dc..47318e1 100644 --- a/test/merge-project-monorepo/test.cy.ts +++ b/test/merge-project-monorepo/test.cy.ts @@ -21,3 +21,30 @@ describe("index.html", () => { cy.get("nav").find("a[href='./documents/doc2.html']"); }); }); + +describe("modules.html", () => { + beforeEach(() => { + cy.visit("./merge-project-monorepo/output/modules.html"); + }); + + it("contains the category Alpha and its description", () => { + const sectionSelector = ".col-content .tsd-index-section:nth-of-type(1)"; + + cy.get(sectionSelector + " h3").should("have.text", "Alpha"); + cy.get(sectionSelector + " p").should("have.text", "Category description from the file of A."); + }); + + it("contains the category Beta and its description", () => { + const sectionSelector = ".col-content .tsd-index-section:nth-of-type(2)"; + + cy.get(sectionSelector + " h3").should("have.text", "Beta"); + cy.get(sectionSelector + " p").should("have.text", "Category description from the file of B."); + }); + + it("contains the category Gamma and its description", () => { + const sectionSelector = ".col-content .tsd-index-section:nth-of-type(3)"; + + cy.get(sectionSelector + " h3").should("have.text", "Gamma"); + cy.get(sectionSelector + " p").should("have.text", "Category description from the file of C."); + }); +}); diff --git a/test/merge-project/input/a.ts b/test/merge-project/input/a.ts index 1e14df5..21a9b2b 100644 --- a/test/merge-project/input/a.ts +++ b/test/merge-project/input/a.ts @@ -1 +1,8 @@ +/** + * @module + * @categoryDescription Alpha + * Category description from the file of A. + */ + +/** @category Alpha */ export class A {} diff --git a/test/merge-project/input/b.ts b/test/merge-project/input/b.ts index 1aa41a5..24fc95e 100644 --- a/test/merge-project/input/b.ts +++ b/test/merge-project/input/b.ts @@ -1 +1,8 @@ +/** + * @module + * @categoryDescription Beta + * Category description from the file of B. + */ + +/** @category Beta */ export class B {} diff --git a/test/merge-project/input/c.ts b/test/merge-project/input/c.ts index 1ec0ebf..e386ebf 100644 --- a/test/merge-project/input/c.ts +++ b/test/merge-project/input/c.ts @@ -1 +1,8 @@ +/** + * @module + * @categoryDescription Gamma + * Category description from the file of C. + */ + +/** @category Gamma */ export class C {} diff --git a/test/merge-project/test.cy.ts b/test/merge-project/test.cy.ts index 23fcdba..68349ef 100644 --- a/test/merge-project/test.cy.ts +++ b/test/merge-project/test.cy.ts @@ -20,3 +20,30 @@ describe("index.html", () => { cy.get("nav").find("a[href='./documents/doc1.html']"); }); }); + +describe("modules.html", () => { + beforeEach(() => { + cy.visit("./merge-project/output/modules.html"); + }); + + it("contains the category Alpha and its description", () => { + const sectionSelector = ".col-content .tsd-index-section:nth-of-type(1)"; + + cy.get(sectionSelector + " h3").should("have.text", "Alpha"); + cy.get(sectionSelector + " p").should("have.text", "Category description from the file of A."); + }); + + it("contains the category Beta and its description", () => { + const sectionSelector = ".col-content .tsd-index-section:nth-of-type(2)"; + + cy.get(sectionSelector + " h3").should("have.text", "Beta"); + cy.get(sectionSelector + " p").should("have.text", "Category description from the file of B."); + }); + + it("contains the category Gamma and its description", () => { + const sectionSelector = ".col-content .tsd-index-section:nth-of-type(3)"; + + cy.get(sectionSelector + " h3").should("have.text", "Gamma"); + cy.get(sectionSelector + " p").should("have.text", "Category description from the file of C."); + }); +});