Extension points for custom parsers, workers, filters, and language support. The plugin system allows customization of the documentation generation process through hooks and custom components.
interface App {
addHook(name: string, func: Function, priority?: number): void;
hook(name: string, ...args: any[]): any;
filters: Record<string, string>;
languages: Record<string, string>;
parsers: Record<string, string>;
workers: Record<string, string>;
options: ApiDocOptions;
log: Logger;
markdownParser: MarkdownParser;
}The App object provides the main interface for plugin registration and execution.
addHook(name: string, func: Function, priority?: number): void;Registers a hook function for a specific event.
Parameters:
name (string): Hook name identifierfunc (Function): Callback function to executepriority (number, optional): Execution priority (lower numbers execute first, default: 100)hook(name: string, ...args: any[]): any;Executes all registered hooks for a specific event.
Parameters:
name (string): Hook name to execute...args (any[]): Arguments to pass to hook functionsReturns: Modified first argument or original value
parser-find-elements: Called when discovering API elements in source code
app.addHook('parser-find-elements', function(elements, filename) {
// Modify or filter discovered elements
return elements;
});parser-find-element-{type}: Called for specific element types
app.addHook('parser-find-element-api', function(element, filename) {
// Modify specific API elements
return element;
});worker-start: Called before worker processing begins
app.addHook('worker-start', function(parsedFiles, filenames, packageInfos) {
// Pre-process parsed data
});worker-end: Called after worker processing completes
app.addHook('worker-end', function(parsedFiles, filenames, packageInfos) {
// Post-process parsed data
});writer-start: Called before output generation
app.addHook('writer-start', function(api, app) {
// Modify API data before output
});writer-end: Called after output generation
app.addHook('writer-end', function(outputPath, app) {
// Post-process generated files
});interface Parser {
parse(content: string, source: string, filename: string): any;
path: string;
method: string;
}Custom parsers handle specific API documentation tags.
Example Parser:
// custom-parser.js
function parse(content, source, filename) {
const result = {};
// Extract custom tag information
const match = content.match(/@apiCustomTag\s+(.+)/);
if (match) {
result.customData = match[1].trim();
}
return result;
}
module.exports = {
parse: parse,
path: 'local.custom',
method: 'push'
};Registration:
const apidoc = require('apidoc');
const result = apidoc.createDoc({
src: ['./src'],
dest: './docs',
parsers: {
'apicustomtag': './custom-parser.js'
}
});interface Worker {
process(parsedFiles: any[], filenames: string[], packageInfos: any): void;
}Workers post-process parsed API data.
Example Worker:
// custom-worker.js
function process(parsedFiles, filenames, packageInfos) {
parsedFiles.forEach(file => {
file.forEach(element => {
if (element.type === 'api' && element.customData) {
// Process custom data
element.processedCustomData = processCustomData(element.customData);
}
});
});
}
function processCustomData(data) {
// Custom processing logic
return data.toUpperCase();
}
module.exports = {
process: process
};Registration:
const result = apidoc.createDoc({
src: ['./src'],
dest: './docs',
workers: {
'customprocessor': './custom-worker.js'
}
});interface Filter {
postFilter(parsedFiles: any[], filenames: string[]): any[];
}Filters clean up and validate parsed data.
Example Filter:
// custom-filter.js
function postFilter(parsedFiles, filenames) {
const filteredFiles = [];
parsedFiles.forEach(file => {
const filteredFile = file.filter(element => {
// Custom filtering logic
return element.type === 'api' && element.name;
});
if (filteredFile.length > 0) {
filteredFiles.push(filteredFile);
}
});
return filteredFiles;
}
module.exports = {
postFilter: postFilter
};interface Language {
docBlocksRegExp: RegExp;
inlineRegExp: RegExp;
}Language parsers define comment patterns for different programming languages.
Example Language:
// custom-language.js
module.exports = {
// Multi-line comment blocks
docBlocksRegExp: /\/\*\*([\s\S]*?)\*\//g,
// Single-line comments
inlineRegExp: /^(\s*)\*\s?(.*)$/gm
};Registration:
const result = apidoc.createDoc({
src: ['./src'],
dest: './docs',
languages: {
'.customext': './custom-language.js'
}
});// validation-plugin.js
function validateApiElements(elements, filename) {
elements.forEach(element => {
if (element.type === 'api') {
// Validate required fields
if (!element.name) {
throw new Error(`API element missing name in ${filename}`);
}
if (!element.group) {
throw new Error(`API element "${element.name}" missing group in ${filename}`);
}
if (!element.title) {
element.title = element.name; // Auto-generate title
}
}
});
return elements;
}
function registerValidationPlugin(app) {
app.addHook('parser-find-elements', validateApiElements, 10);
}
module.exports = registerValidationPlugin;// metrics-plugin.js
const metrics = {
totalEndpoints: 0,
groupCounts: {},
versionCounts: {}
};
function collectMetrics(elements, filename) {
elements.forEach(element => {
if (element.type === 'api') {
metrics.totalEndpoints++;
// Count by group
const group = element.group || 'Unknown';
metrics.groupCounts[group] = (metrics.groupCounts[group] || 0) + 1;
// Count by version
const version = element.version || 'Unknown';
metrics.versionCounts[version] = (metrics.versionCounts[version] || 0) + 1;
}
});
return elements;
}
function outputMetrics(outputPath, app) {
const metricsPath = require('path').join(outputPath, 'metrics.json');
require('fs').writeFileSync(metricsPath, JSON.stringify(metrics, null, 2));
app.log.info(`Metrics saved to ${metricsPath}`);
}
function registerMetricsPlugin(app) {
app.addHook('parser-find-elements', collectMetrics, 50);
app.addHook('writer-end', outputMetrics, 100);
}
module.exports = registerMetricsPlugin;// security-plugin.js
const sensitivePatterns = [
/password/i,
/secret/i,
/token/i,
/key/i,
/auth/i
];
function markSensitiveParameters(elements, filename) {
elements.forEach(element => {
if (element.parameter) {
element.parameter.forEach(param => {
const isSensitive = sensitivePatterns.some(pattern =>
pattern.test(param.field) || pattern.test(param.description || '')
);
if (isSensitive) {
param.sensitive = true;
param.description = `⚠️ ${param.description || ''} (Sensitive data)`.trim();
}
});
}
});
return elements;
}
function registerSecurityPlugin(app) {
app.addHook('parser-find-elements', markSensitiveParameters, 20);
}
module.exports = registerSecurityPlugin;const apidoc = require('apidoc');
const validationPlugin = require('./validation-plugin');
// Register plugin via hook
const app = {
addHook: (name, func, priority) => {
// Hook registration logic
}
};
validationPlugin(app);
const result = apidoc.createDoc({
src: ['./src'],
dest: './docs'
});const apidoc = require('apidoc');
const validationPlugin = require('./validation-plugin');
const metricsPlugin = require('./metrics-plugin');
const securityPlugin = require('./security-plugin');
// Plugin configuration
const plugins = [
validationPlugin,
metricsPlugin,
securityPlugin
];
// Register all plugins
plugins.forEach(plugin => plugin(app));
const result = apidoc.createDoc({
src: ['./src'],
dest: './docs'
});// apidoc.config.js
module.exports = {
name: 'My API',
version: '1.0.0',
input: ['src'],
output: 'docs',
// Custom plugin configuration
plugins: [
{
name: 'validation',
path: './plugins/validation-plugin.js',
options: {
strictMode: true,
requiredFields: ['name', 'group', 'description']
}
},
{
name: 'metrics',
path: './plugins/metrics-plugin.js',
options: {
outputPath: './metrics',
includeTimestamps: true
}
}
]
};function safeParserHook(elements, filename) {
try {
// Plugin logic here
return processElements(elements);
} catch (error) {
console.error(`Plugin error in ${filename}:`, error.message);
return elements; // Return original elements on error
}
}function efficientHook(elements, filename) {
// Early return for empty elements
if (!elements || elements.length === 0) {
return elements;
}
// Batch processing for large datasets
const batchSize = 100;
for (let i = 0; i < elements.length; i += batchSize) {
const batch = elements.slice(i, i + batchSize);
processBatch(batch);
}
return elements;
}// plugin-test.js
const assert = require('assert');
const validationPlugin = require('./validation-plugin');
describe('Validation Plugin', () => {
it('should validate API elements', () => {
const mockApp = {
hooks: {},
addHook(name, func, priority) {
this.hooks[name] = func;
}
};
validationPlugin(mockApp);
const elements = [
{ type: 'api', name: 'GetUser', group: 'User' }
];
const result = mockApp.hooks['parser-find-elements'](elements, 'test.js');
assert.equal(result.length, 1);
assert.equal(result[0].title, 'GetUser');
});
});