mirror of
https://github.com/pese-git/cherrypick.git
synced 2026-01-24 13:47:24 +00:00
Compare commits
20 Commits
cherrypick
...
cherrypick
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8fcb61ef3e | ||
|
|
69e166644a | ||
|
|
feb7258302 | ||
|
|
c722ad0c07 | ||
|
|
8468eff5f7 | ||
|
|
24bb47f741 | ||
|
|
b5f6fff8d1 | ||
|
|
e7f20d8f63 | ||
|
|
e057bb487b | ||
|
|
2e7c9129bb | ||
|
|
292af4a4f3 | ||
|
|
5220ebc4b9 | ||
|
|
a0a0a967a2 | ||
|
|
a9260e0413 | ||
|
|
dd608031a2 | ||
|
|
49e3654ab8 | ||
|
|
bc28ff79ef | ||
|
|
52bc66f2f9 | ||
|
|
79a050d056 | ||
|
|
3beb53a094 |
76
CHANGELOG.md
76
CHANGELOG.md
@@ -3,6 +3,82 @@
|
|||||||
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-07-28
|
||||||
|
|
||||||
|
### Changes
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Packages with breaking changes:
|
||||||
|
|
||||||
|
- [`cherrypick_flutter` - `v1.1.2`](#cherrypick_flutter---v112)
|
||||||
|
|
||||||
|
Packages with other changes:
|
||||||
|
|
||||||
|
- [`cherrypick` - `v2.2.0`](#cherrypick---v220)
|
||||||
|
- [`cherrypick_annotations` - `v1.1.0`](#cherrypick_annotations---v110)
|
||||||
|
- [`cherrypick_generator` - `v1.1.0`](#cherrypick_generator---v110)
|
||||||
|
|
||||||
|
Packages graduated to a stable release (see pre-releases prior to the stable version for changelog entries):
|
||||||
|
|
||||||
|
- `cherrypick` - `v2.2.0`
|
||||||
|
- `cherrypick_annotations` - `v1.1.0`
|
||||||
|
- `cherrypick_flutter` - `v1.1.2`
|
||||||
|
- `cherrypick_generator` - `v1.1.0`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### `cherrypick_flutter` - `v1.1.2`
|
||||||
|
|
||||||
|
#### `cherrypick` - `v2.2.0`
|
||||||
|
|
||||||
|
#### `cherrypick_annotations` - `v1.1.0`
|
||||||
|
|
||||||
|
#### `cherrypick_generator` - `v1.1.0`
|
||||||
|
|
||||||
|
|
||||||
|
## 2025-06-04
|
||||||
|
|
||||||
|
### Changes
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Packages with breaking changes:
|
||||||
|
|
||||||
|
- There are no breaking changes in this release.
|
||||||
|
|
||||||
|
Packages with other changes:
|
||||||
|
|
||||||
|
- [`cherrypick_generator` - `v1.1.0-dev.5`](#cherrypick_generator---v110-dev5)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### `cherrypick_generator` - `v1.1.0-dev.5`
|
||||||
|
|
||||||
|
- **FEAT**: implement tryResolve via generate code.
|
||||||
|
|
||||||
|
|
||||||
|
## 2025-05-28
|
||||||
|
|
||||||
|
### Changes
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Packages with breaking changes:
|
||||||
|
|
||||||
|
- There are no breaking changes in this release.
|
||||||
|
|
||||||
|
Packages with other changes:
|
||||||
|
|
||||||
|
- [`cherrypick_generator` - `v1.1.0-dev.4`](#cherrypick_generator---v110-dev4)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### `cherrypick_generator` - `v1.1.0-dev.4`
|
||||||
|
|
||||||
|
- **FIX**: fixed warnings.
|
||||||
|
|
||||||
|
|
||||||
## 2025-05-23
|
## 2025-05-23
|
||||||
|
|
||||||
### Changes
|
### Changes
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
## 2.2.0
|
||||||
|
|
||||||
|
- Graduate package to a stable release. See pre-releases prior to this version for changelog entries.
|
||||||
|
|
||||||
## 2.2.0-dev.1
|
## 2.2.0-dev.1
|
||||||
|
|
||||||
- **FIX**: fix warnings.
|
- **FIX**: fix warnings.
|
||||||
|
|||||||
@@ -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: 2.2.0-dev.1
|
version: 2.2.0
|
||||||
homepage: https://pese-git.github.io/cherrypick-site/
|
homepage: https://pese-git.github.io/cherrypick-site/
|
||||||
documentation: https://github.com/pese-git/cherrypick/wiki
|
documentation: https://github.com/pese-git/cherrypick/wiki
|
||||||
repository: https://github.com/pese-git/cherrypick
|
repository: https://github.com/pese-git/cherrypick
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
## 1.1.0
|
||||||
|
|
||||||
|
- Graduate package to a stable release. See pre-releases prior to this version for changelog entries.
|
||||||
|
|
||||||
## 1.1.0-dev.1
|
## 1.1.0-dev.1
|
||||||
|
|
||||||
- **FEAT**: implement InjectGenerator.
|
- **FEAT**: implement InjectGenerator.
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
[](LICENSE)
|
[](LICENSE)
|
||||||
|
|
||||||
A lightweight set of Dart annotations designed for dependency injection (DI) frameworks and code generation, inspired by modern approaches like Dagger and Injectable. Works best in tandem with [`cherrypick_generator`](https://pub.dev/packages/cherrypick_generator).
|
A lightweight set of Dart annotations for dependency injection (DI) frameworks and code generation, inspired by modern approaches like Dagger and Injectable. Optimized for use with [`cherrypick_generator`](https://pub.dev/packages/cherrypick_generator).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -10,10 +10,13 @@ A lightweight set of Dart annotations designed for dependency injection (DI) fra
|
|||||||
|
|
||||||
- **@module** – Marks a class as a DI module for service/provider registration.
|
- **@module** – Marks a class as a DI module for service/provider registration.
|
||||||
- **@singleton** – Declares that a method or class should be provided as a singleton.
|
- **@singleton** – Declares that a method or class should be provided as a singleton.
|
||||||
- **@instance** – Marks a method or class so that a new instance is provided on each request (not a singleton).
|
- **@instance** – Marks a method or class so that a new instance is provided on each request.
|
||||||
- **@provide** – Marks a method whose return value should be registered as a provider, supporting dependency injection into parameters.
|
- **@provide** – Marks a method whose return value should be registered as a provider, supporting DI into its parameters.
|
||||||
- **@named** – Assigns a string name to a binding for keyed resolution.
|
- **@named** – Assigns a string name to a binding for keyed resolution and injection.
|
||||||
- **@params** – Indicates that a parameter should be injected with runtime-supplied arguments.
|
- **@params** – Indicates that a parameter should be injected with runtime-supplied arguments.
|
||||||
|
- **@injectable** – Marks a class as eligible for automatic field injection. Fields annotated with `@inject` will be injected by the code generator.
|
||||||
|
- **@inject** – Marks a field to be automatically injected by the code generator.
|
||||||
|
- **@scope** – Declares the DI scope from which a dependency should be resolved for a field.
|
||||||
|
|
||||||
These annotations streamline DI configuration and serve as markers for code generation tools such as [`cherrypick_generator`](https://pub.dev/packages/cherrypick_generator).
|
These annotations streamline DI configuration and serve as markers for code generation tools such as [`cherrypick_generator`](https://pub.dev/packages/cherrypick_generator).
|
||||||
|
|
||||||
@@ -32,18 +35,21 @@ Add as a `dev_dependency` for code generation:
|
|||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
|
cherrypick_generator: ^latest
|
||||||
build_runner: ^latest
|
build_runner: ^latest
|
||||||
cherrypick_generator:
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2. Annotate your DI modules and providers
|
---
|
||||||
|
|
||||||
|
### 2. Annotate your DI modules, providers, and injectable classes
|
||||||
|
|
||||||
|
#### **Module and Provider Example**
|
||||||
|
|
||||||
```dart
|
```dart
|
||||||
import 'package:cherrypick_annotations/cherrypick_annotations.dart';
|
import 'package:cherrypick_annotations/cherrypick_annotations.dart';
|
||||||
import 'package:cherrypick/cherrypick.dart';
|
|
||||||
|
|
||||||
@module()
|
@module()
|
||||||
abstract class AppModule extends Module {
|
abstract class AppModule {
|
||||||
@singleton()
|
@singleton()
|
||||||
Dio dio() => Dio();
|
Dio dio() => Dio();
|
||||||
|
|
||||||
@@ -61,7 +67,7 @@ abstract class AppModule extends Module {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
When used with `cherrypick_generator`, code similar to the following will be generated:
|
With `cherrypick_generator`, code like the following will be generated:
|
||||||
|
|
||||||
```dart
|
```dart
|
||||||
final class $AppModule extends AppModule {
|
final class $AppModule extends AppModule {
|
||||||
@@ -78,13 +84,78 @@ final class $AppModule extends AppModule {
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
#### **Field Injection Example**
|
||||||
|
|
||||||
|
```dart
|
||||||
|
import 'package:cherrypick_annotations/cherrypick_annotations.dart';
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
class ProfileView with _$ProfileView{
|
||||||
|
@inject()
|
||||||
|
late final AuthService auth;
|
||||||
|
|
||||||
|
@inject()
|
||||||
|
@scope('profile')
|
||||||
|
late final ProfileManager manager;
|
||||||
|
|
||||||
|
@inject()
|
||||||
|
@named('admin')
|
||||||
|
late final UserService adminUserService;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The code generator produces a mixin (simplified):
|
||||||
|
|
||||||
|
```dart
|
||||||
|
mixin _$ProfileView {
|
||||||
|
void _inject(ProfileView instance) {
|
||||||
|
instance.auth = CherryPick.openRootScope().resolve<AuthService>();
|
||||||
|
instance.manager = CherryPick.openScope(scopeName: 'profile').resolve<ProfileManager>();
|
||||||
|
instance.adminUserService = CherryPick.openRootScope().resolve<UserService>(named: 'admin');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Annotation Reference
|
## Annotation Reference
|
||||||
|
|
||||||
|
### `@injectable`
|
||||||
|
|
||||||
|
```dart
|
||||||
|
@injectable()
|
||||||
|
class MyWidget { ... }
|
||||||
|
```
|
||||||
|
Marks a class as injectable for CherryPick DI. The code generator will generate a mixin to perform automatic injection of fields marked with `@inject()`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `@inject`
|
||||||
|
|
||||||
|
```dart
|
||||||
|
@inject()
|
||||||
|
late final SomeService service;
|
||||||
|
```
|
||||||
|
Applied to a field to request automatic injection of the dependency using the CherryPick DI framework.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `@scope`
|
||||||
|
|
||||||
|
```dart
|
||||||
|
@inject()
|
||||||
|
@scope('profile')
|
||||||
|
late final ProfileManager manager;
|
||||||
|
```
|
||||||
|
Specifies the scope from which the dependency should be resolved for an injected field.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
### `@module`
|
### `@module`
|
||||||
|
|
||||||
```dart
|
```dart
|
||||||
@module()
|
@module()
|
||||||
abstract class AppModule extends Module {}
|
abstract class AppModule {}
|
||||||
```
|
```
|
||||||
Use on classes to mark them as a DI module. This is the root for registering your dependency providers.
|
Use on classes to mark them as a DI module. This is the root for registering your dependency providers.
|
||||||
|
|
||||||
@@ -127,6 +198,7 @@ Use on methods to indicate they provide a dependency to the DI module. Dependenc
|
|||||||
String token() => 'abc';
|
String token() => 'abc';
|
||||||
```
|
```
|
||||||
Assigns a name to a binding for keyed injection or resolution.
|
Assigns a name to a binding for keyed injection or resolution.
|
||||||
|
Can be used on both provider methods and fields.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -136,7 +208,7 @@ Assigns a name to a binding for keyed injection or resolution.
|
|||||||
@provide()
|
@provide()
|
||||||
String greet(@params() dynamic params) => 'Hello $params';
|
String greet(@params() dynamic params) => 'Hello $params';
|
||||||
```
|
```
|
||||||
Use on method parameters to indicate that this parameter should receive runtime-supplied arguments during dependency resolution (for example, via `.toProvide*((params) => greate(params))` in generated code).
|
Indicates that this parameter should receive runtime-supplied arguments during dependency resolution.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,20 @@
|
|||||||
|
|
||||||
import 'package:meta/meta.dart';
|
import 'package:meta/meta.dart';
|
||||||
|
|
||||||
|
/// Annotation for field injection in CherryPick DI framework.
|
||||||
|
/// Apply this to a field, and the code generator will automatically inject
|
||||||
|
/// the appropriate dependency into it.
|
||||||
|
///
|
||||||
|
/// ---
|
||||||
|
///
|
||||||
|
/// Аннотация для внедрения зависимости в поле через фреймворк CherryPick DI.
|
||||||
|
/// Поместите её на поле класса — генератор кода автоматически подставит нужную зависимость.
|
||||||
|
///
|
||||||
|
/// Example / Пример:
|
||||||
|
/// ```dart
|
||||||
|
/// @inject()
|
||||||
|
/// late final SomeService service;
|
||||||
|
/// ```
|
||||||
@experimental
|
@experimental
|
||||||
// ignore: camel_case_types
|
// ignore: camel_case_types
|
||||||
final class inject {
|
final class inject {
|
||||||
|
|||||||
@@ -13,6 +13,24 @@
|
|||||||
|
|
||||||
import 'package:meta/meta.dart';
|
import 'package:meta/meta.dart';
|
||||||
|
|
||||||
|
/// Marks a class as injectable for the CherryPick dependency injection framework.
|
||||||
|
/// If a class is annotated with [@injectable()], the code generator will
|
||||||
|
/// create a mixin to perform automatic injection of fields marked with [@inject].
|
||||||
|
///
|
||||||
|
/// ---
|
||||||
|
///
|
||||||
|
/// Помечает класс как внедряемый для фреймворка внедрения зависимостей CherryPick.
|
||||||
|
/// Если класс помечен аннотацией [@injectable()], генератор создаст миксин
|
||||||
|
/// для автоматического внедрения полей, отмеченных [@inject].
|
||||||
|
///
|
||||||
|
/// Example / Пример:
|
||||||
|
/// ```dart
|
||||||
|
/// @injectable()
|
||||||
|
/// class MyWidget extends StatelessWidget {
|
||||||
|
/// @inject()
|
||||||
|
/// late final MyService service;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
@experimental
|
@experimental
|
||||||
// ignore: camel_case_types
|
// ignore: camel_case_types
|
||||||
final class injectable {
|
final class injectable {
|
||||||
|
|||||||
@@ -13,6 +13,22 @@
|
|||||||
|
|
||||||
import 'package:meta/meta.dart';
|
import 'package:meta/meta.dart';
|
||||||
|
|
||||||
|
/// Annotation to specify a scope for dependency injection in CherryPick.
|
||||||
|
/// Use this on an injected field to indicate from which scope
|
||||||
|
/// the dependency must be resolved.
|
||||||
|
///
|
||||||
|
/// ---
|
||||||
|
///
|
||||||
|
/// Аннотация для указания области внедрения (scope) в CherryPick.
|
||||||
|
/// Используйте её на инъецируемом поле, чтобы определить из какой области
|
||||||
|
/// должна быть получена зависимость.
|
||||||
|
///
|
||||||
|
/// Example / Пример:
|
||||||
|
/// ```dart
|
||||||
|
/// @inject()
|
||||||
|
/// @scope('profile')
|
||||||
|
/// late final ProfileManager profileManager;
|
||||||
|
/// ```
|
||||||
@experimental
|
@experimental
|
||||||
// ignore: camel_case_types
|
// ignore: camel_case_types
|
||||||
final class scope {
|
final class scope {
|
||||||
|
|||||||
@@ -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.0-dev.1
|
version: 1.1.0
|
||||||
documentation: https://github.com/pese-git/cherrypick/wiki
|
documentation: https://github.com/pese-git/cherrypick/wiki
|
||||||
repository: https://github.com/pese-git/cherrypick/cherrypick_annotations
|
repository: https://github.com/pese-git/cherrypick/cherrypick_annotations
|
||||||
issue_tracker: https://github.com/pese-git/cherrypick/issues
|
issue_tracker: https://github.com/pese-git/cherrypick/issues
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
## 1.1.2
|
||||||
|
|
||||||
|
- Graduate package to a stable release. See pre-releases prior to this version for changelog entries.
|
||||||
|
|
||||||
## 1.1.2-dev.1
|
## 1.1.2-dev.1
|
||||||
|
|
||||||
- Update a dependency to the latest release.
|
- Update a dependency to the latest release.
|
||||||
|
|||||||
@@ -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.2-dev.1
|
version: 1.1.2
|
||||||
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
|
||||||
@@ -13,7 +13,7 @@ environment:
|
|||||||
dependencies:
|
dependencies:
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
cherrypick: ^2.2.0-dev.1
|
cherrypick: ^2.2.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|||||||
2
cherrypick_generator/.gitignore
vendored
2
cherrypick_generator/.gitignore
vendored
@@ -26,3 +26,5 @@ doc/api/
|
|||||||
melos_cherrypick_generator.iml
|
melos_cherrypick_generator.iml
|
||||||
|
|
||||||
**/*.mocks.dart
|
**/*.mocks.dart
|
||||||
|
|
||||||
|
coverage
|
||||||
@@ -1,3 +1,15 @@
|
|||||||
|
## 1.1.0
|
||||||
|
|
||||||
|
- Graduate package to a stable release. See pre-releases prior to this version for changelog entries.
|
||||||
|
|
||||||
|
## 1.1.0-dev.5
|
||||||
|
|
||||||
|
- **FEAT**: implement tryResolve via generate code.
|
||||||
|
|
||||||
|
## 1.1.0-dev.4
|
||||||
|
|
||||||
|
- **FIX**: fixed warnings.
|
||||||
|
|
||||||
## 1.1.0-dev.3
|
## 1.1.0-dev.3
|
||||||
|
|
||||||
- **FEAT**: implement InjectGenerator.
|
- **FEAT**: implement InjectGenerator.
|
||||||
|
|||||||
@@ -1,54 +1,95 @@
|
|||||||
# Cherrypick Generator
|
# Cherrypick Generator
|
||||||
|
|
||||||
**Cherrypick Generator** is a Dart code generation library for automatic boilerplate creation in dependency injection (DI) modules. It processes classes annotated with `@module()` (from [cherrypick_annotations](https://pub.dev/packages/cherrypick_annotations)) and generates code for registering dependencies, handling singletons, named bindings, runtime parameters, and more.
|
**Cherrypick Generator** is a Dart code generation library for automating dependency injection (DI) boilerplate. It processes classes and fields annotated with [cherrypick_annotations](https://pub.dev/packages/cherrypick_annotations) and generates registration code for services, modules, and field injection for classes marked as `@injectable`. It supports advanced DI features such as scopes, named bindings, parameters, and asynchronous dependencies.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- **Automatic Binding Generation:**
|
- **Automatic Field Injection:**
|
||||||
Generates `bind<Type>()` registration code for every method in a DI module marked with `@module()`.
|
Detects classes annotated with `@injectable()`, and generates mixins to inject all fields annotated with `@inject()`, supporting scope and named qualifiers.
|
||||||
|
|
||||||
- **Support for DI Annotations:**
|
- **Module and Service Registration:**
|
||||||
Understands and processes meta-annotations such as `@singleton`, `@named`, `@instance`, `@provide`, and `@params`.
|
For classes annotated with `@module()`, generates service registration code for methods using annotations such as `@provide`, `@instance`, `@singleton`, `@named`, and `@params`.
|
||||||
|
|
||||||
- **Runtime & Compile-Time Parameters:**
|
- **Scope & Named Qualifier Support:**
|
||||||
Handles both injected (compile-time) and runtime parameters for provider/binding methods.
|
Supports advanced DI features:
|
||||||
|
• Field-level scoping with `@scope('scopename')`
|
||||||
|
• Named dependencies via `@named('value')`
|
||||||
|
|
||||||
- **Synchronous & Asynchronous Support:**
|
- **Synchronous & Asynchronous Support:**
|
||||||
Correctly distinguishes between synchronous and asynchronous bindings (including `Future<T>` return types).
|
Handles both synchronous and asynchronous services (including `Future<T>`) for both field injection and module registration.
|
||||||
|
|
||||||
- **Named Bindings:**
|
- **Parameters and Runtime Arguments:**
|
||||||
Allows registration of named services via the `@named()` annotation.
|
Recognizes and wires both injected dependencies and runtime parameters using `@params`.
|
||||||
|
|
||||||
- **Singletons:**
|
- **Error Handling:**
|
||||||
Registers singletons via the `@singleton` annotation.
|
Validates annotations at generation time. Provides helpful errors for incorrect usage (e.g., using `@injectable` on non-class elements).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## How It Works
|
## How It Works
|
||||||
|
|
||||||
1. **Annotations**
|
### 1. Annotate your code
|
||||||
Annotate your module classes and methods using `@module()`, `@instance`, `@provide`, `@singleton`, and `@named` as needed.
|
|
||||||
|
|
||||||
2. **Code Scanning**
|
Use annotations from [cherrypick_annotations](https://pub.dev/packages/cherrypick_annotations):
|
||||||
During the build process (with `build_runner`), the generator scans your annotated classes.
|
|
||||||
|
|
||||||
3. **Code Generation**
|
- `@injectable()` — on classes to enable field injection
|
||||||
For each `@module()` class, a new class (with a `$` prefix) is generated.
|
- `@inject()` — on fields to specify they should be injected
|
||||||
This class overrides the `builder(Scope)` method to register all bindings.
|
- `@scope()`, `@named()` — on fields or parameters for advanced wiring
|
||||||
|
- `@module()` — on classes to mark as DI modules
|
||||||
|
- `@provide`, `@instance`, `@singleton`, `@params` — on methods and parameters for module-based DI
|
||||||
|
|
||||||
4. **Binding Logic**
|
### 2. Run the generator
|
||||||
Each binding method's signature and annotations are analyzed. Registration code is generated according to:
|
|
||||||
- Return type (sync/async)
|
Use `build_runner` to process your code and generate `.module.cherrypick.g.dart` and `.inject.cherrypick.g.dart` files.
|
||||||
- Annotations (`@singleton`, `@named`, etc.)
|
|
||||||
- Parameter list (DI dependencies, `@named`, or `@params` for runtime values)
|
### 3. Use the output in your application
|
||||||
|
|
||||||
|
- For modules: Register DI providers using the generated `$YourModule` class.
|
||||||
|
- For services: Enable field injection on classes using the generated mixin.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Example
|
## Field Injection Example
|
||||||
|
|
||||||
Given the following annotated Dart code:
|
Given the following:
|
||||||
|
|
||||||
|
```dart
|
||||||
|
import 'package:cherrypick_annotations/cherrypick_annotations.dart';
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
class MyWidget with _$MyWidget {
|
||||||
|
@inject()
|
||||||
|
late final AuthService auth;
|
||||||
|
|
||||||
|
@inject()
|
||||||
|
@scope('profile')
|
||||||
|
late final ProfileManager manager;
|
||||||
|
|
||||||
|
@inject()
|
||||||
|
@named('special')
|
||||||
|
late final ApiClient specialApi;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**The generator will output (simplified):**
|
||||||
|
```dart
|
||||||
|
mixin _$MyWidget {
|
||||||
|
void _inject(MyWidget instance) {
|
||||||
|
instance.auth = CherryPick.openRootScope().resolve<AuthService>();
|
||||||
|
instance.manager = CherryPick.openScope(scopeName: 'profile').resolve<ProfileManager>();
|
||||||
|
instance.specialApi = CherryPick.openRootScope().resolve<ApiClient>(named: 'special');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
You can then mix this into your widget to enable automatic DI at runtime.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Module Registration Example
|
||||||
|
|
||||||
|
Given:
|
||||||
|
|
||||||
```dart
|
```dart
|
||||||
import 'package:cherrypick_annotations/cherrypick_annotations.dart';
|
import 'package:cherrypick_annotations/cherrypick_annotations.dart';
|
||||||
@@ -57,98 +98,92 @@ import 'package:cherrypick_annotations/cherrypick_annotations.dart';
|
|||||||
class MyModule {
|
class MyModule {
|
||||||
@singleton
|
@singleton
|
||||||
@instance
|
@instance
|
||||||
SomeService provideService(ApiClient client);
|
AuthService provideAuth(Api api);
|
||||||
|
|
||||||
@provide
|
@provide
|
||||||
@named('special')
|
@named('logging')
|
||||||
Future<Handler> createHandler(@params Map<String, dynamic> params);
|
Future<Logger> provideLogger(@params Map<String, dynamic> args);
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
The generator will output (simplified):
|
**The generator will output (simplified):**
|
||||||
|
|
||||||
```dart
|
```dart
|
||||||
final class $MyModule extends MyModule {
|
final class $MyModule extends MyModule {
|
||||||
@override
|
@override
|
||||||
void builder(Scope currentScope) {
|
void builder(Scope currentScope) {
|
||||||
bind<SomeService>()
|
bind<AuthService>()
|
||||||
.toInstance(provideService(currentScope.resolve<ApiClient>()))
|
.toInstance(provideAuth(currentScope.resolve<Api>()))
|
||||||
.singleton();
|
.singleton();
|
||||||
|
|
||||||
bind<Handler>()
|
bind<Logger>()
|
||||||
.toProvideAsyncWithParams((args) => createHandler(args))
|
.toProvideAsyncWithParams((args) => provideLogger(args))
|
||||||
.withName('special');
|
.withName('logging');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Generated Code Overview
|
## Key Points
|
||||||
|
|
||||||
- **Constructor Registration:**
|
- **Rich Annotation Support:**
|
||||||
All non-abstract methods are considered as providers and processed for DI registration.
|
Mix and match field, parameter, and method annotations for maximum flexibility.
|
||||||
|
- **Scope and Named Resolution:**
|
||||||
- **Parameter Handling:**
|
Use `@scope('...')` and `@named('...')` to precisely control where and how dependencies are wired.
|
||||||
Each method parameter is analyzed:
|
- **Async/Synchronous:**
|
||||||
- Standard DI dependency: resolved via `currentScope.resolve<Type>()`.
|
The generator distinguishes between sync (`resolve<T>`) and async (`resolveAsync<T>`) dependencies.
|
||||||
- Named dependency: resolved via `currentScope.resolve<Type>(named: 'name')`.
|
- **Automatic Mixins:**
|
||||||
- Runtime parameter (`@params`): passed through as-is (e.g., `args`).
|
For classes with `@injectable()`, a mixin is generated that injects all relevant fields (using constructor or setter).
|
||||||
|
- **Comprehensive Error Checking:**
|
||||||
- **Binding Types:**
|
Misapplied annotations (e.g., `@injectable()` on non-class) produce clear build-time errors.
|
||||||
Supports both `.toInstance()` and `.toProvide()` (including async variants).
|
|
||||||
|
|
||||||
- **Singleton/Named:**
|
|
||||||
Appends `.singleton()` and/or `.withName('name')` as appropriate.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
1. **Add dependencies**
|
1. **Add dependencies**
|
||||||
In your `pubspec.yaml`:
|
|
||||||
```yaml
|
```yaml
|
||||||
dependencies:
|
dependencies:
|
||||||
cherrypick_annotations: ^x.y.z
|
cherrypick_annotations: ^latest
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
|
cherrypick_generator: ^latest
|
||||||
build_runner: ^2.1.0
|
build_runner: ^2.1.0
|
||||||
cherrypick_generator: ^x.y.z
|
|
||||||
```
|
```
|
||||||
|
|
||||||
2. **Apply annotations**
|
2. **Annotate your classes and modules as above**
|
||||||
Annotate your DI modules and provider methods as shown above.
|
|
||||||
|
|
||||||
3. **Run the generator**
|
3. **Run the generator**
|
||||||
```
|
|
||||||
|
```shell
|
||||||
dart run build_runner build
|
dart run build_runner build
|
||||||
# or with Flutter:
|
# or, if using Flutter:
|
||||||
flutter pub run build_runner build
|
flutter pub run build_runner build
|
||||||
```
|
```
|
||||||
|
|
||||||
4. **Import and use the generated code**
|
4. **Use generated code**
|
||||||
The generated files (suffix `.cherrypick.g.dart`) contain your `$YourModule` classes ready for use with your DI framework.
|
|
||||||
|
- Import the generated `.inject.cherrypick.g.dart` or `.cherrypick.g.dart` files where needed
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Advanced
|
## Advanced Usage
|
||||||
|
|
||||||
- **Customizing Parameter Names:**
|
|
||||||
Use the `@named('value')` annotation on methods and parameters for named bindings.
|
|
||||||
|
|
||||||
- **Runtime Arguments:**
|
|
||||||
Use `@params` to designate parameters as runtime arguments that are supplied at injection time.
|
|
||||||
|
|
||||||
|
- **Combining Modules and Field Injection:**
|
||||||
|
It's possible to mix both style of DI — modules for binding, and field injection for consuming services.
|
||||||
|
- **Parameter and Named Injection:**
|
||||||
|
Use `@named` on both provider and parameter for named registration and lookup; use `@params` to pass runtime arguments.
|
||||||
- **Async Factories:**
|
- **Async Factories:**
|
||||||
Methods returning `Future<T>` generate the appropriate `.toProvideAsync()` or `.toInstanceAsync()` bindings.
|
Methods returning Future<T> generate async bindings and async field resolution logic.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Developer Notes
|
## Developer Notes
|
||||||
|
|
||||||
- The generator relies on Dart's analyzer, source_gen, and build packages.
|
- The generator relies on the Dart analyzer, `source_gen`, and `build` packages.
|
||||||
- Each class and method is parsed for annotations; missing required annotations (like `@instance` or `@provide`) will result in a generation error.
|
- All classes and methods are parsed for annotations.
|
||||||
- The generated code is designed to extend your original module classes while injecting all binding logic.
|
- Improper annotation usage will result in generator errors.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -162,6 +197,7 @@ Licensed under the Apache License, Version 2.0
|
|||||||
|
|
||||||
## Contribution
|
## Contribution
|
||||||
|
|
||||||
Pull requests and issues are welcome! Please open git issues or submit improvements as needed.
|
Pull requests and issues are welcome! Please open GitHub issues or submit improvements.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -28,3 +28,7 @@ include: package:lints/recommended.yaml
|
|||||||
|
|
||||||
# For additional information about configuring this file, see
|
# For additional information about configuring this file, see
|
||||||
# https://dart.dev/guides/language/analysis-options
|
# https://dart.dev/guides/language/analysis-options
|
||||||
|
|
||||||
|
analyzer:
|
||||||
|
errors:
|
||||||
|
deprecated_member_use: ignore
|
||||||
@@ -2,7 +2,7 @@ builders:
|
|||||||
module_generator:
|
module_generator:
|
||||||
import: "package:cherrypick_generator/module_generator.dart"
|
import: "package:cherrypick_generator/module_generator.dart"
|
||||||
builder_factories: ["moduleBuilder"]
|
builder_factories: ["moduleBuilder"]
|
||||||
build_extensions: {".dart": [".cherrypick.g.dart"]}
|
build_extensions: {".dart": [".module.cherrypick.g.dart"]}
|
||||||
auto_apply: dependents
|
auto_apply: dependents
|
||||||
required_inputs: ["lib/**"]
|
required_inputs: ["lib/**"]
|
||||||
runs_before: []
|
runs_before: []
|
||||||
@@ -10,7 +10,7 @@ builders:
|
|||||||
inject_generator:
|
inject_generator:
|
||||||
import: "package:cherrypick_generator/inject_generator.dart"
|
import: "package:cherrypick_generator/inject_generator.dart"
|
||||||
builder_factories: ["injectBuilder"]
|
builder_factories: ["injectBuilder"]
|
||||||
build_extensions: {".dart": [".cherrypick.g.dart"]}
|
build_extensions: {".dart": [".inject.cherrypick.g.dart"]}
|
||||||
auto_apply: dependents
|
auto_apply: dependents
|
||||||
required_inputs: ["lib/**"]
|
required_inputs: ["lib/**"]
|
||||||
runs_before: []
|
runs_before: []
|
||||||
|
|||||||
137
cherrypick_generator/coverage_analysis.py
Normal file
137
cherrypick_generator/coverage_analysis.py
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Анализ покрытия тестами для CherryPick Generator
|
||||||
|
"""
|
||||||
|
|
||||||
|
import re
|
||||||
|
import os
|
||||||
|
|
||||||
|
def analyze_lcov_file(lcov_path):
|
||||||
|
"""Анализирует LCOV файл и возвращает статистику покрытия"""
|
||||||
|
|
||||||
|
if not os.path.exists(lcov_path):
|
||||||
|
print(f"❌ LCOV файл не найден: {lcov_path}")
|
||||||
|
return
|
||||||
|
|
||||||
|
with open(lcov_path, 'r') as f:
|
||||||
|
content = f.read()
|
||||||
|
|
||||||
|
# Разбиваем на секции по файлам
|
||||||
|
file_sections = content.split('SF:')[1:] # Убираем первую пустую секцию
|
||||||
|
|
||||||
|
total_lines = 0
|
||||||
|
total_hit = 0
|
||||||
|
files_coverage = {}
|
||||||
|
|
||||||
|
for section in file_sections:
|
||||||
|
lines = section.strip().split('\n')
|
||||||
|
if not lines:
|
||||||
|
continue
|
||||||
|
|
||||||
|
file_path = lines[0]
|
||||||
|
file_name = os.path.basename(file_path)
|
||||||
|
|
||||||
|
# Подсчитываем строки
|
||||||
|
da_lines = [line for line in lines if line.startswith('DA:')]
|
||||||
|
|
||||||
|
file_total = len(da_lines)
|
||||||
|
file_hit = 0
|
||||||
|
|
||||||
|
for da_line in da_lines:
|
||||||
|
# DA:line_number,hit_count
|
||||||
|
parts = da_line.split(',')
|
||||||
|
if len(parts) >= 2:
|
||||||
|
hit_count = int(parts[1])
|
||||||
|
if hit_count > 0:
|
||||||
|
file_hit += 1
|
||||||
|
|
||||||
|
if file_total > 0:
|
||||||
|
coverage_percent = (file_hit / file_total) * 100
|
||||||
|
files_coverage[file_name] = {
|
||||||
|
'total': file_total,
|
||||||
|
'hit': file_hit,
|
||||||
|
'percent': coverage_percent
|
||||||
|
}
|
||||||
|
|
||||||
|
total_lines += file_total
|
||||||
|
total_hit += file_hit
|
||||||
|
|
||||||
|
# Общая статистика
|
||||||
|
overall_percent = (total_hit / total_lines) * 100 if total_lines > 0 else 0
|
||||||
|
|
||||||
|
print("📊 АНАЛИЗ ПОКРЫТИЯ ТЕСТАМИ CHERRYPICK GENERATOR")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
print(f"\n🎯 ОБЩАЯ СТАТИСТИКА:")
|
||||||
|
print(f" Всего строк кода: {total_lines}")
|
||||||
|
print(f" Покрыто тестами: {total_hit}")
|
||||||
|
print(f" Общее покрытие: {overall_percent:.1f}%")
|
||||||
|
|
||||||
|
print(f"\n📁 ПОКРЫТИЕ ПО ФАЙЛАМ:")
|
||||||
|
|
||||||
|
# Сортируем по проценту покрытия
|
||||||
|
sorted_files = sorted(files_coverage.items(), key=lambda x: x[1]['percent'], reverse=True)
|
||||||
|
|
||||||
|
for file_name, stats in sorted_files:
|
||||||
|
percent = stats['percent']
|
||||||
|
hit = stats['hit']
|
||||||
|
total = stats['total']
|
||||||
|
|
||||||
|
# Эмодзи в зависимости от покрытия
|
||||||
|
if percent >= 80:
|
||||||
|
emoji = "✅"
|
||||||
|
elif percent >= 50:
|
||||||
|
emoji = "🟡"
|
||||||
|
else:
|
||||||
|
emoji = "❌"
|
||||||
|
|
||||||
|
print(f" {emoji} {file_name:<25} {hit:>3}/{total:<3} ({percent:>5.1f}%)")
|
||||||
|
|
||||||
|
print(f"\n🏆 РЕЙТИНГ КОМПОНЕНТОВ:")
|
||||||
|
|
||||||
|
# Группируем по типам компонентов
|
||||||
|
core_files = ['bind_spec.dart', 'bind_parameters_spec.dart', 'generated_class.dart']
|
||||||
|
utils_files = ['metadata_utils.dart']
|
||||||
|
generator_files = ['module_generator.dart', 'inject_generator.dart']
|
||||||
|
|
||||||
|
def calculate_group_coverage(file_list):
|
||||||
|
group_total = sum(files_coverage.get(f, {}).get('total', 0) for f in file_list)
|
||||||
|
group_hit = sum(files_coverage.get(f, {}).get('hit', 0) for f in file_list)
|
||||||
|
return (group_hit / group_total * 100) if group_total > 0 else 0
|
||||||
|
|
||||||
|
core_coverage = calculate_group_coverage(core_files)
|
||||||
|
utils_coverage = calculate_group_coverage(utils_files)
|
||||||
|
generators_coverage = calculate_group_coverage(generator_files)
|
||||||
|
|
||||||
|
print(f" 🔧 Core Components: {core_coverage:>5.1f}%")
|
||||||
|
print(f" 🛠️ Utils: {utils_coverage:>5.1f}%")
|
||||||
|
print(f" ⚙️ Generators: {generators_coverage:>5.1f}%")
|
||||||
|
|
||||||
|
print(f"\n📈 РЕКОМЕНДАЦИИ:")
|
||||||
|
|
||||||
|
# Файлы с низким покрытием
|
||||||
|
low_coverage = [(f, s) for f, s in files_coverage.items() if s['percent'] < 50]
|
||||||
|
if low_coverage:
|
||||||
|
print(" 🎯 Приоритет для улучшения:")
|
||||||
|
for file_name, stats in sorted(low_coverage, key=lambda x: x[1]['percent']):
|
||||||
|
print(f" • {file_name} ({stats['percent']:.1f}%)")
|
||||||
|
|
||||||
|
# Файлы без покрытия
|
||||||
|
zero_coverage = [(f, s) for f, s in files_coverage.items() if s['percent'] == 0]
|
||||||
|
if zero_coverage:
|
||||||
|
print(" ❗ Требуют срочного внимания:")
|
||||||
|
for file_name, stats in zero_coverage:
|
||||||
|
print(f" • {file_name} (0% покрытия)")
|
||||||
|
|
||||||
|
print(f"\n✨ ДОСТИЖЕНИЯ:")
|
||||||
|
high_coverage = [(f, s) for f, s in files_coverage.items() if s['percent'] >= 80]
|
||||||
|
if high_coverage:
|
||||||
|
print(" 🏅 Отлично протестированы:")
|
||||||
|
for file_name, stats in sorted(high_coverage, key=lambda x: x[1]['percent'], reverse=True):
|
||||||
|
print(f" • {file_name} ({stats['percent']:.1f}%)")
|
||||||
|
|
||||||
|
return files_coverage, overall_percent
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
lcov_path = "coverage/lcov.info"
|
||||||
|
analyze_lcov_file(lcov_path)
|
||||||
@@ -13,6 +13,7 @@
|
|||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'package:analyzer/dart/constant/value.dart';
|
import 'package:analyzer/dart/constant/value.dart';
|
||||||
|
import 'package:analyzer/dart/element/nullability_suffix.dart';
|
||||||
import 'package:analyzer/dart/element/type.dart';
|
import 'package:analyzer/dart/element/type.dart';
|
||||||
import 'package:build/build.dart';
|
import 'package:build/build.dart';
|
||||||
import 'package:source_gen/source_gen.dart';
|
import 'package:source_gen/source_gen.dart';
|
||||||
@@ -119,10 +120,20 @@ class InjectGenerator extends GeneratorForAnnotation<ann.injectable> {
|
|||||||
isFuture = false;
|
isFuture = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ***
|
||||||
|
// Добавим определение nullable для типа (например PostRepository? или Future<PostRepository?>)
|
||||||
|
bool isNullable = dartType.nullabilitySuffix ==
|
||||||
|
NullabilitySuffix.question ||
|
||||||
|
(dartType is ParameterizedType &&
|
||||||
|
(dartType)
|
||||||
|
.typeArguments
|
||||||
|
.any((t) => t.nullabilitySuffix == NullabilitySuffix.question));
|
||||||
|
|
||||||
return _ParsedInjectField(
|
return _ParsedInjectField(
|
||||||
fieldName: field.name,
|
fieldName: field.name,
|
||||||
coreType: coreTypeName,
|
coreType: coreTypeName.replaceAll('?', ''), // удаляем "?" на всякий
|
||||||
isFuture: isFuture,
|
isFuture: isFuture,
|
||||||
|
isNullable: isNullable,
|
||||||
scopeName: scopeName,
|
scopeName: scopeName,
|
||||||
namedValue: namedValue,
|
namedValue: namedValue,
|
||||||
);
|
);
|
||||||
@@ -134,17 +145,24 @@ class InjectGenerator extends GeneratorForAnnotation<ann.injectable> {
|
|||||||
/// Генерирует строку кода, которая внедряет зависимость для поля.
|
/// Генерирует строку кода, которая внедряет зависимость для поля.
|
||||||
/// Учитывает resolve/resolveAsync, scoping и named qualifier.
|
/// Учитывает resolve/resolveAsync, scoping и named qualifier.
|
||||||
String _generateInjectionLine(_ParsedInjectField field) {
|
String _generateInjectionLine(_ParsedInjectField field) {
|
||||||
final methodName = field.isFuture
|
// Используем tryResolve для nullable, иначе resolve
|
||||||
? 'resolveAsync<${field.coreType}>'
|
final resolveMethod = field.isFuture
|
||||||
: 'resolve<${field.coreType}>';
|
? (field.isNullable
|
||||||
|
? 'tryResolveAsync<${field.coreType}>'
|
||||||
|
: 'resolveAsync<${field.coreType}>')
|
||||||
|
: (field.isNullable
|
||||||
|
? 'tryResolve<${field.coreType}>'
|
||||||
|
: 'resolve<${field.coreType}>');
|
||||||
|
|
||||||
final openCall = (field.scopeName != null && field.scopeName!.isNotEmpty)
|
final openCall = (field.scopeName != null && field.scopeName!.isNotEmpty)
|
||||||
? "CherryPick.openScope(scopeName: '${field.scopeName}')"
|
? "CherryPick.openScope(scopeName: '${field.scopeName}')"
|
||||||
: "CherryPick.openRootScope()";
|
: "CherryPick.openRootScope()";
|
||||||
|
|
||||||
final params = (field.namedValue != null && field.namedValue!.isNotEmpty)
|
final params = (field.namedValue != null && field.namedValue!.isNotEmpty)
|
||||||
? "(named: '${field.namedValue}')"
|
? "(named: '${field.namedValue}')"
|
||||||
: '()';
|
: '()';
|
||||||
|
|
||||||
return " instance.${field.fieldName} = $openCall.$methodName$params;";
|
return " instance.${field.fieldName} = $openCall.$resolveMethod$params;";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -170,10 +188,13 @@ class _ParsedInjectField {
|
|||||||
/// Optional named annotation argument / Опциональное имя named.
|
/// Optional named annotation argument / Опциональное имя named.
|
||||||
final String? namedValue;
|
final String? namedValue;
|
||||||
|
|
||||||
|
final bool isNullable;
|
||||||
|
|
||||||
_ParsedInjectField({
|
_ParsedInjectField({
|
||||||
required this.fieldName,
|
required this.fieldName,
|
||||||
required this.coreType,
|
required this.coreType,
|
||||||
required this.isFuture,
|
required this.isFuture,
|
||||||
|
required this.isNullable,
|
||||||
this.scopeName,
|
this.scopeName,
|
||||||
this.namedValue,
|
this.namedValue,
|
||||||
});
|
});
|
||||||
|
|||||||
321
cherrypick_generator/lib/src/annotation_validator.dart
Normal file
321
cherrypick_generator/lib/src/annotation_validator.dart
Normal file
@@ -0,0 +1,321 @@
|
|||||||
|
//
|
||||||
|
// 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:analyzer/dart/element/element.dart';
|
||||||
|
import 'exceptions.dart';
|
||||||
|
import 'metadata_utils.dart';
|
||||||
|
|
||||||
|
/// Validates annotation combinations and usage patterns
|
||||||
|
class AnnotationValidator {
|
||||||
|
/// Validates annotations on a method element
|
||||||
|
static void validateMethodAnnotations(MethodElement method) {
|
||||||
|
final annotations = _getAnnotationNames(method.metadata);
|
||||||
|
|
||||||
|
_validateMutuallyExclusiveAnnotations(method, annotations);
|
||||||
|
_validateAnnotationCombinations(method, annotations);
|
||||||
|
_validateAnnotationParameters(method);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Validates annotations on a field element
|
||||||
|
static void validateFieldAnnotations(FieldElement field) {
|
||||||
|
final annotations = _getAnnotationNames(field.metadata);
|
||||||
|
|
||||||
|
_validateInjectFieldAnnotations(field, annotations);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Validates annotations on a class element
|
||||||
|
static void validateClassAnnotations(ClassElement classElement) {
|
||||||
|
final annotations = _getAnnotationNames(classElement.metadata);
|
||||||
|
|
||||||
|
_validateModuleClassAnnotations(classElement, annotations);
|
||||||
|
_validateInjectableClassAnnotations(classElement, annotations);
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<String> _getAnnotationNames(List<ElementAnnotation> metadata) {
|
||||||
|
return metadata
|
||||||
|
.map((m) => m.computeConstantValue()?.type?.getDisplayString())
|
||||||
|
.where((name) => name != null)
|
||||||
|
.cast<String>()
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _validateMutuallyExclusiveAnnotations(
|
||||||
|
MethodElement method,
|
||||||
|
List<String> annotations,
|
||||||
|
) {
|
||||||
|
// @instance and @provide are mutually exclusive
|
||||||
|
if (annotations.contains('instance') && annotations.contains('provide')) {
|
||||||
|
throw AnnotationValidationException(
|
||||||
|
'Method cannot have both @instance and @provide annotations',
|
||||||
|
element: method,
|
||||||
|
suggestion:
|
||||||
|
'Use either @instance for direct instances or @provide for factory methods',
|
||||||
|
context: {
|
||||||
|
'method_name': method.displayName,
|
||||||
|
'annotations': annotations,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _validateAnnotationCombinations(
|
||||||
|
MethodElement method,
|
||||||
|
List<String> annotations,
|
||||||
|
) {
|
||||||
|
// @params can only be used with @provide
|
||||||
|
if (annotations.contains('params') && !annotations.contains('provide')) {
|
||||||
|
throw AnnotationValidationException(
|
||||||
|
'@params annotation can only be used with @provide annotation',
|
||||||
|
element: method,
|
||||||
|
suggestion: 'Remove @params or add @provide annotation',
|
||||||
|
context: {
|
||||||
|
'method_name': method.displayName,
|
||||||
|
'annotations': annotations,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Methods must have either @instance or @provide
|
||||||
|
if (!annotations.contains('instance') && !annotations.contains('provide')) {
|
||||||
|
throw AnnotationValidationException(
|
||||||
|
'Method must be marked with either @instance or @provide annotation',
|
||||||
|
element: method,
|
||||||
|
suggestion:
|
||||||
|
'Add @instance() for direct instances or @provide() for factory methods',
|
||||||
|
context: {
|
||||||
|
'method_name': method.displayName,
|
||||||
|
'available_annotations': annotations,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// @singleton validation
|
||||||
|
if (annotations.contains('singleton')) {
|
||||||
|
_validateSingletonUsage(method, annotations);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _validateSingletonUsage(
|
||||||
|
MethodElement method,
|
||||||
|
List<String> annotations,
|
||||||
|
) {
|
||||||
|
// Singleton with params might not make sense in some contexts
|
||||||
|
if (annotations.contains('params')) {
|
||||||
|
// This is a warning, not an error - could be useful for parameterized singletons
|
||||||
|
// We could add a warning system later
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if return type is suitable for singleton
|
||||||
|
final returnType = method.returnType.getDisplayString();
|
||||||
|
if (returnType == 'void') {
|
||||||
|
throw AnnotationValidationException(
|
||||||
|
'Singleton methods cannot return void',
|
||||||
|
element: method,
|
||||||
|
suggestion: 'Remove @singleton annotation or change return type',
|
||||||
|
context: {
|
||||||
|
'method_name': method.displayName,
|
||||||
|
'return_type': returnType,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _validateAnnotationParameters(MethodElement method) {
|
||||||
|
// Validate @named annotation parameters
|
||||||
|
final namedValue = MetadataUtils.getNamedValue(method.metadata);
|
||||||
|
if (namedValue != null) {
|
||||||
|
if (namedValue.isEmpty) {
|
||||||
|
throw AnnotationValidationException(
|
||||||
|
'@named annotation cannot have empty value',
|
||||||
|
element: method,
|
||||||
|
suggestion: 'Provide a non-empty string value for @named annotation',
|
||||||
|
context: {
|
||||||
|
'method_name': method.displayName,
|
||||||
|
'named_value': namedValue,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for valid naming conventions
|
||||||
|
if (!RegExp(r'^[a-zA-Z_][a-zA-Z0-9_]*$').hasMatch(namedValue)) {
|
||||||
|
throw AnnotationValidationException(
|
||||||
|
'@named value should follow valid identifier naming conventions',
|
||||||
|
element: method,
|
||||||
|
suggestion:
|
||||||
|
'Use alphanumeric characters and underscores only, starting with a letter or underscore',
|
||||||
|
context: {
|
||||||
|
'method_name': method.displayName,
|
||||||
|
'named_value': namedValue,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate method parameters for @params usage
|
||||||
|
for (final param in method.parameters) {
|
||||||
|
final paramAnnotations = _getAnnotationNames(param.metadata);
|
||||||
|
if (paramAnnotations.contains('params')) {
|
||||||
|
_validateParamsParameter(param, method);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _validateParamsParameter(
|
||||||
|
ParameterElement param, MethodElement method) {
|
||||||
|
// @params parameter should typically be dynamic or Map<String, dynamic>
|
||||||
|
final paramType = param.type.getDisplayString();
|
||||||
|
|
||||||
|
if (paramType != 'dynamic' &&
|
||||||
|
paramType != 'Map<String, dynamic>' &&
|
||||||
|
paramType != 'Map<String, dynamic>?') {
|
||||||
|
// This is more of a warning - other types might be valid
|
||||||
|
// We could add a warning system for this
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if parameter is required when using @params
|
||||||
|
try {
|
||||||
|
final hasDefault = (param as dynamic).defaultValue != null;
|
||||||
|
if (param.isRequired && !hasDefault) {
|
||||||
|
// This might be intentional, so we don't throw an error
|
||||||
|
// but we could warn about it
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// Ignore if defaultValue is not available in this analyzer version
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _validateInjectFieldAnnotations(
|
||||||
|
FieldElement field,
|
||||||
|
List<String> annotations,
|
||||||
|
) {
|
||||||
|
if (!annotations.contains('inject')) {
|
||||||
|
return; // Not an inject field, nothing to validate
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if field type is suitable for injection
|
||||||
|
final fieldType = field.type.getDisplayString();
|
||||||
|
if (fieldType == 'void') {
|
||||||
|
throw AnnotationValidationException(
|
||||||
|
'Cannot inject void type',
|
||||||
|
element: field,
|
||||||
|
suggestion: 'Use a concrete type instead of void',
|
||||||
|
context: {
|
||||||
|
'field_name': field.displayName,
|
||||||
|
'field_type': fieldType,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate scope annotation if present
|
||||||
|
for (final meta in field.metadata) {
|
||||||
|
final obj = meta.computeConstantValue();
|
||||||
|
final type = obj?.type?.getDisplayString();
|
||||||
|
if (type == 'scope') {
|
||||||
|
// Empty scope name is treated as no scope (uses root scope)
|
||||||
|
// This is allowed for backward compatibility and convenience
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _validateModuleClassAnnotations(
|
||||||
|
ClassElement classElement,
|
||||||
|
List<String> annotations,
|
||||||
|
) {
|
||||||
|
if (!annotations.contains('module')) {
|
||||||
|
return; // Not a module class
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if class has public methods
|
||||||
|
final publicMethods =
|
||||||
|
classElement.methods.where((m) => m.isPublic).toList();
|
||||||
|
if (publicMethods.isEmpty) {
|
||||||
|
throw AnnotationValidationException(
|
||||||
|
'Module class must have at least one public method',
|
||||||
|
element: classElement,
|
||||||
|
suggestion: 'Add public methods with @instance or @provide annotations',
|
||||||
|
context: {
|
||||||
|
'class_name': classElement.displayName,
|
||||||
|
'method_count': publicMethods.length,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate that public methods have appropriate annotations
|
||||||
|
for (final method in publicMethods) {
|
||||||
|
final methodAnnotations = _getAnnotationNames(method.metadata);
|
||||||
|
if (!methodAnnotations.contains('instance') &&
|
||||||
|
!methodAnnotations.contains('provide')) {
|
||||||
|
throw AnnotationValidationException(
|
||||||
|
'Public methods in module class must have @instance or @provide annotation',
|
||||||
|
element: method,
|
||||||
|
suggestion: 'Add @instance() or @provide() annotation to the method',
|
||||||
|
context: {
|
||||||
|
'class_name': classElement.displayName,
|
||||||
|
'method_name': method.displayName,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _validateInjectableClassAnnotations(
|
||||||
|
ClassElement classElement,
|
||||||
|
List<String> annotations,
|
||||||
|
) {
|
||||||
|
if (!annotations.contains('injectable')) {
|
||||||
|
return; // Not an injectable class
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if class has injectable fields
|
||||||
|
final injectFields = classElement.fields.where((f) {
|
||||||
|
final fieldAnnotations = _getAnnotationNames(f.metadata);
|
||||||
|
return fieldAnnotations.contains('inject');
|
||||||
|
}).toList();
|
||||||
|
|
||||||
|
// Allow injectable classes without @inject fields to generate empty mixins
|
||||||
|
// This can be useful for classes that will have @inject fields added later
|
||||||
|
// or for testing purposes
|
||||||
|
if (injectFields.isEmpty) {
|
||||||
|
// Just log a warning but don't throw an exception
|
||||||
|
// print('Warning: Injectable class ${classElement.displayName} has no @inject fields');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate that injectable fields are properly declared
|
||||||
|
for (final field in injectFields) {
|
||||||
|
// Injectable fields should be late final for immutability after injection
|
||||||
|
if (!field.isFinal) {
|
||||||
|
throw AnnotationValidationException(
|
||||||
|
'Injectable fields should be final for immutability',
|
||||||
|
element: field,
|
||||||
|
suggestion:
|
||||||
|
'Add final keyword to injectable field (preferably late final)',
|
||||||
|
context: {
|
||||||
|
'class_name': classElement.displayName,
|
||||||
|
'field_name': field.displayName,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if field is late (recommended pattern)
|
||||||
|
try {
|
||||||
|
final isLate = (field as dynamic).isLate ?? false;
|
||||||
|
if (!isLate) {
|
||||||
|
// This is a warning, not an error - late final is recommended but not required
|
||||||
|
// We could add a warning system later
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// Ignore if isLate is not available in this analyzer version
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,10 +12,12 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import 'package:analyzer/dart/element/element.dart';
|
import 'package:analyzer/dart/element/element.dart';
|
||||||
import 'package:source_gen/source_gen.dart';
|
|
||||||
|
|
||||||
import 'bind_parameters_spec.dart';
|
import 'bind_parameters_spec.dart';
|
||||||
import 'metadata_utils.dart';
|
import 'metadata_utils.dart';
|
||||||
|
import 'exceptions.dart';
|
||||||
|
import 'type_parser.dart';
|
||||||
|
import 'annotation_validator.dart';
|
||||||
|
|
||||||
enum BindingType {
|
enum BindingType {
|
||||||
instance,
|
instance,
|
||||||
@@ -105,10 +107,42 @@ class BindSpec {
|
|||||||
final indentStr = ' ' * indent;
|
final indentStr = ' ' * indent;
|
||||||
final provide = _generateProvideClause(indent);
|
final provide = _generateProvideClause(indent);
|
||||||
final postfix = _generatePostfix();
|
final postfix = _generatePostfix();
|
||||||
return '$indentStr'
|
|
||||||
'bind<$returnType>()'
|
// Create the full single-line version first
|
||||||
'$provide'
|
final singleLine = '${indentStr}bind<$returnType>()$provide$postfix;';
|
||||||
'$postfix;';
|
|
||||||
|
// Check if we need multiline formatting
|
||||||
|
final needsMultiline = singleLine.length > 80 || provide.contains('\n');
|
||||||
|
|
||||||
|
if (!needsMultiline) {
|
||||||
|
return singleLine;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For multiline formatting, check if we need to break after bind<Type>()
|
||||||
|
if (provide.contains('\n')) {
|
||||||
|
// Provider clause is already multiline
|
||||||
|
if (postfix.isNotEmpty) {
|
||||||
|
// If there's a postfix, break after bind<Type>()
|
||||||
|
final multilinePostfix = _generateMultilinePostfix(indent);
|
||||||
|
return '${indentStr}bind<$returnType>()'
|
||||||
|
'\n${' ' * (indent + 4)}$provide'
|
||||||
|
'$multilinePostfix;';
|
||||||
|
} else {
|
||||||
|
// No postfix, keep bind<Type>() with provide start
|
||||||
|
return '${indentStr}bind<$returnType>()$provide;';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Simple multiline: break after bind<Type>()
|
||||||
|
if (postfix.isNotEmpty) {
|
||||||
|
final multilinePostfix = _generateMultilinePostfix(indent);
|
||||||
|
return '${indentStr}bind<$returnType>()'
|
||||||
|
'\n${' ' * (indent + 4)}$provide'
|
||||||
|
'$multilinePostfix;';
|
||||||
|
} else {
|
||||||
|
return '${indentStr}bind<$returnType>()'
|
||||||
|
'\n${' ' * (indent + 4)}$provide;';
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Internal method: decides how the provide clause should be generated by param kind.
|
// Internal method: decides how the provide clause should be generated by param kind.
|
||||||
@@ -122,6 +156,7 @@ class BindSpec {
|
|||||||
// Safe variable name for parameters.
|
// Safe variable name for parameters.
|
||||||
const paramVar = 'args';
|
const paramVar = 'args';
|
||||||
final fnArgs = parameters.map((p) => p.generateArg(paramVar)).join(', ');
|
final fnArgs = parameters.map((p) => p.generateArg(paramVar)).join(', ');
|
||||||
|
// Use multiline format only if args are long or contain newlines
|
||||||
final multiLine = fnArgs.length > 60 || fnArgs.contains('\n');
|
final multiLine = fnArgs.length > 60 || fnArgs.contains('\n');
|
||||||
switch (bindingType) {
|
switch (bindingType) {
|
||||||
case BindingType.instance:
|
case BindingType.instance:
|
||||||
@@ -131,7 +166,6 @@ class BindSpec {
|
|||||||
// ? '.toInstanceAsync(($fnArgs) => $methodName($fnArgs))'
|
// ? '.toInstanceAsync(($fnArgs) => $methodName($fnArgs))'
|
||||||
// : '.toInstance(($fnArgs) => $methodName($fnArgs))';
|
// : '.toInstance(($fnArgs) => $methodName($fnArgs))';
|
||||||
case BindingType.provide:
|
case BindingType.provide:
|
||||||
default:
|
|
||||||
if (isAsyncProvide) {
|
if (isAsyncProvide) {
|
||||||
return multiLine
|
return multiLine
|
||||||
? '.toProvideAsyncWithParams(\n${' ' * (indent + 2)}($paramVar) => $methodName($fnArgs))'
|
? '.toProvideAsyncWithParams(\n${' ' * (indent + 2)}($paramVar) => $methodName($fnArgs))'
|
||||||
@@ -147,22 +181,38 @@ class BindSpec {
|
|||||||
/// EN / RU: Supports only injected dependencies, not runtime (@params).
|
/// EN / RU: Supports only injected dependencies, not runtime (@params).
|
||||||
String _generatePlainProvideClause(int indent) {
|
String _generatePlainProvideClause(int indent) {
|
||||||
final argsStr = parameters.map((p) => p.generateArg()).join(', ');
|
final argsStr = parameters.map((p) => p.generateArg()).join(', ');
|
||||||
final multiLine = argsStr.length > 60 || argsStr.contains('\n');
|
|
||||||
|
// Check if we need multiline formatting based on total line length
|
||||||
|
final singleLineCall = '$methodName($argsStr)';
|
||||||
|
final needsMultiline =
|
||||||
|
singleLineCall.length >= 45 || argsStr.contains('\n');
|
||||||
|
|
||||||
switch (bindingType) {
|
switch (bindingType) {
|
||||||
case BindingType.instance:
|
case BindingType.instance:
|
||||||
return isAsyncInstance
|
return isAsyncInstance
|
||||||
? '.toInstanceAsync($methodName($argsStr))'
|
? '.toInstanceAsync($methodName($argsStr))'
|
||||||
: '.toInstance($methodName($argsStr))';
|
: '.toInstance($methodName($argsStr))';
|
||||||
case BindingType.provide:
|
case BindingType.provide:
|
||||||
default:
|
|
||||||
if (isAsyncProvide) {
|
if (isAsyncProvide) {
|
||||||
return multiLine
|
if (needsMultiline) {
|
||||||
? '.toProvideAsync(\n${' ' * (indent + 2)}() => $methodName($argsStr))'
|
final lambdaIndent =
|
||||||
: '.toProvideAsync(() => $methodName($argsStr))';
|
(isSingleton || named != null) ? indent + 6 : indent + 2;
|
||||||
|
final closingIndent =
|
||||||
|
(isSingleton || named != null) ? indent + 4 : indent;
|
||||||
|
return '.toProvideAsync(\n${' ' * lambdaIndent}() => $methodName($argsStr),\n${' ' * closingIndent})';
|
||||||
|
} else {
|
||||||
|
return '.toProvideAsync(() => $methodName($argsStr))';
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return multiLine
|
if (needsMultiline) {
|
||||||
? '.toProvide(\n${' ' * (indent + 2)}() => $methodName($argsStr))'
|
final lambdaIndent =
|
||||||
: '.toProvide(() => $methodName($argsStr))';
|
(isSingleton || named != null) ? indent + 6 : indent + 2;
|
||||||
|
final closingIndent =
|
||||||
|
(isSingleton || named != null) ? indent + 4 : indent;
|
||||||
|
return '.toProvide(\n${' ' * lambdaIndent}() => $methodName($argsStr),\n${' ' * closingIndent})';
|
||||||
|
} else {
|
||||||
|
return '.toProvide(() => $methodName($argsStr))';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -174,6 +224,20 @@ class BindSpec {
|
|||||||
return '$namePart$singletonPart';
|
return '$namePart$singletonPart';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// EN / RU: Generates multiline postfix with proper indentation.
|
||||||
|
String _generateMultilinePostfix(int indent) {
|
||||||
|
final parts = <String>[];
|
||||||
|
if (named != null) {
|
||||||
|
parts.add(".withName('$named')");
|
||||||
|
}
|
||||||
|
if (isSingleton) {
|
||||||
|
parts.add('.singleton()');
|
||||||
|
}
|
||||||
|
if (parts.isEmpty) return '';
|
||||||
|
|
||||||
|
return parts.map((part) => '\n${' ' * (indent + 4)}$part').join('');
|
||||||
|
}
|
||||||
|
|
||||||
/// -------------------------------------------------------------------------
|
/// -------------------------------------------------------------------------
|
||||||
/// fromMethod
|
/// fromMethod
|
||||||
///
|
///
|
||||||
@@ -188,73 +252,98 @@ class BindSpec {
|
|||||||
/// асинхронности. Если нет @instance или @provide — кидает ошибку.
|
/// асинхронности. Если нет @instance или @provide — кидает ошибку.
|
||||||
/// -------------------------------------------------------------------------
|
/// -------------------------------------------------------------------------
|
||||||
static BindSpec fromMethod(MethodElement method) {
|
static BindSpec fromMethod(MethodElement method) {
|
||||||
var returnType = method.returnType.getDisplayString();
|
try {
|
||||||
|
// Validate method annotations
|
||||||
|
AnnotationValidator.validateMethodAnnotations(method);
|
||||||
|
|
||||||
final methodName = method.displayName;
|
// Parse return type using improved type parser
|
||||||
// Check for @singleton annotation.
|
final parsedReturnType = TypeParser.parseType(method.returnType, method);
|
||||||
final isSingleton = MetadataUtils.anyMeta(method.metadata, 'singleton');
|
|
||||||
|
|
||||||
// Get @named value if present.
|
final methodName = method.displayName;
|
||||||
final named = MetadataUtils.getNamedValue(method.metadata);
|
|
||||||
|
|
||||||
// Parse each method parameter.
|
// Check for @singleton annotation.
|
||||||
final params = <BindParameterSpec>[];
|
final isSingleton = MetadataUtils.anyMeta(method.metadata, 'singleton');
|
||||||
bool hasParams = false;
|
|
||||||
for (final p in method.parameters) {
|
|
||||||
final typeStr = p.type.getDisplayString();
|
|
||||||
final paramNamed = MetadataUtils.getNamedValue(p.metadata);
|
|
||||||
final isParams = MetadataUtils.anyMeta(p.metadata, 'params');
|
|
||||||
if (isParams) hasParams = true;
|
|
||||||
params.add(BindParameterSpec(typeStr, paramNamed, isParams: isParams));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Determine bindingType: @instance or @provide.
|
// Get @named value if present.
|
||||||
final hasInstance = MetadataUtils.anyMeta(method.metadata, 'instance');
|
final named = MetadataUtils.getNamedValue(method.metadata);
|
||||||
final hasProvide = MetadataUtils.anyMeta(method.metadata, 'provide');
|
|
||||||
if (!hasInstance && !hasProvide) {
|
// Parse each method parameter.
|
||||||
throw InvalidGenerationSourceError(
|
final params = <BindParameterSpec>[];
|
||||||
'Method $methodName must be marked with @instance() or @provide().',
|
bool hasParams = false;
|
||||||
|
for (final p in method.parameters) {
|
||||||
|
final typeStr = p.type.getDisplayString();
|
||||||
|
final paramNamed = MetadataUtils.getNamedValue(p.metadata);
|
||||||
|
final isParams = MetadataUtils.anyMeta(p.metadata, 'params');
|
||||||
|
if (isParams) hasParams = true;
|
||||||
|
params.add(BindParameterSpec(typeStr, paramNamed, isParams: isParams));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine bindingType: @instance or @provide.
|
||||||
|
final hasInstance = MetadataUtils.anyMeta(method.metadata, 'instance');
|
||||||
|
final hasProvide = MetadataUtils.anyMeta(method.metadata, 'provide');
|
||||||
|
|
||||||
|
if (!hasInstance && !hasProvide) {
|
||||||
|
throw AnnotationValidationException(
|
||||||
|
'Method must be marked with either @instance() or @provide() annotation',
|
||||||
|
element: method,
|
||||||
|
suggestion:
|
||||||
|
'Add @instance() for direct instances or @provide() for factory methods',
|
||||||
|
context: {
|
||||||
|
'method_name': methodName,
|
||||||
|
'return_type': parsedReturnType.displayString,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final bindingType =
|
||||||
|
hasInstance ? BindingType.instance : BindingType.provide;
|
||||||
|
|
||||||
|
// PROHIBIT @params with @instance bindings!
|
||||||
|
if (bindingType == BindingType.instance && hasParams) {
|
||||||
|
throw AnnotationValidationException(
|
||||||
|
'@params() (runtime arguments) cannot be used together with @instance()',
|
||||||
|
element: method,
|
||||||
|
suggestion: 'Use @provide() instead if you want runtime arguments',
|
||||||
|
context: {
|
||||||
|
'method_name': methodName,
|
||||||
|
'binding_type': 'instance',
|
||||||
|
'has_params': hasParams,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set async flags based on parsed type
|
||||||
|
final isAsyncInstance =
|
||||||
|
bindingType == BindingType.instance && parsedReturnType.isFuture;
|
||||||
|
final isAsyncProvide =
|
||||||
|
bindingType == BindingType.provide && parsedReturnType.isFuture;
|
||||||
|
|
||||||
|
return BindSpec(
|
||||||
|
returnType: parsedReturnType.codeGenType,
|
||||||
|
methodName: methodName,
|
||||||
|
isSingleton: isSingleton,
|
||||||
|
named: named,
|
||||||
|
parameters: params,
|
||||||
|
bindingType: bindingType,
|
||||||
|
isAsyncInstance: isAsyncInstance,
|
||||||
|
isAsyncProvide: isAsyncProvide,
|
||||||
|
hasParams: hasParams,
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
if (e is CherryPickGeneratorException) {
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
throw CodeGenerationException(
|
||||||
|
'Failed to create BindSpec from method "${method.displayName}"',
|
||||||
element: method,
|
element: method,
|
||||||
|
suggestion:
|
||||||
|
'Check that the method has valid annotations and return type',
|
||||||
|
context: {
|
||||||
|
'method_name': method.displayName,
|
||||||
|
'return_type': method.returnType.getDisplayString(),
|
||||||
|
'error': e.toString(),
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
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;
|
|
||||||
bool isAsyncProvide = false;
|
|
||||||
final futureInnerType = _extractFutureInnerType(returnType);
|
|
||||||
if (futureInnerType != null) {
|
|
||||||
returnType = futureInnerType;
|
|
||||||
if (bindingType == BindingType.instance) isAsyncInstance = true;
|
|
||||||
if (bindingType == BindingType.provide) isAsyncProvide = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return BindSpec(
|
|
||||||
returnType: returnType,
|
|
||||||
methodName: methodName,
|
|
||||||
isSingleton: isSingleton,
|
|
||||||
named: named,
|
|
||||||
parameters: params,
|
|
||||||
bindingType: bindingType,
|
|
||||||
isAsyncInstance: isAsyncInstance,
|
|
||||||
isAsyncProvide: isAsyncProvide,
|
|
||||||
hasParams: hasParams,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// EN / RU: Extracts inner type from Future<T>, returns e.g. "T" or null.
|
|
||||||
static String? _extractFutureInnerType(String typeName) {
|
|
||||||
final match = RegExp(r'^Future<(.+)>$').firstMatch(typeName);
|
|
||||||
return match?.group(1)?.trim();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
117
cherrypick_generator/lib/src/exceptions.dart
Normal file
117
cherrypick_generator/lib/src/exceptions.dart
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
//
|
||||||
|
// 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:analyzer/dart/element/element.dart';
|
||||||
|
import 'package:source_gen/source_gen.dart';
|
||||||
|
|
||||||
|
/// Enhanced exception class for CherryPick generator with detailed context information
|
||||||
|
class CherryPickGeneratorException extends InvalidGenerationSourceError {
|
||||||
|
final String category;
|
||||||
|
final String? suggestion;
|
||||||
|
final Map<String, dynamic>? context;
|
||||||
|
|
||||||
|
CherryPickGeneratorException(
|
||||||
|
String message, {
|
||||||
|
required Element element,
|
||||||
|
required this.category,
|
||||||
|
this.suggestion,
|
||||||
|
this.context,
|
||||||
|
}) : super(
|
||||||
|
_formatMessage(message, category, suggestion, context, element),
|
||||||
|
element: element,
|
||||||
|
);
|
||||||
|
|
||||||
|
static String _formatMessage(
|
||||||
|
String message,
|
||||||
|
String category,
|
||||||
|
String? suggestion,
|
||||||
|
Map<String, dynamic>? context,
|
||||||
|
Element element,
|
||||||
|
) {
|
||||||
|
final buffer = StringBuffer();
|
||||||
|
|
||||||
|
// Header with category
|
||||||
|
buffer.writeln('[$category] $message');
|
||||||
|
|
||||||
|
// Element context
|
||||||
|
buffer.writeln('');
|
||||||
|
buffer.writeln('Context:');
|
||||||
|
buffer.writeln(' Element: ${element.displayName}');
|
||||||
|
buffer.writeln(' Type: ${element.runtimeType}');
|
||||||
|
buffer.writeln(' Location: ${element.source?.fullName ?? 'unknown'}');
|
||||||
|
|
||||||
|
// Note: enclosingElement may not be available in all analyzer versions
|
||||||
|
try {
|
||||||
|
final enclosing = (element as dynamic).enclosingElement;
|
||||||
|
if (enclosing != null) {
|
||||||
|
buffer.writeln(' Enclosing: ${enclosing.displayName}');
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// Ignore if enclosingElement is not available
|
||||||
|
}
|
||||||
|
|
||||||
|
// Additional context
|
||||||
|
if (context != null && context.isNotEmpty) {
|
||||||
|
buffer.writeln('');
|
||||||
|
buffer.writeln('Additional Context:');
|
||||||
|
context.forEach((key, value) {
|
||||||
|
buffer.writeln(' $key: $value');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Suggestion
|
||||||
|
if (suggestion != null) {
|
||||||
|
buffer.writeln('');
|
||||||
|
buffer.writeln('💡 Suggestion: $suggestion');
|
||||||
|
}
|
||||||
|
|
||||||
|
return buffer.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Specific exception types for different error categories
|
||||||
|
class AnnotationValidationException extends CherryPickGeneratorException {
|
||||||
|
AnnotationValidationException(
|
||||||
|
super.message, {
|
||||||
|
required super.element,
|
||||||
|
super.suggestion,
|
||||||
|
super.context,
|
||||||
|
}) : super(category: 'ANNOTATION_VALIDATION');
|
||||||
|
}
|
||||||
|
|
||||||
|
class TypeParsingException extends CherryPickGeneratorException {
|
||||||
|
TypeParsingException(
|
||||||
|
super.message, {
|
||||||
|
required super.element,
|
||||||
|
super.suggestion,
|
||||||
|
super.context,
|
||||||
|
}) : super(category: 'TYPE_PARSING');
|
||||||
|
}
|
||||||
|
|
||||||
|
class CodeGenerationException extends CherryPickGeneratorException {
|
||||||
|
CodeGenerationException(
|
||||||
|
super.message, {
|
||||||
|
required super.element,
|
||||||
|
super.suggestion,
|
||||||
|
super.context,
|
||||||
|
}) : super(category: 'CODE_GENERATION');
|
||||||
|
}
|
||||||
|
|
||||||
|
class DependencyResolutionException extends CherryPickGeneratorException {
|
||||||
|
DependencyResolutionException(
|
||||||
|
super.message, {
|
||||||
|
required super.element,
|
||||||
|
super.suggestion,
|
||||||
|
super.context,
|
||||||
|
}) : super(category: 'DEPENDENCY_RESOLUTION');
|
||||||
|
}
|
||||||
@@ -49,10 +49,15 @@ class GeneratedClass {
|
|||||||
/// Список всех обнаруженных биндингов
|
/// Список всех обнаруженных биндингов
|
||||||
final List<BindSpec> binds;
|
final List<BindSpec> binds;
|
||||||
|
|
||||||
|
/// Source file name for the part directive
|
||||||
|
/// Имя исходного файла для part директивы
|
||||||
|
final String sourceFile;
|
||||||
|
|
||||||
GeneratedClass(
|
GeneratedClass(
|
||||||
this.className,
|
this.className,
|
||||||
this.generatedClassName,
|
this.generatedClassName,
|
||||||
this.binds,
|
this.binds,
|
||||||
|
this.sourceFile,
|
||||||
);
|
);
|
||||||
|
|
||||||
/// -------------------------------------------------------------------------
|
/// -------------------------------------------------------------------------
|
||||||
@@ -72,13 +77,15 @@ class GeneratedClass {
|
|||||||
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).
|
||||||
final generatedClassName = r'$' + className;
|
final generatedClassName = r'$' + className;
|
||||||
|
// Get source file name
|
||||||
|
final sourceFile = element.source.shortName;
|
||||||
// Collect bindings for all non-abstract methods.
|
// Collect bindings for all non-abstract methods.
|
||||||
final binds = element.methods
|
final binds = element.methods
|
||||||
.where((m) => !m.isAbstract)
|
.where((m) => !m.isAbstract)
|
||||||
.map(BindSpec.fromMethod)
|
.map(BindSpec.fromMethod)
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
return GeneratedClass(className, generatedClassName, binds);
|
return GeneratedClass(className, generatedClassName, binds, sourceFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// -------------------------------------------------------------------------
|
/// -------------------------------------------------------------------------
|
||||||
@@ -95,11 +102,10 @@ class GeneratedClass {
|
|||||||
/// и регистрирует все зависимости через методы bind<Type>()...
|
/// и регистрирует все зависимости через методы bind<Type>()...
|
||||||
/// -------------------------------------------------------------------------
|
/// -------------------------------------------------------------------------
|
||||||
String generate() {
|
String generate() {
|
||||||
final buffer = StringBuffer();
|
final buffer = StringBuffer()
|
||||||
|
..writeln('final class $generatedClassName extends $className {')
|
||||||
buffer.writeln('final class $generatedClassName extends $className {');
|
..writeln(' @override')
|
||||||
buffer.writeln(' @override');
|
..writeln(' void builder(Scope currentScope) {');
|
||||||
buffer.writeln(' void builder(Scope currentScope) {');
|
|
||||||
|
|
||||||
// For each binding, generate bind<Type>() code string.
|
// For each binding, generate bind<Type>() code string.
|
||||||
// Для каждого биндинга — генерируем строку bind<Type>()...
|
// Для каждого биндинга — генерируем строку bind<Type>()...
|
||||||
@@ -107,8 +113,9 @@ class GeneratedClass {
|
|||||||
buffer.writeln(bind.generateBind(4));
|
buffer.writeln(bind.generateBind(4));
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer.writeln(' }');
|
buffer
|
||||||
buffer.writeln('}');
|
..writeln(' }')
|
||||||
|
..writeln('}');
|
||||||
|
|
||||||
return buffer.toString();
|
return buffer.toString();
|
||||||
}
|
}
|
||||||
|
|||||||
218
cherrypick_generator/lib/src/type_parser.dart
Normal file
218
cherrypick_generator/lib/src/type_parser.dart
Normal file
@@ -0,0 +1,218 @@
|
|||||||
|
//
|
||||||
|
// 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:analyzer/dart/element/element.dart';
|
||||||
|
import 'package:analyzer/dart/element/nullability_suffix.dart';
|
||||||
|
import 'package:analyzer/dart/element/type.dart';
|
||||||
|
import 'exceptions.dart';
|
||||||
|
|
||||||
|
/// Enhanced type parser that uses AST analysis instead of regular expressions
|
||||||
|
class TypeParser {
|
||||||
|
/// Parses a DartType and extracts detailed type information
|
||||||
|
static ParsedType parseType(DartType dartType, Element context) {
|
||||||
|
try {
|
||||||
|
return _parseTypeInternal(dartType, context);
|
||||||
|
} catch (e) {
|
||||||
|
throw TypeParsingException(
|
||||||
|
'Failed to parse type: ${dartType.getDisplayString()}',
|
||||||
|
element: context,
|
||||||
|
suggestion: 'Ensure the type is properly imported and accessible',
|
||||||
|
context: {
|
||||||
|
'original_type': dartType.getDisplayString(),
|
||||||
|
'error': e.toString(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static ParsedType _parseTypeInternal(DartType dartType, Element context) {
|
||||||
|
final displayString = dartType.getDisplayString();
|
||||||
|
final isNullable = dartType.nullabilitySuffix == NullabilitySuffix.question;
|
||||||
|
|
||||||
|
// Check if it's a Future type
|
||||||
|
if (dartType.isDartAsyncFuture) {
|
||||||
|
return _parseFutureType(dartType, context, isNullable);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if it's a generic type (List, Map, etc.)
|
||||||
|
if (dartType is ParameterizedType && dartType.typeArguments.isNotEmpty) {
|
||||||
|
return _parseGenericType(dartType, context, isNullable);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Simple type
|
||||||
|
return ParsedType(
|
||||||
|
displayString: displayString,
|
||||||
|
coreType: displayString.replaceAll('?', ''),
|
||||||
|
isNullable: isNullable,
|
||||||
|
isFuture: false,
|
||||||
|
isGeneric: false,
|
||||||
|
typeArguments: [],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ParsedType _parseFutureType(
|
||||||
|
DartType dartType, Element context, bool isNullable) {
|
||||||
|
if (dartType is! ParameterizedType || dartType.typeArguments.isEmpty) {
|
||||||
|
throw TypeParsingException(
|
||||||
|
'Future type must have a type argument',
|
||||||
|
element: context,
|
||||||
|
suggestion: 'Use Future<T> instead of raw Future',
|
||||||
|
context: {'type': dartType.getDisplayString()},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final innerType = dartType.typeArguments.first;
|
||||||
|
final innerParsed = _parseTypeInternal(innerType, context);
|
||||||
|
|
||||||
|
return ParsedType(
|
||||||
|
displayString: dartType.getDisplayString(),
|
||||||
|
coreType: innerParsed.coreType,
|
||||||
|
isNullable: isNullable || innerParsed.isNullable,
|
||||||
|
isFuture: true,
|
||||||
|
isGeneric: innerParsed.isGeneric,
|
||||||
|
typeArguments: innerParsed.typeArguments,
|
||||||
|
innerType: innerParsed,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ParsedType _parseGenericType(
|
||||||
|
ParameterizedType dartType, Element context, bool isNullable) {
|
||||||
|
final typeArguments = dartType.typeArguments
|
||||||
|
.map((arg) => _parseTypeInternal(arg, context))
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
final baseType = dartType.element?.name ?? dartType.getDisplayString();
|
||||||
|
|
||||||
|
return ParsedType(
|
||||||
|
displayString: dartType.getDisplayString(),
|
||||||
|
coreType: baseType,
|
||||||
|
isNullable: isNullable,
|
||||||
|
isFuture: false,
|
||||||
|
isGeneric: true,
|
||||||
|
typeArguments: typeArguments,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Validates that a type is suitable for dependency injection
|
||||||
|
static void validateInjectableType(ParsedType parsedType, Element context) {
|
||||||
|
// Check for void type
|
||||||
|
if (parsedType.coreType == 'void') {
|
||||||
|
throw TypeParsingException(
|
||||||
|
'Cannot inject void type',
|
||||||
|
element: context,
|
||||||
|
suggestion: 'Use a concrete type instead of void',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for dynamic type (warning)
|
||||||
|
if (parsedType.coreType == 'dynamic') {
|
||||||
|
// This could be a warning instead of an error
|
||||||
|
throw TypeParsingException(
|
||||||
|
'Using dynamic type reduces type safety',
|
||||||
|
element: context,
|
||||||
|
suggestion: 'Consider using a specific type instead of dynamic',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate nested types for complex generics
|
||||||
|
for (final typeArg in parsedType.typeArguments) {
|
||||||
|
validateInjectableType(typeArg, context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents a parsed type with detailed information
|
||||||
|
class ParsedType {
|
||||||
|
/// The full display string of the type (e.g., "Future<List<String>?>")
|
||||||
|
final String displayString;
|
||||||
|
|
||||||
|
/// The core type name without nullability and Future wrapper (e.g., "List<String>")
|
||||||
|
final String coreType;
|
||||||
|
|
||||||
|
/// Whether the type is nullable
|
||||||
|
final bool isNullable;
|
||||||
|
|
||||||
|
/// Whether the type is wrapped in Future
|
||||||
|
final bool isFuture;
|
||||||
|
|
||||||
|
/// Whether the type has generic parameters
|
||||||
|
final bool isGeneric;
|
||||||
|
|
||||||
|
/// Parsed type arguments for generic types
|
||||||
|
final List<ParsedType> typeArguments;
|
||||||
|
|
||||||
|
/// For Future types, the inner type
|
||||||
|
final ParsedType? innerType;
|
||||||
|
|
||||||
|
const ParsedType({
|
||||||
|
required this.displayString,
|
||||||
|
required this.coreType,
|
||||||
|
required this.isNullable,
|
||||||
|
required this.isFuture,
|
||||||
|
required this.isGeneric,
|
||||||
|
required this.typeArguments,
|
||||||
|
this.innerType,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// Returns the type string suitable for code generation
|
||||||
|
String get codeGenType {
|
||||||
|
if (isFuture && innerType != null) {
|
||||||
|
return innerType!.codeGenType;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For generic types, include type arguments
|
||||||
|
if (isGeneric && typeArguments.isNotEmpty) {
|
||||||
|
final args = typeArguments.map((arg) => arg.codeGenType).join(', ');
|
||||||
|
return '$coreType<$args>';
|
||||||
|
}
|
||||||
|
|
||||||
|
return coreType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns whether this type should use tryResolve instead of resolve
|
||||||
|
bool get shouldUseTryResolve => isNullable;
|
||||||
|
|
||||||
|
/// Returns the appropriate resolve method name
|
||||||
|
String get resolveMethodName {
|
||||||
|
if (isFuture) {
|
||||||
|
return shouldUseTryResolve ? 'tryResolveAsync' : 'resolveAsync';
|
||||||
|
}
|
||||||
|
return shouldUseTryResolve ? 'tryResolve' : 'resolve';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'ParsedType(displayString: $displayString, coreType: $coreType, '
|
||||||
|
'isNullable: $isNullable, isFuture: $isFuture, isGeneric: $isGeneric)';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
if (identical(this, other)) return true;
|
||||||
|
return other is ParsedType &&
|
||||||
|
other.displayString == displayString &&
|
||||||
|
other.coreType == coreType &&
|
||||||
|
other.isNullable == isNullable &&
|
||||||
|
other.isFuture == isFuture &&
|
||||||
|
other.isGeneric == isGeneric;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode {
|
||||||
|
return displayString.hashCode ^
|
||||||
|
coreType.hashCode ^
|
||||||
|
isNullable.hashCode ^
|
||||||
|
isFuture.hashCode ^
|
||||||
|
isGeneric.hashCode;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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.0-dev.3
|
version: 1.1.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
|
||||||
@@ -12,15 +12,16 @@ environment:
|
|||||||
|
|
||||||
# Add regular dependencies here.
|
# Add regular dependencies here.
|
||||||
dependencies:
|
dependencies:
|
||||||
cherrypick_annotations: ^1.1.0-dev.1
|
cherrypick_annotations: ^1.1.0
|
||||||
analyzer: ^6.7.0
|
analyzer: ^7.0.0
|
||||||
dart_style: ^2.3.7
|
dart_style: ^3.0.0
|
||||||
build: ^2.4.1
|
build: ^2.4.1
|
||||||
build_runner: ^2.4.13
|
source_gen: ^2.0.0
|
||||||
source_gen: ^1.5.0
|
|
||||||
collection: ^1.18.0
|
collection: ^1.18.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
lints: ^5.0.0
|
lints: ^4.0.0
|
||||||
mockito: ^5.4.4
|
mockito: ^5.4.4
|
||||||
test: ^1.25.8
|
test: ^1.25.8
|
||||||
|
build_test: ^2.1.7
|
||||||
|
build_runner: ^2.4.13
|
||||||
|
|||||||
307
cherrypick_generator/test/bind_spec_test.dart
Normal file
307
cherrypick_generator/test/bind_spec_test.dart
Normal file
@@ -0,0 +1,307 @@
|
|||||||
|
//
|
||||||
|
// 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:cherrypick_generator/src/bind_spec.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('BindSpec Tests', () {
|
||||||
|
group('BindSpec Creation', () {
|
||||||
|
test('should create BindSpec with all properties', () {
|
||||||
|
final bindSpec = BindSpec(
|
||||||
|
returnType: 'ApiClient',
|
||||||
|
methodName: 'createApiClient',
|
||||||
|
isSingleton: true,
|
||||||
|
named: 'mainApi',
|
||||||
|
parameters: [],
|
||||||
|
bindingType: BindingType.provide,
|
||||||
|
isAsyncInstance: false,
|
||||||
|
isAsyncProvide: true,
|
||||||
|
hasParams: false,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(bindSpec.returnType, equals('ApiClient'));
|
||||||
|
expect(bindSpec.methodName, equals('createApiClient'));
|
||||||
|
expect(bindSpec.isSingleton, isTrue);
|
||||||
|
expect(bindSpec.named, equals('mainApi'));
|
||||||
|
expect(bindSpec.parameters, isEmpty);
|
||||||
|
expect(bindSpec.bindingType, equals(BindingType.provide));
|
||||||
|
expect(bindSpec.isAsyncInstance, isFalse);
|
||||||
|
expect(bindSpec.isAsyncProvide, isTrue);
|
||||||
|
expect(bindSpec.hasParams, isFalse);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should create BindSpec with minimal properties', () {
|
||||||
|
final bindSpec = BindSpec(
|
||||||
|
returnType: 'String',
|
||||||
|
methodName: 'getString',
|
||||||
|
isSingleton: false,
|
||||||
|
parameters: [],
|
||||||
|
bindingType: BindingType.instance,
|
||||||
|
isAsyncInstance: false,
|
||||||
|
isAsyncProvide: false,
|
||||||
|
hasParams: false,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(bindSpec.returnType, equals('String'));
|
||||||
|
expect(bindSpec.methodName, equals('getString'));
|
||||||
|
expect(bindSpec.isSingleton, isFalse);
|
||||||
|
expect(bindSpec.named, isNull);
|
||||||
|
expect(bindSpec.bindingType, equals(BindingType.instance));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('Bind Generation - Instance', () {
|
||||||
|
test('should generate simple instance bind', () {
|
||||||
|
final bindSpec = BindSpec(
|
||||||
|
returnType: 'String',
|
||||||
|
methodName: 'getString',
|
||||||
|
isSingleton: false,
|
||||||
|
parameters: [],
|
||||||
|
bindingType: BindingType.instance,
|
||||||
|
isAsyncInstance: false,
|
||||||
|
isAsyncProvide: false,
|
||||||
|
hasParams: false,
|
||||||
|
);
|
||||||
|
|
||||||
|
final result = bindSpec.generateBind(4);
|
||||||
|
expect(result, equals(' bind<String>().toInstance(getString());'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should generate singleton instance bind', () {
|
||||||
|
final bindSpec = BindSpec(
|
||||||
|
returnType: 'String',
|
||||||
|
methodName: 'getString',
|
||||||
|
isSingleton: true,
|
||||||
|
parameters: [],
|
||||||
|
bindingType: BindingType.instance,
|
||||||
|
isAsyncInstance: false,
|
||||||
|
isAsyncProvide: false,
|
||||||
|
hasParams: false,
|
||||||
|
);
|
||||||
|
|
||||||
|
final result = bindSpec.generateBind(4);
|
||||||
|
expect(result,
|
||||||
|
equals(' bind<String>().toInstance(getString()).singleton();'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should generate named instance bind', () {
|
||||||
|
final bindSpec = BindSpec(
|
||||||
|
returnType: 'String',
|
||||||
|
methodName: 'getString',
|
||||||
|
isSingleton: false,
|
||||||
|
named: 'testString',
|
||||||
|
parameters: [],
|
||||||
|
bindingType: BindingType.instance,
|
||||||
|
isAsyncInstance: false,
|
||||||
|
isAsyncProvide: false,
|
||||||
|
hasParams: false,
|
||||||
|
);
|
||||||
|
|
||||||
|
final result = bindSpec.generateBind(4);
|
||||||
|
expect(
|
||||||
|
result,
|
||||||
|
equals(
|
||||||
|
" bind<String>().toInstance(getString()).withName('testString');"));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should generate named singleton instance bind', () {
|
||||||
|
final bindSpec = BindSpec(
|
||||||
|
returnType: 'String',
|
||||||
|
methodName: 'getString',
|
||||||
|
isSingleton: true,
|
||||||
|
named: 'testString',
|
||||||
|
parameters: [],
|
||||||
|
bindingType: BindingType.instance,
|
||||||
|
isAsyncInstance: false,
|
||||||
|
isAsyncProvide: false,
|
||||||
|
hasParams: false,
|
||||||
|
);
|
||||||
|
|
||||||
|
final result = bindSpec.generateBind(4);
|
||||||
|
expect(
|
||||||
|
result,
|
||||||
|
equals(
|
||||||
|
" bind<String>().toInstance(getString()).withName('testString').singleton();"));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should generate async instance bind', () {
|
||||||
|
final bindSpec = BindSpec(
|
||||||
|
returnType: 'String',
|
||||||
|
methodName: 'getString',
|
||||||
|
isSingleton: false,
|
||||||
|
parameters: [],
|
||||||
|
bindingType: BindingType.instance,
|
||||||
|
isAsyncInstance: true,
|
||||||
|
isAsyncProvide: false,
|
||||||
|
hasParams: false,
|
||||||
|
);
|
||||||
|
|
||||||
|
final result = bindSpec.generateBind(4);
|
||||||
|
expect(
|
||||||
|
result, equals(' bind<String>().toInstanceAsync(getString());'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('Bind Generation - Provide', () {
|
||||||
|
test('should generate simple provide bind', () {
|
||||||
|
final bindSpec = BindSpec(
|
||||||
|
returnType: 'String',
|
||||||
|
methodName: 'getString',
|
||||||
|
isSingleton: false,
|
||||||
|
parameters: [],
|
||||||
|
bindingType: BindingType.provide,
|
||||||
|
isAsyncInstance: false,
|
||||||
|
isAsyncProvide: false,
|
||||||
|
hasParams: false,
|
||||||
|
);
|
||||||
|
|
||||||
|
final result = bindSpec.generateBind(4);
|
||||||
|
expect(
|
||||||
|
result, equals(' bind<String>().toProvide(() => getString());'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should generate async provide bind', () {
|
||||||
|
final bindSpec = BindSpec(
|
||||||
|
returnType: 'String',
|
||||||
|
methodName: 'getString',
|
||||||
|
isSingleton: false,
|
||||||
|
parameters: [],
|
||||||
|
bindingType: BindingType.provide,
|
||||||
|
isAsyncInstance: false,
|
||||||
|
isAsyncProvide: true,
|
||||||
|
hasParams: false,
|
||||||
|
);
|
||||||
|
|
||||||
|
final result = bindSpec.generateBind(4);
|
||||||
|
expect(result,
|
||||||
|
equals(' bind<String>().toProvideAsync(() => getString());'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should generate provide bind with params', () {
|
||||||
|
final bindSpec = BindSpec(
|
||||||
|
returnType: 'String',
|
||||||
|
methodName: 'getString',
|
||||||
|
isSingleton: false,
|
||||||
|
parameters: [],
|
||||||
|
bindingType: BindingType.provide,
|
||||||
|
isAsyncInstance: false,
|
||||||
|
isAsyncProvide: false,
|
||||||
|
hasParams: true,
|
||||||
|
);
|
||||||
|
|
||||||
|
final result = bindSpec.generateBind(4);
|
||||||
|
expect(
|
||||||
|
result,
|
||||||
|
equals(
|
||||||
|
' bind<String>().toProvideWithParams((args) => getString());'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should generate async provide bind with params', () {
|
||||||
|
final bindSpec = BindSpec(
|
||||||
|
returnType: 'String',
|
||||||
|
methodName: 'getString',
|
||||||
|
isSingleton: false,
|
||||||
|
parameters: [],
|
||||||
|
bindingType: BindingType.provide,
|
||||||
|
isAsyncInstance: false,
|
||||||
|
isAsyncProvide: true,
|
||||||
|
hasParams: true,
|
||||||
|
);
|
||||||
|
|
||||||
|
final result = bindSpec.generateBind(4);
|
||||||
|
expect(
|
||||||
|
result,
|
||||||
|
equals(
|
||||||
|
' bind<String>().toProvideAsyncWithParams((args) => getString());'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('Complex Scenarios', () {
|
||||||
|
test('should generate bind with all options', () {
|
||||||
|
final bindSpec = BindSpec(
|
||||||
|
returnType: 'ApiClient',
|
||||||
|
methodName: 'createApiClient',
|
||||||
|
isSingleton: true,
|
||||||
|
named: 'mainApi',
|
||||||
|
parameters: [],
|
||||||
|
bindingType: BindingType.provide,
|
||||||
|
isAsyncInstance: false,
|
||||||
|
isAsyncProvide: true,
|
||||||
|
hasParams: false,
|
||||||
|
);
|
||||||
|
|
||||||
|
final result = bindSpec.generateBind(4);
|
||||||
|
expect(
|
||||||
|
result,
|
||||||
|
equals(
|
||||||
|
" bind<ApiClient>()\n"
|
||||||
|
" .toProvideAsync(() => createApiClient())\n"
|
||||||
|
" .withName('mainApi')\n"
|
||||||
|
" .singleton();"));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should handle different indentation', () {
|
||||||
|
final bindSpec = BindSpec(
|
||||||
|
returnType: 'String',
|
||||||
|
methodName: 'getString',
|
||||||
|
isSingleton: false,
|
||||||
|
parameters: [],
|
||||||
|
bindingType: BindingType.instance,
|
||||||
|
isAsyncInstance: false,
|
||||||
|
isAsyncProvide: false,
|
||||||
|
hasParams: false,
|
||||||
|
);
|
||||||
|
|
||||||
|
final result2 = bindSpec.generateBind(2);
|
||||||
|
expect(result2, startsWith(' '));
|
||||||
|
|
||||||
|
final result8 = bindSpec.generateBind(8);
|
||||||
|
expect(result8, startsWith(' '));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should handle complex type names', () {
|
||||||
|
final bindSpec = BindSpec(
|
||||||
|
returnType: 'Map<String, List<User>>',
|
||||||
|
methodName: 'getComplexData',
|
||||||
|
isSingleton: false,
|
||||||
|
parameters: [],
|
||||||
|
bindingType: BindingType.provide,
|
||||||
|
isAsyncInstance: false,
|
||||||
|
isAsyncProvide: false,
|
||||||
|
hasParams: false,
|
||||||
|
);
|
||||||
|
|
||||||
|
final result = bindSpec.generateBind(4);
|
||||||
|
expect(result, contains('bind<Map<String, List<User>>>()'));
|
||||||
|
expect(result, contains('toProvide'));
|
||||||
|
expect(result, contains('getComplexData'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('BindingType Enum', () {
|
||||||
|
test('should have correct enum values', () {
|
||||||
|
expect(BindingType.instance, isNotNull);
|
||||||
|
expect(BindingType.provide, isNotNull);
|
||||||
|
expect(BindingType.values, hasLength(2));
|
||||||
|
expect(BindingType.values, contains(BindingType.instance));
|
||||||
|
expect(BindingType.values, contains(BindingType.provide));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should have correct string representation', () {
|
||||||
|
expect(BindingType.instance.toString(), contains('instance'));
|
||||||
|
expect(BindingType.provide.toString(), contains('provide'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -1,13 +1,32 @@
|
|||||||
|
//
|
||||||
|
// 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:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
void main() {
|
// Import working test suites
|
||||||
group('A group of tests', () {
|
import 'simple_test.dart' as simple_tests;
|
||||||
setUp(() {
|
import 'bind_spec_test.dart' as bind_spec_tests;
|
||||||
// Additional setup goes here.
|
import 'metadata_utils_test.dart' as metadata_utils_tests;
|
||||||
});
|
// Import integration test suites (now working!)
|
||||||
|
import 'module_generator_test.dart' as module_generator_tests;
|
||||||
|
import 'inject_generator_test.dart' as inject_generator_tests;
|
||||||
|
|
||||||
test('First Test', () {
|
void main() {
|
||||||
expect(2, 2);
|
group('CherryPick Generator Tests', () {
|
||||||
});
|
group('Simple Tests', simple_tests.main);
|
||||||
|
group('BindSpec Tests', bind_spec_tests.main);
|
||||||
|
group('MetadataUtils Tests', metadata_utils_tests.main);
|
||||||
|
group('ModuleGenerator Tests', module_generator_tests.main);
|
||||||
|
group('InjectGenerator Tests', inject_generator_tests.main);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
604
cherrypick_generator/test/inject_generator_test.dart
Normal file
604
cherrypick_generator/test/inject_generator_test.dart
Normal file
@@ -0,0 +1,604 @@
|
|||||||
|
//
|
||||||
|
// 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:build/build.dart';
|
||||||
|
import 'package:build_test/build_test.dart';
|
||||||
|
import 'package:cherrypick_generator/inject_generator.dart';
|
||||||
|
import 'package:source_gen/source_gen.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('InjectGenerator Tests', () {
|
||||||
|
setUp(() {
|
||||||
|
// InjectGenerator setup if needed
|
||||||
|
});
|
||||||
|
|
||||||
|
group('Basic Injection', () {
|
||||||
|
test('should generate mixin for simple injection', () async {
|
||||||
|
const input = '''
|
||||||
|
import 'package:cherrypick_annotations/cherrypick_annotations.dart';
|
||||||
|
|
||||||
|
part 'test_widget.inject.cherrypick.g.dart';
|
||||||
|
|
||||||
|
class MyService {}
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
class TestWidget {
|
||||||
|
@inject()
|
||||||
|
late final MyService service;
|
||||||
|
}
|
||||||
|
''';
|
||||||
|
|
||||||
|
const expectedOutput = '''
|
||||||
|
// dart format width=80
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'test_widget.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// InjectGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
mixin _\$TestWidget {
|
||||||
|
void _inject(TestWidget instance) {
|
||||||
|
instance.service = CherryPick.openRootScope().resolve<MyService>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
''';
|
||||||
|
|
||||||
|
await _testGeneration(input, expectedOutput);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should generate mixin for nullable injection', () async {
|
||||||
|
const input = '''
|
||||||
|
import 'package:cherrypick_annotations/cherrypick_annotations.dart';
|
||||||
|
|
||||||
|
part 'test_widget.inject.cherrypick.g.dart';
|
||||||
|
|
||||||
|
class MyService {}
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
class TestWidget {
|
||||||
|
@inject()
|
||||||
|
late final MyService? service;
|
||||||
|
}
|
||||||
|
''';
|
||||||
|
|
||||||
|
const expectedOutput = '''
|
||||||
|
// dart format width=80
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'test_widget.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// InjectGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
mixin _\$TestWidget {
|
||||||
|
void _inject(TestWidget instance) {
|
||||||
|
instance.service = CherryPick.openRootScope().tryResolve<MyService>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
''';
|
||||||
|
|
||||||
|
await _testGeneration(input, expectedOutput);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('Named Injection', () {
|
||||||
|
test('should generate mixin for named injection', () async {
|
||||||
|
const input = '''
|
||||||
|
import 'package:cherrypick_annotations/cherrypick_annotations.dart';
|
||||||
|
|
||||||
|
part 'test_widget.inject.cherrypick.g.dart';
|
||||||
|
|
||||||
|
class MyService {}
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
class TestWidget {
|
||||||
|
@inject()
|
||||||
|
@named('myService')
|
||||||
|
late final MyService service;
|
||||||
|
}
|
||||||
|
''';
|
||||||
|
|
||||||
|
const expectedOutput = '''
|
||||||
|
// dart format width=80
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'test_widget.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// InjectGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
mixin _\$TestWidget {
|
||||||
|
void _inject(TestWidget instance) {
|
||||||
|
instance.service = CherryPick.openRootScope().resolve<MyService>(
|
||||||
|
named: 'myService',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
''';
|
||||||
|
|
||||||
|
await _testGeneration(input, expectedOutput);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should generate mixin for named nullable injection', () async {
|
||||||
|
const input = '''
|
||||||
|
import 'package:cherrypick_annotations/cherrypick_annotations.dart';
|
||||||
|
|
||||||
|
part 'test_widget.inject.cherrypick.g.dart';
|
||||||
|
|
||||||
|
class MyService {}
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
class TestWidget {
|
||||||
|
@inject()
|
||||||
|
@named('myService')
|
||||||
|
late final MyService? service;
|
||||||
|
}
|
||||||
|
''';
|
||||||
|
|
||||||
|
const expectedOutput = '''
|
||||||
|
// dart format width=80
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'test_widget.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// InjectGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
mixin _\$TestWidget {
|
||||||
|
void _inject(TestWidget instance) {
|
||||||
|
instance.service = CherryPick.openRootScope().tryResolve<MyService>(
|
||||||
|
named: 'myService',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
''';
|
||||||
|
|
||||||
|
await _testGeneration(input, expectedOutput);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('Scoped Injection', () {
|
||||||
|
test('should generate mixin for scoped injection', () async {
|
||||||
|
const input = '''
|
||||||
|
import 'package:cherrypick_annotations/cherrypick_annotations.dart';
|
||||||
|
|
||||||
|
part 'test_widget.inject.cherrypick.g.dart';
|
||||||
|
|
||||||
|
class MyService {}
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
class TestWidget {
|
||||||
|
@inject()
|
||||||
|
@scope('userScope')
|
||||||
|
late final MyService service;
|
||||||
|
}
|
||||||
|
''';
|
||||||
|
|
||||||
|
const expectedOutput = '''
|
||||||
|
// dart format width=80
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'test_widget.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// InjectGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
mixin _\$TestWidget {
|
||||||
|
void _inject(TestWidget instance) {
|
||||||
|
instance.service =
|
||||||
|
CherryPick.openScope(scopeName: 'userScope').resolve<MyService>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
''';
|
||||||
|
|
||||||
|
await _testGeneration(input, expectedOutput);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should generate mixin for scoped named injection', () async {
|
||||||
|
const input = '''
|
||||||
|
import 'package:cherrypick_annotations/cherrypick_annotations.dart';
|
||||||
|
|
||||||
|
part 'test_widget.inject.cherrypick.g.dart';
|
||||||
|
|
||||||
|
class MyService {}
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
class TestWidget {
|
||||||
|
@inject()
|
||||||
|
@scope('userScope')
|
||||||
|
@named('myService')
|
||||||
|
late final MyService service;
|
||||||
|
}
|
||||||
|
''';
|
||||||
|
|
||||||
|
const expectedOutput = '''
|
||||||
|
// dart format width=80
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'test_widget.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// InjectGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
mixin _\$TestWidget {
|
||||||
|
void _inject(TestWidget instance) {
|
||||||
|
instance.service = CherryPick.openScope(
|
||||||
|
scopeName: 'userScope',
|
||||||
|
).resolve<MyService>(named: 'myService');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
''';
|
||||||
|
|
||||||
|
await _testGeneration(input, expectedOutput);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('Async Injection', () {
|
||||||
|
test('should generate mixin for Future injection', () async {
|
||||||
|
const input = '''
|
||||||
|
import 'package:cherrypick_annotations/cherrypick_annotations.dart';
|
||||||
|
|
||||||
|
part 'test_widget.inject.cherrypick.g.dart';
|
||||||
|
|
||||||
|
class MyService {}
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
class TestWidget {
|
||||||
|
@inject()
|
||||||
|
late final Future<MyService> service;
|
||||||
|
}
|
||||||
|
''';
|
||||||
|
|
||||||
|
const expectedOutput = '''
|
||||||
|
// dart format width=80
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'test_widget.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// InjectGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
mixin _\$TestWidget {
|
||||||
|
void _inject(TestWidget instance) {
|
||||||
|
instance.service = CherryPick.openRootScope().resolveAsync<MyService>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
''';
|
||||||
|
|
||||||
|
await _testGeneration(input, expectedOutput);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should generate mixin for nullable Future injection', () async {
|
||||||
|
const input = '''
|
||||||
|
import 'package:cherrypick_annotations/cherrypick_annotations.dart';
|
||||||
|
|
||||||
|
part 'test_widget.inject.cherrypick.g.dart';
|
||||||
|
|
||||||
|
class MyService {}
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
class TestWidget {
|
||||||
|
@inject()
|
||||||
|
late final Future<MyService?> service;
|
||||||
|
}
|
||||||
|
''';
|
||||||
|
|
||||||
|
const expectedOutput = '''
|
||||||
|
// dart format width=80
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'test_widget.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// InjectGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
mixin _\$TestWidget {
|
||||||
|
void _inject(TestWidget instance) {
|
||||||
|
instance.service = CherryPick.openRootScope().tryResolveAsync<MyService>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
''';
|
||||||
|
|
||||||
|
await _testGeneration(input, expectedOutput);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should generate mixin for named Future injection', () async {
|
||||||
|
const input = '''
|
||||||
|
import 'package:cherrypick_annotations/cherrypick_annotations.dart';
|
||||||
|
|
||||||
|
part 'test_widget.inject.cherrypick.g.dart';
|
||||||
|
|
||||||
|
class MyService {}
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
class TestWidget {
|
||||||
|
@inject()
|
||||||
|
@named('myService')
|
||||||
|
late final Future<MyService> service;
|
||||||
|
}
|
||||||
|
''';
|
||||||
|
|
||||||
|
const expectedOutput = '''
|
||||||
|
// dart format width=80
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'test_widget.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// InjectGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
mixin _\$TestWidget {
|
||||||
|
void _inject(TestWidget instance) {
|
||||||
|
instance.service = CherryPick.openRootScope().resolveAsync<MyService>(
|
||||||
|
named: 'myService',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
''';
|
||||||
|
|
||||||
|
await _testGeneration(input, expectedOutput);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('Multiple Fields', () {
|
||||||
|
test('should generate mixin for multiple injected fields', () async {
|
||||||
|
const input = '''
|
||||||
|
import 'package:cherrypick_annotations/cherrypick_annotations.dart';
|
||||||
|
|
||||||
|
part 'test_widget.inject.cherrypick.g.dart';
|
||||||
|
|
||||||
|
class ApiService {}
|
||||||
|
class DatabaseService {}
|
||||||
|
class CacheService {}
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
class TestWidget {
|
||||||
|
@inject()
|
||||||
|
late final ApiService apiService;
|
||||||
|
|
||||||
|
@inject()
|
||||||
|
@named('cache')
|
||||||
|
late final CacheService? cacheService;
|
||||||
|
|
||||||
|
@inject()
|
||||||
|
@scope('dbScope')
|
||||||
|
late final Future<DatabaseService> dbService;
|
||||||
|
|
||||||
|
// Non-injected field should be ignored
|
||||||
|
String nonInjectedField = "test";
|
||||||
|
}
|
||||||
|
''';
|
||||||
|
|
||||||
|
const expectedOutput = '''
|
||||||
|
// dart format width=80
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'test_widget.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// InjectGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
mixin _\$TestWidget {
|
||||||
|
void _inject(TestWidget instance) {
|
||||||
|
instance.apiService = CherryPick.openRootScope().resolve<ApiService>();
|
||||||
|
instance.cacheService = CherryPick.openRootScope().tryResolve<CacheService>(
|
||||||
|
named: 'cache',
|
||||||
|
);
|
||||||
|
instance.dbService =
|
||||||
|
CherryPick.openScope(
|
||||||
|
scopeName: 'dbScope',
|
||||||
|
).resolveAsync<DatabaseService>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
''';
|
||||||
|
|
||||||
|
await _testGeneration(input, expectedOutput);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('Complex Types', () {
|
||||||
|
test('should handle generic types', () async {
|
||||||
|
const input = '''
|
||||||
|
import 'package:cherrypick_annotations/cherrypick_annotations.dart';
|
||||||
|
|
||||||
|
part 'test_widget.inject.cherrypick.g.dart';
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
class TestWidget {
|
||||||
|
@inject()
|
||||||
|
late final List<String> stringList;
|
||||||
|
|
||||||
|
@inject()
|
||||||
|
late final Map<String, int> stringIntMap;
|
||||||
|
|
||||||
|
@inject()
|
||||||
|
late final Future<List<String>> futureStringList;
|
||||||
|
}
|
||||||
|
''';
|
||||||
|
|
||||||
|
const expectedOutput = '''
|
||||||
|
// dart format width=80
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'test_widget.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// InjectGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
mixin _\$TestWidget {
|
||||||
|
void _inject(TestWidget instance) {
|
||||||
|
instance.stringList = CherryPick.openRootScope().resolve<List<String>>();
|
||||||
|
instance.stringIntMap =
|
||||||
|
CherryPick.openRootScope().resolve<Map<String, int>>();
|
||||||
|
instance.futureStringList =
|
||||||
|
CherryPick.openRootScope().resolveAsync<List<String>>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
''';
|
||||||
|
|
||||||
|
await _testGeneration(input, expectedOutput);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('Error Cases', () {
|
||||||
|
test('should throw error for non-class element', () async {
|
||||||
|
const input = '''
|
||||||
|
import 'package:cherrypick_annotations/cherrypick_annotations.dart';
|
||||||
|
|
||||||
|
part 'test_widget.inject.cherrypick.g.dart';
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
void notAClass() {}
|
||||||
|
''';
|
||||||
|
|
||||||
|
await expectLater(
|
||||||
|
() => _testGeneration(input, ''),
|
||||||
|
throwsA(isA<InvalidGenerationSourceError>()),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should generate empty mixin for class without @inject fields',
|
||||||
|
() async {
|
||||||
|
const input = '''
|
||||||
|
import 'package:cherrypick_annotations/cherrypick_annotations.dart';
|
||||||
|
|
||||||
|
part 'test_widget.inject.cherrypick.g.dart';
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
class TestWidget {
|
||||||
|
String normalField = "test";
|
||||||
|
int anotherField = 42;
|
||||||
|
}
|
||||||
|
''';
|
||||||
|
|
||||||
|
const expectedOutput = '''
|
||||||
|
// dart format width=80
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'test_widget.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// InjectGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
mixin _\$TestWidget {
|
||||||
|
void _inject(TestWidget instance) {}
|
||||||
|
}
|
||||||
|
''';
|
||||||
|
|
||||||
|
await _testGeneration(input, expectedOutput);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('Edge Cases', () {
|
||||||
|
test('should handle empty scope name', () async {
|
||||||
|
const input = '''
|
||||||
|
import 'package:cherrypick_annotations/cherrypick_annotations.dart';
|
||||||
|
|
||||||
|
part 'test_widget.inject.cherrypick.g.dart';
|
||||||
|
|
||||||
|
class MyService {}
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
class TestWidget {
|
||||||
|
@inject()
|
||||||
|
@scope('')
|
||||||
|
late final MyService service;
|
||||||
|
}
|
||||||
|
''';
|
||||||
|
|
||||||
|
const expectedOutput = '''
|
||||||
|
// dart format width=80
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'test_widget.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// InjectGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
mixin _\$TestWidget {
|
||||||
|
void _inject(TestWidget instance) {
|
||||||
|
instance.service = CherryPick.openRootScope().resolve<MyService>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
''';
|
||||||
|
|
||||||
|
await _testGeneration(input, expectedOutput);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should handle empty named value', () async {
|
||||||
|
const input = '''
|
||||||
|
import 'package:cherrypick_annotations/cherrypick_annotations.dart';
|
||||||
|
|
||||||
|
part 'test_widget.inject.cherrypick.g.dart';
|
||||||
|
|
||||||
|
class MyService {}
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
class TestWidget {
|
||||||
|
@inject()
|
||||||
|
@named('')
|
||||||
|
late final MyService service;
|
||||||
|
}
|
||||||
|
''';
|
||||||
|
|
||||||
|
const expectedOutput = '''
|
||||||
|
// dart format width=80
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'test_widget.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// InjectGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
mixin _\$TestWidget {
|
||||||
|
void _inject(TestWidget instance) {
|
||||||
|
instance.service = CherryPick.openRootScope().resolve<MyService>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
''';
|
||||||
|
|
||||||
|
await _testGeneration(input, expectedOutput);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper function to test code generation
|
||||||
|
Future<void> _testGeneration(String input, String expectedOutput) async {
|
||||||
|
await testBuilder(
|
||||||
|
injectBuilder(BuilderOptions.empty),
|
||||||
|
{
|
||||||
|
'a|lib/test_widget.dart': input,
|
||||||
|
},
|
||||||
|
outputs: {
|
||||||
|
'a|lib/test_widget.inject.cherrypick.g.dart': expectedOutput,
|
||||||
|
},
|
||||||
|
reader: await PackageAssetReader.currentIsolate(),
|
||||||
|
);
|
||||||
|
}
|
||||||
72
cherrypick_generator/test/metadata_utils_test.dart
Normal file
72
cherrypick_generator/test/metadata_utils_test.dart
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
//
|
||||||
|
// 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:cherrypick_generator/src/metadata_utils.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('MetadataUtils Tests', () {
|
||||||
|
group('Basic Functionality', () {
|
||||||
|
test('should handle empty metadata lists', () {
|
||||||
|
expect(MetadataUtils.anyMeta([], 'singleton'), isFalse);
|
||||||
|
expect(MetadataUtils.getNamedValue([]), isNull);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should be available for testing', () {
|
||||||
|
// This test ensures the MetadataUtils class is accessible
|
||||||
|
// More comprehensive tests would require mock setup or integration tests
|
||||||
|
expect(MetadataUtils, isNotNull);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should handle null inputs gracefully', () {
|
||||||
|
expect(MetadataUtils.anyMeta([], ''), isFalse);
|
||||||
|
expect(MetadataUtils.getNamedValue([]), isNull);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should have static methods available', () {
|
||||||
|
// Verify that the static methods exist and can be called
|
||||||
|
// This is a basic smoke test
|
||||||
|
expect(() => MetadataUtils.anyMeta([], 'test'), returnsNormally);
|
||||||
|
expect(() => MetadataUtils.getNamedValue([]), returnsNormally);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('Method Signatures', () {
|
||||||
|
test('anyMeta should return bool', () {
|
||||||
|
final result = MetadataUtils.anyMeta([], 'singleton');
|
||||||
|
expect(result, isA<bool>());
|
||||||
|
});
|
||||||
|
|
||||||
|
test('getNamedValue should return String or null', () {
|
||||||
|
final result = MetadataUtils.getNamedValue([]);
|
||||||
|
expect(result, anyOf(isA<String>(), isNull));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('Edge Cases', () {
|
||||||
|
test('should handle various annotation names', () {
|
||||||
|
// Test with different annotation names
|
||||||
|
expect(MetadataUtils.anyMeta([], 'singleton'), isFalse);
|
||||||
|
expect(MetadataUtils.anyMeta([], 'provide'), isFalse);
|
||||||
|
expect(MetadataUtils.anyMeta([], 'instance'), isFalse);
|
||||||
|
expect(MetadataUtils.anyMeta([], 'named'), isFalse);
|
||||||
|
expect(MetadataUtils.anyMeta([], 'params'), isFalse);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should handle empty strings', () {
|
||||||
|
expect(MetadataUtils.anyMeta([], ''), isFalse);
|
||||||
|
expect(MetadataUtils.getNamedValue([]), isNull);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
648
cherrypick_generator/test/module_generator_test.dart
Normal file
648
cherrypick_generator/test/module_generator_test.dart
Normal file
@@ -0,0 +1,648 @@
|
|||||||
|
//
|
||||||
|
// 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:test/test.dart';
|
||||||
|
import 'package:build_test/build_test.dart';
|
||||||
|
import 'package:build/build.dart';
|
||||||
|
|
||||||
|
import 'package:cherrypick_generator/module_generator.dart';
|
||||||
|
import 'package:source_gen/source_gen.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('ModuleGenerator Tests', () {
|
||||||
|
setUp(() {
|
||||||
|
// ModuleGenerator setup if needed
|
||||||
|
});
|
||||||
|
|
||||||
|
group('Simple Module Generation', () {
|
||||||
|
test('should generate basic module with instance binding', () async {
|
||||||
|
const input = '''
|
||||||
|
import 'package:cherrypick_annotations/cherrypick_annotations.dart';
|
||||||
|
import 'package:cherrypick/cherrypick.dart';
|
||||||
|
|
||||||
|
part 'test_module.module.cherrypick.g.dart';
|
||||||
|
|
||||||
|
@module()
|
||||||
|
abstract class TestModule extends Module {
|
||||||
|
@instance()
|
||||||
|
String testString() => "Hello World";
|
||||||
|
}
|
||||||
|
''';
|
||||||
|
|
||||||
|
const expectedOutput = '''
|
||||||
|
// dart format width=80
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'test_module.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// ModuleGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
final class \$TestModule extends TestModule {
|
||||||
|
@override
|
||||||
|
void builder(Scope currentScope) {
|
||||||
|
bind<String>().toInstance(testString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
''';
|
||||||
|
|
||||||
|
await _testGeneration(input, expectedOutput);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should generate basic module with provide binding', () async {
|
||||||
|
const input = '''
|
||||||
|
import 'package:cherrypick_annotations/cherrypick_annotations.dart';
|
||||||
|
import 'package:cherrypick/cherrypick.dart';
|
||||||
|
|
||||||
|
part 'test_module.module.cherrypick.g.dart';
|
||||||
|
|
||||||
|
@module()
|
||||||
|
abstract class TestModule extends Module {
|
||||||
|
@provide()
|
||||||
|
String testString() => "Hello World";
|
||||||
|
}
|
||||||
|
''';
|
||||||
|
|
||||||
|
const expectedOutput = '''
|
||||||
|
// dart format width=80
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'test_module.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// ModuleGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
final class \$TestModule extends TestModule {
|
||||||
|
@override
|
||||||
|
void builder(Scope currentScope) {
|
||||||
|
bind<String>().toProvide(() => testString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
''';
|
||||||
|
|
||||||
|
await _testGeneration(input, expectedOutput);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('Singleton Bindings', () {
|
||||||
|
test('should generate singleton instance binding', () async {
|
||||||
|
const input = '''
|
||||||
|
import 'package:cherrypick_annotations/cherrypick_annotations.dart';
|
||||||
|
import 'package:cherrypick/cherrypick.dart';
|
||||||
|
|
||||||
|
part 'test_module.module.cherrypick.g.dart';
|
||||||
|
|
||||||
|
@module()
|
||||||
|
abstract class TestModule extends Module {
|
||||||
|
@instance()
|
||||||
|
@singleton()
|
||||||
|
String testString() => "Hello World";
|
||||||
|
}
|
||||||
|
''';
|
||||||
|
|
||||||
|
const expectedOutput = '''
|
||||||
|
// dart format width=80
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'test_module.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// ModuleGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
final class \$TestModule extends TestModule {
|
||||||
|
@override
|
||||||
|
void builder(Scope currentScope) {
|
||||||
|
bind<String>().toInstance(testString()).singleton();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
''';
|
||||||
|
|
||||||
|
await _testGeneration(input, expectedOutput);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should generate singleton provide binding', () async {
|
||||||
|
const input = '''
|
||||||
|
import 'package:cherrypick_annotations/cherrypick_annotations.dart';
|
||||||
|
import 'package:cherrypick/cherrypick.dart';
|
||||||
|
|
||||||
|
part 'test_module.module.cherrypick.g.dart';
|
||||||
|
|
||||||
|
@module()
|
||||||
|
abstract class TestModule extends Module {
|
||||||
|
@provide()
|
||||||
|
@singleton()
|
||||||
|
String testString() => "Hello World";
|
||||||
|
}
|
||||||
|
''';
|
||||||
|
|
||||||
|
const expectedOutput = '''
|
||||||
|
// dart format width=80
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'test_module.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// ModuleGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
final class \$TestModule extends TestModule {
|
||||||
|
@override
|
||||||
|
void builder(Scope currentScope) {
|
||||||
|
bind<String>().toProvide(() => testString()).singleton();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
''';
|
||||||
|
|
||||||
|
await _testGeneration(input, expectedOutput);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('Named Bindings', () {
|
||||||
|
test('should generate named instance binding', () async {
|
||||||
|
const input = '''
|
||||||
|
import 'package:cherrypick_annotations/cherrypick_annotations.dart';
|
||||||
|
import 'package:cherrypick/cherrypick.dart';
|
||||||
|
|
||||||
|
part 'test_module.module.cherrypick.g.dart';
|
||||||
|
|
||||||
|
@module()
|
||||||
|
abstract class TestModule extends Module {
|
||||||
|
@instance()
|
||||||
|
@named('testName')
|
||||||
|
String testString() => "Hello World";
|
||||||
|
}
|
||||||
|
''';
|
||||||
|
|
||||||
|
const expectedOutput = '''
|
||||||
|
// dart format width=80
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'test_module.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// ModuleGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
final class \$TestModule extends TestModule {
|
||||||
|
@override
|
||||||
|
void builder(Scope currentScope) {
|
||||||
|
bind<String>().toInstance(testString()).withName('testName');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
''';
|
||||||
|
|
||||||
|
await _testGeneration(input, expectedOutput);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should generate named singleton binding', () async {
|
||||||
|
const input = '''
|
||||||
|
import 'package:cherrypick_annotations/cherrypick_annotations.dart';
|
||||||
|
import 'package:cherrypick/cherrypick.dart';
|
||||||
|
|
||||||
|
part 'test_module.module.cherrypick.g.dart';
|
||||||
|
|
||||||
|
@module()
|
||||||
|
abstract class TestModule extends Module {
|
||||||
|
@provide()
|
||||||
|
@singleton()
|
||||||
|
@named('testName')
|
||||||
|
String testString() => "Hello World";
|
||||||
|
}
|
||||||
|
''';
|
||||||
|
|
||||||
|
const expectedOutput = '''
|
||||||
|
// dart format width=80
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'test_module.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// ModuleGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
final class \$TestModule extends TestModule {
|
||||||
|
@override
|
||||||
|
void builder(Scope currentScope) {
|
||||||
|
bind<String>()
|
||||||
|
.toProvide(() => testString())
|
||||||
|
.withName('testName')
|
||||||
|
.singleton();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
''';
|
||||||
|
|
||||||
|
await _testGeneration(input, expectedOutput);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('Async Bindings', () {
|
||||||
|
test('should generate async instance binding', () async {
|
||||||
|
const input = '''
|
||||||
|
import 'package:cherrypick_annotations/cherrypick_annotations.dart';
|
||||||
|
import 'package:cherrypick/cherrypick.dart';
|
||||||
|
|
||||||
|
part 'test_module.module.cherrypick.g.dart';
|
||||||
|
|
||||||
|
@module()
|
||||||
|
abstract class TestModule extends Module {
|
||||||
|
@instance()
|
||||||
|
Future<String> testString() async => "Hello World";
|
||||||
|
}
|
||||||
|
''';
|
||||||
|
|
||||||
|
const expectedOutput = '''
|
||||||
|
// dart format width=80
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'test_module.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// ModuleGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
final class \$TestModule extends TestModule {
|
||||||
|
@override
|
||||||
|
void builder(Scope currentScope) {
|
||||||
|
bind<String>().toInstanceAsync(testString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
''';
|
||||||
|
|
||||||
|
await _testGeneration(input, expectedOutput);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should generate async provide binding', () async {
|
||||||
|
const input = '''
|
||||||
|
import 'package:cherrypick_annotations/cherrypick_annotations.dart';
|
||||||
|
import 'package:cherrypick/cherrypick.dart';
|
||||||
|
|
||||||
|
part 'test_module.module.cherrypick.g.dart';
|
||||||
|
|
||||||
|
@module()
|
||||||
|
abstract class TestModule extends Module {
|
||||||
|
@provide()
|
||||||
|
Future<String> testString() async => "Hello World";
|
||||||
|
}
|
||||||
|
''';
|
||||||
|
|
||||||
|
const expectedOutput = '''
|
||||||
|
// dart format width=80
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'test_module.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// ModuleGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
final class \$TestModule extends TestModule {
|
||||||
|
@override
|
||||||
|
void builder(Scope currentScope) {
|
||||||
|
bind<String>().toProvideAsync(() => testString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
''';
|
||||||
|
|
||||||
|
await _testGeneration(input, expectedOutput);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should generate async binding with params', () async {
|
||||||
|
const input = '''
|
||||||
|
import 'package:cherrypick_annotations/cherrypick_annotations.dart';
|
||||||
|
import 'package:cherrypick/cherrypick.dart';
|
||||||
|
|
||||||
|
part 'test_module.module.cherrypick.g.dart';
|
||||||
|
|
||||||
|
@module()
|
||||||
|
abstract class TestModule extends Module {
|
||||||
|
@provide()
|
||||||
|
Future<String> testString(@params() dynamic params) async => "Hello \$params";
|
||||||
|
}
|
||||||
|
''';
|
||||||
|
|
||||||
|
const expectedOutput = '''
|
||||||
|
// dart format width=80
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'test_module.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// ModuleGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
final class \$TestModule extends TestModule {
|
||||||
|
@override
|
||||||
|
void builder(Scope currentScope) {
|
||||||
|
bind<String>().toProvideAsyncWithParams((args) => testString(args));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
''';
|
||||||
|
|
||||||
|
await _testGeneration(input, expectedOutput);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('Dependencies Injection', () {
|
||||||
|
test('should generate binding with injected dependencies', () async {
|
||||||
|
const input = '''
|
||||||
|
import 'package:cherrypick_annotations/cherrypick_annotations.dart';
|
||||||
|
import 'package:cherrypick/cherrypick.dart';
|
||||||
|
|
||||||
|
part 'test_module.module.cherrypick.g.dart';
|
||||||
|
|
||||||
|
class ApiClient {}
|
||||||
|
class Repository {}
|
||||||
|
|
||||||
|
@module()
|
||||||
|
abstract class TestModule extends Module {
|
||||||
|
@provide()
|
||||||
|
Repository repository(ApiClient client) => Repository();
|
||||||
|
}
|
||||||
|
''';
|
||||||
|
|
||||||
|
const expectedOutput = '''
|
||||||
|
// dart format width=80
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'test_module.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// ModuleGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
final class \$TestModule extends TestModule {
|
||||||
|
@override
|
||||||
|
void builder(Scope currentScope) {
|
||||||
|
bind<Repository>().toProvide(
|
||||||
|
() => repository(currentScope.resolve<ApiClient>()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
''';
|
||||||
|
|
||||||
|
await _testGeneration(input, expectedOutput);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should generate binding with named dependencies', () async {
|
||||||
|
const input = '''
|
||||||
|
import 'package:cherrypick_annotations/cherrypick_annotations.dart';
|
||||||
|
import 'package:cherrypick/cherrypick.dart';
|
||||||
|
|
||||||
|
part 'test_module.module.cherrypick.g.dart';
|
||||||
|
|
||||||
|
class ApiClient {}
|
||||||
|
class Repository {}
|
||||||
|
|
||||||
|
@module()
|
||||||
|
abstract class TestModule extends Module {
|
||||||
|
@provide()
|
||||||
|
Repository repository(@named('api') ApiClient client) => Repository();
|
||||||
|
}
|
||||||
|
''';
|
||||||
|
|
||||||
|
const expectedOutput = '''
|
||||||
|
// dart format width=80
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'test_module.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// ModuleGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
final class \$TestModule extends TestModule {
|
||||||
|
@override
|
||||||
|
void builder(Scope currentScope) {
|
||||||
|
bind<Repository>().toProvide(
|
||||||
|
() => repository(currentScope.resolve<ApiClient>(named: 'api')),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
''';
|
||||||
|
|
||||||
|
await _testGeneration(input, expectedOutput);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('Runtime Parameters', () {
|
||||||
|
test('should generate binding with params', () async {
|
||||||
|
const input = '''
|
||||||
|
import 'package:cherrypick_annotations/cherrypick_annotations.dart';
|
||||||
|
import 'package:cherrypick/cherrypick.dart';
|
||||||
|
|
||||||
|
part 'test_module.module.cherrypick.g.dart';
|
||||||
|
|
||||||
|
@module()
|
||||||
|
abstract class TestModule extends Module {
|
||||||
|
@provide()
|
||||||
|
String testString(@params() dynamic params) => "Hello \$params";
|
||||||
|
}
|
||||||
|
''';
|
||||||
|
|
||||||
|
const expectedOutput = '''
|
||||||
|
// dart format width=80
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'test_module.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// ModuleGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
final class \$TestModule extends TestModule {
|
||||||
|
@override
|
||||||
|
void builder(Scope currentScope) {
|
||||||
|
bind<String>().toProvideWithParams((args) => testString(args));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
''';
|
||||||
|
|
||||||
|
await _testGeneration(input, expectedOutput);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should generate async binding with params', () async {
|
||||||
|
const input = '''
|
||||||
|
import 'package:cherrypick_annotations/cherrypick_annotations.dart';
|
||||||
|
import 'package:cherrypick/cherrypick.dart';
|
||||||
|
|
||||||
|
part 'test_module.module.cherrypick.g.dart';
|
||||||
|
|
||||||
|
@module()
|
||||||
|
abstract class TestModule extends Module {
|
||||||
|
@provide()
|
||||||
|
Future<String> testString(@params() dynamic params) async => "Hello \$params";
|
||||||
|
}
|
||||||
|
''';
|
||||||
|
|
||||||
|
const expectedOutput = '''
|
||||||
|
// dart format width=80
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'test_module.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// ModuleGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
final class \$TestModule extends TestModule {
|
||||||
|
@override
|
||||||
|
void builder(Scope currentScope) {
|
||||||
|
bind<String>().toProvideAsyncWithParams((args) => testString(args));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
''';
|
||||||
|
|
||||||
|
await _testGeneration(input, expectedOutput);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('Complex Scenarios', () {
|
||||||
|
test('should generate module with multiple bindings', () async {
|
||||||
|
const input = '''
|
||||||
|
import 'package:cherrypick_annotations/cherrypick_annotations.dart';
|
||||||
|
import 'package:cherrypick/cherrypick.dart';
|
||||||
|
|
||||||
|
part 'test_module.module.cherrypick.g.dart';
|
||||||
|
|
||||||
|
class ApiClient {}
|
||||||
|
class Repository {}
|
||||||
|
|
||||||
|
@module()
|
||||||
|
abstract class TestModule extends Module {
|
||||||
|
@instance()
|
||||||
|
@singleton()
|
||||||
|
@named('baseUrl')
|
||||||
|
String baseUrl() => "https://api.example.com";
|
||||||
|
|
||||||
|
@provide()
|
||||||
|
@singleton()
|
||||||
|
ApiClient apiClient(@named('baseUrl') String url) => ApiClient();
|
||||||
|
|
||||||
|
@provide()
|
||||||
|
Repository repository(ApiClient client) => Repository();
|
||||||
|
|
||||||
|
@provide()
|
||||||
|
@named('greeting')
|
||||||
|
String greeting(@params() dynamic name) => "Hello \$name";
|
||||||
|
}
|
||||||
|
''';
|
||||||
|
|
||||||
|
const expectedOutput = '''
|
||||||
|
// dart format width=80
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'test_module.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// ModuleGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
final class \$TestModule extends TestModule {
|
||||||
|
@override
|
||||||
|
void builder(Scope currentScope) {
|
||||||
|
bind<String>().toInstance(baseUrl()).withName('baseUrl').singleton();
|
||||||
|
bind<ApiClient>()
|
||||||
|
.toProvide(
|
||||||
|
() => apiClient(currentScope.resolve<String>(named: 'baseUrl')),
|
||||||
|
)
|
||||||
|
.singleton();
|
||||||
|
bind<Repository>().toProvide(
|
||||||
|
() => repository(currentScope.resolve<ApiClient>()),
|
||||||
|
);
|
||||||
|
bind<String>()
|
||||||
|
.toProvideWithParams((args) => greeting(args))
|
||||||
|
.withName('greeting');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
''';
|
||||||
|
|
||||||
|
await _testGeneration(input, expectedOutput);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('Error Cases', () {
|
||||||
|
test('should throw error for non-class element', () async {
|
||||||
|
const input = '''
|
||||||
|
import 'package:cherrypick_annotations/cherrypick_annotations.dart';
|
||||||
|
|
||||||
|
part 'test_module.module.cherrypick.g.dart';
|
||||||
|
|
||||||
|
@module()
|
||||||
|
void notAClass() {}
|
||||||
|
''';
|
||||||
|
|
||||||
|
await expectLater(
|
||||||
|
() => _testGeneration(input, ''),
|
||||||
|
throwsA(isA<InvalidGenerationSourceError>()),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should throw error for method without @instance or @provide',
|
||||||
|
() async {
|
||||||
|
const input = '''
|
||||||
|
import 'package:cherrypick_annotations/cherrypick_annotations.dart';
|
||||||
|
import 'package:cherrypick/cherrypick.dart';
|
||||||
|
|
||||||
|
part 'test_module.module.cherrypick.g.dart';
|
||||||
|
|
||||||
|
@module()
|
||||||
|
abstract class TestModule extends Module {
|
||||||
|
String testString() => "Hello World";
|
||||||
|
}
|
||||||
|
''';
|
||||||
|
|
||||||
|
await expectLater(
|
||||||
|
() => _testGeneration(input, ''),
|
||||||
|
throwsA(isA<InvalidGenerationSourceError>()),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should throw error for @params with @instance', () async {
|
||||||
|
const input = '''
|
||||||
|
import 'package:cherrypick_annotations/cherrypick_annotations.dart';
|
||||||
|
import 'package:cherrypick/cherrypick.dart';
|
||||||
|
|
||||||
|
part 'test_module.module.cherrypick.g.dart';
|
||||||
|
|
||||||
|
@module()
|
||||||
|
abstract class TestModule extends Module {
|
||||||
|
@instance()
|
||||||
|
String testString(@params() dynamic params) => "Hello \$params";
|
||||||
|
}
|
||||||
|
''';
|
||||||
|
|
||||||
|
await expectLater(
|
||||||
|
() => _testGeneration(input, ''),
|
||||||
|
throwsA(isA<InvalidGenerationSourceError>()),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper function to test code generation
|
||||||
|
Future<void> _testGeneration(String input, String expectedOutput) async {
|
||||||
|
await testBuilder(
|
||||||
|
moduleBuilder(BuilderOptions.empty),
|
||||||
|
{
|
||||||
|
'a|lib/test_module.dart': input,
|
||||||
|
},
|
||||||
|
outputs: {
|
||||||
|
'a|lib/test_module.module.cherrypick.g.dart': expectedOutput,
|
||||||
|
},
|
||||||
|
reader: await PackageAssetReader.currentIsolate(),
|
||||||
|
);
|
||||||
|
}
|
||||||
176
cherrypick_generator/test/simple_test.dart
Normal file
176
cherrypick_generator/test/simple_test.dart
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
//
|
||||||
|
// 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:cherrypick_generator/src/bind_spec.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('Simple Generator Tests', () {
|
||||||
|
group('BindSpec', () {
|
||||||
|
test('should create BindSpec with correct properties', () {
|
||||||
|
final bindSpec = BindSpec(
|
||||||
|
returnType: 'String',
|
||||||
|
methodName: 'getString',
|
||||||
|
isSingleton: false,
|
||||||
|
parameters: [],
|
||||||
|
bindingType: BindingType.instance,
|
||||||
|
isAsyncInstance: false,
|
||||||
|
isAsyncProvide: false,
|
||||||
|
hasParams: false,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(bindSpec.returnType, equals('String'));
|
||||||
|
expect(bindSpec.methodName, equals('getString'));
|
||||||
|
expect(bindSpec.isSingleton, isFalse);
|
||||||
|
expect(bindSpec.bindingType, equals(BindingType.instance));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should generate basic bind code', () {
|
||||||
|
final bindSpec = BindSpec(
|
||||||
|
returnType: 'String',
|
||||||
|
methodName: 'getString',
|
||||||
|
isSingleton: false,
|
||||||
|
parameters: [],
|
||||||
|
bindingType: BindingType.instance,
|
||||||
|
isAsyncInstance: false,
|
||||||
|
isAsyncProvide: false,
|
||||||
|
hasParams: false,
|
||||||
|
);
|
||||||
|
|
||||||
|
final result = bindSpec.generateBind(4);
|
||||||
|
expect(result, contains('bind<String>()'));
|
||||||
|
expect(result, contains('toInstance'));
|
||||||
|
expect(result, contains('getString'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should generate singleton bind code', () {
|
||||||
|
final bindSpec = BindSpec(
|
||||||
|
returnType: 'String',
|
||||||
|
methodName: 'getString',
|
||||||
|
isSingleton: true,
|
||||||
|
parameters: [],
|
||||||
|
bindingType: BindingType.instance,
|
||||||
|
isAsyncInstance: false,
|
||||||
|
isAsyncProvide: false,
|
||||||
|
hasParams: false,
|
||||||
|
);
|
||||||
|
|
||||||
|
final result = bindSpec.generateBind(4);
|
||||||
|
expect(result, contains('singleton()'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should generate named bind code', () {
|
||||||
|
final bindSpec = BindSpec(
|
||||||
|
returnType: 'String',
|
||||||
|
methodName: 'getString',
|
||||||
|
isSingleton: false,
|
||||||
|
named: 'testName',
|
||||||
|
parameters: [],
|
||||||
|
bindingType: BindingType.instance,
|
||||||
|
isAsyncInstance: false,
|
||||||
|
isAsyncProvide: false,
|
||||||
|
hasParams: false,
|
||||||
|
);
|
||||||
|
|
||||||
|
final result = bindSpec.generateBind(4);
|
||||||
|
expect(result, contains("withName('testName')"));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should generate provide bind code', () {
|
||||||
|
final bindSpec = BindSpec(
|
||||||
|
returnType: 'String',
|
||||||
|
methodName: 'getString',
|
||||||
|
isSingleton: false,
|
||||||
|
parameters: [],
|
||||||
|
bindingType: BindingType.provide,
|
||||||
|
isAsyncInstance: false,
|
||||||
|
isAsyncProvide: false,
|
||||||
|
hasParams: false,
|
||||||
|
);
|
||||||
|
|
||||||
|
final result = bindSpec.generateBind(4);
|
||||||
|
expect(result, contains('toProvide'));
|
||||||
|
expect(result, contains('() => getString'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should generate async provide bind code', () {
|
||||||
|
final bindSpec = BindSpec(
|
||||||
|
returnType: 'String',
|
||||||
|
methodName: 'getString',
|
||||||
|
isSingleton: false,
|
||||||
|
parameters: [],
|
||||||
|
bindingType: BindingType.provide,
|
||||||
|
isAsyncInstance: false,
|
||||||
|
isAsyncProvide: true,
|
||||||
|
hasParams: false,
|
||||||
|
);
|
||||||
|
|
||||||
|
final result = bindSpec.generateBind(4);
|
||||||
|
expect(result, contains('toProvideAsync'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should generate params bind code', () {
|
||||||
|
final bindSpec = BindSpec(
|
||||||
|
returnType: 'String',
|
||||||
|
methodName: 'getString',
|
||||||
|
isSingleton: false,
|
||||||
|
parameters: [],
|
||||||
|
bindingType: BindingType.provide,
|
||||||
|
isAsyncInstance: false,
|
||||||
|
isAsyncProvide: false,
|
||||||
|
hasParams: true,
|
||||||
|
);
|
||||||
|
|
||||||
|
final result = bindSpec.generateBind(4);
|
||||||
|
expect(result, contains('toProvideWithParams'));
|
||||||
|
expect(result, contains('(args) => getString()'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should generate complex bind with all options', () {
|
||||||
|
final bindSpec = BindSpec(
|
||||||
|
returnType: 'ApiClient',
|
||||||
|
methodName: 'createApiClient',
|
||||||
|
isSingleton: true,
|
||||||
|
named: 'mainApi',
|
||||||
|
parameters: [],
|
||||||
|
bindingType: BindingType.provide,
|
||||||
|
isAsyncInstance: false,
|
||||||
|
isAsyncProvide: true,
|
||||||
|
hasParams: false,
|
||||||
|
);
|
||||||
|
|
||||||
|
final result = bindSpec.generateBind(4);
|
||||||
|
expect(result, contains('bind<ApiClient>()'));
|
||||||
|
expect(result, contains('toProvideAsync'));
|
||||||
|
expect(result, contains("withName('mainApi')"));
|
||||||
|
expect(result, contains('singleton()'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('BindingType Enum', () {
|
||||||
|
test('should have correct values', () {
|
||||||
|
expect(BindingType.instance, isNotNull);
|
||||||
|
expect(BindingType.provide, isNotNull);
|
||||||
|
expect(BindingType.values.length, equals(2));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('Generator Classes', () {
|
||||||
|
test('should be able to import generators', () {
|
||||||
|
// Test that we can import the generator classes
|
||||||
|
expect(BindSpec, isNotNull);
|
||||||
|
expect(BindingType, isNotNull);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
235
cherrypick_generator/test/type_parser_test.dart
Normal file
235
cherrypick_generator/test/type_parser_test.dart
Normal file
@@ -0,0 +1,235 @@
|
|||||||
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
import 'package:analyzer/dart/element/element.dart';
|
||||||
|
import 'package:analyzer/source/source.dart';
|
||||||
|
import 'package:cherrypick_generator/src/type_parser.dart';
|
||||||
|
import 'package:cherrypick_generator/src/exceptions.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('TypeParser', () {
|
||||||
|
group('parseType', () {
|
||||||
|
test('should parse simple types correctly', () {
|
||||||
|
// This would require setting up analyzer infrastructure
|
||||||
|
// For now, we'll test the ParsedType class directly
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should parse Future types correctly', () {
|
||||||
|
// This would require setting up analyzer infrastructure
|
||||||
|
// For now, we'll test the ParsedType class directly
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should parse nullable types correctly', () {
|
||||||
|
// This would require setting up analyzer infrastructure
|
||||||
|
// For now, we'll test the ParsedType class directly
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should throw TypeParsingException for invalid types', () {
|
||||||
|
// This would require setting up analyzer infrastructure
|
||||||
|
// For now, we'll test the ParsedType class directly
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('validateInjectableType', () {
|
||||||
|
test('should throw for void type', () {
|
||||||
|
final parsedType = ParsedType(
|
||||||
|
displayString: 'void',
|
||||||
|
coreType: 'void',
|
||||||
|
isNullable: false,
|
||||||
|
isFuture: false,
|
||||||
|
isGeneric: false,
|
||||||
|
typeArguments: [],
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
() => TypeParser.validateInjectableType(
|
||||||
|
parsedType, _createMockElement()),
|
||||||
|
throwsA(isA<TypeParsingException>()),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should throw for dynamic type', () {
|
||||||
|
final parsedType = ParsedType(
|
||||||
|
displayString: 'dynamic',
|
||||||
|
coreType: 'dynamic',
|
||||||
|
isNullable: false,
|
||||||
|
isFuture: false,
|
||||||
|
isGeneric: false,
|
||||||
|
typeArguments: [],
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
() => TypeParser.validateInjectableType(
|
||||||
|
parsedType, _createMockElement()),
|
||||||
|
throwsA(isA<TypeParsingException>()),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should pass for valid types', () {
|
||||||
|
final parsedType = ParsedType(
|
||||||
|
displayString: 'String',
|
||||||
|
coreType: 'String',
|
||||||
|
isNullable: false,
|
||||||
|
isFuture: false,
|
||||||
|
isGeneric: false,
|
||||||
|
typeArguments: [],
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
() => TypeParser.validateInjectableType(
|
||||||
|
parsedType, _createMockElement()),
|
||||||
|
returnsNormally,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('ParsedType', () {
|
||||||
|
test('should return correct codeGenType for simple types', () {
|
||||||
|
final parsedType = ParsedType(
|
||||||
|
displayString: 'String',
|
||||||
|
coreType: 'String',
|
||||||
|
isNullable: false,
|
||||||
|
isFuture: false,
|
||||||
|
isGeneric: false,
|
||||||
|
typeArguments: [],
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(parsedType.codeGenType, equals('String'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should return correct codeGenType for Future types', () {
|
||||||
|
final innerType = ParsedType(
|
||||||
|
displayString: 'String',
|
||||||
|
coreType: 'String',
|
||||||
|
isNullable: false,
|
||||||
|
isFuture: false,
|
||||||
|
isGeneric: false,
|
||||||
|
typeArguments: [],
|
||||||
|
);
|
||||||
|
|
||||||
|
final parsedType = ParsedType(
|
||||||
|
displayString: 'Future<String>',
|
||||||
|
coreType: 'Future<String>',
|
||||||
|
isNullable: false,
|
||||||
|
isFuture: true,
|
||||||
|
isGeneric: false,
|
||||||
|
typeArguments: [],
|
||||||
|
innerType: innerType,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(parsedType.codeGenType, equals('String'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should return correct resolveMethodName for sync types', () {
|
||||||
|
final parsedType = ParsedType(
|
||||||
|
displayString: 'String',
|
||||||
|
coreType: 'String',
|
||||||
|
isNullable: false,
|
||||||
|
isFuture: false,
|
||||||
|
isGeneric: false,
|
||||||
|
typeArguments: [],
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(parsedType.resolveMethodName, equals('resolve'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should return correct resolveMethodName for nullable sync types', () {
|
||||||
|
final parsedType = ParsedType(
|
||||||
|
displayString: 'String?',
|
||||||
|
coreType: 'String',
|
||||||
|
isNullable: true,
|
||||||
|
isFuture: false,
|
||||||
|
isGeneric: false,
|
||||||
|
typeArguments: [],
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(parsedType.resolveMethodName, equals('tryResolve'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should return correct resolveMethodName for async types', () {
|
||||||
|
final parsedType = ParsedType(
|
||||||
|
displayString: 'Future<String>',
|
||||||
|
coreType: 'String',
|
||||||
|
isNullable: false,
|
||||||
|
isFuture: true,
|
||||||
|
isGeneric: false,
|
||||||
|
typeArguments: [],
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(parsedType.resolveMethodName, equals('resolveAsync'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should return correct resolveMethodName for nullable async types',
|
||||||
|
() {
|
||||||
|
final parsedType = ParsedType(
|
||||||
|
displayString: 'Future<String?>',
|
||||||
|
coreType: 'String',
|
||||||
|
isNullable: true,
|
||||||
|
isFuture: true,
|
||||||
|
isGeneric: false,
|
||||||
|
typeArguments: [],
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(parsedType.resolveMethodName, equals('tryResolveAsync'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should implement equality correctly', () {
|
||||||
|
final parsedType1 = ParsedType(
|
||||||
|
displayString: 'String',
|
||||||
|
coreType: 'String',
|
||||||
|
isNullable: false,
|
||||||
|
isFuture: false,
|
||||||
|
isGeneric: false,
|
||||||
|
typeArguments: [],
|
||||||
|
);
|
||||||
|
|
||||||
|
final parsedType2 = ParsedType(
|
||||||
|
displayString: 'String',
|
||||||
|
coreType: 'String',
|
||||||
|
isNullable: false,
|
||||||
|
isFuture: false,
|
||||||
|
isGeneric: false,
|
||||||
|
typeArguments: [],
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(parsedType1, equals(parsedType2));
|
||||||
|
expect(parsedType1.hashCode, equals(parsedType2.hashCode));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should implement toString correctly', () {
|
||||||
|
final parsedType = ParsedType(
|
||||||
|
displayString: 'String',
|
||||||
|
coreType: 'String',
|
||||||
|
isNullable: false,
|
||||||
|
isFuture: false,
|
||||||
|
isGeneric: false,
|
||||||
|
typeArguments: [],
|
||||||
|
);
|
||||||
|
|
||||||
|
final result = parsedType.toString();
|
||||||
|
expect(result, contains('ParsedType'));
|
||||||
|
expect(result, contains('String'));
|
||||||
|
expect(result, contains('isNullable: false'));
|
||||||
|
expect(result, contains('isFuture: false'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mock element for testing
|
||||||
|
Element _createMockElement() {
|
||||||
|
return _MockElement();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MockElement implements Element {
|
||||||
|
@override
|
||||||
|
String get displayName => 'MockElement';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get name => 'MockElement';
|
||||||
|
|
||||||
|
@override
|
||||||
|
Source? get source => null;
|
||||||
|
|
||||||
|
@override
|
||||||
|
noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
|
||||||
|
}
|
||||||
140
doc/annotations_en.md
Normal file
140
doc/annotations_en.md
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
# DI Code Generation with Annotations (CherryPick)
|
||||||
|
|
||||||
|
CherryPick enables smart, fully-automated dependency injection (DI) for Dart/Flutter via annotations and code generation.
|
||||||
|
This eliminates boilerplate and guarantees correctness—just annotate, run the generator, and use!
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. How does it work?
|
||||||
|
|
||||||
|
You annotate classes, fields, and modules using [cherrypick_annotations].
|
||||||
|
The [cherrypick_generator] processes these, generating code that registers your dependencies and wires up fields or modules.
|
||||||
|
|
||||||
|
You then run:
|
||||||
|
```sh
|
||||||
|
dart run build_runner build --delete-conflicting-outputs
|
||||||
|
```
|
||||||
|
— and use the generated files in your app.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Supported Annotations
|
||||||
|
|
||||||
|
| Annotation | Where | Purpose |
|
||||||
|
|-------------------|-----------------|----------------------------------------------------------|
|
||||||
|
| `@injectable()` | class | Enables auto field injection; mixin will be generated |
|
||||||
|
| `@inject()` | field | Field will be injected automatically |
|
||||||
|
| `@scope()` | field/param | Use a named scope when resolving this dep |
|
||||||
|
| `@named()` | field/param | Bind/resolve a named interface implementation |
|
||||||
|
| `@module()` | class | Marks as a DI module (methods = providers) |
|
||||||
|
| `@provide` | method | Registers a type via this provider method |
|
||||||
|
| `@instance` | method | Registers a direct instance (like singleton/factory) |
|
||||||
|
| `@singleton` | method/class | The target is a singleton |
|
||||||
|
| `@params` | param | Accepts runtime/constructor params for providers |
|
||||||
|
|
||||||
|
**You can combine annotations as needed for advanced use-cases.**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Practical Examples
|
||||||
|
|
||||||
|
### A. Field Injection (recommended for widgets/classes)
|
||||||
|
|
||||||
|
```dart
|
||||||
|
import 'package:cherrypick_annotations/cherrypick_annotations.dart';
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
class MyWidget with _$MyWidget { // the generated mixin
|
||||||
|
@inject()
|
||||||
|
late final AuthService auth;
|
||||||
|
|
||||||
|
@inject()
|
||||||
|
@scope('profile')
|
||||||
|
late final ProfileManager profile;
|
||||||
|
|
||||||
|
@inject()
|
||||||
|
@named('special')
|
||||||
|
late final ApiClient specialApi;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- After running build_runner, the mixin _$MyWidget is created.
|
||||||
|
- Call `MyWidget().injectFields();` (method name may be `_inject` or similar) to populate the fields!
|
||||||
|
|
||||||
|
### B. Module Binding (recommended for global app services)
|
||||||
|
|
||||||
|
```dart
|
||||||
|
@module()
|
||||||
|
abstract class AppModule extends Module {
|
||||||
|
@singleton
|
||||||
|
AuthService provideAuth(Api api) => AuthService(api);
|
||||||
|
|
||||||
|
@provide
|
||||||
|
@named('logging')
|
||||||
|
Future<Logger> provideLogger(@params Map<String, dynamic> args) async => ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- Providers can return async(`Future<T>`) or sync.
|
||||||
|
- `@singleton` = one instance per scope.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Using the Generated Code
|
||||||
|
|
||||||
|
1. Add to your `pubspec.yaml`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
dependencies:
|
||||||
|
cherrypick: any
|
||||||
|
cherrypick_annotations: any
|
||||||
|
|
||||||
|
dev_dependencies:
|
||||||
|
cherrypick_generator: any
|
||||||
|
build_runner: any
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Import generated files (e.g. `app_module.module.cherrypick.g.dart`, `your_class.inject.cherrypick.g.dart`).
|
||||||
|
|
||||||
|
3. Register modules:
|
||||||
|
|
||||||
|
```dart
|
||||||
|
final scope = openRootScope()
|
||||||
|
..installModules([$AppModule()]);
|
||||||
|
```
|
||||||
|
|
||||||
|
4. For classes with auto-injected fields, mix in the generated mixin and call the injector:
|
||||||
|
|
||||||
|
```dart
|
||||||
|
final widget = MyWidget();
|
||||||
|
widget.injectFields(); // or use the mixin's helper
|
||||||
|
```
|
||||||
|
|
||||||
|
5. All dependencies are now available and ready to use!
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Advanced Features
|
||||||
|
|
||||||
|
- **Named and Scoped dependencies:** use `@named`, `@scope` on fields/methods and in resolve().
|
||||||
|
- **Async support:** Providers or injected fields can be Future<T> (resolveAsync).
|
||||||
|
- **Runtime parameters:** Decorate a parameter with `@params`, and use `resolve<T>(params: ...)`.
|
||||||
|
- **Combining strategies:** Mix field injection (`@injectable`) and module/provider (`@module` + methods) in one app.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Troubleshooting
|
||||||
|
|
||||||
|
- Make sure all dependencies are annotated, imports are correct, and run `build_runner` on every code/DI change.
|
||||||
|
- Errors in annotation usage (e.g. `@singleton` on non-class/method) will be shown at build time.
|
||||||
|
- Use the `.g.dart` files directly—do not edit them by hand.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. References
|
||||||
|
|
||||||
|
- [Cherrypick Generator README (extended)](../cherrypick_generator/README.md)
|
||||||
|
- Example: `examples/postly`
|
||||||
|
- [API Reference](../cherrypick/doc/api/)
|
||||||
|
|
||||||
|
---
|
||||||
137
doc/annotations_ru.md
Normal file
137
doc/annotations_ru.md
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
# Генерация DI-кода через аннотации (CherryPick)
|
||||||
|
|
||||||
|
CherryPick позволяет получить умный и полностью автоматизированный DI для Dart/Flutter на основе аннотаций и генерации кода.
|
||||||
|
Это убирает boilerplate — просто ставьте аннотации, запускайте генератор и используйте результат!
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Как это работает?
|
||||||
|
|
||||||
|
Вы размечаете классы, поля и модули с помощью [cherrypick_annotations].
|
||||||
|
[cherrypick_generator] анализирует их и создаёт код для регистрации зависимостей и подстановки полей или модулей.
|
||||||
|
|
||||||
|
Далее — запускайте:
|
||||||
|
```sh
|
||||||
|
dart run build_runner build --delete-conflicting-outputs
|
||||||
|
```
|
||||||
|
— и используйте сгенерированные файлы в проекте.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Поддерживаемые аннотации
|
||||||
|
|
||||||
|
| Аннотация | Где применить | Значение |
|
||||||
|
|--------------------|------------------|------------------------------------------------------------|
|
||||||
|
| `@injectable()` | класс | Включает автоподстановку полей, генерируется mixin |
|
||||||
|
| `@inject()` | поле | Поле будет автоматически подставлено DI |
|
||||||
|
| `@scope()` | поле/параметр | Использовать определённый scope при разрешении |
|
||||||
|
| `@named()` | поле/параметр | Именованный биндинг для интерфейсов/реализаций |
|
||||||
|
| `@module()` | класс | Класс как DI-модуль (методы — провайдеры) |
|
||||||
|
| `@provide` | метод | Регистрирует тип через этот метод-провайдер |
|
||||||
|
| `@instance` | метод | Регистрирует как прямой инстанс (singleton/factory, как есть)|
|
||||||
|
| `@singleton` | метод/класс | Синглтон (один экземпляр на scope) |
|
||||||
|
| `@params` | параметр | Пробрасывает параметры рантайм/конструктора в DI |
|
||||||
|
|
||||||
|
Миксуйте аннотации для сложных сценариев!
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Примеры использования
|
||||||
|
|
||||||
|
### A. Field Injection (рекомендуется для виджетов/классов)
|
||||||
|
|
||||||
|
```dart
|
||||||
|
import 'package:cherrypick_annotations/cherrypick_annotations.dart';
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
class MyWidget with _$MyWidget {
|
||||||
|
@inject()
|
||||||
|
late final AuthService auth;
|
||||||
|
|
||||||
|
@inject()
|
||||||
|
@scope('profile')
|
||||||
|
late final ProfileManager profile;
|
||||||
|
|
||||||
|
@inject()
|
||||||
|
@named('special')
|
||||||
|
late final ApiClient specialApi;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- После build_runner появится mixin _$MyWidget.
|
||||||
|
- Вызовите `MyWidget().injectFields();` (или соответствующий метод из mixin), чтобы заполнить поля.
|
||||||
|
|
||||||
|
### B. Binding через модуль (вариант для глобальных сервисов)
|
||||||
|
|
||||||
|
```dart
|
||||||
|
@module()
|
||||||
|
abstract class AppModule extends Module {
|
||||||
|
@singleton
|
||||||
|
AuthService provideAuth(Api api) => AuthService(api);
|
||||||
|
|
||||||
|
@provide
|
||||||
|
@named('logging')
|
||||||
|
Future<Logger> provideLogger(@params Map<String, dynamic> args) async => ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- Методы-провайдеры поддерживают async (Future<T>) и singleton.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Использование сгенерированного кода
|
||||||
|
|
||||||
|
1. В `pubspec.yaml`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
dependencies:
|
||||||
|
cherrypick: any
|
||||||
|
cherrypick_annotations: any
|
||||||
|
|
||||||
|
dev_dependencies:
|
||||||
|
cherrypick_generator: any
|
||||||
|
build_runner: any
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Импортируйте сгенерированные файлы (`app_module.module.cherrypick.g.dart`, `your_class.inject.cherrypick.g.dart`).
|
||||||
|
|
||||||
|
3. Регистрируйте модули так:
|
||||||
|
|
||||||
|
```dart
|
||||||
|
final scope = openRootScope()
|
||||||
|
..installModules([$AppModule()]);
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Для классов с автоподстановкой полей (field injection): используйте mixin и вызовите injector:
|
||||||
|
|
||||||
|
```dart
|
||||||
|
final widget = MyWidget();
|
||||||
|
widget.injectFields(); // или эквивалентный метод из mixin
|
||||||
|
```
|
||||||
|
|
||||||
|
5. Все зависимости готовы к использованию!
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Расширенные возможности
|
||||||
|
|
||||||
|
- **Именованные и scope-зависимости:** используйте `@named`, `@scope` в полях/методах/resolve.
|
||||||
|
- **Async:** Провайдеры и поля могут быть Future<T> (resolveAsync).
|
||||||
|
- **Параметры рантайм:** через `@params` прямо к провайдеру: `resolve<T>(params: ...)`.
|
||||||
|
- **Комбинированная стратегия:** можно смешивать field injection и модульные провайдеры в одном проекте.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Советы и FAQ
|
||||||
|
|
||||||
|
- Проверьте аннотации, пути import и запускайте build_runner после каждого изменения DI/кода.
|
||||||
|
- Ошибки применения аннотаций появляются на этапе генерации.
|
||||||
|
- Никогда не редактируйте .g.dart файлы вручную.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Полезные ссылки
|
||||||
|
|
||||||
|
- [README по генератору](../cherrypick_generator/README.md)
|
||||||
|
- Пример интеграции: `examples/postly`
|
||||||
|
- [API Reference](../cherrypick/doc/api/)
|
||||||
|
|
||||||
|
---
|
||||||
407
doc/full_tutorial_en.md
Normal file
407
doc/full_tutorial_en.md
Normal file
@@ -0,0 +1,407 @@
|
|||||||
|
# Full Guide to CherryPick DI for Dart and Flutter: Dependency Injection with Annotations and Automatic Code Generation
|
||||||
|
|
||||||
|
**CherryPick** is a powerful tool for dependency injection in Dart and Flutter projects. It offers a modern approach with code generation, async providers, named and parameterized bindings, and field injection using annotations.
|
||||||
|
|
||||||
|
> Tools:
|
||||||
|
> - [`cherrypick`](https://pub.dev/packages/cherrypick) — runtime DI core
|
||||||
|
> - [`cherrypick_annotations`](https://pub.dev/packages/cherrypick_annotations) — DI annotations
|
||||||
|
> - [`cherrypick_generator`](https://pub.dev/packages/cherrypick_generator) — DI code generation
|
||||||
|
>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## CherryPick advantages vs other DI frameworks
|
||||||
|
|
||||||
|
- 📦 Simple declarative API for registering and resolving dependencies
|
||||||
|
- ⚡️ Full support for both sync and async registrations
|
||||||
|
- 🧩 DI via annotations with codegen, including advanced field injection
|
||||||
|
- 🏷️ Named bindings for multiple interface implementations
|
||||||
|
- 🏭 Parameterized bindings for runtime factories (e.g., by ID)
|
||||||
|
- 🌲 Flexible scope system for dependency isolation and hierarchy
|
||||||
|
- 🕹️ Optional resolution (`tryResolve`)
|
||||||
|
- 🐞 Clear compile-time errors for invalid annotation or DI configuration
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## How CherryPick works: core concepts
|
||||||
|
|
||||||
|
### Dependency registration (bindings)
|
||||||
|
|
||||||
|
```dart
|
||||||
|
bind<MyService>().toProvide(() => MyServiceImpl());
|
||||||
|
bind<MyRepository>().toProvideAsync(() async => await initRepo());
|
||||||
|
bind<UserService>().toProvideWithParams((id) => UserService(id));
|
||||||
|
|
||||||
|
// Singleton
|
||||||
|
bind<MyApi>().toProvide(() => MyApi()).singleton();
|
||||||
|
|
||||||
|
// Register an already created object
|
||||||
|
final config = AppConfig.dev();
|
||||||
|
bind<AppConfig>().toInstance(config);
|
||||||
|
|
||||||
|
// Register an already running Future/async value
|
||||||
|
final setupFuture = loadEnvironment();
|
||||||
|
bind<Environment>().toInstanceAsync(setupFuture);
|
||||||
|
```
|
||||||
|
|
||||||
|
- **toProvide** — regular sync factory
|
||||||
|
- **toProvideAsync** — async factory (if you need to await a Future)
|
||||||
|
- **toProvideWithParams / toProvideAsyncWithParams** — factories with runtime parameters
|
||||||
|
- **toInstance** — registers an already created object as a dependency
|
||||||
|
- **toInstanceAsync** — registers an already started Future as an async dependency
|
||||||
|
|
||||||
|
### Named bindings
|
||||||
|
|
||||||
|
You can register several implementations of an interface under different names:
|
||||||
|
|
||||||
|
```dart
|
||||||
|
bind<ApiClient>().toProvide(() => ApiClientProd()).withName('prod');
|
||||||
|
bind<ApiClient>().toProvide(() => ApiClientMock()).withName('mock');
|
||||||
|
|
||||||
|
// Resolving by name:
|
||||||
|
final api = scope.resolve<ApiClient>(named: 'mock');
|
||||||
|
```
|
||||||
|
|
||||||
|
### Lifecycle: singleton
|
||||||
|
|
||||||
|
- `.singleton()` — single instance per Scope lifetime
|
||||||
|
- By default, every resolve creates a new object
|
||||||
|
|
||||||
|
### Parameterized bindings
|
||||||
|
|
||||||
|
Allows you to create dependencies with runtime parameters, e.g., a service for a user with a given ID:
|
||||||
|
|
||||||
|
```dart
|
||||||
|
bind<UserService>().toProvideWithParams((userId) => UserService(userId));
|
||||||
|
|
||||||
|
// Resolve:
|
||||||
|
final userService = scope.resolveWithParams<UserService>(params: '123');
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Scope management: dependency hierarchy
|
||||||
|
|
||||||
|
For most business cases, a single root scope is enough, but CherryPick supports nested scopes:
|
||||||
|
|
||||||
|
```dart
|
||||||
|
final rootScope = CherryPick.openRootScope();
|
||||||
|
final profileScope = rootScope.openSubScope('profile')
|
||||||
|
..installModules([ProfileModule()]);
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Subscope** can override parent dependencies.
|
||||||
|
- When resolving, first checks its own scope, then up the hierarchy.
|
||||||
|
|
||||||
|
|
||||||
|
## Managing names and scope hierarchy (subscopes) in CherryPick
|
||||||
|
|
||||||
|
CherryPick supports nested scopes, each can be "root" or a child. For accessing/managing the hierarchy, CherryPick uses scope names (strings) as well as convenient open/close methods.
|
||||||
|
|
||||||
|
### Open subScope by name
|
||||||
|
|
||||||
|
CherryPick uses separator-delimited strings to search and build scope trees, for example:
|
||||||
|
|
||||||
|
```dart
|
||||||
|
final subScope = CherryPick.openScope(scopeName: 'profile.settings');
|
||||||
|
```
|
||||||
|
|
||||||
|
- Here, `'profile.settings'` will open 'profile' subscope in root, then 'settings' subscope in 'profile'.
|
||||||
|
- Default separator is a dot (`.`), can be changed via `separator` argument.
|
||||||
|
|
||||||
|
**Example with another separator:**
|
||||||
|
|
||||||
|
```dart
|
||||||
|
final subScope = CherryPick.openScope(
|
||||||
|
scopeName: 'project>>dev>>api',
|
||||||
|
separator: '>>',
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Hierarchy & access
|
||||||
|
|
||||||
|
Each hierarchy level is a separate scope.
|
||||||
|
This is convenient for restricting/localizing dependencies, for example:
|
||||||
|
- `main.profile` — dependencies only for user profile
|
||||||
|
- `main.profile.details` — even narrower context
|
||||||
|
|
||||||
|
### Closing subscopes
|
||||||
|
|
||||||
|
To close a specific subScope, use the same path:
|
||||||
|
|
||||||
|
```dart
|
||||||
|
CherryPick.closeScope(scopeName: 'profile.settings');
|
||||||
|
```
|
||||||
|
|
||||||
|
- Closing a top-level scope (`profile`) wipes all children too.
|
||||||
|
|
||||||
|
### Methods summary
|
||||||
|
|
||||||
|
| Method | Description |
|
||||||
|
|---------------------------|--------------------------------------------------------|
|
||||||
|
| `openRootScope()` | Open/get root scope |
|
||||||
|
| `closeRootScope()` | Close root scope, remove all dependencies |
|
||||||
|
| `openScope(scopeName)` | Open scope(s) by name & hierarchy (`'a.b.c'`) |
|
||||||
|
| `closeScope(scopeName)` | Close specified scope or subScope |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Recommendations:**
|
||||||
|
Use meaningful names and dot notation for scope structuring in large apps—this improves readability and dependency management on any level.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
|
||||||
|
```dart
|
||||||
|
// Opens scopes by hierarchy: app -> module -> page
|
||||||
|
final scope = CherryPick.openScope(scopeName: 'app.module.page');
|
||||||
|
|
||||||
|
// Closes 'module' and all nested subscopes
|
||||||
|
CherryPick.closeScope(scopeName: 'app.module');
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
This lets you scale CherryPick DI for any app complexity!
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Safe dependency resolution
|
||||||
|
|
||||||
|
If not sure a dependency exists, use tryResolve/tryResolveAsync:
|
||||||
|
|
||||||
|
```dart
|
||||||
|
final service = scope.tryResolve<OptionalService>(); // returns null if not exists
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Dependency injection with annotations & code generation
|
||||||
|
|
||||||
|
CherryPick supports DI with annotations, letting you eliminate manual DI setup.
|
||||||
|
|
||||||
|
### Annotation structure
|
||||||
|
|
||||||
|
| Annotation | Purpose | Where to use |
|
||||||
|
|---------------|---------------------------|------------------------------------|
|
||||||
|
| `@module` | DI module | Classes |
|
||||||
|
| `@singleton` | Singleton | Module methods |
|
||||||
|
| `@instance` | New object | Module methods |
|
||||||
|
| `@provide` | Provider | Methods (with DI params) |
|
||||||
|
| `@named` | Named binding | Method argument/Class fields |
|
||||||
|
| `@params` | Parameter passing | Provider argument |
|
||||||
|
| `@injectable` | Field injection support | Classes |
|
||||||
|
| `@inject` | Auto-injection | Class fields |
|
||||||
|
| `@scope` | Scope/realm | Class fields |
|
||||||
|
|
||||||
|
### Example DI module
|
||||||
|
|
||||||
|
```dart
|
||||||
|
import 'package:cherrypick_annotations/cherrypick_annotations.dart';
|
||||||
|
|
||||||
|
@module()
|
||||||
|
abstract class AppModule extends Module {
|
||||||
|
@singleton()
|
||||||
|
@provide()
|
||||||
|
ApiClient apiClient() => ApiClient();
|
||||||
|
|
||||||
|
@provide()
|
||||||
|
UserService userService(ApiClient api) => UserService(api);
|
||||||
|
|
||||||
|
@singleton()
|
||||||
|
@provide()
|
||||||
|
@named('mock')
|
||||||
|
ApiClient mockApiClient() => ApiClientMock();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- Methods annotated with `@provide` become DI factories.
|
||||||
|
- Add other annotations to specify binding type or name.
|
||||||
|
|
||||||
|
Generated code will look like:
|
||||||
|
|
||||||
|
```dart
|
||||||
|
class $AppModule extends AppModule {
|
||||||
|
@override
|
||||||
|
void builder(Scope currentScope) {
|
||||||
|
bind<ApiClient>().toProvide(() => apiClient()).singleton();
|
||||||
|
bind<UserService>().toProvide(() => userService(currentScope.resolve<ApiClient>()));
|
||||||
|
bind<ApiClient>().toProvide(() => mockApiClient()).withName('mock').singleton();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Example: field injection
|
||||||
|
|
||||||
|
```dart
|
||||||
|
@injectable()
|
||||||
|
class ProfileBloc with _$ProfileBloc {
|
||||||
|
@inject()
|
||||||
|
late final AuthService auth;
|
||||||
|
|
||||||
|
@inject()
|
||||||
|
@named('admin')
|
||||||
|
late final UserService adminUser;
|
||||||
|
|
||||||
|
ProfileBloc() {
|
||||||
|
_inject(this); // injectFields — generated method
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- Generator creates a mixin (`_$ProfileBloc`) which automatically resolves and injects dependencies into fields.
|
||||||
|
- The `@named` annotation links a field to a named implementation.
|
||||||
|
|
||||||
|
Example generated code:
|
||||||
|
|
||||||
|
```dart
|
||||||
|
mixin $ProfileBloc {
|
||||||
|
@override
|
||||||
|
void _inject(ProfileBloc instance) {
|
||||||
|
instance.auth = CherryPick.openRootScope().resolve<AuthService>();
|
||||||
|
instance.adminUser = CherryPick.openRootScope().resolve<UserService>(named: 'admin');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### How to connect it
|
||||||
|
|
||||||
|
```dart
|
||||||
|
void main() async {
|
||||||
|
final scope = CherryPick.openRootScope();
|
||||||
|
scope.installModules([
|
||||||
|
$AppModule(),
|
||||||
|
]);
|
||||||
|
// DI via field injection
|
||||||
|
final bloc = ProfileBloc();
|
||||||
|
runApp(MyApp(bloc: bloc));
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Async dependencies
|
||||||
|
|
||||||
|
For async providers, use `toProvideAsync`, and resolve them with `resolveAsync`:
|
||||||
|
|
||||||
|
```dart
|
||||||
|
bind<RemoteConfig>().toProvideAsync(() async => await RemoteConfig.load());
|
||||||
|
|
||||||
|
// Usage:
|
||||||
|
final config = await scope.resolveAsync<RemoteConfig>();
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Validation and diagnostics
|
||||||
|
|
||||||
|
- If you use incorrect annotations or DI config, you'll get clear compile-time errors.
|
||||||
|
- Binding errors are found during code generation, minimizing runtime issues and speeding up development.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Flutter integration: cherrypick_flutter
|
||||||
|
|
||||||
|
### What it is
|
||||||
|
|
||||||
|
[`cherrypick_flutter`](https://pub.dev/packages/cherrypick_flutter) is the integration package for CherryPick DI in Flutter. It provides a convenient `CherryPickProvider` widget which sits in your widget tree and gives access to the root DI scope (and subscopes) from context.
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
- **Global DI Scope Access:**
|
||||||
|
Use `CherryPickProvider` to access rootScope and subscopes anywhere in the widget tree.
|
||||||
|
- **Context integration:**
|
||||||
|
Use `CherryPickProvider.of(context)` for DI access inside your widgets.
|
||||||
|
|
||||||
|
### Usage Example
|
||||||
|
|
||||||
|
```dart
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:cherrypick_flutter/cherrypick_flutter.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
runApp(
|
||||||
|
CherryPickProvider(
|
||||||
|
child: MyApp(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class MyApp extends StatelessWidget {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final rootScope = CherryPickProvider.of(context).openRootScope();
|
||||||
|
|
||||||
|
return MaterialApp(
|
||||||
|
home: Scaffold(
|
||||||
|
body: Center(
|
||||||
|
child: Text(
|
||||||
|
rootScope.resolve<AppService>().getStatus(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- Here, `CherryPickProvider` wraps the app and gives DI scope access via context.
|
||||||
|
- You can create subscopes, e.g. for screens or modules:
|
||||||
|
`final subScope = CherryPickProvider.of(context).openSubScope(scopeName: "profileFeature");`
|
||||||
|
|
||||||
|
---
|
||||||
|
## CherryPick is not just for Flutter!
|
||||||
|
|
||||||
|
You can use CherryPick in Dart CLI, server apps, and microservices. All major features work without Flutter.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## CherryPick Example Project: Step by Step
|
||||||
|
|
||||||
|
1. Add dependencies:
|
||||||
|
```yaml
|
||||||
|
dependencies:
|
||||||
|
cherrypick: ^1.0.0
|
||||||
|
cherrypick_annotations: ^1.0.0
|
||||||
|
|
||||||
|
dev_dependencies:
|
||||||
|
build_runner: ^2.0.0
|
||||||
|
cherrypick_generator: ^1.0.0
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Describe your modules using annotations.
|
||||||
|
|
||||||
|
3. To generate DI code:
|
||||||
|
```shell
|
||||||
|
dart run build_runner build --delete-conflicting-outputs
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Enjoy modern DI with no boilerplate!
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Conclusion
|
||||||
|
|
||||||
|
**CherryPick** is a modern DI solution for Dart and Flutter, combining a concise API and advanced annotation/codegen features. Scopes, parameterized providers, named bindings, and field-injection make it great for both small and large-scale projects.
|
||||||
|
|
||||||
|
**Full annotation list and their purposes:**
|
||||||
|
|
||||||
|
| Annotation | Purpose | Where to use |
|
||||||
|
|---------------|---------------------------|------------------------------------|
|
||||||
|
| `@module` | DI module | Classes |
|
||||||
|
| `@singleton` | Singleton | Module methods |
|
||||||
|
| `@instance` | New object | Module methods |
|
||||||
|
| `@provide` | Provider | Methods (with DI params) |
|
||||||
|
| `@named` | Named binding | Method argument/Class fields |
|
||||||
|
| `@params` | Parameter passing | Provider argument |
|
||||||
|
| `@injectable` | Field injection support | Classes |
|
||||||
|
| `@inject` | Auto-injection | Class fields |
|
||||||
|
| `@scope` | Scope/realm | Class fields |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Useful Links
|
||||||
|
|
||||||
|
- [cherrypick](https://pub.dev/packages/cherrypick)
|
||||||
|
- [cherrypick_annotations](https://pub.dev/packages/cherrypick_annotations)
|
||||||
|
- [cherrypick_generator](https://pub.dev/packages/cherrypick_generator)
|
||||||
|
- [Sources on GitHub](https://github.com/pese-git/cherrypick)
|
||||||
411
doc/full_tutorial_ru.md
Normal file
411
doc/full_tutorial_ru.md
Normal file
@@ -0,0 +1,411 @@
|
|||||||
|
# Полный гайд по CherryPick DI для Dart и Flutter: внедрение зависимостей с аннотациями и автоматической генерацией кода
|
||||||
|
|
||||||
|
**CherryPick** — это мощный инструмент для инъекции зависимостей в проектах на Dart и Flutter. Он предлагает современный подход с поддержкой генерации кода, асинхронных провайдеров, именованных и параметризируемых биндингов, а также field injection с использованием аннотаций.
|
||||||
|
|
||||||
|
> Инструменты:
|
||||||
|
> - [`cherrypick`](https://pub.dev/packages/cherrypick) — runtime DI core
|
||||||
|
> - [`cherrypick_annotations`](https://pub.dev/packages/cherrypick_annotations) — аннотации для DI
|
||||||
|
> - [`cherrypick_generator`](https://pub.dev/packages/cherrypick_generator) — генерация DI-кода
|
||||||
|
>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Преимущества CherryPick по сравнению с другими DI-фреймворками
|
||||||
|
|
||||||
|
- 📦 Простой декларативный API для регистрации и разрешения зависимостей.
|
||||||
|
- ⚡️ Полная поддержка синхронных _и_ асинхронных регистраций.
|
||||||
|
- 🧩 DI через аннотации с автогенерацией кода, включая field injection.
|
||||||
|
- 🏷️ Именованные зависимости (named bindings).
|
||||||
|
- 🏭 Параметризация биндингов для runtime-использования фабрик.
|
||||||
|
- 🌲 Гибкая система Scope'ов для изоляции и иерархии зависимостей.
|
||||||
|
- 🕹️ Опциональное разрешение (tryResolve).
|
||||||
|
- 🐞 Ясные compile-time ошибки при неправильной аннотации или неверном DI-описании.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Как работает CherryPick: основные концепции
|
||||||
|
|
||||||
|
### Регистрация зависимостей: биндинги
|
||||||
|
|
||||||
|
```dart
|
||||||
|
bind<MyService>().toProvide(() => MyServiceImpl());
|
||||||
|
bind<MyRepository>().toProvideAsync(() async => await initRepo());
|
||||||
|
bind<UserService>().toProvideWithParams((id) => UserService(id));
|
||||||
|
|
||||||
|
// Singleton
|
||||||
|
bind<MyApi>().toProvide(() => MyApi()).singleton();
|
||||||
|
|
||||||
|
// Зарегистрировать уже существующий объект
|
||||||
|
final config = AppConfig.dev();
|
||||||
|
bind<AppConfig>().toInstance(config);
|
||||||
|
|
||||||
|
// Зарегистрировать уже существующий Future/асинхронное значение
|
||||||
|
final setupFuture = loadEnvironment();
|
||||||
|
bind<Environment>().toInstanceAsync(setupFuture);
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
- **toProvide** — обычная синхронная фабрика.
|
||||||
|
- **toProvideAsync** — асинхронная фабрика (например, если нужно дожидаться Future).
|
||||||
|
- **toProvideWithParams / toProvideAsyncWithParams** — фабрики с параметрами.
|
||||||
|
- **toInstance** — регистрирует уже созданный экземпляр класса как зависимость.
|
||||||
|
- **toInstanceAsync** — регистрирует уже запущенный Future, как асинхронную зависимость.
|
||||||
|
|
||||||
|
### Именованные биндинги (Named)
|
||||||
|
|
||||||
|
Можно регистрировать несколько реализаций одного интерфейса под разными именами:
|
||||||
|
|
||||||
|
```dart
|
||||||
|
bind<ApiClient>().toProvide(() => ApiClientProd()).withName('prod');
|
||||||
|
bind<ApiClient>().toProvide(() => ApiClientMock()).withName('mock');
|
||||||
|
|
||||||
|
// Получение по имени:
|
||||||
|
final api = scope.resolve<ApiClient>(named: 'mock');
|
||||||
|
```
|
||||||
|
|
||||||
|
### Жизненный цикл: singleton
|
||||||
|
|
||||||
|
- `.singleton()` — один инстанс на всё время жизни Scope.
|
||||||
|
- По умолчанию каждый resolve создаёт новый объект.
|
||||||
|
|
||||||
|
### Параметрические биндинги
|
||||||
|
|
||||||
|
Позволяют создавать зависимости с runtime-параметрами — например, сервис для пользователя с ID:
|
||||||
|
|
||||||
|
```dart
|
||||||
|
bind<UserService>().toProvideWithParams((userId) => UserService(userId));
|
||||||
|
|
||||||
|
// Получение
|
||||||
|
final userService = scope.resolveWithParams<UserService>(params: '123');
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Управление Scope'ами: иерархия зависимостей
|
||||||
|
|
||||||
|
Для большинства бизнес-кейсов достаточно одного Scope (root), но CherryPick поддерживает создание вложенных Scope:
|
||||||
|
|
||||||
|
```dart
|
||||||
|
final rootScope = CherryPick.openRootScope();
|
||||||
|
final profileScope = rootScope.openSubScope('profile')
|
||||||
|
..installModules([ProfileModule()]);
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Под-скоуп** может переопределять зависимости родителя.
|
||||||
|
- При разрешении сначала проверяется свой Scope, потом иерархия вверх.
|
||||||
|
|
||||||
|
|
||||||
|
## Работа с именованием и иерархией подскоупов (subscopes) в CherryPick
|
||||||
|
|
||||||
|
CherryPick поддерживает вложенные области видимости (scopes), где каждый scope может быть как "корневым", так и дочерним. Для доступа и управления иерархией используется понятие **scope name** (имя области видимости), а также удобные методы для открытия и закрытия скопов по строковым идентификаторам.
|
||||||
|
|
||||||
|
### Открытие subScope по имени
|
||||||
|
|
||||||
|
CherryPick использует строки с разделителями для поиска и построения дерева областей видимости. Например:
|
||||||
|
|
||||||
|
```dart
|
||||||
|
final subScope = CherryPick.openScope(scopeName: 'profile.settings');
|
||||||
|
```
|
||||||
|
|
||||||
|
- Здесь `'profile.settings'` означает, что сначала откроется подскоуп `profile` у rootScope, затем — подскоуп `settings` у `profile`.
|
||||||
|
- Разделитель по умолчанию — точка (`.`). Его можно изменить, указав `separator` аргументом.
|
||||||
|
|
||||||
|
**Пример с другим разделителем:**
|
||||||
|
|
||||||
|
```dart
|
||||||
|
final subScope = CherryPick.openScope(
|
||||||
|
scopeName: 'project>>dev>>api',
|
||||||
|
separator: '>>',
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Иерархия и доступ
|
||||||
|
|
||||||
|
Каждый уровень иерархии соответствует отдельному scope.
|
||||||
|
Это удобно для ограничения и локализации зависимостей, например:
|
||||||
|
- `main.profile` — зависимости только для профиля пользователя
|
||||||
|
- `main.profile.details` — ещё более "узкая" область видимости
|
||||||
|
|
||||||
|
### Закрытие подскоупов
|
||||||
|
|
||||||
|
Чтобы закрыть конкретный subScope, используйте тот же путь:
|
||||||
|
|
||||||
|
```dart
|
||||||
|
CherryPick.closeScope(scopeName: 'profile.settings');
|
||||||
|
```
|
||||||
|
|
||||||
|
- Если закрываете верхний скоуп (`profile`), все дочерние тоже будут очищены.
|
||||||
|
|
||||||
|
### Кратко о методах
|
||||||
|
|
||||||
|
| Метод | Описание |
|
||||||
|
|--------------------------|--------------------------------------------------------|
|
||||||
|
| `openRootScope()` | Открыть/получить корневой scope |
|
||||||
|
| `closeRootScope()` | Закрыть root scope, удалить все зависимости |
|
||||||
|
| `openScope(scopeName)` | Открыть scope(ы) по имени с иерархией (`'a.b.c'`) |
|
||||||
|
| `closeScope(scopeName)` | Закрыть указанный scope или subscope |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Рекомендации:**
|
||||||
|
Используйте осмысленные имена и "точечную" нотацию для структурирования зон видимости в крупных приложениях — это повысит читаемость и позволит удобно управлять зависимостями на любых уровнях.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Пример:**
|
||||||
|
|
||||||
|
```dart
|
||||||
|
// Откроет scopes по иерархии: app -> module -> page
|
||||||
|
final scope = CherryPick.openScope(scopeName: 'app.module.page');
|
||||||
|
|
||||||
|
// Закроет 'module' и все вложенные subscopes
|
||||||
|
CherryPick.closeScope(scopeName: 'app.module');
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Это позволит масштабировать DI-подход CherryPick в приложениях любой сложности!
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Безопасное разрешение зависимостей
|
||||||
|
|
||||||
|
Если не уверены, что нужная зависимость есть, используйте tryResolve/tryResolveAsync:
|
||||||
|
|
||||||
|
```dart
|
||||||
|
final service = scope.tryResolve<OptionalService>(); // вернет null, если нет
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Внедрение зависимостей через аннотации и автогенерацию
|
||||||
|
|
||||||
|
CherryPick поддерживает DI через аннотации, что позволяет полностью избавиться от ручного внедрения зависимостей.
|
||||||
|
|
||||||
|
### Структура аннотаций
|
||||||
|
|
||||||
|
| Аннотация | Для чего | Где применяют |
|
||||||
|
| ------------- | ------------------------- | -------------------------------- |
|
||||||
|
| `@module` | DI-модуль | Классы |
|
||||||
|
| `@singleton` | Singleton | Методы класса |
|
||||||
|
| `@instance` | Новый объект | Методы класса |
|
||||||
|
| `@provide` | Провайдер | Методы (с DI params) |
|
||||||
|
| `@named` | Именованный биндинг | Аргумент метода/Аттрибуты класса |
|
||||||
|
| `@params` | Передача параметров | Аргумент провайдера |
|
||||||
|
| `@injectable` | Поддержка field injection | Классы |
|
||||||
|
| `@inject` | Автовнедрение | Аттрибуты класса |
|
||||||
|
| `@scope` | Scope/realm | Аттрибуты класса |
|
||||||
|
|
||||||
|
### Пример DI-модуля
|
||||||
|
|
||||||
|
```dart
|
||||||
|
import 'package:cherrypick_annotations/cherrypick_annotations.dart';
|
||||||
|
|
||||||
|
@module()
|
||||||
|
abstract class AppModule extends Module {
|
||||||
|
@singleton()
|
||||||
|
@provide()
|
||||||
|
ApiClient apiClient() => ApiClient();
|
||||||
|
|
||||||
|
@provide()
|
||||||
|
UserService userService(ApiClient api) => UserService(api);
|
||||||
|
|
||||||
|
@singleton()
|
||||||
|
@provide()
|
||||||
|
@named('mock')
|
||||||
|
ApiClient mockApiClient() => ApiClientMock();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- Методы, отмеченные `@provide`, становятся фабриками DI.
|
||||||
|
- Можно добавлять другие аннотации для уточнения типа биндинга, имени.
|
||||||
|
|
||||||
|
Сгенерированный код будет выглядеть вот таким образом:
|
||||||
|
|
||||||
|
```dart
|
||||||
|
class $AppModule extends AppModule {
|
||||||
|
@override
|
||||||
|
void builder(Scope currentScope) {
|
||||||
|
bind<ApiClient>().toProvide(() => apiClient()).singelton();
|
||||||
|
bind<UserService>().toProvide(() => userService(currentScope.resolve<ApiClient>()));
|
||||||
|
bind<ApiClient>().toProvide(() => mockApiClient()).withName('mock').singelton();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Пример инъекций зависимостей через field injection
|
||||||
|
|
||||||
|
```dart
|
||||||
|
@injectable()
|
||||||
|
class ProfileBloc with _$ProfileBloc {
|
||||||
|
@inject()
|
||||||
|
late final AuthService auth;
|
||||||
|
|
||||||
|
@inject()
|
||||||
|
@named('admin')
|
||||||
|
late final UserService adminUser;
|
||||||
|
|
||||||
|
ProfileBloc() {
|
||||||
|
_inject(this); // injectFields — сгенерированный метод
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- Генератор создаёт mixin (`_$ProfileBloc`), который автоматически резолвит и подставляет зависимости в поля класса.
|
||||||
|
- Аннотация `@named` привязывает конкретную реализацию по имени.
|
||||||
|
|
||||||
|
Сгенерированный код будет выглядеть вот таким образом:
|
||||||
|
|
||||||
|
```dart
|
||||||
|
mixin $ProfileBloc {
|
||||||
|
@override
|
||||||
|
void _inject(ProfileBloc instance) {
|
||||||
|
instance.auth = CherryPick.openRootScope().resolve<AuthService>();
|
||||||
|
instance.adminUser = CherryPick.openRootScope().resolve<UserService>(named: 'admin');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Как это подключается
|
||||||
|
|
||||||
|
```dart
|
||||||
|
void main() async {
|
||||||
|
final scope = CherryPick.openRootScope();
|
||||||
|
scope.installModules([
|
||||||
|
$AppModule(),
|
||||||
|
]);
|
||||||
|
// DI через field injection
|
||||||
|
final bloc = ProfileBloc();
|
||||||
|
runApp(MyApp(bloc: bloc));
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Асинхронные зависимости
|
||||||
|
|
||||||
|
Для асинхронных провайдеров используйте `toProvideAsync`, а получать их — через `resolveAsync`:
|
||||||
|
|
||||||
|
```dart
|
||||||
|
bind<RemoteConfig>().toProvideAsync(() async => await RemoteConfig.load());
|
||||||
|
|
||||||
|
// Использование:
|
||||||
|
final config = await scope.resolveAsync<RemoteConfig>();
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Проверка и диагностика
|
||||||
|
|
||||||
|
- При неправильных аннотациях или ошибках DI появляется понятное compile-time сообщение.
|
||||||
|
- Ошибки биндингов выявляются при генерации кода. Это минимизирует runtime-ошибки и ускоряет разработку.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Использование CherryPick с Flutter: пакет cherrypick_flutter
|
||||||
|
|
||||||
|
### Что это такое
|
||||||
|
|
||||||
|
[`cherrypick_flutter`](https://pub.dev/packages/cherrypick_flutter) — это пакет интеграции CherryPick DI с Flutter. Он предоставляет удобный виджет-провайдер `CherryPickProvider`, который размещается в вашем дереве виджетов и даёт доступ к root scope DI (и подскоупам) прямо из контекста.
|
||||||
|
|
||||||
|
### Ключевые возможности
|
||||||
|
|
||||||
|
- **Глобальный доступ к DI Scope:**
|
||||||
|
Через `CherryPickProvider` вы легко получаете доступ к rootScope и подскоупам из любого места дерева Flutter.
|
||||||
|
- **Интеграция с контекстом:**
|
||||||
|
Используйте `CherryPickProvider.of(context)` для доступа к DI внутри ваших виджетов.
|
||||||
|
|
||||||
|
### Пример использования
|
||||||
|
|
||||||
|
```dart
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:cherrypick_flutter/cherrypick_flutter.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
runApp(
|
||||||
|
CherryPickProvider(
|
||||||
|
child: MyApp(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class MyApp extends StatelessWidget {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final rootScope = CherryPickProvider.of(context).openRootScope();
|
||||||
|
|
||||||
|
return MaterialApp(
|
||||||
|
home: Scaffold(
|
||||||
|
body: Center(
|
||||||
|
child: Text(
|
||||||
|
rootScope.resolve<AppService>().getStatus(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- В этом примере `CherryPickProvider` оборачивает приложение и предоставляет доступ к DI scope через контекст.
|
||||||
|
- Вы можете создавать подскоупы, если нужно, например, для экранов или модулей:
|
||||||
|
`final subScope = CherryPickProvider.of(context).openSubScope(scopeName: "profileFeature");`
|
||||||
|
|
||||||
|
---
|
||||||
|
## CherryPick подходит не только для Flutter!
|
||||||
|
|
||||||
|
Вы можете использовать CherryPick и в Dart CLI, серверных проектах и микросервисах. Все основные возможности доступны и без Flutter.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Пример проекта на CherryPick: полный путь
|
||||||
|
|
||||||
|
1. Установите зависимости:
|
||||||
|
```yaml
|
||||||
|
dependencies:
|
||||||
|
cherrypick: ^1.0.0
|
||||||
|
cherrypick_annotations: ^1.0.0
|
||||||
|
|
||||||
|
dev_dependencies:
|
||||||
|
build_runner: ^2.0.0
|
||||||
|
cherrypick_generator: ^1.0.0
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Описываете свои модули с помощью аннотаций.
|
||||||
|
|
||||||
|
3. Для автоматической генерации DI кода используйте:
|
||||||
|
```shell
|
||||||
|
dart run build_runner build --delete-conflicting-outputs
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Наслаждайтесь современным DI без боли!
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Заключение
|
||||||
|
|
||||||
|
**CherryPick** — это современное DI-решение для Dart и Flutter, сочетающее лаконичный API и расширенные возможности аннотирования и генерации кода. Гибкость Scopes, параметрические провайдеры, именованные биндинги и field-injection делают его особенно мощным как для небольших, так и для масштабных проектов.
|
||||||
|
|
||||||
|
|
||||||
|
**Полный список аннотаций и их предназначение:**
|
||||||
|
|
||||||
|
| Аннотация | Для чего | Где применяют |
|
||||||
|
| ------------- | ------------------------- | -------------------------------- |
|
||||||
|
| `@module` | DI-модуль | Классы |
|
||||||
|
| `@singleton` | Singleton | Методы класса |
|
||||||
|
| `@instance` | Новый объект | Методы класса |
|
||||||
|
| `@provide` | Провайдер | Методы (с DI params) |
|
||||||
|
| `@named` | Именованный биндинг | Аргумент метода/Аттрибуты класса |
|
||||||
|
| `@params` | Передача параметров | Аргумент провайдера |
|
||||||
|
| `@injectable` | Поддержка field injection | Классы |
|
||||||
|
| `@inject` | Автовнедрение | Аттрибуты класса |
|
||||||
|
| `@scope` | Scope/realm | Аттрибуты класса |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Полезные ссылки
|
||||||
|
|
||||||
|
- [cherrypick](https://pub.dev/packages/cherrypick)
|
||||||
|
- [cherrypick_annotations](https://pub.dev/packages/cherrypick_annotations)
|
||||||
|
- [cherrypick_generator](https://pub.dev/packages/cherrypick_generator)
|
||||||
|
- [Исходники на GitHub](https://github.com/pese-git/cherrypick)
|
||||||
@@ -5,23 +5,18 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: _fe_analyzer_shared
|
name: _fe_analyzer_shared
|
||||||
sha256: f256b0c0ba6c7577c15e2e4e114755640a875e885099367bf6e012b19314c834
|
sha256: e55636ed79578b9abca5fecf9437947798f5ef7456308b5cb85720b793eac92f
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "72.0.0"
|
version: "82.0.0"
|
||||||
_macros:
|
|
||||||
dependency: transitive
|
|
||||||
description: dart
|
|
||||||
source: sdk
|
|
||||||
version: "0.3.2"
|
|
||||||
analyzer:
|
analyzer:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: analyzer
|
name: analyzer
|
||||||
sha256: b652861553cd3990d8ed361f7979dc6d7053a9ac8843fa73820ab68ce5410139
|
sha256: "904ae5bb474d32c38fb9482e2d925d5454cda04ddd0e55d2e6826bc72f6ba8c0"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.7.0"
|
version: "7.4.5"
|
||||||
args:
|
args:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -34,26 +29,26 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: async
|
name: async
|
||||||
sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c"
|
sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.11.0"
|
version: "2.12.0"
|
||||||
boolean_selector:
|
boolean_selector:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: boolean_selector
|
name: boolean_selector
|
||||||
sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66"
|
sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.1"
|
version: "2.1.2"
|
||||||
build:
|
build:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: build
|
name: build
|
||||||
sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0"
|
sha256: cef23f1eda9b57566c81e2133d196f8e3df48f244b317368d65c5943d91148f0
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.4.1"
|
version: "2.4.2"
|
||||||
build_config:
|
build_config:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -74,26 +69,26 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: build_resolvers
|
name: build_resolvers
|
||||||
sha256: "339086358431fa15d7eca8b6a36e5d783728cf025e559b834f4609a1fcfb7b0a"
|
sha256: b9e4fda21d846e192628e7a4f6deda6888c36b5b69ba02ff291a01fd529140f0
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.4.2"
|
version: "2.4.4"
|
||||||
build_runner:
|
build_runner:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
name: build_runner
|
name: build_runner
|
||||||
sha256: "028819cfb90051c6b5440c7e574d1896f8037e3c96cf17aaeb054c9311cfbf4d"
|
sha256: "058fe9dce1de7d69c4b84fada934df3e0153dd000758c4d65964d0166779aa99"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.4.13"
|
version: "2.4.15"
|
||||||
build_runner_core:
|
build_runner_core:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: build_runner_core
|
name: build_runner_core
|
||||||
sha256: f8126682b87a7282a339b871298cc12009cb67109cfa1614d6436fb0289193e0
|
sha256: "22e3aa1c80e0ada3722fe5b63fd43d9c8990759d0a2cf489c8c5d7b2bdebc021"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "7.3.2"
|
version: "8.0.0"
|
||||||
built_collection:
|
built_collection:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -114,10 +109,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: characters
|
name: characters
|
||||||
sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605"
|
sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.0"
|
version: "1.4.0"
|
||||||
checked_yaml:
|
checked_yaml:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -139,7 +134,7 @@ packages:
|
|||||||
path: "../../cherrypick_annotations"
|
path: "../../cherrypick_annotations"
|
||||||
relative: true
|
relative: true
|
||||||
source: path
|
source: path
|
||||||
version: "1.1.0-dev.0"
|
version: "1.1.0-dev.1"
|
||||||
cherrypick_flutter:
|
cherrypick_flutter:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -153,15 +148,15 @@ packages:
|
|||||||
path: "../../cherrypick_generator"
|
path: "../../cherrypick_generator"
|
||||||
relative: true
|
relative: true
|
||||||
source: path
|
source: path
|
||||||
version: "1.1.0-dev.2"
|
version: "1.1.0-dev.5"
|
||||||
clock:
|
clock:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: clock
|
name: clock
|
||||||
sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf
|
sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.1"
|
version: "1.1.2"
|
||||||
code_builder:
|
code_builder:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -174,10 +169,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: collection
|
name: collection
|
||||||
sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
|
sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.18.0"
|
version: "1.19.1"
|
||||||
convert:
|
convert:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -206,18 +201,18 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: dart_style
|
name: dart_style
|
||||||
sha256: "7856d364b589d1f08986e140938578ed36ed948581fbc3bc9aef1805039ac5ab"
|
sha256: "27eb0ae77836989a3bc541ce55595e8ceee0992807f14511552a898ddd0d88ac"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.7"
|
version: "3.0.1"
|
||||||
fake_async:
|
fake_async:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: fake_async
|
name: fake_async
|
||||||
sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78"
|
sha256: "6a95e56b2449df2273fd8c45a662d6947ce1ebb7aafe80e550a3f68297f3cacc"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.1"
|
version: "1.3.2"
|
||||||
file:
|
file:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -276,6 +271,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.2"
|
version: "2.3.2"
|
||||||
|
http:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: http
|
||||||
|
sha256: "2c11f3f94c687ee9bad77c171151672986360b2b001d109814ee7140b2cf261b"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.4.0"
|
||||||
http_multi_server:
|
http_multi_server:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -320,18 +323,18 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: leak_tracker
|
name: leak_tracker
|
||||||
sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05"
|
sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "10.0.5"
|
version: "10.0.8"
|
||||||
leak_tracker_flutter_testing:
|
leak_tracker_flutter_testing:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: leak_tracker_flutter_testing
|
name: leak_tracker_flutter_testing
|
||||||
sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806"
|
sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.5"
|
version: "3.0.9"
|
||||||
leak_tracker_testing:
|
leak_tracker_testing:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -356,22 +359,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.0"
|
version: "1.3.0"
|
||||||
macros:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: macros
|
|
||||||
sha256: "0acaed5d6b7eab89f63350bccd82119e6c602df0f391260d0e32b5e23db79536"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "0.1.2-main.4"
|
|
||||||
matcher:
|
matcher:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: matcher
|
name: matcher
|
||||||
sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb
|
sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.12.16+1"
|
version: "0.12.17"
|
||||||
material_color_utilities:
|
material_color_utilities:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -384,10 +379,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: meta
|
name: meta
|
||||||
sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7
|
sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.15.0"
|
version: "1.16.0"
|
||||||
mime:
|
mime:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -408,10 +403,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: path
|
name: path
|
||||||
sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af"
|
sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.9.0"
|
version: "1.9.1"
|
||||||
pool:
|
pool:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -456,39 +451,39 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.99"
|
version: "0.0.0"
|
||||||
source_gen:
|
source_gen:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: source_gen
|
name: source_gen
|
||||||
sha256: "14658ba5f669685cd3d63701d01b31ea748310f7ab854e471962670abcf57832"
|
sha256: "35c8150ece9e8c8d263337a265153c3329667640850b9304861faea59fc98f6b"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.5.0"
|
version: "2.0.0"
|
||||||
source_span:
|
source_span:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: source_span
|
name: source_span
|
||||||
sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
|
sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.10.0"
|
version: "1.10.1"
|
||||||
stack_trace:
|
stack_trace:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: stack_trace
|
name: stack_trace
|
||||||
sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b"
|
sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.11.1"
|
version: "1.12.1"
|
||||||
stream_channel:
|
stream_channel:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: stream_channel
|
name: stream_channel
|
||||||
sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
|
sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.2"
|
version: "2.1.4"
|
||||||
stream_transform:
|
stream_transform:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -501,26 +496,26 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: string_scanner
|
name: string_scanner
|
||||||
sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde"
|
sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.2.0"
|
version: "1.4.1"
|
||||||
term_glyph:
|
term_glyph:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: term_glyph
|
name: term_glyph
|
||||||
sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84
|
sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.2.1"
|
version: "1.2.2"
|
||||||
test_api:
|
test_api:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: test_api
|
name: test_api
|
||||||
sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb"
|
sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.7.2"
|
version: "0.7.4"
|
||||||
timing:
|
timing:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -549,10 +544,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: vm_service
|
name: vm_service
|
||||||
sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d"
|
sha256: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "14.2.5"
|
version: "14.3.1"
|
||||||
watcher:
|
watcher:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -594,5 +589,5 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.3"
|
version: "3.1.3"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=3.5.2 <4.0.0"
|
dart: ">=3.7.0-0 <4.0.0"
|
||||||
flutter: ">=3.18.0-18.0.pre.54"
|
flutter: ">=3.18.0-18.0.pre.54"
|
||||||
|
|||||||
@@ -11,10 +11,13 @@ environment:
|
|||||||
dependencies:
|
dependencies:
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
cherrypick: ^2.2.0-dev.1
|
cherrypick:
|
||||||
cherrypick_flutter: ^1.1.2-dev.1
|
path: ../../cherrypick
|
||||||
|
cherrypick_flutter:
|
||||||
|
path: ../../cherrypick_flutter
|
||||||
|
|
||||||
cherrypick_annotations: ^1.1.0-dev.1
|
cherrypick_annotations:
|
||||||
|
path: ../../cherrypick_annotations
|
||||||
|
|
||||||
cupertino_icons: ^1.0.8
|
cupertino_icons: ^1.0.8
|
||||||
|
|
||||||
@@ -24,8 +27,9 @@ dev_dependencies:
|
|||||||
|
|
||||||
flutter_lints: ^5.0.0
|
flutter_lints: ^5.0.0
|
||||||
|
|
||||||
cherrypick_generator: ^1.1.0-dev.3
|
cherrypick_generator:
|
||||||
build_runner: ^2.4.13
|
path: ../../cherrypick_generator
|
||||||
|
build_runner: ^2.4.15
|
||||||
|
|
||||||
# For information on the generic Dart part of this file, see the
|
# For information on the generic Dart part of this file, see the
|
||||||
# following page: https://dart.dev/tools/pub/pubspec
|
# following page: https://dart.dev/tools/pub/pubspec
|
||||||
|
|||||||
@@ -9,6 +9,13 @@
|
|||||||
# packages, and plugins designed to encourage good coding practices.
|
# packages, and plugins designed to encourage good coding practices.
|
||||||
include: package:flutter_lints/flutter.yaml
|
include: package:flutter_lints/flutter.yaml
|
||||||
|
|
||||||
|
analyzer:
|
||||||
|
exclude:
|
||||||
|
- "**/*.g.dart"
|
||||||
|
- "**/*.freezed.dart"
|
||||||
|
- "**/*.gr.dart"
|
||||||
|
- "**/*.config.dart"
|
||||||
|
|
||||||
linter:
|
linter:
|
||||||
# The lint rules applied to this project can be customized in the
|
# The lint rules applied to this project can be customized in the
|
||||||
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
|
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
|
||||||
|
|||||||
@@ -5,23 +5,18 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: _fe_analyzer_shared
|
name: _fe_analyzer_shared
|
||||||
sha256: f256b0c0ba6c7577c15e2e4e114755640a875e885099367bf6e012b19314c834
|
sha256: e55636ed79578b9abca5fecf9437947798f5ef7456308b5cb85720b793eac92f
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "72.0.0"
|
version: "82.0.0"
|
||||||
_macros:
|
|
||||||
dependency: transitive
|
|
||||||
description: dart
|
|
||||||
source: sdk
|
|
||||||
version: "0.3.2"
|
|
||||||
analyzer:
|
analyzer:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: analyzer
|
name: analyzer
|
||||||
sha256: b652861553cd3990d8ed361f7979dc6d7053a9ac8843fa73820ab68ce5410139
|
sha256: "904ae5bb474d32c38fb9482e2d925d5454cda04ddd0e55d2e6826bc72f6ba8c0"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.7.0"
|
version: "7.4.5"
|
||||||
args:
|
args:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -34,10 +29,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: async
|
name: async
|
||||||
sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c"
|
sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.11.0"
|
version: "2.12.0"
|
||||||
auto_route:
|
auto_route:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -50,10 +45,10 @@ packages:
|
|||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
name: auto_route_generator
|
name: auto_route_generator
|
||||||
sha256: c9086eb07271e51b44071ad5cff34e889f3156710b964a308c2ab590769e79e6
|
sha256: c2e359d8932986d4d1bcad7a428143f81384ce10fef8d4aa5bc29e1f83766a46
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "9.0.0"
|
version: "9.3.1"
|
||||||
bloc:
|
bloc:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -66,18 +61,18 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: boolean_selector
|
name: boolean_selector
|
||||||
sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66"
|
sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.1"
|
version: "2.1.2"
|
||||||
build:
|
build:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: build
|
name: build
|
||||||
sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0"
|
sha256: cef23f1eda9b57566c81e2133d196f8e3df48f244b317368d65c5943d91148f0
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.4.1"
|
version: "2.4.2"
|
||||||
build_config:
|
build_config:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -98,26 +93,26 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: build_resolvers
|
name: build_resolvers
|
||||||
sha256: "339086358431fa15d7eca8b6a36e5d783728cf025e559b834f4609a1fcfb7b0a"
|
sha256: b9e4fda21d846e192628e7a4f6deda6888c36b5b69ba02ff291a01fd529140f0
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.4.2"
|
version: "2.4.4"
|
||||||
build_runner:
|
build_runner:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
name: build_runner
|
name: build_runner
|
||||||
sha256: "028819cfb90051c6b5440c7e574d1896f8037e3c96cf17aaeb054c9311cfbf4d"
|
sha256: "058fe9dce1de7d69c4b84fada934df3e0153dd000758c4d65964d0166779aa99"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.4.13"
|
version: "2.4.15"
|
||||||
build_runner_core:
|
build_runner_core:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: build_runner_core
|
name: build_runner_core
|
||||||
sha256: f8126682b87a7282a339b871298cc12009cb67109cfa1614d6436fb0289193e0
|
sha256: "22e3aa1c80e0ada3722fe5b63fd43d9c8990759d0a2cf489c8c5d7b2bdebc021"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "7.3.2"
|
version: "8.0.0"
|
||||||
built_collection:
|
built_collection:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -138,10 +133,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: characters
|
name: characters
|
||||||
sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605"
|
sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.0"
|
version: "1.4.0"
|
||||||
checked_yaml:
|
checked_yaml:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -163,22 +158,22 @@ packages:
|
|||||||
path: "../../cherrypick_annotations"
|
path: "../../cherrypick_annotations"
|
||||||
relative: true
|
relative: true
|
||||||
source: path
|
source: path
|
||||||
version: "1.1.0-dev.0"
|
version: "1.1.0-dev.1"
|
||||||
cherrypick_generator:
|
cherrypick_generator:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
path: "../../cherrypick_generator"
|
path: "../../cherrypick_generator"
|
||||||
relative: true
|
relative: true
|
||||||
source: path
|
source: path
|
||||||
version: "1.1.0-dev.2"
|
version: "1.1.0-dev.5"
|
||||||
clock:
|
clock:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: clock
|
name: clock
|
||||||
sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf
|
sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.1"
|
version: "1.1.2"
|
||||||
code_builder:
|
code_builder:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -191,10 +186,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: collection
|
name: collection
|
||||||
sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
|
sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.18.0"
|
version: "1.19.1"
|
||||||
convert:
|
convert:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -223,10 +218,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: dart_style
|
name: dart_style
|
||||||
sha256: "7856d364b589d1f08986e140938578ed36ed948581fbc3bc9aef1805039ac5ab"
|
sha256: "5b236382b47ee411741447c1f1e111459c941ea1b3f2b540dde54c210a3662af"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.7"
|
version: "3.1.0"
|
||||||
dartz:
|
dartz:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -255,10 +250,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: fake_async
|
name: fake_async
|
||||||
sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78"
|
sha256: "6a95e56b2449df2273fd8c45a662d6947ce1ebb7aafe80e550a3f68297f3cacc"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.1"
|
version: "1.3.2"
|
||||||
file:
|
file:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -305,10 +300,10 @@ packages:
|
|||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
name: freezed
|
name: freezed
|
||||||
sha256: "44c19278dd9d89292cf46e97dc0c1e52ce03275f40a97c5a348e802a924bf40e"
|
sha256: "59a584c24b3acdc5250bb856d0d3e9c0b798ed14a4af1ddb7dc1c7b41df91c9c"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.5.7"
|
version: "2.5.8"
|
||||||
freezed_annotation:
|
freezed_annotation:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -341,6 +336,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.2"
|
version: "2.3.2"
|
||||||
|
http:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: http
|
||||||
|
sha256: "2c11f3f94c687ee9bad77c171151672986360b2b001d109814ee7140b2cf261b"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.4.0"
|
||||||
http_multi_server:
|
http_multi_server:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -385,26 +388,26 @@ packages:
|
|||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
name: json_serializable
|
name: json_serializable
|
||||||
sha256: c2fcb3920cf2b6ae6845954186420fca40bc0a8abcc84903b7801f17d7050d7c
|
sha256: c50ef5fc083d5b5e12eef489503ba3bf5ccc899e487d691584699b4bdefeea8c
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.9.0"
|
version: "6.9.5"
|
||||||
leak_tracker:
|
leak_tracker:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: leak_tracker
|
name: leak_tracker
|
||||||
sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05"
|
sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "10.0.5"
|
version: "10.0.8"
|
||||||
leak_tracker_flutter_testing:
|
leak_tracker_flutter_testing:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: leak_tracker_flutter_testing
|
name: leak_tracker_flutter_testing
|
||||||
sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806"
|
sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.5"
|
version: "3.0.9"
|
||||||
leak_tracker_testing:
|
leak_tracker_testing:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -429,22 +432,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.0"
|
version: "1.3.0"
|
||||||
macros:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: macros
|
|
||||||
sha256: "0acaed5d6b7eab89f63350bccd82119e6c602df0f391260d0e32b5e23db79536"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "0.1.2-main.4"
|
|
||||||
matcher:
|
matcher:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: matcher
|
name: matcher
|
||||||
sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb
|
sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.12.16+1"
|
version: "0.12.17"
|
||||||
material_color_utilities:
|
material_color_utilities:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -457,10 +452,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: meta
|
name: meta
|
||||||
sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7
|
sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.15.0"
|
version: "1.16.0"
|
||||||
mime:
|
mime:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -489,10 +484,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: path
|
name: path
|
||||||
sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af"
|
sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.9.0"
|
version: "1.9.1"
|
||||||
petitparser:
|
petitparser:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -513,10 +508,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: protobuf
|
name: protobuf
|
||||||
sha256: "68645b24e0716782e58948f8467fd42a880f255096a821f9e7d0ec625b00c84d"
|
sha256: "579fe5557eae58e3adca2e999e38f02441d8aa908703854a9e0a0f47fa857731"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.0"
|
version: "4.1.0"
|
||||||
provider:
|
provider:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -553,10 +548,10 @@ packages:
|
|||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
name: retrofit_generator
|
name: retrofit_generator
|
||||||
sha256: f76fdb2b66854690d5a332e7364d7561fc9dc2b3c924d7956ab8070495e21f6a
|
sha256: "65d28d3a7b4db485f1c73fee8ee32f552ef23ee4ecb68ba491f39d80b73bdcbf"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "9.1.5"
|
version: "9.2.0"
|
||||||
shelf:
|
shelf:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -577,15 +572,15 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.99"
|
version: "0.0.0"
|
||||||
source_gen:
|
source_gen:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: source_gen
|
name: source_gen
|
||||||
sha256: "14658ba5f669685cd3d63701d01b31ea748310f7ab854e471962670abcf57832"
|
sha256: "35c8150ece9e8c8d263337a265153c3329667640850b9304861faea59fc98f6b"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.5.0"
|
version: "2.0.0"
|
||||||
source_helper:
|
source_helper:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -598,26 +593,26 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: source_span
|
name: source_span
|
||||||
sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
|
sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.10.0"
|
version: "1.10.1"
|
||||||
stack_trace:
|
stack_trace:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: stack_trace
|
name: stack_trace
|
||||||
sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b"
|
sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.11.1"
|
version: "1.12.1"
|
||||||
stream_channel:
|
stream_channel:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: stream_channel
|
name: stream_channel
|
||||||
sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
|
sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.2"
|
version: "2.1.4"
|
||||||
stream_transform:
|
stream_transform:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -630,26 +625,26 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: string_scanner
|
name: string_scanner
|
||||||
sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde"
|
sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.2.0"
|
version: "1.4.1"
|
||||||
term_glyph:
|
term_glyph:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: term_glyph
|
name: term_glyph
|
||||||
sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84
|
sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.2.1"
|
version: "1.2.2"
|
||||||
test_api:
|
test_api:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: test_api
|
name: test_api
|
||||||
sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb"
|
sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.7.2"
|
version: "0.7.4"
|
||||||
timing:
|
timing:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -678,10 +673,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: vm_service
|
name: vm_service
|
||||||
sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d"
|
sha256: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "14.2.5"
|
version: "14.3.1"
|
||||||
watcher:
|
watcher:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -731,5 +726,5 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.3"
|
version: "3.1.3"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=3.5.2 <4.0.0"
|
dart: ">=3.7.0 <4.0.0"
|
||||||
flutter: ">=3.18.0-18.0.pre.54"
|
flutter: ">=3.18.0-18.0.pre.54"
|
||||||
|
|||||||
@@ -12,12 +12,14 @@ dependencies:
|
|||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
|
||||||
cherrypick: ^2.2.0-dev.1
|
cherrypick:
|
||||||
cherrypick_annotations: ^1.1.0-dev.1
|
path: ../../cherrypick
|
||||||
|
cherrypick_annotations:
|
||||||
|
path: ../../cherrypick_annotations
|
||||||
|
|
||||||
dio: ^5.4.0
|
dio: ^5.4.0
|
||||||
retrofit: ^4.0.3
|
retrofit: ^4.0.3
|
||||||
freezed_annotation: ^2.3.2
|
freezed_annotation: ^2.4.4
|
||||||
dartz: ^0.10.1
|
dartz: ^0.10.1
|
||||||
flutter_bloc: ^9.1.1
|
flutter_bloc: ^9.1.1
|
||||||
auto_route: ^9.3.0+1
|
auto_route: ^9.3.0+1
|
||||||
@@ -30,11 +32,12 @@ dev_dependencies:
|
|||||||
|
|
||||||
flutter_lints: ^5.0.0
|
flutter_lints: ^5.0.0
|
||||||
|
|
||||||
cherrypick_generator: ^1.1.0-dev.3
|
cherrypick_generator:
|
||||||
build_runner: 2.4.13
|
path: ../../cherrypick_generator
|
||||||
|
build_runner: 2.4.15
|
||||||
|
|
||||||
retrofit_generator: ^9.1.5
|
retrofit_generator: ^9.1.5
|
||||||
freezed: ^2.3.2
|
freezed: ^2.5.8
|
||||||
json_serializable: ^6.9.0
|
json_serializable: ^6.9.0
|
||||||
auto_route_generator: ^9.0.0
|
auto_route_generator: ^9.0.0
|
||||||
|
|
||||||
|
|||||||
18
melos.yaml
18
melos.yaml
@@ -18,7 +18,23 @@ scripts:
|
|||||||
exec: dart format lib
|
exec: dart format lib
|
||||||
|
|
||||||
test:
|
test:
|
||||||
exec: flutter test
|
run: |
|
||||||
|
echo "Running Dart tests..."
|
||||||
|
melos exec --scope="cherrypick,cherrypick_annotations,cherrypick_generator" -- dart test --reporter=compact
|
||||||
|
echo "Running Flutter tests..."
|
||||||
|
melos exec --scope="cherrypick_flutter" -- flutter test --reporter=compact
|
||||||
|
|
||||||
|
test:dart:
|
||||||
|
description: "Run tests for Dart packages only"
|
||||||
|
exec: dart test --reporter=compact
|
||||||
|
packageFilters:
|
||||||
|
scope: ["cherrypick", "cherrypick_annotations", "cherrypick_generator"]
|
||||||
|
|
||||||
|
test:flutter:
|
||||||
|
description: "Run tests for Flutter packages only"
|
||||||
|
exec: flutter test --reporter=compact
|
||||||
|
packageFilters:
|
||||||
|
scope: ["cherrypick_flutter"]
|
||||||
|
|
||||||
codegen:
|
codegen:
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
14
pubspec.lock
14
pubspec.lock
@@ -5,23 +5,23 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: _fe_analyzer_shared
|
name: _fe_analyzer_shared
|
||||||
sha256: "45cfa8471b89fb6643fe9bf51bd7931a76b8f5ec2d65de4fb176dba8d4f22c77"
|
sha256: "16e298750b6d0af7ce8a3ba7c18c69c3785d11b15ec83f6dcd0ad2a0009b3cab"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "73.0.0"
|
version: "76.0.0"
|
||||||
_macros:
|
_macros:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description: dart
|
description: dart
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.3.2"
|
version: "0.3.3"
|
||||||
analyzer:
|
analyzer:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: analyzer
|
name: analyzer
|
||||||
sha256: "4959fec185fe70cce007c57e9ab6983101dbe593d2bf8bbfb4453aaec0cf470a"
|
sha256: "1f14db053a8c23e260789e9b0980fa27f2680dd640932cae5e1137cce0e46e1e"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.8.0"
|
version: "6.11.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: "0acaed5d6b7eab89f63350bccd82119e6c602df0f391260d0e32b5e23db79536"
|
sha256: "1d9e801cd66f7ea3663c45fc708450db1fa57f988142c64289142c9b7ee80656"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.1.2-main.4"
|
version: "0.1.3-main.0"
|
||||||
matcher:
|
matcher:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
Reference in New Issue
Block a user