or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

cli.mdcomment-tags.mdconfiguration.mdcore-generation.mdindex.mdoutput-generation.mdparsing.mdplugins.md
tile.json

plugins.mddocs/

Plugin System

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.

Core Plugin Interface

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.

Hook System

addHook

addHook(name: string, func: Function, priority?: number): void;

Registers a hook function for a specific event.

Parameters:

  • name (string): Hook name identifier
  • func (Function): Callback function to execute
  • priority (number, optional): Execution priority (lower numbers execute first, default: 100)

hook

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 functions

Returns: Modified first argument or original value

Available Hooks

Parsing Hooks

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;
});

Processing Hooks

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
});

Output Hooks

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
});

Custom Components

Custom Parsers

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'
  }
});

Custom Workers

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'
  }
});

Custom Filters

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
};

Custom Languages

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'
  }
});

Plugin Examples

Validation Plugin

// 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

// 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

// 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;

Plugin Usage

Single Plugin

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'
});

Multiple Plugins

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'
});

Configuration-Based Plugins

// 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
      }
    }
  ]
};

Plugin Development Best Practices

Error Handling

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
  }
}

Performance Considerations

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;
}

Testing Plugins

// 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');
  });
});