Skip to main content

Plugin API

Zorphy 2.0 introduces a Plugin API that lets third-party code inspect and mutate the generated code_builder specs before they are emitted. This is the foundation of the Extensible Compiler Pipeline.

How It Works

The code generation pipeline runs in these phases:

  1. AnalysisClassAnalyzer produces ClassMetadata from the annotated element.
  2. Generation — Built-in generators produce Spec objects (Class, Method, Extension, etc.).
  3. Plugin Transform (new) — Registered plugins mutate the specs in topological order.
  4. Emission — The mutated specs are assembled into a Library and emitted once via ZorphyEmitter.
┌──────────┐    ┌──────────────┐    ┌──────────────┐    ┌──────────────┐
│ Analysis │───>│ Generation │───>│Plugin Pass │───>│ Emission │
└──────────┘ └──────────────┘ └──────────────┘ └──────────────┘

┌───────┴────────┐
│ Plugin A │
│ Plugin B │
│ Plugin C │
└────────────────┘

Writing a Plugin

Extend ZorphyPlugin and implement one or more transform hooks:

import 'package:code_builder/code_builder.dart';
import 'package:zorphy/zorphy_plugin.dart';

class TimestampPlugin extends ZorphyPlugin {

String get name => 'timestamp';


Set<String> get runAfter => const {'logging'};


Spec transformClass(Spec spec, PluginContext context) {
if (spec is! Class) return spec;

// Rebuild the class with an additional field.
return spec.rebuild((c) => c
..fields.add(
Field((f) {
f.name = 'generatedAt';
f.type = refer('DateTime');
f.modifier = FieldModifier.final$;
}),
));
}
}

Plugin Contract

ZorphyPlugin

MemberTypeDescription
nameStringUnique identifier for ordering and registration.
decoratorNamesSet<String>Which decorator names this plugin reacts to. Empty = runs for every class.
runBeforeSet<String>Plugin names that must run after this one.
runAfterSet<String>Plugin names that must run before this one.
transformClassSpec FunctionMutate a Class spec. Return the (possibly replaced) spec.
transformMethodSpec FunctionMutate a Method spec. Called for each method in a class.
transformFieldSpec FunctionMutate a Field spec. Called for each field in a class.

All transform hooks have pass-through defaults — override only the ones you need.

PluginContext

Passed to every transform hook. Provides:

MethodDescription
addImport(uri, {asName, showNames, hideNames})Add an import directive to the output library.
diagnostic(message, {level})Record a diagnostic (info / warning / error).
importsRead accumulated import directives.
diagnosticsRead accumulated diagnostics.
metadataThe ClassMetadata for the class being transformed.
configThe GenerationConfig (preset, flags).

Plugin Ordering

Plugins are executed in topological order determined by runBefore / runAfter constraints, using Kahn's algorithm with registration-order fallback for unordered plugins.

// A runs before B, B runs before C.
final registry = PluginRegistry();
registry.register(PluginA()); // runBefore: {'B'}
registry.register(PluginB()); // runBefore: {'C'}
registry.register(PluginC());

print(registry.ordered().map((p) => p.name).toList());
// [A, B, C]

Unknown names in runBefore/runAfter are treated as no-ops. Cycles do not crash — remaining plugins emit in registration order.

Registration via build.yaml

Add plugin URIs to the builder options:

targets:
$default:
builders:
zorphy:zorphy:
options:
plugins:
- package:my_plugin/my_plugin.dart

The builder reads the plugins list and passes the URIs to ZorphyGenerator. When no plugins option is present, output is byte-identical to the pre-plugin pipeline — zero regressions.

Programmatic Registration

For tests or custom pipelines, register plugins directly:

import 'package:zorphy/zorphy_plugin.dart';

final registry = PluginRegistry();
registry.register(TimestampPlugin());

// Pass to ZorphyGenerator:
final generator = ZorphyGenerator(pluginRegistry: registry);

v2.1 Extension Surface

The following hooks are planned for v2.1 and are documented in ZorphyPlugin's doc comments as anchor points:

  • transformExtension — mutate Extension specs
  • transformConstructor — mutate Constructor specs
  • transformLibrary — mutate the Library spec itself
  • onGenerateStart / onGenerateEnd — lifecycle hooks for side effects (logging, metrics)

Example

See example/lib/plugin_example.dart for a compilable no-op plugin that demonstrates the full contract.