2025-05-21 15:50:24 +03:00
|
|
|
|
//
|
|
|
|
|
|
// 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.
|
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
|
|
import 'package:analyzer/dart/element/element.dart';
|
|
|
|
|
|
import 'package:source_gen/source_gen.dart';
|
|
|
|
|
|
|
|
|
|
|
|
import 'bind_parameters_spec.dart';
|
|
|
|
|
|
import 'metadata_utils.dart';
|
|
|
|
|
|
|
2025-05-22 23:32:26 +03:00
|
|
|
|
enum BindingType {
|
|
|
|
|
|
instance,
|
|
|
|
|
|
provide;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-05-22 16:05:09 +03:00
|
|
|
|
/// ---------------------------------------------------------------------------
|
|
|
|
|
|
/// BindSpec -- describes a binding specification generated for a dependency.
|
2025-05-21 15:50:24 +03:00
|
|
|
|
///
|
2025-05-22 16:05:09 +03:00
|
|
|
|
/// 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.
|
2025-05-21 15:50:24 +03:00
|
|
|
|
///
|
2025-05-22 16:05:09 +03:00
|
|
|
|
/// RUSSIAN
|
|
|
|
|
|
/// Описывает параметры для создания одного биндинга зависимости (binding spec).
|
|
|
|
|
|
/// Каждый биндинг соответствует одному публичному методу класса-модуля и
|
|
|
|
|
|
/// содержит всю информацию для генерации кода регистрации этого биндинга в
|
|
|
|
|
|
/// DI-контейнере: тип возвращаемой зависимости, имя метода, параметры, аннотации
|
|
|
|
|
|
/// (@singleton, @named, @instance, @provide), асинхронность, признак runtime
|
|
|
|
|
|
/// аргументов и др. Генерирует правильный Dart-код для регистрации биндера.
|
|
|
|
|
|
/// ---------------------------------------------------------------------------
|
2025-05-21 15:50:24 +03:00
|
|
|
|
class BindSpec {
|
2025-05-22 16:05:09 +03:00
|
|
|
|
/// The type this binding provides (e.g. SomeService)
|
2025-05-21 15:50:24 +03:00
|
|
|
|
/// Тип, который предоставляет биндинг (например, SomeService)
|
|
|
|
|
|
final String returnType;
|
|
|
|
|
|
|
2025-05-22 16:05:09 +03:00
|
|
|
|
/// Method name that implements the binding
|
2025-05-21 15:50:24 +03:00
|
|
|
|
/// Имя метода, который реализует биндинг
|
|
|
|
|
|
final String methodName;
|
|
|
|
|
|
|
2025-05-22 16:05:09 +03:00
|
|
|
|
/// Optional name for named dependency (from @named)
|
2025-05-21 15:50:24 +03:00
|
|
|
|
/// Необязательное имя, для именованной зависимости (используется с @named)
|
|
|
|
|
|
final String? named;
|
|
|
|
|
|
|
2025-05-22 16:05:09 +03:00
|
|
|
|
/// Whether the dependency is a singleton (@singleton annotation)
|
2025-05-21 15:50:24 +03:00
|
|
|
|
/// Является ли зависимость синглтоном (имеется ли аннотация @singleton)
|
|
|
|
|
|
final bool isSingleton;
|
|
|
|
|
|
|
2025-05-22 16:05:09 +03:00
|
|
|
|
/// List of method parameters to inject dependencies with
|
2025-05-21 15:50:24 +03:00
|
|
|
|
/// Список параметров, которые требуются методу для внедрения зависимостей
|
|
|
|
|
|
final List<BindParameterSpec> parameters;
|
|
|
|
|
|
|
2025-05-22 16:05:09 +03:00
|
|
|
|
/// Binding type: 'instance' or 'provide' (@instance or @provide)
|
2025-05-22 23:32:26 +03:00
|
|
|
|
final BindingType bindingType; // 'instance' | 'provide'
|
2025-05-21 15:50:24 +03:00
|
|
|
|
|
2025-05-22 16:05:09 +03:00
|
|
|
|
/// True if the method is asynchronous and uses instance binding (Future)
|
2025-05-21 15:50:24 +03:00
|
|
|
|
final bool isAsyncInstance;
|
|
|
|
|
|
|
2025-05-22 16:05:09 +03:00
|
|
|
|
/// True if the method is asynchronous and uses provide binding (Future)
|
2025-05-21 15:50:24 +03:00
|
|
|
|
final bool isAsyncProvide;
|
|
|
|
|
|
|
2025-05-22 16:05:09 +03:00
|
|
|
|
/// True if the binding method accepts runtime "params" argument (@params)
|
2025-05-21 15:50:24 +03:00
|
|
|
|
final bool hasParams;
|
|
|
|
|
|
|
|
|
|
|
|
BindSpec({
|
|
|
|
|
|
required this.returnType,
|
|
|
|
|
|
required this.methodName,
|
|
|
|
|
|
required this.isSingleton,
|
|
|
|
|
|
required this.parameters,
|
|
|
|
|
|
this.named,
|
|
|
|
|
|
required this.bindingType,
|
|
|
|
|
|
required this.isAsyncInstance,
|
|
|
|
|
|
required this.isAsyncProvide,
|
|
|
|
|
|
required this.hasParams,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2025-05-22 16:05:09 +03:00
|
|
|
|
/// -------------------------------------------------------------------------
|
|
|
|
|
|
/// generateBind
|
|
|
|
|
|
///
|
|
|
|
|
|
/// ENGLISH
|
|
|
|
|
|
/// Generates a line of Dart code registering the binding with the DI framework.
|
|
|
|
|
|
/// Produces something like:
|
2025-05-21 15:50:24 +03:00
|
|
|
|
/// bind<Type>().toProvide(() => method(args)).withName('name').singleton();
|
2025-05-22 16:05:09 +03:00
|
|
|
|
/// Indent parameter allows formatted multiline output.
|
2025-05-21 15:50:24 +03:00
|
|
|
|
///
|
2025-05-22 16:05:09 +03:00
|
|
|
|
/// RUSSIAN
|
|
|
|
|
|
/// Формирует dart-код для биндинга, например:
|
|
|
|
|
|
/// bind<Type>().toProvide(() => method(args)).withName('name').singleton();
|
2025-05-21 15:50:24 +03:00
|
|
|
|
/// Параметр [indent] задаёт отступ для красивого форматирования кода.
|
2025-05-22 16:05:09 +03:00
|
|
|
|
/// -------------------------------------------------------------------------
|
2025-05-21 15:50:24 +03:00
|
|
|
|
String generateBind(int indent) {
|
|
|
|
|
|
final indentStr = ' ' * indent;
|
2025-05-21 15:59:11 +03:00
|
|
|
|
final provide = _generateProvideClause(indent);
|
|
|
|
|
|
final postfix = _generatePostfix();
|
|
|
|
|
|
return '$indentStr'
|
|
|
|
|
|
'bind<$returnType>()'
|
|
|
|
|
|
'$provide'
|
|
|
|
|
|
'$postfix;';
|
|
|
|
|
|
}
|
2025-05-21 15:50:24 +03:00
|
|
|
|
|
2025-05-22 16:05:09 +03:00
|
|
|
|
// Internal method: decides how the provide clause should be generated by param kind.
|
2025-05-21 15:59:11 +03:00
|
|
|
|
String _generateProvideClause(int indent) {
|
|
|
|
|
|
if (hasParams) return _generateWithParamsProvideClause(indent);
|
|
|
|
|
|
return _generatePlainProvideClause(indent);
|
|
|
|
|
|
}
|
2025-05-21 15:50:24 +03:00
|
|
|
|
|
2025-05-22 16:05:09 +03:00
|
|
|
|
/// EN / RU: Supports runtime parameters (@params).
|
2025-05-21 15:59:11 +03:00
|
|
|
|
String _generateWithParamsProvideClause(int indent) {
|
2025-05-22 16:05:09 +03:00
|
|
|
|
// Safe variable name for parameters.
|
2025-05-21 15:59:11 +03:00
|
|
|
|
const paramVar = 'args';
|
2025-05-22 23:27:41 +03:00
|
|
|
|
final fnArgs = parameters.map((p) => p.generateArg(paramVar)).join(', ');
|
2025-05-21 15:59:11 +03:00
|
|
|
|
final multiLine = fnArgs.length > 60 || fnArgs.contains('\n');
|
|
|
|
|
|
switch (bindingType) {
|
2025-05-22 23:32:26 +03:00
|
|
|
|
case BindingType.instance:
|
2025-05-21 15:59:11 +03:00
|
|
|
|
return isAsyncInstance
|
2025-05-21 15:50:24 +03:00
|
|
|
|
? '.toInstanceAsync(($fnArgs) => $methodName($fnArgs))'
|
|
|
|
|
|
: '.toInstance(($fnArgs) => $methodName($fnArgs))';
|
2025-05-22 23:32:26 +03:00
|
|
|
|
case BindingType.provide:
|
2025-05-21 15:59:11 +03:00
|
|
|
|
default:
|
|
|
|
|
|
if (isAsyncProvide) {
|
|
|
|
|
|
return multiLine
|
|
|
|
|
|
? '.toProvideAsyncWithParams(\n${' ' * (indent + 2)}($paramVar) => $methodName($fnArgs))'
|
|
|
|
|
|
: '.toProvideAsyncWithParams(($paramVar) => $methodName($fnArgs))';
|
|
|
|
|
|
} else {
|
|
|
|
|
|
return multiLine
|
|
|
|
|
|
? '.toProvideWithParams(\n${' ' * (indent + 2)}($paramVar) => $methodName($fnArgs))'
|
|
|
|
|
|
: '.toProvideWithParams(($paramVar) => $methodName($fnArgs))';
|
|
|
|
|
|
}
|
2025-05-21 15:50:24 +03:00
|
|
|
|
}
|
2025-05-21 15:59:11 +03:00
|
|
|
|
}
|
2025-05-21 15:50:24 +03:00
|
|
|
|
|
2025-05-22 16:05:09 +03:00
|
|
|
|
/// EN / RU: Supports only injected dependencies, not runtime (@params).
|
2025-05-21 15:59:11 +03:00
|
|
|
|
String _generatePlainProvideClause(int indent) {
|
2025-05-21 15:50:24 +03:00
|
|
|
|
final argsStr = parameters.map((p) => p.generateArg()).join(', ');
|
2025-05-21 15:59:11 +03:00
|
|
|
|
final multiLine = argsStr.length > 60 || argsStr.contains('\n');
|
|
|
|
|
|
switch (bindingType) {
|
2025-05-22 23:32:26 +03:00
|
|
|
|
case BindingType.instance:
|
2025-05-21 15:59:11 +03:00
|
|
|
|
return isAsyncInstance
|
|
|
|
|
|
? '.toInstanceAsync($methodName($argsStr))'
|
|
|
|
|
|
: '.toInstance($methodName($argsStr))';
|
2025-05-22 23:32:26 +03:00
|
|
|
|
case BindingType.provide:
|
2025-05-21 15:59:11 +03:00
|
|
|
|
default:
|
|
|
|
|
|
if (isAsyncProvide) {
|
|
|
|
|
|
return multiLine
|
|
|
|
|
|
? '.toProvideAsync(\n${' ' * (indent + 2)}() => $methodName($argsStr))'
|
|
|
|
|
|
: '.toProvideAsync(() => $methodName($argsStr))';
|
|
|
|
|
|
} else {
|
|
|
|
|
|
return multiLine
|
|
|
|
|
|
? '.toProvide(\n${' ' * (indent + 2)}() => $methodName($argsStr))'
|
|
|
|
|
|
: '.toProvide(() => $methodName($argsStr))';
|
|
|
|
|
|
}
|
2025-05-21 15:50:24 +03:00
|
|
|
|
}
|
2025-05-21 15:59:11 +03:00
|
|
|
|
}
|
2025-05-21 15:50:24 +03:00
|
|
|
|
|
2025-05-22 16:05:09 +03:00
|
|
|
|
/// EN / RU: Adds .withName and .singleton if needed.
|
2025-05-21 15:59:11 +03:00
|
|
|
|
String _generatePostfix() {
|
2025-05-21 15:50:24 +03:00
|
|
|
|
final namePart = named != null ? ".withName('$named')" : '';
|
|
|
|
|
|
final singletonPart = isSingleton ? '.singleton()' : '';
|
2025-05-21 15:59:11 +03:00
|
|
|
|
return '$namePart$singletonPart';
|
2025-05-21 15:50:24 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-05-22 16:05:09 +03:00
|
|
|
|
/// -------------------------------------------------------------------------
|
|
|
|
|
|
/// 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().
|
|
|
|
|
|
///
|
|
|
|
|
|
/// RUSSIAN
|
|
|
|
|
|
/// Создаёт спецификацию биндинга (BindSpec) из метода класса-модуля, анализируя
|
|
|
|
|
|
/// возвращаемый тип, аннотации, параметры (и их аннотации), а также факт
|
|
|
|
|
|
/// асинхронности. Если нет @instance или @provide — кидает ошибку.
|
|
|
|
|
|
/// -------------------------------------------------------------------------
|
2025-05-21 15:50:24 +03:00
|
|
|
|
static BindSpec fromMethod(MethodElement method) {
|
|
|
|
|
|
var returnType = method.returnType.getDisplayString();
|
|
|
|
|
|
|
|
|
|
|
|
final methodName = method.displayName;
|
2025-05-22 16:05:09 +03:00
|
|
|
|
// Check for @singleton annotation.
|
2025-05-21 15:50:24 +03:00
|
|
|
|
final isSingleton = MetadataUtils.anyMeta(method.metadata, 'singleton');
|
|
|
|
|
|
|
2025-05-22 16:05:09 +03:00
|
|
|
|
// Get @named value if present.
|
2025-05-21 15:50:24 +03:00
|
|
|
|
final named = MetadataUtils.getNamedValue(method.metadata);
|
|
|
|
|
|
|
2025-05-22 16:05:09 +03:00
|
|
|
|
// Parse each method parameter.
|
2025-05-21 15:50:24 +03:00
|
|
|
|
final params = <BindParameterSpec>[];
|
|
|
|
|
|
bool hasParams = false;
|
|
|
|
|
|
for (final p in method.parameters) {
|
|
|
|
|
|
final typeStr = p.type.getDisplayString();
|
|
|
|
|
|
final paramNamed = MetadataUtils.getNamedValue(p.metadata);
|
|
|
|
|
|
final isParams = MetadataUtils.anyMeta(p.metadata, 'params');
|
|
|
|
|
|
if (isParams) hasParams = true;
|
|
|
|
|
|
params.add(BindParameterSpec(typeStr, paramNamed, isParams: isParams));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-05-22 16:05:09 +03:00
|
|
|
|
// Determine bindingType: @instance or @provide.
|
2025-05-21 15:50:24 +03:00
|
|
|
|
final hasInstance = MetadataUtils.anyMeta(method.metadata, 'instance');
|
|
|
|
|
|
final hasProvide = MetadataUtils.anyMeta(method.metadata, 'provide');
|
|
|
|
|
|
if (!hasInstance && !hasProvide) {
|
|
|
|
|
|
throw InvalidGenerationSourceError(
|
2025-05-22 16:05:09 +03:00
|
|
|
|
'Method $methodName must be marked with @instance() or @provide().',
|
2025-05-21 15:50:24 +03:00
|
|
|
|
element: method,
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
2025-05-22 23:32:26 +03:00
|
|
|
|
final bindingType =
|
|
|
|
|
|
hasInstance ? BindingType.instance : BindingType.provide;
|
2025-05-21 15:50:24 +03:00
|
|
|
|
|
2025-05-22 16:05:09 +03:00
|
|
|
|
// -- Extract inner type for Future<T> and set async flags.
|
2025-05-21 15:50:24 +03:00
|
|
|
|
bool isAsyncInstance = false;
|
|
|
|
|
|
bool isAsyncProvide = false;
|
2025-05-21 15:59:11 +03:00
|
|
|
|
final futureInnerType = _extractFutureInnerType(returnType);
|
|
|
|
|
|
if (futureInnerType != null) {
|
|
|
|
|
|
returnType = futureInnerType;
|
2025-05-22 23:32:26 +03:00
|
|
|
|
if (bindingType == BindingType.instance) isAsyncInstance = true;
|
|
|
|
|
|
if (bindingType == BindingType.provide) isAsyncProvide = true;
|
2025-05-21 15:50:24 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return BindSpec(
|
|
|
|
|
|
returnType: returnType,
|
|
|
|
|
|
methodName: methodName,
|
|
|
|
|
|
isSingleton: isSingleton,
|
|
|
|
|
|
named: named,
|
|
|
|
|
|
parameters: params,
|
|
|
|
|
|
bindingType: bindingType,
|
|
|
|
|
|
isAsyncInstance: isAsyncInstance,
|
|
|
|
|
|
isAsyncProvide: isAsyncProvide,
|
|
|
|
|
|
hasParams: hasParams,
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
2025-05-21 15:59:11 +03:00
|
|
|
|
|
2025-05-22 16:05:09 +03:00
|
|
|
|
/// EN / RU: Extracts inner type from Future<T>, returns e.g. "T" or null.
|
2025-05-21 15:59:11 +03:00
|
|
|
|
static String? _extractFutureInnerType(String typeName) {
|
|
|
|
|
|
final match = RegExp(r'^Future<(.+)>$').firstMatch(typeName);
|
|
|
|
|
|
return match?.group(1)?.trim();
|
|
|
|
|
|
}
|
2025-05-21 15:50:24 +03:00
|
|
|
|
}
|