feat: implement generator di module

This commit is contained in:
Sergey Penkovsky
2025-05-17 00:34:56 +03:00
parent b906e927c3
commit d1e726aaec
18 changed files with 118 additions and 129 deletions

View File

@@ -3,6 +3,9 @@
/// More dartdocs go here.
library;
export 'injectable.dart';
export 'src/module.dart';
export 'src/bind.dart';
export 'src/provide.dart';
export 'src/singleton.dart';
// TODO: Export any libraries intended for clients of this package.

View File

@@ -0,0 +1,4 @@
// ignore: camel_case_types
class Bind {
const Bind();
}

View File

@@ -1,6 +1,5 @@
library;
/// Отмечает класс как injectable для автоматической регистрации.
class Injectable {
const Injectable();
// ignore: camel_case_types
class module {
const module();
}

View File

@@ -0,0 +1,4 @@
// ignore: camel_case_types
class provide {
const provide();
}

View File

@@ -0,0 +1,4 @@
// ignore: camel_case_types
class singleton {
const singleton();
}

View File

@@ -1,8 +1,8 @@
builders:
injectable:
import: "package:cherrypick_generator/injectable_generator.dart"
builder_factories: ["injectableBuilder"]
build_extensions: {".dart": [".cherrypick_injectable.g.dart"]}
module_generator:
import: "package:cherrypick_generator/module_generator.dart"
builder_factories: ["moduleBuilder"]
build_extensions: {".dart": [".cherrypick.g.dart"]}
auto_apply: dependents
required_inputs: ["lib/**"]
runs_before: []
@@ -11,6 +11,6 @@ builders:
targets:
$default:
builders:
cherrypick_generator|injectable:
cherrypick_generator|module_generator:
generate_for:
- lib/**.dart

View File

@@ -1,3 +1,3 @@
library;
export 'inject_generator.dart';
export 'module_generator.dart';

View File

@@ -1,38 +0,0 @@
import 'dart:async';
import 'package:build/build.dart';
import 'package:source_gen/source_gen.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:cherrypick_annotations/cherrypick_annotations.dart';
class InjectGenerator extends GeneratorForAnnotation<Injectable> {
@override
FutureOr<String> generateForAnnotatedElement(
Element element,
ConstantReader annotation,
BuildStep buildStep,
) {
print('[TRACE] Processing element: ${element.name}');
if (element is! FieldElement) {
throw InvalidGenerationSourceError(
'Inject can only be used on fields.',
element: element,
);
}
print('[TRACE] Starting code generation for element: ${element.name}');
final className = element.enclosingElement.name;
final fieldName = element.name;
final fieldType = element.type.getDisplayString(withNullability: false);
final annotationName = annotation.read('named').stringValue;
return '''
extension \$${className}Inject on $className {
void init$fieldName() {
print("Injected $fieldType named '$annotationName' into $fieldName");
}
}
''';
}
}

View File

@@ -1,33 +0,0 @@
import 'package:source_gen/source_gen.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:build/build.dart';
import 'package:cherrypick_annotations/cherrypick_annotations.dart';
class InjectableGenerator extends GeneratorForAnnotation<Injectable> {
@override
generateForAnnotatedElement(
Element element, ConstantReader annotation, BuildStep buildStep) {
if (element is! ClassElement) return null;
final className = element.name;
// Используйте уникальное имя функции (например, привязанное к файлу/классу)
return '''
void \$initCherrypickGenerated() {
print("Generate code success $className");
}
''';
}
}
Builder injectableBuilder(BuilderOptions options) =>
PartBuilder([InjectableGenerator()], '.cherrypick_injectable.g.dart');
/*
Builder injectableBuilder(BuilderOptions options) => SharedPartBuilder(
[InjectableGenerator()],
'injectable',
allowSyntaxErrors: true,
writeDescriptions: true,
);
*/

View File

@@ -0,0 +1,60 @@
// ... остальные импорты ...
import 'package:analyzer/dart/element/element.dart';
import 'package:build/build.dart';
import 'package:source_gen/source_gen.dart';
import 'package:cherrypick_annotations/cherrypick_annotations.dart' as ann;
class ModuleGenerator extends GeneratorForAnnotation<ann.module> {
@override
String generateForAnnotatedElement(
Element element, ConstantReader annotation, BuildStep buildStep) {
if (element is! ClassElement) {
throw InvalidGenerationSourceError(
'@module() может быть применён только к классам.',
element: element,
);
}
final classElement = element;
final className = classElement.displayName;
final generatedClassName = r'$' + className;
final buffer = StringBuffer();
//buffer.writeln("part of '${buildStep.inputId.uri.pathSegments.last}';\n");
buffer.writeln('final class $generatedClassName extends $className {');
buffer.writeln(' @override');
buffer.writeln(' void builder(Scope currentScope) {');
for (final method in classElement.methods.where((m) => !m.isAbstract)) {
final hasSingleton = method.metadata.any(
(m) =>
m
.computeConstantValue()
?.type
?.getDisplayString(withNullability: false)
.toLowerCase()
.contains('singleton') ??
false,
);
if (!hasSingleton) continue;
final returnType =
method.returnType.getDisplayString(withNullability: false);
final methodName = method.displayName;
final args = method.parameters
.map((p) =>
"currentScope.resolve<${p.type.getDisplayString(withNullability: false)}>()")
.join(', ');
buffer.write(' bind<$returnType>()'
'.toProvide(() => $methodName($args))'
'.singleton();\n');
}
buffer.writeln(' }\n}');
return buffer.toString();
}
}
Builder moduleBuilder(BuilderOptions options) =>
PartBuilder([ModuleGenerator()], '.cherrypick.g.dart');

View File

@@ -1,14 +0,0 @@
import 'package:cherrypick_annotations/cherrypick_annotations.dart';
part 'foo.cherrypick_injectable.g.dart';
@Injectable()
class Foo {
late final String field;
}
// где-то в main:
void iniFoo() {
$initCherrypickGenerated();
// ... остальной код
}

View File

@@ -1,10 +1,6 @@
import 'package:cherrypick_annotations/cherrypick_annotations.dart';
import 'package:flutter/material.dart';
import 'use_case.dart';
part 'my_home_page.cherrypick_injectable.g.dart';
@Injectable()
class MyHomePage extends StatelessWidget {
late final UseCase useCase;

View File

@@ -1,12 +0,0 @@
import 'package:cherrypick_annotations/cherrypick_annotations.dart';
part 'my_service.cherrypick_injectable.g.dart';
@Injectable()
class MyService {}
// где-то в main:
void init() {
$initCherrypickGenerated();
// ... остальной код
}

View File

@@ -1,21 +1,20 @@
import 'package:cherrypick_annotations/cherrypick_annotations.dart';
import 'package:dio/dio.dart';
import 'package:cherrypick/cherrypick.dart';
import '../data/network/json_placeholder_api.dart';
import '../data/post_repository_impl.dart';
import '../domain/repository/post_repository.dart';
class AppModule extends Module {
@override
void builder(Scope currentScope) {
bind<Dio>().toProvide(() => Dio()).singleton();
part 'app_module.cherrypick.g.dart';
bind<JsonPlaceholderApi>()
.toProvide(() => JsonPlaceholderApi(currentScope.resolve<Dio>()))
.singleton();
@module()
abstract class AppModule extends Module {
@singleton()
Dio dio() => Dio();
bind<PostRepository>()
.toProvide(() =>
PostRepositoryImpl(currentScope.resolve<JsonPlaceholderApi>()))
.singleton();
}
@singleton()
JsonPlaceholderApi api(Dio dio) => JsonPlaceholderApi(dio);
@singleton()
PostRepository repo(JsonPlaceholderApi api) => PostRepositoryImpl(api);
}

View File

@@ -8,7 +8,7 @@ import 'router/app_router.dart';
void main() {
final scope = CherryPick.openRootScope();
scope.installModules([AppModule()]);
scope.installModules([$AppModule()]);
runApp(MyApp(scope: scope));
}

View File

@@ -156,7 +156,21 @@ packages:
path: "../../cherrypick"
relative: true
source: path
version: "2.1.0"
version: "2.1.0-dev.1"
cherrypick_annotations:
dependency: "direct main"
description:
path: "../../cherrypick_annotations"
relative: true
source: path
version: "1.0.0"
cherrypick_generator:
dependency: "direct dev"
description:
path: "../../cherrypick_generator"
relative: true
source: path
version: "1.0.0"
clock:
dependency: transitive
description:

View File

@@ -12,7 +12,8 @@ dependencies:
flutter:
sdk: flutter
cherrypick: ^2.1.0
cherrypick: any
cherrypick_annotations: any
dio: ^5.4.0
retrofit: ^4.0.3
@@ -29,7 +30,9 @@ dev_dependencies:
flutter_lints: ^4.0.0
build_runner: ^2.4.6
cherrypick_generator: any
build_runner: any
retrofit_generator: ^8.0.4
freezed: ^2.3.2
json_serializable: any

View File

@@ -17,7 +17,7 @@ void main() {
setUp(() {
scope = CherryPick.openRootScope();
scope.installModules([AppModule()]);
scope.installModules([$AppModule()]);
});
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
expect(1, 1);