mirror of
https://github.com/pese-git/cherrypick.git
synced 2026-05-16 10:10:43 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3331a3ee9c |
36
CHANGELOG.md
36
CHANGELOG.md
@@ -3,42 +3,6 @@
|
|||||||
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.
|
||||||
|
|
||||||
## 2026-04-28
|
|
||||||
|
|
||||||
### Changes
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
Packages with breaking changes:
|
|
||||||
|
|
||||||
- There are no breaking changes in this release.
|
|
||||||
|
|
||||||
Packages with other changes:
|
|
||||||
|
|
||||||
- [`cherrypick` - `v3.0.3`](#cherrypick---v303)
|
|
||||||
- [`cherrypick_generator` - `v4.0.0-dev.1`](#cherrypick_generator---v400-dev1)
|
|
||||||
- [`cherrypick_flutter` - `v3.0.3`](#cherrypick_flutter---v303)
|
|
||||||
- [`talker_cherrypick_logger` - `v3.0.3`](#talker_cherrypick_logger---v303)
|
|
||||||
|
|
||||||
Packages with dependency updates only:
|
|
||||||
|
|
||||||
> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project.
|
|
||||||
|
|
||||||
- `cherrypick_flutter` - `v3.0.3`
|
|
||||||
- `talker_cherrypick_logger` - `v3.0.3`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### `cherrypick` - `v3.0.3`
|
|
||||||
|
|
||||||
- **DOCS**: sync references after ProviderFactory rename.
|
|
||||||
|
|
||||||
#### `cherrypick_generator` - `v4.0.0-dev.1`
|
|
||||||
|
|
||||||
- **REFACTOR**(cherrypick_generator): migrate inject codegen to code_builder.
|
|
||||||
- **FIX**: add deps.
|
|
||||||
|
|
||||||
|
|
||||||
## 2026-01-29
|
## 2026-01-29
|
||||||
|
|
||||||
### Changes
|
### Changes
|
||||||
|
|||||||
@@ -1,11 +1,3 @@
|
|||||||
## 3.0.3
|
|
||||||
|
|
||||||
- **DOCS**: sync references after ProviderFactory rename.
|
|
||||||
|
|
||||||
## Unreleased
|
|
||||||
|
|
||||||
- **BREAKING** **REFACTOR**(binding): rename `Provider<T>` typedef to `ProviderFactory<T>` to avoid naming conflicts with `package:provider`.
|
|
||||||
|
|
||||||
## 3.0.2
|
## 3.0.2
|
||||||
|
|
||||||
- **FIX**(test): fix warning.
|
- **FIX**(test): fix warning.
|
||||||
@@ -196,4 +188,4 @@
|
|||||||
- **MAINT**: Updated pubspec.
|
- **MAINT**: Updated pubspec.
|
||||||
|
|
||||||
## 0.1.0
|
## 0.1.0
|
||||||
- **INIT**: Initial release.
|
- **INIT**: Initial release.
|
||||||
@@ -205,7 +205,7 @@ class Binding<T> {
|
|||||||
/// bind<Api>().toProvide(() => ApiService());
|
/// bind<Api>().toProvide(() => ApiService());
|
||||||
/// bind<Db>().toProvide(() async => await openDb());
|
/// bind<Db>().toProvide(() async => await openDb());
|
||||||
/// ```
|
/// ```
|
||||||
Binding<T> toProvide(ProviderFactory<T> value) {
|
Binding<T> toProvide(Provider<T> value) {
|
||||||
_resolver = ProviderResolver<T>((_) => value.call(), withParams: false);
|
_resolver = ProviderResolver<T>((_) => value.call(), withParams: false);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
@@ -227,7 +227,7 @@ class Binding<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Deprecated('Use toProvide instead of toProvideAsync')
|
@Deprecated('Use toProvide instead of toProvideAsync')
|
||||||
Binding<T> toProvideAsync(ProviderFactory<T> value) {
|
Binding<T> toProvideAsync(Provider<T> value) {
|
||||||
return toProvide(value);
|
return toProvide(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,10 +28,10 @@ typedef Instance<T> = FutureOr<T>;
|
|||||||
///
|
///
|
||||||
/// Example:
|
/// Example:
|
||||||
/// ```dart
|
/// ```dart
|
||||||
/// ProviderFactory<MyService> provider = () => MyService();
|
/// Provider<MyService> provider = () => MyService();
|
||||||
/// ProviderFactory<Api> asyncProvider = () async => await Api.connect();
|
/// Provider<Api> asyncProvider = () async => await Api.connect();
|
||||||
/// ```
|
/// ```
|
||||||
typedef ProviderFactory<T> = FutureOr<T> Function();
|
typedef Provider<T> = FutureOr<T> Function();
|
||||||
|
|
||||||
/// Provider function type that accepts a dynamic parameter, for factory/parametrized injection.
|
/// Provider function type that accepts a dynamic parameter, for factory/parametrized injection.
|
||||||
/// Returns [T] or [Future<T>].
|
/// Returns [T] or [Future<T>].
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
name: cherrypick
|
name: cherrypick
|
||||||
description: Cherrypick is a small dependency injection (DI) library for dart/flutter projects.
|
description: Cherrypick is a small dependency injection (DI) library for dart/flutter projects.
|
||||||
version: 3.0.3
|
version: 3.0.2
|
||||||
homepage: https://cherrypick-di.netlify.app
|
homepage: https://cherrypick-di.netlify.app
|
||||||
documentation: https://cherrypick-di.netlify.app/docs/intro
|
documentation: https://cherrypick-di.netlify.app/docs/intro
|
||||||
repository: https://github.com/pese-git/cherrypick
|
repository: https://github.com/pese-git/cherrypick
|
||||||
|
|||||||
@@ -1,7 +1,3 @@
|
|||||||
## 3.0.3
|
|
||||||
|
|
||||||
- Update a dependency to the latest release.
|
|
||||||
|
|
||||||
## 3.0.2
|
## 3.0.2
|
||||||
|
|
||||||
- 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: 3.0.3
|
version: 3.0.2
|
||||||
homepage: https://cherrypick-di.netlify.app
|
homepage: https://cherrypick-di.netlify.app
|
||||||
documentation: https://cherrypick-di.netlify.app/docs/intro
|
documentation: https://cherrypick-di.netlify.app/docs/intro
|
||||||
repository: https://github.com/pese-git/cherrypick
|
repository: https://github.com/pese-git/cherrypick
|
||||||
@@ -19,7 +19,7 @@ environment:
|
|||||||
dependencies:
|
dependencies:
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
cherrypick: ^3.0.3
|
cherrypick: ^3.0.2
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|||||||
@@ -1,8 +1,3 @@
|
|||||||
## 4.0.0-dev.1
|
|
||||||
|
|
||||||
- **REFACTOR**(cherrypick_generator): migrate inject codegen to code_builder.
|
|
||||||
- **FIX**: add deps.
|
|
||||||
|
|
||||||
## 4.0.0-dev.0
|
## 4.0.0-dev.0
|
||||||
|
|
||||||
> Note: This release has breaking changes.
|
> Note: This release has breaking changes.
|
||||||
|
|||||||
@@ -11,17 +11,14 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
import 'package:analyzer/dart/constant/value.dart';
|
||||||
import 'package:analyzer/dart/element/element2.dart';
|
import 'package:analyzer/dart/element/element2.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:code_builder/code_builder.dart';
|
|
||||||
import 'package:source_gen/source_gen.dart';
|
import 'package:source_gen/source_gen.dart';
|
||||||
import 'package:cherrypick_annotations/cherrypick_annotations.dart' as ann;
|
import 'package:cherrypick_annotations/cherrypick_annotations.dart' as ann;
|
||||||
|
|
||||||
import 'src/annotation_validator.dart';
|
|
||||||
import 'src/code_builder_emitters.dart';
|
|
||||||
import 'src/type_parser.dart';
|
|
||||||
|
|
||||||
/// CherryPick DI field injector generator for codegen.
|
/// CherryPick DI field injector generator for codegen.
|
||||||
///
|
///
|
||||||
/// Analyzes all Dart classes marked with `@injectable()` and generates a mixin (for example, `_$ProfileScreen`)
|
/// Analyzes all Dart classes marked with `@injectable()` and generates a mixin (for example, `_$ProfileScreen`)
|
||||||
@@ -118,54 +115,24 @@ class InjectGenerator extends GeneratorForAnnotation<ann.injectable> {
|
|||||||
final className = classElement.firstFragment.name2;
|
final className = classElement.firstFragment.name2;
|
||||||
final mixinName = '_\$$className';
|
final mixinName = '_\$$className';
|
||||||
|
|
||||||
AnnotationValidator.validateClassAnnotations(classElement);
|
final buffer = StringBuffer()
|
||||||
|
..writeln('mixin $mixinName {')
|
||||||
final classType = TypeParser.parseType(classElement.thisType, classElement);
|
..writeln(' void _inject($className instance) {');
|
||||||
|
|
||||||
|
// Collect and process all @inject fields
|
||||||
final injectFields = classElement.fields2
|
final injectFields = classElement.fields2
|
||||||
.where((f) => _isInjectField(f))
|
.where((f) => _isInjectField(f))
|
||||||
.map(_parseInjectField)
|
.map((f) => _parseInjectField(f));
|
||||||
.toList();
|
|
||||||
|
|
||||||
final injectMethod = Method((b) {
|
for (final parsedField in injectFields) {
|
||||||
b
|
buffer.writeln(_generateInjectionLine(parsedField));
|
||||||
..name = '_inject'
|
}
|
||||||
..returns = refer('void')
|
|
||||||
..requiredParameters.add(
|
|
||||||
Parameter((p) {
|
|
||||||
p
|
|
||||||
..name = 'instance'
|
|
||||||
..type = CodeBuilderEmitters.resolveTypeRef(classType);
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
..body = Block((body) {
|
|
||||||
for (final field in injectFields) {
|
|
||||||
final scopeExpr = CodeBuilderEmitters.openScope(
|
|
||||||
scopeName: field.scopeName,
|
|
||||||
);
|
|
||||||
final resolveExpr = CodeBuilderEmitters.resolveCall(
|
|
||||||
scopeExpr: scopeExpr,
|
|
||||||
parsedType: field.parsedType,
|
|
||||||
named: field.namedValue,
|
|
||||||
);
|
|
||||||
body.statements.add(
|
|
||||||
refer(
|
|
||||||
'instance',
|
|
||||||
).property(field.fieldName).assign(resolveExpr).statement,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
final mixin = Mixin((b) {
|
buffer
|
||||||
b
|
..writeln(' }')
|
||||||
..name = mixinName
|
..writeln('}');
|
||||||
..methods.add(injectMethod);
|
|
||||||
});
|
|
||||||
|
|
||||||
final library = Library((b) => b..body.add(mixin));
|
return buffer.toString();
|
||||||
final emitter = DartEmitter(useNullSafetySyntax: true);
|
|
||||||
return '${library.accept(emitter)}';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true if a field is annotated with `@inject`.
|
/// Returns true if a field is annotated with `@inject`.
|
||||||
@@ -183,13 +150,11 @@ class InjectGenerator extends GeneratorForAnnotation<ann.injectable> {
|
|||||||
/// Converts Dart field declaration and all parameterizing injection-related
|
/// Converts Dart field declaration and all parameterizing injection-related
|
||||||
/// annotations into a [_ParsedInjectField] which is used for codegen.
|
/// annotations into a [_ParsedInjectField] which is used for codegen.
|
||||||
static _ParsedInjectField _parseInjectField(FieldElement2 field) {
|
static _ParsedInjectField _parseInjectField(FieldElement2 field) {
|
||||||
AnnotationValidator.validateFieldAnnotations(field);
|
|
||||||
|
|
||||||
String? scopeName;
|
String? scopeName;
|
||||||
String? namedValue;
|
String? namedValue;
|
||||||
|
|
||||||
for (final meta in field.firstFragment.metadata2.annotations) {
|
for (final meta in field.firstFragment.metadata2.annotations) {
|
||||||
final obj = meta.computeConstantValue();
|
final DartObject? obj = meta.computeConstantValue();
|
||||||
final type = obj?.type?.getDisplayString();
|
final type = obj?.type?.getDisplayString();
|
||||||
if (type == 'scope') {
|
if (type == 'scope') {
|
||||||
scopeName = obj?.getField('name')?.toStringValue();
|
scopeName = obj?.getField('name')?.toStringValue();
|
||||||
@@ -199,15 +164,65 @@ class InjectGenerator extends GeneratorForAnnotation<ann.injectable> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final DartType dartType = field.type;
|
final DartType dartType = field.type;
|
||||||
final parsedType = TypeParser.parseType(dartType, field);
|
String coreTypeName;
|
||||||
|
bool isFuture;
|
||||||
|
|
||||||
|
if (dartType.isDartAsyncFuture) {
|
||||||
|
final ParameterizedType paramType = dartType as ParameterizedType;
|
||||||
|
coreTypeName = paramType.typeArguments.first.getDisplayString();
|
||||||
|
isFuture = true;
|
||||||
|
} else {
|
||||||
|
coreTypeName = dartType.getDisplayString();
|
||||||
|
isFuture = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine nullability for field types like T? or Future<T?>
|
||||||
|
bool isNullable =
|
||||||
|
dartType.nullabilitySuffix == NullabilitySuffix.question ||
|
||||||
|
(dartType is ParameterizedType &&
|
||||||
|
(dartType).typeArguments.any(
|
||||||
|
(t) => t.nullabilitySuffix == NullabilitySuffix.question,
|
||||||
|
));
|
||||||
|
|
||||||
return _ParsedInjectField(
|
return _ParsedInjectField(
|
||||||
fieldName: field.firstFragment.name2 ?? '',
|
fieldName: field.firstFragment.name2 ?? '',
|
||||||
parsedType: parsedType,
|
coreType: coreTypeName.replaceAll('?', ''), // удаляем "?" на всякий
|
||||||
|
isFuture: isFuture,
|
||||||
|
isNullable: isNullable,
|
||||||
scopeName: scopeName,
|
scopeName: scopeName,
|
||||||
namedValue: namedValue,
|
namedValue: namedValue,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Generates Dart code for a single dependency-injected field based on its metadata.
|
||||||
|
///
|
||||||
|
/// This code will resolve the field from the CherryPick DI container and assign it to the class instance.
|
||||||
|
/// Correctly dispatches to resolve, tryResolve, resolveAsync, or tryResolveAsync methods,
|
||||||
|
/// and applies container scoping or named resolution where required.
|
||||||
|
///
|
||||||
|
/// Returns literal Dart code as string (1 line).
|
||||||
|
///
|
||||||
|
/// Example output:
|
||||||
|
/// `instance.logger = CherryPick.openRootScope().resolve<Logger>();`
|
||||||
|
String _generateInjectionLine(_ParsedInjectField field) {
|
||||||
|
final resolveMethod = field.isFuture
|
||||||
|
? (field.isNullable
|
||||||
|
? 'tryResolveAsync<${field.coreType}>'
|
||||||
|
: 'resolveAsync<${field.coreType}>')
|
||||||
|
: (field.isNullable
|
||||||
|
? 'tryResolve<${field.coreType}>'
|
||||||
|
: 'resolve<${field.coreType}>');
|
||||||
|
|
||||||
|
final openCall = (field.scopeName != null && field.scopeName!.isNotEmpty)
|
||||||
|
? "CherryPick.openScope(scopeName: '${field.scopeName}')"
|
||||||
|
: "CherryPick.openRootScope()";
|
||||||
|
|
||||||
|
final params = (field.namedValue != null && field.namedValue!.isNotEmpty)
|
||||||
|
? "(named: '${field.namedValue}')"
|
||||||
|
: '()';
|
||||||
|
|
||||||
|
return " instance.${field.fieldName} = $openCall.$resolveMethod$params;";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Internal structure: describes all required information for generating the injection
|
/// Internal structure: describes all required information for generating the injection
|
||||||
@@ -218,8 +233,14 @@ class _ParsedInjectField {
|
|||||||
/// The name of the field to be injected.
|
/// The name of the field to be injected.
|
||||||
final String fieldName;
|
final String fieldName;
|
||||||
|
|
||||||
/// Parsed type info for the field.
|
/// The Dart type to resolve (e.g. `Logger` from `Logger` or `Future<Logger>`).
|
||||||
final ParsedType parsedType;
|
final String coreType;
|
||||||
|
|
||||||
|
/// True if the field is an async dependency (Future<...>), otherwise false.
|
||||||
|
final bool isFuture;
|
||||||
|
|
||||||
|
/// True if the field accepts null (T?), otherwise false.
|
||||||
|
final bool isNullable;
|
||||||
|
|
||||||
/// The scoping for DI resolution, or null to use root scope.
|
/// The scoping for DI resolution, or null to use root scope.
|
||||||
final String? scopeName;
|
final String? scopeName;
|
||||||
@@ -229,7 +250,9 @@ class _ParsedInjectField {
|
|||||||
|
|
||||||
_ParsedInjectField({
|
_ParsedInjectField({
|
||||||
required this.fieldName,
|
required this.fieldName,
|
||||||
required this.parsedType,
|
required this.coreType,
|
||||||
|
required this.isFuture,
|
||||||
|
required this.isNullable,
|
||||||
this.scopeName,
|
this.scopeName,
|
||||||
this.namedValue,
|
this.namedValue,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,80 +0,0 @@
|
|||||||
//
|
|
||||||
// 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
|
|
||||||
// https://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:code_builder/code_builder.dart';
|
|
||||||
|
|
||||||
import 'type_parser.dart';
|
|
||||||
|
|
||||||
/// Small helpers for building code_builder AST nodes used by generators.
|
|
||||||
class CodeBuilderEmitters {
|
|
||||||
/// Builds a CherryPick scope opener expression.
|
|
||||||
///
|
|
||||||
/// - If [scopeName] is empty or null, uses openRootScope().
|
|
||||||
/// - Otherwise uses openScope(scopeName: ...).
|
|
||||||
static Expression openScope({String? scopeName}) {
|
|
||||||
if (scopeName == null || scopeName.isEmpty) {
|
|
||||||
return refer('CherryPick').property('openRootScope').call([]);
|
|
||||||
}
|
|
||||||
return refer(
|
|
||||||
'CherryPick',
|
|
||||||
).property('openScope').call([], {'scopeName': literalString(scopeName)});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Builds a TypeReference appropriate for resolving a dependency.
|
|
||||||
///
|
|
||||||
/// For Future<T>, it returns the inner type reference (T).
|
|
||||||
/// Nullability and generic arguments are preserved.
|
|
||||||
static TypeReference resolveTypeRef(ParsedType parsedType) {
|
|
||||||
final target = parsedType.isFuture && parsedType.innerType != null
|
|
||||||
? parsedType.innerType!
|
|
||||||
: parsedType;
|
|
||||||
return _typeRefFromParsedType(target, stripNullability: true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Builds a DI resolve call on [scopeExpr] using [parsedType] and [named].
|
|
||||||
///
|
|
||||||
/// The method name is derived from [ParsedType.resolveMethodName].
|
|
||||||
static Expression resolveCall({
|
|
||||||
required Expression scopeExpr,
|
|
||||||
required ParsedType parsedType,
|
|
||||||
String? named,
|
|
||||||
}) {
|
|
||||||
final typeRef = resolveTypeRef(parsedType);
|
|
||||||
final method = parsedType.resolveMethodName;
|
|
||||||
final args = <Expression>[];
|
|
||||||
final namedArgs = <String, Expression>{};
|
|
||||||
if (named != null && named.isNotEmpty) {
|
|
||||||
namedArgs['named'] = literalString(named);
|
|
||||||
}
|
|
||||||
return scopeExpr.property(method).call(args, namedArgs, [typeRef]);
|
|
||||||
}
|
|
||||||
|
|
||||||
static TypeReference _typeRefFromParsedType(
|
|
||||||
ParsedType parsedType, {
|
|
||||||
required bool stripNullability,
|
|
||||||
}) {
|
|
||||||
return TypeReference((b) {
|
|
||||||
b
|
|
||||||
..symbol = parsedType.coreType
|
|
||||||
..isNullable = stripNullability ? false : parsedType.isNullable;
|
|
||||||
if (parsedType.typeArguments.isNotEmpty) {
|
|
||||||
b.types.addAll(
|
|
||||||
parsedType.typeArguments.map(
|
|
||||||
(arg) =>
|
|
||||||
_typeRefFromParsedType(arg, stripNullability: stripNullability),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -65,8 +65,6 @@ class CherryPickGeneratorException extends InvalidGenerationSourceError {
|
|||||||
Element2 element,
|
Element2 element,
|
||||||
) {
|
) {
|
||||||
final buffer = StringBuffer();
|
final buffer = StringBuffer();
|
||||||
final location = _safeLocation(element);
|
|
||||||
final enclosing = _safeEnclosingDisplayName(element);
|
|
||||||
|
|
||||||
// Header with category
|
// Header with category
|
||||||
buffer.writeln('[$category] $message');
|
buffer.writeln('[$category] $message');
|
||||||
@@ -76,11 +74,18 @@ class CherryPickGeneratorException extends InvalidGenerationSourceError {
|
|||||||
buffer.writeln('Context:');
|
buffer.writeln('Context:');
|
||||||
buffer.writeln(' Element: ${element.displayName}');
|
buffer.writeln(' Element: ${element.displayName}');
|
||||||
buffer.writeln(' Type: ${element.runtimeType}');
|
buffer.writeln(' Type: ${element.runtimeType}');
|
||||||
buffer.writeln(' Location: $location');
|
buffer.writeln(
|
||||||
|
' Location: ${element.firstFragment.libraryFragment?.source.fullName ?? 'unknown'}',
|
||||||
|
);
|
||||||
|
|
||||||
// Try to show enclosing element info for extra context
|
// Try to show enclosing element info for extra context
|
||||||
if (enclosing != null) {
|
try {
|
||||||
buffer.writeln(' Enclosing: $enclosing');
|
final enclosing = (element as dynamic).enclosingElement;
|
||||||
|
if (enclosing != null) {
|
||||||
|
buffer.writeln(' Enclosing: ${enclosing.displayName}');
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// Ignore if enclosingElement is not available
|
||||||
}
|
}
|
||||||
|
|
||||||
// Arbitrary user context
|
// Arbitrary user context
|
||||||
@@ -100,34 +105,6 @@ class CherryPickGeneratorException extends InvalidGenerationSourceError {
|
|||||||
|
|
||||||
return buffer.toString();
|
return buffer.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Best-effort extraction of element location for diagnostics.
|
|
||||||
///
|
|
||||||
/// Some tests use lightweight mocks for [Element2] that don't implement
|
|
||||||
/// analyzer fragment APIs, so this method must never throw.
|
|
||||||
static String _safeLocation(Element2 element) {
|
|
||||||
try {
|
|
||||||
return element.firstFragment.libraryFragment?.source.fullName ??
|
|
||||||
'unknown';
|
|
||||||
} catch (_) {
|
|
||||||
return 'unknown';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Best-effort extraction of enclosing element display name.
|
|
||||||
///
|
|
||||||
/// Accessed via dynamic to stay compatible with analyzer API differences.
|
|
||||||
static String? _safeEnclosingDisplayName(Element2 element) {
|
|
||||||
try {
|
|
||||||
final enclosing = (element as dynamic).enclosingElement;
|
|
||||||
if (enclosing == null) return null;
|
|
||||||
final name = enclosing.displayName;
|
|
||||||
if (name is String && name.isNotEmpty) return name;
|
|
||||||
return enclosing.toString();
|
|
||||||
} catch (_) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// ---------------------------------------------------------------------------
|
/// ---------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -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: 4.0.0-dev.1
|
version: 4.0.0-dev.0
|
||||||
homepage: https://cherrypick-di.netlify.app
|
homepage: https://cherrypick-di.netlify.app
|
||||||
documentation: https://cherrypick-di.netlify.app/docs/intro
|
documentation: https://cherrypick-di.netlify.app/docs/intro
|
||||||
repository: https://github.com/pese-git/cherrypick/cherrypick_generator
|
repository: https://github.com/pese-git/cherrypick/cherrypick_generator
|
||||||
@@ -22,12 +22,9 @@ dependencies:
|
|||||||
cherrypick_annotations: ^4.0.0-dev.0
|
cherrypick_annotations: ^4.0.0-dev.0
|
||||||
analyzer: ">=8.2.9 <10.0.1"
|
analyzer: ">=8.2.9 <10.0.1"
|
||||||
dart_style: ^3.0.0
|
dart_style: ^3.0.0
|
||||||
code_builder: ^4.10.1
|
|
||||||
build: ^3.0.0
|
build: ^3.0.0
|
||||||
source_gen: ^4.2.0
|
source_gen: ^4.2.0
|
||||||
collection: ^1.18.0
|
collection: ^1.18.0
|
||||||
package_config: ^2.2.0
|
|
||||||
build_runner_core: ^9.3.1
|
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
lints: ^6.0.0
|
lints: ^6.0.0
|
||||||
|
|||||||
32
cherrypick_generator/test/cherrypick_generator_test.dart
Normal file
32
cherrypick_generator/test/cherrypick_generator_test.dart
Normal file
@@ -0,0 +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
|
||||||
|
// https://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 working test suites
|
||||||
|
import 'simple_test.dart' as simple_tests;
|
||||||
|
import 'bind_spec_test.dart' as bind_spec_tests;
|
||||||
|
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;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -11,13 +11,9 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
|
|
||||||
import 'dart:isolate';
|
|
||||||
|
|
||||||
import 'package:build/build.dart';
|
import 'package:build/build.dart';
|
||||||
import 'package:build_test/build_test.dart';
|
import 'package:build_test/build_test.dart';
|
||||||
import 'package:build_runner_core/build_runner_core.dart';
|
|
||||||
import 'package:cherrypick_generator/inject_generator.dart';
|
import 'package:cherrypick_generator/inject_generator.dart';
|
||||||
import 'package:package_config/package_config.dart';
|
|
||||||
import 'package:source_gen/source_gen.dart';
|
import 'package:source_gen/source_gen.dart';
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
@@ -44,8 +40,8 @@ class TestWidget {
|
|||||||
''';
|
''';
|
||||||
|
|
||||||
const expectedOutput = '''
|
const expectedOutput = '''
|
||||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
|
||||||
// dart format width=80
|
// dart format width=80
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
part of 'test_widget.dart';
|
part of 'test_widget.dart';
|
||||||
|
|
||||||
@@ -79,8 +75,8 @@ class TestWidget {
|
|||||||
''';
|
''';
|
||||||
|
|
||||||
const expectedOutput = '''
|
const expectedOutput = '''
|
||||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
|
||||||
// dart format width=80
|
// dart format width=80
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
part of 'test_widget.dart';
|
part of 'test_widget.dart';
|
||||||
|
|
||||||
@@ -117,8 +113,8 @@ class TestWidget {
|
|||||||
''';
|
''';
|
||||||
|
|
||||||
const expectedOutput = '''
|
const expectedOutput = '''
|
||||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
|
||||||
// dart format width=80
|
// dart format width=80
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
part of 'test_widget.dart';
|
part of 'test_widget.dart';
|
||||||
|
|
||||||
@@ -128,8 +124,9 @@ part of 'test_widget.dart';
|
|||||||
|
|
||||||
mixin _\$TestWidget {
|
mixin _\$TestWidget {
|
||||||
void _inject(TestWidget instance) {
|
void _inject(TestWidget instance) {
|
||||||
instance.service =
|
instance.service = CherryPick.openRootScope().resolve<MyService>(
|
||||||
CherryPick.openRootScope().resolve<MyService>(named: 'myService');
|
named: 'myService',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
''';
|
''';
|
||||||
@@ -154,8 +151,8 @@ class TestWidget {
|
|||||||
''';
|
''';
|
||||||
|
|
||||||
const expectedOutput = '''
|
const expectedOutput = '''
|
||||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
|
||||||
// dart format width=80
|
// dart format width=80
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
part of 'test_widget.dart';
|
part of 'test_widget.dart';
|
||||||
|
|
||||||
@@ -165,8 +162,9 @@ part of 'test_widget.dart';
|
|||||||
|
|
||||||
mixin _\$TestWidget {
|
mixin _\$TestWidget {
|
||||||
void _inject(TestWidget instance) {
|
void _inject(TestWidget instance) {
|
||||||
instance.service =
|
instance.service = CherryPick.openRootScope().tryResolve<MyService>(
|
||||||
CherryPick.openRootScope().tryResolve<MyService>(named: 'myService');
|
named: 'myService',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
''';
|
''';
|
||||||
@@ -193,8 +191,8 @@ class TestWidget {
|
|||||||
''';
|
''';
|
||||||
|
|
||||||
const expectedOutput = '''
|
const expectedOutput = '''
|
||||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
|
||||||
// dart format width=80
|
// dart format width=80
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
part of 'test_widget.dart';
|
part of 'test_widget.dart';
|
||||||
|
|
||||||
@@ -231,8 +229,8 @@ class TestWidget {
|
|||||||
''';
|
''';
|
||||||
|
|
||||||
const expectedOutput = '''
|
const expectedOutput = '''
|
||||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
|
||||||
// dart format width=80
|
// dart format width=80
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
part of 'test_widget.dart';
|
part of 'test_widget.dart';
|
||||||
|
|
||||||
@@ -242,9 +240,9 @@ part of 'test_widget.dart';
|
|||||||
|
|
||||||
mixin _\$TestWidget {
|
mixin _\$TestWidget {
|
||||||
void _inject(TestWidget instance) {
|
void _inject(TestWidget instance) {
|
||||||
instance.service =
|
instance.service = CherryPick.openScope(
|
||||||
CherryPick.openScope(scopeName: 'userScope')
|
scopeName: 'userScope',
|
||||||
.resolve<MyService>(named: 'myService');
|
).resolve<MyService>(named: 'myService');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
''';
|
''';
|
||||||
@@ -270,8 +268,8 @@ class TestWidget {
|
|||||||
''';
|
''';
|
||||||
|
|
||||||
const expectedOutput = '''
|
const expectedOutput = '''
|
||||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
|
||||||
// dart format width=80
|
// dart format width=80
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
part of 'test_widget.dart';
|
part of 'test_widget.dart';
|
||||||
|
|
||||||
@@ -305,8 +303,8 @@ class TestWidget {
|
|||||||
''';
|
''';
|
||||||
|
|
||||||
const expectedOutput = '''
|
const expectedOutput = '''
|
||||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
|
||||||
// dart format width=80
|
// dart format width=80
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
part of 'test_widget.dart';
|
part of 'test_widget.dart';
|
||||||
|
|
||||||
@@ -341,8 +339,8 @@ class TestWidget {
|
|||||||
''';
|
''';
|
||||||
|
|
||||||
const expectedOutput = '''
|
const expectedOutput = '''
|
||||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
|
||||||
// dart format width=80
|
// dart format width=80
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
part of 'test_widget.dart';
|
part of 'test_widget.dart';
|
||||||
|
|
||||||
@@ -352,8 +350,9 @@ part of 'test_widget.dart';
|
|||||||
|
|
||||||
mixin _\$TestWidget {
|
mixin _\$TestWidget {
|
||||||
void _inject(TestWidget instance) {
|
void _inject(TestWidget instance) {
|
||||||
instance.service =
|
instance.service = CherryPick.openRootScope().resolveAsync<MyService>(
|
||||||
CherryPick.openRootScope().resolveAsync<MyService>(named: 'myService');
|
named: 'myService',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
''';
|
''';
|
||||||
@@ -392,8 +391,8 @@ class TestWidget {
|
|||||||
''';
|
''';
|
||||||
|
|
||||||
const expectedOutput = '''
|
const expectedOutput = '''
|
||||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
|
||||||
// dart format width=80
|
// dart format width=80
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
part of 'test_widget.dart';
|
part of 'test_widget.dart';
|
||||||
|
|
||||||
@@ -404,11 +403,13 @@ part of 'test_widget.dart';
|
|||||||
mixin _\$TestWidget {
|
mixin _\$TestWidget {
|
||||||
void _inject(TestWidget instance) {
|
void _inject(TestWidget instance) {
|
||||||
instance.apiService = CherryPick.openRootScope().resolve<ApiService>();
|
instance.apiService = CherryPick.openRootScope().resolve<ApiService>();
|
||||||
instance.cacheService =
|
instance.cacheService = CherryPick.openRootScope().tryResolve<CacheService>(
|
||||||
CherryPick.openRootScope().tryResolve<CacheService>(named: 'cache');
|
named: 'cache',
|
||||||
|
);
|
||||||
instance.dbService =
|
instance.dbService =
|
||||||
CherryPick.openScope(scopeName: 'dbScope')
|
CherryPick.openScope(
|
||||||
.resolveAsync<DatabaseService>();
|
scopeName: 'dbScope',
|
||||||
|
).resolveAsync<DatabaseService>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
''';
|
''';
|
||||||
@@ -438,8 +439,8 @@ class TestWidget {
|
|||||||
''';
|
''';
|
||||||
|
|
||||||
const expectedOutput = '''
|
const expectedOutput = '''
|
||||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
|
||||||
// dart format width=80
|
// dart format width=80
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
part of 'test_widget.dart';
|
part of 'test_widget.dart';
|
||||||
|
|
||||||
@@ -495,8 +496,8 @@ class TestWidget {
|
|||||||
''';
|
''';
|
||||||
|
|
||||||
const expectedOutput = '''
|
const expectedOutput = '''
|
||||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
|
||||||
// dart format width=80
|
// dart format width=80
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
part of 'test_widget.dart';
|
part of 'test_widget.dart';
|
||||||
|
|
||||||
@@ -532,8 +533,8 @@ class TestWidget {
|
|||||||
''';
|
''';
|
||||||
|
|
||||||
const expectedOutput = '''
|
const expectedOutput = '''
|
||||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
|
||||||
// dart format width=80
|
// dart format width=80
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
part of 'test_widget.dart';
|
part of 'test_widget.dart';
|
||||||
|
|
||||||
@@ -568,8 +569,8 @@ class TestWidget {
|
|||||||
''';
|
''';
|
||||||
|
|
||||||
const expectedOutput = '''
|
const expectedOutput = '''
|
||||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
|
||||||
// dart format width=80
|
// dart format width=80
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
part of 'test_widget.dart';
|
part of 'test_widget.dart';
|
||||||
|
|
||||||
@@ -592,41 +593,10 @@ mixin _\$TestWidget {
|
|||||||
|
|
||||||
/// Helper function to test code generation
|
/// Helper function to test code generation
|
||||||
Future<void> _testGeneration(String input, String expectedOutput) async {
|
Future<void> _testGeneration(String input, String expectedOutput) async {
|
||||||
final readerWriter = TestReaderWriter(rootPackage: 'a');
|
await testBuilder(
|
||||||
await readerWriter.testing.loadIsolateSources();
|
|
||||||
final packageConfig = await loadPackageConfigUri(
|
|
||||||
(await Isolate.packageConfig)!,
|
|
||||||
);
|
|
||||||
final outputs = expectedOutput.isEmpty
|
|
||||||
? null
|
|
||||||
: {
|
|
||||||
'a|lib/test_widget.inject.cherrypick.g.dart':
|
|
||||||
decodedMatches(_normalizedEquals(expectedOutput)),
|
|
||||||
};
|
|
||||||
final result = await testBuilder(
|
|
||||||
injectBuilder(BuilderOptions.empty),
|
injectBuilder(BuilderOptions.empty),
|
||||||
{'a|lib/test_widget.dart': input},
|
{'a|lib/test_widget.dart': input},
|
||||||
outputs: outputs,
|
outputs: {'a|lib/test_widget.inject.cherrypick.g.dart': expectedOutput},
|
||||||
readerWriter: readerWriter,
|
readerWriter: TestReaderWriter(),
|
||||||
rootPackage: 'a',
|
|
||||||
packageConfig: packageConfig,
|
|
||||||
);
|
|
||||||
if (expectedOutput.isEmpty && result.buildResult.status == BuildStatus.failure) {
|
|
||||||
throw InvalidGenerationSourceError('Build failed');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Matcher _normalizedEquals(String expected) {
|
|
||||||
return predicate<String>(
|
|
||||||
(actual) => _normalize(actual) == _normalize(expected),
|
|
||||||
'matches after normalization',
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
String _normalize(String input) {
|
|
||||||
return input
|
|
||||||
.replaceAll(RegExp(r'\s+'), '')
|
|
||||||
.replaceAll(RegExp(r',\)'), ')')
|
|
||||||
.replaceAll(RegExp(r',\]'), ']')
|
|
||||||
.replaceAll(RegExp(r',\}'), '}');
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -11,16 +11,12 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
|
|
||||||
import 'dart:isolate';
|
|
||||||
|
|
||||||
import 'package:build/build.dart';
|
|
||||||
import 'package:build_test/build_test.dart';
|
|
||||||
import 'package:build_runner_core/build_runner_core.dart';
|
|
||||||
import 'package:package_config/package_config.dart';
|
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
import 'package:build_test/build_test.dart';
|
||||||
|
import 'package:build/build.dart';
|
||||||
|
|
||||||
import 'package:source_gen/source_gen.dart';
|
|
||||||
import 'package:cherrypick_generator/module_generator.dart';
|
import 'package:cherrypick_generator/module_generator.dart';
|
||||||
|
import 'package:source_gen/source_gen.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
group('ModuleGenerator Tests', () {
|
group('ModuleGenerator Tests', () {
|
||||||
@@ -44,8 +40,8 @@ abstract class TestModule extends Module {
|
|||||||
''';
|
''';
|
||||||
|
|
||||||
const expectedOutput = '''
|
const expectedOutput = '''
|
||||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
|
||||||
// dart format width=80
|
// dart format width=80
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
part of 'test_module.dart';
|
part of 'test_module.dart';
|
||||||
|
|
||||||
@@ -79,8 +75,8 @@ abstract class TestModule extends Module {
|
|||||||
''';
|
''';
|
||||||
|
|
||||||
const expectedOutput = '''
|
const expectedOutput = '''
|
||||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
|
||||||
// dart format width=80
|
// dart format width=80
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
part of 'test_module.dart';
|
part of 'test_module.dart';
|
||||||
|
|
||||||
@@ -117,8 +113,8 @@ abstract class TestModule extends Module {
|
|||||||
''';
|
''';
|
||||||
|
|
||||||
const expectedOutput = '''
|
const expectedOutput = '''
|
||||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
|
||||||
// dart format width=80
|
// dart format width=80
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
part of 'test_module.dart';
|
part of 'test_module.dart';
|
||||||
|
|
||||||
@@ -153,8 +149,8 @@ abstract class TestModule extends Module {
|
|||||||
''';
|
''';
|
||||||
|
|
||||||
const expectedOutput = '''
|
const expectedOutput = '''
|
||||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
|
||||||
// dart format width=80
|
// dart format width=80
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
part of 'test_module.dart';
|
part of 'test_module.dart';
|
||||||
|
|
||||||
@@ -191,8 +187,8 @@ abstract class TestModule extends Module {
|
|||||||
''';
|
''';
|
||||||
|
|
||||||
const expectedOutput = '''
|
const expectedOutput = '''
|
||||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
|
||||||
// dart format width=80
|
// dart format width=80
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
part of 'test_module.dart';
|
part of 'test_module.dart';
|
||||||
|
|
||||||
@@ -228,8 +224,8 @@ abstract class TestModule extends Module {
|
|||||||
''';
|
''';
|
||||||
|
|
||||||
const expectedOutput = '''
|
const expectedOutput = '''
|
||||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
|
||||||
// dart format width=80
|
// dart format width=80
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
part of 'test_module.dart';
|
part of 'test_module.dart';
|
||||||
|
|
||||||
@@ -268,8 +264,8 @@ abstract class TestModule extends Module {
|
|||||||
''';
|
''';
|
||||||
|
|
||||||
const expectedOutput = '''
|
const expectedOutput = '''
|
||||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
|
||||||
// dart format width=80
|
// dart format width=80
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
part of 'test_module.dart';
|
part of 'test_module.dart';
|
||||||
|
|
||||||
@@ -303,8 +299,8 @@ abstract class TestModule extends Module {
|
|||||||
''';
|
''';
|
||||||
|
|
||||||
const expectedOutput = '''
|
const expectedOutput = '''
|
||||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
|
||||||
// dart format width=80
|
// dart format width=80
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
part of 'test_module.dart';
|
part of 'test_module.dart';
|
||||||
|
|
||||||
@@ -338,8 +334,8 @@ abstract class TestModule extends Module {
|
|||||||
''';
|
''';
|
||||||
|
|
||||||
const expectedOutput = '''
|
const expectedOutput = '''
|
||||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
|
||||||
// dart format width=80
|
// dart format width=80
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
part of 'test_module.dart';
|
part of 'test_module.dart';
|
||||||
|
|
||||||
@@ -378,8 +374,8 @@ abstract class TestModule extends Module {
|
|||||||
''';
|
''';
|
||||||
|
|
||||||
const expectedOutput = '''
|
const expectedOutput = '''
|
||||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
|
||||||
// dart format width=80
|
// dart format width=80
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
part of 'test_module.dart';
|
part of 'test_module.dart';
|
||||||
|
|
||||||
@@ -418,8 +414,8 @@ abstract class TestModule extends Module {
|
|||||||
''';
|
''';
|
||||||
|
|
||||||
const expectedOutput = '''
|
const expectedOutput = '''
|
||||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
|
||||||
// dart format width=80
|
// dart format width=80
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
part of 'test_module.dart';
|
part of 'test_module.dart';
|
||||||
|
|
||||||
@@ -457,8 +453,8 @@ abstract class TestModule extends Module {
|
|||||||
''';
|
''';
|
||||||
|
|
||||||
const expectedOutput = '''
|
const expectedOutput = '''
|
||||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
|
||||||
// dart format width=80
|
// dart format width=80
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
part of 'test_module.dart';
|
part of 'test_module.dart';
|
||||||
|
|
||||||
@@ -492,8 +488,8 @@ abstract class TestModule extends Module {
|
|||||||
''';
|
''';
|
||||||
|
|
||||||
const expectedOutput = '''
|
const expectedOutput = '''
|
||||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
|
||||||
// dart format width=80
|
// dart format width=80
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
part of 'test_module.dart';
|
part of 'test_module.dart';
|
||||||
|
|
||||||
@@ -545,8 +541,8 @@ abstract class TestModule extends Module {
|
|||||||
''';
|
''';
|
||||||
|
|
||||||
const expectedOutput = '''
|
const expectedOutput = '''
|
||||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
|
||||||
// dart format width=80
|
// dart format width=80
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
part of 'test_module.dart';
|
part of 'test_module.dart';
|
||||||
|
|
||||||
@@ -641,41 +637,10 @@ abstract class TestModule extends Module {
|
|||||||
|
|
||||||
/// Helper function to test code generation
|
/// Helper function to test code generation
|
||||||
Future<void> _testGeneration(String input, String expectedOutput) async {
|
Future<void> _testGeneration(String input, String expectedOutput) async {
|
||||||
final readerWriter = TestReaderWriter(rootPackage: 'a');
|
await testBuilder(
|
||||||
await readerWriter.testing.loadIsolateSources();
|
|
||||||
final packageConfig = await loadPackageConfigUri(
|
|
||||||
(await Isolate.packageConfig)!,
|
|
||||||
);
|
|
||||||
final outputs = expectedOutput.isEmpty
|
|
||||||
? null
|
|
||||||
: {
|
|
||||||
'a|lib/test_module.module.cherrypick.g.dart':
|
|
||||||
decodedMatches(_normalizedEquals(expectedOutput)),
|
|
||||||
};
|
|
||||||
final result = await testBuilder(
|
|
||||||
moduleBuilder(BuilderOptions.empty),
|
moduleBuilder(BuilderOptions.empty),
|
||||||
{'a|lib/test_module.dart': input},
|
{'a|lib/test_module.dart': input},
|
||||||
outputs: outputs,
|
outputs: {'a|lib/test_module.module.cherrypick.g.dart': expectedOutput},
|
||||||
readerWriter: readerWriter,
|
readerWriter: TestReaderWriter(),
|
||||||
rootPackage: 'a',
|
|
||||||
packageConfig: packageConfig,
|
|
||||||
);
|
|
||||||
if (expectedOutput.isEmpty && result.buildResult.status == BuildStatus.failure) {
|
|
||||||
throw InvalidGenerationSourceError('Build failed');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Matcher _normalizedEquals(String expected) {
|
|
||||||
return predicate<String>(
|
|
||||||
(actual) => _normalize(actual) == _normalize(expected),
|
|
||||||
'matches after normalization',
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
String _normalize(String input) {
|
|
||||||
return input
|
|
||||||
.replaceAll(RegExp(r'\s+'), '')
|
|
||||||
.replaceAll(RegExp(r',\)'), ')')
|
|
||||||
.replaceAll(RegExp(r',\]'), ']')
|
|
||||||
.replaceAll(RegExp(r',\}'), '}');
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -550,4 +550,4 @@ void main() {
|
|||||||
- [cherrypick](https://pub.dev/packages/cherrypick)
|
- [cherrypick](https://pub.dev/packages/cherrypick)
|
||||||
- [cherrypick_annotations](https://pub.dev/packages/cherrypick_annotations)
|
- [cherrypick_annotations](https://pub.dev/packages/cherrypick_annotations)
|
||||||
- [cherrypick_generator](https://pub.dev/packages/cherrypick_generator)
|
- [cherrypick_generator](https://pub.dev/packages/cherrypick_generator)
|
||||||
- [Исходники на GitHub](https://github.com/pese-git/cherrypick)
|
- [Исходники на GitHub](https://github.com/pese-git/cherrypick)
|
||||||
@@ -255,4 +255,4 @@ class ApiClientImpl implements ApiClient {
|
|||||||
return 'Network data';
|
return 'Network data';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -134,7 +134,7 @@ packages:
|
|||||||
path: "../../cherrypick_annotations"
|
path: "../../cherrypick_annotations"
|
||||||
relative: true
|
relative: true
|
||||||
source: path
|
source: path
|
||||||
version: "4.0.0-dev.0"
|
version: "3.0.2"
|
||||||
cherrypick_flutter:
|
cherrypick_flutter:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -148,7 +148,7 @@ packages:
|
|||||||
path: "../../cherrypick_generator"
|
path: "../../cherrypick_generator"
|
||||||
relative: true
|
relative: true
|
||||||
source: path
|
source: path
|
||||||
version: "4.0.0-dev.0"
|
version: "3.0.2"
|
||||||
clock:
|
clock:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
@@ -182,14 +182,14 @@ packages:
|
|||||||
path: "../../cherrypick_annotations"
|
path: "../../cherrypick_annotations"
|
||||||
relative: true
|
relative: true
|
||||||
source: path
|
source: path
|
||||||
version: "4.0.0-dev.0"
|
version: "3.0.2"
|
||||||
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: "4.0.0-dev.0"
|
version: "3.0.2"
|
||||||
cli_launcher:
|
cli_launcher:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ dependencies:
|
|||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
|
||||||
cherrypick: ^3.0.3
|
cherrypick: any
|
||||||
cherrypick_annotations: ^4.0.0-dev.0
|
cherrypick_annotations: ^4.0.0-dev.0
|
||||||
|
|
||||||
dio: ^5.4.0
|
dio: ^5.4.0
|
||||||
@@ -27,7 +27,7 @@ dependencies:
|
|||||||
cupertino_icons: ^1.0.8
|
cupertino_icons: ^1.0.8
|
||||||
|
|
||||||
talker_flutter: ^5.0.0
|
talker_flutter: ^5.0.0
|
||||||
talker_cherrypick_logger: ^3.0.3
|
talker_cherrypick_logger: any
|
||||||
talker_dio_logger: ^5.0.0
|
talker_dio_logger: ^5.0.0
|
||||||
talker_bloc_logger: ^5.0.0
|
talker_bloc_logger: ^5.0.0
|
||||||
|
|
||||||
@@ -38,7 +38,7 @@ dev_dependencies:
|
|||||||
flutter_lints: ^6.0.0
|
flutter_lints: ^6.0.0
|
||||||
|
|
||||||
build_runner: ^2.5.0
|
build_runner: ^2.5.0
|
||||||
cherrypick_generator: ^4.0.0-dev.1
|
cherrypick_generator: ^4.0.0-dev.0
|
||||||
|
|
||||||
json_serializable: ^6.9.0
|
json_serializable: ^6.9.0
|
||||||
retrofit_generator: ^10.0.5
|
retrofit_generator: ^10.0.5
|
||||||
|
|||||||
@@ -60,10 +60,8 @@ scripts:
|
|||||||
scope: ["cherrypick_flutter"]
|
scope: ["cherrypick_flutter"]
|
||||||
|
|
||||||
codegen:
|
codegen:
|
||||||
description: "Run build for Flutter packages only"
|
run: |
|
||||||
exec: dart run build_runner build --delete-conflicting-outputs
|
melos exec --scope="postly" -- dart run build_runner build --delete-conflicting-outputs
|
||||||
packageFilters:
|
|
||||||
scope: ["postly"]
|
|
||||||
|
|
||||||
outdated:
|
outdated:
|
||||||
exec: dart pub outdated
|
exec: dart pub outdated
|
||||||
|
|||||||
@@ -64,10 +64,14 @@
|
|||||||
- **WHEN** метод помечен одновременно `@instance` и `@provide`
|
- **WHEN** метод помечен одновременно `@instance` и `@provide`
|
||||||
- **THEN** генератор завершает сборку с ошибкой валидации
|
- **THEN** генератор завершает сборку с ошибкой валидации
|
||||||
|
|
||||||
#### Scenario: Требования к @named
|
#### Scenario: Требования к @named на provider-методе
|
||||||
- **WHEN** `@named` использует пустую строку или некорректный идентификатор
|
- **WHEN** `@named` на provider-методе использует пустую строку или некорректный идентификатор
|
||||||
- **THEN** генератор завершает сборку с ошибкой валидации
|
- **THEN** генератор завершает сборку с ошибкой валидации
|
||||||
|
|
||||||
|
#### Scenario: Пустой @named на inject-поле
|
||||||
|
- **WHEN** `@named('')` указан на поле с `@inject`
|
||||||
|
- **THEN** генератор трактует поле как безымянный резолв (без параметра `named`)
|
||||||
|
|
||||||
#### Scenario: Валидность @module
|
#### Scenario: Валидность @module
|
||||||
- **WHEN** класс с `@module` не имеет публичных методов
|
- **WHEN** класс с `@module` не имеет публичных методов
|
||||||
- **THEN** генератор завершает сборку с ошибкой валидации
|
- **THEN** генератор завершает сборку с ошибкой валидации
|
||||||
|
|||||||
@@ -23,11 +23,11 @@
|
|||||||
- **THEN** subscope удаляется из дерева, а связанные ресурсы освобождаются
|
- **THEN** subscope удаляется из дерева, а связанные ресурсы освобождаются
|
||||||
|
|
||||||
#### Scenario: Путь scope и разделитель
|
#### Scenario: Путь scope и разделитель
|
||||||
- **WHEN** scope открывается по иерархическому пути с разделителем
|
- **WHEN** вызывается `CherryPick.openScope(scopeName: ..., separator: ...)` с иерархическим путем
|
||||||
- **THEN** создается цепочка subscopes по каждому сегменту пути
|
- **THEN** создается цепочка subscopes по каждому сегменту пути
|
||||||
|
|
||||||
#### Scenario: Пустой scopeName
|
#### Scenario: Пустой scopeName
|
||||||
- **WHEN** scope открывается с пустым именем
|
- **WHEN** вызывается `CherryPick.openScope(scopeName: '')`
|
||||||
- **THEN** возвращается root scope
|
- **THEN** возвращается root scope
|
||||||
|
|
||||||
### Requirement: Установка и удаление модулей
|
### Requirement: Установка и удаление модулей
|
||||||
@@ -81,8 +81,8 @@
|
|||||||
### Requirement: Ошибки несоответствия sync/async
|
### Requirement: Ошибки несоответствия sync/async
|
||||||
Резолв MUST выбрасывать ошибки при несоответствии sync/async режима.
|
Резолв MUST выбрасывать ошибки при несоответствии sync/async режима.
|
||||||
|
|
||||||
#### Scenario: ResolveSync для async‑инстанса
|
#### Scenario: Синхронный резолв для async‑binding
|
||||||
- **WHEN** binding зарегистрирован как async‑инстанс или async‑provider, а вызывается `resolveSync`
|
- **WHEN** binding зарегистрирован как async‑инстанс или async‑provider, а вызывается `resolve<T>()` или `tryResolve<T>()`
|
||||||
- **THEN** выбрасывается ошибка с указанием использовать async‑резолв
|
- **THEN** выбрасывается ошибка с указанием использовать async‑резолв
|
||||||
|
|
||||||
### Requirement: Управление Disposable
|
### Requirement: Управление Disposable
|
||||||
@@ -111,7 +111,7 @@
|
|||||||
- **THEN** observer получает соответствующие уведомления
|
- **THEN** observer получает соответствующие уведомления
|
||||||
|
|
||||||
### Requirement: Ошибки и сообщения об ошибках
|
### Requirement: Ошибки и сообщения об ошибках
|
||||||
При критических сбоях резолва ядро MUST выбрасывать ошибку с понятным сообщением, а для tryResolve MUST не бросать исключения.
|
При критических сбоях резолва ядро MUST выбрасывать ошибку с понятным сообщением. Для отсутствующей зависимости `tryResolve`/`tryResolveAsync` MUST возвращать `null` без исключения; ошибки выполнения резолва (например, цикл, sync/async mismatch, отсутствие обязательных params) MAY быть проброшены.
|
||||||
|
|
||||||
#### Scenario: Ошибка отсутствующей зависимости
|
#### Scenario: Ошибка отсутствующей зависимости
|
||||||
- **WHEN** вызывается `resolve<T>()` для незарегистрированной зависимости
|
- **WHEN** вызывается `resolve<T>()` для незарегистрированной зависимости
|
||||||
|
|||||||
@@ -41,14 +41,14 @@ Flutter‑интеграция MUST предоставлять `CherryPickProvid
|
|||||||
|
|
||||||
#### Scenario: Ошибка lookup
|
#### Scenario: Ошибка lookup
|
||||||
- **WHEN** вызов происходит вне поддерева провайдера
|
- **WHEN** вызов происходит вне поддерева провайдера
|
||||||
- **THEN** происходит assertion‑ошибка
|
- **THEN** в debug‑режиме происходит assertion‑ошибка
|
||||||
|
|
||||||
### Requirement: Ошибки и сообщения
|
### Requirement: Ошибки и сообщения
|
||||||
При отсутствии провайдера в дереве MUST быть диагностируемая ошибка.
|
При отсутствии провайдера в дереве MUST быть диагностируемая ошибка.
|
||||||
|
|
||||||
#### Scenario: Диагностика отсутствия провайдера
|
#### Scenario: Диагностика отсутствия провайдера
|
||||||
- **WHEN** `CherryPickProvider.of(context)` не находит провайдер
|
- **WHEN** `CherryPickProvider.of(context)` не находит провайдер
|
||||||
- **THEN** сообщение об ошибке указывает на отсутствие провайдера
|
- **THEN** в debug‑режиме сообщение assertion указывает на отсутствие провайдера
|
||||||
|
|
||||||
### Requirement: Точки расширения
|
### Requirement: Точки расширения
|
||||||
Flutter‑интеграция MUST позволять использовать собственные DI‑scope стратегии поверх `CherryPickProvider`.
|
Flutter‑интеграция MUST позволять использовать собственные DI‑scope стратегии поверх `CherryPickProvider`.
|
||||||
|
|||||||
@@ -1,7 +1,3 @@
|
|||||||
## 3.0.3
|
|
||||||
|
|
||||||
- Update a dependency to the latest release.
|
|
||||||
|
|
||||||
## 3.0.2
|
## 3.0.2
|
||||||
|
|
||||||
- Update a dependency to the latest release.
|
- Update a dependency to the latest release.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
name: talker_cherrypick_logger
|
name: talker_cherrypick_logger
|
||||||
description: A Talker logger integration for CherryPick DI to observe and log DI events and errors.
|
description: A Talker logger integration for CherryPick DI to observe and log DI events and errors.
|
||||||
version: 3.0.3
|
version: 3.0.2
|
||||||
homepage: https://cherrypick-di.netlify.app
|
homepage: https://cherrypick-di.netlify.app
|
||||||
documentation: https://cherrypick-di.netlify.app/docs/intro
|
documentation: https://cherrypick-di.netlify.app/docs/intro
|
||||||
repository: https://github.com/pese-git/cherrypick
|
repository: https://github.com/pese-git/cherrypick
|
||||||
@@ -18,7 +18,7 @@ environment:
|
|||||||
# Add regular dependencies here.
|
# Add regular dependencies here.
|
||||||
dependencies:
|
dependencies:
|
||||||
talker: ^5.0.0
|
talker: ^5.0.0
|
||||||
cherrypick: ^3.0.3
|
cherrypick: ^3.0.2
|
||||||
|
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
|
|||||||
@@ -38,4 +38,4 @@ See [Installation](installation.md) for instructions on adding CherryPick to you
|
|||||||
|
|
||||||
CherryPick is open-source. Contributions and questions are welcome!
|
CherryPick is open-source. Contributions and questions are welcome!
|
||||||
|
|
||||||
---
|
---
|
||||||
Reference in New Issue
Block a user