refactor: adding support for class@method syntax

This commit is contained in:
Patrick Stewart 2024-12-26 23:25:59 -07:00
parent f4e567c046
commit a07bd666c2
3 changed files with 205 additions and 0 deletions

View file

@ -1033,6 +1033,41 @@ class Container {
}
}
/// Call a method on a resolved instance using Class@method syntax.
///
/// This allows you to resolve and call a method in one step:
/// ```dart
/// container.call('Logger@log', ['Hello world']);
/// ```
dynamic call(String target, [List<dynamic> parameters = const []]) {
var parts = target.split('@');
if (parts.length != 2) {
throw ArgumentError('Invalid Class@method syntax: $target');
}
var className = parts[0];
var methodName = parts[1];
// Find the type by name
var type = reflector.findTypeByName(className);
if (type == null) {
throw ArgumentError('Class not found: $className');
}
// Resolve the instance
var instance = make(type);
// Find and call the method
var method = reflector.findInstanceMethod(instance, methodName);
if (method == null) {
throw ArgumentError('Method not found: $methodName on $className');
}
return method
.invoke(Invocation.method(Symbol(methodName), parameters))
.reflectee;
}
/// Check if we're in danger of a circular dependency.
void _checkCircularDependency(Type type) {
if (_buildStack.contains(type)) {

View file

@ -43,6 +43,22 @@ abstract class Reflector {
ReflectedType reflectFutureOf(Type type) {
throw UnsupportedError('`reflectFutureOf` requires `dart:mirrors`.');
}
/// Find a type by its name.
///
/// This method is used to support Class@method syntax by finding a type
/// from its string name.
Type? findTypeByName(String name) {
throw UnsupportedError('`findTypeByName` requires `dart:mirrors`.');
}
/// Find an instance method by its name.
///
/// This method is used to support Class@method syntax by finding a method
/// on an instance from its string name.
ReflectedFunction? findInstanceMethod(Object instance, String methodName) {
throw UnsupportedError('`findInstanceMethod` requires `dart:mirrors`.');
}
}
/// Represents a reflected instance of an object.
@ -241,6 +257,9 @@ abstract class ReflectedFunction {
other.isGetter == isGetter &&
other.isSetter == isSetter;
/// Invoke this function with the given invocation.
///
/// This method is used to support dynamic method invocation.
ReflectedInstance invoke(Invocation invocation);
}

View file

@ -0,0 +1,151 @@
import 'package:platformed_container/container.dart';
import 'package:test/test.dart';
class Logger {
String log(String message) => message;
void debug(String message, {int? level}) {}
int count(List<String> items) => items.length;
}
class MockReflector extends Reflector {
@override
String? getName(Symbol symbol) => null;
@override
ReflectedClass? reflectClass(Type clazz) => null;
@override
ReflectedType? reflectType(Type type) => null;
@override
ReflectedInstance? reflectInstance(Object? instance) => null;
@override
ReflectedFunction? reflectFunction(Function function) => null;
@override
ReflectedType reflectFutureOf(Type type) => throw UnimplementedError();
@override
Type? findTypeByName(String name) {
if (name == 'Logger') return Logger;
return null;
}
@override
ReflectedFunction? findInstanceMethod(Object instance, String methodName) {
if (instance is Logger) {
switch (methodName) {
case 'log':
return MockMethod('log', (invocation) {
var args = invocation.positionalArguments;
return MockReflectedInstance(instance.log(args[0] as String));
});
case 'debug':
return MockMethod('debug', (invocation) {
var args = invocation.positionalArguments;
instance.debug(args[0] as String);
return MockReflectedInstance(null);
});
case 'count':
return MockMethod('count', (invocation) {
var args = invocation.positionalArguments;
return MockReflectedInstance(
instance.count(args[0] as List<String>));
});
}
}
return null;
}
}
class MockMethod implements ReflectedFunction {
final String methodName;
final ReflectedInstance Function(Invocation) handler;
MockMethod(this.methodName, this.handler);
@override
List<ReflectedInstance> get annotations => [];
@override
bool get isGetter => false;
@override
bool get isSetter => false;
@override
String get name => methodName;
@override
List<ReflectedParameter> get parameters => [];
@override
ReflectedType? get returnType => null;
@override
List<ReflectedTypeParameter> get typeParameters => [];
@override
ReflectedInstance invoke(Invocation invocation) => handler(invocation);
}
class MockReflectedInstance implements ReflectedInstance {
final dynamic value;
MockReflectedInstance(this.value);
@override
ReflectedClass get clazz => throw UnimplementedError();
@override
ReflectedInstance getField(String name) => throw UnimplementedError();
@override
dynamic get reflectee => value;
@override
ReflectedType get type => throw UnimplementedError();
}
void main() {
late Container container;
setUp(() {
container = Container(MockReflector());
container.registerSingleton(Logger());
});
group('Class@method syntax', () {
test('can call method with return value', () {
var result = container.call('Logger@log', ['Hello world']);
expect(result, equals('Hello world'));
});
test('can call void method', () {
expect(() => container.call('Logger@debug', ['Debug message']),
returnsNormally);
});
test('can call method with list parameter', () {
var result = container.call('Logger@count', [
['one', 'two', 'three']
]);
expect(result, equals(3));
});
test('throws on invalid syntax', () {
expect(() => container.call('Logger'), throwsArgumentError);
expect(() => container.call('Logger@'), throwsArgumentError);
expect(() => container.call('@log'), throwsArgumentError);
});
test('throws on unknown class', () {
expect(() => container.call('Unknown@method'), throwsArgumentError);
});
test('throws on unknown method', () {
expect(() => container.call('Logger@unknown'), throwsArgumentError);
});
});
}