singleton_manager_generator 2.0.0
singleton_manager_generator: ^2.0.0 copied to clipboard
CLI tool that generates dependencyInjectionFactory() constructors for @dependencyInjectable classes.
singleton_manager_generator #
A CLI tool that generates a dependencyInjectionFactory() constructor for classes annotated with @dependencyInjectable, writing it directly into the annotated class itself.
Features #
- Scans Dart source files for classes annotated with
@dependencyInjectable - Reads the class's default (unnamed) constructor and generates a matching
factory <ClassName>.dependencyInjectionFactory() - Each constructor parameter is resolved from the
RegistryManager—getInstance<T>()for non-nullable types,getInstanceNullable<T>()for nullable ones - Preserves the original parameter shape: positional-required, positional-optional (
[...]), or named (optionallyrequired) - Resolves the type of field-formal parameters (
this._field) with no explicit type from the matching field declaration - Writes the factory right after the default constructor, directly in the source file — no separate
_di.dartfile, and both constructors stay grouped before any field/method (satisfying thesort_constructors_firstlint) - Every generated line carries a trailing
// GENERATED CODE - DO NOT MODIFY BY HANDcomment — not just a header above the block, each line individually - Idempotent: running the generator again fully replaces the previously generated block (factory + trailing members) — whether the constructor's parameters changed shape or the factory had been left in the wrong place, no duplication
- Automatically inserts the
singleton_managerimport if it's missing - Minimal overhead — uses Dart's
analyzerpackage for lightweight AST parsing
Installation #
Add the packages to your pubspec.yaml:
dependencies:
singleton_manager: ^2.0.0
dev_dependencies:
singleton_manager_generator: ^2.0.0
Global Installation #
To install singleton_manager_generator globally and use it across multiple projects:
dart pub global activate singleton_manager_generator
Then use it directly from anywhere:
singleton_manager_generator --input lib
To update the global installation:
dart pub global activate singleton_manager_generator
To deactivate the global installation:
dart pub global deactivate singleton_manager_generator
Usage #
1. Annotate your class #
import 'package:singleton_manager/singleton_manager.dart';
abstract class IPluto {}
abstract class IPaperino {}
abstract class ITopolino {}
@dependencyInjectable
class Pippo {
IPluto _pluto;
IPaperino? _paperino;
ITopolino? _topolino;
Pippo(
this._pluto, {
IPaperino? paperino,
ITopolino? topolino,
}) {
_paperino = paperino;
_topolino = topolino;
}
}
2. Run the generator #
dart run singleton_manager_generator --input lib
Or use melos if set up in your workspace:
melos run generate
3. The generated factory is written directly into your class #
@dependencyInjectable
class Pippo {
IPluto _pluto;
IPaperino? _paperino;
ITopolino? _topolino;
Pippo(
this._pluto, {
IPaperino? paperino,
ITopolino? topolino,
}) {
_paperino = paperino;
_topolino = topolino;
}
factory Pippo.dependencyInjectionFactory() { // GENERATED CODE - DO NOT MODIFY BY HAND
final pluto = RegistryManager.instance.getInstance<IPluto>(); // GENERATED CODE - DO NOT MODIFY BY HAND
final paperino = RegistryManager.instance.getInstanceNullable<IPaperino>(); // GENERATED CODE - DO NOT MODIFY BY HAND
final topolino = RegistryManager.instance.getInstanceNullable<ITopolino>(); // GENERATED CODE - DO NOT MODIFY BY HAND
return Pippo( // GENERATED CODE - DO NOT MODIFY BY HAND
pluto, // GENERATED CODE - DO NOT MODIFY BY HAND
paperino: paperino, // GENERATED CODE - DO NOT MODIFY BY HAND
topolino: topolino, // GENERATED CODE - DO NOT MODIFY BY HAND
); // GENERATED CODE - DO NOT MODIFY BY HAND
} // GENERATED CODE - DO NOT MODIFY BY HAND
}
The factory is always inserted (or, on a re-run, fully replaced) right
after the default constructor — before any field or method declared after
it, which get shifted down right after the factory, unchanged. Every
non-blank generated line carries the trailing // GENERATED CODE - DO NOT MODIFY BY HAND marker shown above.
Use it once the dependencies are connected:
final registry = RegistryManager.instance;
registry.connectInstance<IPluto, Pluto>(Pluto.dependencyInjectionFactory);
registry.connectInstance<IPaperino, Paperino>(Paperino.dependencyInjectionFactory);
registry.connectInstance<ITopolino, Topolino>(Topolino.dependencyInjectionFactory);
final pippo = Pippo.dependencyInjectionFactory();
How parameters map to the factory #
| Constructor parameter shape | Generated factory behavior |
|---|---|
Non-nullable type (IPluto _pluto) |
RegistryManager.instance.getInstance<IPluto>() |
Nullable type (IPaperino? paperino) |
RegistryManager.instance.getInstanceNullable<IPaperino>() |
| Positional-required | Passed positionally |
Positional-optional ([...]) |
Passed positionally |
Named ({...}), optionally required |
Passed by name: name: localVar |
Field-formal param with no explicit type (this._field) |
Type looked up from the matching field declaration |
CLI Options #
--input, -i Input directory containing source Dart files (default: lib)
--output, -o Output directory to write the rewritten files to (default: same as input — in-place rewrite)
--registry-output, -r Write a .dart file at this path that registers every discovered singleton into RegistryManager (skipped if omitted)
--verbose, -v Enable verbose logging
--help, -h Show help message
By default (no --output, or --output equal to --input), the generator rewrites the actual source files in place.
Generating a registry file #
Pass --registry-output to also generate a .dart file that wires every discovered @dependencyInjectable class into RegistryManager, so you don't have to hand-write the connectInstance calls:
dart run singleton_manager_generator --input lib --registry-output lib/singleton_registry.g.dart
For the Pippo/IPluto/IPaperino/ITopolino example above, this produces:
// GENERATED CODE - DO NOT MODIFY BY HAND
import 'package:singleton_manager/singleton_manager.dart'; // GENERATED CODE - DO NOT MODIFY BY HAND
import 'paperino.dart'; // GENERATED CODE - DO NOT MODIFY BY HAND
import 'pippo.dart'; // GENERATED CODE - DO NOT MODIFY BY HAND
import 'pluto.dart'; // GENERATED CODE - DO NOT MODIFY BY HAND
import 'topolino.dart'; // GENERATED CODE - DO NOT MODIFY BY HAND
/// Connects every `@dependencyInjectable` class discovered under the scanned
/// input directory to `RegistryManager.instance`, using each generated
/// `dependencyInjectionFactory()` as the connected factory.
void registerAllSingletons() { // GENERATED CODE - DO NOT MODIFY BY HAND
RegistryManager.instance // GENERATED CODE - DO NOT MODIFY BY HAND
..connectInstance<IPluto, Pluto>(Pluto.dependencyInjectionFactory) // GENERATED CODE - DO NOT MODIFY BY HAND
..connectInstance<IPaperino, Paperino>(Paperino.dependencyInjectionFactory) // GENERATED CODE - DO NOT MODIFY BY HAND
..connectInstance<ITopolino, Topolino>(Topolino.dependencyInjectionFactory) // GENERATED CODE - DO NOT MODIFY BY HAND
..connectInstance<Pippo, Pippo>(Pippo.dependencyInjectionFactory); // GENERATED CODE - DO NOT MODIFY BY HAND
} // GENERATED CODE - DO NOT MODIFY BY HAND
(imports are sorted alphabetically and deduped; when no classes are
discovered, the function body is just a // No @dependencyInjectable classes were discovered. comment — no unused variable is emitted.)
Call registerAllSingletons() once at startup instead of connecting each type by hand. The type each class is registered under is the first interface in its implements clause; a class implementing nothing is registered under its own type. Import paths are computed relative to the registry file's own location, so it can be placed anywhere.
Requirements #
- Dart SDK >= 3.11.0
package:singleton_manager^2.0.0 (for@dependencyInjectableandRegistryManager)package:analyzerfor AST parsingpackage:argsfor CLI argument handlingpackage:pathfor path utilities
How it works #
- Parsing: Scans Dart files using the
analyzerpackage to find classes annotated with@dependencyInjectableand their default constructor's parameters - Generation: Builds a
factory <ClassName>.dependencyInjectionFactory()body that resolves each parameter fromRegistryManager.instanceand forwards it to the constructor - Rewriting: Inserts (or fully replaces, on a re-run) the factory right after the default constructor, and ensures the
singleton_managerimport is present - Output: Writes the rewritten file back to disk — in place by default, or under
--outputif given
Example Project #
See example/my_service.dart and the test/ directory for further examples.