Skip to main content
The plugin system provides a language-agnostic way to add static analysis capabilities to Understand Anything. Each analyzer plugin implements a common interface and is responsible for a set of programming languages. The core engine delegates all file analysis to whichever plugin claims that language.

The AnalyzerPlugin Interface

Every analyzer plugin must implement the AnalyzerPlugin interface defined in packages/core/src/types.ts:
export interface AnalyzerPlugin {
  name: string;
  languages: string[];
  analyzeFile(filePath: string, content: string): StructuralAnalysis;
  resolveImports(filePath: string, content: string): ImportResolution[];
  extractCallGraph?(filePath: string, content: string): CallGraphEntry[];
}
MemberRequiredDescription
nameYesUnique identifier for the plugin (e.g. "tree-sitter")
languagesYesList of language names this plugin handles (e.g. ["typescript", "javascript"])
analyzeFileYesExtracts functions, classes, imports, and exports from a file
resolveImportsYesResolves import paths to absolute or package identifiers
extractCallGraphNoOptional — extracts caller/callee relationships

Supporting Types

The three return types used by AnalyzerPlugin are also defined in packages/core/src/types.ts:
export interface StructuralAnalysis {
  functions: Array<{
    name: string;
    lineRange: [number, number];
    params: string[];
    returnType?: string;
  }>;
  classes: Array<{
    name: string;
    lineRange: [number, number];
    methods: string[];
    properties: string[];
  }>;
  imports: Array<{ source: string; specifiers: string[]; lineNumber: number }>;
  exports: Array<{ name: string; lineNumber: number }>;
}

export interface ImportResolution {
  source: string;
  resolvedPath: string;
  specifiers: string[];
}

export interface CallGraphEntry {
  caller: string;
  callee: string;
  lineNumber: number;
}

Plugin Discovery and Configuration

Plugin configuration lives in a PluginConfig object. The types are defined in packages/core/src/plugins/discovery.ts:
export interface PluginEntry {
  name: string;
  enabled: boolean;
  languages: string[];
  options?: Record<string, unknown>;
}

export interface PluginConfig {
  plugins: PluginEntry[];
}
The built-in default config enables the tree-sitter plugin for TypeScript and JavaScript:
export const DEFAULT_PLUGIN_CONFIG: PluginConfig = {
  plugins: [
    {
      name: "tree-sitter",
      enabled: true,
      languages: ["typescript", "javascript"],
    },
  ],
};
Two helpers are exported to load and save config:
// Parse a JSON string into PluginConfig (returns DEFAULT_PLUGIN_CONFIG on failure)
parsePluginConfig(jsonString: string): PluginConfig

// Serialize a PluginConfig to a JSON string for saving
serializePluginConfig(config: PluginConfig): string

PluginRegistry

PluginRegistry (defined in packages/core/src/plugins/registry.ts) is the central store that maps languages to plugins. The file analyzer and the context-builder both go through this registry.
export class PluginRegistry {
  register(plugin: AnalyzerPlugin): void;
  unregister(name: string): void;
  getPluginForLanguage(language: string): AnalyzerPlugin | null;
  getPluginForFile(filePath: string): AnalyzerPlugin | null;
  analyzeFile(filePath: string, content: string): StructuralAnalysis | null;
  resolveImports(filePath: string, content: string): ImportResolution[] | null;
  getPlugins(): AnalyzerPlugin[];
  getSupportedLanguages(): string[];
}

Language-to-extension mapping

The registry maps file extensions to language names using a built-in table:
Extension(s)Language
.ts, .tsxtypescript
.js, .jsxjavascript
.pypython
.gogo
.rsrust
.rbruby
.javajava
.ktkotlin
.cscsharp
.cppcpp
.cc
.swiftswift
.phpphp
If you register a plugin for any of these languages, getPluginForFile will route files with the matching extension to your plugin automatically.

Creating a Custom Analyzer Plugin

1

Implement the AnalyzerPlugin interface

Create a class (or plain object) that satisfies AnalyzerPlugin:
import type {
  AnalyzerPlugin,
  StructuralAnalysis,
  ImportResolution,
  CallGraphEntry,
} from '@understand-anything/core/types';

export class MyLanguagePlugin implements AnalyzerPlugin {
  readonly name = 'my-language';
  readonly languages = ['mylang'];

  analyzeFile(filePath: string, content: string): StructuralAnalysis {
    // Parse `content` and extract functions, classes, imports, exports.
    return {
      functions: [],
      classes: [],
      imports: [],
      exports: [],
    };
  }

  resolveImports(filePath: string, content: string): ImportResolution[] {
    const analysis = this.analyzeFile(filePath, content);
    return analysis.imports.map((imp) => ({
      source: imp.source,
      resolvedPath: imp.source, // resolve relative paths as needed
      specifiers: imp.specifiers,
    }));
  }

  // Optional
  extractCallGraph(filePath: string, content: string): CallGraphEntry[] {
    return [];
  }
}
2

Register the plugin with PluginRegistry

Instantiate your plugin and register it before running any analysis:
import { PluginRegistry } from '@understand-anything/core';
import { MyLanguagePlugin } from './my-language-plugin.js';

const registry = new PluginRegistry();
const plugin = new MyLanguagePlugin();

// If your plugin needs async initialization (e.g. loading WASM)
// await plugin.init();

registry.register(plugin);
3

Use the registry to analyze files

Once registered, the registry automatically routes files to the correct plugin based on file extension:
import { readFileSync } from 'node:fs';

const content = readFileSync('/path/to/file.mylang', 'utf-8');

const analysis = registry.analyzeFile('/path/to/file.mylang', content);
// Returns null if no plugin is registered for the file extension

const imports = registry.resolveImports('/path/to/file.mylang', content);
Plugin names must be unique within a registry. Registering a second plugin with the same name will not replace the first — each language key in languageMap is overwritten, but both plugin entries remain in the internal list. Use unregister(name) first if you need to replace a plugin.

The Built-in TreeSitterPlugin

The built-in plugin (packages/core/src/plugins/tree-sitter-plugin.ts) covers TypeScript, TSX, and JavaScript using web-tree-sitter (WASM). Key characteristics:
  • Requires an async init() call to load the WASM runtime and grammar files. After that, analyzeFile, resolveImports, and extractCallGraph are all synchronous.
  • Implements the optional extractCallGraph method to track caller/callee relationships at the function level.
  • Uses createRequire to resolve .wasm grammar paths from inside an ESM module.
import { TreeSitterPlugin } from '@understand-anything/core';

const plugin = new TreeSitterPlugin();
await plugin.init(); // Must be awaited once before any analysis

const registry = new PluginRegistry();
registry.register(plugin);
Always await TreeSitterPlugin.init() before registering it or calling any analysis methods. Calling analyzeFile before init() completes throws an error.

Build docs developers (and LLMs) love