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. Analysis — ClassAnalyzer 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.