Compare commits

...

18 Commits

Author SHA1 Message Date
Sergey Penkovsky
21955640d9 chore(release): publish packages
- cherrypick_annotations@1.1.0-dev.1
 - cherrypick_generator@1.1.0-dev.3
2025-05-23 16:11:08 +03:00
Sergey Penkovsky
a62052daa5 doc: update documentations 2025-05-23 16:10:09 +03:00
Sergey Penkovsky
7dbaa59c01 refactor inject generator 2025-05-23 16:03:29 +03:00
Sergey Penkovsky
8438697107 implement inject generator 2025-05-23 15:26:09 +03:00
Sergey Penkovsky
9c42ba4cef feat: implement InjectGenerator 2025-05-23 14:08:08 +03:00
Sergey Penkovsky
1f6ee172a1 starting implement inject generator 2025-05-23 12:21:23 +03:00
Sergey Penkovsky
161e9085f4 chore(release): publish packages
- cherrypick_generator@1.1.0-dev.2
2025-05-23 08:21:46 +03:00
Sergey Penkovsky
ef49595627 doc: update documentations 2025-05-23 08:21:11 +03:00
Sergey Penkovsky
0fd10488f3 update deps 2025-05-23 08:06:28 +03:00
Sergey Penkovsky
46c2939125 fix: update instance generator code 2025-05-23 08:06:08 +03:00
Sergey Penkovsky
6d5537f068 update pubspec 2025-05-23 00:18:54 +03:00
Sergey Penkovsky
2480757797 update pubspec 2025-05-23 00:17:32 +03:00
Sergey Penkovsky
f8340c6a84 chore(release): publish packages
- cherrypick@2.2.0-dev.1
 - cherrypick_generator@1.1.0-dev.1
 - cherrypick_flutter@1.1.2-dev.1
2025-05-22 23:52:55 +03:00
Sergey Penkovsky
62a1655728 fix: fix warnings 2025-05-22 23:52:02 +03:00
Sergey Penkovsky
fc941c0041 update deps 2025-05-22 23:50:55 +03:00
Sergey Penkovsky
5161fa19b6 refactor code 2025-05-22 23:32:26 +03:00
Sergey Penkovsky
8093f077b1 fix: optimize code 2025-05-22 23:27:41 +03:00
Sergey Penkovsky
45b93db6f5 fix pubspecs 2025-05-22 16:58:59 +03:00
34 changed files with 713 additions and 153 deletions

View File

@@ -3,6 +3,86 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## 2025-05-23
### Changes
---
Packages with breaking changes:
- There are no breaking changes in this release.
Packages with other changes:
- [`cherrypick_annotations` - `v1.1.0-dev.1`](#cherrypick_annotations---v110-dev1)
- [`cherrypick_generator` - `v1.1.0-dev.3`](#cherrypick_generator---v110-dev3)
---
#### `cherrypick_annotations` - `v1.1.0-dev.1`
- **FEAT**: implement InjectGenerator.
#### `cherrypick_generator` - `v1.1.0-dev.3`
- **FEAT**: implement InjectGenerator.
## 2025-05-23
### Changes
---
Packages with breaking changes:
- There are no breaking changes in this release.
Packages with other changes:
- [`cherrypick_generator` - `v1.1.0-dev.2`](#cherrypick_generator---v110-dev2)
---
#### `cherrypick_generator` - `v1.1.0-dev.2`
- **FIX**: update instance generator code.
## 2025-05-22
### Changes
---
Packages with breaking changes:
- There are no breaking changes in this release.
Packages with other changes:
- [`cherrypick` - `v2.2.0-dev.1`](#cherrypick---v220-dev1)
- [`cherrypick_generator` - `v1.1.0-dev.1`](#cherrypick_generator---v110-dev1)
- [`cherrypick_flutter` - `v1.1.2-dev.1`](#cherrypick_flutter---v112-dev1)
Packages with dependency updates only:
> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project.
- `cherrypick_flutter` - `v1.1.2-dev.1`
---
#### `cherrypick` - `v2.2.0-dev.1`
- **FIX**: fix warnings.
#### `cherrypick_generator` - `v1.1.0-dev.1`
- **FIX**: optimize code.
## 2025-05-22
### Changes

View File

@@ -1,3 +1,7 @@
## 2.2.0-dev.1
- **FIX**: fix warnings.
## 2.2.0-dev.0
- **FEAT**: Add async dependency resolution and enhance example.

View File

@@ -1,5 +1,4 @@
import 'dart:async';
import 'package:meta/meta.dart';
import 'package:cherrypick/cherrypick.dart';
class AppModule extends Module {
@@ -95,21 +94,19 @@ class NetworkDataRepository implements DataRepository {
}
abstract class ApiClient {
Future sendRequest({@required String url, String token, Map requestBody});
Future sendRequest({String url, String token, Map requestBody});
}
class ApiClientMock implements ApiClient {
@override
Future sendRequest(
{@required String? url, String? token, Map? requestBody}) async {
Future sendRequest({String? url, String? token, Map? requestBody}) async {
return 'Local Data';
}
}
class ApiClientImpl implements ApiClient {
@override
Future sendRequest(
{@required String? url, String? token, Map? requestBody}) async {
Future sendRequest({String? url, String? token, Map? requestBody}) async {
return 'Network data';
}
}

View File

@@ -1,6 +1,6 @@
name: cherrypick
description: Cherrypick is a small dependency injection (DI) library for dart/flutter projects.
version: 2.2.0-dev.0
version: 2.2.0-dev.1
homepage: https://pese-git.github.io/cherrypick-site/
documentation: https://github.com/pese-git/cherrypick/wiki
repository: https://github.com/pese-git/cherrypick

View File

@@ -1,3 +1,7 @@
## 1.1.0-dev.1
- **FEAT**: implement InjectGenerator.
## 1.1.0-dev.0
- **FEAT**: implement generator for dynamic params.

View File

@@ -19,3 +19,6 @@ export 'src/instance.dart';
export 'src/singleton.dart';
export 'src/named.dart';
export 'src/params.dart';
export 'src/inject.dart';
export 'src/injectable.dart';
export 'src/scope.dart';

View File

@@ -0,0 +1,20 @@
//
// Copyright 2021 Sergey Penkovsky (sergey.penkovsky@gmail.com)
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import 'package:meta/meta.dart';
@experimental
// ignore: camel_case_types
final class inject {
const inject();
}

View File

@@ -0,0 +1,20 @@
//
// Copyright 2021 Sergey Penkovsky (sergey.penkovsky@gmail.com)
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import 'package:meta/meta.dart';
@experimental
// ignore: camel_case_types
final class injectable {
const injectable();
}

View File

@@ -11,11 +11,12 @@
// limitations under the License.
//
/// An annotation to specify that a method or class provides a new instance
/// each time it is requested.
/// ENGLISH:
/// Annotation to specify that a new instance should be provided on each request.
///
/// This is typically used to indicate that the annotated binding should
/// not be a singleton and a new object is created for every injection.
/// Use the `@instance()` annotation for methods or classes in your DI module
/// to declare that the DI container must create a new object every time
/// the dependency is injected (i.e., no singleton behavior).
///
/// Example:
/// ```dart
@@ -35,6 +36,32 @@
/// }
/// }
/// ```
///
/// RUSSIAN (Русский):
/// Аннотация для создания нового экземпляра при каждом запросе.
///
/// Используйте `@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 {
const instance();

View File

@@ -11,25 +11,57 @@
// limitations under the License.
//
/// An annotation used to mark a Dart class or library as a module.
/// ENGLISH:
/// Annotation for marking Dart classes or libraries as modules.
///
/// This annotation can be used for tooling, code generation,
/// or to provide additional metadata about the module.
/// Use the `@module()` annotation on abstract classes (or on a library)
/// to indicate that the class represents a DI (Dependency Injection) module.
/// This is commonly used in code generation tools to automatically register
/// and configure dependencies defined within the module.
///
/// Example:
/// ```dart
/// @module()
/// abstract class AppModule extends Module {
/// // Dependency definitions go here.
/// }
/// ```
/// Сгенерирует код:
///
/// Generates code like:
/// ```dart
/// final class $AppModule extends AppModule {
/// @override
/// void builder(Scope currentScope) {
///
/// // Dependency registration...
/// }
/// }
/// ```
///
/// RUSSIAN (Русский):
/// Аннотация для пометки классов или библиотек Dart как модуля.
///
/// Используйте `@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 {
/// Creates a [module] annotation.

View File

@@ -11,10 +11,13 @@
// limitations under the License.
//
/// An annotation to assign a name or identifier to a class, method, or other element.
/// ENGLISH:
/// Annotation to assign a name or identifier to a class, method, or other element.
///
/// This can be useful for code generation, dependency injection,
/// or providing metadata within a framework.
/// The `@named('value')` annotation allows you to specify a string name
/// for a dependency, factory, or injectable. This is useful for distinguishing
/// between multiple registrations of the same type in dependency injection,
/// code generation, and for providing human-readable metadata.
///
/// Example:
/// ```dart
@@ -25,7 +28,33 @@
/// }
/// ```
///
/// Сгенерирует код:
/// This will generate:
/// ```dart
/// final class $AppModule extends AppModule {
/// @override
/// void builder(Scope currentScope) {
/// bind<Dio>().toProvide(() => dio()).withName('dio').singleton();
/// }
/// }
/// ```
///
/// RUSSIAN (Русский):
/// Аннотация для задания имени или идентификатора классу, методу или другому элементу.
///
/// Аннотация `@named('значение')` позволяет указать строковое имя для зависимости,
/// фабрики или внедряемого значения. Это удобно для различения нескольких
/// регистраций одного типа в DI, генерации кода.
///
/// Пример:
/// ```dart
/// @module()
/// abstract class AppModule extends Module {
/// @named('dio')
/// Dio dio() => Dio();
/// }
/// ```
///
/// Будет сгенерирован следующий код:
/// ```dart
/// final class $AppModule extends AppModule {
/// @override
@@ -36,9 +65,13 @@
/// ```
// ignore: camel_case_types
final class named {
/// The assigned name or identifier.
/// EN: The assigned name or identifier for the element.
///
/// RU: Назначенное имя или идентификатор для элемента.
final String value;
/// Creates a [named] annotation with the given [value].
/// EN: Creates a [named] annotation with the given [value].
///
/// RU: Создаёт аннотацию [named] с заданным значением [value].
const named(this.value);
}

View File

@@ -11,11 +11,14 @@
// limitations under the License.
//
/// An annotation to indicate that a parameter is to be injected with run-time provided arguments.
/// ENGLISH:
/// Annotation to mark a method parameter for injection with run-time arguments.
///
/// Use this annotation to mark a method parameter that should receive arguments
/// passed during the resolution of a dependency (for example, through the
/// `.withParams(...)` method in the generated code).
/// Use the `@params()` annotation to specify that a particular parameter of a
/// provider method should be assigned a value supplied at resolution time,
/// rather than during static dependency graph creation. This is useful in DI
/// when a dependency must receive dynamic data passed by the consumer
/// (via `.withParams(...)` in the generated code).
///
/// Example:
/// ```dart
@@ -27,6 +30,26 @@
/// ```dart
/// bind<String>().toProvideWithParams((args) => greet(args));
/// ```
///
/// 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 {
const params();

View File

@@ -11,28 +11,56 @@
// limitations under the License.
//
/// An annotation to indicate that a method provides a dependency to the module.
/// ENGLISH:
/// Annotation to declare a factory/provider method or class as a singleton.
///
/// This annotation is typically used in conjunction with dependency injection,
/// marking methods whose return value should be registered as a provider.
/// The annotated method can optionally declare dependencies as parameters,
/// which will be resolved and injected automatically.
/// Use the `@singleton()` annotation on methods in your DI module to specify
/// that only one instance of the resulting object should be created and shared
/// for all consumers. This is especially useful in dependency injection
/// frameworks and service locators.
///
/// Example:
/// ```dart
/// @module()
/// abstract class AppModule extends Module {
/// @provide()
/// Foo foo(Bar bar) => Foo(bar);
/// @singleton()
/// Dio dio() => Dio();
/// }
/// ```
///
/// This will generate:
/// This generates the following code:
/// ```dart
/// final class $AppModule extends AppModule {
/// @override
/// void builder(Scope currentScope) {
/// bind<Foo>().toProvide(() => foo(currentScope.resolve<Bar>()));
/// 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();
/// }
/// }
/// ```

View File

@@ -0,0 +1,21 @@
//
// Copyright 2021 Sergey Penkovsky (sergey.penkovsky@gmail.com)
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import 'package:meta/meta.dart';
@experimental
// ignore: camel_case_types
final class scope {
final String? name;
const scope(this.name);
}

View File

@@ -11,11 +11,14 @@
// limitations under the License.
//
/// An annotation to declare a class as a singleton.
/// ENGLISH:
/// Annotation to declare a dependency as a singleton.
///
/// This can be used to indicate that only one instance of the class
/// should be created, which is often useful in dependency injection
/// frameworks or service locators.
/// Use the `@singleton()` annotation on provider methods inside a module
/// to indicate that only a single instance of this dependency should be
/// 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:
/// ```dart
@@ -25,7 +28,36 @@
/// Dio dio() => Dio();
/// }
/// ```
/// Сгенерирует код:
///
/// This will generate code like:
/// ```dart
/// final class $AppModule extends AppModule {
/// @override
/// void builder(Scope currentScope) {
/// bind<Dio>().toProvide(() => dio()).singleton();
/// }
/// }
/// ```
///
/// RUSSIAN (Русский):
/// Аннотация для объявления зависимости как синглтона.
///
/// Используйте `@singleton()` для методов-провайдеров внутри модуля,
/// чтобы указать, что соответствующий объект должен быть создан
/// единожды и использоваться во всём приложении (общий синглтон).
/// Это характерно для систем внедрения зависимостей и сервис-локаторов,
/// чтобы гарантировать один общий экземпляр.
///
/// Пример:
/// ```dart
/// @module()
/// abstract class AppModule extends Module {
/// @singleton()
/// Dio dio() => Dio();
/// }
/// ```
///
/// Будет сгенерирован следующий код:
/// ```dart
/// final class $AppModule extends AppModule {
/// @override

View File

@@ -1,8 +1,9 @@
name: cherrypick_annotations
description: A starting point for Dart libraries or applications.
version: 1.1.0-dev.0
description: |
Set of annotations for CherryPick dependency injection library. Enables code generation and declarative DI for Dart & Flutter projects.
version: 1.1.0-dev.1
documentation: https://github.com/pese-git/cherrypick/wiki
repository: https://github.com/pese-git/cherrypick
repository: https://github.com/pese-git/cherrypick/cherrypick_annotations
issue_tracker: https://github.com/pese-git/cherrypick/issues
environment:

View File

@@ -1,3 +1,7 @@
## 1.1.2-dev.1
- Update a dependency to the latest release.
## 1.1.2-dev.0
- **FIX**: fix warning.

View File

@@ -1,6 +1,6 @@
name: cherrypick_flutter
description: "Flutter library that allows access to the root scope through the context using `CherryPickProvider`."
version: 1.1.2-dev.0
version: 1.1.2-dev.1
homepage: https://pese-git.github.io/cherrypick-site/
documentation: https://github.com/pese-git/cherrypick/wiki
repository: https://github.com/pese-git/cherrypick
@@ -13,7 +13,7 @@ environment:
dependencies:
flutter:
sdk: flutter
cherrypick: ^2.2.0-dev.0
cherrypick: ^2.2.0-dev.1
dev_dependencies:
flutter_test:

View File

@@ -1,3 +1,15 @@
## 1.1.0-dev.3
- **FEAT**: implement InjectGenerator.
## 1.1.0-dev.2
- **FIX**: update instance generator code.
## 1.1.0-dev.1
- **FIX**: optimize code.
## 1.1.0-dev.0
- **FIX**: fix warning conflict with names.

View File

@@ -7,6 +7,14 @@ builders:
required_inputs: ["lib/**"]
runs_before: []
build_to: source
inject_generator:
import: "package:cherrypick_generator/inject_generator.dart"
builder_factories: ["injectBuilder"]
build_extensions: {".dart": [".cherrypick.g.dart"]}
auto_apply: dependents
required_inputs: ["lib/**"]
runs_before: []
build_to: source
targets:
$default:
@@ -14,3 +22,6 @@ targets:
cherrypick_generator|module_generator:
generate_for:
- lib/**.dart
cherrypick_generator|inject_generator:
generate_for:
- lib/**.dart

View File

@@ -14,3 +14,4 @@ library;
//
export 'module_generator.dart';
export 'inject_generator.dart';

View File

@@ -0,0 +1,186 @@
//
// Copyright 2021 Sergey Penkovsky (sergey.penkovsky@gmail.com)
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import 'dart:async';
import 'package:analyzer/dart/constant/value.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:build/build.dart';
import 'package:source_gen/source_gen.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:cherrypick_annotations/cherrypick_annotations.dart' as ann;
/// InjectGenerator generates a mixin for a class marked with @injectable()
/// and injects all fields annotated with @inject(), using CherryPick DI.
///
/// For Future<T> fields it calls .resolveAsync<T>(),
/// otherwise .resolve<T>() is used. Scope and named qualifiers are supported.
///
/// ---
///
/// InjectGenerator генерирует миксин для класса с аннотацией @injectable()
/// и внедряет все поля, помеченные @inject(), используя DI-фреймворк CherryPick.
///
/// Для Future<T> полей вызывается .resolveAsync<T>(),
/// для остальных — .resolve<T>(). Поддерживаются scope и named qualifier.
///
class InjectGenerator extends GeneratorForAnnotation<ann.injectable> {
const InjectGenerator();
/// The main entry point for code generation.
///
/// Checks class validity, collects injectable fields, and produces injection code.
///
/// Основная точка входа генератора. Проверяет класс, собирает инъектируемые поля и создает код внедрения зависимостей.
@override
FutureOr<String> generateForAnnotatedElement(
Element element,
ConstantReader annotation,
BuildStep buildStep,
) {
if (element is! ClassElement) {
throw InvalidGenerationSourceError(
'@injectable() can only be applied to classes.',
element: element,
);
}
final classElement = element;
final className = classElement.name;
final mixinName = '_\$$className';
final buffer = StringBuffer()
..writeln('mixin $mixinName {')
..writeln(' void _inject($className instance) {');
// Collect and process all @inject fields.
// Собираем и обрабатываем все поля с @inject.
final injectFields =
classElement.fields.where(_isInjectField).map(_parseInjectField);
for (final parsedField in injectFields) {
buffer.writeln(_generateInjectionLine(parsedField));
}
buffer
..writeln(' }')
..writeln('}');
return buffer.toString();
}
/// Checks if a field has the @inject annotation.
///
/// Проверяет, отмечено ли поле аннотацией @inject.
static bool _isInjectField(FieldElement field) {
return field.metadata.any(
(m) => m.computeConstantValue()?.type?.getDisplayString() == 'inject',
);
}
/// Parses the field for scope/named qualifiers and determines its type.
/// Returns a [_ParsedInjectField] describing injection information.
///
/// Разбирает поле на наличие модификаторов scope/named и выясняет его тип.
/// Возвращает [_ParsedInjectField] с информацией о внедрении.
static _ParsedInjectField _parseInjectField(FieldElement field) {
String? scopeName;
String? namedValue;
for (final meta in field.metadata) {
final DartObject? obj = meta.computeConstantValue();
final type = obj?.type?.getDisplayString();
if (type == 'scope') {
scopeName = obj?.getField('name')?.toStringValue();
} else if (type == 'named') {
namedValue = obj?.getField('value')?.toStringValue();
}
}
final DartType dartType = field.type;
String coreTypeName;
bool isFuture;
if (dartType.isDartAsyncFuture) {
final ParameterizedType paramType = dartType as ParameterizedType;
coreTypeName = paramType.typeArguments.first.getDisplayString();
isFuture = true;
} else {
coreTypeName = dartType.getDisplayString();
isFuture = false;
}
return _ParsedInjectField(
fieldName: field.name,
coreType: coreTypeName,
isFuture: isFuture,
scopeName: scopeName,
namedValue: namedValue,
);
}
/// Generates a line of code that performs the dependency injection for a field.
/// Handles resolve/resolveAsync, scoping, and named qualifiers.
///
/// Генерирует строку кода, которая внедряет зависимость для поля.
/// Учитывает resolve/resolveAsync, scoping и named qualifier.
String _generateInjectionLine(_ParsedInjectField field) {
final methodName = field.isFuture
? 'resolveAsync<${field.coreType}>'
: 'resolve<${field.coreType}>';
final openCall = (field.scopeName != null && field.scopeName!.isNotEmpty)
? "CherryPick.openScope(scopeName: '${field.scopeName}')"
: "CherryPick.openRootScope()";
final params = (field.namedValue != null && field.namedValue!.isNotEmpty)
? "(named: '${field.namedValue}')"
: '()';
return " instance.${field.fieldName} = $openCall.$methodName$params;";
}
}
/// Data structure representing all information required to generate
/// injection code for a field.
///
/// Структура данных, содержащая всю информацию,
/// необходимую для генерации кода внедрения для поля.
class _ParsedInjectField {
/// The name of the field / Имя поля.
final String fieldName;
/// The base type name (T or Future<T>) / Базовый тип (T или тип из Future<T>).
final String coreType;
/// True if the field type is Future<T>; false otherwise
/// Истина, если поле — Future<T>, иначе — ложь.
final bool isFuture;
/// Optional scope annotation argument / Опциональное имя scope.
final String? scopeName;
/// Optional named annotation argument / Опциональное имя named.
final String? namedValue;
_ParsedInjectField({
required this.fieldName,
required this.coreType,
required this.isFuture,
this.scopeName,
this.namedValue,
});
}
/// Builder factory. Used by build_runner.
///
/// Фабрика билдера. Используется build_runner.
Builder injectBuilder(BuilderOptions options) =>
PartBuilder([InjectGenerator()], '.inject.cherrypick.g.dart');

View File

@@ -90,4 +90,4 @@ class ModuleGenerator extends GeneratorForAnnotation<ann.module> {
/// файлов, где встречается @module().
/// ---------------------------------------------------------------------------
Builder moduleBuilder(BuilderOptions options) =>
PartBuilder([ModuleGenerator()], '.cherrypick.g.dart');
PartBuilder([ModuleGenerator()], '.module.cherrypick.g.dart');

View File

@@ -17,6 +17,11 @@ import 'package:source_gen/source_gen.dart';
import 'bind_parameters_spec.dart';
import 'metadata_utils.dart';
enum BindingType {
instance,
provide;
}
/// ---------------------------------------------------------------------------
/// BindSpec -- describes a binding specification generated for a dependency.
///
@@ -59,7 +64,7 @@ class BindSpec {
final List<BindParameterSpec> parameters;
/// Binding type: 'instance' or 'provide' (@instance or @provide)
final String bindingType; // 'instance' | 'provide'
final BindingType bindingType; // 'instance' | 'provide'
/// True if the method is asynchronous and uses instance binding (Future)
final bool isAsyncInstance;
@@ -116,16 +121,16 @@ class BindSpec {
String _generateWithParamsProvideClause(int indent) {
// Safe variable name for parameters.
const paramVar = 'args';
final fnArgs = parameters
.map((p) => p.isParams ? paramVar : p.generateArg(paramVar))
.join(', ');
final fnArgs = parameters.map((p) => p.generateArg(paramVar)).join(', ');
final multiLine = fnArgs.length > 60 || fnArgs.contains('\n');
switch (bindingType) {
case 'instance':
return isAsyncInstance
? '.toInstanceAsync(($fnArgs) => $methodName($fnArgs))'
: '.toInstance(($fnArgs) => $methodName($fnArgs))';
case 'provide':
case BindingType.instance:
throw StateError(
'Internal error: _generateWithParamsProvideClause called for @instance binding with @params.');
//return isAsyncInstance
// ? '.toInstanceAsync(($fnArgs) => $methodName($fnArgs))'
// : '.toInstance(($fnArgs) => $methodName($fnArgs))';
case BindingType.provide:
default:
if (isAsyncProvide) {
return multiLine
@@ -144,11 +149,11 @@ class BindSpec {
final argsStr = parameters.map((p) => p.generateArg()).join(', ');
final multiLine = argsStr.length > 60 || argsStr.contains('\n');
switch (bindingType) {
case 'instance':
case BindingType.instance:
return isAsyncInstance
? '.toInstanceAsync($methodName($argsStr))'
: '.toInstance($methodName($argsStr))';
case 'provide':
case BindingType.provide:
default:
if (isAsyncProvide) {
return multiLine
@@ -212,7 +217,17 @@ class BindSpec {
element: method,
);
}
final bindingType = hasInstance ? 'instance' : 'provide';
final bindingType =
hasInstance ? BindingType.instance : BindingType.provide;
// PROHIBIT @params with @instance bindings!
if (bindingType == BindingType.instance && hasParams) {
throw InvalidGenerationSourceError(
'@params() (runtime arguments) cannot be used together with @instance() on method $methodName. '
'Use @provide() instead if you want runtime arguments.',
element: method,
);
}
// -- Extract inner type for Future<T> and set async flags.
bool isAsyncInstance = false;
@@ -220,8 +235,8 @@ class BindSpec {
final futureInnerType = _extractFutureInnerType(returnType);
if (futureInnerType != null) {
returnType = futureInnerType;
if (bindingType == 'instance') isAsyncInstance = true;
if (bindingType == 'provide') isAsyncProvide = true;
if (bindingType == BindingType.instance) isAsyncInstance = true;
if (bindingType == BindingType.provide) isAsyncProvide = true;
}
return BindSpec(

View File

@@ -1,8 +1,10 @@
name: cherrypick_generator
description: Code generator for cherrypick annotations
version: 1.1.0-dev.0
description: |
Source code generator for the cherrypick dependency injection system. Processes annotations to generate binding and module code for Dart & Flutter projects.
version: 1.1.0-dev.3
documentation: https://github.com/pese-git/cherrypick/wiki
repository: https://github.com/pese-git/cherrypick
repository: https://github.com/pese-git/cherrypick/cherrypick_generator
issue_tracker: https://github.com/pese-git/cherrypick/issues
environment:
@@ -10,14 +12,15 @@ environment:
# Add regular dependencies here.
dependencies:
cherrypick_annotations: ^1.1.0-dev.0
analyzer: any
dart_style: any
build: any
build_runner: any
source_gen: any
cherrypick_annotations: ^1.1.0-dev.1
analyzer: ^6.7.0
dart_style: ^2.3.7
build: ^2.4.1
build_runner: ^2.4.13
source_gen: ^1.5.0
collection: ^1.18.0
dev_dependencies:
lints: ^5.0.0
mockito: any
mockito: ^5.4.4
test: ^1.25.8

View File

@@ -132,28 +132,28 @@ packages:
path: "../../cherrypick"
relative: true
source: path
version: "2.1.0"
version: "2.2.0-dev.1"
cherrypick_annotations:
dependency: "direct main"
description:
path: "../../cherrypick_annotations"
relative: true
source: path
version: "1.0.0"
version: "1.1.0-dev.0"
cherrypick_flutter:
dependency: "direct main"
description:
path: "../../cherrypick_flutter"
relative: true
source: path
version: "1.1.1"
version: "1.1.2-dev.1"
cherrypick_generator:
dependency: "direct dev"
description:
path: "../../cherrypick_generator"
relative: true
source: path
version: "1.0.0"
version: "1.1.0-dev.2"
clock:
dependency: transitive
description:
@@ -243,10 +243,10 @@ packages:
dependency: "direct dev"
description:
name: flutter_lints
sha256: "3f41d009ba7172d5ff9be5f6e6e6abb4300e263aab8866d2a0842ed2a70f8f0c"
sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1"
url: "https://pub.dev"
source: hosted
version: "4.0.0"
version: "5.0.0"
flutter_test:
dependency: "direct dev"
description: flutter
@@ -344,10 +344,10 @@ packages:
dependency: transitive
description:
name: lints
sha256: "976c774dd944a42e83e2467f4cc670daef7eed6295b10b36ae8c85bcbf828235"
sha256: "3315600f3fb3b135be672bf4a178c55f274bebe368325ae18462c89ac1e3b413"
url: "https://pub.dev"
source: hosted
version: "4.0.0"
version: "5.0.0"
logging:
dependency: transitive
description:

View File

@@ -11,10 +11,10 @@ environment:
dependencies:
flutter:
sdk: flutter
cherrypick: ^2.2.0-dev.0
cherrypick_flutter: ^1.1.2-dev.0
cherrypick: ^2.2.0-dev.1
cherrypick_flutter: ^1.1.2-dev.1
cherrypick_annotations: ^1.1.0-dev.0
cherrypick_annotations: ^1.1.0-dev.1
cupertino_icons: ^1.0.8
@@ -22,10 +22,10 @@ dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^4.0.0
flutter_lints: ^5.0.0
cherrypick_generator: ^1.1.0-dev.0
build_runner: any
cherrypick_generator: ^1.1.0-dev.3
build_runner: ^2.4.13
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec

View File

@@ -0,0 +1,35 @@
import 'package:cherrypick/cherrypick.dart';
import 'package:cherrypick_annotations/cherrypick_annotations.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'domain/repository/post_repository.dart';
import 'presentation/bloc/post_bloc.dart';
import 'router/app_router.dart';
part 'app.inject.cherrypick.g.dart';
@injectable()
class MyApp extends StatelessWidget with _$MyApp {
final _appRouter = AppRouter();
@named('repo')
@inject()
late final PostRepository repository;
MyApp({super.key}) {
_inject(this);
}
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (_) => PostBloc(repository),
child: MaterialApp.router(
routeInformationParser: _appRouter.defaultRouteParser(),
routerDelegate: _appRouter.delegate(),
theme: ThemeData.light(),
),
);
}
}

View File

@@ -5,7 +5,7 @@ import '../data/network/json_placeholder_api.dart';
import '../data/post_repository_impl.dart';
import '../domain/repository/post_repository.dart';
part 'app_module.cherrypick.g.dart';
part 'app_module.module.cherrypick.g.dart';
@module()
abstract class AppModule extends Module {

View File

@@ -1,36 +1,9 @@
import 'package:cherrypick/cherrypick.dart';
import 'package:flutter/material.dart';
import 'package:postly/app.dart';
import 'di/app_module.dart';
import 'domain/repository/post_repository.dart';
import 'presentation/bloc/post_bloc.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'router/app_router.dart';
void main() {
final scope = CherryPick.openRootScope();
scope.installModules([$AppModule()]);
runApp(MyApp(scope: scope));
}
class MyApp extends StatelessWidget {
final Scope scope;
final _appRouter = AppRouter();
MyApp({super.key, required this.scope});
@override
Widget build(BuildContext context) {
// Получаем репозиторий через injector
final repository = scope.resolve<PostRepository>(named: 'repo');
return BlocProvider(
create: (_) => PostBloc(repository),
child: MaterialApp.router(
routeInformationParser: _appRouter.defaultRouteParser(),
routerDelegate: _appRouter.delegate(),
theme: ThemeData.light(),
),
);
}
CherryPick.openRootScope().installModules([$AppModule()]);
runApp(MyApp());
}

View File

@@ -3,7 +3,7 @@ import 'package:auto_route/auto_route.dart';
import 'app_router.gr.dart';
@AutoRouterConfig()
class AppRouter extends $AppRouter {
class AppRouter extends RootStackRouter {
@override
List<AutoRoute> get routes => [
AutoRoute(page: PostsRoute.page, initial: true),

View File

@@ -42,26 +42,26 @@ packages:
dependency: "direct main"
description:
name: auto_route
sha256: eb33554581a0a4aa7e6da0f13a44291a55bf71359012f1d9feb41634ff908ff8
sha256: "1d1bd908a1fec327719326d5d0791edd37f16caff6493c01003689fb03315ad7"
url: "https://pub.dev"
source: hosted
version: "7.9.2"
version: "9.3.0+1"
auto_route_generator:
dependency: "direct dev"
description:
name: auto_route_generator
sha256: "11067a3bcd643812518fe26c0c9ec073990286cabfd9d74b6da9ef9b913c4d22"
sha256: c9086eb07271e51b44071ad5cff34e889f3156710b964a308c2ab590769e79e6
url: "https://pub.dev"
source: hosted
version: "7.3.2"
version: "9.0.0"
bloc:
dependency: transitive
description:
name: bloc
sha256: "106842ad6569f0b60297619e9e0b1885c2fb9bf84812935490e6c5275777804e"
sha256: "52c10575f4445c61dd9e0cafcc6356fdd827c4c64dd7945ef3c4105f6b6ac189"
url: "https://pub.dev"
source: hosted
version: "8.1.4"
version: "9.0.0"
boolean_selector:
dependency: transitive
description:
@@ -156,21 +156,21 @@ packages:
path: "../../cherrypick"
relative: true
source: path
version: "2.1.0"
version: "2.2.0-dev.1"
cherrypick_annotations:
dependency: "direct main"
description:
path: "../../cherrypick_annotations"
relative: true
source: path
version: "1.0.0"
version: "1.1.0-dev.0"
cherrypick_generator:
dependency: "direct dev"
description:
path: "../../cherrypick_generator"
relative: true
source: path
version: "1.0.0"
version: "1.1.0-dev.2"
clock:
dependency: transitive
description:
@@ -284,18 +284,18 @@ packages:
dependency: "direct main"
description:
name: flutter_bloc
sha256: b594505eac31a0518bdcb4b5b79573b8d9117b193cc80cc12e17d639b10aa27a
sha256: cf51747952201a455a1c840f8171d273be009b932c75093020f9af64f2123e38
url: "https://pub.dev"
source: hosted
version: "8.1.6"
version: "9.1.1"
flutter_lints:
dependency: "direct dev"
description:
name: flutter_lints
sha256: "3f41d009ba7172d5ff9be5f6e6e6abb4300e263aab8866d2a0842ed2a70f8f0c"
sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1"
url: "https://pub.dev"
source: hosted
version: "4.0.0"
version: "5.0.0"
flutter_test:
dependency: "direct dev"
description: flutter
@@ -417,10 +417,10 @@ packages:
dependency: transitive
description:
name: lints
sha256: "976c774dd944a42e83e2467f4cc670daef7eed6295b10b36ae8c85bcbf828235"
sha256: "3315600f3fb3b135be672bf4a178c55f274bebe368325ae18462c89ac1e3b413"
url: "https://pub.dev"
source: hosted
version: "4.0.0"
version: "5.0.0"
logging:
dependency: transitive
description:
@@ -553,10 +553,10 @@ packages:
dependency: "direct dev"
description:
name: retrofit_generator
sha256: "8dfc406cdfa171f33cbd21bf5bd8b6763548cc217de19cdeaa07a76727fac4ca"
sha256: f76fdb2b66854690d5a332e7364d7561fc9dc2b3c924d7956ab8070495e21f6a
url: "https://pub.dev"
source: hosted
version: "8.2.1"
version: "9.1.5"
shelf:
dependency: transitive
description:
@@ -658,14 +658,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.0.2"
tuple:
dependency: transitive
description:
name: tuple
sha256: a97ce2013f240b2f3807bcbaf218765b6f301c3eff91092bcfa23a039e7dd151
url: "https://pub.dev"
source: hosted
version: "2.0.2"
typed_data:
dependency: transitive
description:

View File

@@ -12,15 +12,15 @@ dependencies:
flutter:
sdk: flutter
cherrypick: ^2.2.0-dev.0
cherrypick_annotations: ^1.1.0-dev.0
cherrypick: ^2.2.0-dev.1
cherrypick_annotations: ^1.1.0-dev.1
dio: ^5.4.0
retrofit: ^4.0.3
freezed_annotation: ^2.0.0
freezed_annotation: ^2.3.2
dartz: ^0.10.1
flutter_bloc: ^8.1.2
auto_route: ^7.8.4
flutter_bloc: ^9.1.1
auto_route: ^9.3.0+1
cupertino_icons: ^1.0.8
@@ -28,15 +28,15 @@ dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^4.0.0
flutter_lints: ^5.0.0
cherrypick_generator: ^1.1.0-dev.0
build_runner: any
cherrypick_generator: ^1.1.0-dev.3
build_runner: 2.4.13
retrofit_generator: ^8.0.4
retrofit_generator: ^9.1.5
freezed: ^2.3.2
json_serializable: any
auto_route_generator: ^7.3.2
json_serializable: ^6.9.0
auto_route_generator: ^9.0.0
flutter:

View File

@@ -24,8 +24,11 @@ scripts:
run: |
melos exec --scope="postly" -- dart run build_runner build --delete-conflicting-outputs
outdated:
exec: dart pub outdated
upgrade:
exec: darp pub upgrade
exec: dart pub upgrade --major-versions
drop:
exec: flutter clean