diff --git a/cherrypick_annotations/lib/cherrypick_annotations.dart b/cherrypick_annotations/lib/cherrypick_annotations.dart index 979008a..ec4afe5 100644 --- a/cherrypick_annotations/lib/cherrypick_annotations.dart +++ b/cherrypick_annotations/lib/cherrypick_annotations.dart @@ -19,3 +19,5 @@ export 'src/instance.dart'; export 'src/singleton.dart'; export 'src/named.dart'; export 'src/params.dart'; +export 'src/inject.dart'; +export 'src/injectable.dart'; diff --git a/cherrypick_annotations/lib/src/inject.dart b/cherrypick_annotations/lib/src/inject.dart new file mode 100644 index 0000000..d5d7edd --- /dev/null +++ b/cherrypick_annotations/lib/src/inject.dart @@ -0,0 +1,17 @@ +// +// Copyright 2021 Sergey Penkovsky (sergey.penkovsky@gmail.com) +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +// ignore: camel_case_types +final class inject { + const inject(); +} diff --git a/cherrypick_annotations/lib/src/injectable.dart b/cherrypick_annotations/lib/src/injectable.dart new file mode 100644 index 0000000..e242ed8 --- /dev/null +++ b/cherrypick_annotations/lib/src/injectable.dart @@ -0,0 +1,17 @@ +// +// Copyright 2021 Sergey Penkovsky (sergey.penkovsky@gmail.com) +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +// ignore: camel_case_types +final class injectable { + const injectable(); +} diff --git a/cherrypick_generator/build.yaml b/cherrypick_generator/build.yaml index 8181c17..9541079 100644 --- a/cherrypick_generator/build.yaml +++ b/cherrypick_generator/build.yaml @@ -7,10 +7,21 @@ builders: required_inputs: ["lib/**"] runs_before: [] build_to: source + inject_generator: + import: "package:cherrypick_generator/inject_generator.dart" + builder_factories: ["injectBuilder"] + build_extensions: {".dart": [".cherrypick.g.dart"]} + auto_apply: dependents + required_inputs: ["lib/**"] + runs_before: [] + build_to: source targets: $default: builders: cherrypick_generator|module_generator: + generate_for: + - lib/**.dart + cherrypick_generator|inject_generator: generate_for: - lib/**.dart \ No newline at end of file diff --git a/cherrypick_generator/lib/cherrypick_generator.dart b/cherrypick_generator/lib/cherrypick_generator.dart index d420d25..fdb6577 100644 --- a/cherrypick_generator/lib/cherrypick_generator.dart +++ b/cherrypick_generator/lib/cherrypick_generator.dart @@ -14,3 +14,4 @@ library; // export 'module_generator.dart'; +export 'inject_generator.dart'; diff --git a/cherrypick_generator/lib/inject_generator.dart b/cherrypick_generator/lib/inject_generator.dart new file mode 100644 index 0000000..598062c --- /dev/null +++ b/cherrypick_generator/lib/inject_generator.dart @@ -0,0 +1,65 @@ +import 'dart:async'; +import 'package:build/build.dart'; +import 'package:collection/collection.dart'; +import 'package:source_gen/source_gen.dart'; +import 'package:analyzer/dart/element/element.dart'; +import 'package:cherrypick_annotations/cherrypick_annotations.dart' as ann; + +class InjectGenerator extends GeneratorForAnnotation { + const InjectGenerator(); + + @override + FutureOr generateForAnnotatedElement( + Element element, ConstantReader annotation, BuildStep buildStep) { + // Only classes are supported for @module() annotation + // Обрабатываются только классы (другие элементы — ошибка) + if (element is! ClassElement) { + throw InvalidGenerationSourceError( + '@injectable() can only be applied to classes.', + element: element, + ); + } + + final classElement = element; + final className = classElement.name; + final mixinName = '_\$$className'; + + final injectedFields = classElement.fields.where((field) => field.metadata + .any((m) => + m.computeConstantValue()?.type?.getDisplayString() == 'inject')); + + // Генерируем инициализацию для каждого поля с аннотацией @inject() + final buffer = StringBuffer(); + buffer.writeln('mixin $mixinName {'); + buffer.writeln(' void _inject($className instance) {'); + + for (final field in injectedFields) { + // Получаем имя типа + final fieldType = field.type.getDisplayString(); + // Ищем аннотацию @named + final namedAnnotation = field.metadata.firstWhereOrNull( + (m) => m.computeConstantValue()?.type?.getDisplayString() == 'named', + ); + String namedParam = ''; + if (namedAnnotation != null) { + final namedValue = namedAnnotation + .computeConstantValue() + ?.getField('value') + ?.toStringValue(); + if (namedValue != null) { + namedParam = "(named: '$namedValue')"; + } + } + buffer.writeln( + " instance.${field.name} = CherryPick.openRootScope().resolve<$fieldType>$namedParam;"); + } + + buffer.writeln(' }'); + buffer.writeln('}'); + + return buffer.toString(); + } +} + +Builder injectBuilder(BuilderOptions options) => + PartBuilder([InjectGenerator()], '.inject.cherrypick.g.dart'); diff --git a/cherrypick_generator/lib/module_generator.dart b/cherrypick_generator/lib/module_generator.dart index 0f766e5..54bcc3b 100644 --- a/cherrypick_generator/lib/module_generator.dart +++ b/cherrypick_generator/lib/module_generator.dart @@ -90,4 +90,4 @@ class ModuleGenerator extends GeneratorForAnnotation { /// файлов, где встречается @module(). /// --------------------------------------------------------------------------- Builder moduleBuilder(BuilderOptions options) => - PartBuilder([ModuleGenerator()], '.cherrypick.g.dart'); + PartBuilder([ModuleGenerator()], '.module.cherrypick.g.dart'); diff --git a/cherrypick_generator/pubspec.yaml b/cherrypick_generator/pubspec.yaml index 4e1834d..f79f1a0 100644 --- a/cherrypick_generator/pubspec.yaml +++ b/cherrypick_generator/pubspec.yaml @@ -18,6 +18,7 @@ dependencies: build: ^2.4.1 build_runner: ^2.4.13 source_gen: ^1.5.0 + collection: ^1.18.0 dev_dependencies: lints: ^5.0.0 diff --git a/examples/client_app/pubspec.lock b/examples/client_app/pubspec.lock index 607ffbd..28b975d 100644 --- a/examples/client_app/pubspec.lock +++ b/examples/client_app/pubspec.lock @@ -153,7 +153,7 @@ packages: path: "../../cherrypick_generator" relative: true source: path - version: "1.1.0-dev.1" + version: "1.1.0-dev.2" clock: dependency: transitive description: diff --git a/examples/postly/lib/app.dart b/examples/postly/lib/app.dart new file mode 100644 index 0000000..86bae2e --- /dev/null +++ b/examples/postly/lib/app.dart @@ -0,0 +1,35 @@ +import 'package:cherrypick/cherrypick.dart'; +import 'package:cherrypick_annotations/cherrypick_annotations.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'domain/repository/post_repository.dart'; +import 'presentation/bloc/post_bloc.dart'; +import 'router/app_router.dart'; + +part 'app.inject.cherrypick.g.dart'; + +@injectable() +class MyApp extends StatelessWidget with _$MyApp { + final Scope scope; + final _appRouter = AppRouter(); + + @named('repo') + @inject() + late final PostRepository repository; + + MyApp({super.key, required this.scope}); + + @override + Widget build(BuildContext context) { + _inject(this); + return BlocProvider( + create: (_) => PostBloc(repository), + child: MaterialApp.router( + routeInformationParser: _appRouter.defaultRouteParser(), + routerDelegate: _appRouter.delegate(), + theme: ThemeData.light(), + ), + ); + } +} diff --git a/examples/postly/lib/di/app_module.dart b/examples/postly/lib/di/app_module.dart index 24c106b..27cf10e 100644 --- a/examples/postly/lib/di/app_module.dart +++ b/examples/postly/lib/di/app_module.dart @@ -5,7 +5,7 @@ import '../data/network/json_placeholder_api.dart'; import '../data/post_repository_impl.dart'; import '../domain/repository/post_repository.dart'; -part 'app_module.cherrypick.g.dart'; +part 'app_module.module.cherrypick.g.dart'; @module() abstract class AppModule extends Module { diff --git a/examples/postly/lib/main.dart b/examples/postly/lib/main.dart index 3205daf..37cbf6b 100644 --- a/examples/postly/lib/main.dart +++ b/examples/postly/lib/main.dart @@ -1,10 +1,7 @@ import 'package:cherrypick/cherrypick.dart'; import 'package:flutter/material.dart'; +import 'package:postly/app.dart'; import 'di/app_module.dart'; -import 'domain/repository/post_repository.dart'; -import 'presentation/bloc/post_bloc.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'router/app_router.dart'; void main() { final scope = CherryPick.openRootScope(); @@ -12,25 +9,3 @@ void main() { runApp(MyApp(scope: scope)); } - -class MyApp extends StatelessWidget { - final Scope scope; - final _appRouter = AppRouter(); - - MyApp({super.key, required this.scope}); - - @override - Widget build(BuildContext context) { - // Получаем репозиторий через injector - final repository = scope.resolve(named: 'repo'); - - return BlocProvider( - create: (_) => PostBloc(repository), - child: MaterialApp.router( - routeInformationParser: _appRouter.defaultRouteParser(), - routerDelegate: _appRouter.delegate(), - theme: ThemeData.light(), - ), - ); - } -} diff --git a/examples/postly/pubspec.lock b/examples/postly/pubspec.lock index 5ae3b6b..8460e13 100644 --- a/examples/postly/pubspec.lock +++ b/examples/postly/pubspec.lock @@ -170,7 +170,7 @@ packages: path: "../../cherrypick_generator" relative: true source: path - version: "1.1.0-dev.1" + version: "1.1.0-dev.2" clock: dependency: transitive description: