UNPKG

@xpack/docusaurus-plugin-doxygen

Version:

A Docusaurus plugin to integrate Doxygen into a Docusaurus project.

628 lines 28.5 kB
/* * This file is part of the xPack project (http://xpack.github.io). * Copyright (c) 2025 Liviu Ionescu. All rights reserved. * * Permission to use, copy, modify, and/or distribute this software * for any purpose is hereby granted, under the terms of the MIT license. * * If a copy of the license was not distributed with this file, it can * be obtained from https://opensource.org/licenses/MIT. */ import assert from 'node:assert'; import path from 'node:path'; import { Sect1DataModel } from '../../data-model/compounds/descriptiontype-dm.js'; import { InnerClassDataModel } from '../../data-model/compounds/reftype-dm.js'; import { escapeHtml, escapeMdx } from '../utils.js'; import { Section } from './members-vm.js'; import { RefTextDataModel } from '../../data-model/compounds/reftexttype-dm.js'; import { SectionDefByKindDataModel } from '../../data-model/compounds/sectiondeftype-dm.js'; // ---------------------------------------------------------------------------- export class CompoundBase { // -------------------------------------------------------------------------- constructor(collection, compoundDef) { this.kind = ''; this.compoundName = ''; this.id = ''; // Set in 2 steps, first the Ids and then, when all objects are in, the references. // Folder objects use separate arrays for files and folders children. this.childrenIds = []; this.children = []; /** The name shown in the index section. */ this.indexName = ''; /** The name shown in the page title. */ this.pageTitle = ''; this.hasSect1InDescription = false; // detailedDescriptionMdxLines: string[] | undefined this.sections = []; this.locationSet = new Set(); this._private = {}; this._private._compoundDef = compoundDef; this.collection = collection; this.kind = compoundDef.kind; this.compoundName = compoundDef.compoundName; this.id = compoundDef.id; if (compoundDef.title !== undefined) { this.titleMdxText = escapeMdx(compoundDef.title); } if (compoundDef?.location?.file !== undefined) { this.locationFilePath = compoundDef.location.file; } } createSections(classUnqualifiedName) { const reorderedSectionDefs = this.reorderSectionDefs(classUnqualifiedName); if (reorderedSectionDefs !== undefined) { const sections = []; for (const sectionDef of reorderedSectionDefs) { sections.push(new Section(this, sectionDef)); } this.sections = sections.sort((a, b) => { return a.getSectionOrderByKind() - b.getSectionOrderByKind(); }); } } reorderSectionDefs(classUnqualifiedName) { const sectionDefs = this._private._compoundDef?.sectionDefs; if (sectionDefs === undefined) { return undefined; } const resultSectionDefs = []; const sectionDefsByKind = new Map(); for (const sectionDef of sectionDefs) { if (sectionDef.kind === 'user-defined' && sectionDef.header !== undefined) { resultSectionDefs.push(sectionDef); continue; } if (sectionDef.memberDefs !== undefined) { for (const memberDef of sectionDef.memberDefs) { const adjustedSectionKind = this.adjustSectionKind(sectionDef, memberDef, classUnqualifiedName); let mapSectionDef = sectionDefsByKind.get(adjustedSectionKind); if (mapSectionDef === undefined) { mapSectionDef = new SectionDefByKindDataModel(adjustedSectionKind); sectionDefsByKind.set(adjustedSectionKind, mapSectionDef); } if (mapSectionDef.memberDefs === undefined) { mapSectionDef.memberDefs = []; } mapSectionDef.memberDefs.push(memberDef); } } if (sectionDef.members !== undefined) { for (const member of sectionDef.members) { const adjustedSectionKind = this.adjustSectionKind(sectionDef, member, classUnqualifiedName); let mapSectionDef = sectionDefsByKind.get(adjustedSectionKind); if (mapSectionDef === undefined) { mapSectionDef = new SectionDefByKindDataModel(adjustedSectionKind); sectionDefsByKind.set(adjustedSectionKind, mapSectionDef); } if (mapSectionDef.members === undefined) { mapSectionDef.members = []; } mapSectionDef.members.push(member); } } } resultSectionDefs.push(...sectionDefsByKind.values()); return resultSectionDefs; } // <xsd:simpleType name="DoxMemberKind"> // <xsd:restriction base="xsd:string"> // <xsd:enumeration value="define" /> // <xsd:enumeration value="property" /> // <xsd:enumeration value="event" /> // <xsd:enumeration value="variable" /> // <xsd:enumeration value="typedef" /> // <xsd:enumeration value="enum" /> // <xsd:enumeration value="function" /> // <xsd:enumeration value="signal" /> // <xsd:enumeration value="prototype" /> // <xsd:enumeration value="friend" /> // <xsd:enumeration value="dcop" /> // <xsd:enumeration value="slot" /> // <xsd:enumeration value="interface" /> // <xsd:enumeration value="service" /> // </xsd:restriction> // </xsd:simpleType> adjustSectionKind(sectionDef, memberBase, classUnqualifiedName) { // In general, adjust to member kind. let adjustedSectionKind = memberBase.kind; switch (memberBase.kind) { case 'function': // If public/protected/private, preserve the prefix. if (this.isOperator(memberBase.name)) { adjustedSectionKind = sectionDef.computeAdjustedKind('operator'); } else if (classUnqualifiedName !== undefined) { if (memberBase.name === classUnqualifiedName) { adjustedSectionKind = sectionDef.computeAdjustedKind('constructorr'); } else if (memberBase.name.replace('~', '') === classUnqualifiedName) { adjustedSectionKind = sectionDef.computeAdjustedKind('destructor'); } else { adjustedSectionKind = sectionDef.computeAdjustedKind('func', 'function'); } } else { adjustedSectionKind = sectionDef.computeAdjustedKind('func', 'function'); } break; case 'variable': adjustedSectionKind = sectionDef.computeAdjustedKind('attrib', 'variable'); break; case 'typedef': adjustedSectionKind = sectionDef.computeAdjustedKind('type', 'typedef'); break; case 'slot': adjustedSectionKind = sectionDef.computeAdjustedKind('slot'); break; // case 'define': // case 'property': // case 'event': // case 'enum': // case 'signal': // case 'prototype': // case 'friend': // case 'dcop': // case 'interface': // case 'service': default: // Adjust to member kind. adjustedSectionKind = memberBase.kind; break; } // console.log('adjustedSectionKind:', memberBase.kind, adjustedSectionKind) return adjustedSectionKind; } initializeLate() { const workspace = this.collection.workspace; const compoundDef = this._private._compoundDef; assert(compoundDef !== undefined); if (compoundDef.briefDescription !== undefined) { this.briefDescriptionMdxText = workspace.renderElementToMdxText(compoundDef.briefDescription); } if (compoundDef.detailedDescription !== undefined) { this.detailedDescriptionMdxText = workspace.renderElementToMdxText(compoundDef.detailedDescription); for (const child of compoundDef.detailedDescription.children) { if (child instanceof Sect1DataModel) { this.hasSect1InDescription = true; break; } } } if (this.kind === 'page') { // The location for pages is not usable. } else if (this.kind === 'dir') { // The location for folders is not used. } else { if (compoundDef.location !== undefined) { this.locationMdxText = this.renderLocationToMdxText(compoundDef.location); } } if (compoundDef.sectionDefs !== undefined) { for (const sectionDef of compoundDef.sectionDefs) { if (sectionDef.memberDefs !== undefined) { for (const memberDef of sectionDef.memberDefs) { if (memberDef.location !== undefined) { const file = memberDef.location.file; this.locationSet.add(file); if (memberDef.location.bodyfile !== undefined) { this.locationSet.add(memberDef.location.bodyfile); } } } } } } if (compoundDef.includes !== undefined) { this.includes = compoundDef.includes; } for (const innerKey of Object.keys(compoundDef)) { if (innerKey.startsWith('inner')) { if (this.innerCompounds === undefined) { this.innerCompounds = new Map(); } this.innerCompounds.set(innerKey, compoundDef); } } } isOperator(name) { // Two word operators, like if (name.startsWith('operator') && ' =!<>+-*/%&|^~,"(['.includes(name.charAt(8))) { return true; } return false; } // -------------------------------------------------------------------------- renderBriefDescriptionToMdxText({ briefDescriptionMdxText, todo = '', morePermalink }) { let text = ''; if (!this.collection.workspace.pluginOptions.suggestToDoDescriptions) { todo = ''; } if (briefDescriptionMdxText === undefined && todo.length === 0) { return ''; } if (briefDescriptionMdxText !== undefined && briefDescriptionMdxText.length > 0) { text += briefDescriptionMdxText; if (morePermalink !== undefined && morePermalink.length > 0) { text += ` <a href="${morePermalink}">`; text += 'More...'; text += '</a>'; } } else if (todo.length > 0) { text += `TODO: add <code>@brief</code> to <code>${todo}</code>`; } return text; } renderDetailedDescriptionToMdxLines({ briefDescriptionMdxText, detailedDescriptionMdxText, todo = '', showHeader, showBrief = false }) { const lines = []; if (!this.collection.workspace.pluginOptions.suggestToDoDescriptions) { todo = ''; } // const workspace = this.collection.workspace if (showHeader) { if ((detailedDescriptionMdxText !== undefined && detailedDescriptionMdxText.length > 0) || todo.length > 0 || (showBrief && briefDescriptionMdxText !== undefined && briefDescriptionMdxText.length > 0)) { lines.push(''); lines.push('## Description {#details}'); } } if (showBrief) { if (showHeader) { lines.push(''); } if (briefDescriptionMdxText !== undefined && briefDescriptionMdxText.length > 0) { lines.push(briefDescriptionMdxText); } else if (todo.length > 0) { lines.push(`TODO: add <code>@brief</code> to <code>${todo}</code>`); } } // console.log(util.inspect(compoundDef.detailedDescription, { compact: false, depth: 999 })) if (detailedDescriptionMdxText !== undefined && detailedDescriptionMdxText.length > 0) { lines.push(''); lines.push(detailedDescriptionMdxText); } else if (todo.length > 0) { lines.push(''); lines.push(`TODO: add <code>@details</code> to <code>${todo}</code>`); } return lines; } // -------------------------------------------------------------------------- hasInnerIndices() { return (this.innerCompounds !== undefined) && (this.innerCompounds.size > 0); } renderInnerIndicesToMdxLines({ suffixes = [] }) { const lines = []; if (this.innerCompounds !== undefined) { for (const innerKey of Object.keys(this.innerCompounds)) { if (innerKey.startsWith('inner')) { const suffix = innerKey.substring(5); if (!suffixes.includes(suffix)) { console.warn(innerKey, 'not processed for', this.compoundName, 'in renderInnerIndicesMdx'); continue; } } } } const workspace = this.collection.workspace; for (const suffix of suffixes) { const innerKey = `inner${suffix}`; const innerCompound = this.innerCompounds !== undefined ? (this.innerCompounds.get(innerKey)) : undefined; const innerObjects = innerCompound !== undefined ? innerCompound[innerKey] : undefined; if (innerObjects !== undefined && innerObjects.length > 0) { lines.push(''); lines.push(`## ${suffix === 'Dirs' ? 'Folders' : (suffix === 'Groups' ? 'Topics' : suffix)} Index`); lines.push(''); lines.push('<MembersIndex>'); for (const innerObject of innerObjects) { // console.log(util.inspect(innerObject, { compact: false, depth: 999 })) const innerDataObject = workspace.compoundsById.get(innerObject.refid); if (innerDataObject !== undefined) { const kind = innerDataObject.kind; const itemType = kind === 'dir' ? 'folder' : (kind === 'group' ? '&nbsp;' : kind); const permalink = workspace.getPagePermalink(innerObject.refid); const itemName = `<a href="${permalink}">${escapeHtml(innerDataObject.indexName)}</a>`; lines.push(''); lines.push('<MembersIndexItem'); lines.push(` type="${itemType}"`); if (itemName.includes('<') || itemName.includes('&')) { lines.push(` name={<>${itemName}</>}>`); } else { lines.push(` name="${itemName}">`); } const morePermalink = innerDataObject.renderDetailedDescriptionToMdxLines !== undefined ? `${permalink}/#details` : undefined; if (innerDataObject.briefDescriptionMdxText !== undefined && innerDataObject.briefDescriptionMdxText.length > 0) { lines.push(this.renderBriefDescriptionToMdxText({ briefDescriptionMdxText: innerDataObject.briefDescriptionMdxText, morePermalink })); } lines.push('</MembersIndexItem>'); } else if (innerObject instanceof InnerClassDataModel) { lines.push(''); lines.push('<MembersIndexItem'); lines.push(' type="class"'); lines.push(` name="${escapeHtml(innerObject.text)}">`); lines.push('</MembersIndexItem>'); } else { if (this.collection.workspace.pluginOptions.debug) { console.warn(innerObject); } if (this.collection.workspace.pluginOptions.verbose) { console.warn('Object not rendered in renderInnerIndicesToMdxLines()'); } } } lines.push(''); lines.push('</MembersIndex>'); } } return lines; } hasSections() { return (this.sections !== undefined) && (this.sections.length > 0); } renderSectionIndicesToMdxLines() { const lines = []; for (const section of this.sections) { // console.log(sectionDef) lines.push(...section.renderIndexToMdxLines()); } return lines; } // -------------------------------------------------------------------------- renderIncludesIndexToMdxLines() { const lines = []; const workspace = this.collection.workspace; if (this.includes !== undefined) { lines.push(''); lines.push('## Included Headers'); lines.push(''); lines.push('<IncludesList>'); for (const include of this.includes) { lines.push(workspace.renderElementToMdxText(include)); } lines.push('</IncludesList>'); } return lines; } // -------------------------------------------------------------------------- renderSectionsToMdxLines() { const lines = []; if (this.sections !== undefined) { for (const section of this.sections) { lines.push(...section.renderToMdxLines()); } } return lines; } renderLocationToMdxText(location) { let text = ''; const workspace = this.collection.workspace; if (location !== undefined) { // console.log('location.file:', location.file) if (location.file.includes('[')) { // Ignore cases like `[generated]`, encountered in llvm. return text; } const files = workspace.viewModel.get('files'); assert(files !== undefined); // console.log('renderLocationToMdxText', this.kind, this.compoundName, this.id) const file = files.filesByPath.get(location.file); if (file !== undefined) { const permalink = workspace.getPagePermalink(file.id); text += '\n'; if (location.bodyfile !== undefined && location.file !== location.bodyfile) { text += 'Declaration '; if (location.line !== undefined) { text += 'at line '; const lineAttribute = `l${location.line?.toString().padStart(5, '0')}`; if (!file.listingLineNumbers.has(location.line)) { text += location.line?.toString(); } else { text += `<a href="${permalink}/#${lineAttribute}">${escapeMdx(location.line?.toString() ?? '?')}</a>`; } text += ' of file '; } else { text += ' in file '; } text += `<a href="${permalink}">${escapeMdx(path.basename(location.file))}</a>`; const definitionFile = files.filesByPath.get(location.bodyfile); if (definitionFile !== undefined) { const definitionPermalink = workspace.getPagePermalink(definitionFile.id); text += ', definition '; if (location.bodystart !== undefined) { text += 'at line '; const lineStart = `l${location.bodystart?.toString().padStart(5, '0')}`; if (!definitionFile.listingLineNumbers.has(location.bodystart)) { text += location.bodystart?.toString(); } else { text += `<a href="${definitionPermalink}/#${lineStart}">${escapeMdx(location.bodystart?.toString() ?? '?')}</a>`; } text += ' of file '; } else { text += ' in file '; } text += `<a href="${definitionPermalink}">${escapeMdx(path.basename(location.bodyfile))}</a>`; } else { if (this.collection.workspace.pluginOptions.verbose) { console.warn('File', location.bodyfile, 'not a location.'); } } text += '.'; } else { text += 'Definition '; if (location.line !== undefined) { text += 'at line '; const lineAttribute = `l${location.line?.toString().padStart(5, '0')}`; if (!file.listingLineNumbers.has(location.line)) { text += location.line?.toString(); } else { text += `<a href="${permalink}/#${lineAttribute}">${escapeMdx(location.line?.toString() ?? '?')}</a>`; } text += ' of file '; } else { text += ' in file '; } text += `<a href="${permalink}">${escapeMdx(path.basename(location.file))}</a>`; text += '.'; } } else { if (this.collection.workspace.pluginOptions.verbose) { console.warn('File', location.file, 'not a known location.'); } } } return text; } renderGeneratedFromToMdxLines() { const lines = []; if (this.locationSet.size > 0) { lines.push(''); lines.push('<hr/>'); lines.push(''); lines.push(`The documentation for this ${this.kind} was generated from the following file${this.locationSet.size > 1 ? 's' : ''}:`); lines.push(''); lines.push('<ul>'); const workspace = this.collection.workspace; const files = workspace.viewModel.get('files'); const sortedFiles = [...this.locationSet].sort((a, b) => a.localeCompare(b)); for (const fileName of sortedFiles) { // console.log('search', fileName) const file = files.filesByPath.get(fileName); if (file !== undefined) { const permalink = workspace.getPagePermalink(file.id); if (permalink !== undefined && permalink.length > 0) { lines.push(`<li><a href="${permalink}">${path.basename(fileName)}</a></li>`); } else { lines.push(`<li>${path.basename(fileName)}</li>`); } } else { lines.push(`<li>${path.basename(fileName)}</li>`); } } lines.push('</ul>'); } return lines; } // -------------------------------------------------------------------------- /** * Return an array of types, like `class T`, or `class U = T`, or `N T::* MP` * @param templateParamList * @returns */ collectTemplateParameters({ templateParamList, withDefaults = false }) { if (templateParamList?.params === undefined) { return []; } const templateParameters = []; for (const param of templateParamList.params) { // console.log(util.inspect(param, { compact: false, depth: 999 })) assert(param.type !== undefined); let paramString = ''; for (const child of param.type.children) { if (typeof child === 'string') { paramString += child; } else if (child instanceof RefTextDataModel) { paramString += child.text; } } if (param.declname !== undefined) { paramString += ` ${param.declname}`; } if (withDefaults) { if (param.defval !== undefined) { const defval = param.defval; paramString += ' = '; for (const child of defval.children) { if (typeof child === 'string') { paramString += child; } else if (child instanceof RefTextDataModel) { paramString += child.text; } } } } templateParameters.push(paramString); } return templateParameters; } isTemplate(templateParamList) { return (templateParamList?.params ?? []).length > 0; } collectTemplateParameterNames(templateParamList) { if (templateParamList?.params === undefined) { return []; } const templateParameterNames = []; for (const param of templateParamList.params) { // console.log(util.inspect(param, { compact: false, depth: 999 })) assert(param.type !== undefined); let paramString = ''; // declname? defname? order? if (param.declname !== undefined) { paramString += param.declname; } else { for (const child of param.type.children) { if (typeof child === 'string') { // Extract the parameter name, passed as `class T`. paramString += child; } else if (child instanceof RefTextDataModel) { paramString += child.text; } } } const paramName = paramString.replaceAll(/class /g, '').replaceAll(/typename /g, ''); templateParameterNames.push(paramName); } return templateParameterNames; } renderTemplateParametersToMdxText({ templateParamList, withDefaults = false }) { let text = ''; if (templateParamList?.params !== undefined) { const templateParameters = this.collectTemplateParameters({ templateParamList, withDefaults }); if (templateParameters.length > 0) { text += `<${templateParameters.join(', ')}>`; } } return text; } renderTemplateParameterNamesToMdxText(templateParamList) { let text = ''; if (templateParamList?.params !== undefined) { const templateParameterNames = this.collectTemplateParameterNames(templateParamList); if (templateParameterNames.length > 0) { text += `<${templateParameterNames.join(', ')}>`; } } return text; } } // ---------------------------------------------------------------------------- //# sourceMappingURL=compound-base-vm.js.map