Skip to content

Creating a Plugin

Atscript plugins extend the language with custom primitives, annotations, and code generators. A plugin is a plain object that implements the TAtscriptPlugin interface — a name plus optional hooks that participate in the Atscript processing pipeline.

What Plugins Can Do

CapabilityHookExample
Add semantic types (primitives)config()mongo.objectId, mongo.vector
Define annotation specsconfig()@mongo.collection, @mongo.index.unique
Remap or virtualize module pathsresolve()Path aliases, virtual modules
Provide virtual file contentload()Synthetic .as modules
Post-process parsed documentsonDocument()Inject virtual props, run custom checks
Generate output filesrender().d.ts, .js, .py, .json — any format
Aggregate across all documentsbuildEnd()Global type declarations, indexes

Your First Plugin

Here's a minimal plugin that adds a @ui.hidden annotation:

typescript
import { createAtscriptPlugin, AnnotationSpec } from '@atscript/core'

export const uiPlugin = () => createAtscriptPlugin({
  name: 'ui',
  config() {
    return {
      annotations: {
        ui: {
          hidden: new AnnotationSpec({
            description: 'Hide this field in the UI',
            nodeType: ['prop'],
          }),
        },
      },
    }
  },
})

createAtscriptPlugin is a type-safe identity function — it returns the object you pass in, but gives you full TypeScript IntelliSense on the hook signatures.

Registering Your Plugin

Add the plugin to your atscript.config.ts:

typescript
import { defineConfig } from '@atscript/core'
import { tsPlugin } from '@atscript/typescript'
import { uiPlugin } from './plugins/ui-plugin'

export default defineConfig({
  rootDir: 'src',
  plugins: [tsPlugin(), uiPlugin()],
})

Plugins execute in array order. Each plugin's config() output is merged with the accumulated config using deep defaults (defu), so multiple plugins can contribute primitives and annotations without conflicts.

The TAtscriptPlugin Interface

typescript
interface TAtscriptPlugin {
  name: string

  config?(config: TAtscriptConfig): TAtscriptConfig | undefined
  resolve?(id: string): string | undefined
  load?(id: string): string | undefined
  onDocument?(doc: AtscriptDoc): void
  render?(doc: AtscriptDoc, format: string): TPluginOutput[]
  buildEnd?(output: TOutput[], format: string, repo: AtscriptRepo): void
}

All hooks except name are optional. A plugin can implement any combination — from a simple annotation-only plugin (just config()) to a full language extension with code generation (config() + render() + buildEnd()).

Guide Roadmap

This guide walks you through plugin development from simple to complex:

  1. Plugin Architecture — The processing pipeline, AST node types, plugin lifecycle, and the document API you'll use throughout.

  2. Custom Primitives — Adding semantic types with validation constraints, complex type definitions, and inheritance.

  3. Custom Annotations — Defining annotation specs with typed arguments, custom validation, merge strategies, and AST modification.

  4. Building a Code Generator — Writing a render() hook to generate output files, walking the AST, resolving types, and building a complete code generator from scratch.

  5. Plugin Hooks Reference — Complete reference for all six hooks with signatures, execution order, and examples.

  6. Validation Specification — Language-agnostic spec for implementing data validation: type dispatch, constraint annotations, optional vs required, error reporting.

  7. Testing Plugins — Test setup with Vitest, snapshot testing generated output, and testing diagnostics.

  8. VSCode & Build Integration — How plugins integrate with the CLI, build tools, and VSCode on-save generation.

Prerequisites

  • Familiarity with Atscript syntax (interfaces, types, annotations)
  • A working TypeScript development environment
  • @atscript/core as a dependency (the only required package for plugin development)

Released under the ISC License.