mirror of
https://github.com/pese-git/cherrypick.git
synced 2026-01-24 13:47:24 +00:00
Compare commits
12 Commits
cherrypick
...
talker_che
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
99e662124f | ||
|
|
03f54981f3 | ||
|
|
349efe6ba6 | ||
|
|
c2f0e027b6 | ||
|
|
f85036d20f | ||
|
|
db4d128d04 | ||
|
|
2c4e2ed251 | ||
|
|
7b4642f407 | ||
|
|
7d45d00d6a | ||
|
|
884df50a34 | ||
|
|
5710af2f9b | ||
|
|
9312ef46ea |
83
CHANGELOG.md
83
CHANGELOG.md
@@ -3,6 +3,89 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## 2025-08-13
|
||||||
|
|
||||||
|
### Changes
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Packages with breaking changes:
|
||||||
|
|
||||||
|
- There are no breaking changes in this release.
|
||||||
|
|
||||||
|
Packages with other changes:
|
||||||
|
|
||||||
|
- [`talker_cherrypick_logger` - `v1.1.0-dev.3`](#talker_cherrypick_logger---v110-dev3)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### `talker_cherrypick_logger` - `v1.1.0-dev.3`
|
||||||
|
|
||||||
|
|
||||||
|
## 2025-08-13
|
||||||
|
|
||||||
|
### Changes
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Packages with breaking changes:
|
||||||
|
|
||||||
|
- There are no breaking changes in this release.
|
||||||
|
|
||||||
|
Packages with other changes:
|
||||||
|
|
||||||
|
- [`talker_cherrypick_logger` - `v1.1.0-dev.2`](#talker_cherrypick_logger---v110-dev2)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### `talker_cherrypick_logger` - `v1.1.0-dev.2`
|
||||||
|
|
||||||
|
- Bump "talker_cherrypick_logger" to `1.1.0-dev.2`.
|
||||||
|
|
||||||
|
|
||||||
|
## 2025-08-13
|
||||||
|
|
||||||
|
### Changes
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Packages with breaking changes:
|
||||||
|
|
||||||
|
- [`cherrypick_generator` - `v2.0.0-dev.0`](#cherrypick_generator---v200-dev0)
|
||||||
|
|
||||||
|
Packages with other changes:
|
||||||
|
|
||||||
|
- [`cherrypick` - `v3.0.0-dev.9`](#cherrypick---v300-dev9)
|
||||||
|
- [`cherrypick_annotations` - `v1.1.2-dev.0`](#cherrypick_annotations---v112-dev0)
|
||||||
|
- [`cherrypick_flutter` - `v1.1.3-dev.9`](#cherrypick_flutter---v113-dev9)
|
||||||
|
- [`talker_cherrypick_logger` - `v1.1.0-dev.0`](#talker_cherrypick_logger---v110-dev0)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### `cherrypick_generator` - `v2.0.0-dev.0`
|
||||||
|
|
||||||
|
- **BREAKING** **DOCS**(generator): improve and unify English documentation and examples for all DI source files.
|
||||||
|
|
||||||
|
#### `cherrypick` - `v3.0.0-dev.9`
|
||||||
|
|
||||||
|
- **DOCS**(readme): add talker_cherrypick_logger to Additional Modules section.
|
||||||
|
- **DOCS**(api): improve all DI core code documentation with English dartdoc and examples.
|
||||||
|
|
||||||
|
#### `cherrypick_annotations` - `v1.1.2-dev.0`
|
||||||
|
|
||||||
|
- **DOCS**(annotations): unify and improve English DartDoc for all DI annotations.
|
||||||
|
|
||||||
|
#### `cherrypick_flutter` - `v1.1.3-dev.9`
|
||||||
|
|
||||||
|
- **DOCS**(provider): add detailed English API documentation for CherryPickProvider Flutter integration.
|
||||||
|
|
||||||
|
#### `talker_cherrypick_logger` - `v1.1.0-dev.0`
|
||||||
|
|
||||||
|
- **FEAT**(logging): add talker_dio_logger and talker_bloc_logger integration, improve cherrypick logger structure, add UI log screen for DI and network/bloc debug.
|
||||||
|
- **DOCS**: add full English documentation and usage guide to README.md.
|
||||||
|
- **DOCS**: add detailed English documentation and usage examples for TalkerCherryPickObserver.
|
||||||
|
|
||||||
|
|
||||||
## 2025-08-12
|
## 2025-08-12
|
||||||
|
|
||||||
### Changes
|
### Changes
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ packages:
|
|||||||
path: "../cherrypick"
|
path: "../cherrypick"
|
||||||
relative: true
|
relative: true
|
||||||
source: path
|
source: path
|
||||||
version: "3.0.0-dev.7"
|
version: "3.0.0-dev.8"
|
||||||
collection:
|
collection:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
@@ -1,3 +1,8 @@
|
|||||||
|
## 3.0.0-dev.9
|
||||||
|
|
||||||
|
- **DOCS**(readme): add talker_cherrypick_logger to Additional Modules section.
|
||||||
|
- **DOCS**(api): improve all DI core code documentation with English dartdoc and examples.
|
||||||
|
|
||||||
## 3.0.0-dev.8
|
## 3.0.0-dev.8
|
||||||
|
|
||||||
- **REFACTOR**(tests): replace MockLogger with MockObserver in scope tests to align with updated observer API.
|
- **REFACTOR**(tests): replace MockLogger with MockObserver in scope tests to align with updated observer API.
|
||||||
|
|||||||
@@ -707,11 +707,12 @@ Yes! Even if none of your services currently implement `Disposable`, always use
|
|||||||
|
|
||||||
CherryPick provides a set of official add-on modules for advanced use cases and specific platforms:
|
CherryPick provides a set of official add-on modules for advanced use cases and specific platforms:
|
||||||
|
|
||||||
| Module name | Description | Documentation |
|
| Module name | Description |
|
||||||
|-------------|-------------|---------------|
|
|-------------|-------------|
|
||||||
| [**cherrypick_annotations**](https://pub.dev/packages/cherrypick_annotations) | Dart annotations for concise DI definitions and code generation. | [README](../cherrypick_annotations/README.md) |
|
| [**cherrypick_annotations**](https://pub.dev/packages/cherrypick_annotations) | Dart annotations for concise DI definitions and code generation. |
|
||||||
| [**cherrypick_generator**](https://pub.dev/packages/cherrypick_generator) | Code generator to produce DI bindings based on annotations. | [README](../cherrypick_generator/README.md) |
|
| [**cherrypick_generator**](https://pub.dev/packages/cherrypick_generator) | Code generator to produce DI bindings based on annotations. |
|
||||||
| [**cherrypick_flutter**](https://pub.dev/packages/cherrypick_flutter) | Flutter integration: DI provider widgets and helpers for Flutter. | [README](../cherrypick_flutter/README.md) |
|
| [**cherrypick_flutter**](https://pub.dev/packages/cherrypick_flutter) | Flutter integration: DI provider widgets and helpers for Flutter. |
|
||||||
|
| [**talker_cherrypick_logger**](https://pub.dev/packages/talker_cherrypick_logger) | Advanced logger for CherryPick DI events and state. Provides seamless integration with [Talker](https://pub.dev/packages/talker) logger, enabling central and visual tracking of DI events, errors, and diagnostics in both UI and console. |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
@@ -13,41 +13,70 @@
|
|||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
|
/// Represents a direct instance or an async instance ([T] or [Future<T>]).
|
||||||
|
/// Used for both direct and async bindings.
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
/// ```dart
|
||||||
|
/// Instance<String> sync = "hello";
|
||||||
|
/// Instance<MyApi> async = Future.value(MyApi());
|
||||||
|
/// ```
|
||||||
typedef Instance<T> = FutureOr<T>;
|
typedef Instance<T> = FutureOr<T>;
|
||||||
|
|
||||||
/// RU: Синхронный или асинхронный провайдер без параметров, возвращающий [T] или [Future<T>].
|
/// Provider function type for synchronous or asynchronous, parameterless creation of [T].
|
||||||
/// ENG: Synchronous or asynchronous provider without parameters, returning [T] or [Future<T>].
|
/// Can return [T] or [Future<T>].
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
/// ```dart
|
||||||
|
/// Provider<MyService> provider = () => MyService();
|
||||||
|
/// Provider<Api> asyncProvider = () async => await Api.connect();
|
||||||
|
/// ```
|
||||||
typedef Provider<T> = FutureOr<T> Function();
|
typedef Provider<T> = FutureOr<T> Function();
|
||||||
|
|
||||||
/// RU: Провайдер с динамическим параметром, возвращающий [T] или [Future<T>] в зависимости от реализации.
|
/// Provider function type that accepts a dynamic parameter, for factory/parametrized injection.
|
||||||
/// ENG: Provider with dynamic parameter, returning [T] or [Future<T>] depending on implementation.
|
/// Returns [T] or [Future<T>].
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
/// ```dart
|
||||||
|
/// ProviderWithParams<User> provider = (params) => User(params["name"]);
|
||||||
|
/// ```
|
||||||
typedef ProviderWithParams<T> = FutureOr<T> Function(dynamic);
|
typedef ProviderWithParams<T> = FutureOr<T> Function(dynamic);
|
||||||
|
|
||||||
/// RU: Абстрактный интерфейс для классов, которые разрешают зависимости типа [T].
|
/// Abstract interface for dependency resolvers used by [Binding].
|
||||||
/// ENG: Abstract interface for classes that resolve dependencies of type [T].
|
/// Defines how to resolve instances of type [T].
|
||||||
|
///
|
||||||
|
/// You usually don't use this directly; it's used internally for advanced/low-level DI.
|
||||||
abstract class BindingResolver<T> {
|
abstract class BindingResolver<T> {
|
||||||
/// RU: Синхронное разрешение зависимости с параметром [params].
|
/// Synchronously resolves the dependency, optionally taking parameters (for factory cases).
|
||||||
/// ENG: Synchronous resolution of the dependency with [params].
|
/// Throws if implementation does not support sync resolution.
|
||||||
T? resolveSync([dynamic params]);
|
T? resolveSync([dynamic params]);
|
||||||
|
|
||||||
/// RU: Асинхронное разрешение зависимости с параметром [params].
|
/// Asynchronously resolves the dependency, optionally taking parameters (for factory cases).
|
||||||
/// ENG: Asynchronous resolution of the dependency with [params].
|
/// If instance is already a [Future], returns it directly.
|
||||||
Future<T>? resolveAsync([dynamic params]);
|
Future<T>? resolveAsync([dynamic params]);
|
||||||
|
|
||||||
/// RU: Помечает текущий резолвер как синглтон — результат будет закеширован.
|
/// Marks this resolver as singleton: instance(s) will be cached and reused inside the scope.
|
||||||
/// ENG: Marks this resolver as singleton — result will be cached.
|
|
||||||
void toSingleton();
|
void toSingleton();
|
||||||
|
|
||||||
|
/// Returns true if this resolver is marked as singleton.
|
||||||
bool get isSingleton;
|
bool get isSingleton;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// RU: Резолвер, оборачивающий конкретный экземпляр [T] (или Future<T>), без вызова провайдера.
|
/// Concrete resolver for direct instance ([T] or [Future<T>]). No provider is called.
|
||||||
/// ENG: Resolver that wraps a concrete instance of [T] (or Future<T>), without provider invocation.
|
///
|
||||||
|
/// Used for [Binding.toInstance].
|
||||||
|
/// Supports both sync and async resolution; sync will throw if underlying instance is [Future].
|
||||||
|
/// Examples:
|
||||||
|
/// ```dart
|
||||||
|
/// var resolver = InstanceResolver("hello");
|
||||||
|
/// resolver.resolveSync(); // == "hello"
|
||||||
|
/// var asyncResolver = InstanceResolver(Future.value(7));
|
||||||
|
/// asyncResolver.resolveAsync(); // Future<int>
|
||||||
|
/// ```
|
||||||
class InstanceResolver<T> implements BindingResolver<T> {
|
class InstanceResolver<T> implements BindingResolver<T> {
|
||||||
final Instance<T> _instance;
|
final Instance<T> _instance;
|
||||||
|
|
||||||
/// RU: Создаёт резолвер, оборачивающий значение [instance].
|
/// Wraps the given instance (sync or async) in a resolver.
|
||||||
/// ENG: Creates a resolver that wraps the given [instance].
|
|
||||||
InstanceResolver(this._instance);
|
InstanceResolver(this._instance);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -62,7 +91,6 @@ class InstanceResolver<T> implements BindingResolver<T> {
|
|||||||
@override
|
@override
|
||||||
Future<T> resolveAsync([_]) {
|
Future<T> resolveAsync([_]) {
|
||||||
if (_instance is Future<T>) return _instance;
|
if (_instance is Future<T>) return _instance;
|
||||||
|
|
||||||
return Future.value(_instance);
|
return Future.value(_instance);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,8 +101,23 @@ class InstanceResolver<T> implements BindingResolver<T> {
|
|||||||
bool get isSingleton => true;
|
bool get isSingleton => true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// RU: Резолвер, оборачивающий провайдер, с возможностью синглтон-кеширования.
|
/// Resolver for provider functions (sync/async/factory), with optional singleton caching.
|
||||||
/// ENG: Resolver that wraps a provider, with optional singleton caching.
|
/// Used for [Binding.toProvide], [Binding.toProvideWithParams], [Binding.singleton].
|
||||||
|
///
|
||||||
|
/// Examples:
|
||||||
|
/// ```dart
|
||||||
|
/// // No param, sync:
|
||||||
|
/// var r = ProviderResolver((_) => 5, withParams: false);
|
||||||
|
/// r.resolveSync(); // == 5
|
||||||
|
/// // With param:
|
||||||
|
/// var rp = ProviderResolver((p) => p * 2, withParams: true);
|
||||||
|
/// rp.resolveSync(2); // == 4
|
||||||
|
/// // Singleton:
|
||||||
|
/// r.toSingleton();
|
||||||
|
/// // Async:
|
||||||
|
/// var ra = ProviderResolver((_) async => await Future.value(10), withParams: false);
|
||||||
|
/// await ra.resolveAsync(); // == 10
|
||||||
|
/// ```
|
||||||
class ProviderResolver<T> implements BindingResolver<T> {
|
class ProviderResolver<T> implements BindingResolver<T> {
|
||||||
final ProviderWithParams<T> _provider;
|
final ProviderWithParams<T> _provider;
|
||||||
final bool _withParams;
|
final bool _withParams;
|
||||||
@@ -82,8 +125,7 @@ class ProviderResolver<T> implements BindingResolver<T> {
|
|||||||
FutureOr<T>? _cache;
|
FutureOr<T>? _cache;
|
||||||
bool _singleton = false;
|
bool _singleton = false;
|
||||||
|
|
||||||
/// RU: Создаёт резолвер из произвольной функции [raw], поддерживающей ноль или один параметр.
|
/// Creates a resolver from [provider], optionally accepting dynamic params.
|
||||||
/// ENG: Creates a resolver from arbitrary function [raw], supporting zero or one parameter.
|
|
||||||
ProviderResolver(
|
ProviderResolver(
|
||||||
ProviderWithParams<T> provider, {
|
ProviderWithParams<T> provider, {
|
||||||
required bool withParams,
|
required bool withParams,
|
||||||
@@ -93,16 +135,13 @@ class ProviderResolver<T> implements BindingResolver<T> {
|
|||||||
@override
|
@override
|
||||||
T resolveSync([dynamic params]) {
|
T resolveSync([dynamic params]) {
|
||||||
_checkParams(params);
|
_checkParams(params);
|
||||||
|
|
||||||
final result = _cache ?? _provider(params);
|
final result = _cache ?? _provider(params);
|
||||||
|
|
||||||
if (result is T) {
|
if (result is T) {
|
||||||
if (_singleton) {
|
if (_singleton) {
|
||||||
_cache ??= result;
|
_cache ??= result;
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
throw StateError(
|
throw StateError(
|
||||||
'Provider [$_provider] return Future<$T>. Use resolveAsync() instead.',
|
'Provider [$_provider] return Future<$T>. Use resolveAsync() instead.',
|
||||||
);
|
);
|
||||||
@@ -111,14 +150,11 @@ class ProviderResolver<T> implements BindingResolver<T> {
|
|||||||
@override
|
@override
|
||||||
Future<T> resolveAsync([dynamic params]) {
|
Future<T> resolveAsync([dynamic params]) {
|
||||||
_checkParams(params);
|
_checkParams(params);
|
||||||
|
|
||||||
final result = _cache ?? _provider(params);
|
final result = _cache ?? _provider(params);
|
||||||
final target = result is Future<T> ? result : Future<T>.value(result);
|
final target = result is Future<T> ? result : Future<T>.value(result);
|
||||||
|
|
||||||
if (_singleton) {
|
if (_singleton) {
|
||||||
_cache ??= target;
|
_cache ??= target;
|
||||||
}
|
}
|
||||||
|
|
||||||
return target;
|
return target;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,8 +166,7 @@ class ProviderResolver<T> implements BindingResolver<T> {
|
|||||||
@override
|
@override
|
||||||
bool get isSingleton => _singleton;
|
bool get isSingleton => _singleton;
|
||||||
|
|
||||||
/// RU: Проверяет, был ли передан параметр, если провайдер требует его.
|
/// Throws if params required but not supplied.
|
||||||
/// ENG: Checks if parameter is passed when the provider expects it.
|
|
||||||
void _checkParams(dynamic params) {
|
void _checkParams(dynamic params) {
|
||||||
if (_withParams && params == null) {
|
if (_withParams && params == null) {
|
||||||
throw StateError(
|
throw StateError(
|
||||||
|
|||||||
@@ -14,16 +14,20 @@
|
|||||||
import 'dart:collection';
|
import 'dart:collection';
|
||||||
import 'package:cherrypick/src/observer.dart';
|
import 'package:cherrypick/src/observer.dart';
|
||||||
|
|
||||||
/// RU: Исключение, выбрасываемое при обнаружении циклической зависимости.
|
/// Exception thrown when a circular dependency is detected during dependency resolution.
|
||||||
/// ENG: Exception thrown when a circular dependency is detected.
|
///
|
||||||
|
/// Contains a [message] and the [dependencyChain] showing the resolution cycle.
|
||||||
|
///
|
||||||
|
/// Example diagnostic:
|
||||||
|
/// ```
|
||||||
|
/// CircularDependencyException: Circular dependency detected for A
|
||||||
|
/// Dependency chain: A -> B -> C -> A
|
||||||
|
/// ```
|
||||||
class CircularDependencyException implements Exception {
|
class CircularDependencyException implements Exception {
|
||||||
final String message;
|
final String message;
|
||||||
final List<String> dependencyChain;
|
final List<String> dependencyChain;
|
||||||
|
|
||||||
CircularDependencyException(this.message, this.dependencyChain) {
|
CircularDependencyException(this.message, this.dependencyChain);
|
||||||
// DEBUG
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
@@ -32,8 +36,26 @@ class CircularDependencyException implements Exception {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// RU: Детектор циклических зависимостей для CherryPick DI контейнера.
|
/// Circular dependency detector for CherryPick DI containers.
|
||||||
/// ENG: Circular dependency detector for CherryPick DI container.
|
///
|
||||||
|
/// Tracks dependency resolution chains to detect and prevent infinite recursion caused by cycles.
|
||||||
|
/// Whenever a resolve chain re-enters a started dependency, a [CircularDependencyException] is thrown with the full chain.
|
||||||
|
///
|
||||||
|
/// This class is used internally, but you can interact with it through [CycleDetectionMixin].
|
||||||
|
///
|
||||||
|
/// Example usage (pseudocode):
|
||||||
|
/// ```dart
|
||||||
|
/// final detector = CycleDetector(observer: myObserver);
|
||||||
|
/// try {
|
||||||
|
/// detector.startResolving<A>();
|
||||||
|
/// // ... resolving A which depends on B, etc
|
||||||
|
/// detector.startResolving<B>();
|
||||||
|
/// detector.startResolving<A>(); // BOOM: throws exception
|
||||||
|
/// } finally {
|
||||||
|
/// detector.finishResolving<B>();
|
||||||
|
/// detector.finishResolving<A>();
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
class CycleDetector {
|
class CycleDetector {
|
||||||
final CherryPickObserver _observer;
|
final CherryPickObserver _observer;
|
||||||
final Set<String> _resolutionStack = HashSet<String>();
|
final Set<String> _resolutionStack = HashSet<String>();
|
||||||
@@ -41,10 +63,9 @@ class CycleDetector {
|
|||||||
|
|
||||||
CycleDetector({required CherryPickObserver observer}) : _observer = observer;
|
CycleDetector({required CherryPickObserver observer}) : _observer = observer;
|
||||||
|
|
||||||
/// RU: Начинает отслеживание разрешения зависимости.
|
/// Starts tracking dependency resolution for type [T] and optional [named] qualifier.
|
||||||
/// ENG: Starts tracking dependency resolution.
|
|
||||||
///
|
///
|
||||||
/// Throws [CircularDependencyException] if circular dependency is detected.
|
/// Throws [CircularDependencyException] if a cycle is found.
|
||||||
void startResolving<T>({String? named}) {
|
void startResolving<T>({String? named}) {
|
||||||
final dependencyKey = _createDependencyKey<T>(named);
|
final dependencyKey = _createDependencyKey<T>(named);
|
||||||
_observer.onDiagnostic(
|
_observer.onDiagnostic(
|
||||||
@@ -57,26 +78,19 @@ class CycleDetector {
|
|||||||
if (_resolutionStack.contains(dependencyKey)) {
|
if (_resolutionStack.contains(dependencyKey)) {
|
||||||
final cycleStartIndex = _resolutionHistory.indexOf(dependencyKey);
|
final cycleStartIndex = _resolutionHistory.indexOf(dependencyKey);
|
||||||
final cycle = _resolutionHistory.sublist(cycleStartIndex)..add(dependencyKey);
|
final cycle = _resolutionHistory.sublist(cycleStartIndex)..add(dependencyKey);
|
||||||
_observer.onCycleDetected(
|
_observer.onCycleDetected(cycle);
|
||||||
cycle,
|
_observer.onError('Cycle detected for $dependencyKey', null, null);
|
||||||
);
|
|
||||||
_observer.onError(
|
|
||||||
'Cycle detected for $dependencyKey',
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
);
|
|
||||||
throw CircularDependencyException(
|
throw CircularDependencyException(
|
||||||
'Circular dependency detected for $dependencyKey',
|
'Circular dependency detected for $dependencyKey',
|
||||||
cycle,
|
cycle,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
_resolutionStack.add(dependencyKey);
|
_resolutionStack.add(dependencyKey);
|
||||||
_resolutionHistory.add(dependencyKey);
|
_resolutionHistory.add(dependencyKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// RU: Завершает отслеживание разрешения зависимости.
|
/// Stops tracking dependency resolution for type [T] and optional [named] qualifier.
|
||||||
/// ENG: Finishes tracking dependency resolution.
|
/// Should always be called after [startResolving], including for errors.
|
||||||
void finishResolving<T>({String? named}) {
|
void finishResolving<T>({String? named}) {
|
||||||
final dependencyKey = _createDependencyKey<T>(named);
|
final dependencyKey = _createDependencyKey<T>(named);
|
||||||
_observer.onDiagnostic(
|
_observer.onDiagnostic(
|
||||||
@@ -84,15 +98,13 @@ class CycleDetector {
|
|||||||
details: {'event': 'finishResolving'},
|
details: {'event': 'finishResolving'},
|
||||||
);
|
);
|
||||||
_resolutionStack.remove(dependencyKey);
|
_resolutionStack.remove(dependencyKey);
|
||||||
// Удаляем из истории только если это последний элемент
|
// Only remove from history if it's the last one
|
||||||
if (_resolutionHistory.isNotEmpty &&
|
if (_resolutionHistory.isNotEmpty && _resolutionHistory.last == dependencyKey) {
|
||||||
_resolutionHistory.last == dependencyKey) {
|
|
||||||
_resolutionHistory.removeLast();
|
_resolutionHistory.removeLast();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// RU: Очищает все состояние детектора.
|
/// Clears all resolution state and resets the cycle detector.
|
||||||
/// ENG: Clears all detector state.
|
|
||||||
void clear() {
|
void clear() {
|
||||||
_observer.onDiagnostic(
|
_observer.onDiagnostic(
|
||||||
'CycleDetector clear',
|
'CycleDetector clear',
|
||||||
@@ -105,33 +117,45 @@ class CycleDetector {
|
|||||||
_resolutionHistory.clear();
|
_resolutionHistory.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// RU: Проверяет, находится ли зависимость в процессе разрешения.
|
/// Returns true if dependency [T] (and [named], if specified) is being resolved right now.
|
||||||
/// ENG: Checks if dependency is currently being resolved.
|
|
||||||
bool isResolving<T>({String? named}) {
|
bool isResolving<T>({String? named}) {
|
||||||
final dependencyKey = _createDependencyKey<T>(named);
|
final dependencyKey = _createDependencyKey<T>(named);
|
||||||
return _resolutionStack.contains(dependencyKey);
|
return _resolutionStack.contains(dependencyKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// RU: Возвращает текущую цепочку разрешения зависимостей.
|
/// Gets the current dependency resolution chain (for diagnostics or debugging).
|
||||||
/// ENG: Returns current dependency resolution chain.
|
|
||||||
List<String> get currentResolutionChain => List.unmodifiable(_resolutionHistory);
|
List<String> get currentResolutionChain => List.unmodifiable(_resolutionHistory);
|
||||||
|
|
||||||
/// RU: Создает уникальный ключ для зависимости.
|
/// Returns a unique string key for type [T] (+name).
|
||||||
/// ENG: Creates unique key for dependency.
|
|
||||||
String _createDependencyKey<T>(String? named) {
|
String _createDependencyKey<T>(String? named) {
|
||||||
final typeName = T.toString();
|
final typeName = T.toString();
|
||||||
return named != null ? '$typeName@$named' : typeName;
|
return named != null ? '$typeName@$named' : typeName;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// RU: Миксин для добавления поддержки обнаружения циклических зависимостей.
|
/// Mixin for adding circular dependency detection support to custom DI containers/classes.
|
||||||
/// ENG: Mixin for adding circular dependency detection support.
|
///
|
||||||
|
/// Fields:
|
||||||
|
/// - `observer`: must be implemented by your class (used for diagnostics and error reporting)
|
||||||
|
///
|
||||||
|
/// Example usage:
|
||||||
|
/// ```dart
|
||||||
|
/// class MyContainer with CycleDetectionMixin {
|
||||||
|
/// @override
|
||||||
|
/// CherryPickObserver get observer => myObserver;
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// final c = MyContainer();
|
||||||
|
/// c.enableCycleDetection();
|
||||||
|
/// c.withCycleDetection(String, null, () {
|
||||||
|
/// // ... dependency resolution code
|
||||||
|
/// });
|
||||||
|
/// ```
|
||||||
mixin CycleDetectionMixin {
|
mixin CycleDetectionMixin {
|
||||||
CycleDetector? _cycleDetector;
|
CycleDetector? _cycleDetector;
|
||||||
CherryPickObserver get observer;
|
CherryPickObserver get observer;
|
||||||
|
|
||||||
/// RU: Включает обнаружение циклических зависимостей.
|
/// Turns on circular dependency detection for this class/container.
|
||||||
/// ENG: Enables circular dependency detection.
|
|
||||||
void enableCycleDetection() {
|
void enableCycleDetection() {
|
||||||
_cycleDetector = CycleDetector(observer: observer);
|
_cycleDetector = CycleDetector(observer: observer);
|
||||||
observer.onDiagnostic(
|
observer.onDiagnostic(
|
||||||
@@ -143,8 +167,7 @@ mixin CycleDetectionMixin {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// RU: Отключает обнаружение циклических зависимостей.
|
/// Shuts off detection and clears any cycle history for this container.
|
||||||
/// ENG: Disables circular dependency detection.
|
|
||||||
void disableCycleDetection() {
|
void disableCycleDetection() {
|
||||||
_cycleDetector?.clear();
|
_cycleDetector?.clear();
|
||||||
observer.onDiagnostic(
|
observer.onDiagnostic(
|
||||||
@@ -157,12 +180,17 @@ mixin CycleDetectionMixin {
|
|||||||
_cycleDetector = null;
|
_cycleDetector = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// RU: Проверяет, включено ли обнаружение циклических зависимостей.
|
/// Returns true if detection is currently enabled.
|
||||||
/// ENG: Checks if circular dependency detection is enabled.
|
|
||||||
bool get isCycleDetectionEnabled => _cycleDetector != null;
|
bool get isCycleDetectionEnabled => _cycleDetector != null;
|
||||||
|
|
||||||
/// RU: Выполняет действие с отслеживанием циклических зависимостей.
|
/// Executes [action] while tracking for circular DI cycles for [dependencyType] and [named].
|
||||||
/// ENG: Executes action with circular dependency tracking.
|
///
|
||||||
|
/// Throws [CircularDependencyException] if a dependency cycle is detected.
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
/// ```dart
|
||||||
|
/// withCycleDetection(String, 'api', () => resolveApi());
|
||||||
|
/// ```
|
||||||
T withCycleDetection<T>(
|
T withCycleDetection<T>(
|
||||||
Type dependencyType,
|
Type dependencyType,
|
||||||
String? named,
|
String? named,
|
||||||
@@ -180,14 +208,8 @@ mixin CycleDetectionMixin {
|
|||||||
final cycleStartIndex = _cycleDetector!._resolutionHistory.indexOf(dependencyKey);
|
final cycleStartIndex = _cycleDetector!._resolutionHistory.indexOf(dependencyKey);
|
||||||
final cycle = _cycleDetector!._resolutionHistory.sublist(cycleStartIndex)
|
final cycle = _cycleDetector!._resolutionHistory.sublist(cycleStartIndex)
|
||||||
..add(dependencyKey);
|
..add(dependencyKey);
|
||||||
observer.onCycleDetected(
|
observer.onCycleDetected(cycle);
|
||||||
cycle,
|
observer.onError('Cycle detected for $dependencyKey', null, null);
|
||||||
);
|
|
||||||
observer.onError(
|
|
||||||
'Cycle detected for $dependencyKey',
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
);
|
|
||||||
throw CircularDependencyException(
|
throw CircularDependencyException(
|
||||||
'Circular dependency detected for $dependencyKey',
|
'Circular dependency detected for $dependencyKey',
|
||||||
cycle,
|
cycle,
|
||||||
@@ -208,8 +230,7 @@ mixin CycleDetectionMixin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// RU: Возвращает текущую цепочку разрешения зависимостей.
|
/// Gets the current active dependency resolution chain.
|
||||||
/// ENG: Returns current dependency resolution chain.
|
|
||||||
List<String> get currentResolutionChain =>
|
List<String> get currentResolutionChain =>
|
||||||
_cycleDetector?.currentResolutionChain ?? [];
|
_cycleDetector?.currentResolutionChain ?? [];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,28 @@
|
|||||||
//
|
//
|
||||||
import 'package:cherrypick/src/scope.dart';
|
import 'package:cherrypick/src/scope.dart';
|
||||||
|
|
||||||
|
/// Abstract factory interface for creating objects of type [T] using a [Scope].
|
||||||
|
///
|
||||||
|
/// Can be implemented for advanced dependency injection scenarios where
|
||||||
|
/// the resolution requires contextual information from the DI [Scope].
|
||||||
|
///
|
||||||
|
/// Often used to supply complex objects, runtime-bound services,
|
||||||
|
/// or objects depending on dynamic configuration.
|
||||||
|
///
|
||||||
|
/// Example usage:
|
||||||
|
/// ```dart
|
||||||
|
/// class MyServiceFactory implements Factory<MyService> {
|
||||||
|
/// @override
|
||||||
|
/// MyService createInstance(Scope scope) {
|
||||||
|
/// final db = scope.resolve<Database>(named: "main");
|
||||||
|
/// return MyService(db);
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// // Usage in a module:
|
||||||
|
/// bind<MyService>().toProvide(() => MyServiceFactory().createInstance(scope));
|
||||||
|
/// ```
|
||||||
abstract class Factory<T> {
|
abstract class Factory<T> {
|
||||||
|
/// Implement this to provide an instance of [T], with access to the resolving [scope].
|
||||||
T createInstance(Scope scope);
|
T createInstance(Scope scope);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,33 +15,47 @@ import 'dart:collection';
|
|||||||
import 'package:cherrypick/cherrypick.dart';
|
import 'package:cherrypick/cherrypick.dart';
|
||||||
|
|
||||||
|
|
||||||
/// RU: Глобальный детектор циклических зависимостей для всей иерархии скоупов.
|
/// GlobalCycleDetector detects and prevents circular dependencies across an entire DI scope hierarchy.
|
||||||
/// ENG: Global circular dependency detector for entire scope hierarchy.
|
///
|
||||||
|
/// This is particularly important for modular/feature-based applications
|
||||||
|
/// where subscopes can introduce indirect cycles that span different scopes.
|
||||||
|
///
|
||||||
|
/// The detector tracks resolution chains and throws [CircularDependencyException]
|
||||||
|
/// when a cycle is found, showing the full chain (including scope context).
|
||||||
|
///
|
||||||
|
/// Example usage via [GlobalCycleDetectionMixin]:
|
||||||
|
/// ```dart
|
||||||
|
/// class MyScope with GlobalCycleDetectionMixin { /* ... */ }
|
||||||
|
///
|
||||||
|
/// final scope = MyScope();
|
||||||
|
/// scope.setScopeId('feature');
|
||||||
|
/// scope.enableGlobalCycleDetection();
|
||||||
|
///
|
||||||
|
/// scope.withGlobalCycleDetection(String, null, () {
|
||||||
|
/// // ... resolve dependencies here, will detect cross-scope cycles
|
||||||
|
/// });
|
||||||
|
/// ```
|
||||||
class GlobalCycleDetector {
|
class GlobalCycleDetector {
|
||||||
static GlobalCycleDetector? _instance;
|
static GlobalCycleDetector? _instance;
|
||||||
|
|
||||||
final CherryPickObserver _observer;
|
final CherryPickObserver _observer;
|
||||||
|
|
||||||
// Глобальный стек разрешения зависимостей
|
// Global set and chain history for all resolutions
|
||||||
final Set<String> _globalResolutionStack = HashSet<String>();
|
final Set<String> _globalResolutionStack = HashSet<String>();
|
||||||
|
|
||||||
// История разрешения для построения цепочки зависимостей
|
|
||||||
final List<String> _globalResolutionHistory = [];
|
final List<String> _globalResolutionHistory = [];
|
||||||
|
|
||||||
// Карта активных детекторов по скоупам
|
// Map of active detectors for subscopes (rarely used directly)
|
||||||
final Map<String, CycleDetector> _scopeDetectors = HashMap<String, CycleDetector>();
|
final Map<String, CycleDetector> _scopeDetectors = HashMap<String, CycleDetector>();
|
||||||
|
|
||||||
GlobalCycleDetector._internal({required CherryPickObserver observer}): _observer = observer;
|
GlobalCycleDetector._internal({required CherryPickObserver observer}): _observer = observer;
|
||||||
|
|
||||||
/// RU: Получить единственный экземпляр глобального детектора.
|
/// Returns the singleton global detector instance, initializing it if needed.
|
||||||
/// ENG: Get singleton instance of global detector.
|
|
||||||
static GlobalCycleDetector get instance {
|
static GlobalCycleDetector get instance {
|
||||||
_instance ??= GlobalCycleDetector._internal(observer: CherryPick.globalObserver);
|
_instance ??= GlobalCycleDetector._internal(observer: CherryPick.globalObserver);
|
||||||
return _instance!;
|
return _instance!;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// RU: Сбросить глобальный детектор (полезно для тестов).
|
/// Reset internal state (useful for testing).
|
||||||
/// ENG: Reset global detector (useful for tests).
|
|
||||||
static void reset() {
|
static void reset() {
|
||||||
_instance?._globalResolutionStack.clear();
|
_instance?._globalResolutionStack.clear();
|
||||||
_instance?._globalResolutionHistory.clear();
|
_instance?._globalResolutionHistory.clear();
|
||||||
@@ -49,24 +63,16 @@ class GlobalCycleDetector {
|
|||||||
_instance = null;
|
_instance = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// RU: Начать отслеживание разрешения зависимости в глобальном контексте.
|
/// Start tracking resolution of dependency [T] with optional [named] and [scopeId].
|
||||||
/// ENG: Start tracking dependency resolution in global context.
|
/// Throws [CircularDependencyException] on cycle.
|
||||||
void startGlobalResolving<T>({String? named, String? scopeId}) {
|
void startGlobalResolving<T>({String? named, String? scopeId}) {
|
||||||
final dependencyKey = _createDependencyKeyFromType(T, named, scopeId);
|
final dependencyKey = _createDependencyKeyFromType(T, named, scopeId);
|
||||||
|
|
||||||
if (_globalResolutionStack.contains(dependencyKey)) {
|
if (_globalResolutionStack.contains(dependencyKey)) {
|
||||||
// Найдена глобальная циклическая зависимость
|
|
||||||
final cycleStartIndex = _globalResolutionHistory.indexOf(dependencyKey);
|
final cycleStartIndex = _globalResolutionHistory.indexOf(dependencyKey);
|
||||||
final cycle = _globalResolutionHistory.sublist(cycleStartIndex)..add(dependencyKey);
|
final cycle = _globalResolutionHistory.sublist(cycleStartIndex)..add(dependencyKey);
|
||||||
_observer.onCycleDetected(
|
_observer.onCycleDetected(cycle, scopeName: scopeId);
|
||||||
cycle,
|
_observer.onError('Global circular dependency detected for $dependencyKey', null, null);
|
||||||
scopeName: scopeId,
|
|
||||||
);
|
|
||||||
_observer.onError(
|
|
||||||
'Global circular dependency detected for $dependencyKey',
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
);
|
|
||||||
throw CircularDependencyException(
|
throw CircularDependencyException(
|
||||||
'Global circular dependency detected for $dependencyKey',
|
'Global circular dependency detected for $dependencyKey',
|
||||||
cycle,
|
cycle,
|
||||||
@@ -77,21 +83,18 @@ class GlobalCycleDetector {
|
|||||||
_globalResolutionHistory.add(dependencyKey);
|
_globalResolutionHistory.add(dependencyKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// RU: Завершить отслеживание разрешения зависимости в глобальном контексте.
|
/// Finish tracking a dependency. Should always be called after [startGlobalResolving].
|
||||||
/// ENG: Finish tracking dependency resolution in global context.
|
|
||||||
void finishGlobalResolving<T>({String? named, String? scopeId}) {
|
void finishGlobalResolving<T>({String? named, String? scopeId}) {
|
||||||
final dependencyKey = _createDependencyKeyFromType(T, named, scopeId);
|
final dependencyKey = _createDependencyKeyFromType(T, named, scopeId);
|
||||||
_globalResolutionStack.remove(dependencyKey);
|
_globalResolutionStack.remove(dependencyKey);
|
||||||
|
|
||||||
// Удаляем из истории только если это последний элемент
|
if (_globalResolutionHistory.isNotEmpty && _globalResolutionHistory.last == dependencyKey) {
|
||||||
if (_globalResolutionHistory.isNotEmpty &&
|
|
||||||
_globalResolutionHistory.last == dependencyKey) {
|
|
||||||
_globalResolutionHistory.removeLast();
|
_globalResolutionHistory.removeLast();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// RU: Выполнить действие с глобальным отслеживанием циклических зависимостей.
|
/// Internally execute [action] with global cycle detection for [dependencyType], [named], [scopeId].
|
||||||
/// ENG: Execute action with global circular dependency tracking.
|
/// Throws [CircularDependencyException] on cycle.
|
||||||
T withGlobalCycleDetection<T>(
|
T withGlobalCycleDetection<T>(
|
||||||
Type dependencyType,
|
Type dependencyType,
|
||||||
String? named,
|
String? named,
|
||||||
@@ -102,17 +105,9 @@ class GlobalCycleDetector {
|
|||||||
|
|
||||||
if (_globalResolutionStack.contains(dependencyKey)) {
|
if (_globalResolutionStack.contains(dependencyKey)) {
|
||||||
final cycleStartIndex = _globalResolutionHistory.indexOf(dependencyKey);
|
final cycleStartIndex = _globalResolutionHistory.indexOf(dependencyKey);
|
||||||
final cycle = _globalResolutionHistory.sublist(cycleStartIndex)
|
final cycle = _globalResolutionHistory.sublist(cycleStartIndex)..add(dependencyKey);
|
||||||
..add(dependencyKey);
|
_observer.onCycleDetected(cycle, scopeName: scopeId);
|
||||||
_observer.onCycleDetected(
|
_observer.onError('Global circular dependency detected for $dependencyKey', null, null);
|
||||||
cycle,
|
|
||||||
scopeName: scopeId,
|
|
||||||
);
|
|
||||||
_observer.onError(
|
|
||||||
'Global circular dependency detected for $dependencyKey',
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
);
|
|
||||||
throw CircularDependencyException(
|
throw CircularDependencyException(
|
||||||
'Global circular dependency detected for $dependencyKey',
|
'Global circular dependency detected for $dependencyKey',
|
||||||
cycle,
|
cycle,
|
||||||
@@ -126,38 +121,32 @@ class GlobalCycleDetector {
|
|||||||
return action();
|
return action();
|
||||||
} finally {
|
} finally {
|
||||||
_globalResolutionStack.remove(dependencyKey);
|
_globalResolutionStack.remove(dependencyKey);
|
||||||
if (_globalResolutionHistory.isNotEmpty &&
|
if (_globalResolutionHistory.isNotEmpty && _globalResolutionHistory.last == dependencyKey) {
|
||||||
_globalResolutionHistory.last == dependencyKey) {
|
|
||||||
_globalResolutionHistory.removeLast();
|
_globalResolutionHistory.removeLast();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// RU: Получить детектор для конкретного скоупа.
|
/// Get per-scope detector (not usually needed by consumers).
|
||||||
/// ENG: Get detector for specific scope.
|
|
||||||
CycleDetector getScopeDetector(String scopeId) {
|
CycleDetector getScopeDetector(String scopeId) {
|
||||||
return _scopeDetectors.putIfAbsent(scopeId, () => CycleDetector(observer: CherryPick.globalObserver));
|
return _scopeDetectors.putIfAbsent(scopeId, () => CycleDetector(observer: CherryPick.globalObserver));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// RU: Удалить детектор для скоупа.
|
/// Remove detector for a given scope.
|
||||||
/// ENG: Remove detector for scope.
|
|
||||||
void removeScopeDetector(String scopeId) {
|
void removeScopeDetector(String scopeId) {
|
||||||
_scopeDetectors.remove(scopeId);
|
_scopeDetectors.remove(scopeId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// RU: Проверить, находится ли зависимость в процессе глобального разрешения.
|
/// Returns true if dependency [T] is currently being resolved in the global scope.
|
||||||
/// ENG: Check if dependency is currently being resolved globally.
|
|
||||||
bool isGloballyResolving<T>({String? named, String? scopeId}) {
|
bool isGloballyResolving<T>({String? named, String? scopeId}) {
|
||||||
final dependencyKey = _createDependencyKeyFromType(T, named, scopeId);
|
final dependencyKey = _createDependencyKeyFromType(T, named, scopeId);
|
||||||
return _globalResolutionStack.contains(dependencyKey);
|
return _globalResolutionStack.contains(dependencyKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// RU: Получить текущую глобальную цепочку разрешения зависимостей.
|
/// Get current global dependency resolution chain (for debugging or diagnostics).
|
||||||
/// ENG: Get current global dependency resolution chain.
|
|
||||||
List<String> get globalResolutionChain => List.unmodifiable(_globalResolutionHistory);
|
List<String> get globalResolutionChain => List.unmodifiable(_globalResolutionHistory);
|
||||||
|
|
||||||
/// RU: Очистить все состояние детектора.
|
/// Clears all global and per-scope state in this detector.
|
||||||
/// ENG: Clear all detector state.
|
|
||||||
void clear() {
|
void clear() {
|
||||||
_globalResolutionStack.clear();
|
_globalResolutionStack.clear();
|
||||||
_globalResolutionHistory.clear();
|
_globalResolutionHistory.clear();
|
||||||
@@ -167,14 +156,7 @@ class GlobalCycleDetector {
|
|||||||
|
|
||||||
void _detectorClear(detector) => detector.clear();
|
void _detectorClear(detector) => detector.clear();
|
||||||
|
|
||||||
/// RU: Создать уникальный ключ для зависимости с учетом скоупа.
|
/// Creates a unique dependency key string including scope and name (for diagnostics/cycle checks).
|
||||||
/// ENG: Create unique key for dependency including scope.
|
|
||||||
//String _createDependencyKey<T>(String? named, String? scopeId) {
|
|
||||||
// return _createDependencyKeyFromType(T, named, scopeId);
|
|
||||||
//}
|
|
||||||
|
|
||||||
/// RU: Создать уникальный ключ для зависимости по типу с учетом скоупа.
|
|
||||||
/// ENG: Create unique key for dependency by type including scope.
|
|
||||||
String _createDependencyKeyFromType(Type type, String? named, String? scopeId) {
|
String _createDependencyKeyFromType(Type type, String? named, String? scopeId) {
|
||||||
final typeName = type.toString();
|
final typeName = type.toString();
|
||||||
final namePrefix = named != null ? '@$named' : '';
|
final namePrefix = named != null ? '@$named' : '';
|
||||||
@@ -183,40 +165,53 @@ class GlobalCycleDetector {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// RU: Улучшенный миксин для глобального обнаружения циклических зависимостей.
|
/// Enhanced mixin for global circular dependency detection, to be mixed into
|
||||||
/// ENG: Enhanced mixin for global circular dependency detection.
|
/// DI scopes or containers that want cross-scope protection.
|
||||||
|
///
|
||||||
|
/// Typical usage pattern:
|
||||||
|
/// ```dart
|
||||||
|
/// class MySubscope with GlobalCycleDetectionMixin { ... }
|
||||||
|
///
|
||||||
|
/// final scope = MySubscope();
|
||||||
|
/// scope.setScopeId('user_profile');
|
||||||
|
/// scope.enableGlobalCycleDetection();
|
||||||
|
///
|
||||||
|
/// scope.withGlobalCycleDetection(UserService, null, () {
|
||||||
|
/// // ... resolve user service and friends, auto-detects global cycles
|
||||||
|
/// });
|
||||||
|
/// ```
|
||||||
mixin GlobalCycleDetectionMixin {
|
mixin GlobalCycleDetectionMixin {
|
||||||
String? _scopeId;
|
String? _scopeId;
|
||||||
bool _globalCycleDetectionEnabled = false;
|
bool _globalCycleDetectionEnabled = false;
|
||||||
|
|
||||||
/// RU: Установить идентификатор скоупа для глобального отслеживания.
|
/// Set the scope's unique identifier for global tracking (should be called at scope initialization).
|
||||||
/// ENG: Set scope identifier for global tracking.
|
|
||||||
void setScopeId(String scopeId) {
|
void setScopeId(String scopeId) {
|
||||||
_scopeId = scopeId;
|
_scopeId = scopeId;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// RU: Получить идентификатор скоупа.
|
/// Get the scope's id, if configured.
|
||||||
/// ENG: Get scope identifier.
|
|
||||||
String? get scopeId => _scopeId;
|
String? get scopeId => _scopeId;
|
||||||
|
|
||||||
/// RU: Включить глобальное обнаружение циклических зависимостей.
|
/// Enable global cross-scope circular dependency detection.
|
||||||
/// ENG: Enable global circular dependency detection.
|
|
||||||
void enableGlobalCycleDetection() {
|
void enableGlobalCycleDetection() {
|
||||||
_globalCycleDetectionEnabled = true;
|
_globalCycleDetectionEnabled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// RU: Отключить глобальное обнаружение циклических зависимостей.
|
/// Disable global cycle detection (no cycle checks will be performed globally).
|
||||||
/// ENG: Disable global circular dependency detection.
|
|
||||||
void disableGlobalCycleDetection() {
|
void disableGlobalCycleDetection() {
|
||||||
_globalCycleDetectionEnabled = false;
|
_globalCycleDetectionEnabled = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// RU: Проверить, включено ли глобальное обнаружение циклических зависимостей.
|
/// Returns true if global cycle detection is currently enabled for this scope/container.
|
||||||
/// ENG: Check if global circular dependency detection is enabled.
|
|
||||||
bool get isGlobalCycleDetectionEnabled => _globalCycleDetectionEnabled;
|
bool get isGlobalCycleDetectionEnabled => _globalCycleDetectionEnabled;
|
||||||
|
|
||||||
/// RU: Выполнить действие с глобальным отслеживанием циклических зависимостей.
|
/// Executes [action] with global cycle detection for [dependencyType] and [named].
|
||||||
/// ENG: Execute action with global circular dependency tracking.
|
/// Throws [CircularDependencyException] if a cycle is detected.
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
/// ```dart
|
||||||
|
/// withGlobalCycleDetection(UserService, null, () => resolveUser());
|
||||||
|
/// ```
|
||||||
T withGlobalCycleDetection<T>(
|
T withGlobalCycleDetection<T>(
|
||||||
Type dependencyType,
|
Type dependencyType,
|
||||||
String? named,
|
String? named,
|
||||||
@@ -234,8 +229,7 @@ mixin GlobalCycleDetectionMixin {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// RU: Получить текущую глобальную цепочку разрешения зависимостей.
|
/// Access the current global dependency resolution chain for diagnostics.
|
||||||
/// ENG: Get current global dependency resolution chain.
|
|
||||||
List<String> get globalResolutionChain =>
|
List<String> get globalResolutionChain =>
|
||||||
GlobalCycleDetector.instance.globalResolutionChain;
|
GlobalCycleDetector.instance.globalResolutionChain;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,39 +15,71 @@ import 'dart:collection';
|
|||||||
import 'package:cherrypick/src/binding.dart';
|
import 'package:cherrypick/src/binding.dart';
|
||||||
import 'package:cherrypick/src/scope.dart';
|
import 'package:cherrypick/src/scope.dart';
|
||||||
|
|
||||||
/// RU: Класс Module является основой для пользовательских модулей.
|
/// Represents a DI module—a reusable group of dependency bindings.
|
||||||
/// Этот класс нужен для инициализации [Scope].
|
|
||||||
///
|
///
|
||||||
/// RU: The Module class is the basis for custom modules.
|
/// Extend [Module] to declaratively group related [Binding] definitions,
|
||||||
/// This class is needed to initialize [Scope].
|
/// then install your module(s) into a [Scope] for dependency resolution.
|
||||||
|
///
|
||||||
|
/// Modules make it easier to organize your DI configuration for features, layers,
|
||||||
|
/// infrastructure, or integration, and support modular app architecture.
|
||||||
|
///
|
||||||
|
/// Usage example:
|
||||||
|
/// ```dart
|
||||||
|
/// class AppModule extends Module {
|
||||||
|
/// @override
|
||||||
|
/// void builder(Scope currentScope) {
|
||||||
|
/// bind<NetworkService>().toProvide(() => NetworkService());
|
||||||
|
/// bind<AuthService>().toProvide(() => AuthService(currentScope.resolve<NetworkService>()));
|
||||||
|
/// bind<Config>().toInstance(Config.dev());
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// // Installing the module into the root DI scope:
|
||||||
|
/// final rootScope = CherryPick.openRootScope();
|
||||||
|
/// rootScope.installModules([AppModule()]);
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Combine several modules and submodules to implement scalable architectures.
|
||||||
///
|
///
|
||||||
abstract class Module {
|
abstract class Module {
|
||||||
final Set<Binding> _bindingSet = HashSet();
|
final Set<Binding> _bindingSet = HashSet();
|
||||||
|
|
||||||
/// RU: Метод добавляет в коллекцию модуля [Binding] экземпляр.
|
/// Begins the declaration of a new binding within this module.
|
||||||
///
|
///
|
||||||
/// ENG: The method adds an instance to the collection of the [Binding] module.
|
/// Typically used within [builder] to register all needed dependency bindings.
|
||||||
///
|
///
|
||||||
/// return [Binding<T>]
|
/// Example:
|
||||||
|
/// ```dart
|
||||||
|
/// bind<Api>().toProvide(() => MockApi());
|
||||||
|
/// bind<Config>().toInstance(Config.dev());
|
||||||
|
/// ```
|
||||||
Binding<T> bind<T>() {
|
Binding<T> bind<T>() {
|
||||||
final binding = Binding<T>();
|
final binding = Binding<T>();
|
||||||
_bindingSet.add(binding);
|
_bindingSet.add(binding);
|
||||||
return binding;
|
return binding;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// RU: Метод возвращает коллекцию [Binding] экземпляров.
|
/// Returns the set of all [Binding] instances registered in this module.
|
||||||
///
|
///
|
||||||
/// ENG: The method returns a collection of [Binding] instances.
|
/// This is typically used internally by [Scope] during module installation,
|
||||||
///
|
/// but can also be used for diagnostics or introspection.
|
||||||
/// return [Set<Binding>]
|
|
||||||
Set<Binding> get bindingSet => _bindingSet;
|
Set<Binding> get bindingSet => _bindingSet;
|
||||||
|
|
||||||
/// RU: Абстрактный метод для инициализации пользовательских экземпляров.
|
/// Abstract method where all dependency bindings are registered.
|
||||||
/// В этом методе осуществляется конфигурация зависимостей.
|
|
||||||
///
|
///
|
||||||
/// ENG: Abstract method for initializing custom instances.
|
/// Override this method in your custom module subclass to declare
|
||||||
/// This method configures dependencies.
|
/// all dependency bindings to be provided by this module.
|
||||||
///
|
///
|
||||||
/// return [void]
|
/// The provided [currentScope] can be used for resolving other dependencies,
|
||||||
|
/// accessing configuration, or controlling binding behavior dynamically.
|
||||||
|
///
|
||||||
|
/// Example (with dependency chaining):
|
||||||
|
/// ```dart
|
||||||
|
/// @override
|
||||||
|
/// void builder(Scope currentScope) {
|
||||||
|
/// bind<ApiClient>().toProvide(() => RestApi());
|
||||||
|
/// bind<UserRepo>().toProvide(() => UserRepo(currentScope.resolve<ApiClient>()));
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
void builder(Scope currentScope);
|
void builder(Scope currentScope);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,37 @@ import 'package:cherrypick/src/module.dart';
|
|||||||
import 'package:cherrypick/src/observer.dart';
|
import 'package:cherrypick/src/observer.dart';
|
||||||
// import 'package:cherrypick/src/log_format.dart';
|
// import 'package:cherrypick/src/log_format.dart';
|
||||||
|
|
||||||
|
/// Represents a DI scope (container) for modules, subscopes,
|
||||||
|
/// and dependency resolution (sync/async) in CherryPick.
|
||||||
|
///
|
||||||
|
/// Scopes provide hierarchical DI: you can resolve dependencies from parents,
|
||||||
|
/// override or isolate modules, and manage scope-specific singletons.
|
||||||
|
///
|
||||||
|
/// Each scope:
|
||||||
|
/// - Can install modules ([installModules]) that define [Binding]s
|
||||||
|
/// - Supports parent-child scope tree (see [openSubScope])
|
||||||
|
/// - Can resolve dependencies synchronously ([resolve]) or asynchronously ([resolveAsync])
|
||||||
|
/// - Cleans up resources for [Disposable] objects (see [dispose])
|
||||||
|
/// - Detects dependency cycles (local and global, if enabled)
|
||||||
|
///
|
||||||
|
/// Example usage:
|
||||||
|
/// ```dart
|
||||||
|
/// final rootScope = CherryPick.openRootScope();
|
||||||
|
/// rootScope.installModules([AppModule()]);
|
||||||
|
///
|
||||||
|
/// // Synchronous resolution:
|
||||||
|
/// final auth = rootScope.resolve<AuthService>();
|
||||||
|
///
|
||||||
|
/// // Asynchronous resolution:
|
||||||
|
/// final db = await rootScope.resolveAsync<Database>();
|
||||||
|
///
|
||||||
|
/// // Open a child scope (for a feature, page, or test):
|
||||||
|
/// final userScope = rootScope.openSubScope('user');
|
||||||
|
/// userScope.installModules([UserModule()]);
|
||||||
|
///
|
||||||
|
/// // Proper resource cleanup (calls dispose() on tracked objects)
|
||||||
|
/// await CherryPick.closeRootScope();
|
||||||
|
/// ```
|
||||||
class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin {
|
class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin {
|
||||||
final Scope? _parentScope;
|
final Scope? _parentScope;
|
||||||
|
|
||||||
@@ -32,11 +63,7 @@ class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin {
|
|||||||
/// COLLECTS all resolved instances that implement [Disposable].
|
/// COLLECTS all resolved instances that implement [Disposable].
|
||||||
final Set<Disposable> _disposables = HashSet();
|
final Set<Disposable> _disposables = HashSet();
|
||||||
|
|
||||||
/// RU: Метод возвращает родительский [Scope].
|
/// Returns the parent [Scope] if present, or null if this is the root scope.
|
||||||
///
|
|
||||||
/// ENG: The method returns the parent [Scope].
|
|
||||||
///
|
|
||||||
/// return [Scope]
|
|
||||||
Scope? get parentScope => _parentScope;
|
Scope? get parentScope => _parentScope;
|
||||||
|
|
||||||
final Map<String, Scope> _scopeMap = HashMap();
|
final Map<String, Scope> _scopeMap = HashMap();
|
||||||
@@ -61,8 +88,9 @@ class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin {
|
|||||||
final Map<Object, Map<String?, BindingResolver>> _bindingResolvers = {};
|
final Map<Object, Map<String?, BindingResolver>> _bindingResolvers = {};
|
||||||
|
|
||||||
|
|
||||||
/// RU: Генерирует уникальный идентификатор для скоупа.
|
/// Generates a unique identifier string for this scope instance.
|
||||||
/// ENG: Generates unique identifier for scope.
|
///
|
||||||
|
/// Used internally for diagnostics, logging and global scope tracking.
|
||||||
String _generateScopeId() {
|
String _generateScopeId() {
|
||||||
final random = Random();
|
final random = Random();
|
||||||
final timestamp = DateTime.now().millisecondsSinceEpoch;
|
final timestamp = DateTime.now().millisecondsSinceEpoch;
|
||||||
@@ -70,16 +98,20 @@ class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin {
|
|||||||
return 'scope_${timestamp}_$randomPart';
|
return 'scope_${timestamp}_$randomPart';
|
||||||
}
|
}
|
||||||
|
|
||||||
/// RU: Метод открывает дочерний (дополнительный) [Scope].
|
/// Opens a named child [Scope] (subscope) as a descendant of the current scope.
|
||||||
///
|
///
|
||||||
/// ENG: The method opens child (additional) [Scope].
|
/// Subscopes inherit modules and DI context from their parent, but can override or extend bindings.
|
||||||
|
/// Useful for feature-isolation, screens, request/transaction lifetimes, and test separation.
|
||||||
///
|
///
|
||||||
/// return [Scope]
|
/// Example:
|
||||||
|
/// ```dart
|
||||||
|
/// final featureScope = rootScope.openSubScope('feature');
|
||||||
|
/// featureScope.installModules([FeatureModule()]);
|
||||||
|
/// final dep = featureScope.resolve<MyDep>();
|
||||||
|
/// ```
|
||||||
Scope openSubScope(String name) {
|
Scope openSubScope(String name) {
|
||||||
if (!_scopeMap.containsKey(name)) {
|
if (!_scopeMap.containsKey(name)) {
|
||||||
final childScope = Scope(this, observer: observer); // Наследуем observer вниз по иерархии
|
final childScope = Scope(this, observer: observer);
|
||||||
// print removed (trace)
|
|
||||||
// Наследуем настройки обнаружения циклических зависимостей
|
|
||||||
if (isCycleDetectionEnabled) {
|
if (isCycleDetectionEnabled) {
|
||||||
childScope.enableCycleDetection();
|
childScope.enableCycleDetection();
|
||||||
}
|
}
|
||||||
@@ -101,16 +133,19 @@ class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin {
|
|||||||
return _scopeMap[name]!;
|
return _scopeMap[name]!;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// RU: Метод закрывает дочерний (дополнительный) [Scope] асинхронно.
|
/// Asynchronously closes and disposes a named child [Scope] (subscope).
|
||||||
///
|
///
|
||||||
/// ENG: The method closes child (additional) [Scope] asynchronously.
|
/// Ensures all [Disposable] objects and internal modules
|
||||||
|
/// in the subscope are properly cleaned up. Also removes any global cycle detectors associated with the subscope.
|
||||||
///
|
///
|
||||||
/// return [Future<void>]
|
/// Example:
|
||||||
|
/// ```dart
|
||||||
|
/// await rootScope.closeSubScope('feature');
|
||||||
|
/// ```
|
||||||
Future<void> closeSubScope(String name) async {
|
Future<void> closeSubScope(String name) async {
|
||||||
final childScope = _scopeMap[name];
|
final childScope = _scopeMap[name];
|
||||||
if (childScope != null) {
|
if (childScope != null) {
|
||||||
await childScope.dispose(); // асинхронный вызов
|
await childScope.dispose();
|
||||||
// Очищаем детектор для дочернего скоупа
|
|
||||||
if (childScope.scopeId != null) {
|
if (childScope.scopeId != null) {
|
||||||
GlobalCycleDetector.instance.removeScopeDetector(childScope.scopeId!);
|
GlobalCycleDetector.instance.removeScopeDetector(childScope.scopeId!);
|
||||||
}
|
}
|
||||||
@@ -129,11 +164,15 @@ class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin {
|
|||||||
_scopeMap.remove(name);
|
_scopeMap.remove(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// RU: Метод инициализирует пользовательские модули в [Scope].
|
/// Installs a list of custom [Module]s into the [Scope].
|
||||||
///
|
///
|
||||||
/// ENG: The method initializes custom modules in [Scope].
|
/// Each module registers bindings and configuration for dependencies.
|
||||||
|
/// After calling this, bindings are immediately available for resolve/tryResolve.
|
||||||
///
|
///
|
||||||
/// return [Scope]
|
/// Example:
|
||||||
|
/// ```dart
|
||||||
|
/// rootScope.installModules([AppModule(), NetworkModule()]);
|
||||||
|
/// ```
|
||||||
Scope installModules(List<Module> modules) {
|
Scope installModules(List<Module> modules) {
|
||||||
_modulesList.addAll(modules);
|
_modulesList.addAll(modules);
|
||||||
if (modules.isNotEmpty) {
|
if (modules.isNotEmpty) {
|
||||||
@@ -153,7 +192,7 @@ class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
module.builder(this);
|
module.builder(this);
|
||||||
// После builder: для всех новых биндингов
|
// Associate bindings with this scope's observer
|
||||||
for (final binding in module.bindingSet) {
|
for (final binding in module.bindingSet) {
|
||||||
binding.observer = observer;
|
binding.observer = observer;
|
||||||
binding.logAllDeferred();
|
binding.logAllDeferred();
|
||||||
@@ -163,11 +202,15 @@ class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// RU: Метод удаляет пользовательские модули из [Scope].
|
/// Removes all installed [Module]s and their bindings from this [Scope].
|
||||||
///
|
///
|
||||||
/// ENG: This method removes custom modules from [Scope].
|
/// Typically used in tests or when resetting app configuration/runtime environment.
|
||||||
|
/// Note: this does not dispose resolved [Disposable]s (call [dispose] for that).
|
||||||
///
|
///
|
||||||
/// return [Scope]
|
/// Example:
|
||||||
|
/// ```dart
|
||||||
|
/// testScope.dropModules();
|
||||||
|
/// ```
|
||||||
Scope dropModules() {
|
Scope dropModules() {
|
||||||
if (_modulesList.isNotEmpty) {
|
if (_modulesList.isNotEmpty) {
|
||||||
observer.onModulesRemoved(
|
observer.onModulesRemoved(
|
||||||
@@ -188,24 +231,22 @@ class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// RU: Возвращает найденную зависимость, определенную параметром типа [T].
|
/// Resolves a dependency of type [T], optionally by name and with params.
|
||||||
/// Выдает [StateError], если зависимость не может быть разрешена.
|
|
||||||
/// Если вы хотите получить [null], если зависимость не может быть найдена,
|
|
||||||
/// то используйте вместо этого [tryResolve]
|
|
||||||
/// return - возвращает объект типа [T] или [StateError]
|
|
||||||
///
|
///
|
||||||
/// ENG: Returns the found dependency specified by the type parameter [T].
|
/// Throws [StateError] if the dependency cannot be resolved. (Use [tryResolve] for fallible lookup).
|
||||||
/// Throws [StateError] if the dependency cannot be resolved.
|
/// Resolves from installed modules or recurses up the parent scope chain.
|
||||||
/// If you want to get [null] if the dependency cannot be found then use [tryResolve] instead
|
|
||||||
/// return - returns an object of type [T] or [StateError]
|
|
||||||
///
|
///
|
||||||
|
/// Example:
|
||||||
|
/// ```dart
|
||||||
|
/// final logger = scope.resolve<Logger>();
|
||||||
|
/// final special = scope.resolve<Service>(named: 'special');
|
||||||
|
/// ```
|
||||||
T resolve<T>({String? named, dynamic params}) {
|
T resolve<T>({String? named, dynamic params}) {
|
||||||
observer.onInstanceRequested(T.toString(), T, scopeName: scopeId);
|
observer.onInstanceRequested(T.toString(), T, scopeName: scopeId);
|
||||||
// Используем глобальное отслеживание, если включено
|
|
||||||
T result;
|
T result;
|
||||||
if (isGlobalCycleDetectionEnabled) {
|
if (isGlobalCycleDetectionEnabled) {
|
||||||
try {
|
try {
|
||||||
result = withGlobalCycleDetection<T>(T, named, () {
|
result = withGlobalCycleDetection<T>(T, named, () {
|
||||||
return _resolveWithLocalDetection<T>(named: named, params: params);
|
return _resolveWithLocalDetection<T>(named: named, params: params);
|
||||||
});
|
});
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
@@ -232,8 +273,9 @@ class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// RU: Разрешение с локальным детектором циклических зависимостей.
|
/// Resolves [T] using the local cycle detector for this scope.
|
||||||
/// ENG: Resolution with local circular dependency detector.
|
/// Throws [StateError] if not found or cycle is detected.
|
||||||
|
/// Used internally by [resolve].
|
||||||
T _resolveWithLocalDetection<T>({String? named, dynamic params}) {
|
T _resolveWithLocalDetection<T>({String? named, dynamic params}) {
|
||||||
return withCycleDetection<T>(T, named, () {
|
return withCycleDetection<T>(T, named, () {
|
||||||
var resolved = _tryResolveInternal<T>(named: named, params: params);
|
var resolved = _tryResolveInternal<T>(named: named, params: params);
|
||||||
@@ -262,11 +304,16 @@ class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// RU: Возвращает найденную зависимость типа [T] или null, если она не может быть найдена.
|
/// Attempts to resolve a dependency of type [T], optionally by name and with params.
|
||||||
/// ENG: Returns found dependency of type [T] or null if it cannot be found.
|
|
||||||
///
|
///
|
||||||
|
/// Returns the resolved dependency, or `null` if not found.
|
||||||
|
/// Does not throw if missing (unlike [resolve]).
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
/// ```dart
|
||||||
|
/// final maybeDb = scope.tryResolve<Database>();
|
||||||
|
/// ```
|
||||||
T? tryResolve<T>({String? named, dynamic params}) {
|
T? tryResolve<T>({String? named, dynamic params}) {
|
||||||
// Используем глобальное отслеживание, если включено
|
|
||||||
T? result;
|
T? result;
|
||||||
if (isGlobalCycleDetectionEnabled) {
|
if (isGlobalCycleDetectionEnabled) {
|
||||||
result = withGlobalCycleDetection<T?>(T, named, () {
|
result = withGlobalCycleDetection<T?>(T, named, () {
|
||||||
@@ -279,8 +326,8 @@ class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// RU: Попытка разрешения с локальным детектором циклических зависимостей.
|
/// Attempts to resolve [T] using the local cycle detector. Returns null if not found or cycle.
|
||||||
/// ENG: Try resolution with local circular dependency detector.
|
/// Used internally by [tryResolve].
|
||||||
T? _tryResolveWithLocalDetection<T>({String? named, dynamic params}) {
|
T? _tryResolveWithLocalDetection<T>({String? named, dynamic params}) {
|
||||||
if (isCycleDetectionEnabled) {
|
if (isCycleDetectionEnabled) {
|
||||||
return withCycleDetection<T?>(T, named, () {
|
return withCycleDetection<T?>(T, named, () {
|
||||||
@@ -291,29 +338,25 @@ class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// RU: Внутренний метод для разрешения зависимостей без проверки циклических зависимостей.
|
/// Locates and resolves [T] without cycle detection (direct lookup).
|
||||||
/// ENG: Internal method for dependency resolution without circular dependency checking.
|
/// Returns null if not found. Used internally.
|
||||||
T? _tryResolveInternal<T>({String? named, dynamic params}) {
|
T? _tryResolveInternal<T>({String? named, dynamic params}) {
|
||||||
final resolver = _findBindingResolver<T>(named);
|
final resolver = _findBindingResolver<T>(named);
|
||||||
|
// 1 - Try from own modules; 2 - Fallback to parent
|
||||||
// 1 Поиск зависимости по всем модулям текущего скоупа
|
|
||||||
return resolver?.resolveSync(params) ??
|
return resolver?.resolveSync(params) ??
|
||||||
// 2 Поиск зависимостей в родительском скоупе
|
|
||||||
_parentScope?.tryResolve(named: named, params: params);
|
_parentScope?.tryResolve(named: named, params: params);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// RU: Асинхронно возвращает найденную зависимость, определенную параметром типа [T].
|
/// Asynchronously resolves a dependency of type [T].
|
||||||
/// Выдает [StateError], если зависимость не может быть разрешена.
|
|
||||||
/// Если хотите получить [null], если зависимость не может быть найдена, используйте [tryResolveAsync].
|
|
||||||
/// return - возвращает объект типа [T] or [StateError]
|
|
||||||
///
|
///
|
||||||
/// ENG: Asynchronously returns the found dependency specified by the type parameter [T].
|
/// Throws [StateError] if not found. (Use [tryResolveAsync] for a fallible async resolve.)
|
||||||
/// Throws [StateError] if the dependency cannot be resolved.
|
|
||||||
/// If you want to get [null] if the dependency cannot be found, use [tryResolveAsync] instead.
|
|
||||||
/// return - returns an object of type [T] or [StateError]
|
|
||||||
///
|
///
|
||||||
|
/// Example:
|
||||||
|
/// ```dart
|
||||||
|
/// final db = await scope.resolveAsync<Database>();
|
||||||
|
/// final special = await scope.resolveAsync<Service>(named: "special");
|
||||||
|
/// ```
|
||||||
Future<T> resolveAsync<T>({String? named, dynamic params}) async {
|
Future<T> resolveAsync<T>({String? named, dynamic params}) async {
|
||||||
// Используем глобальное отслеживание, если включено
|
|
||||||
T result;
|
T result;
|
||||||
if (isGlobalCycleDetectionEnabled) {
|
if (isGlobalCycleDetectionEnabled) {
|
||||||
result = await withGlobalCycleDetection<Future<T>>(T, named, () async {
|
result = await withGlobalCycleDetection<Future<T>>(T, named, () async {
|
||||||
@@ -326,8 +369,8 @@ class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// RU: Асинхронное разрешение с локальным детектором циклических зависимостей.
|
/// Resolves [T] asynchronously using local cycle detector. Throws if not found.
|
||||||
/// ENG: Async resolution with local circular dependency detector.
|
/// Internal implementation for async [resolveAsync].
|
||||||
Future<T> _resolveAsyncWithLocalDetection<T>({String? named, dynamic params}) async {
|
Future<T> _resolveAsyncWithLocalDetection<T>({String? named, dynamic params}) async {
|
||||||
return withCycleDetection<Future<T>>(T, named, () async {
|
return withCycleDetection<Future<T>>(T, named, () async {
|
||||||
var resolved = await _tryResolveAsyncInternal<T>(named: named, params: params);
|
var resolved = await _tryResolveAsyncInternal<T>(named: named, params: params);
|
||||||
@@ -356,8 +399,14 @@ class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Attempts to asynchronously resolve a dependency of type [T].
|
||||||
|
/// Returns the dependency or null if not present (never throws).
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
/// ```dart
|
||||||
|
/// final user = await scope.tryResolveAsync<User>();
|
||||||
|
/// ```
|
||||||
Future<T?> tryResolveAsync<T>({String? named, dynamic params}) async {
|
Future<T?> tryResolveAsync<T>({String? named, dynamic params}) async {
|
||||||
// Используем глобальное отслеживание, если включено
|
|
||||||
T? result;
|
T? result;
|
||||||
if (isGlobalCycleDetectionEnabled) {
|
if (isGlobalCycleDetectionEnabled) {
|
||||||
result = await withGlobalCycleDetection<Future<T?>>(T, named, () async {
|
result = await withGlobalCycleDetection<Future<T?>>(T, named, () async {
|
||||||
@@ -370,8 +419,8 @@ class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// RU: Асинхронная попытка разрешения с локальным детектором циклических зависимостей.
|
/// Attempts to resolve [T] asynchronously using local cycle detector. Returns null if missing.
|
||||||
/// ENG: Async try resolution with local circular dependency detector.
|
/// Internal implementation for async [tryResolveAsync].
|
||||||
Future<T?> _tryResolveAsyncWithLocalDetection<T>({String? named, dynamic params}) async {
|
Future<T?> _tryResolveAsyncWithLocalDetection<T>({String? named, dynamic params}) async {
|
||||||
if (isCycleDetectionEnabled) {
|
if (isCycleDetectionEnabled) {
|
||||||
return withCycleDetection<Future<T?>>(T, named, () async {
|
return withCycleDetection<Future<T?>>(T, named, () async {
|
||||||
@@ -382,21 +431,21 @@ class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// RU: Внутренний метод для асинхронного разрешения зависимостей без проверки циклических зависимостей.
|
/// Direct async resolution for [T] without cycle check. Returns null if missing. Internal use only.
|
||||||
/// ENG: Internal method for async dependency resolution without circular dependency checking.
|
|
||||||
Future<T?> _tryResolveAsyncInternal<T>({String? named, dynamic params}) async {
|
Future<T?> _tryResolveAsyncInternal<T>({String? named, dynamic params}) async {
|
||||||
final resolver = _findBindingResolver<T>(named);
|
final resolver = _findBindingResolver<T>(named);
|
||||||
|
// 1 - Try from own modules; 2 - Fallback to parent
|
||||||
// 1 Поиск зависимости по всем модулям текущего скоупа
|
|
||||||
return resolver?.resolveAsync(params) ??
|
return resolver?.resolveAsync(params) ??
|
||||||
// 2 Поиск зависимостей в родительском скоупе
|
|
||||||
_parentScope?.tryResolveAsync(named: named, params: params);
|
_parentScope?.tryResolveAsync(named: named, params: params);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Looks up the [BindingResolver] for [T] and [named] within this scope.
|
||||||
|
/// Returns null if none found. Internal use only.
|
||||||
BindingResolver<T>? _findBindingResolver<T>(String? named) =>
|
BindingResolver<T>? _findBindingResolver<T>(String? named) =>
|
||||||
_bindingResolvers[T]?[named] as BindingResolver<T>?;
|
_bindingResolvers[T]?[named] as BindingResolver<T>?;
|
||||||
|
|
||||||
// Индексируем все binding’и после каждого installModules/dropModules
|
/// Rebuilds the internal index of all [BindingResolver]s from installed modules.
|
||||||
|
/// Called after [installModules] and [dropModules]. Internal use only.
|
||||||
void _rebuildResolversIndex() {
|
void _rebuildResolversIndex() {
|
||||||
_bindingResolvers.clear();
|
_bindingResolvers.clear();
|
||||||
for (var module in _modulesList) {
|
for (var module in _modulesList) {
|
||||||
@@ -408,14 +457,24 @@ class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// INTERNAL: Tracks Disposable objects
|
/// Tracks resolved [Disposable] instances, to ensure dispose is called automatically.
|
||||||
|
/// Internal use only.
|
||||||
void _trackDisposable(Object? obj) {
|
void _trackDisposable(Object? obj) {
|
||||||
if (obj is Disposable && !_disposables.contains(obj)) {
|
if (obj is Disposable && !_disposables.contains(obj)) {
|
||||||
_disposables.add(obj);
|
_disposables.add(obj);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Calls dispose on all tracked disposables and child scopes recursively (async).
|
/// Asynchronously disposes this [Scope], all tracked [Disposable] objects, and recursively
|
||||||
|
/// all its child subscopes.
|
||||||
|
///
|
||||||
|
/// This method should always be called when a scope is no longer needed
|
||||||
|
/// to guarantee timely resource cleanup (files, sockets, streams, handles, etc).
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
/// ```dart
|
||||||
|
/// await myScope.dispose();
|
||||||
|
/// ```
|
||||||
Future<void> dispose() async {
|
Future<void> dispose() async {
|
||||||
// First dispose children scopes
|
// First dispose children scopes
|
||||||
for (final subScope in _scopeMap.values) {
|
for (final subScope in _scopeMap.values) {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
name: cherrypick
|
name: cherrypick
|
||||||
description: Cherrypick is a small dependency injection (DI) library for dart/flutter projects.
|
description: Cherrypick is a small dependency injection (DI) library for dart/flutter projects.
|
||||||
version: 3.0.0-dev.8
|
version: 3.0.0-dev.9
|
||||||
homepage: https://pese-git.github.io/cherrypick-site/
|
homepage: https://pese-git.github.io/cherrypick-site/
|
||||||
documentation: https://github.com/pese-git/cherrypick/wiki
|
documentation: https://github.com/pese-git/cherrypick/wiki
|
||||||
repository: https://github.com/pese-git/cherrypick
|
repository: https://github.com/pese-git/cherrypick
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
## 1.1.2-dev.0
|
||||||
|
|
||||||
|
- **DOCS**(annotations): unify and improve English DartDoc for all DI annotations.
|
||||||
|
|
||||||
## 1.1.1
|
## 1.1.1
|
||||||
|
|
||||||
- **FIX**(license): correct urls.
|
- **FIX**(license): correct urls.
|
||||||
|
|||||||
@@ -12,6 +12,9 @@
|
|||||||
# The core lints are also what is used by pub.dev for scoring packages.
|
# The core lints are also what is used by pub.dev for scoring packages.
|
||||||
|
|
||||||
include: package:lints/recommended.yaml
|
include: package:lints/recommended.yaml
|
||||||
|
analyzer:
|
||||||
|
errors:
|
||||||
|
camel_case_types: ignore
|
||||||
|
|
||||||
# Uncomment the following section to specify additional rules.
|
# Uncomment the following section to specify additional rules.
|
||||||
|
|
||||||
|
|||||||
@@ -13,22 +13,30 @@
|
|||||||
|
|
||||||
import 'package:meta/meta.dart';
|
import 'package:meta/meta.dart';
|
||||||
|
|
||||||
/// Annotation for field injection in CherryPick DI framework.
|
/// Marks a field for dependency injection by CherryPick's code generator.
|
||||||
/// Apply this to a field, and the code generator will automatically inject
|
|
||||||
/// the appropriate dependency into it.
|
|
||||||
///
|
///
|
||||||
/// ---
|
/// Use `@inject()` on fields within a class marked with `@injectable()`.
|
||||||
|
/// Such fields will be automatically injected from the DI [Scope]
|
||||||
|
/// when using the generated mixin or calling `.injectFields()`.
|
||||||
///
|
///
|
||||||
/// Аннотация для внедрения зависимости в поле через фреймворк CherryPick DI.
|
/// Example:
|
||||||
/// Поместите её на поле класса — генератор кода автоматически подставит нужную зависимость.
|
|
||||||
///
|
|
||||||
/// Example / Пример:
|
|
||||||
/// ```dart
|
/// ```dart
|
||||||
/// @inject()
|
/// import 'package:cherrypick_annotations/cherrypick_annotations.dart';
|
||||||
/// late final SomeService service;
|
///
|
||||||
|
/// @injectable()
|
||||||
|
/// class LoginScreen with _\$LoginScreen {
|
||||||
|
/// @inject()
|
||||||
|
/// late final AuthService authService;
|
||||||
|
///
|
||||||
|
/// @inject()
|
||||||
|
/// @named('main')
|
||||||
|
/// late final ApiClient api;
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// // After running build_runner, call:
|
||||||
|
/// // LoginScreen().injectFields();
|
||||||
/// ```
|
/// ```
|
||||||
@experimental
|
@experimental
|
||||||
// ignore: camel_case_types
|
|
||||||
final class inject {
|
final class inject {
|
||||||
const inject();
|
const inject();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,26 +13,31 @@
|
|||||||
|
|
||||||
import 'package:meta/meta.dart';
|
import 'package:meta/meta.dart';
|
||||||
|
|
||||||
/// Marks a class as injectable for the CherryPick dependency injection framework.
|
/// Marks a class as injectable, enabling automatic field injection by CherryPick's code generator.
|
||||||
/// If a class is annotated with [@injectable()], the code generator will
|
|
||||||
/// create a mixin to perform automatic injection of fields marked with [@inject].
|
|
||||||
///
|
///
|
||||||
/// ---
|
/// Use `@injectable()` on a class whose fields (marked with `@inject`) you want to be automatically injected from the DI [Scope].
|
||||||
|
/// When used together with code generation (see cherrypick_generator), a mixin will be generated to inject fields.
|
||||||
///
|
///
|
||||||
/// Помечает класс как внедряемый для фреймворка внедрения зависимостей CherryPick.
|
/// Example:
|
||||||
/// Если класс помечен аннотацией [@injectable()], генератор создаст миксин
|
|
||||||
/// для автоматического внедрения полей, отмеченных [@inject].
|
|
||||||
///
|
|
||||||
/// Example / Пример:
|
|
||||||
/// ```dart
|
/// ```dart
|
||||||
|
/// import 'package:cherrypick_annotations/cherrypick_annotations.dart';
|
||||||
|
///
|
||||||
/// @injectable()
|
/// @injectable()
|
||||||
/// class MyWidget extends StatelessWidget {
|
/// class ProfileScreen with _\$ProfileScreen {
|
||||||
/// @inject()
|
/// @inject()
|
||||||
/// late final MyService service;
|
/// late final UserManager manager;
|
||||||
|
///
|
||||||
|
/// @inject()
|
||||||
|
/// @named('main')
|
||||||
|
/// late final ApiClient api;
|
||||||
/// }
|
/// }
|
||||||
|
///
|
||||||
|
/// // After running build_runner, call:
|
||||||
|
/// // profileScreen.injectFields();
|
||||||
/// ```
|
/// ```
|
||||||
|
///
|
||||||
|
/// After running the generator, the mixin (`_\$ProfileScreen`) will be available to help auto-inject all [@inject] fields in your widget/service/controller.
|
||||||
@experimental
|
@experimental
|
||||||
// ignore: camel_case_types
|
|
||||||
final class injectable {
|
final class injectable {
|
||||||
const injectable();
|
const injectable();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,58 +11,39 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
|
|
||||||
/// ENGLISH:
|
import 'package:meta/meta.dart';
|
||||||
/// Annotation to specify that a new instance should be provided on each request.
|
|
||||||
|
/// Marks a provider method or class to always create a new instance (factory) in CherryPick DI.
|
||||||
///
|
///
|
||||||
/// Use the `@instance()` annotation for methods or classes in your DI module
|
/// Use `@instance()` to annotate methods or classes in your DI module/class
|
||||||
/// to declare that the DI container must create a new object every time
|
/// when you want a new object to be created on every injection (no singleton caching).
|
||||||
/// the dependency is injected (i.e., no singleton behavior).
|
/// The default DI lifecycle is instance/factory unless otherwise specified.
|
||||||
///
|
///
|
||||||
/// Example:
|
/// ### Example (in a module method)
|
||||||
/// ```dart
|
/// ```dart
|
||||||
|
/// import 'package:cherrypick_annotations/cherrypick_annotations.dart';
|
||||||
|
///
|
||||||
/// @module()
|
/// @module()
|
||||||
/// abstract class AppModule extends Module {
|
/// abstract class FeatureModule {
|
||||||
/// @instance()
|
/// @instance()
|
||||||
/// Foo foo() => Foo();
|
/// MyService provideService() => MyService();
|
||||||
|
///
|
||||||
|
/// @singleton()
|
||||||
|
/// Logger provideLogger() => Logger();
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// This will generate:
|
/// ### Example (on a class, with @injectable)
|
||||||
/// ```dart
|
/// ```dart
|
||||||
/// final class $AppModule extends AppModule {
|
/// @injectable()
|
||||||
/// @override
|
/// @instance()
|
||||||
/// void builder(Scope currentScope) {
|
/// class MyFactoryClass {
|
||||||
/// bind<Foo>().toInstance(() => foo());
|
/// // ...
|
||||||
/// }
|
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// RUSSIAN (Русский):
|
/// See also: [@singleton]
|
||||||
/// Аннотация для создания нового экземпляра при каждом запросе.
|
@experimental
|
||||||
///
|
|
||||||
/// Используйте `@instance()` для методов или классов в DI-модуле,
|
|
||||||
/// чтобы указать, что контейнер внедрения зависимостей должен создавать
|
|
||||||
/// новый объект при каждом обращении к зависимости (то есть, не синглтон).
|
|
||||||
///
|
|
||||||
/// Пример:
|
|
||||||
/// ```dart
|
|
||||||
/// @module()
|
|
||||||
/// abstract class AppModule extends Module {
|
|
||||||
/// @instance()
|
|
||||||
/// Foo foo() => Foo();
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// Будет сгенерирован следующий код:
|
|
||||||
/// ```dart
|
|
||||||
/// final class $AppModule extends AppModule {
|
|
||||||
/// @override
|
|
||||||
/// void builder(Scope currentScope) {
|
|
||||||
/// bind<Foo>().toInstance(() => foo());
|
|
||||||
/// }
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
// ignore: camel_case_types
|
|
||||||
final class instance {
|
final class instance {
|
||||||
const instance();
|
const instance();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,59 +11,40 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
|
|
||||||
/// ENGLISH:
|
import 'package:meta/meta.dart';
|
||||||
/// Annotation for marking Dart classes or libraries as modules.
|
|
||||||
|
/// Marks an abstract Dart class as a dependency injection module for CherryPick code generation.
|
||||||
///
|
///
|
||||||
/// Use the `@module()` annotation on abstract classes (or on a library)
|
/// Use `@module()` on your abstract class to indicate it provides DI bindings (via provider methods).
|
||||||
/// to indicate that the class represents a DI (Dependency Injection) module.
|
/// This enables code generation of a concrete module that registers all bindings from your methods.
|
||||||
/// This is commonly used in code generation tools to automatically register
|
|
||||||
/// and configure dependencies defined within the module.
|
|
||||||
///
|
///
|
||||||
/// Example:
|
/// Typical usage:
|
||||||
/// ```dart
|
/// ```dart
|
||||||
|
/// import 'package:cherrypick_annotations/cherrypick_annotations.dart';
|
||||||
|
///
|
||||||
/// @module()
|
/// @module()
|
||||||
/// abstract class AppModule extends Module {
|
/// abstract class AppModule {
|
||||||
/// // Dependency definitions go here.
|
/// @singleton()
|
||||||
|
/// Logger provideLogger() => Logger();
|
||||||
|
///
|
||||||
|
/// @named('mock')
|
||||||
|
/// ApiClient mockApi() => MockApiClient();
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// Generates code like:
|
/// The generated code will look like:
|
||||||
/// ```dart
|
/// ```dart
|
||||||
/// final class $AppModule extends AppModule {
|
/// final class $AppModule extends AppModule {
|
||||||
/// @override
|
/// @override
|
||||||
/// void builder(Scope currentScope) {
|
/// void builder(Scope currentScope) {
|
||||||
/// // Dependency registration...
|
/// // Dependency registration code...
|
||||||
/// }
|
/// }
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// RUSSIAN (Русский):
|
/// See also: [@provide], [@singleton], [@instance], [@named]
|
||||||
/// Аннотация для пометки классов или библиотек Dart как модуля.
|
@experimental
|
||||||
///
|
|
||||||
/// Используйте `@module()` для абстрактных классов (или библиотек), чтобы
|
|
||||||
/// показать, что класс реализует DI-модуль (Dependency Injection).
|
|
||||||
/// Обычно используется генераторами кода для автоматической регистрации
|
|
||||||
/// и конфигурирования зависимостей, определённых в модуле.
|
|
||||||
///
|
|
||||||
/// Пример:
|
|
||||||
/// ```dart
|
|
||||||
/// @module()
|
|
||||||
/// abstract class AppModule extends Module {
|
|
||||||
/// // Определения зависимостей
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// Будет сгенерирован код:
|
|
||||||
/// ```dart
|
|
||||||
/// final class $AppModule extends AppModule {
|
|
||||||
/// @override
|
|
||||||
/// void builder(Scope currentScope) {
|
|
||||||
/// // Регистрация зависимостей...
|
|
||||||
/// }
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
// ignore: camel_case_types
|
|
||||||
final class module {
|
final class module {
|
||||||
/// Creates a [module] annotation.
|
/// Creates a [module] annotation for use on a DI module class.
|
||||||
const module();
|
const module();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,67 +11,52 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
|
|
||||||
/// ENGLISH:
|
import 'package:meta/meta.dart';
|
||||||
/// Annotation to assign a name or identifier to a class, method, or other element.
|
|
||||||
|
/// Assigns a name or key identifier to a class, field, factory method or parameter
|
||||||
|
/// for use in multi-registration scenarios (named dependencies) in CherryPick DI.
|
||||||
///
|
///
|
||||||
/// The `@named('value')` annotation allows you to specify a string name
|
/// Use `@named('key')` to distinguish between multiple bindings/implementations
|
||||||
/// for a dependency, factory, or injectable. This is useful for distinguishing
|
/// of the same type—when registering and when injecting dependencies.
|
||||||
/// between multiple registrations of the same type in dependency injection,
|
|
||||||
/// code generation, and for providing human-readable metadata.
|
|
||||||
///
|
///
|
||||||
/// Example:
|
/// You can use `@named`:
|
||||||
|
/// - On provider/factory methods in a module
|
||||||
|
/// - On fields with `@inject()` to receive a named instance
|
||||||
|
/// - On function parameters (for method/constructor injection)
|
||||||
|
///
|
||||||
|
/// ### Example: On Provider Method
|
||||||
/// ```dart
|
/// ```dart
|
||||||
/// @module()
|
/// @module()
|
||||||
/// abstract class AppModule extends Module {
|
/// abstract class AppModule {
|
||||||
/// @named('dio')
|
/// @named('main')
|
||||||
/// Dio dio() => Dio();
|
/// ApiClient apiClient() => ApiClient();
|
||||||
|
///
|
||||||
|
/// @named('mock')
|
||||||
|
/// ApiClient mockApi() => MockApiClient();
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// This will generate:
|
/// ### Example: On Injectable Field
|
||||||
/// ```dart
|
/// ```dart
|
||||||
/// final class $AppModule extends AppModule {
|
/// @injectable()
|
||||||
/// @override
|
/// class WidgetModel with _\$WidgetModel {
|
||||||
/// void builder(Scope currentScope) {
|
/// @inject()
|
||||||
/// bind<Dio>().toProvide(() => dio()).withName('dio').singleton();
|
/// @named('main')
|
||||||
/// }
|
/// late final ApiClient api;
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// RUSSIAN (Русский):
|
/// ### Example: On Parameter
|
||||||
/// Аннотация для задания имени или идентификатора классу, методу или другому элементу.
|
|
||||||
///
|
|
||||||
/// Аннотация `@named('значение')` позволяет указать строковое имя для зависимости,
|
|
||||||
/// фабрики или внедряемого значения. Это удобно для различения нескольких
|
|
||||||
/// регистраций одного типа в DI, генерации кода.
|
|
||||||
///
|
|
||||||
/// Пример:
|
|
||||||
/// ```dart
|
/// ```dart
|
||||||
/// @module()
|
/// class UserScreen {
|
||||||
/// abstract class AppModule extends Module {
|
/// UserScreen(@named('current') User user);
|
||||||
/// @named('dio')
|
|
||||||
/// Dio dio() => Dio();
|
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
///
|
@experimental
|
||||||
/// Будет сгенерирован следующий код:
|
|
||||||
/// ```dart
|
|
||||||
/// final class $AppModule extends AppModule {
|
|
||||||
/// @override
|
|
||||||
/// void builder(Scope currentScope) {
|
|
||||||
/// bind<Dio>().toProvide(() => dio()).withName('dio').singleton();
|
|
||||||
/// }
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
// ignore: camel_case_types
|
|
||||||
final class named {
|
final class named {
|
||||||
/// EN: The assigned name or identifier for the element.
|
/// The assigned name or identifier for the dependency, provider, or parameter.
|
||||||
///
|
|
||||||
/// RU: Назначенное имя или идентификатор для элемента.
|
|
||||||
final String value;
|
final String value;
|
||||||
|
|
||||||
/// EN: Creates a [named] annotation with the given [value].
|
/// Creates a [named] annotation with the given [value] key or name.
|
||||||
///
|
|
||||||
/// RU: Создаёт аннотацию [named] с заданным значением [value].
|
|
||||||
const named(this.value);
|
const named(this.value);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,46 +11,33 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
|
|
||||||
/// ENGLISH:
|
import 'package:meta/meta.dart';
|
||||||
/// Annotation to mark a method parameter for injection with run-time arguments.
|
|
||||||
|
/// Marks a parameter in a provider method to receive dynamic runtime arguments when resolving a dependency.
|
||||||
///
|
///
|
||||||
/// Use the `@params()` annotation to specify that a particular parameter of a
|
/// Use `@params()` in a DI module/factory method when the value must be supplied by the user/code at injection time,
|
||||||
/// provider method should be assigned a value supplied at resolution time,
|
/// not during static wiring (such as user input, navigation arguments, etc).
|
||||||
/// rather than during static dependency graph creation. This is useful in DI
|
///
|
||||||
/// when a dependency must receive dynamic data passed by the consumer
|
/// This enables CherryPick and its codegen to generate .withParams or .toProvideWithParams bindings — so your provider can access runtime values.
|
||||||
/// (via `.withParams(...)` in the generated code).
|
|
||||||
///
|
///
|
||||||
/// Example:
|
/// Example:
|
||||||
/// ```dart
|
/// ```dart
|
||||||
/// @provide()
|
/// import 'package:cherrypick_annotations/cherrypick_annotations.dart';
|
||||||
/// String greet(@params() dynamic params) => 'Hello $params';
|
|
||||||
/// ```
|
|
||||||
///
|
///
|
||||||
/// This will generate:
|
/// @module()
|
||||||
|
/// abstract class FeatureModule {
|
||||||
|
/// @provide
|
||||||
|
/// UserManager createManager(@params Map<String, dynamic> runtimeParams) {
|
||||||
|
/// return UserManager.forUserId(runtimeParams['userId']);
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
/// Usage at injection/resolution:
|
||||||
/// ```dart
|
/// ```dart
|
||||||
/// bind<String>().toProvideWithParams((args) => greet(args));
|
/// final manager = scope.resolve<UserManager>(params: {'userId': myId});
|
||||||
/// ```
|
/// ```
|
||||||
///
|
@experimental
|
||||||
/// RUSSIAN (Русский):
|
|
||||||
/// Аннотация для пометки параметра метода, который будет внедряться со значением во время выполнения.
|
|
||||||
///
|
|
||||||
/// Используйте `@params()` чтобы указать, что конкретный параметр метода-провайдера
|
|
||||||
/// должен получать значение, передаваемое в момент обращения к зависимости,
|
|
||||||
/// а не на этапе построения графа зависимостей. Это полезно, если зависимость
|
|
||||||
/// должна получать данные динамически от пользователя или другого процесса
|
|
||||||
/// через `.withParams(...)` в сгенерированном коде.
|
|
||||||
///
|
|
||||||
/// Пример:
|
|
||||||
/// ```dart
|
|
||||||
/// @provide()
|
|
||||||
/// String greet(@params() dynamic params) => 'Hello $params';
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// Будет сгенерировано:
|
|
||||||
/// ```dart
|
|
||||||
/// bind<String>().toProvideWithParams((args) => greet(args));
|
|
||||||
/// ```
|
|
||||||
// ignore: camel_case_types
|
|
||||||
final class params {
|
final class params {
|
||||||
|
/// Marks a method/constructor parameter as supplied at runtime by the caller.
|
||||||
const params();
|
const params();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,60 +11,34 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
|
|
||||||
/// ENGLISH:
|
import 'package:meta/meta.dart';
|
||||||
/// Annotation to declare a factory/provider method or class as a singleton.
|
|
||||||
|
/// Marks a method or class as a dependency provider (factory/provider) for CherryPick module code generation.
|
||||||
///
|
///
|
||||||
/// Use the `@singleton()` annotation on methods in your DI module to specify
|
/// Use `@provide` on any method inside a `@module()` annotated class when you want that method
|
||||||
/// that only one instance of the resulting object should be created and shared
|
/// to be used as a DI factory/provider during code generation.
|
||||||
/// for all consumers. This is especially useful in dependency injection
|
///
|
||||||
/// frameworks and service locators.
|
/// This should be used for methods that create dynamic, optional, or complex dependencies, especially
|
||||||
|
/// if you want to control the codegen/injection pipeline explicitly and support parameters.
|
||||||
///
|
///
|
||||||
/// Example:
|
/// Example:
|
||||||
/// ```dart
|
/// ```dart
|
||||||
|
/// import 'package:cherrypick_annotations/cherrypick_annotations.dart';
|
||||||
|
///
|
||||||
/// @module()
|
/// @module()
|
||||||
/// abstract class AppModule extends Module {
|
/// abstract class FeatureModule {
|
||||||
|
/// @provide
|
||||||
|
/// Future<Api> provideApi(@params Map<String, dynamic> args) async => ...;
|
||||||
|
///
|
||||||
/// @singleton()
|
/// @singleton()
|
||||||
/// Dio dio() => Dio();
|
/// @provide
|
||||||
|
/// Logger provideLogger() => Logger();
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// This generates the following code:
|
/// See also: [@singleton], [@instance], [@params], [@named]
|
||||||
/// ```dart
|
@experimental
|
||||||
/// final class $AppModule extends AppModule {
|
|
||||||
/// @override
|
|
||||||
/// void builder(Scope currentScope) {
|
|
||||||
/// bind<Dio>().toProvide(() => dio()).singleton();
|
|
||||||
/// }
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// RUSSIAN (Русский):
|
|
||||||
/// Аннотация для объявления фабричного/провайдерного метода или класса синглтоном.
|
|
||||||
///
|
|
||||||
/// Используйте `@singleton()` для методов внутри DI-модуля, чтобы указать,
|
|
||||||
/// что соответствующий объект (экземпляр класса) должен быть создан только один раз
|
|
||||||
/// и использоваться всеми компонентами приложения (единый общий экземпляр).
|
|
||||||
/// Это характерно для систем внедрения зависимостей и сервис-локаторов.
|
|
||||||
///
|
|
||||||
/// Пример:
|
|
||||||
/// ```dart
|
|
||||||
/// @module()
|
|
||||||
/// abstract class AppModule extends Module {
|
|
||||||
/// @singleton()
|
|
||||||
/// Dio dio() => Dio();
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// Будет сгенерирован следующий код:
|
|
||||||
/// ```dart
|
|
||||||
/// final class $AppModule extends AppModule {
|
|
||||||
/// @override
|
|
||||||
/// void builder(Scope currentScope) {
|
|
||||||
/// bind<Dio>().toProvide(() => dio()).singleton();
|
|
||||||
/// }
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
// ignore: camel_case_types
|
|
||||||
final class provide {
|
final class provide {
|
||||||
|
/// Creates a [provide] annotation.
|
||||||
const provide();
|
const provide();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,25 +13,41 @@
|
|||||||
|
|
||||||
import 'package:meta/meta.dart';
|
import 'package:meta/meta.dart';
|
||||||
|
|
||||||
/// Annotation to specify a scope for dependency injection in CherryPick.
|
/// Specifies the DI scope or region from which a dependency should be resolved.
|
||||||
/// Use this on an injected field to indicate from which scope
|
|
||||||
/// the dependency must be resolved.
|
|
||||||
///
|
///
|
||||||
/// ---
|
/// Use `@scope('scopeName')` on an injected field, parameter, or provider method when you want
|
||||||
|
/// to resolve a dependency not from the current scope, but from another named scope/subcontainer.
|
||||||
///
|
///
|
||||||
/// Аннотация для указания области внедрения (scope) в CherryPick.
|
/// Useful for advanced DI scenarios: multi-feature/state isolation, navigation stacks, explicit subscopes, or testing.
|
||||||
/// Используйте её на инъецируемом поле, чтобы определить из какой области
|
|
||||||
/// должна быть получена зависимость.
|
|
||||||
///
|
///
|
||||||
/// Example / Пример:
|
/// Example (injected field):
|
||||||
/// ```dart
|
/// ```dart
|
||||||
/// @inject()
|
/// @injectable()
|
||||||
/// @scope('profile')
|
/// class ProfileScreen with _\$ProfileScreen {
|
||||||
/// late final ProfileManager profileManager;
|
/// @inject()
|
||||||
|
/// @scope('profile')
|
||||||
|
/// late final ProfileManager manager;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Example (parameter):
|
||||||
|
/// ```dart
|
||||||
|
/// class TabBarModel {
|
||||||
|
/// TabBarModel(@scope('tabs') TabContext context);
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Example (in a module):
|
||||||
|
/// ```dart
|
||||||
|
/// @module()
|
||||||
|
/// abstract class FeatureModule {
|
||||||
|
/// @provide
|
||||||
|
/// Service service(@scope('shared') SharedConfig config);
|
||||||
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
@experimental
|
@experimental
|
||||||
// ignore: camel_case_types
|
|
||||||
final class scope {
|
final class scope {
|
||||||
|
/// The name/key of the DI scope from which to resolve this dependency.
|
||||||
final String? name;
|
final String? name;
|
||||||
const scope(this.name);
|
const scope(this.name);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,63 +11,32 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
|
|
||||||
/// ENGLISH:
|
import 'package:meta/meta.dart';
|
||||||
/// Annotation to declare a dependency as a singleton.
|
|
||||||
|
/// Marks a provider method or class so its instance is created only once and shared (singleton) for DI in CherryPick.
|
||||||
///
|
///
|
||||||
/// Use the `@singleton()` annotation on provider methods inside a module
|
/// Use `@singleton()` on provider methods or classes in your DI module to ensure only one instance is ever created
|
||||||
/// to indicate that only a single instance of this dependency should be
|
/// and reused across the application's lifetime (or scope lifetime).
|
||||||
/// created and shared throughout the application's lifecycle. This is
|
|
||||||
/// typically used in dependency injection frameworks or service locators
|
|
||||||
/// to guarantee a single shared instance.
|
|
||||||
///
|
///
|
||||||
/// Example:
|
/// Example:
|
||||||
/// ```dart
|
/// ```dart
|
||||||
|
/// import 'package:cherrypick_annotations/cherrypick_annotations.dart';
|
||||||
|
///
|
||||||
/// @module()
|
/// @module()
|
||||||
/// abstract class AppModule extends Module {
|
/// abstract class AppModule {
|
||||||
/// @singleton()
|
/// @singleton()
|
||||||
/// Dio dio() => Dio();
|
/// ApiClient createApi() => ApiClient();
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// This will generate code like:
|
/// The generated code will ensure:
|
||||||
/// ```dart
|
/// ```dart
|
||||||
/// final class $AppModule extends AppModule {
|
/// bind<ApiClient>().toProvide(() => createApi()).singleton();
|
||||||
/// @override
|
|
||||||
/// void builder(Scope currentScope) {
|
|
||||||
/// bind<Dio>().toProvide(() => dio()).singleton();
|
|
||||||
/// }
|
|
||||||
/// }
|
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// RUSSIAN (Русский):
|
/// See also: [@instance], [@provide], [@named]
|
||||||
/// Аннотация для объявления зависимости как синглтона.
|
@experimental
|
||||||
///
|
|
||||||
/// Используйте `@singleton()` для методов-провайдеров внутри модуля,
|
|
||||||
/// чтобы указать, что соответствующий объект должен быть создан
|
|
||||||
/// единожды и использоваться во всём приложении (общий синглтон).
|
|
||||||
/// Это характерно для систем внедрения зависимостей и сервис-локаторов,
|
|
||||||
/// чтобы гарантировать один общий экземпляр.
|
|
||||||
///
|
|
||||||
/// Пример:
|
|
||||||
/// ```dart
|
|
||||||
/// @module()
|
|
||||||
/// abstract class AppModule extends Module {
|
|
||||||
/// @singleton()
|
|
||||||
/// Dio dio() => Dio();
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// Будет сгенерирован следующий код:
|
|
||||||
/// ```dart
|
|
||||||
/// final class $AppModule extends AppModule {
|
|
||||||
/// @override
|
|
||||||
/// void builder(Scope currentScope) {
|
|
||||||
/// bind<Dio>().toProvide(() => dio()).singleton();
|
|
||||||
/// }
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
// ignore: camel_case_types
|
|
||||||
final class singleton {
|
final class singleton {
|
||||||
/// Creates a [singleton] annotation.
|
/// Creates a [singleton] annotation for DI providers/classes.
|
||||||
const singleton();
|
const singleton();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
name: cherrypick_annotations
|
name: cherrypick_annotations
|
||||||
description: |
|
description: |
|
||||||
Set of annotations for CherryPick dependency injection library. Enables code generation and declarative DI for Dart & Flutter projects.
|
Set of annotations for CherryPick dependency injection library. Enables code generation and declarative DI for Dart & Flutter projects.
|
||||||
version: 1.1.1
|
version: 1.1.2-dev.0
|
||||||
documentation: https://github.com/pese-git/cherrypick/wiki
|
documentation: https://github.com/pese-git/cherrypick/wiki
|
||||||
repository: https://github.com/pese-git/cherrypick/cherrypick_annotations
|
repository: https://github.com/pese-git/cherrypick/cherrypick_annotations
|
||||||
issue_tracker: https://github.com/pese-git/cherrypick/issues
|
issue_tracker: https://github.com/pese-git/cherrypick/issues
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
## 1.1.3-dev.9
|
||||||
|
|
||||||
|
- **DOCS**(provider): add detailed English API documentation for CherryPickProvider Flutter integration.
|
||||||
|
|
||||||
## 1.1.3-dev.8
|
## 1.1.3-dev.8
|
||||||
|
|
||||||
- Update a dependency to the latest release.
|
- Update a dependency to the latest release.
|
||||||
|
|||||||
@@ -14,29 +14,87 @@ import 'package:flutter/widgets.dart';
|
|||||||
/// limitations under the License.
|
/// limitations under the License.
|
||||||
///
|
///
|
||||||
|
|
||||||
|
/// {@template cherrypick_flutter_provider}
|
||||||
|
/// An [InheritedWidget] that provides convenient integration of CherryPick
|
||||||
|
/// dependency injection scopes into the Flutter widget tree.
|
||||||
|
///
|
||||||
|
/// Place `CherryPickProvider` at the top of your widget subtree to make a
|
||||||
|
/// [Scope] (or its descendants) available via `CherryPickProvider.of(context)`.
|
||||||
|
///
|
||||||
|
/// This is the recommended entry point for connecting CherryPick DI to your
|
||||||
|
/// Flutter app or feature area, enabling context-based scope management and
|
||||||
|
/// DI resolution in child widgets.
|
||||||
|
///
|
||||||
|
/// ### Example: Root Integration
|
||||||
|
/// ```dart
|
||||||
|
/// void main() {
|
||||||
|
/// final rootScope = CherryPick.openRootScope()
|
||||||
|
/// ..installModules([AppModule()]);
|
||||||
|
/// runApp(
|
||||||
|
/// CherryPickProvider(
|
||||||
|
/// child: MyApp(),
|
||||||
|
/// ),
|
||||||
|
/// );
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// // In any widget:
|
||||||
|
/// final provider = CherryPickProvider.of(context);
|
||||||
|
/// final scope = provider.openRootScope();
|
||||||
|
/// final myService = scope.resolve<MyService>();
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ### Example: Subscope for a Feature/Screen
|
||||||
|
/// ```dart
|
||||||
|
/// Widget build(BuildContext context) {
|
||||||
|
/// final provider = CherryPickProvider.of(context);
|
||||||
|
/// final featureScope = provider.openSubScope(scopeName: 'featureA');
|
||||||
|
/// return MyFeatureScreen(scope: featureScope);
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// You can use [openRootScope] and [openSubScope] as helpers to get/create scopes.
|
||||||
|
/// {@endtemplate}
|
||||||
final class CherryPickProvider extends InheritedWidget {
|
final class CherryPickProvider extends InheritedWidget {
|
||||||
|
/// Opens (or returns) the application-wide root [Scope].
|
||||||
|
///
|
||||||
|
/// Use to make all dependencies available at the top of your widget tree.
|
||||||
Scope openRootScope() => CherryPick.openRootScope();
|
Scope openRootScope() => CherryPick.openRootScope();
|
||||||
|
|
||||||
|
/// Opens a subscope (child [Scope]) with the given [scopeName].
|
||||||
|
///
|
||||||
|
/// Useful to create isolated feature/module scopes in widget subtrees.
|
||||||
|
/// If [scopeName] is empty, an unnamed scope is created.
|
||||||
Scope openSubScope({String scopeName = '', String separator = '.'}) =>
|
Scope openSubScope({String scopeName = '', String separator = '.'}) =>
|
||||||
CherryPick.openScope(scopeName: scopeName, separator: separator);
|
CherryPick.openScope(scopeName: scopeName, separator: separator);
|
||||||
|
|
||||||
// Constructor for CherryPickProvider. Initializes with a required rootScope and child widget.
|
/// Creates a [CherryPickProvider] and exposes it to the widget subtree.
|
||||||
|
///
|
||||||
|
/// Place near the root of your widget tree. Use [child] to provide the subtree.
|
||||||
const CherryPickProvider({
|
const CherryPickProvider({
|
||||||
super.key,
|
super.key,
|
||||||
required super.child,
|
required super.child,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Method to access the nearest CherryPickProvider instance from the context
|
/// Locates the nearest [CherryPickProvider] up the widget tree from [context].
|
||||||
|
///
|
||||||
|
/// Throws if not found. Use this to access DI [Scope] controls anywhere below the provider.
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
/// ```dart
|
||||||
|
/// final provider = CherryPickProvider.of(context);
|
||||||
|
/// final scope = provider.openRootScope();
|
||||||
|
/// ```
|
||||||
static CherryPickProvider of(BuildContext context) {
|
static CherryPickProvider of(BuildContext context) {
|
||||||
// Looks up the widget tree for an instance of CherryPickProvider
|
|
||||||
final CherryPickProvider? result =
|
final CherryPickProvider? result =
|
||||||
context.dependOnInheritedWidgetOfExactType<CherryPickProvider>();
|
context.dependOnInheritedWidgetOfExactType<CherryPickProvider>();
|
||||||
// Assert to ensure a CherryPickProvider is present in the context
|
|
||||||
assert(result != null, 'No CherryPickProvider found in context');
|
assert(result != null, 'No CherryPickProvider found in context');
|
||||||
return result!;
|
return result!;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determines whether the widget should notify dependents when it changes
|
/// Controls update notifications for dependent widgets.
|
||||||
|
///
|
||||||
|
/// Always returns false because the provider itself is stateless:
|
||||||
|
/// changes are to the underlying scopes, not the widget.
|
||||||
@override
|
@override
|
||||||
bool updateShouldNotify(CherryPickProvider oldWidget) {
|
bool updateShouldNotify(CherryPickProvider oldWidget) {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
name: cherrypick_flutter
|
name: cherrypick_flutter
|
||||||
description: "Flutter library that allows access to the root scope through the context using `CherryPickProvider`."
|
description: "Flutter library that allows access to the root scope through the context using `CherryPickProvider`."
|
||||||
version: 1.1.3-dev.8
|
version: 1.1.3-dev.9
|
||||||
homepage: https://pese-git.github.io/cherrypick-site/
|
homepage: https://pese-git.github.io/cherrypick-site/
|
||||||
documentation: https://github.com/pese-git/cherrypick/wiki
|
documentation: https://github.com/pese-git/cherrypick/wiki
|
||||||
repository: https://github.com/pese-git/cherrypick
|
repository: https://github.com/pese-git/cherrypick
|
||||||
@@ -19,7 +19,7 @@ environment:
|
|||||||
dependencies:
|
dependencies:
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
cherrypick: ^3.0.0-dev.8
|
cherrypick: ^3.0.0-dev.9
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
## 2.0.0-dev.0
|
||||||
|
|
||||||
|
> Note: This release has breaking changes.
|
||||||
|
|
||||||
|
- **BREAKING** **DOCS**(generator): improve and unify English documentation and examples for all DI source files.
|
||||||
|
|
||||||
## 1.1.1
|
## 1.1.1
|
||||||
|
|
||||||
- **FIX**(license): correct urls.
|
- **FIX**(license): correct urls.
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
library;
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// Copyright 2021 Sergey Penkovsky (sergey.penkovsky@gmail.com)
|
// Copyright 2021 Sergey Penkovsky (sergey.penkovsky@gmail.com)
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@@ -12,6 +10,28 @@ library;
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
|
library;
|
||||||
|
|
||||||
|
/// CherryPick code generation library: entry point for build_runner DI codegen.
|
||||||
|
///
|
||||||
|
/// This library exports generators for CherryPick dependency injection:
|
||||||
|
/// - [ModuleGenerator]: Generates DI module classes for all `@module()`-annotated classes.
|
||||||
|
/// - [InjectGenerator]: Generates field-injection mixins for classes annotated with `@injectable()`.
|
||||||
|
///
|
||||||
|
/// These generators are hooked into [build_runner] and cherrypick_generator's builder configuration.
|
||||||
|
/// Normally you do not import this directly; instead, add cherrypick_generator
|
||||||
|
/// as a dev_dependency and run `dart run build_runner build`.
|
||||||
|
///
|
||||||
|
/// Example usage in `build.yaml` or your project's workflow:
|
||||||
|
/// ```yaml
|
||||||
|
/// targets:
|
||||||
|
/// $default:
|
||||||
|
/// builders:
|
||||||
|
/// cherrypick_generator|cherrypick_generator:
|
||||||
|
/// generate_for:
|
||||||
|
/// - lib/**.dart
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// For annotation details, see `package:cherrypick_annotations`.
|
||||||
export 'module_generator.dart';
|
export 'module_generator.dart';
|
||||||
export 'inject_generator.dart';
|
export 'inject_generator.dart';
|
||||||
|
|||||||
@@ -20,28 +20,85 @@ import 'package:source_gen/source_gen.dart';
|
|||||||
import 'package:analyzer/dart/element/element.dart';
|
import 'package:analyzer/dart/element/element.dart';
|
||||||
import 'package:cherrypick_annotations/cherrypick_annotations.dart' as ann;
|
import 'package:cherrypick_annotations/cherrypick_annotations.dart' as ann;
|
||||||
|
|
||||||
/// InjectGenerator generates a mixin for a class marked with @injectable()
|
/// CherryPick DI field injector generator for codegen.
|
||||||
/// and injects all fields annotated with @inject(), using CherryPick DI.
|
|
||||||
///
|
///
|
||||||
/// For Future<T> fields it calls .resolveAsync<T>(),
|
/// Analyzes all Dart classes marked with `@injectable()` and generates a mixin (for example, `_$ProfileScreen`)
|
||||||
/// otherwise .resolve<T>() is used. Scope and named qualifiers are supported.
|
/// which contains the `_inject` method. This method will assign all fields annotated with `@inject()`
|
||||||
|
/// using the CherryPick DI container. Extra annotation qualifiers such as `@named` and `@scope` are respected
|
||||||
|
/// for each field. Nullable fields and Future/injectable async dependencies are also supported automatically.
|
||||||
///
|
///
|
||||||
/// ---
|
/// ---
|
||||||
///
|
///
|
||||||
/// InjectGenerator генерирует миксин для класса с аннотацией @injectable()
|
/// ### Example usage in a project:
|
||||||
/// и внедряет все поля, помеченные @inject(), используя DI-фреймворк CherryPick.
|
|
||||||
///
|
///
|
||||||
/// Для Future<T> полей вызывается .resolveAsync<T>(),
|
/// ```dart
|
||||||
/// для остальных — .resolve<T>(). Поддерживаются scope и named qualifier.
|
/// import 'package:cherrypick_annotations/cherrypick_annotations.dart';
|
||||||
///
|
///
|
||||||
|
/// @injectable()
|
||||||
|
/// class MyScreen with _$MyScreen {
|
||||||
|
/// @inject()
|
||||||
|
/// late final Logger logger;
|
||||||
|
///
|
||||||
|
/// @inject()
|
||||||
|
/// @named('test')
|
||||||
|
/// late final HttpClient client;
|
||||||
|
///
|
||||||
|
/// @inject()
|
||||||
|
/// Future<Analytics>? analytics;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// After running build_runner, this mixin will be auto-generated:
|
||||||
|
///
|
||||||
|
/// ```dart
|
||||||
|
/// mixin _$MyScreen {
|
||||||
|
/// void _inject(MyScreen instance) {
|
||||||
|
/// instance.logger = CherryPick.openRootScope().resolve<Logger>();
|
||||||
|
/// instance.client = CherryPick.openRootScope().resolve<HttpClient>(named: 'test');
|
||||||
|
/// instance.analytics = CherryPick.openRootScope().tryResolveAsync<Analytics>(); // nullable async inject
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// You may use the mixin (e.g., `myScreen._inject(myScreen)`) or expose your own public helper for instance field injection.
|
||||||
|
///
|
||||||
|
/// **Supported scenarios:**
|
||||||
|
/// - Ordinary injectable fields: `resolve<T>()`.
|
||||||
|
/// - Named qualifiers: `resolve<T>(named: ...)`.
|
||||||
|
/// - Scoping: `CherryPick.openScope(scopeName: ...).resolve<T>()`.
|
||||||
|
/// - Nullable/incomplete fields: `tryResolve`/`tryResolveAsync`.
|
||||||
|
/// - Async dependencies: `Future<T>`/`resolveAsync<T>()`.
|
||||||
|
///
|
||||||
|
/// See also:
|
||||||
|
/// * @inject
|
||||||
|
/// * @injectable
|
||||||
class InjectGenerator extends GeneratorForAnnotation<ann.injectable> {
|
class InjectGenerator extends GeneratorForAnnotation<ann.injectable> {
|
||||||
const InjectGenerator();
|
const InjectGenerator();
|
||||||
|
|
||||||
/// The main entry point for code generation.
|
/// Main entry point for CherryPick field injection code generation.
|
||||||
///
|
///
|
||||||
/// Checks class validity, collects injectable fields, and produces injection code.
|
/// - Only triggers for classes marked with `@injectable()`.
|
||||||
|
/// - Throws an error if used on non-class elements.
|
||||||
|
/// - Scans all fields marked with `@inject()` and gathers qualifiers (if any).
|
||||||
|
/// - Generates Dart code for a mixin that injects all dependencies into the target class instance.
|
||||||
///
|
///
|
||||||
/// Основная точка входа генератора. Проверяет класс, собирает инъектируемые поля и создает код внедрения зависимостей.
|
/// Returns the Dart code as a String defining the new mixin.
|
||||||
|
///
|
||||||
|
/// Example input (user code):
|
||||||
|
/// ```dart
|
||||||
|
/// @injectable()
|
||||||
|
/// class UserBloc with _$UserBloc {
|
||||||
|
/// @inject() late final AuthRepository authRepository;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
/// Example output (generated):
|
||||||
|
/// ```dart
|
||||||
|
/// mixin _$UserBloc {
|
||||||
|
/// void _inject(UserBloc instance) {
|
||||||
|
/// instance.authRepository = CherryPick.openRootScope().resolve<AuthRepository>();
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
@override
|
@override
|
||||||
FutureOr<String> generateForAnnotatedElement(
|
FutureOr<String> generateForAnnotatedElement(
|
||||||
Element element,
|
Element element,
|
||||||
@@ -63,8 +120,7 @@ class InjectGenerator extends GeneratorForAnnotation<ann.injectable> {
|
|||||||
..writeln('mixin $mixinName {')
|
..writeln('mixin $mixinName {')
|
||||||
..writeln(' void _inject($className instance) {');
|
..writeln(' void _inject($className instance) {');
|
||||||
|
|
||||||
// Collect and process all @inject fields.
|
// Collect and process all @inject fields
|
||||||
// Собираем и обрабатываем все поля с @inject.
|
|
||||||
final injectFields =
|
final injectFields =
|
||||||
classElement.fields.where(_isInjectField).map(_parseInjectField);
|
classElement.fields.where(_isInjectField).map(_parseInjectField);
|
||||||
|
|
||||||
@@ -79,20 +135,20 @@ class InjectGenerator extends GeneratorForAnnotation<ann.injectable> {
|
|||||||
return buffer.toString();
|
return buffer.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Checks if a field has the @inject annotation.
|
/// Returns true if a field is annotated with `@inject`.
|
||||||
///
|
///
|
||||||
/// Проверяет, отмечено ли поле аннотацией @inject.
|
/// Used to detect which fields should be processed for injection.
|
||||||
static bool _isInjectField(FieldElement field) {
|
static bool _isInjectField(FieldElement field) {
|
||||||
return field.metadata.any(
|
return field.metadata.any(
|
||||||
(m) => m.computeConstantValue()?.type?.getDisplayString() == 'inject',
|
(m) => m.computeConstantValue()?.type?.getDisplayString() == 'inject',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parses the field for scope/named qualifiers and determines its type.
|
/// Parses `@inject()` field and extracts all injection metadata
|
||||||
/// Returns a [_ParsedInjectField] describing injection information.
|
/// (core type, qualifiers, scope, nullability, etc).
|
||||||
///
|
///
|
||||||
/// Разбирает поле на наличие модификаторов scope/named и выясняет его тип.
|
/// Converts Dart field declaration and all parameterizing injection-related
|
||||||
/// Возвращает [_ParsedInjectField] с информацией о внедрении.
|
/// annotations into a [_ParsedInjectField] which is used for codegen.
|
||||||
static _ParsedInjectField _parseInjectField(FieldElement field) {
|
static _ParsedInjectField _parseInjectField(FieldElement field) {
|
||||||
String? scopeName;
|
String? scopeName;
|
||||||
String? namedValue;
|
String? namedValue;
|
||||||
@@ -120,8 +176,7 @@ class InjectGenerator extends GeneratorForAnnotation<ann.injectable> {
|
|||||||
isFuture = false;
|
isFuture = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ***
|
// Determine nullability for field types like T? or Future<T?>
|
||||||
// Добавим определение nullable для типа (например PostRepository? или Future<PostRepository?>)
|
|
||||||
bool isNullable = dartType.nullabilitySuffix ==
|
bool isNullable = dartType.nullabilitySuffix ==
|
||||||
NullabilitySuffix.question ||
|
NullabilitySuffix.question ||
|
||||||
(dartType is ParameterizedType &&
|
(dartType is ParameterizedType &&
|
||||||
@@ -139,13 +194,17 @@ class InjectGenerator extends GeneratorForAnnotation<ann.injectable> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generates a line of code that performs the dependency injection for a field.
|
/// Generates Dart code for a single dependency-injected field based on its metadata.
|
||||||
/// Handles resolve/resolveAsync, scoping, and named qualifiers.
|
|
||||||
///
|
///
|
||||||
/// Генерирует строку кода, которая внедряет зависимость для поля.
|
/// This code will resolve the field from the CherryPick DI container and assign it to the class instance.
|
||||||
/// Учитывает resolve/resolveAsync, scoping и named qualifier.
|
/// Correctly dispatches to resolve, tryResolve, resolveAsync, or tryResolveAsync methods,
|
||||||
|
/// and applies container scoping or named resolution where required.
|
||||||
|
///
|
||||||
|
/// Returns literal Dart code as string (1 line).
|
||||||
|
///
|
||||||
|
/// Example output:
|
||||||
|
/// `instance.logger = CherryPick.openRootScope().resolve<Logger>();`
|
||||||
String _generateInjectionLine(_ParsedInjectField field) {
|
String _generateInjectionLine(_ParsedInjectField field) {
|
||||||
// Используем tryResolve для nullable, иначе resolve
|
|
||||||
final resolveMethod = field.isFuture
|
final resolveMethod = field.isFuture
|
||||||
? (field.isNullable
|
? (field.isNullable
|
||||||
? 'tryResolveAsync<${field.coreType}>'
|
? 'tryResolveAsync<${field.coreType}>'
|
||||||
@@ -166,29 +225,29 @@ class InjectGenerator extends GeneratorForAnnotation<ann.injectable> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Data structure representing all information required to generate
|
/// Internal structure: describes all required information for generating the injection
|
||||||
/// injection code for a field.
|
/// assignment for a given field.
|
||||||
///
|
///
|
||||||
/// Структура данных, содержащая всю информацию,
|
/// Not exported. Used as a DTO in the generator for each field.
|
||||||
/// необходимую для генерации кода внедрения для поля.
|
|
||||||
class _ParsedInjectField {
|
class _ParsedInjectField {
|
||||||
/// The name of the field / Имя поля.
|
/// The name of the field to be injected.
|
||||||
final String fieldName;
|
final String fieldName;
|
||||||
|
|
||||||
/// The base type name (T or Future<T>) / Базовый тип (T или тип из Future<T>).
|
/// The Dart type to resolve (e.g. `Logger` from `Logger` or `Future<Logger>`).
|
||||||
final String coreType;
|
final String coreType;
|
||||||
|
|
||||||
/// True if the field type is Future<T>; false otherwise
|
/// True if the field is an async dependency (Future<...>), otherwise false.
|
||||||
/// Истина, если поле — Future<T>, иначе — ложь.
|
|
||||||
final bool isFuture;
|
final bool isFuture;
|
||||||
|
|
||||||
/// Optional scope annotation argument / Опциональное имя scope.
|
/// True if the field accepts null (T?), otherwise false.
|
||||||
|
final bool isNullable;
|
||||||
|
|
||||||
|
/// The scoping for DI resolution, or null to use root scope.
|
||||||
final String? scopeName;
|
final String? scopeName;
|
||||||
|
|
||||||
/// Optional named annotation argument / Опциональное имя named.
|
/// Name qualifier for named resolution, or null if not set.
|
||||||
final String? namedValue;
|
final String? namedValue;
|
||||||
|
|
||||||
final bool isNullable;
|
|
||||||
|
|
||||||
_ParsedInjectField({
|
_ParsedInjectField({
|
||||||
required this.fieldName,
|
required this.fieldName,
|
||||||
@@ -200,8 +259,8 @@ class _ParsedInjectField {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Builder factory. Used by build_runner.
|
/// Factory for creating the build_runner builder for DI field injection.
|
||||||
///
|
///
|
||||||
/// Фабрика билдера. Используется build_runner.
|
/// Add this builder in your build.yaml if you're invoking CherryPick generators manually.
|
||||||
Builder injectBuilder(BuilderOptions options) =>
|
Builder injectBuilder(BuilderOptions options) =>
|
||||||
PartBuilder([InjectGenerator()], '.inject.cherrypick.g.dart');
|
PartBuilder([InjectGenerator()], '.inject.cherrypick.g.dart');
|
||||||
|
|||||||
@@ -19,75 +19,89 @@ import 'package:cherrypick_annotations/cherrypick_annotations.dart' as ann;
|
|||||||
import 'src/generated_class.dart';
|
import 'src/generated_class.dart';
|
||||||
|
|
||||||
/// ---------------------------------------------------------------------------
|
/// ---------------------------------------------------------------------------
|
||||||
/// ModuleGenerator for code generation of dependency-injected modules.
|
/// CherryPick Module Generator — Codegen for DI modules
|
||||||
///
|
///
|
||||||
/// ENGLISH
|
/// This generator scans Dart classes annotated with `@module()` and generates
|
||||||
/// This generator scans for Dart classes annotated with `@module()` and
|
/// boilerplate for dependency injection registration automatically. Each public
|
||||||
/// automatically generates boilerplate code for dependency injection
|
/// method in such classes can be annotated to describe how an object should be
|
||||||
/// (DI) based on the public methods in those classes. Each method can be
|
/// bound to the DI container (singleton, factory, named, with parameters, etc).
|
||||||
/// annotated to describe how an object should be provided to the DI container.
|
|
||||||
/// The generated code registers those methods as bindings. This automates the
|
|
||||||
/// creation of factories, singletons, and named instances, reducing repetitive
|
|
||||||
/// manual code.
|
|
||||||
///
|
///
|
||||||
/// RUSSIAN
|
/// The generated code collects all such bind methods and produces a Dart
|
||||||
/// Генератор зависимостей для DI-контейнера на основе аннотаций.
|
/// companion *module registration class* with a `.bindAll()` method, which you
|
||||||
/// Данный генератор автоматически создаёт код для внедрения зависимостей (DI)
|
/// can use from your DI root to automatically register those dependencies.
|
||||||
/// на основе аннотаций в вашем исходном коде. Когда вы отмечаете класс
|
///
|
||||||
/// аннотацией `@module()`, этот генератор обработает все его публичные методы
|
/// ## Example
|
||||||
/// и автоматически сгенерирует класс с биндингами (регистрациями зависимостей)
|
/// ```dart
|
||||||
/// для DI-контейнера. Это избавляет от написания однообразного шаблонного кода.
|
/// import 'package:cherrypick_annotations/cherrypick_annotations.dart';
|
||||||
|
///
|
||||||
|
/// @module()
|
||||||
|
/// abstract class AppModule {
|
||||||
|
/// @singleton()
|
||||||
|
/// Logger logger() => Logger();
|
||||||
|
///
|
||||||
|
/// @provide()
|
||||||
|
/// ApiService api(Logger logger) => ApiService(logger);
|
||||||
|
///
|
||||||
|
/// @named('dev')
|
||||||
|
/// FakeService fake() => FakeService();
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// After codegen, you will get (simplified):
|
||||||
|
/// ```dart
|
||||||
|
/// class _\$AppModuleCherrypickModule extend AppModule {
|
||||||
|
/// static void bindAll(CherryPickScope scope, AppModule module) {
|
||||||
|
/// scope.addSingleton<Logger>(() => module.logger());
|
||||||
|
/// scope.addFactory<ApiService>(() => module.api(scope.resolve<Logger>()));
|
||||||
|
/// scope.addFactory<FakeService>(() => module.fake(), named: 'dev');
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Use it e.g. in your bootstrap:
|
||||||
|
/// ```dart
|
||||||
|
/// final scope = CherryPick.openRootScope()..intallModules([_\$AppModuleCherrypickModule()]);
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Features supported:
|
||||||
|
/// - Singleton, factory, named, parametric, and async providers
|
||||||
|
/// - Eliminates all boilerplate for DI registration
|
||||||
|
/// - Works with abstract classes and real classes
|
||||||
|
/// - Error if @module() is applied to a non-class
|
||||||
|
///
|
||||||
|
/// See also: [@singleton], [@provide], [@named], [@module]
|
||||||
/// ---------------------------------------------------------------------------
|
/// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
class ModuleGenerator extends GeneratorForAnnotation<ann.module> {
|
class ModuleGenerator extends GeneratorForAnnotation<ann.module> {
|
||||||
/// -------------------------------------------------------------------------
|
/// Generates Dart source for a class marked with the `@module()` annotation.
|
||||||
/// ENGLISH
|
|
||||||
/// Generates the Dart source for a class marked with the `@module()` annotation.
|
|
||||||
/// - [element]: the original Dart class element.
|
|
||||||
/// - [annotation]: the annotation parameters (not usually used here).
|
|
||||||
/// - [buildStep]: the current build step info.
|
|
||||||
///
|
///
|
||||||
/// RUSSIAN
|
/// Throws [InvalidGenerationSourceError] if used on anything except a class.
|
||||||
/// Генерирует исходный код для класса-модуля с аннотацией `@module()`.
|
///
|
||||||
/// [element] — исходный класс, помеченный аннотацией.
|
/// See file-level docs for usage and generated output example.
|
||||||
/// [annotation] — значения параметров аннотации.
|
|
||||||
/// [buildStep] — информация о текущем шаге генерации.
|
|
||||||
/// -------------------------------------------------------------------------
|
|
||||||
@override
|
@override
|
||||||
String generateForAnnotatedElement(
|
String generateForAnnotatedElement(
|
||||||
Element element,
|
Element element,
|
||||||
ConstantReader annotation,
|
ConstantReader annotation,
|
||||||
BuildStep buildStep,
|
BuildStep buildStep,
|
||||||
) {
|
) {
|
||||||
// Only classes are supported for @module() annotation
|
|
||||||
// Обрабатываются только классы (другие элементы — ошибка)
|
|
||||||
if (element is! ClassElement) {
|
if (element is! ClassElement) {
|
||||||
throw InvalidGenerationSourceError(
|
throw InvalidGenerationSourceError(
|
||||||
'@module() can only be applied to classes. / @module() может быть применён только к классам.',
|
'@module() can only be applied to classes.',
|
||||||
element: element,
|
element: element,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
final classElement = element;
|
final classElement = element;
|
||||||
|
|
||||||
// Build a representation of the generated bindings based on class methods /
|
|
||||||
// Создаёт объект, описывающий, какие биндинги нужно сгенерировать на основании методов класса
|
|
||||||
final generatedClass = GeneratedClass.fromClassElement(classElement);
|
final generatedClass = GeneratedClass.fromClassElement(classElement);
|
||||||
|
|
||||||
// Generate the resulting Dart code / Генерирует итоговый Dart-код
|
|
||||||
return generatedClass.generate();
|
return generatedClass.generate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// ---------------------------------------------------------------------------
|
/// ---------------------------------------------------------------------------
|
||||||
/// ENGLISH
|
/// Codegen builder entry point: register this builder in build.yaml or your package.
|
||||||
/// Entry point for build_runner. Returns a Builder used to generate code for
|
|
||||||
/// every file with a @module() annotation.
|
|
||||||
///
|
///
|
||||||
/// RUSSIAN
|
/// Used by build_runner. Generates .module.cherrypick.g.dart files for each
|
||||||
/// Точка входа для генератора build_runner.
|
/// source file with an annotated @module() class.
|
||||||
/// Возвращает Builder, используемый build_runner для генерации кода для всех
|
|
||||||
/// файлов, где встречается @module().
|
|
||||||
/// ---------------------------------------------------------------------------
|
/// ---------------------------------------------------------------------------
|
||||||
Builder moduleBuilder(BuilderOptions options) =>
|
Builder moduleBuilder(BuilderOptions options) =>
|
||||||
PartBuilder([ModuleGenerator()], '.module.cherrypick.g.dart');
|
PartBuilder([ModuleGenerator()], '.module.cherrypick.g.dart');
|
||||||
|
|||||||
@@ -15,9 +15,43 @@ import 'package:analyzer/dart/element/element.dart';
|
|||||||
import 'exceptions.dart';
|
import 'exceptions.dart';
|
||||||
import 'metadata_utils.dart';
|
import 'metadata_utils.dart';
|
||||||
|
|
||||||
/// Validates annotation combinations and usage patterns
|
/// Provides static utility methods for validating annotation usage in CherryPick
|
||||||
|
/// dependency injection code generation.
|
||||||
|
///
|
||||||
|
/// This validator helps ensure that `@provide`, `@instance`, `@singleton`, `@params`,
|
||||||
|
/// `@inject`, `@named`, `@module`, and `@injectable` annotations are correctly and safely
|
||||||
|
/// combined in your codebase, preventing common configuration and codegen errors before
|
||||||
|
/// code is generated.
|
||||||
|
///
|
||||||
|
/// #### Example Usage
|
||||||
|
/// ```dart
|
||||||
|
/// void processMethod(MethodElement method) {
|
||||||
|
/// AnnotationValidator.validateMethodAnnotations(method);
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// All exceptions are thrown as [AnnotationValidationException] and will include
|
||||||
|
/// a helpful message and context.
|
||||||
|
///
|
||||||
|
/// ---
|
||||||
|
/// Typical checks performed by this utility:
|
||||||
|
/// - Mutual exclusivity (`@instance` vs `@provide`)
|
||||||
|
/// - Required presence for fields and methods
|
||||||
|
/// - Proper parameters for `@named` and `@params`
|
||||||
|
/// - Correct usage of injectable fields, module class methods, etc.
|
||||||
|
///
|
||||||
class AnnotationValidator {
|
class AnnotationValidator {
|
||||||
/// Validates annotations on a method element
|
/// Validates annotations for a given [MethodElement].
|
||||||
|
///
|
||||||
|
/// Checks:
|
||||||
|
/// - Mutual exclusivity of `@instance` and `@provide`.
|
||||||
|
/// - That a method is annotated with exactly one DI-producing annotation.
|
||||||
|
/// - If `@params` is present, that it is used together with `@provide`.
|
||||||
|
/// - Appropriate usage of `@singleton`.
|
||||||
|
/// - [@named] syntax and conventions.
|
||||||
|
/// - Parameter validation for method arguments.
|
||||||
|
///
|
||||||
|
/// Throws [AnnotationValidationException] on any violation.
|
||||||
static void validateMethodAnnotations(MethodElement method) {
|
static void validateMethodAnnotations(MethodElement method) {
|
||||||
final annotations = _getAnnotationNames(method.metadata);
|
final annotations = _getAnnotationNames(method.metadata);
|
||||||
|
|
||||||
@@ -26,14 +60,28 @@ class AnnotationValidator {
|
|||||||
_validateAnnotationParameters(method);
|
_validateAnnotationParameters(method);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Validates annotations on a field element
|
/// Validates that a [FieldElement] has correct injection annotations.
|
||||||
|
///
|
||||||
|
/// Specifically, ensures:
|
||||||
|
/// - Injectable fields are of valid type.
|
||||||
|
/// - No `void` injection.
|
||||||
|
/// - Correct scope naming if present.
|
||||||
|
///
|
||||||
|
/// Throws [AnnotationValidationException] if checks fail.
|
||||||
static void validateFieldAnnotations(FieldElement field) {
|
static void validateFieldAnnotations(FieldElement field) {
|
||||||
final annotations = _getAnnotationNames(field.metadata);
|
final annotations = _getAnnotationNames(field.metadata);
|
||||||
|
|
||||||
_validateInjectFieldAnnotations(field, annotations);
|
_validateInjectFieldAnnotations(field, annotations);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Validates annotations on a class element
|
/// Validates all class-level DI annotations.
|
||||||
|
///
|
||||||
|
/// Executes checks for:
|
||||||
|
/// - Module class validity (e.g. must have public DI methods if `@module`).
|
||||||
|
/// - Injectable class: at least one @inject field, field finalness, etc.
|
||||||
|
/// - Provides helpful context for error/warning reporting.
|
||||||
|
///
|
||||||
|
/// Throws [AnnotationValidationException] if checks fail.
|
||||||
static void validateClassAnnotations(ClassElement classElement) {
|
static void validateClassAnnotations(ClassElement classElement) {
|
||||||
final annotations = _getAnnotationNames(classElement.metadata);
|
final annotations = _getAnnotationNames(classElement.metadata);
|
||||||
|
|
||||||
@@ -41,6 +89,9 @@ class AnnotationValidator {
|
|||||||
_validateInjectableClassAnnotations(classElement, annotations);
|
_validateInjectableClassAnnotations(classElement, annotations);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Internal helpers follow (private) ---
|
||||||
|
|
||||||
|
/// Helper: Returns the names of all annotation types on `metadata`.
|
||||||
static List<String> _getAnnotationNames(List<ElementAnnotation> metadata) {
|
static List<String> _getAnnotationNames(List<ElementAnnotation> metadata) {
|
||||||
return metadata
|
return metadata
|
||||||
.map((m) => m.computeConstantValue()?.type?.getDisplayString())
|
.map((m) => m.computeConstantValue()?.type?.getDisplayString())
|
||||||
@@ -49,6 +100,9 @@ class AnnotationValidator {
|
|||||||
.toList();
|
.toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Validates that mutually exclusive method annotations are not used together.
|
||||||
|
///
|
||||||
|
/// For example, `@instance` and `@provide` cannot both be present.
|
||||||
static void _validateMutuallyExclusiveAnnotations(
|
static void _validateMutuallyExclusiveAnnotations(
|
||||||
MethodElement method,
|
MethodElement method,
|
||||||
List<String> annotations,
|
List<String> annotations,
|
||||||
@@ -68,6 +122,10 @@ class AnnotationValidator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Validates correct annotation combinations, e.g.
|
||||||
|
/// - `@params` must be with `@provide`
|
||||||
|
/// - One of `@instance` or `@provide` must be present for a registration method
|
||||||
|
/// - Validates singleton usage
|
||||||
static void _validateAnnotationCombinations(
|
static void _validateAnnotationCombinations(
|
||||||
MethodElement method,
|
MethodElement method,
|
||||||
List<String> annotations,
|
List<String> annotations,
|
||||||
@@ -105,6 +163,7 @@ class AnnotationValidator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Singleton-specific method annotation checks.
|
||||||
static void _validateSingletonUsage(
|
static void _validateSingletonUsage(
|
||||||
MethodElement method,
|
MethodElement method,
|
||||||
List<String> annotations,
|
List<String> annotations,
|
||||||
@@ -130,6 +189,7 @@ class AnnotationValidator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Validates extra requirements or syntactic rules for annotation arguments, like @named.
|
||||||
static void _validateAnnotationParameters(MethodElement method) {
|
static void _validateAnnotationParameters(MethodElement method) {
|
||||||
// Validate @named annotation parameters
|
// Validate @named annotation parameters
|
||||||
final namedValue = MetadataUtils.getNamedValue(method.metadata);
|
final namedValue = MetadataUtils.getNamedValue(method.metadata);
|
||||||
@@ -170,11 +230,11 @@ class AnnotationValidator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Checks that @params is used with compatible parameter type.
|
||||||
static void _validateParamsParameter(
|
static void _validateParamsParameter(
|
||||||
ParameterElement param, MethodElement method) {
|
ParameterElement param, MethodElement method) {
|
||||||
// @params parameter should typically be dynamic or Map<String, dynamic>
|
// @params parameter should typically be dynamic or Map<String, dynamic>
|
||||||
final paramType = param.type.getDisplayString();
|
final paramType = param.type.getDisplayString();
|
||||||
|
|
||||||
if (paramType != 'dynamic' &&
|
if (paramType != 'dynamic' &&
|
||||||
paramType != 'Map<String, dynamic>' &&
|
paramType != 'Map<String, dynamic>' &&
|
||||||
paramType != 'Map<String, dynamic>?') {
|
paramType != 'Map<String, dynamic>?') {
|
||||||
@@ -194,6 +254,7 @@ class AnnotationValidator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Checks field-level annotation for valid injectable fields.
|
||||||
static void _validateInjectFieldAnnotations(
|
static void _validateInjectFieldAnnotations(
|
||||||
FieldElement field,
|
FieldElement field,
|
||||||
List<String> annotations,
|
List<String> annotations,
|
||||||
@@ -227,6 +288,7 @@ class AnnotationValidator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Checks @module usage: must have at least one DI method, each with DI-annotation.
|
||||||
static void _validateModuleClassAnnotations(
|
static void _validateModuleClassAnnotations(
|
||||||
ClassElement classElement,
|
ClassElement classElement,
|
||||||
List<String> annotations,
|
List<String> annotations,
|
||||||
@@ -268,6 +330,7 @@ class AnnotationValidator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Checks @injectable usage on classes and their fields.
|
||||||
static void _validateInjectableClassAnnotations(
|
static void _validateInjectableClassAnnotations(
|
||||||
ClassElement classElement,
|
ClassElement classElement,
|
||||||
List<String> annotations,
|
List<String> annotations,
|
||||||
|
|||||||
@@ -12,57 +12,59 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
/// ----------------------------------------------------------------------------
|
/// ----------------------------------------------------------------------------
|
||||||
/// BindParameterSpec - describes a single method parameter and how to resolve it.
|
/// BindParameterSpec
|
||||||
///
|
///
|
||||||
/// ENGLISH
|
/// Describes a single parameter for a DI provider/factory/binding method,
|
||||||
/// Describes a single parameter for a provider/binding method in the DI system.
|
/// specifying how that parameter is to be resolved in generated code.
|
||||||
/// Stores the parameter type, its optional `@named` key for named resolution,
|
|
||||||
/// and whether it is a runtime "params" argument. Used to generate code that
|
|
||||||
/// resolves dependencies from the DI scope:
|
|
||||||
/// - If the parameter is a dependency type (e.g. SomeDep), emits:
|
|
||||||
/// currentScope.resolve<SomeDep>()
|
|
||||||
/// - If the parameter is named, emits:
|
|
||||||
/// currentScope.resolve<SomeDep>(named: 'yourName')
|
|
||||||
/// - If it's a runtime parameter (e.g. via @params()), emits:
|
|
||||||
/// args
|
|
||||||
///
|
///
|
||||||
/// RUSSIAN
|
/// Stores the parameter's type name, optional `@named` identifier (for named DI resolution),
|
||||||
/// Описывает один параметр метода в DI, и его способ разрешения из контейнера.
|
/// and a flag for runtime (@params) arguments. Used in CherryPick generator
|
||||||
/// Сохраняет имя типа, дополнительное имя (если параметр аннотирован через @named),
|
/// for creating argument lists when invoking factories or provider methods.
|
||||||
/// и признак runtime-параметра (@params).
|
///
|
||||||
/// Для обычной зависимости типа (например, SomeDep) генерирует строку вида:
|
/// ## Example usage
|
||||||
/// currentScope.resolve<SomeDep>()
|
/// ```dart
|
||||||
/// Для зависимости с именем:
|
/// // Binding method: @provide() Logger provideLogger(@named('debug') Config config, @params Map<String, dynamic> args)
|
||||||
/// currentScope.resolve<SomeDep>(named: 'имя')
|
/// final namedParam = BindParameterSpec('Config', 'debug');
|
||||||
/// Для runtime-параметра:
|
/// final runtimeParam = BindParameterSpec('Map<String, dynamic>', null, isParams: true);
|
||||||
/// args
|
/// print(namedParam.generateArg()); // prints: currentScope.resolve<Config>(named: 'debug')
|
||||||
|
/// print(runtimeParam.generateArg()); // prints: args
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ## Code generation logic
|
||||||
|
/// - Injected: currentScope.resolve<Service>()
|
||||||
|
/// - Named: currentScope.resolve<Service>(named: 'name')
|
||||||
|
/// - @params: args
|
||||||
/// ----------------------------------------------------------------------------
|
/// ----------------------------------------------------------------------------
|
||||||
class BindParameterSpec {
|
class BindParameterSpec {
|
||||||
/// Type name of the parameter (e.g. SomeService)
|
/// The type name of the parameter (e.g., 'UserRepository')
|
||||||
/// Имя типа параметра (например, SomeService)
|
|
||||||
final String typeName;
|
final String typeName;
|
||||||
|
|
||||||
/// Optional name for named resolution (from @named)
|
/// If non-null, this is the named-key for DI resolution (from @named).
|
||||||
/// Необязательное имя для разрешения по имени (если аннотировано через @named)
|
|
||||||
final String? named;
|
final String? named;
|
||||||
|
|
||||||
/// True if this parameter uses @params and should be provided from runtime args
|
/// True if this parameter is a runtime param (annotated with @params and
|
||||||
/// Признак, что параметр — runtime (через @params)
|
/// filled by a runtime argument map).
|
||||||
final bool isParams;
|
final bool isParams;
|
||||||
|
|
||||||
BindParameterSpec(this.typeName, this.named, {this.isParams = false});
|
BindParameterSpec(this.typeName, this.named, {this.isParams = false});
|
||||||
|
|
||||||
/// --------------------------------------------------------------------------
|
/// Generates Dart code to resolve this parameter in the DI container.
|
||||||
/// generateArg
|
|
||||||
///
|
///
|
||||||
/// ENGLISH
|
/// - For normal dependencies: resolves by type
|
||||||
/// Generates Dart code for resolving the dependency from the DI scope,
|
/// - For named dependencies: resolves by type and name
|
||||||
/// considering type, named, and param-argument.
|
/// - For @params: uses the supplied params variable (default 'args')
|
||||||
///
|
///
|
||||||
/// RUSSIAN
|
/// ## Example
|
||||||
/// Генерирует строку для получения зависимости из DI scope (с учётом имени
|
/// ```dart
|
||||||
/// и типа параметра или runtime-режима @params).
|
/// final a = BindParameterSpec('Api', null); // normal
|
||||||
/// --------------------------------------------------------------------------
|
/// print(a.generateArg()); // currentScope.resolve<Api>()
|
||||||
|
///
|
||||||
|
/// final b = BindParameterSpec('Api', 'prod'); // named
|
||||||
|
/// print(b.generateArg()); // currentScope.resolve<Api>(named: 'prod')
|
||||||
|
///
|
||||||
|
/// final c = BindParameterSpec('Map<String,dynamic>', null, isParams: true); // params
|
||||||
|
/// print(c.generateArg()); // args
|
||||||
|
/// ```
|
||||||
String generateArg([String paramsVar = 'args']) {
|
String generateArg([String paramsVar = 'args']) {
|
||||||
if (isParams) {
|
if (isParams) {
|
||||||
return paramsVar;
|
return paramsVar;
|
||||||
|
|||||||
@@ -19,62 +19,63 @@ import 'exceptions.dart';
|
|||||||
import 'type_parser.dart';
|
import 'type_parser.dart';
|
||||||
import 'annotation_validator.dart';
|
import 'annotation_validator.dart';
|
||||||
|
|
||||||
|
/// Enum representing the binding annotation applied to a module method.
|
||||||
enum BindingType {
|
enum BindingType {
|
||||||
|
/// Direct instance returned from the method (@instance).
|
||||||
instance,
|
instance,
|
||||||
|
/// Provider/factory function (@provide).
|
||||||
provide;
|
provide;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// ---------------------------------------------------------------------------
|
/// ---------------------------------------------------------------------------
|
||||||
/// BindSpec -- describes a binding specification generated for a dependency.
|
/// BindSpec
|
||||||
///
|
///
|
||||||
/// ENGLISH
|
/// Describes a DI container binding as generated from a single public factory,
|
||||||
/// Represents all the data necessary to generate a DI binding for a single
|
/// instance, or provider method of a module (annotated with @instance or @provide).
|
||||||
/// method in a module class. Each BindSpec corresponds to one public method
|
|
||||||
/// and contains information about its type, provider method, lifecycle (singleton),
|
|
||||||
/// parameters (with their annotations), binding strategy (instance/provide),
|
|
||||||
/// asynchronous mode, and named keys. It is responsible for generating the
|
|
||||||
/// correct Dart code to register this binding with the DI container, in both
|
|
||||||
/// sync and async cases, with and without named or runtime arguments.
|
|
||||||
///
|
///
|
||||||
/// RUSSIAN
|
/// Includes all annotation-driven parameters required to generate valid DI
|
||||||
/// Описывает параметры для создания одного биндинга зависимости (binding spec).
|
/// registration Dart code in CherryPick:
|
||||||
/// Каждый биндинг соответствует одному публичному методу класса-модуля и
|
/// - Return type
|
||||||
/// содержит всю информацию для генерации кода регистрации этого биндинга в
|
/// - Provider method name
|
||||||
/// DI-контейнере: тип возвращаемой зависимости, имя метода, параметры, аннотации
|
/// - Singleton flag
|
||||||
/// (@singleton, @named, @instance, @provide), асинхронность, признак runtime
|
/// - Named identifier (from @named)
|
||||||
/// аргументов и др. Генерирует правильный Dart-код для регистрации биндера.
|
/// - List of resolved or runtime (@params) parameters
|
||||||
|
/// - Binding mode (instance/provide)
|
||||||
|
/// - Async and parametric variants
|
||||||
|
///
|
||||||
|
/// ## Example usage
|
||||||
|
/// ```dart
|
||||||
|
/// // Suppose @provide() Api api(@named('test') Client client)
|
||||||
|
/// final bindSpec = BindSpec.fromMethod(methodElement);
|
||||||
|
/// print(bindSpec.generateBind(2)); // bind<Api>().toProvide(() => api(currentScope.resolve<Client>(named: 'test')));
|
||||||
|
/// ```
|
||||||
/// ---------------------------------------------------------------------------
|
/// ---------------------------------------------------------------------------
|
||||||
class BindSpec {
|
class BindSpec {
|
||||||
/// The type this binding provides (e.g. SomeService)
|
/// The type this binding provides (e.g. SomeService)
|
||||||
/// Тип, который предоставляет биндинг (например, SomeService)
|
|
||||||
final String returnType;
|
final String returnType;
|
||||||
|
|
||||||
/// Method name that implements the binding
|
/// Binding provider/factory method name
|
||||||
/// Имя метода, который реализует биндинг
|
|
||||||
final String methodName;
|
final String methodName;
|
||||||
|
|
||||||
/// Optional name for named dependency (from @named)
|
/// Named identifier for DI resolution (null if unnamed)
|
||||||
/// Необязательное имя, для именованной зависимости (используется с @named)
|
|
||||||
final String? named;
|
final String? named;
|
||||||
|
|
||||||
/// Whether the dependency is a singleton (@singleton annotation)
|
/// If true, binding is registered as singleton in DI
|
||||||
/// Является ли зависимость синглтоном (имеется ли аннотация @singleton)
|
|
||||||
final bool isSingleton;
|
final bool isSingleton;
|
||||||
|
|
||||||
/// List of method parameters to inject dependencies with
|
/// Provider/factory method parameters (in order)
|
||||||
/// Список параметров, которые требуются методу для внедрения зависимостей
|
|
||||||
final List<BindParameterSpec> parameters;
|
final List<BindParameterSpec> parameters;
|
||||||
|
|
||||||
/// Binding type: 'instance' or 'provide' (@instance or @provide)
|
/// Instance vs provider mode, from annotation choice
|
||||||
final BindingType bindingType; // 'instance' | 'provide'
|
final BindingType bindingType;
|
||||||
|
|
||||||
/// True if the method is asynchronous and uses instance binding (Future)
|
/// Async flag for .toInstanceAsync()
|
||||||
final bool isAsyncInstance;
|
final bool isAsyncInstance;
|
||||||
|
|
||||||
/// True if the method is asynchronous and uses provide binding (Future)
|
/// Async flag for .toProvideAsync()
|
||||||
final bool isAsyncProvide;
|
final bool isAsyncProvide;
|
||||||
|
|
||||||
/// True if the binding method accepts runtime "params" argument (@params)
|
/// True if a @params runtime parameter is present
|
||||||
final bool hasParams;
|
final bool hasParams;
|
||||||
|
|
||||||
BindSpec({
|
BindSpec({
|
||||||
@@ -89,20 +90,12 @@ class BindSpec {
|
|||||||
required this.hasParams,
|
required this.hasParams,
|
||||||
});
|
});
|
||||||
|
|
||||||
/// -------------------------------------------------------------------------
|
/// Generates a Dart code line for binding registration.
|
||||||
/// generateBind
|
|
||||||
///
|
///
|
||||||
/// ENGLISH
|
/// Example (single-line):
|
||||||
/// Generates a line of Dart code registering the binding with the DI framework.
|
/// bind<Api>().toProvide(() => provideApi(currentScope.resolve<Client>(named: 'test'))).withName('prod').singleton();
|
||||||
/// Produces something like:
|
|
||||||
/// bind<Type>().toProvide(() => method(args)).withName('name').singleton();
|
|
||||||
/// Indent parameter allows formatted multiline output.
|
|
||||||
///
|
///
|
||||||
/// RUSSIAN
|
/// The [indent] argument sets the space indentation for pretty-printing.
|
||||||
/// Формирует dart-код для биндинга, например:
|
|
||||||
/// bind<Type>().toProvide(() => method(args)).withName('name').singleton();
|
|
||||||
/// Параметр [indent] задаёт отступ для красивого форматирования кода.
|
|
||||||
/// -------------------------------------------------------------------------
|
|
||||||
String generateBind(int indent) {
|
String generateBind(int indent) {
|
||||||
final indentStr = ' ' * indent;
|
final indentStr = ' ' * indent;
|
||||||
final provide = _generateProvideClause(indent);
|
final provide = _generateProvideClause(indent);
|
||||||
@@ -151,7 +144,7 @@ class BindSpec {
|
|||||||
return _generatePlainProvideClause(indent);
|
return _generatePlainProvideClause(indent);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// EN / RU: Supports runtime parameters (@params).
|
/// Generates code when using runtime parameters (@params).
|
||||||
String _generateWithParamsProvideClause(int indent) {
|
String _generateWithParamsProvideClause(int indent) {
|
||||||
// Safe variable name for parameters.
|
// Safe variable name for parameters.
|
||||||
const paramVar = 'args';
|
const paramVar = 'args';
|
||||||
@@ -178,7 +171,7 @@ class BindSpec {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// EN / RU: Supports only injected dependencies, not runtime (@params).
|
/// Generates code when only resolved (not runtime) arguments used.
|
||||||
String _generatePlainProvideClause(int indent) {
|
String _generatePlainProvideClause(int indent) {
|
||||||
final argsStr = parameters.map((p) => p.generateArg()).join(', ');
|
final argsStr = parameters.map((p) => p.generateArg()).join(', ');
|
||||||
|
|
||||||
@@ -241,16 +234,17 @@ class BindSpec {
|
|||||||
/// -------------------------------------------------------------------------
|
/// -------------------------------------------------------------------------
|
||||||
/// fromMethod
|
/// fromMethod
|
||||||
///
|
///
|
||||||
/// ENGLISH
|
/// Constructs a [BindSpec] from an analyzer [MethodElement].
|
||||||
/// Creates a BindSpec from a module class method by analyzing its return type,
|
|
||||||
/// annotations, list of parameters (with their own annotations), and async-ness.
|
|
||||||
/// Throws if a method does not have the required @instance() or @provide().
|
|
||||||
///
|
///
|
||||||
/// RUSSIAN
|
/// Validates and parses all type annotations, method/parameter DI hints,
|
||||||
/// Создаёт спецификацию биндинга (BindSpec) из метода класса-модуля, анализируя
|
/// and derives async and parametric flags as needed.
|
||||||
/// возвращаемый тип, аннотации, параметры (и их аннотации), а также факт
|
///
|
||||||
/// асинхронности. Если нет @instance или @provide — кидает ошибку.
|
/// ## Example
|
||||||
/// -------------------------------------------------------------------------
|
/// ```dart
|
||||||
|
/// final bindSpec = BindSpec.fromMethod(methodElement);
|
||||||
|
/// print(bindSpec.returnType); // e.g., 'Logger'
|
||||||
|
/// ```
|
||||||
|
/// Throws [AnnotationValidationException] or [CodeGenerationException] if invalid.
|
||||||
static BindSpec fromMethod(MethodElement method) {
|
static BindSpec fromMethod(MethodElement method) {
|
||||||
try {
|
try {
|
||||||
// Validate method annotations
|
// Validate method annotations
|
||||||
|
|||||||
@@ -14,10 +14,36 @@
|
|||||||
import 'package:analyzer/dart/element/element.dart';
|
import 'package:analyzer/dart/element/element.dart';
|
||||||
import 'package:source_gen/source_gen.dart';
|
import 'package:source_gen/source_gen.dart';
|
||||||
|
|
||||||
/// Enhanced exception class for CherryPick generator with detailed context information
|
/// ---------------------------------------------------------------------------
|
||||||
|
/// CherryPickGeneratorException
|
||||||
|
///
|
||||||
|
/// The base exception for all CherryPick code generation and annotation
|
||||||
|
/// validation errors. This exception provides enhanced diagnostics including
|
||||||
|
/// the error category, helpful suggestions, and additional debugging context.
|
||||||
|
///
|
||||||
|
/// All errors are structured to be as helpful as possible for users
|
||||||
|
/// running build_runner and for CherryPick contributors debugging generators.
|
||||||
|
///
|
||||||
|
/// ## Example usage:
|
||||||
|
/// ```dart
|
||||||
|
/// if (someErrorCondition) {
|
||||||
|
/// throw AnnotationValidationException(
|
||||||
|
/// 'Custom message about what went wrong',
|
||||||
|
/// element: methodElement,
|
||||||
|
/// suggestion: 'Add @provide() or @instance() annotation',
|
||||||
|
/// context: {'found_annotations': annotations},
|
||||||
|
/// );
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
/// ---------------------------------------------------------------------------
|
||||||
class CherryPickGeneratorException extends InvalidGenerationSourceError {
|
class CherryPickGeneratorException extends InvalidGenerationSourceError {
|
||||||
|
/// A string describing the error category (for grouping).
|
||||||
final String category;
|
final String category;
|
||||||
|
|
||||||
|
/// An optional suggestion string for resolving the error.
|
||||||
final String? suggestion;
|
final String? suggestion;
|
||||||
|
|
||||||
|
/// Arbitrary key-value map for additional debugging information.
|
||||||
final Map<String, dynamic>? context;
|
final Map<String, dynamic>? context;
|
||||||
|
|
||||||
CherryPickGeneratorException(
|
CherryPickGeneratorException(
|
||||||
@@ -50,7 +76,7 @@ class CherryPickGeneratorException extends InvalidGenerationSourceError {
|
|||||||
buffer.writeln(' Type: ${element.runtimeType}');
|
buffer.writeln(' Type: ${element.runtimeType}');
|
||||||
buffer.writeln(' Location: ${element.source?.fullName ?? 'unknown'}');
|
buffer.writeln(' Location: ${element.source?.fullName ?? 'unknown'}');
|
||||||
|
|
||||||
// Note: enclosingElement may not be available in all analyzer versions
|
// Try to show enclosing element info for extra context
|
||||||
try {
|
try {
|
||||||
final enclosing = (element as dynamic).enclosingElement;
|
final enclosing = (element as dynamic).enclosingElement;
|
||||||
if (enclosing != null) {
|
if (enclosing != null) {
|
||||||
@@ -60,7 +86,7 @@ class CherryPickGeneratorException extends InvalidGenerationSourceError {
|
|||||||
// Ignore if enclosingElement is not available
|
// Ignore if enclosingElement is not available
|
||||||
}
|
}
|
||||||
|
|
||||||
// Additional context
|
// Arbitrary user context
|
||||||
if (context != null && context.isNotEmpty) {
|
if (context != null && context.isNotEmpty) {
|
||||||
buffer.writeln('');
|
buffer.writeln('');
|
||||||
buffer.writeln('Additional Context:');
|
buffer.writeln('Additional Context:');
|
||||||
@@ -69,7 +95,7 @@ class CherryPickGeneratorException extends InvalidGenerationSourceError {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Suggestion
|
// Hint/suggestion if present
|
||||||
if (suggestion != null) {
|
if (suggestion != null) {
|
||||||
buffer.writeln('');
|
buffer.writeln('');
|
||||||
buffer.writeln('💡 Suggestion: $suggestion');
|
buffer.writeln('💡 Suggestion: $suggestion');
|
||||||
@@ -79,7 +105,24 @@ class CherryPickGeneratorException extends InvalidGenerationSourceError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Specific exception types for different error categories
|
/// ---------------------------------------------------------------------------
|
||||||
|
/// AnnotationValidationException
|
||||||
|
///
|
||||||
|
/// Thrown when annotation usage is invalid (e.g., missing required annotation,
|
||||||
|
/// mutually exclusive annotations, or incorrect @named format).
|
||||||
|
///
|
||||||
|
/// Grouped as category "ANNOTATION_VALIDATION".
|
||||||
|
///
|
||||||
|
/// ## Example:
|
||||||
|
/// ```dart
|
||||||
|
/// throw AnnotationValidationException(
|
||||||
|
/// '@instance and @provide cannot be used together',
|
||||||
|
/// element: method,
|
||||||
|
/// suggestion: 'Use only one of @instance or @provide.',
|
||||||
|
/// context: {'method_name': method.displayName},
|
||||||
|
/// );
|
||||||
|
/// ```
|
||||||
|
/// ---------------------------------------------------------------------------
|
||||||
class AnnotationValidationException extends CherryPickGeneratorException {
|
class AnnotationValidationException extends CherryPickGeneratorException {
|
||||||
AnnotationValidationException(
|
AnnotationValidationException(
|
||||||
super.message, {
|
super.message, {
|
||||||
@@ -89,6 +132,24 @@ class AnnotationValidationException extends CherryPickGeneratorException {
|
|||||||
}) : super(category: 'ANNOTATION_VALIDATION');
|
}) : super(category: 'ANNOTATION_VALIDATION');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// ---------------------------------------------------------------------------
|
||||||
|
/// TypeParsingException
|
||||||
|
///
|
||||||
|
/// Thrown when a Dart type cannot be interpreted/parsed for DI,
|
||||||
|
/// or if it's not compatible (void, raw Future, etc).
|
||||||
|
///
|
||||||
|
/// Grouped as category "TYPE_PARSING".
|
||||||
|
///
|
||||||
|
/// ## Example:
|
||||||
|
/// ```dart
|
||||||
|
/// throw TypeParsingException(
|
||||||
|
/// 'Cannot parse injected type',
|
||||||
|
/// element: field,
|
||||||
|
/// suggestion: 'Specify a concrete type. Avoid dynamic and raw Future.',
|
||||||
|
/// context: {'type': field.type.getDisplayString()},
|
||||||
|
/// );
|
||||||
|
/// ```
|
||||||
|
/// ---------------------------------------------------------------------------
|
||||||
class TypeParsingException extends CherryPickGeneratorException {
|
class TypeParsingException extends CherryPickGeneratorException {
|
||||||
TypeParsingException(
|
TypeParsingException(
|
||||||
super.message, {
|
super.message, {
|
||||||
@@ -98,6 +159,23 @@ class TypeParsingException extends CherryPickGeneratorException {
|
|||||||
}) : super(category: 'TYPE_PARSING');
|
}) : super(category: 'TYPE_PARSING');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// ---------------------------------------------------------------------------
|
||||||
|
/// CodeGenerationException
|
||||||
|
///
|
||||||
|
/// Thrown on unexpected code generation or formatting failures
|
||||||
|
/// during generator execution.
|
||||||
|
///
|
||||||
|
/// Grouped as category "CODE_GENERATION".
|
||||||
|
///
|
||||||
|
/// ## Example:
|
||||||
|
/// ```dart
|
||||||
|
/// throw CodeGenerationException(
|
||||||
|
/// 'Could not generate module binding',
|
||||||
|
/// element: classElement,
|
||||||
|
/// suggestion: 'Check module class methods and signatures.',
|
||||||
|
/// );
|
||||||
|
/// ```
|
||||||
|
/// ---------------------------------------------------------------------------
|
||||||
class CodeGenerationException extends CherryPickGeneratorException {
|
class CodeGenerationException extends CherryPickGeneratorException {
|
||||||
CodeGenerationException(
|
CodeGenerationException(
|
||||||
super.message, {
|
super.message, {
|
||||||
@@ -107,6 +185,23 @@ class CodeGenerationException extends CherryPickGeneratorException {
|
|||||||
}) : super(category: 'CODE_GENERATION');
|
}) : super(category: 'CODE_GENERATION');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// ---------------------------------------------------------------------------
|
||||||
|
/// DependencyResolutionException
|
||||||
|
///
|
||||||
|
/// Thrown if dependency information (for example, types or names)
|
||||||
|
/// cannot be resolved during code generation analysis.
|
||||||
|
///
|
||||||
|
/// Grouped as category "DEPENDENCY_RESOLUTION".
|
||||||
|
///
|
||||||
|
/// ## Example:
|
||||||
|
/// ```dart
|
||||||
|
/// throw DependencyResolutionException(
|
||||||
|
/// 'Dependency type not found in scope',
|
||||||
|
/// element: someElement,
|
||||||
|
/// suggestion: 'Check CherryPick registration for this type.',
|
||||||
|
/// );
|
||||||
|
/// ```
|
||||||
|
/// ---------------------------------------------------------------------------
|
||||||
class DependencyResolutionException extends CherryPickGeneratorException {
|
class DependencyResolutionException extends CherryPickGeneratorException {
|
||||||
DependencyResolutionException(
|
DependencyResolutionException(
|
||||||
super.message, {
|
super.message, {
|
||||||
|
|||||||
@@ -12,45 +12,48 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import 'package:analyzer/dart/element/element.dart';
|
import 'package:analyzer/dart/element/element.dart';
|
||||||
|
|
||||||
import 'bind_spec.dart';
|
import 'bind_spec.dart';
|
||||||
|
|
||||||
/// ---------------------------------------------------------------------------
|
/// ---------------------------------------------------------------------------
|
||||||
/// GeneratedClass -- represents the result of processing a single module class.
|
/// GeneratedClass
|
||||||
///
|
///
|
||||||
/// ENGLISH
|
/// Represents a processed DI module class with all its binding methods analyzed.
|
||||||
/// Encapsulates all the information produced from analyzing a DI module class:
|
/// Stores:
|
||||||
/// - The original class name,
|
/// - The original class name,
|
||||||
/// - Its generated class name (e.g., `$SomeModule`),
|
/// - The generated implementation class name (with $ prefix),
|
||||||
/// - The collection of bindings (BindSpec) for all implemented provider methods.
|
/// - The list of all BindSpec for the module methods,
|
||||||
|
/// - The source file name for reference or directive generation.
|
||||||
///
|
///
|
||||||
/// Also provides code generation functionality, allowing to generate the source
|
/// Provides static and instance methods to construct from a ClassElement
|
||||||
/// code for the derived DI module class, including all binding registrations.
|
/// and generate Dart source code for the resulting DI registration class.
|
||||||
///
|
///
|
||||||
/// RUSSIAN
|
/// ## Example usage
|
||||||
/// Описывает результат обработки одного класса-модуля DI:
|
/// ```dart
|
||||||
/// - Имя оригинального класса,
|
/// final gen = GeneratedClass.fromClassElement(myModuleClassElement);
|
||||||
/// - Имя генерируемого класса (например, `$SomeModule`),
|
/// print(gen.generate());
|
||||||
/// - Список всех бидингов (BindSpec) — по публичным методам модуля.
|
/// /*
|
||||||
///
|
/// Produces:
|
||||||
/// Также содержит функцию генерации исходного кода для этого класса и
|
/// final class $MyModule extends MyModule {
|
||||||
/// регистрации всех зависимостей через bind(...).
|
/// @override
|
||||||
|
/// void builder(Scope currentScope) {
|
||||||
|
/// bind<Service>().toProvide(() => provideService(currentScope.resolve<Dep>()));
|
||||||
|
/// ...
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// */
|
||||||
|
/// ```
|
||||||
/// ---------------------------------------------------------------------------
|
/// ---------------------------------------------------------------------------
|
||||||
class GeneratedClass {
|
class GeneratedClass {
|
||||||
/// The name of the original module class.
|
/// Name of the original Dart module class.
|
||||||
/// Имя исходного класса-модуля
|
|
||||||
final String className;
|
final String className;
|
||||||
|
|
||||||
/// The name of the generated class (e.g., $SomeModule).
|
/// Name of the generated class, e.g. `$MyModule`
|
||||||
/// Имя генерируемого класса (например, $SomeModule)
|
|
||||||
final String generatedClassName;
|
final String generatedClassName;
|
||||||
|
|
||||||
/// List of all discovered bindings for the class.
|
/// Binding specs for all provider/factory methods in the class.
|
||||||
/// Список всех обнаруженных биндингов
|
|
||||||
final List<BindSpec> binds;
|
final List<BindSpec> binds;
|
||||||
|
|
||||||
/// Source file name for the part directive
|
/// Source filename of the module class (for code references).
|
||||||
/// Имя исходного файла для part директивы
|
|
||||||
final String sourceFile;
|
final String sourceFile;
|
||||||
|
|
||||||
GeneratedClass(
|
GeneratedClass(
|
||||||
@@ -63,16 +66,15 @@ class GeneratedClass {
|
|||||||
/// -------------------------------------------------------------------------
|
/// -------------------------------------------------------------------------
|
||||||
/// fromClassElement
|
/// fromClassElement
|
||||||
///
|
///
|
||||||
/// ENGLISH
|
/// Creates a [GeneratedClass] by analyzing a Dart [ClassElement].
|
||||||
/// Static factory: creates a GeneratedClass from a Dart ClassElement (AST representation).
|
/// Collects all public non-abstract methods, creates a [BindSpec] for each,
|
||||||
/// Discovers all non-abstract methods, builds BindSpec for each, and computes the
|
/// and infers the generated class name using a `$` prefix.
|
||||||
/// generated class name by prefixing `$`.
|
|
||||||
///
|
///
|
||||||
/// RUSSIAN
|
/// ## Example usage
|
||||||
/// Строит объект класса по элементу AST (ClassElement): имя класса,
|
/// ```dart
|
||||||
/// сгенерированное имя, список BindSpec по всем не абстрактным методам.
|
/// final gen = GeneratedClass.fromClassElement(classElement);
|
||||||
/// Имя ген-класса строится с префиксом `$`.
|
/// print(gen.generatedClassName); // e.g. $AppModule
|
||||||
/// -------------------------------------------------------------------------
|
/// ```
|
||||||
static GeneratedClass fromClassElement(ClassElement element) {
|
static GeneratedClass fromClassElement(ClassElement element) {
|
||||||
final className = element.displayName;
|
final className = element.displayName;
|
||||||
// Generated class name with '$' prefix (standard for generated Dart code).
|
// Generated class name with '$' prefix (standard for generated Dart code).
|
||||||
@@ -91,16 +93,19 @@ class GeneratedClass {
|
|||||||
/// -------------------------------------------------------------------------
|
/// -------------------------------------------------------------------------
|
||||||
/// generate
|
/// generate
|
||||||
///
|
///
|
||||||
/// ENGLISH
|
/// Generates the Dart source code for the DI registration class.
|
||||||
/// Generates Dart source code for the DI module class. The generated class
|
/// The generated class extends the original module, and the `builder` method
|
||||||
/// inherits from the original, overrides the 'builder' method, and registers
|
/// registers all bindings (dependencies) into the DI scope.
|
||||||
/// all bindings in the DI scope.
|
|
||||||
///
|
///
|
||||||
/// RUSSIAN
|
/// ## Example output
|
||||||
/// Генерирует исходный Dart-код для класса-модуля DI.
|
/// ```dart
|
||||||
/// Новая версия класса наследует оригинальный, переопределяет builder(Scope),
|
/// final class $UserModule extends UserModule {
|
||||||
/// и регистрирует все зависимости через методы bind<Type>()...
|
/// @override
|
||||||
/// -------------------------------------------------------------------------
|
/// void builder(Scope currentScope) {
|
||||||
|
/// bind<Service>().toProvide(() => provideService(currentScope.resolve<Dep>()));
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
String generate() {
|
String generate() {
|
||||||
final buffer = StringBuffer()
|
final buffer = StringBuffer()
|
||||||
..writeln('final class $generatedClassName extends $className {')
|
..writeln('final class $generatedClassName extends $className {')
|
||||||
@@ -108,7 +113,6 @@ class GeneratedClass {
|
|||||||
..writeln(' void builder(Scope currentScope) {');
|
..writeln(' void builder(Scope currentScope) {');
|
||||||
|
|
||||||
// For each binding, generate bind<Type>() code string.
|
// For each binding, generate bind<Type>() code string.
|
||||||
// Для каждого биндинга — генерируем строку bind<Type>()...
|
|
||||||
for (final bind in binds) {
|
for (final bind in binds) {
|
||||||
buffer.writeln(bind.generateBind(4));
|
buffer.writeln(bind.generateBind(4));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,30 +14,32 @@
|
|||||||
import 'package:analyzer/dart/element/element.dart';
|
import 'package:analyzer/dart/element/element.dart';
|
||||||
|
|
||||||
/// ---------------------------------------------------------------------------
|
/// ---------------------------------------------------------------------------
|
||||||
/// MetadataUtils -- utilities for analyzing method and parameter annotations.
|
/// MetadataUtils
|
||||||
///
|
///
|
||||||
/// ENGLISH
|
/// Static utilities for querying and extracting information from
|
||||||
/// Provides static utility methods to analyze Dart annotations on methods or
|
/// Dart annotations ([ElementAnnotation]) in the context of code generation,
|
||||||
/// parameters. For instance, helps to find if an element is annotated with
|
/// such as checking for the presence of specific DI-related annotations.
|
||||||
/// `@named()`, `@singleton()`, or other meta-annotations used in this DI framework.
|
/// Designed to be used internally by code generation and validation routines.
|
||||||
///
|
///
|
||||||
/// RUSSIAN
|
/// # Example usage
|
||||||
/// Утилиты для разбора аннотаций методов и параметров.
|
/// ```dart
|
||||||
/// Позволяют находить наличие аннотаций, например, @named() и @singleton(),
|
/// if (MetadataUtils.anyMeta(method.metadata, 'singleton')) {
|
||||||
/// у методов и параметров. Используется для анализа исходного кода при генерации.
|
/// // The method is annotated with @singleton
|
||||||
|
/// }
|
||||||
|
/// final name = MetadataUtils.getNamedValue(field.metadata);
|
||||||
|
/// if (name != null) print('@named value: $name');
|
||||||
|
/// ```
|
||||||
/// ---------------------------------------------------------------------------
|
/// ---------------------------------------------------------------------------
|
||||||
class MetadataUtils {
|
class MetadataUtils {
|
||||||
/// -------------------------------------------------------------------------
|
/// Checks whether any annotation in [meta] matches the [typeName]
|
||||||
/// anyMeta
|
/// (type name is compared in a case-insensitive manner and can be partial).
|
||||||
///
|
///
|
||||||
/// ENGLISH
|
/// Returns true if an annotation (such as @singleton, @provide, @named) is found.
|
||||||
/// Checks if any annotation in the list has a type name that contains
|
|
||||||
/// [typeName] (case insensitive).
|
|
||||||
///
|
///
|
||||||
/// RUSSIAN
|
/// Example:
|
||||||
/// Проверяет: есть ли среди аннотаций метка, имя которой содержит [typeName]
|
/// ```dart
|
||||||
/// (регистр не учитывается).
|
/// bool isSingleton = MetadataUtils.anyMeta(myMethod.metadata, 'singleton');
|
||||||
/// -------------------------------------------------------------------------
|
/// ```
|
||||||
static bool anyMeta(List<ElementAnnotation> meta, String typeName) {
|
static bool anyMeta(List<ElementAnnotation> meta, String typeName) {
|
||||||
return meta.any((m) =>
|
return meta.any((m) =>
|
||||||
m
|
m
|
||||||
@@ -49,17 +51,15 @@ class MetadataUtils {
|
|||||||
false);
|
false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// -------------------------------------------------------------------------
|
/// Extracts the string value from a `@named('value')` annotation if present in [meta].
|
||||||
/// getNamedValue
|
|
||||||
///
|
///
|
||||||
/// ENGLISH
|
/// Returns the named value or `null` if not annotated.
|
||||||
/// Retrieves the value from a `@named('value')` annotation if present.
|
|
||||||
/// Returns the string value or null if not found.
|
|
||||||
///
|
///
|
||||||
/// RUSSIAN
|
/// Example:
|
||||||
/// Находит значение из аннотации @named('значение').
|
/// ```dart
|
||||||
/// Возвращает строку значения, если аннотация присутствует; иначе null.
|
/// // For: @named('dev') ApiClient provideApi() ...
|
||||||
/// -------------------------------------------------------------------------
|
/// final named = MetadataUtils.getNamedValue(method.metadata); // 'dev'
|
||||||
|
/// ```
|
||||||
static String? getNamedValue(List<ElementAnnotation> meta) {
|
static String? getNamedValue(List<ElementAnnotation> meta) {
|
||||||
for (final m in meta) {
|
for (final m in meta) {
|
||||||
final cv = m.computeConstantValue();
|
final cv = m.computeConstantValue();
|
||||||
|
|||||||
@@ -16,9 +16,35 @@ import 'package:analyzer/dart/element/nullability_suffix.dart';
|
|||||||
import 'package:analyzer/dart/element/type.dart';
|
import 'package:analyzer/dart/element/type.dart';
|
||||||
import 'exceptions.dart';
|
import 'exceptions.dart';
|
||||||
|
|
||||||
/// Enhanced type parser that uses AST analysis instead of regular expressions
|
/// Utility for analyzing and parsing Dart types for CherryPick DI code generation.
|
||||||
|
///
|
||||||
|
/// This type parser leverages the Dart analyzer AST to extract nuanced information
|
||||||
|
/// from Dart types encountered in the source code, in particular for dependency
|
||||||
|
/// injection purposes. It is capable of extracting nullability, generics,
|
||||||
|
/// and Future-related metadata with strong guarantees and handles even nested generics.
|
||||||
|
///
|
||||||
|
/// # Example usage for parsing types:
|
||||||
|
/// ```dart
|
||||||
|
/// final parsed = TypeParser.parseType(method.returnType, method);
|
||||||
|
/// print(parsed);
|
||||||
|
/// print(parsed.resolveMethodName); // e.g. "resolveAsync" or "tryResolve"
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// # Supported scenarios:
|
||||||
|
/// - Nullable types (e.g., `List<String>?`)
|
||||||
|
/// - Generic types (e.g., `Map<String, User>`)
|
||||||
|
/// - Async types (`Future<T>`, including nested generics)
|
||||||
|
/// - Validation for DI compatibility (throws for `void`, warns on `dynamic`)
|
||||||
class TypeParser {
|
class TypeParser {
|
||||||
/// Parses a DartType and extracts detailed type information
|
/// Parses a [DartType] and extracts detailed type information for use in code generation.
|
||||||
|
///
|
||||||
|
/// If a type is not suitable or cannot be parsed, a [TypeParsingException] is thrown.
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
/// ```dart
|
||||||
|
/// final parsed = TypeParser.parseType(field.type, field);
|
||||||
|
/// if (parsed.isNullable) print('Field is nullable');
|
||||||
|
/// ```
|
||||||
static ParsedType parseType(DartType dartType, Element context) {
|
static ParsedType parseType(DartType dartType, Element context) {
|
||||||
try {
|
try {
|
||||||
return _parseTypeInternal(dartType, context);
|
return _parseTypeInternal(dartType, context);
|
||||||
@@ -49,7 +75,7 @@ class TypeParser {
|
|||||||
return _parseGenericType(dartType, context, isNullable);
|
return _parseGenericType(dartType, context, isNullable);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Simple type
|
// Simple type (non-generic, non-Future)
|
||||||
return ParsedType(
|
return ParsedType(
|
||||||
displayString: displayString,
|
displayString: displayString,
|
||||||
coreType: displayString.replaceAll('?', ''),
|
coreType: displayString.replaceAll('?', ''),
|
||||||
@@ -103,7 +129,15 @@ class TypeParser {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Validates that a type is suitable for dependency injection
|
/// Validates that a parsed type is suitable for dependency injection.
|
||||||
|
///
|
||||||
|
/// Throws [TypeParsingException] for void and may warn for dynamic.
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
/// ```dart
|
||||||
|
/// final parsed = TypeParser.parseType(field.type, field);
|
||||||
|
/// TypeParser.validateInjectableType(parsed, field);
|
||||||
|
/// ```
|
||||||
static void validateInjectableType(ParsedType parsedType, Element context) {
|
static void validateInjectableType(ParsedType parsedType, Element context) {
|
||||||
// Check for void type
|
// Check for void type
|
||||||
if (parsedType.coreType == 'void') {
|
if (parsedType.coreType == 'void') {
|
||||||
@@ -131,7 +165,7 @@ class TypeParser {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Represents a parsed type with detailed information
|
/// Represents a parsed type with full metadata for code generation.
|
||||||
class ParsedType {
|
class ParsedType {
|
||||||
/// The full display string of the type (e.g., "Future<List<String>?>")
|
/// The full display string of the type (e.g., "Future<List<String>?>")
|
||||||
final String displayString;
|
final String displayString;
|
||||||
@@ -139,19 +173,19 @@ class ParsedType {
|
|||||||
/// The core type name without nullability and Future wrapper (e.g., "List<String>")
|
/// The core type name without nullability and Future wrapper (e.g., "List<String>")
|
||||||
final String coreType;
|
final String coreType;
|
||||||
|
|
||||||
/// Whether the type is nullable
|
/// True if nullable (has `?`)
|
||||||
final bool isNullable;
|
final bool isNullable;
|
||||||
|
|
||||||
/// Whether the type is wrapped in Future
|
/// True if this type is a `Future<T>`
|
||||||
final bool isFuture;
|
final bool isFuture;
|
||||||
|
|
||||||
/// Whether the type has generic parameters
|
/// True if the type is a generic type (`List<T>`)
|
||||||
final bool isGeneric;
|
final bool isGeneric;
|
||||||
|
|
||||||
/// Parsed type arguments for generic types
|
/// List of parsed type arguments in generics, if any.
|
||||||
final List<ParsedType> typeArguments;
|
final List<ParsedType> typeArguments;
|
||||||
|
|
||||||
/// For Future types, the inner type
|
/// For `Future<T>`, this is the type inside the `Future`.
|
||||||
final ParsedType? innerType;
|
final ParsedType? innerType;
|
||||||
|
|
||||||
const ParsedType({
|
const ParsedType({
|
||||||
@@ -164,7 +198,11 @@ class ParsedType {
|
|||||||
this.innerType,
|
this.innerType,
|
||||||
});
|
});
|
||||||
|
|
||||||
/// Returns the type string suitable for code generation
|
/// Generates the type string suitable for code generation.
|
||||||
|
///
|
||||||
|
/// - For futures, the codegen type of the inner type is returned
|
||||||
|
/// - For generics, returns e.g. `List<User>`
|
||||||
|
/// - For plain types, just the name
|
||||||
String get codeGenType {
|
String get codeGenType {
|
||||||
if (isFuture && innerType != null) {
|
if (isFuture && innerType != null) {
|
||||||
return innerType!.codeGenType;
|
return innerType!.codeGenType;
|
||||||
@@ -179,10 +217,10 @@ class ParsedType {
|
|||||||
return coreType;
|
return coreType;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns whether this type should use tryResolve instead of resolve
|
/// True if this type should use `tryResolve` instead of `resolve` for DI.
|
||||||
bool get shouldUseTryResolve => isNullable;
|
bool get shouldUseTryResolve => isNullable;
|
||||||
|
|
||||||
/// Returns the appropriate resolve method name
|
/// Returns the method name for DI, e.g. "resolve", "tryResolveAsync", etc.
|
||||||
String get resolveMethodName {
|
String get resolveMethodName {
|
||||||
if (isFuture) {
|
if (isFuture) {
|
||||||
return shouldUseTryResolve ? 'tryResolveAsync' : 'resolveAsync';
|
return shouldUseTryResolve ? 'tryResolveAsync' : 'resolveAsync';
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ name: cherrypick_generator
|
|||||||
description: |
|
description: |
|
||||||
Source code generator for the cherrypick dependency injection system. Processes annotations to generate binding and module code for Dart & Flutter projects.
|
Source code generator for the cherrypick dependency injection system. Processes annotations to generate binding and module code for Dart & Flutter projects.
|
||||||
|
|
||||||
version: 1.1.1
|
version: 2.0.0-dev.0
|
||||||
documentation: https://github.com/pese-git/cherrypick/wiki
|
documentation: https://github.com/pese-git/cherrypick/wiki
|
||||||
repository: https://github.com/pese-git/cherrypick/cherrypick_generator
|
repository: https://github.com/pese-git/cherrypick/cherrypick_generator
|
||||||
issue_tracker: https://github.com/pese-git/cherrypick/issues
|
issue_tracker: https://github.com/pese-git/cherrypick/issues
|
||||||
@@ -18,7 +18,7 @@ environment:
|
|||||||
|
|
||||||
# Add regular dependencies here.
|
# Add regular dependencies here.
|
||||||
dependencies:
|
dependencies:
|
||||||
cherrypick_annotations: ^1.1.1
|
cherrypick_annotations: ^1.1.2-dev.0
|
||||||
analyzer: ^7.0.0
|
analyzer: ^7.0.0
|
||||||
dart_style: ^3.0.0
|
dart_style: ^3.0.0
|
||||||
build: ^2.4.1
|
build: ^2.4.1
|
||||||
|
|||||||
@@ -127,7 +127,7 @@ packages:
|
|||||||
path: "../../cherrypick"
|
path: "../../cherrypick"
|
||||||
relative: true
|
relative: true
|
||||||
source: path
|
source: path
|
||||||
version: "3.0.0-dev.7"
|
version: "3.0.0-dev.8"
|
||||||
cherrypick_annotations:
|
cherrypick_annotations:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -141,7 +141,7 @@ packages:
|
|||||||
path: "../../cherrypick_flutter"
|
path: "../../cherrypick_flutter"
|
||||||
relative: true
|
relative: true
|
||||||
source: path
|
source: path
|
||||||
version: "1.1.3-dev.7"
|
version: "1.1.3-dev.8"
|
||||||
cherrypick_generator:
|
cherrypick_generator:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
|
|||||||
@@ -175,7 +175,7 @@ packages:
|
|||||||
path: "../../cherrypick"
|
path: "../../cherrypick"
|
||||||
relative: true
|
relative: true
|
||||||
source: path
|
source: path
|
||||||
version: "3.0.0-dev.7"
|
version: "3.0.0-dev.8"
|
||||||
cherrypick_annotations:
|
cherrypick_annotations:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|||||||
14
pubspec.lock
14
pubspec.lock
@@ -5,23 +5,23 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: _fe_analyzer_shared
|
name: _fe_analyzer_shared
|
||||||
sha256: "16e298750b6d0af7ce8a3ba7c18c69c3785d11b15ec83f6dcd0ad2a0009b3cab"
|
sha256: "45cfa8471b89fb6643fe9bf51bd7931a76b8f5ec2d65de4fb176dba8d4f22c77"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "76.0.0"
|
version: "73.0.0"
|
||||||
_macros:
|
_macros:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description: dart
|
description: dart
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.3.3"
|
version: "0.3.2"
|
||||||
analyzer:
|
analyzer:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: analyzer
|
name: analyzer
|
||||||
sha256: "1f14db053a8c23e260789e9b0980fa27f2680dd640932cae5e1137cce0e46e1e"
|
sha256: "4959fec185fe70cce007c57e9ab6983101dbe593d2bf8bbfb4453aaec0cf470a"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.11.0"
|
version: "6.8.0"
|
||||||
ansi_styles:
|
ansi_styles:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -298,10 +298,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: macros
|
name: macros
|
||||||
sha256: "1d9e801cd66f7ea3663c45fc708450db1fa57f988142c64289142c9b7ee80656"
|
sha256: "0acaed5d6b7eab89f63350bccd82119e6c602df0f391260d0e32b5e23db79536"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.1.3-main.0"
|
version: "0.1.2-main.4"
|
||||||
matcher:
|
matcher:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
29
talker_cherrypick_logger/.gitignore
vendored
29
talker_cherrypick_logger/.gitignore
vendored
@@ -1,7 +1,26 @@
|
|||||||
# https://dart.dev/guides/libraries/private-files
|
# See https://www.dartlang.org/guides/libraries/private-files
|
||||||
# Created by `dart pub`
|
|
||||||
.dart_tool/
|
|
||||||
|
|
||||||
# Avoid committing pubspec.lock for library packages; see
|
# Files and directories created by pub
|
||||||
# https://dart.dev/guides/libraries/private-files#pubspeclock.
|
.dart_tool/
|
||||||
|
.packages
|
||||||
|
build/
|
||||||
|
# If you're building an application, you may want to check-in your pubspec.lock
|
||||||
pubspec.lock
|
pubspec.lock
|
||||||
|
|
||||||
|
# Directory created by dartdoc
|
||||||
|
# If you don't generate documentation locally you can remove this line.
|
||||||
|
doc/api/
|
||||||
|
|
||||||
|
# Avoid committing generated Javascript files:
|
||||||
|
*.dart.js
|
||||||
|
*.info.json # Produced by the --dump-info flag.
|
||||||
|
*.js # When generated by dart2js. Don't specify *.js if your
|
||||||
|
# project includes source files written in JavaScript.
|
||||||
|
*.js_
|
||||||
|
*.js.deps
|
||||||
|
*.js.map
|
||||||
|
|
||||||
|
# FVM Version Cache
|
||||||
|
.fvm/
|
||||||
|
|
||||||
|
pubspec_overrides.yaml
|
||||||
@@ -1,3 +1,15 @@
|
|||||||
|
## 1.1.0-dev.3
|
||||||
|
|
||||||
|
## 1.1.0-dev.2
|
||||||
|
|
||||||
|
- Bump "talker_cherrypick_logger" to `1.1.0-dev.2`.
|
||||||
|
|
||||||
|
## 1.1.0-dev.0
|
||||||
|
|
||||||
|
- **FEAT**(logging): add talker_dio_logger and talker_bloc_logger integration, improve cherrypick logger structure, add UI log screen for DI and network/bloc debug.
|
||||||
|
- **DOCS**: add full English documentation and usage guide to README.md.
|
||||||
|
- **DOCS**: add detailed English documentation and usage examples for TalkerCherryPickObserver.
|
||||||
|
|
||||||
## 1.0.0
|
## 1.0.0
|
||||||
|
|
||||||
- Initial version.
|
- Initial version.
|
||||||
|
|||||||
@@ -1,16 +1,24 @@
|
|||||||
name: talker_cherrypick_logger
|
name: talker_cherrypick_logger
|
||||||
description: A starting point for Dart libraries or applications.
|
description: A Talker logger integration for CherryPick DI to observe and log DI events and errors.
|
||||||
version: 1.0.0
|
version: 1.1.0-dev.3
|
||||||
publish_to: none
|
homepage: https://pese-git.github.io/cherrypick-site/
|
||||||
# repository: https://github.com/my_org/my_repo
|
documentation: https://github.com/pese-git/cherrypick/wiki
|
||||||
|
repository: https://github.com/pese-git/cherrypick
|
||||||
|
issue_tracker: https://github.com/pese-git/cherrypick/issues
|
||||||
|
|
||||||
|
topics:
|
||||||
|
- cherrypick
|
||||||
|
- state
|
||||||
|
- logging
|
||||||
|
- log
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ^3.7.2
|
sdk: ">=3.5.2 <4.0.0"
|
||||||
|
|
||||||
# Add regular dependencies here.
|
# Add regular dependencies here.
|
||||||
dependencies:
|
dependencies:
|
||||||
talker: ^4.9.3
|
talker: ^4.9.3
|
||||||
cherrypick: ^3.0.0-dev.8
|
cherrypick: ^3.0.0-dev.9
|
||||||
|
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
|
|||||||
Reference in New Issue
Block a user