Compare commits

..

12 Commits

Author SHA1 Message Date
Sergey Penkovsky
99e662124f chore(release): publish packages
- talker_cherrypick_logger@1.1.0-dev.3
2025-08-13 15:27:51 +03:00
Sergey Penkovsky
03f54981f3 chore(talker_cherrypick_logger): update package description in pubspec.yaml 2025-08-13 15:26:53 +03:00
Sergey Penkovsky
349efe6ba6 chore(release): publish packages
- talker_cherrypick_logger@1.1.0-dev.2
2025-08-13 15:23:21 +03:00
Sergey Penkovsky
c2f0e027b6 fix(gitignore) - update gitignore 2025-08-13 15:18:39 +03:00
Sergey Penkovsky
f85036d20f chore(release): publish packages
- cherrypick@3.0.0-dev.9
 - cherrypick_annotations@1.1.2-dev.0
 - cherrypick_flutter@1.1.3-dev.9
 - cherrypick_generator@2.0.0-dev.0
 - talker_cherrypick_logger@1.1.0-dev.0
2025-08-13 15:11:23 +03:00
Sergey Penkovsky
db4d128d04 docs(readme): add talker_cherrypick_logger to Additional Modules section
Added information about the talker_cherrypick_logger official module in the Additional Modules table in README. This module provides seamless DI event logging integration with the Talker logging framework.
2025-08-13 15:07:12 +03:00
Sergey Penkovsky
2c4e2ed251 chore(pubspec): update metadata with homepage, docs, repository and topics
Added homepage URL, documentation, repository, issue tracker, and topics to pubspec.yaml for better package metadata. Removed 'publish_to: none' and outdated repository comment.
2025-08-13 14:56:10 +03:00
Sergey Penkovsky
7b4642f407 doc(readme): update readme 2025-08-13 09:11:06 +03:00
Sergey Penkovsky
7d45d00d6a docs(generator): improve and unify English documentation and examples for all DI source files
- Added comprehensive English documentation for all DI generator and support files:
  * inject_generator.dart — full class/method doc-comments, usage samples
  * module_generator.dart — doc-comments, feature explanation, complete example
  * src/annotation_validator.dart — class and detailed static method descriptions
  * src/type_parser.dart — doc, example for ParsedType and TypeParser, specific codegen notes
  * src/bind_spec.dart — interface, static factory, and codegen docs with DI scenarios
  * src/bind_parameters_spec.dart — details and samples for code generation logic
  * src/metadata_utils.dart — full doc and examples for annotation utilities
  * src/exceptions.dart — user- and contributor-friendly errors, structured output, category explanations
  * src/generated_class.dart — usage-centric doc-comments, example of resulting generated DI class
- Removed Russian/duplicate comments for full clarity and maintainability
- Ensured that new and existing contributors can easily extend and maintain DI code generation logic

BREAKING CHANGE: All documentation now English-only; comments include usage examples for each principal structure or routine

See #docs, #generator, #cherrypick
2025-08-13 08:57:06 +03:00
Sergey Penkovsky
884df50a34 docs(annotations): unify and improve English DartDoc for all DI annotations
- Updated all annotation files with complete English DartDoc, field/class/method usage, practical code samples
- Unified documentation style for @inject, @injectable, @instance, @singleton, @named, @scope, @params, @provide, @module
- Removed Russian comments for clarity and consistency
- Improved discoverability and IDE/autocomplete experience for CherryPick DI users
- No functional or API changes; documentation/dev experience only
2025-08-12 16:18:53 +03:00
Sergey Penkovsky
5710af2f9b docs(provider): add detailed English API documentation for CherryPickProvider Flutter integration
- Replaced all comments with complete DartDoc in English for CherryPickProvider
- Documented all methods (constructor, of, openRootScope, openSubScope, updateShouldNotify)
- Added code samples for typical Flutter+CherryPick integration, root and subscope usage
- Makes Flutter DI integration intuitive for new users and improves IDE support
- No logic or API changes, documentation only
2025-08-12 15:46:14 +03:00
Sergey Penkovsky
9312ef46ea docs(api): improve all DI core code documentation with English dartdoc and examples
- Full English API/class/method documentation for core CherryPick classes:
  * Binding<T>
  * BindingResolver<T>
  * CycleDetector and CycleDetectionMixin
  * GlobalCycleDetector and GlobalCycleDetectionMixin
  * Factory<T>
  * Module
  * Scope
- Each public and private method is now documented in clear DartDoc style with usages
- Added code samples for modules, scope, subscopes, DI resolve/async, cycle detection
- Russian comments completely replaced with English for consistency
- NO logic or API changes, documentation and devex improvement only

Also updated: pubspec.lock for workspace/example folders (auto by dependency changes)
2025-08-12 15:38:15 +03:00
45 changed files with 1402 additions and 841 deletions

View File

@@ -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

View File

@@ -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:

View File

@@ -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.

View File

@@ -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.

View File

@@ -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(

View File

@@ -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 ?? [];
} }

View File

@@ -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);
} }

View File

@@ -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;
} }

View File

@@ -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);
} }

View File

@@ -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) {

View File

@@ -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

View File

@@ -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.

View File

@@ -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.

View File

@@ -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();
} }

View File

@@ -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();
} }

View File

@@ -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();
} }

View File

@@ -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();
} }

View File

@@ -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);
} }

View File

@@ -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();
} }

View File

@@ -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();
} }

View File

@@ -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);
} }

View File

@@ -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();
} }

View File

@@ -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

View File

@@ -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.

View File

@@ -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;

View File

@@ -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:

View File

@@ -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.

View File

@@ -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';

View File

@@ -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');

View File

@@ -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');

View File

@@ -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,

View File

@@ -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;

View File

@@ -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

View File

@@ -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, {

View File

@@ -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));
} }

View File

@@ -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();

View File

@@ -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';

View File

@@ -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

View File

@@ -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:

View File

@@ -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:

View File

@@ -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:

View File

@@ -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

View File

@@ -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.

View File

@@ -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: