From 7d45d00d6a49e43117ef65ec7cc38fa482d23c3a Mon Sep 17 00:00:00 2001 From: Sergey Penkovsky Date: Wed, 13 Aug 2025 08:57:06 +0300 Subject: [PATCH] docs(generator): improve and unify English documentation and examples for all DI source files MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Added comprehensive English documentation for all DI generator and support files: * inject_generator.dart — full class/method doc-comments, usage samples * module_generator.dart — doc-comments, feature explanation, complete example * src/annotation_validator.dart — class and detailed static method descriptions * src/type_parser.dart — doc, example for ParsedType and TypeParser, specific codegen notes * src/bind_spec.dart — interface, static factory, and codegen docs with DI scenarios * src/bind_parameters_spec.dart — details and samples for code generation logic * src/metadata_utils.dart — full doc and examples for annotation utilities * src/exceptions.dart — user- and contributor-friendly errors, structured output, category explanations * src/generated_class.dart — usage-centric doc-comments, example of resulting generated DI class - Removed Russian/duplicate comments for full clarity and maintainability - Ensured that new and existing contributors can easily extend and maintain DI code generation logic BREAKING CHANGE: All documentation now English-only; comments include usage examples for each principal structure or routine See #docs, #generator, #cherrypick --- .../lib/cherrypick_generator.dart | 24 ++- .../lib/inject_generator.dart | 137 +++++++++++++----- .../lib/module_generator.dart | 100 +++++++------ .../lib/src/annotation_validator.dart | 73 +++++++++- .../lib/src/bind_parameters_spec.dart | 76 +++++----- cherrypick_generator/lib/src/bind_spec.dart | 100 ++++++------- cherrypick_generator/lib/src/exceptions.dart | 105 +++++++++++++- .../lib/src/generated_class.dart | 92 ++++++------ .../lib/src/metadata_utils.dart | 54 +++---- cherrypick_generator/lib/src/type_parser.dart | 64 ++++++-- 10 files changed, 557 insertions(+), 268 deletions(-) diff --git a/cherrypick_generator/lib/cherrypick_generator.dart b/cherrypick_generator/lib/cherrypick_generator.dart index ba44ff9..c23cad6 100644 --- a/cherrypick_generator/lib/cherrypick_generator.dart +++ b/cherrypick_generator/lib/cherrypick_generator.dart @@ -1,5 +1,3 @@ -library; - // // Copyright 2021 Sergey Penkovsky (sergey.penkovsky@gmail.com) // Licensed under the Apache License, Version 2.0 (the "License"); @@ -12,6 +10,28 @@ library; // See the License for the specific language governing permissions and // limitations under the License. // +library; +/// CherryPick code generation library: entry point for build_runner DI codegen. +/// +/// This library exports generators for CherryPick dependency injection: +/// - [ModuleGenerator]: Generates DI module classes for all `@module()`-annotated classes. +/// - [InjectGenerator]: Generates field-injection mixins for classes annotated with `@injectable()`. +/// +/// These generators are hooked into [build_runner] and cherrypick_generator's builder configuration. +/// Normally you do not import this directly; instead, add cherrypick_generator +/// as a dev_dependency and run `dart run build_runner build`. +/// +/// Example usage in `build.yaml` or your project's workflow: +/// ```yaml +/// targets: +/// $default: +/// builders: +/// cherrypick_generator|cherrypick_generator: +/// generate_for: +/// - lib/**.dart +/// ``` +/// +/// For annotation details, see `package:cherrypick_annotations`. export 'module_generator.dart'; export 'inject_generator.dart'; diff --git a/cherrypick_generator/lib/inject_generator.dart b/cherrypick_generator/lib/inject_generator.dart index 2da9f1d..33df2f3 100644 --- a/cherrypick_generator/lib/inject_generator.dart +++ b/cherrypick_generator/lib/inject_generator.dart @@ -20,28 +20,85 @@ import 'package:source_gen/source_gen.dart'; import 'package:analyzer/dart/element/element.dart'; import 'package:cherrypick_annotations/cherrypick_annotations.dart' as ann; -/// InjectGenerator generates a mixin for a class marked with @injectable() -/// and injects all fields annotated with @inject(), using CherryPick DI. +/// CherryPick DI field injector generator for codegen. /// -/// For Future fields it calls .resolveAsync(), -/// otherwise .resolve() is used. Scope and named qualifiers are supported. +/// Analyzes all Dart classes marked with `@injectable()` and generates a mixin (for example, `_$ProfileScreen`) +/// which contains the `_inject` method. This method will assign all fields annotated with `@inject()` +/// using the CherryPick DI container. Extra annotation qualifiers such as `@named` and `@scope` are respected +/// for each field. Nullable fields and Future/injectable async dependencies are also supported automatically. /// /// --- /// -/// InjectGenerator генерирует миксин для класса с аннотацией @injectable() -/// и внедряет все поля, помеченные @inject(), используя DI-фреймворк CherryPick. +/// ### Example usage in a project: /// -/// Для Future полей вызывается .resolveAsync(), -/// для остальных — .resolve(). Поддерживаются scope и named qualifier. +/// ```dart +/// import 'package:cherrypick_annotations/cherrypick_annotations.dart'; /// +/// @injectable() +/// class MyScreen with _$MyScreen { +/// @inject() +/// late final Logger logger; +/// +/// @inject() +/// @named('test') +/// late final HttpClient client; +/// +/// @inject() +/// Future? analytics; +/// } +/// ``` +/// +/// After running build_runner, this mixin will be auto-generated: +/// +/// ```dart +/// mixin _$MyScreen { +/// void _inject(MyScreen instance) { +/// instance.logger = CherryPick.openRootScope().resolve(); +/// instance.client = CherryPick.openRootScope().resolve(named: 'test'); +/// instance.analytics = CherryPick.openRootScope().tryResolveAsync(); // nullable async inject +/// } +/// } +/// ``` +/// +/// You may use the mixin (e.g., `myScreen._inject(myScreen)`) or expose your own public helper for instance field injection. +/// +/// **Supported scenarios:** +/// - Ordinary injectable fields: `resolve()`. +/// - Named qualifiers: `resolve(named: ...)`. +/// - Scoping: `CherryPick.openScope(scopeName: ...).resolve()`. +/// - Nullable/incomplete fields: `tryResolve`/`tryResolveAsync`. +/// - Async dependencies: `Future`/`resolveAsync()`. +/// +/// See also: +/// * @inject +/// * @injectable class InjectGenerator extends GeneratorForAnnotation { const InjectGenerator(); - /// The main entry point for code generation. + /// Main entry point for CherryPick field injection code generation. /// - /// Checks class validity, collects injectable fields, and produces injection code. + /// - Only triggers for classes marked with `@injectable()`. + /// - Throws an error if used on non-class elements. + /// - Scans all fields marked with `@inject()` and gathers qualifiers (if any). + /// - Generates Dart code for a mixin that injects all dependencies into the target class instance. /// - /// Основная точка входа генератора. Проверяет класс, собирает инъектируемые поля и создает код внедрения зависимостей. + /// Returns the Dart code as a String defining the new mixin. + /// + /// Example input (user code): + /// ```dart + /// @injectable() + /// class UserBloc with _$UserBloc { + /// @inject() late final AuthRepository authRepository; + /// } + /// ``` + /// Example output (generated): + /// ```dart + /// mixin _$UserBloc { + /// void _inject(UserBloc instance) { + /// instance.authRepository = CherryPick.openRootScope().resolve(); + /// } + /// } + /// ``` @override FutureOr generateForAnnotatedElement( Element element, @@ -63,8 +120,7 @@ class InjectGenerator extends GeneratorForAnnotation { ..writeln('mixin $mixinName {') ..writeln(' void _inject($className instance) {'); - // Collect and process all @inject fields. - // Собираем и обрабатываем все поля с @inject. + // Collect and process all @inject fields final injectFields = classElement.fields.where(_isInjectField).map(_parseInjectField); @@ -79,20 +135,20 @@ class InjectGenerator extends GeneratorForAnnotation { return buffer.toString(); } - /// Checks if a field has the @inject annotation. + /// Returns true if a field is annotated with `@inject`. /// - /// Проверяет, отмечено ли поле аннотацией @inject. + /// Used to detect which fields should be processed for injection. static bool _isInjectField(FieldElement field) { return field.metadata.any( (m) => m.computeConstantValue()?.type?.getDisplayString() == 'inject', ); } - /// Parses the field for scope/named qualifiers and determines its type. - /// Returns a [_ParsedInjectField] describing injection information. + /// Parses `@inject()` field and extracts all injection metadata + /// (core type, qualifiers, scope, nullability, etc). /// - /// Разбирает поле на наличие модификаторов scope/named и выясняет его тип. - /// Возвращает [_ParsedInjectField] с информацией о внедрении. + /// Converts Dart field declaration and all parameterizing injection-related + /// annotations into a [_ParsedInjectField] which is used for codegen. static _ParsedInjectField _parseInjectField(FieldElement field) { String? scopeName; String? namedValue; @@ -120,8 +176,7 @@ class InjectGenerator extends GeneratorForAnnotation { isFuture = false; } - // *** - // Добавим определение nullable для типа (например PostRepository? или Future) + // Determine nullability for field types like T? or Future bool isNullable = dartType.nullabilitySuffix == NullabilitySuffix.question || (dartType is ParameterizedType && @@ -139,13 +194,17 @@ class InjectGenerator extends GeneratorForAnnotation { ); } - /// Generates a line of code that performs the dependency injection for a field. - /// Handles resolve/resolveAsync, scoping, and named qualifiers. + /// Generates Dart code for a single dependency-injected field based on its metadata. /// - /// Генерирует строку кода, которая внедряет зависимость для поля. - /// Учитывает resolve/resolveAsync, scoping и named qualifier. + /// 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();` String _generateInjectionLine(_ParsedInjectField field) { - // Используем tryResolve для nullable, иначе resolve final resolveMethod = field.isFuture ? (field.isNullable ? 'tryResolveAsync<${field.coreType}>' @@ -166,29 +225,29 @@ class InjectGenerator extends GeneratorForAnnotation { } } -/// Data structure representing all information required to generate -/// injection code for a field. +/// Internal structure: describes all required information for generating the injection +/// assignment for a given field. /// -/// Структура данных, содержащая всю информацию, -/// необходимую для генерации кода внедрения для поля. +/// Not exported. Used as a DTO in the generator for each field. class _ParsedInjectField { - /// The name of the field / Имя поля. + /// The name of the field to be injected. final String fieldName; - /// The base type name (T or Future) / Базовый тип (T или тип из Future). + /// The Dart type to resolve (e.g. `Logger` from `Logger` or `Future`). final String coreType; - /// True if the field type is Future; false otherwise - /// Истина, если поле — Future, иначе — ложь. + /// True if the field is an async dependency (Future<...>), otherwise false. final bool isFuture; - /// Optional scope annotation argument / Опциональное имя scope. + /// True if the field accepts null (T?), otherwise false. + final bool isNullable; + + /// The scoping for DI resolution, or null to use root scope. final String? scopeName; - /// Optional named annotation argument / Опциональное имя named. + /// Name qualifier for named resolution, or null if not set. final String? namedValue; - final bool isNullable; _ParsedInjectField({ required this.fieldName, @@ -200,8 +259,8 @@ class _ParsedInjectField { }); } -/// Builder factory. Used by build_runner. +/// Factory for creating the build_runner builder for DI field injection. /// -/// Фабрика билдера. Используется build_runner. +/// Add this builder in your build.yaml if you're invoking CherryPick generators manually. 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 735a388..719caf9 100644 --- a/cherrypick_generator/lib/module_generator.dart +++ b/cherrypick_generator/lib/module_generator.dart @@ -19,75 +19,89 @@ import 'package:cherrypick_annotations/cherrypick_annotations.dart' as ann; import 'src/generated_class.dart'; /// --------------------------------------------------------------------------- -/// ModuleGenerator for code generation of dependency-injected modules. +/// CherryPick Module Generator — Codegen for DI modules /// -/// ENGLISH -/// This generator scans for Dart classes annotated with `@module()` and -/// automatically generates boilerplate code for dependency injection -/// (DI) based on the public methods in those classes. Each method can be -/// annotated to describe how an object should be provided to the DI container. -/// The generated code registers those methods as bindings. This automates the -/// creation of factories, singletons, and named instances, reducing repetitive -/// manual code. +/// This generator scans Dart classes annotated with `@module()` and generates +/// boilerplate for dependency injection registration automatically. Each public +/// method in such classes can be annotated to describe how an object should be +/// bound to the DI container (singleton, factory, named, with parameters, etc). /// -/// RUSSIAN -/// Генератор зависимостей для DI-контейнера на основе аннотаций. -/// Данный генератор автоматически создаёт код для внедрения зависимостей (DI) -/// на основе аннотаций в вашем исходном коде. Когда вы отмечаете класс -/// аннотацией `@module()`, этот генератор обработает все его публичные методы -/// и автоматически сгенерирует класс с биндингами (регистрациями зависимостей) -/// для DI-контейнера. Это избавляет от написания однообразного шаблонного кода. +/// The generated code collects all such bind methods and produces a Dart +/// companion *module registration class* with a `.bindAll()` method, which you +/// can use from your DI root to automatically register those dependencies. +/// +/// ## Example +/// ```dart +/// import 'package:cherrypick_annotations/cherrypick_annotations.dart'; +/// +/// @module() +/// abstract class AppModule { +/// @singleton() +/// Logger logger() => Logger(); +/// +/// @provide() +/// ApiService api(Logger logger) => ApiService(logger); +/// +/// @named('dev') +/// FakeService fake() => FakeService(); +/// } +/// ``` +/// +/// After codegen, you will get (simplified): +/// ```dart +/// class _\$AppModuleCherrypickModule extend AppModule { +/// static void bindAll(CherryPickScope scope, AppModule module) { +/// scope.addSingleton(() => module.logger()); +/// scope.addFactory(() => module.api(scope.resolve())); +/// scope.addFactory(() => module.fake(), named: 'dev'); +/// } +/// } +/// ``` +/// +/// Use it e.g. in your bootstrap: +/// ```dart +/// final scope = CherryPick.openRootScope()..intallModules([_\$AppModuleCherrypickModule()]); +/// ``` +/// +/// Features supported: +/// - Singleton, factory, named, parametric, and async providers +/// - Eliminates all boilerplate for DI registration +/// - Works with abstract classes and real classes +/// - Error if @module() is applied to a non-class +/// +/// See also: [@singleton], [@provide], [@named], [@module] /// --------------------------------------------------------------------------- class ModuleGenerator extends GeneratorForAnnotation { - /// ------------------------------------------------------------------------- - /// ENGLISH - /// Generates the Dart source for a class marked with the `@module()` annotation. - /// - [element]: the original Dart class element. - /// - [annotation]: the annotation parameters (not usually used here). - /// - [buildStep]: the current build step info. + /// Generates Dart source for a class marked with the `@module()` annotation. /// - /// RUSSIAN - /// Генерирует исходный код для класса-модуля с аннотацией `@module()`. - /// [element] — исходный класс, помеченный аннотацией. - /// [annotation] — значения параметров аннотации. - /// [buildStep] — информация о текущем шаге генерации. - /// ------------------------------------------------------------------------- + /// Throws [InvalidGenerationSourceError] if used on anything except a class. + /// + /// See file-level docs for usage and generated output example. @override String generateForAnnotatedElement( Element element, ConstantReader annotation, BuildStep buildStep, ) { - // Only classes are supported for @module() annotation - // Обрабатываются только классы (другие элементы — ошибка) if (element is! ClassElement) { throw InvalidGenerationSourceError( - '@module() can only be applied to classes. / @module() может быть применён только к классам.', + '@module() can only be applied to classes.', element: element, ); } final classElement = element; - - // Build a representation of the generated bindings based on class methods / - // Создаёт объект, описывающий, какие биндинги нужно сгенерировать на основании методов класса final generatedClass = GeneratedClass.fromClassElement(classElement); - - // Generate the resulting Dart code / Генерирует итоговый Dart-код return generatedClass.generate(); } } /// --------------------------------------------------------------------------- -/// ENGLISH -/// Entry point for build_runner. Returns a Builder used to generate code for -/// every file with a @module() annotation. +/// Codegen builder entry point: register this builder in build.yaml or your package. /// -/// RUSSIAN -/// Точка входа для генератора build_runner. -/// Возвращает Builder, используемый build_runner для генерации кода для всех -/// файлов, где встречается @module(). +/// Used by build_runner. Generates .module.cherrypick.g.dart files for each +/// source file with an annotated @module() class. /// --------------------------------------------------------------------------- Builder moduleBuilder(BuilderOptions options) => PartBuilder([ModuleGenerator()], '.module.cherrypick.g.dart'); diff --git a/cherrypick_generator/lib/src/annotation_validator.dart b/cherrypick_generator/lib/src/annotation_validator.dart index 6ab8869..5df8208 100644 --- a/cherrypick_generator/lib/src/annotation_validator.dart +++ b/cherrypick_generator/lib/src/annotation_validator.dart @@ -15,9 +15,43 @@ import 'package:analyzer/dart/element/element.dart'; import 'exceptions.dart'; import 'metadata_utils.dart'; -/// Validates annotation combinations and usage patterns +/// Provides static utility methods for validating annotation usage in CherryPick +/// dependency injection code generation. +/// +/// This validator helps ensure that `@provide`, `@instance`, `@singleton`, `@params`, +/// `@inject`, `@named`, `@module`, and `@injectable` annotations are correctly and safely +/// combined in your codebase, preventing common configuration and codegen errors before +/// code is generated. +/// +/// #### Example Usage +/// ```dart +/// void processMethod(MethodElement method) { +/// AnnotationValidator.validateMethodAnnotations(method); +/// } +/// ``` +/// +/// All exceptions are thrown as [AnnotationValidationException] and will include +/// a helpful message and context. +/// +/// --- +/// Typical checks performed by this utility: +/// - Mutual exclusivity (`@instance` vs `@provide`) +/// - Required presence for fields and methods +/// - Proper parameters for `@named` and `@params` +/// - Correct usage of injectable fields, module class methods, etc. +/// class AnnotationValidator { - /// Validates annotations on a method element + /// Validates annotations for a given [MethodElement]. + /// + /// Checks: + /// - Mutual exclusivity of `@instance` and `@provide`. + /// - That a method is annotated with exactly one DI-producing annotation. + /// - If `@params` is present, that it is used together with `@provide`. + /// - Appropriate usage of `@singleton`. + /// - [@named] syntax and conventions. + /// - Parameter validation for method arguments. + /// + /// Throws [AnnotationValidationException] on any violation. static void validateMethodAnnotations(MethodElement method) { final annotations = _getAnnotationNames(method.metadata); @@ -26,14 +60,28 @@ class AnnotationValidator { _validateAnnotationParameters(method); } - /// Validates annotations on a field element + /// Validates that a [FieldElement] has correct injection annotations. + /// + /// Specifically, ensures: + /// - Injectable fields are of valid type. + /// - No `void` injection. + /// - Correct scope naming if present. + /// + /// Throws [AnnotationValidationException] if checks fail. static void validateFieldAnnotations(FieldElement field) { final annotations = _getAnnotationNames(field.metadata); _validateInjectFieldAnnotations(field, annotations); } - /// Validates annotations on a class element + /// Validates all class-level DI annotations. + /// + /// Executes checks for: + /// - Module class validity (e.g. must have public DI methods if `@module`). + /// - Injectable class: at least one @inject field, field finalness, etc. + /// - Provides helpful context for error/warning reporting. + /// + /// Throws [AnnotationValidationException] if checks fail. static void validateClassAnnotations(ClassElement classElement) { final annotations = _getAnnotationNames(classElement.metadata); @@ -41,6 +89,9 @@ class AnnotationValidator { _validateInjectableClassAnnotations(classElement, annotations); } + // --- Internal helpers follow (private) --- + + /// Helper: Returns the names of all annotation types on `metadata`. static List _getAnnotationNames(List metadata) { return metadata .map((m) => m.computeConstantValue()?.type?.getDisplayString()) @@ -49,6 +100,9 @@ class AnnotationValidator { .toList(); } + /// Validates that mutually exclusive method annotations are not used together. + /// + /// For example, `@instance` and `@provide` cannot both be present. static void _validateMutuallyExclusiveAnnotations( MethodElement method, List annotations, @@ -68,6 +122,10 @@ class AnnotationValidator { } } + /// Validates correct annotation combinations, e.g. + /// - `@params` must be with `@provide` + /// - One of `@instance` or `@provide` must be present for a registration method + /// - Validates singleton usage static void _validateAnnotationCombinations( MethodElement method, List annotations, @@ -105,6 +163,7 @@ class AnnotationValidator { } } + /// Singleton-specific method annotation checks. static void _validateSingletonUsage( MethodElement method, List annotations, @@ -130,6 +189,7 @@ class AnnotationValidator { } } + /// Validates extra requirements or syntactic rules for annotation arguments, like @named. static void _validateAnnotationParameters(MethodElement method) { // Validate @named annotation parameters final namedValue = MetadataUtils.getNamedValue(method.metadata); @@ -170,11 +230,11 @@ class AnnotationValidator { } } + /// Checks that @params is used with compatible parameter type. static void _validateParamsParameter( ParameterElement param, MethodElement method) { // @params parameter should typically be dynamic or Map final paramType = param.type.getDisplayString(); - if (paramType != 'dynamic' && paramType != 'Map' && paramType != 'Map?') { @@ -194,6 +254,7 @@ class AnnotationValidator { } } + /// Checks field-level annotation for valid injectable fields. static void _validateInjectFieldAnnotations( FieldElement field, List annotations, @@ -227,6 +288,7 @@ class AnnotationValidator { } } + /// Checks @module usage: must have at least one DI method, each with DI-annotation. static void _validateModuleClassAnnotations( ClassElement classElement, List annotations, @@ -268,6 +330,7 @@ class AnnotationValidator { } } + /// Checks @injectable usage on classes and their fields. static void _validateInjectableClassAnnotations( ClassElement classElement, List annotations, diff --git a/cherrypick_generator/lib/src/bind_parameters_spec.dart b/cherrypick_generator/lib/src/bind_parameters_spec.dart index 7ae827a..3194304 100644 --- a/cherrypick_generator/lib/src/bind_parameters_spec.dart +++ b/cherrypick_generator/lib/src/bind_parameters_spec.dart @@ -12,57 +12,59 @@ // /// ---------------------------------------------------------------------------- -/// BindParameterSpec - describes a single method parameter and how to resolve it. +/// BindParameterSpec /// -/// ENGLISH -/// Describes a single parameter for a provider/binding method in the DI system. -/// Stores the parameter type, its optional `@named` key for named resolution, -/// and whether it is a runtime "params" argument. Used to generate code that -/// resolves dependencies from the DI scope: -/// - If the parameter is a dependency type (e.g. SomeDep), emits: -/// currentScope.resolve() -/// - If the parameter is named, emits: -/// currentScope.resolve(named: 'yourName') -/// - If it's a runtime parameter (e.g. via @params()), emits: -/// args +/// Describes a single parameter for a DI provider/factory/binding method, +/// specifying how that parameter is to be resolved in generated code. /// -/// RUSSIAN -/// Описывает один параметр метода в DI, и его способ разрешения из контейнера. -/// Сохраняет имя типа, дополнительное имя (если параметр аннотирован через @named), -/// и признак runtime-параметра (@params). -/// Для обычной зависимости типа (например, SomeDep) генерирует строку вида: -/// currentScope.resolve() -/// Для зависимости с именем: -/// currentScope.resolve(named: 'имя') -/// Для runtime-параметра: -/// args +/// Stores the parameter's type name, optional `@named` identifier (for named DI resolution), +/// and a flag for runtime (@params) arguments. Used in CherryPick generator +/// for creating argument lists when invoking factories or provider methods. +/// +/// ## Example usage +/// ```dart +/// // Binding method: @provide() Logger provideLogger(@named('debug') Config config, @params Map args) +/// final namedParam = BindParameterSpec('Config', 'debug'); +/// final runtimeParam = BindParameterSpec('Map', null, isParams: true); +/// print(namedParam.generateArg()); // prints: currentScope.resolve(named: 'debug') +/// print(runtimeParam.generateArg()); // prints: args +/// ``` +/// +/// ## Code generation logic +/// - Injected: currentScope.resolve() +/// - Named: currentScope.resolve(named: 'name') +/// - @params: args /// ---------------------------------------------------------------------------- class BindParameterSpec { - /// Type name of the parameter (e.g. SomeService) - /// Имя типа параметра (например, SomeService) + /// The type name of the parameter (e.g., 'UserRepository') final String typeName; - /// Optional name for named resolution (from @named) - /// Необязательное имя для разрешения по имени (если аннотировано через @named) + /// If non-null, this is the named-key for DI resolution (from @named). final String? named; - /// True if this parameter uses @params and should be provided from runtime args - /// Признак, что параметр — runtime (через @params) + /// True if this parameter is a runtime param (annotated with @params and + /// filled by a runtime argument map). final bool isParams; BindParameterSpec(this.typeName, this.named, {this.isParams = false}); - /// -------------------------------------------------------------------------- - /// generateArg + /// Generates Dart code to resolve this parameter in the DI container. /// - /// ENGLISH - /// Generates Dart code for resolving the dependency from the DI scope, - /// considering type, named, and param-argument. + /// - For normal dependencies: resolves by type + /// - For named dependencies: resolves by type and name + /// - For @params: uses the supplied params variable (default 'args') /// - /// RUSSIAN - /// Генерирует строку для получения зависимости из DI scope (с учётом имени - /// и типа параметра или runtime-режима @params). - /// -------------------------------------------------------------------------- + /// ## Example + /// ```dart + /// final a = BindParameterSpec('Api', null); // normal + /// print(a.generateArg()); // currentScope.resolve() + /// + /// final b = BindParameterSpec('Api', 'prod'); // named + /// print(b.generateArg()); // currentScope.resolve(named: 'prod') + /// + /// final c = BindParameterSpec('Map', null, isParams: true); // params + /// print(c.generateArg()); // args + /// ``` String generateArg([String paramsVar = 'args']) { if (isParams) { return paramsVar; diff --git a/cherrypick_generator/lib/src/bind_spec.dart b/cherrypick_generator/lib/src/bind_spec.dart index ec3dc4b..280bec3 100644 --- a/cherrypick_generator/lib/src/bind_spec.dart +++ b/cherrypick_generator/lib/src/bind_spec.dart @@ -19,62 +19,63 @@ import 'exceptions.dart'; import 'type_parser.dart'; import 'annotation_validator.dart'; +/// Enum representing the binding annotation applied to a module method. enum BindingType { + /// Direct instance returned from the method (@instance). instance, + /// Provider/factory function (@provide). provide; } /// --------------------------------------------------------------------------- -/// BindSpec -- describes a binding specification generated for a dependency. +/// BindSpec /// -/// ENGLISH -/// Represents all the data necessary to generate a DI binding for a single -/// method in a module class. Each BindSpec corresponds to one public method -/// and contains information about its type, provider method, lifecycle (singleton), -/// parameters (with their annotations), binding strategy (instance/provide), -/// asynchronous mode, and named keys. It is responsible for generating the -/// correct Dart code to register this binding with the DI container, in both -/// sync and async cases, with and without named or runtime arguments. +/// Describes a DI container binding as generated from a single public factory, +/// instance, or provider method of a module (annotated with @instance or @provide). /// -/// RUSSIAN -/// Описывает параметры для создания одного биндинга зависимости (binding spec). -/// Каждый биндинг соответствует одному публичному методу класса-модуля и -/// содержит всю информацию для генерации кода регистрации этого биндинга в -/// DI-контейнере: тип возвращаемой зависимости, имя метода, параметры, аннотации -/// (@singleton, @named, @instance, @provide), асинхронность, признак runtime -/// аргументов и др. Генерирует правильный Dart-код для регистрации биндера. +/// Includes all annotation-driven parameters required to generate valid DI +/// registration Dart code in CherryPick: +/// - Return type +/// - Provider method name +/// - Singleton flag +/// - Named identifier (from @named) +/// - List of resolved or runtime (@params) parameters +/// - Binding mode (instance/provide) +/// - Async and parametric variants +/// +/// ## Example usage +/// ```dart +/// // Suppose @provide() Api api(@named('test') Client client) +/// final bindSpec = BindSpec.fromMethod(methodElement); +/// print(bindSpec.generateBind(2)); // bind().toProvide(() => api(currentScope.resolve(named: 'test'))); +/// ``` /// --------------------------------------------------------------------------- class BindSpec { /// The type this binding provides (e.g. SomeService) - /// Тип, который предоставляет биндинг (например, SomeService) final String returnType; - /// Method name that implements the binding - /// Имя метода, который реализует биндинг + /// Binding provider/factory method name final String methodName; - /// Optional name for named dependency (from @named) - /// Необязательное имя, для именованной зависимости (используется с @named) + /// Named identifier for DI resolution (null if unnamed) final String? named; - /// Whether the dependency is a singleton (@singleton annotation) - /// Является ли зависимость синглтоном (имеется ли аннотация @singleton) + /// If true, binding is registered as singleton in DI final bool isSingleton; - /// List of method parameters to inject dependencies with - /// Список параметров, которые требуются методу для внедрения зависимостей + /// Provider/factory method parameters (in order) final List parameters; - /// Binding type: 'instance' or 'provide' (@instance or @provide) - final BindingType bindingType; // 'instance' | 'provide' + /// Instance vs provider mode, from annotation choice + final BindingType bindingType; - /// True if the method is asynchronous and uses instance binding (Future) + /// Async flag for .toInstanceAsync() final bool isAsyncInstance; - /// True if the method is asynchronous and uses provide binding (Future) + /// Async flag for .toProvideAsync() final bool isAsyncProvide; - /// True if the binding method accepts runtime "params" argument (@params) + /// True if a @params runtime parameter is present final bool hasParams; BindSpec({ @@ -89,20 +90,12 @@ class BindSpec { required this.hasParams, }); - /// ------------------------------------------------------------------------- - /// generateBind + /// Generates a Dart code line for binding registration. /// - /// ENGLISH - /// Generates a line of Dart code registering the binding with the DI framework. - /// Produces something like: - /// bind().toProvide(() => method(args)).withName('name').singleton(); - /// Indent parameter allows formatted multiline output. + /// Example (single-line): + /// bind().toProvide(() => provideApi(currentScope.resolve(named: 'test'))).withName('prod').singleton(); /// - /// RUSSIAN - /// Формирует dart-код для биндинга, например: - /// bind().toProvide(() => method(args)).withName('name').singleton(); - /// Параметр [indent] задаёт отступ для красивого форматирования кода. - /// ------------------------------------------------------------------------- + /// The [indent] argument sets the space indentation for pretty-printing. String generateBind(int indent) { final indentStr = ' ' * indent; final provide = _generateProvideClause(indent); @@ -151,7 +144,7 @@ class BindSpec { return _generatePlainProvideClause(indent); } - /// EN / RU: Supports runtime parameters (@params). + /// Generates code when using runtime parameters (@params). String _generateWithParamsProvideClause(int indent) { // Safe variable name for parameters. const paramVar = 'args'; @@ -178,7 +171,7 @@ class BindSpec { } } - /// EN / RU: Supports only injected dependencies, not runtime (@params). + /// Generates code when only resolved (not runtime) arguments used. String _generatePlainProvideClause(int indent) { final argsStr = parameters.map((p) => p.generateArg()).join(', '); @@ -241,16 +234,17 @@ class BindSpec { /// ------------------------------------------------------------------------- /// fromMethod /// - /// ENGLISH - /// Creates a BindSpec from a module class method by analyzing its return type, - /// annotations, list of parameters (with their own annotations), and async-ness. - /// Throws if a method does not have the required @instance() or @provide(). + /// Constructs a [BindSpec] from an analyzer [MethodElement]. /// - /// RUSSIAN - /// Создаёт спецификацию биндинга (BindSpec) из метода класса-модуля, анализируя - /// возвращаемый тип, аннотации, параметры (и их аннотации), а также факт - /// асинхронности. Если нет @instance или @provide — кидает ошибку. - /// ------------------------------------------------------------------------- + /// Validates and parses all type annotations, method/parameter DI hints, + /// and derives async and parametric flags as needed. + /// + /// ## Example + /// ```dart + /// final bindSpec = BindSpec.fromMethod(methodElement); + /// print(bindSpec.returnType); // e.g., 'Logger' + /// ``` + /// Throws [AnnotationValidationException] or [CodeGenerationException] if invalid. static BindSpec fromMethod(MethodElement method) { try { // Validate method annotations diff --git a/cherrypick_generator/lib/src/exceptions.dart b/cherrypick_generator/lib/src/exceptions.dart index 94e333a..167249a 100644 --- a/cherrypick_generator/lib/src/exceptions.dart +++ b/cherrypick_generator/lib/src/exceptions.dart @@ -14,10 +14,36 @@ import 'package:analyzer/dart/element/element.dart'; import 'package:source_gen/source_gen.dart'; -/// Enhanced exception class for CherryPick generator with detailed context information +/// --------------------------------------------------------------------------- +/// CherryPickGeneratorException +/// +/// The base exception for all CherryPick code generation and annotation +/// validation errors. This exception provides enhanced diagnostics including +/// the error category, helpful suggestions, and additional debugging context. +/// +/// All errors are structured to be as helpful as possible for users +/// running build_runner and for CherryPick contributors debugging generators. +/// +/// ## Example usage: +/// ```dart +/// if (someErrorCondition) { +/// throw AnnotationValidationException( +/// 'Custom message about what went wrong', +/// element: methodElement, +/// suggestion: 'Add @provide() or @instance() annotation', +/// context: {'found_annotations': annotations}, +/// ); +/// } +/// ``` +/// --------------------------------------------------------------------------- class CherryPickGeneratorException extends InvalidGenerationSourceError { + /// A string describing the error category (for grouping). final String category; + + /// An optional suggestion string for resolving the error. final String? suggestion; + + /// Arbitrary key-value map for additional debugging information. final Map? context; CherryPickGeneratorException( @@ -50,7 +76,7 @@ class CherryPickGeneratorException extends InvalidGenerationSourceError { buffer.writeln(' Type: ${element.runtimeType}'); buffer.writeln(' Location: ${element.source?.fullName ?? 'unknown'}'); - // Note: enclosingElement may not be available in all analyzer versions + // Try to show enclosing element info for extra context try { final enclosing = (element as dynamic).enclosingElement; if (enclosing != null) { @@ -60,7 +86,7 @@ class CherryPickGeneratorException extends InvalidGenerationSourceError { // Ignore if enclosingElement is not available } - // Additional context + // Arbitrary user context if (context != null && context.isNotEmpty) { buffer.writeln(''); buffer.writeln('Additional Context:'); @@ -69,7 +95,7 @@ class CherryPickGeneratorException extends InvalidGenerationSourceError { }); } - // Suggestion + // Hint/suggestion if present if (suggestion != null) { buffer.writeln(''); buffer.writeln('💡 Suggestion: $suggestion'); @@ -79,7 +105,24 @@ class CherryPickGeneratorException extends InvalidGenerationSourceError { } } -/// Specific exception types for different error categories +/// --------------------------------------------------------------------------- +/// AnnotationValidationException +/// +/// Thrown when annotation usage is invalid (e.g., missing required annotation, +/// mutually exclusive annotations, or incorrect @named format). +/// +/// Grouped as category "ANNOTATION_VALIDATION". +/// +/// ## Example: +/// ```dart +/// throw AnnotationValidationException( +/// '@instance and @provide cannot be used together', +/// element: method, +/// suggestion: 'Use only one of @instance or @provide.', +/// context: {'method_name': method.displayName}, +/// ); +/// ``` +/// --------------------------------------------------------------------------- class AnnotationValidationException extends CherryPickGeneratorException { AnnotationValidationException( super.message, { @@ -89,6 +132,24 @@ class AnnotationValidationException extends CherryPickGeneratorException { }) : super(category: 'ANNOTATION_VALIDATION'); } +/// --------------------------------------------------------------------------- +/// TypeParsingException +/// +/// Thrown when a Dart type cannot be interpreted/parsed for DI, +/// or if it's not compatible (void, raw Future, etc). +/// +/// Grouped as category "TYPE_PARSING". +/// +/// ## Example: +/// ```dart +/// throw TypeParsingException( +/// 'Cannot parse injected type', +/// element: field, +/// suggestion: 'Specify a concrete type. Avoid dynamic and raw Future.', +/// context: {'type': field.type.getDisplayString()}, +/// ); +/// ``` +/// --------------------------------------------------------------------------- class TypeParsingException extends CherryPickGeneratorException { TypeParsingException( super.message, { @@ -98,6 +159,23 @@ class TypeParsingException extends CherryPickGeneratorException { }) : super(category: 'TYPE_PARSING'); } +/// --------------------------------------------------------------------------- +/// CodeGenerationException +/// +/// Thrown on unexpected code generation or formatting failures +/// during generator execution. +/// +/// Grouped as category "CODE_GENERATION". +/// +/// ## Example: +/// ```dart +/// throw CodeGenerationException( +/// 'Could not generate module binding', +/// element: classElement, +/// suggestion: 'Check module class methods and signatures.', +/// ); +/// ``` +/// --------------------------------------------------------------------------- class CodeGenerationException extends CherryPickGeneratorException { CodeGenerationException( super.message, { @@ -107,6 +185,23 @@ class CodeGenerationException extends CherryPickGeneratorException { }) : super(category: 'CODE_GENERATION'); } +/// --------------------------------------------------------------------------- +/// DependencyResolutionException +/// +/// Thrown if dependency information (for example, types or names) +/// cannot be resolved during code generation analysis. +/// +/// Grouped as category "DEPENDENCY_RESOLUTION". +/// +/// ## Example: +/// ```dart +/// throw DependencyResolutionException( +/// 'Dependency type not found in scope', +/// element: someElement, +/// suggestion: 'Check CherryPick registration for this type.', +/// ); +/// ``` +/// --------------------------------------------------------------------------- class DependencyResolutionException extends CherryPickGeneratorException { DependencyResolutionException( super.message, { diff --git a/cherrypick_generator/lib/src/generated_class.dart b/cherrypick_generator/lib/src/generated_class.dart index 7ba8c37..4981b31 100644 --- a/cherrypick_generator/lib/src/generated_class.dart +++ b/cherrypick_generator/lib/src/generated_class.dart @@ -12,45 +12,48 @@ // import 'package:analyzer/dart/element/element.dart'; - import 'bind_spec.dart'; /// --------------------------------------------------------------------------- -/// GeneratedClass -- represents the result of processing a single module class. +/// GeneratedClass /// -/// ENGLISH -/// Encapsulates all the information produced from analyzing a DI module class: -/// - The original class name, -/// - Its generated class name (e.g., `$SomeModule`), -/// - The collection of bindings (BindSpec) for all implemented provider methods. +/// Represents a processed DI module class with all its binding methods analyzed. +/// Stores: +/// - The original class name, +/// - The generated implementation class name (with $ prefix), +/// - The list of all BindSpec for the module methods, +/// - The source file name for reference or directive generation. /// -/// Also provides code generation functionality, allowing to generate the source -/// code for the derived DI module class, including all binding registrations. +/// Provides static and instance methods to construct from a ClassElement +/// and generate Dart source code for the resulting DI registration class. /// -/// RUSSIAN -/// Описывает результат обработки одного класса-модуля DI: -/// - Имя оригинального класса, -/// - Имя генерируемого класса (например, `$SomeModule`), -/// - Список всех бидингов (BindSpec) — по публичным методам модуля. -/// -/// Также содержит функцию генерации исходного кода для этого класса и -/// регистрации всех зависимостей через bind(...). +/// ## Example usage +/// ```dart +/// final gen = GeneratedClass.fromClassElement(myModuleClassElement); +/// print(gen.generate()); +/// /* +/// Produces: +/// final class $MyModule extends MyModule { +/// @override +/// void builder(Scope currentScope) { +/// bind().toProvide(() => provideService(currentScope.resolve())); +/// ... +/// } +/// } +/// */ +/// ``` /// --------------------------------------------------------------------------- class GeneratedClass { - /// The name of the original module class. - /// Имя исходного класса-модуля + /// Name of the original Dart module class. final String className; - /// The name of the generated class (e.g., $SomeModule). - /// Имя генерируемого класса (например, $SomeModule) + /// Name of the generated class, e.g. `$MyModule` final String generatedClassName; - /// List of all discovered bindings for the class. - /// Список всех обнаруженных биндингов + /// Binding specs for all provider/factory methods in the class. final List binds; - /// Source file name for the part directive - /// Имя исходного файла для part директивы + /// Source filename of the module class (for code references). final String sourceFile; GeneratedClass( @@ -63,16 +66,15 @@ class GeneratedClass { /// ------------------------------------------------------------------------- /// fromClassElement /// - /// ENGLISH - /// Static factory: creates a GeneratedClass from a Dart ClassElement (AST representation). - /// Discovers all non-abstract methods, builds BindSpec for each, and computes the - /// generated class name by prefixing `$`. + /// Creates a [GeneratedClass] by analyzing a Dart [ClassElement]. + /// Collects all public non-abstract methods, creates a [BindSpec] for each, + /// and infers the generated class name using a `$` prefix. /// - /// RUSSIAN - /// Строит объект класса по элементу AST (ClassElement): имя класса, - /// сгенерированное имя, список BindSpec по всем не абстрактным методам. - /// Имя ген-класса строится с префиксом `$`. - /// ------------------------------------------------------------------------- + /// ## Example usage + /// ```dart + /// final gen = GeneratedClass.fromClassElement(classElement); + /// print(gen.generatedClassName); // e.g. $AppModule + /// ``` static GeneratedClass fromClassElement(ClassElement element) { final className = element.displayName; // Generated class name with '$' prefix (standard for generated Dart code). @@ -91,16 +93,19 @@ class GeneratedClass { /// ------------------------------------------------------------------------- /// generate /// - /// ENGLISH - /// Generates Dart source code for the DI module class. The generated class - /// inherits from the original, overrides the 'builder' method, and registers - /// all bindings in the DI scope. + /// Generates the Dart source code for the DI registration class. + /// The generated class extends the original module, and the `builder` method + /// registers all bindings (dependencies) into the DI scope. /// - /// RUSSIAN - /// Генерирует исходный Dart-код для класса-модуля DI. - /// Новая версия класса наследует оригинальный, переопределяет builder(Scope), - /// и регистрирует все зависимости через методы bind()... - /// ------------------------------------------------------------------------- + /// ## Example output + /// ```dart + /// final class $UserModule extends UserModule { + /// @override + /// void builder(Scope currentScope) { + /// bind().toProvide(() => provideService(currentScope.resolve())); + /// } + /// } + /// ``` String generate() { final buffer = StringBuffer() ..writeln('final class $generatedClassName extends $className {') @@ -108,7 +113,6 @@ class GeneratedClass { ..writeln(' void builder(Scope currentScope) {'); // For each binding, generate bind() code string. - // Для каждого биндинга — генерируем строку bind()... for (final bind in binds) { buffer.writeln(bind.generateBind(4)); } diff --git a/cherrypick_generator/lib/src/metadata_utils.dart b/cherrypick_generator/lib/src/metadata_utils.dart index 8da7197..3eebc51 100644 --- a/cherrypick_generator/lib/src/metadata_utils.dart +++ b/cherrypick_generator/lib/src/metadata_utils.dart @@ -14,30 +14,32 @@ import 'package:analyzer/dart/element/element.dart'; /// --------------------------------------------------------------------------- -/// MetadataUtils -- utilities for analyzing method and parameter annotations. +/// MetadataUtils /// -/// ENGLISH -/// Provides static utility methods to analyze Dart annotations on methods or -/// parameters. For instance, helps to find if an element is annotated with -/// `@named()`, `@singleton()`, or other meta-annotations used in this DI framework. +/// Static utilities for querying and extracting information from +/// Dart annotations ([ElementAnnotation]) in the context of code generation, +/// such as checking for the presence of specific DI-related annotations. +/// Designed to be used internally by code generation and validation routines. /// -/// RUSSIAN -/// Утилиты для разбора аннотаций методов и параметров. -/// Позволяют находить наличие аннотаций, например, @named() и @singleton(), -/// у методов и параметров. Используется для анализа исходного кода при генерации. +/// # Example usage +/// ```dart +/// if (MetadataUtils.anyMeta(method.metadata, 'singleton')) { +/// // The method is annotated with @singleton +/// } +/// final name = MetadataUtils.getNamedValue(field.metadata); +/// if (name != null) print('@named value: $name'); +/// ``` /// --------------------------------------------------------------------------- class MetadataUtils { - /// ------------------------------------------------------------------------- - /// anyMeta + /// Checks whether any annotation in [meta] matches the [typeName] + /// (type name is compared in a case-insensitive manner and can be partial). /// - /// ENGLISH - /// Checks if any annotation in the list has a type name that contains - /// [typeName] (case insensitive). + /// Returns true if an annotation (such as @singleton, @provide, @named) is found. /// - /// RUSSIAN - /// Проверяет: есть ли среди аннотаций метка, имя которой содержит [typeName] - /// (регистр не учитывается). - /// ------------------------------------------------------------------------- + /// Example: + /// ```dart + /// bool isSingleton = MetadataUtils.anyMeta(myMethod.metadata, 'singleton'); + /// ``` static bool anyMeta(List meta, String typeName) { return meta.any((m) => m @@ -49,17 +51,15 @@ class MetadataUtils { false); } - /// ------------------------------------------------------------------------- - /// getNamedValue + /// Extracts the string value from a `@named('value')` annotation if present in [meta]. /// - /// ENGLISH - /// Retrieves the value from a `@named('value')` annotation if present. - /// Returns the string value or null if not found. + /// Returns the named value or `null` if not annotated. /// - /// RUSSIAN - /// Находит значение из аннотации @named('значение'). - /// Возвращает строку значения, если аннотация присутствует; иначе null. - /// ------------------------------------------------------------------------- + /// Example: + /// ```dart + /// // For: @named('dev') ApiClient provideApi() ... + /// final named = MetadataUtils.getNamedValue(method.metadata); // 'dev' + /// ``` static String? getNamedValue(List meta) { for (final m in meta) { final cv = m.computeConstantValue(); diff --git a/cherrypick_generator/lib/src/type_parser.dart b/cherrypick_generator/lib/src/type_parser.dart index dc68369..12f1378 100644 --- a/cherrypick_generator/lib/src/type_parser.dart +++ b/cherrypick_generator/lib/src/type_parser.dart @@ -16,9 +16,35 @@ import 'package:analyzer/dart/element/nullability_suffix.dart'; import 'package:analyzer/dart/element/type.dart'; import 'exceptions.dart'; -/// Enhanced type parser that uses AST analysis instead of regular expressions +/// Utility for analyzing and parsing Dart types for CherryPick DI code generation. +/// +/// This type parser leverages the Dart analyzer AST to extract nuanced information +/// from Dart types encountered in the source code, in particular for dependency +/// injection purposes. It is capable of extracting nullability, generics, +/// and Future-related metadata with strong guarantees and handles even nested generics. +/// +/// # Example usage for parsing types: +/// ```dart +/// final parsed = TypeParser.parseType(method.returnType, method); +/// print(parsed); +/// print(parsed.resolveMethodName); // e.g. "resolveAsync" or "tryResolve" +/// ``` +/// +/// # Supported scenarios: +/// - Nullable types (e.g., `List?`) +/// - Generic types (e.g., `Map`) +/// - Async types (`Future`, including nested generics) +/// - Validation for DI compatibility (throws for `void`, warns on `dynamic`) class TypeParser { - /// Parses a DartType and extracts detailed type information + /// Parses a [DartType] and extracts detailed type information for use in code generation. + /// + /// If a type is not suitable or cannot be parsed, a [TypeParsingException] is thrown. + /// + /// Example: + /// ```dart + /// final parsed = TypeParser.parseType(field.type, field); + /// if (parsed.isNullable) print('Field is nullable'); + /// ``` static ParsedType parseType(DartType dartType, Element context) { try { return _parseTypeInternal(dartType, context); @@ -49,7 +75,7 @@ class TypeParser { return _parseGenericType(dartType, context, isNullable); } - // Simple type + // Simple type (non-generic, non-Future) return ParsedType( displayString: displayString, coreType: displayString.replaceAll('?', ''), @@ -103,7 +129,15 @@ class TypeParser { ); } - /// Validates that a type is suitable for dependency injection + /// Validates that a parsed type is suitable for dependency injection. + /// + /// Throws [TypeParsingException] for void and may warn for dynamic. + /// + /// Example: + /// ```dart + /// final parsed = TypeParser.parseType(field.type, field); + /// TypeParser.validateInjectableType(parsed, field); + /// ``` static void validateInjectableType(ParsedType parsedType, Element context) { // Check for void type if (parsedType.coreType == 'void') { @@ -131,7 +165,7 @@ class TypeParser { } } -/// Represents a parsed type with detailed information +/// Represents a parsed type with full metadata for code generation. class ParsedType { /// The full display string of the type (e.g., "Future?>") final String displayString; @@ -139,19 +173,19 @@ class ParsedType { /// The core type name without nullability and Future wrapper (e.g., "List") final String coreType; - /// Whether the type is nullable + /// True if nullable (has `?`) final bool isNullable; - /// Whether the type is wrapped in Future + /// True if this type is a `Future` final bool isFuture; - /// Whether the type has generic parameters + /// True if the type is a generic type (`List`) final bool isGeneric; - /// Parsed type arguments for generic types + /// List of parsed type arguments in generics, if any. final List typeArguments; - /// For Future types, the inner type + /// For `Future`, this is the type inside the `Future`. final ParsedType? innerType; const ParsedType({ @@ -164,7 +198,11 @@ class ParsedType { this.innerType, }); - /// Returns the type string suitable for code generation + /// Generates the type string suitable for code generation. + /// + /// - For futures, the codegen type of the inner type is returned + /// - For generics, returns e.g. `List` + /// - For plain types, just the name String get codeGenType { if (isFuture && innerType != null) { return innerType!.codeGenType; @@ -179,10 +217,10 @@ class ParsedType { return coreType; } - /// Returns whether this type should use tryResolve instead of resolve + /// True if this type should use `tryResolve` instead of `resolve` for DI. bool get shouldUseTryResolve => isNullable; - /// Returns the appropriate resolve method name + /// Returns the method name for DI, e.g. "resolve", "tryResolveAsync", etc. String get resolveMethodName { if (isFuture) { return shouldUseTryResolve ? 'tryResolveAsync' : 'resolveAsync';