Compare commits

...

10 Commits

Author SHA1 Message Date
Sergey Penkovsky
b4ca6da080 chore(release): publish packages
- cherrypick@3.0.3
 - cherrypick_generator@4.0.0-dev.1
 - cherrypick_flutter@3.0.3
 - talker_cherrypick_logger@3.0.3
2026-04-28 23:52:01 +03:00
Sergey Penkovsky
4b8f5fe421 fix: fix melos script 2026-04-28 23:41:09 +03:00
Sergey Penkovsky
629d2ed504 fix: add deps 2026-04-28 23:35:27 +03:00
Sergey Penkovsky
9cabf98ffb fix: fix melos script 2026-04-28 23:32:28 +03:00
Sergey Penkovsky
8efdcbc918 Merge pull request #29 from pese-git/ref/code_builder
Ref/code builder
2026-04-28 23:12:43 +03:00
Sergey Penkovsky
d4fbbdcc1f Merge pull request #30 from pese-git/issues/26
Issues/26
2026-04-28 23:00:27 +03:00
Sergey Penkovsky
72e1e91b96 docs: sync references after ProviderFactory rename 2026-03-24 10:54:01 +03:00
Sergey Penkovsky
e67ebbe2ab rename Provider typedef to ProviderFactory 2026-03-24 10:40:49 +03:00
Sergey Penkovsky
e47adbb7fd style(cherrypick_generator): format generator files 2026-03-24 10:30:06 +03:00
Sergey Penkovsky
b7979d653e refactor(cherrypick_generator): migrate inject codegen to code_builder 2026-03-02 12:47:02 +03:00
24 changed files with 375 additions and 200 deletions

View File

@@ -3,6 +3,42 @@
All notable changes to this project will be documented in this file.
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
### Changes

View File

@@ -1,3 +1,11 @@
## 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
- **FIX**(test): fix warning.
@@ -188,4 +196,4 @@
- **MAINT**: Updated pubspec.
## 0.1.0
- **INIT**: Initial release.
- **INIT**: Initial release.

View File

@@ -205,7 +205,7 @@ class Binding<T> {
/// bind<Api>().toProvide(() => ApiService());
/// bind<Db>().toProvide(() async => await openDb());
/// ```
Binding<T> toProvide(Provider<T> value) {
Binding<T> toProvide(ProviderFactory<T> value) {
_resolver = ProviderResolver<T>((_) => value.call(), withParams: false);
return this;
}
@@ -227,7 +227,7 @@ class Binding<T> {
}
@Deprecated('Use toProvide instead of toProvideAsync')
Binding<T> toProvideAsync(Provider<T> value) {
Binding<T> toProvideAsync(ProviderFactory<T> value) {
return toProvide(value);
}

View File

@@ -28,10 +28,10 @@ typedef Instance<T> = FutureOr<T>;
///
/// Example:
/// ```dart
/// Provider<MyService> provider = () => MyService();
/// Provider<Api> asyncProvider = () async => await Api.connect();
/// ProviderFactory<MyService> provider = () => MyService();
/// ProviderFactory<Api> asyncProvider = () async => await Api.connect();
/// ```
typedef Provider<T> = FutureOr<T> Function();
typedef ProviderFactory<T> = FutureOr<T> Function();
/// Provider function type that accepts a dynamic parameter, for factory/parametrized injection.
/// Returns [T] or [Future<T>].

View File

@@ -1,6 +1,6 @@
name: cherrypick
description: Cherrypick is a small dependency injection (DI) library for dart/flutter projects.
version: 3.0.2
version: 3.0.3
homepage: https://cherrypick-di.netlify.app
documentation: https://cherrypick-di.netlify.app/docs/intro
repository: https://github.com/pese-git/cherrypick

View File

@@ -1,3 +1,7 @@
## 3.0.3
- Update a dependency to the latest release.
## 3.0.2
- Update a dependency to the latest release.

View File

@@ -1,6 +1,6 @@
name: cherrypick_flutter
description: "Flutter library that allows access to the root scope through the context using `CherryPickProvider`."
version: 3.0.2
version: 3.0.3
homepage: https://cherrypick-di.netlify.app
documentation: https://cherrypick-di.netlify.app/docs/intro
repository: https://github.com/pese-git/cherrypick
@@ -19,7 +19,7 @@ environment:
dependencies:
flutter:
sdk: flutter
cherrypick: ^3.0.2
cherrypick: ^3.0.3
dev_dependencies:
flutter_test:

View File

@@ -1,3 +1,8 @@
## 4.0.0-dev.1
- **REFACTOR**(cherrypick_generator): migrate inject codegen to code_builder.
- **FIX**: add deps.
## 4.0.0-dev.0
> Note: This release has breaking changes.

View File

@@ -11,14 +11,17 @@
// limitations under the License.
//
import 'package:analyzer/dart/constant/value.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:build/build.dart';
import 'package:code_builder/code_builder.dart';
import 'package:source_gen/source_gen.dart';
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.
///
/// Analyzes all Dart classes marked with `@injectable()` and generates a mixin (for example, `_$ProfileScreen`)
@@ -115,24 +118,54 @@ class InjectGenerator extends GeneratorForAnnotation<ann.injectable> {
final className = classElement.firstFragment.name2;
final mixinName = '_\$$className';
final buffer = StringBuffer()
..writeln('mixin $mixinName {')
..writeln(' void _inject($className instance) {');
AnnotationValidator.validateClassAnnotations(classElement);
final classType = TypeParser.parseType(classElement.thisType, classElement);
// Collect and process all @inject fields
final injectFields = classElement.fields2
.where((f) => _isInjectField(f))
.map((f) => _parseInjectField(f));
.map(_parseInjectField)
.toList();
for (final parsedField in injectFields) {
buffer.writeln(_generateInjectionLine(parsedField));
}
final injectMethod = Method((b) {
b
..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,
);
}
});
});
buffer
..writeln(' }')
..writeln('}');
final mixin = Mixin((b) {
b
..name = mixinName
..methods.add(injectMethod);
});
return buffer.toString();
final library = Library((b) => b..body.add(mixin));
final emitter = DartEmitter(useNullSafetySyntax: true);
return '${library.accept(emitter)}';
}
/// Returns true if a field is annotated with `@inject`.
@@ -150,11 +183,13 @@ class InjectGenerator extends GeneratorForAnnotation<ann.injectable> {
/// Converts Dart field declaration and all parameterizing injection-related
/// annotations into a [_ParsedInjectField] which is used for codegen.
static _ParsedInjectField _parseInjectField(FieldElement2 field) {
AnnotationValidator.validateFieldAnnotations(field);
String? scopeName;
String? namedValue;
for (final meta in field.firstFragment.metadata2.annotations) {
final DartObject? obj = meta.computeConstantValue();
final obj = meta.computeConstantValue();
final type = obj?.type?.getDisplayString();
if (type == 'scope') {
scopeName = obj?.getField('name')?.toStringValue();
@@ -164,65 +199,15 @@ class InjectGenerator extends GeneratorForAnnotation<ann.injectable> {
}
final DartType dartType = field.type;
String coreTypeName;
bool isFuture;
if (dartType.isDartAsyncFuture) {
final ParameterizedType paramType = dartType as ParameterizedType;
coreTypeName = paramType.typeArguments.first.getDisplayString();
isFuture = true;
} else {
coreTypeName = dartType.getDisplayString();
isFuture = false;
}
// 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,
));
final parsedType = TypeParser.parseType(dartType, field);
return _ParsedInjectField(
fieldName: field.firstFragment.name2 ?? '',
coreType: coreTypeName.replaceAll('?', ''), // удаляем "?" на всякий
isFuture: isFuture,
isNullable: isNullable,
parsedType: parsedType,
scopeName: scopeName,
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
@@ -233,14 +218,8 @@ class _ParsedInjectField {
/// The name of the field to be injected.
final String fieldName;
/// The Dart type to resolve (e.g. `Logger` from `Logger` or `Future<Logger>`).
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;
/// Parsed type info for the field.
final ParsedType parsedType;
/// The scoping for DI resolution, or null to use root scope.
final String? scopeName;
@@ -250,9 +229,7 @@ class _ParsedInjectField {
_ParsedInjectField({
required this.fieldName,
required this.coreType,
required this.isFuture,
required this.isNullable,
required this.parsedType,
this.scopeName,
this.namedValue,
});

View File

@@ -0,0 +1,80 @@
//
// 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),
),
);
}
});
}
}

View File

@@ -65,6 +65,8 @@ class CherryPickGeneratorException extends InvalidGenerationSourceError {
Element2 element,
) {
final buffer = StringBuffer();
final location = _safeLocation(element);
final enclosing = _safeEnclosingDisplayName(element);
// Header with category
buffer.writeln('[$category] $message');
@@ -74,18 +76,11 @@ class CherryPickGeneratorException extends InvalidGenerationSourceError {
buffer.writeln('Context:');
buffer.writeln(' Element: ${element.displayName}');
buffer.writeln(' Type: ${element.runtimeType}');
buffer.writeln(
' Location: ${element.firstFragment.libraryFragment?.source.fullName ?? 'unknown'}',
);
buffer.writeln(' Location: $location');
// Try to show enclosing element info for extra context
try {
final enclosing = (element as dynamic).enclosingElement;
if (enclosing != null) {
buffer.writeln(' Enclosing: ${enclosing.displayName}');
}
} catch (e) {
// Ignore if enclosingElement is not available
if (enclosing != null) {
buffer.writeln(' Enclosing: $enclosing');
}
// Arbitrary user context
@@ -105,6 +100,34 @@ class CherryPickGeneratorException extends InvalidGenerationSourceError {
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;
}
}
}
/// ---------------------------------------------------------------------------

View File

@@ -2,7 +2,7 @@ name: cherrypick_generator
description: |
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.0
version: 4.0.0-dev.1
homepage: https://cherrypick-di.netlify.app
documentation: https://cherrypick-di.netlify.app/docs/intro
repository: https://github.com/pese-git/cherrypick/cherrypick_generator
@@ -22,9 +22,12 @@ dependencies:
cherrypick_annotations: ^4.0.0-dev.0
analyzer: ">=8.2.9 <10.0.1"
dart_style: ^3.0.0
code_builder: ^4.10.1
build: ^3.0.0
source_gen: ^4.2.0
collection: ^1.18.0
package_config: ^2.2.0
build_runner_core: ^9.3.1
dev_dependencies:
lints: ^6.0.0

View File

@@ -1,32 +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: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);
});
}

View File

@@ -11,9 +11,13 @@
// 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:cherrypick_generator/inject_generator.dart';
import 'package:package_config/package_config.dart';
import 'package:source_gen/source_gen.dart';
import 'package:test/test.dart';
@@ -40,8 +44,8 @@ class TestWidget {
''';
const expectedOutput = '''
// dart format width=80
// GENERATED CODE - DO NOT MODIFY BY HAND
// dart format width=80
part of 'test_widget.dart';
@@ -75,8 +79,8 @@ class TestWidget {
''';
const expectedOutput = '''
// dart format width=80
// GENERATED CODE - DO NOT MODIFY BY HAND
// dart format width=80
part of 'test_widget.dart';
@@ -113,8 +117,8 @@ class TestWidget {
''';
const expectedOutput = '''
// dart format width=80
// GENERATED CODE - DO NOT MODIFY BY HAND
// dart format width=80
part of 'test_widget.dart';
@@ -124,9 +128,8 @@ part of 'test_widget.dart';
mixin _\$TestWidget {
void _inject(TestWidget instance) {
instance.service = CherryPick.openRootScope().resolve<MyService>(
named: 'myService',
);
instance.service =
CherryPick.openRootScope().resolve<MyService>(named: 'myService');
}
}
''';
@@ -151,8 +154,8 @@ class TestWidget {
''';
const expectedOutput = '''
// dart format width=80
// GENERATED CODE - DO NOT MODIFY BY HAND
// dart format width=80
part of 'test_widget.dart';
@@ -162,9 +165,8 @@ part of 'test_widget.dart';
mixin _\$TestWidget {
void _inject(TestWidget instance) {
instance.service = CherryPick.openRootScope().tryResolve<MyService>(
named: 'myService',
);
instance.service =
CherryPick.openRootScope().tryResolve<MyService>(named: 'myService');
}
}
''';
@@ -191,8 +193,8 @@ class TestWidget {
''';
const expectedOutput = '''
// dart format width=80
// GENERATED CODE - DO NOT MODIFY BY HAND
// dart format width=80
part of 'test_widget.dart';
@@ -229,8 +231,8 @@ class TestWidget {
''';
const expectedOutput = '''
// dart format width=80
// GENERATED CODE - DO NOT MODIFY BY HAND
// dart format width=80
part of 'test_widget.dart';
@@ -240,9 +242,9 @@ part of 'test_widget.dart';
mixin _\$TestWidget {
void _inject(TestWidget instance) {
instance.service = CherryPick.openScope(
scopeName: 'userScope',
).resolve<MyService>(named: 'myService');
instance.service =
CherryPick.openScope(scopeName: 'userScope')
.resolve<MyService>(named: 'myService');
}
}
''';
@@ -268,8 +270,8 @@ class TestWidget {
''';
const expectedOutput = '''
// dart format width=80
// GENERATED CODE - DO NOT MODIFY BY HAND
// dart format width=80
part of 'test_widget.dart';
@@ -303,8 +305,8 @@ class TestWidget {
''';
const expectedOutput = '''
// dart format width=80
// GENERATED CODE - DO NOT MODIFY BY HAND
// dart format width=80
part of 'test_widget.dart';
@@ -339,8 +341,8 @@ class TestWidget {
''';
const expectedOutput = '''
// dart format width=80
// GENERATED CODE - DO NOT MODIFY BY HAND
// dart format width=80
part of 'test_widget.dart';
@@ -350,9 +352,8 @@ part of 'test_widget.dart';
mixin _\$TestWidget {
void _inject(TestWidget instance) {
instance.service = CherryPick.openRootScope().resolveAsync<MyService>(
named: 'myService',
);
instance.service =
CherryPick.openRootScope().resolveAsync<MyService>(named: 'myService');
}
}
''';
@@ -391,8 +392,8 @@ class TestWidget {
''';
const expectedOutput = '''
// dart format width=80
// GENERATED CODE - DO NOT MODIFY BY HAND
// dart format width=80
part of 'test_widget.dart';
@@ -403,13 +404,11 @@ part of 'test_widget.dart';
mixin _\$TestWidget {
void _inject(TestWidget instance) {
instance.apiService = CherryPick.openRootScope().resolve<ApiService>();
instance.cacheService = CherryPick.openRootScope().tryResolve<CacheService>(
named: 'cache',
);
instance.cacheService =
CherryPick.openRootScope().tryResolve<CacheService>(named: 'cache');
instance.dbService =
CherryPick.openScope(
scopeName: 'dbScope',
).resolveAsync<DatabaseService>();
CherryPick.openScope(scopeName: 'dbScope')
.resolveAsync<DatabaseService>();
}
}
''';
@@ -439,8 +438,8 @@ class TestWidget {
''';
const expectedOutput = '''
// dart format width=80
// GENERATED CODE - DO NOT MODIFY BY HAND
// dart format width=80
part of 'test_widget.dart';
@@ -496,8 +495,8 @@ class TestWidget {
''';
const expectedOutput = '''
// dart format width=80
// GENERATED CODE - DO NOT MODIFY BY HAND
// dart format width=80
part of 'test_widget.dart';
@@ -533,8 +532,8 @@ class TestWidget {
''';
const expectedOutput = '''
// dart format width=80
// GENERATED CODE - DO NOT MODIFY BY HAND
// dart format width=80
part of 'test_widget.dart';
@@ -569,8 +568,8 @@ class TestWidget {
''';
const expectedOutput = '''
// dart format width=80
// GENERATED CODE - DO NOT MODIFY BY HAND
// dart format width=80
part of 'test_widget.dart';
@@ -593,10 +592,41 @@ mixin _\$TestWidget {
/// Helper function to test code generation
Future<void> _testGeneration(String input, String expectedOutput) async {
await testBuilder(
final readerWriter = TestReaderWriter(rootPackage: 'a');
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),
{'a|lib/test_widget.dart': input},
outputs: {'a|lib/test_widget.inject.cherrypick.g.dart': expectedOutput},
readerWriter: TestReaderWriter(),
outputs: outputs,
readerWriter: readerWriter,
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',\}'), '}');
}

View File

@@ -11,12 +11,16 @@
// limitations under the License.
//
import 'package:test/test.dart';
import 'package:build_test/build_test.dart';
import 'package:build/build.dart';
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:cherrypick_generator/module_generator.dart';
import 'package:source_gen/source_gen.dart';
import 'package:cherrypick_generator/module_generator.dart';
void main() {
group('ModuleGenerator Tests', () {
@@ -40,8 +44,8 @@ abstract class TestModule extends Module {
''';
const expectedOutput = '''
// dart format width=80
// GENERATED CODE - DO NOT MODIFY BY HAND
// dart format width=80
part of 'test_module.dart';
@@ -75,8 +79,8 @@ abstract class TestModule extends Module {
''';
const expectedOutput = '''
// dart format width=80
// GENERATED CODE - DO NOT MODIFY BY HAND
// dart format width=80
part of 'test_module.dart';
@@ -113,8 +117,8 @@ abstract class TestModule extends Module {
''';
const expectedOutput = '''
// dart format width=80
// GENERATED CODE - DO NOT MODIFY BY HAND
// dart format width=80
part of 'test_module.dart';
@@ -149,8 +153,8 @@ abstract class TestModule extends Module {
''';
const expectedOutput = '''
// dart format width=80
// GENERATED CODE - DO NOT MODIFY BY HAND
// dart format width=80
part of 'test_module.dart';
@@ -187,8 +191,8 @@ abstract class TestModule extends Module {
''';
const expectedOutput = '''
// dart format width=80
// GENERATED CODE - DO NOT MODIFY BY HAND
// dart format width=80
part of 'test_module.dart';
@@ -224,8 +228,8 @@ abstract class TestModule extends Module {
''';
const expectedOutput = '''
// dart format width=80
// GENERATED CODE - DO NOT MODIFY BY HAND
// dart format width=80
part of 'test_module.dart';
@@ -264,8 +268,8 @@ abstract class TestModule extends Module {
''';
const expectedOutput = '''
// dart format width=80
// GENERATED CODE - DO NOT MODIFY BY HAND
// dart format width=80
part of 'test_module.dart';
@@ -299,8 +303,8 @@ abstract class TestModule extends Module {
''';
const expectedOutput = '''
// dart format width=80
// GENERATED CODE - DO NOT MODIFY BY HAND
// dart format width=80
part of 'test_module.dart';
@@ -334,8 +338,8 @@ abstract class TestModule extends Module {
''';
const expectedOutput = '''
// dart format width=80
// GENERATED CODE - DO NOT MODIFY BY HAND
// dart format width=80
part of 'test_module.dart';
@@ -374,8 +378,8 @@ abstract class TestModule extends Module {
''';
const expectedOutput = '''
// dart format width=80
// GENERATED CODE - DO NOT MODIFY BY HAND
// dart format width=80
part of 'test_module.dart';
@@ -414,8 +418,8 @@ abstract class TestModule extends Module {
''';
const expectedOutput = '''
// dart format width=80
// GENERATED CODE - DO NOT MODIFY BY HAND
// dart format width=80
part of 'test_module.dart';
@@ -453,8 +457,8 @@ abstract class TestModule extends Module {
''';
const expectedOutput = '''
// dart format width=80
// GENERATED CODE - DO NOT MODIFY BY HAND
// dart format width=80
part of 'test_module.dart';
@@ -488,8 +492,8 @@ abstract class TestModule extends Module {
''';
const expectedOutput = '''
// dart format width=80
// GENERATED CODE - DO NOT MODIFY BY HAND
// dart format width=80
part of 'test_module.dart';
@@ -541,8 +545,8 @@ abstract class TestModule extends Module {
''';
const expectedOutput = '''
// dart format width=80
// GENERATED CODE - DO NOT MODIFY BY HAND
// dart format width=80
part of 'test_module.dart';
@@ -637,10 +641,41 @@ abstract class TestModule extends Module {
/// Helper function to test code generation
Future<void> _testGeneration(String input, String expectedOutput) async {
await testBuilder(
final readerWriter = TestReaderWriter(rootPackage: 'a');
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),
{'a|lib/test_module.dart': input},
outputs: {'a|lib/test_module.module.cherrypick.g.dart': expectedOutput},
readerWriter: TestReaderWriter(),
outputs: outputs,
readerWriter: readerWriter,
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',\}'), '}');
}

View File

@@ -550,4 +550,4 @@ void main() {
- [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)
- [Исходники на GitHub](https://github.com/pese-git/cherrypick)

View File

@@ -255,4 +255,4 @@ class ApiClientImpl implements ApiClient {
return 'Network data';
}
}
```
```

View File

@@ -134,7 +134,7 @@ packages:
path: "../../cherrypick_annotations"
relative: true
source: path
version: "3.0.2"
version: "4.0.0-dev.0"
cherrypick_flutter:
dependency: "direct main"
description:
@@ -148,7 +148,7 @@ packages:
path: "../../cherrypick_generator"
relative: true
source: path
version: "3.0.2"
version: "4.0.0-dev.0"
clock:
dependency: transitive
description:

View File

@@ -182,14 +182,14 @@ packages:
path: "../../cherrypick_annotations"
relative: true
source: path
version: "3.0.2"
version: "4.0.0-dev.0"
cherrypick_generator:
dependency: "direct dev"
description:
path: "../../cherrypick_generator"
relative: true
source: path
version: "3.0.2"
version: "4.0.0-dev.0"
cli_launcher:
dependency: transitive
description:

View File

@@ -12,7 +12,7 @@ dependencies:
flutter:
sdk: flutter
cherrypick: any
cherrypick: ^3.0.3
cherrypick_annotations: ^4.0.0-dev.0
dio: ^5.4.0
@@ -27,7 +27,7 @@ dependencies:
cupertino_icons: ^1.0.8
talker_flutter: ^5.0.0
talker_cherrypick_logger: any
talker_cherrypick_logger: ^3.0.3
talker_dio_logger: ^5.0.0
talker_bloc_logger: ^5.0.0
@@ -38,7 +38,7 @@ dev_dependencies:
flutter_lints: ^6.0.0
build_runner: ^2.5.0
cherrypick_generator: ^4.0.0-dev.0
cherrypick_generator: ^4.0.0-dev.1
json_serializable: ^6.9.0
retrofit_generator: ^10.0.5

View File

@@ -60,8 +60,10 @@ scripts:
scope: ["cherrypick_flutter"]
codegen:
run: |
melos exec --scope="postly" -- dart run build_runner build --delete-conflicting-outputs
description: "Run build for Flutter packages only"
exec: dart run build_runner build --delete-conflicting-outputs
packageFilters:
scope: ["postly"]
outdated:
exec: dart pub outdated

View File

@@ -1,3 +1,7 @@
## 3.0.3
- Update a dependency to the latest release.
## 3.0.2
- Update a dependency to the latest release.

View File

@@ -1,6 +1,6 @@
name: talker_cherrypick_logger
description: A Talker logger integration for CherryPick DI to observe and log DI events and errors.
version: 3.0.2
version: 3.0.3
homepage: https://cherrypick-di.netlify.app
documentation: https://cherrypick-di.netlify.app/docs/intro
repository: https://github.com/pese-git/cherrypick
@@ -18,7 +18,7 @@ environment:
# Add regular dependencies here.
dependencies:
talker: ^5.0.0
cherrypick: ^3.0.2
cherrypick: ^3.0.3
dev_dependencies:

View File

@@ -38,4 +38,4 @@ See [Installation](installation.md) for instructions on adding CherryPick to you
CherryPick is open-source. Contributions and questions are welcome!
---
---