update: package analyzer pass 10 fail 1

This commit is contained in:
Patrick Stewart 2024-12-25 17:19:57 -07:00
parent d0ff4db4a0
commit d728fcb9dd
6 changed files with 963 additions and 3 deletions

View file

@ -4,6 +4,7 @@ import 'package:platform_contracts/contracts.dart';
import 'src/core/mirror_system.dart'; import 'src/core/mirror_system.dart';
import 'src/mirrors/instance_mirror.dart'; import 'src/mirrors/instance_mirror.dart';
import 'src/reflector/runtime_reflector.dart';
/// Annotations /// Annotations
export 'src/annotations/reflectable.dart'; export 'src/annotations/reflectable.dart';
@ -16,6 +17,7 @@ export 'src/discovery/type_analyzer.dart';
export 'src/discovery/library_analyzer.dart'; export 'src/discovery/library_analyzer.dart';
export 'src/discovery/runtime_library_discoverer.dart'; export 'src/discovery/runtime_library_discoverer.dart';
export 'src/discovery/runtime_type_discoverer.dart'; export 'src/discovery/runtime_type_discoverer.dart';
export 'src/discovery/package_analyzer.dart';
/// Discovery Models /// Discovery Models
export 'src/discovery/models/models.dart'; export 'src/discovery/models/models.dart';
@ -58,6 +60,9 @@ export 'src/registry/reflection_registry.dart';
/// Types /// Types
export 'src/types/special_types.dart'; export 'src/types/special_types.dart';
// Export the runtime reflector instance globally
final reflector = RuntimeReflector.instance;
/// Reflects an instance. /// Reflects an instance.
InstanceMirrorContract reflect(dynamic reflectee) { InstanceMirrorContract reflect(dynamic reflectee) {
return InstanceMirror( return InstanceMirror(

View file

@ -0,0 +1,654 @@
import 'dart:io';
import 'package:path/path.dart' as path;
import '../metadata/type_metadata.dart';
import '../metadata/constructor_metadata.dart';
import '../metadata/method_metadata.dart';
import '../metadata/parameter_metadata.dart';
import '../metadata/property_metadata.dart';
import '../registry/reflection_registry.dart';
import '../reflector/runtime_reflector.dart';
import 'proxy_type.dart';
/// Discovers and analyzes types in a package automatically.
class PackageAnalyzer {
// Private constructor to prevent instantiation
PackageAnalyzer._();
// Cache of discovered types and mappings
static final Map<String, Set<Type>> _packageTypes = {};
static final Map<String, Type> _typeCache = {};
static final Map<Type, String> _typeNameCache = {};
// The runtime reflector instance
static final _reflector = RuntimeReflector.instance;
/// Gets or creates a Type instance for a type name.
static Type _getTypeForName(String name) {
// Remove any generic type parameters and whitespace
name = name.split('<')[0].trim();
return _typeCache.putIfAbsent(name, () {
// For built-in types, return the actual type
switch (name) {
case 'String':
return String;
case 'int':
return int;
case 'double':
return double;
case 'bool':
return bool;
case 'List':
return List;
case 'Map':
return Map;
case 'Set':
return Set;
case 'Object':
return Object;
case 'dynamic':
return Object; // Use Object as fallback for dynamic
case 'void':
return Object; // Use Object as fallback for void
case 'Null':
return Object; // Use Object as fallback for Null
default:
// For user-defined types, create a proxy type
final type = ProxyType(name);
_typeNameCache[type] = name;
ReflectionRegistry.register(type);
return type;
}
});
}
/// Gets the name of a Type.
static String? getTypeName(Type type) {
if (type == String) return 'String';
if (type == int) return 'int';
if (type == double) return 'double';
if (type == bool) return 'bool';
if (type == List) return 'List';
if (type == Map) return 'Map';
if (type == Set) return 'Set';
if (type == Object) return _typeNameCache[type] ?? 'Object';
return _typeNameCache[type];
}
/// Discovers all types in a package.
static Set<Type> discoverTypes(String packagePath) {
if (_packageTypes.containsKey(packagePath)) {
return _packageTypes[packagePath]!;
}
final types = _scanPackage(packagePath);
_packageTypes[packagePath] = types;
return types;
}
/// Scans a package directory for Dart files and extracts type information.
static Set<Type> _scanPackage(String packagePath) {
final types = <Type>{};
final libDir = Directory(path.join(packagePath, 'lib'));
if (!libDir.existsSync()) {
return types;
}
// Get all .dart files recursively
final dartFiles = libDir
.listSync(recursive: true)
.where((f) => f.path.endsWith('.dart'))
.cast<File>();
for (final file in dartFiles) {
try {
// Parse file and extract type information
final fileTypes = _analyzeFile(file.path);
types.addAll(fileTypes);
} catch (e) {
print('Warning: Failed to analyze ${file.path}: $e');
}
}
return types;
}
/// Analyzes a Dart file and extracts type information.
static Set<Type> _analyzeFile(String filePath) {
final types = <Type>{};
final source = File(filePath).readAsStringSync();
// Extract class declarations using regex
final classRegex = RegExp(
r'(?:abstract\s+)?class\s+(\w+)(?:<[^>]+>)?(?:\s+extends\s+(\w+)(?:<[^>]+>)?)?(?:\s+implements\s+([^{]+))?\s*{',
multiLine: true);
final matches = classRegex.allMatches(source);
for (final match in matches) {
final className = match.group(1)!;
final superclass = match.group(2);
final interfaces = match.group(3);
// Extract class content for further analysis
final classContent = _extractClassContent(source, className);
// Extract properties
final properties = _extractProperties(classContent, className);
// Extract methods
final methods = _extractMethods(classContent, className);
// Extract constructors
final constructors = _extractConstructors(classContent, className);
// Register with reflection system
_registerType(
className,
properties,
methods,
constructors,
superclass,
interfaces,
source.contains('abstract class $className'),
);
// Add to discovered types
types.add(_getTypeForName(className));
}
return types;
}
/// Registers a type with the reflection system.
static void _registerType(
String className,
Map<String, PropertyMetadata> properties,
Map<String, MethodMetadata> methods,
List<ConstructorMetadata> constructors,
String? superclass,
String? interfaces,
bool isAbstract,
) {
final type = _getTypeForName(className);
// Register properties
properties.forEach((name, metadata) {
ReflectionRegistry.registerProperty(
type,
name,
metadata.type,
isWritable: metadata.isWritable,
);
});
// Register methods
methods.forEach((name, metadata) {
// Get parameter names for named parameters only
final parameterNames = metadata.parameters
.where((p) => p.isNamed)
.map((p) => p.name)
.toList();
// Get required flags for named parameters only
final isRequired = metadata.parameters
.where((p) => p.isNamed)
.map((p) => p.isRequired)
.toList();
// Get isNamed flags for all parameters
final isNamed = metadata.parameters.map((p) => p.isNamed).toList();
ReflectionRegistry.registerMethod(
type,
name,
metadata.parameterTypes,
metadata.returnsVoid,
returnType: metadata.returnType,
parameterNames: parameterNames.isNotEmpty ? parameterNames : null,
isRequired: isRequired.isNotEmpty ? isRequired : null,
isNamed: isNamed.isNotEmpty ? isNamed : null,
isStatic: metadata.isStatic,
);
});
// Register constructors
for (final constructor in constructors) {
// Get parameter names for named parameters
final parameterNames = constructor.parameters
.where((p) => p.isNamed)
.map((p) => p.name)
.toList();
ReflectionRegistry.registerConstructor(
type,
constructor.name,
parameterTypes: constructor.parameterTypes,
parameterNames: parameterNames.isNotEmpty ? parameterNames : null,
);
}
// Create type metadata
final metadata = TypeMetadata(
type: type,
name: className,
properties: properties,
methods: methods,
constructors: constructors,
supertype: superclass != null
? TypeMetadata(
type: _getTypeForName(superclass),
name: superclass,
properties: const {},
methods: const {},
constructors: const [],
)
: null,
interfaces: interfaces
?.split(',')
.map((i) => i.trim())
.where((i) => i.isNotEmpty)
.map((i) => TypeMetadata(
type: _getTypeForName(i),
name: i,
properties: const {},
methods: const {},
constructors: const [],
))
.toList() ??
const [],
);
// Register metadata with reflection system
ReflectionRegistry.registerTypeMetadata(type, metadata);
}
/// Extracts property information from a class.
static Map<String, PropertyMetadata> _extractProperties(
String classContent, String className) {
final properties = <String, PropertyMetadata>{};
// Extract field declarations using regex
final fieldRegex = RegExp(
r'(?:final|const)?\s*(\w+(?:<[^>]+>)?)\s+(\w+)(?:\s*\?)?(?:\s*=\s*[^;]+)?;',
multiLine: true,
);
// Extract nullable field declarations
final nullableFieldRegex = RegExp(
r'(?:final|const)?\s*(\w+(?:<[^>]+>)?)\s*\?\s*(\w+)(?:\s*=\s*[^;]+)?;',
multiLine: true,
);
// Extract field declarations from initializer lists
final initializerRegex = RegExp(
r':\s*([^{]+)',
multiLine: true,
);
// Extract constructor declarations
final constructorRegex = RegExp(
r'$className\s*\(((?:[^)]|\n)*)\)',
multiLine: true,
);
// Process regular field declarations
final fieldMatches = fieldRegex.allMatches(classContent);
for (final match in fieldMatches) {
final type = match.group(1)!;
final name = match.group(2)!;
final fullDecl = classContent.substring(match.start, match.end);
properties[name] = PropertyMetadata(
name: name,
type: _getTypeForName(type),
isReadable: true,
isWritable: !fullDecl.contains('final $type') &&
!fullDecl.contains('const $type'),
);
}
// Process nullable field declarations
final nullableMatches = nullableFieldRegex.allMatches(classContent);
for (final match in nullableMatches) {
final type = match.group(1)!;
final name = match.group(2)!;
final fullDecl = classContent.substring(match.start, match.end);
properties[name] = PropertyMetadata(
name: name,
type: _getTypeForName(type),
isReadable: true,
isWritable: !fullDecl.contains('final $type') &&
!fullDecl.contains('const $type'),
);
}
// Process initializer list assignments
final initializerMatch = initializerRegex.firstMatch(classContent);
if (initializerMatch != null) {
final initializers = initializerMatch.group(1)!;
// Extract assignments in initializer list
final assignmentRegex = RegExp(
r'(\w+)\s*=\s*([^,{]+)(?:,|\s*{)',
multiLine: true,
);
final assignmentMatches = assignmentRegex.allMatches(initializers);
for (final match in assignmentMatches) {
final name = match.group(1)!;
if (!properties.containsKey(name)) {
// Find type from field declaration or parameter
final typeMatch = RegExp(
r'(?:final|const)?\s*(\w+(?:<[^>]+>)?)\s+' +
name +
r'(?:\s*\?)?[;=]',
).firstMatch(classContent);
if (typeMatch != null) {
final type = typeMatch.group(1)!;
properties[name] = PropertyMetadata(
name: name,
type: _getTypeForName(type),
isReadable: true,
isWritable: !classContent.contains('final $type $name') &&
!classContent.contains('const $type $name'),
);
}
}
}
}
// Process constructor parameters
final constructorMatch = constructorRegex.firstMatch(classContent);
if (constructorMatch != null) {
final paramList = constructorMatch.group(1)!;
// Extract positional parameters
final positionalRegex = RegExp(
r'(?:required\s+)?(\w+(?:<[^>]+>)?)\s+(?:this\.)?(\w+)(?:\s*\?)?(?=\s*,|\s*\{|\s*\))',
multiLine: true,
);
final positionalMatches = positionalRegex.allMatches(paramList);
for (final match in positionalMatches) {
final type = match.group(1)!;
final name = match.group(2)!;
final fullParam = paramList.substring(match.start, match.end);
final isNullable = fullParam.contains('$type?') ||
fullParam.contains('$type ?') ||
fullParam.contains('this.$name?');
if (!properties.containsKey(name)) {
properties[name] = PropertyMetadata(
name: name,
type: _getTypeForName(type),
isReadable: true,
isWritable: !classContent.contains('final $type $name') &&
!classContent.contains('const $type $name'),
);
}
}
// Extract named parameters section
final namedParamsMatch = RegExp(r'{([^}]*)}').firstMatch(paramList);
if (namedParamsMatch != null) {
final namedParams = namedParamsMatch.group(1)!;
// Extract named parameters
final namedRegex = RegExp(
r'(?:required\s+)?(\w+(?:<[^>]+>)?)\s+(?:this\.)?(\w+)(?:\s*\?)?(?:\s*=\s*[^,}]+)?(?:\s*,|\s*$)',
multiLine: true,
);
final namedMatches = namedRegex.allMatches(namedParams);
for (final match in namedMatches) {
final type = match.group(1)!;
final name = match.group(2)!;
final fullParam = namedParams.substring(match.start, match.end);
final isNullable = fullParam.contains('$type?') ||
fullParam.contains('$type ?') ||
fullParam.contains('this.$name?');
if (!properties.containsKey(name)) {
properties[name] = PropertyMetadata(
name: name,
type: _getTypeForName(type),
isReadable: true,
isWritable: !classContent.contains('final $type $name') &&
!classContent.contains('const $type $name'),
);
}
}
}
}
// Extract getter declarations
final getterRegex = RegExp(
r'(?:get|set)\s+(\w+)(?:\s*=>|\s*{)',
multiLine: true,
);
final getterMatches = getterRegex.allMatches(classContent);
for (final match in getterMatches) {
final name = match.group(1)!;
if (!properties.containsKey(name)) {
// Try to find the return type from the getter implementation
final getterImpl = RegExp(
r'get\s+' + name + r'\s*(?:=>|\{)\s*(?:return\s+)?(\w+)',
).firstMatch(classContent);
final type = getterImpl?.group(1) ?? 'dynamic';
properties[name] = PropertyMetadata(
name: name,
type: _getTypeForName(type),
isReadable: true,
isWritable: classContent.contains('set $name'),
);
}
}
return properties;
}
/// Extracts method information from a class.
static Map<String, MethodMetadata> _extractMethods(
String classContent, String className) {
final methods = <String, MethodMetadata>{};
// Extract method declarations using regex
final methodRegex = RegExp(
r'(?:static\s+)?(\w+(?:<[^>]+>)?)\s+(\w+)\s*\((.*?)\)',
multiLine: true,
);
final matches = methodRegex.allMatches(classContent);
for (final match in matches) {
final returnType = match.group(1)!;
final name = match.group(2)!;
final params = match.group(3)!;
if (name != className) {
// Skip constructors
methods[name] = MethodMetadata(
name: name,
parameterTypes: _extractParameterTypes(params),
parameters: _extractParameters(params),
returnsVoid: returnType == 'void',
returnType: _getTypeForName(returnType),
isStatic: classContent.contains('static $returnType $name'),
);
}
}
return methods;
}
/// Extracts constructor information from a class.
static List<ConstructorMetadata> _extractConstructors(
String classContent, String className) {
final constructors = <ConstructorMetadata>[];
// Find the class declaration
final classRegex = RegExp(
'(?:abstract\\s+)?class\\s+' + RegExp.escape(className) + '[^{]*\\{',
);
final classMatch = classRegex.firstMatch(classContent);
if (classMatch == null) return constructors;
print('Class content for $className:');
print(classContent);
// Extract all constructor declarations using regex
final constructorPattern = '(?:const\\s+)?(?:factory\\s+)?' +
RegExp.escape(className) +
'(?:\\.([\\w.]+))?\\s*\\(([^)]*?)\\)(?:\\s*:[^{;]*?)?(?:\\s*(?:=>|{|;))';
print('Constructor pattern: $constructorPattern');
final constructorRegex =
RegExp(constructorPattern, multiLine: true, dotAll: true);
final matches = constructorRegex.allMatches(classContent);
print('Found ${matches.length} constructor matches:');
for (final match in matches) {
final fullMatch = classContent.substring(match.start, match.end);
print('Full match: $fullMatch');
print('Group 1 (name): ${match.group(1)}');
print('Group 2 (params): ${match.group(2)}');
}
// Use a map to deduplicate constructors by name
final constructorMap = <String, ConstructorMetadata>{};
// Process constructors
for (final match in matches) {
final fullMatch = classContent.substring(match.start, match.end);
final name = match.group(1) ?? '';
final params = match.group(2) ?? '';
final isFactory = fullMatch.trim().startsWith('factory');
// For factory constructors without a name, use 'create'
final constructorName = isFactory && name.isEmpty ? 'create' : name;
// Only add if we haven't seen this constructor name before
if (!constructorMap.containsKey(constructorName)) {
constructorMap[constructorName] = ConstructorMetadata(
name: constructorName,
parameterTypes: _extractParameterTypes(params),
parameters: _extractParameters(params),
);
}
}
final result = constructorMap.values.toList();
print('Returning ${result.length} constructors');
return result;
}
/// Extracts parameter types from a parameter list string.
static List<Type> _extractParameterTypes(String params) {
final types = <Type>[];
final paramRegex = RegExp(
r'(?:required\s+)?(\w+(?:<[^>]+>)?)\s+(?:this\.)?(\w+)(?:\s*\?)?',
multiLine: true,
);
final matches = paramRegex.allMatches(params);
for (final match in matches) {
final type = match.group(1)!;
types.add(_getTypeForName(type));
}
return types;
}
/// Extracts parameters from a parameter list string.
static List<ParameterMetadata> _extractParameters(String params) {
final parameters = <ParameterMetadata>[];
final paramRegex = RegExp(
r'(?:required\s+)?(\w+(?:<[^>]+>)?)\s+(?:this\.)?(\w+)(?:\s*\?)?(?:\s*=\s*([^,}]+))?(?:,|\s*$|\s*\}|\s*\)|$)',
multiLine: true,
);
final matches = paramRegex.allMatches(params);
for (final match in matches) {
final type = match.group(1)!;
final name = match.group(2)!;
final defaultValue = match.group(3);
parameters.add(ParameterMetadata(
name: name,
type: _getTypeForName(type),
isRequired: params.contains('required $type $name'),
isNamed: params.contains('{') && params.contains('}'),
defaultValue:
defaultValue != null ? _parseDefaultValue(defaultValue) : null,
));
}
return parameters;
}
/// Extracts the content of a class from source code.
static String _extractClassContent(String source, String className) {
// Find the class declaration
final classRegex = RegExp(
'(?:abstract\\s+)?class\\s+' + RegExp.escape(className) + '[^{]*{',
);
final match = classRegex.firstMatch(source);
if (match == null) return '';
final startIndex = match.start;
var bracketCount = 1;
var inString = false;
var stringChar = '';
var content = source.substring(startIndex, match.end);
// Extract everything between the opening and closing braces
for (var i = match.end; i < source.length; i++) {
final char = source[i];
// Handle string literals to avoid counting braces inside strings
if ((char == '"' || char == "'") && source[i - 1] != '\\') {
if (!inString) {
inString = true;
stringChar = char;
} else if (stringChar == char) {
inString = false;
}
}
if (!inString) {
if (char == '{') {
bracketCount++;
} else if (char == '}') {
bracketCount--;
if (bracketCount == 0) {
content += source.substring(match.end, i + 1);
break;
}
}
}
}
return content;
}
/// Parses a default value string into an actual value.
static dynamic _parseDefaultValue(String value) {
// Basic parsing of common default values
if (value == 'null') return null;
if (value == 'true') return true;
if (value == 'false') return false;
if (value.startsWith("'") && value.endsWith("'")) {
return value.substring(1, value.length - 1);
}
if (int.tryParse(value) != null) return int.parse(value);
if (double.tryParse(value) != null) return double.parse(value);
return value;
}
}

View file

@ -0,0 +1,20 @@
/// A proxy type used for representing user-defined types during reflection.
class ProxyType implements Type {
/// The name of the type.
final String name;
/// Creates a new proxy type with the given name.
const ProxyType(this.name);
@override
String toString() => name;
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is ProxyType && other.name == name;
}
@override
int get hashCode => name.hashCode;
}

View file

@ -17,7 +17,7 @@ class RuntimeTypeDiscoverer {
ReflectionRegistry.register(type); ReflectionRegistry.register(type);
// Get mirror system and analyze type // Get mirror system and analyze type
//final mirrorSystem = MirrorSystem.current(); final mirrorSystem = MirrorSystem.current();
final typeInfo = TypeAnalyzer.analyze(type); final typeInfo = TypeAnalyzer.analyze(type);
// Convert properties, methods, and constructors to metadata // Convert properties, methods, and constructors to metadata

View file

@ -7,9 +7,11 @@ environment:
sdk: '>=3.0.0 <4.0.0' sdk: '>=3.0.0 <4.0.0'
dependencies: dependencies:
platform_contracts: ^0.1.0
meta: ^1.9.0
collection: ^1.17.0 collection: ^1.17.0
meta: ^1.9.0
path: ^1.9.1
platform_contracts: ^0.1.0
dev_dependencies: dev_dependencies:
test: ^1.24.0 test: ^1.24.0

View file

@ -0,0 +1,279 @@
import 'dart:io';
import 'package:path/path.dart' as path;
import 'package:platform_mirrors/mirrors.dart';
import 'package:test/test.dart';
// Test classes
@reflectable
class TestUser {
final String id;
String name;
int? age;
List<String> tags;
TestUser(this.id, this.name, {this.age, List<String>? tags})
: tags = tags ?? [];
static TestUser create(String id, String name) => TestUser(id, name);
void addTag(String tag) {
tags.add(tag);
}
String greet() => 'Hello, $name!';
}
@reflectable
abstract class BaseEntity {
String get id;
Map<String, dynamic> toJson();
}
@reflectable
class TestProduct implements BaseEntity {
@override
final String id;
final String name;
final double price;
const TestProduct(this.id, this.name, this.price);
factory TestProduct.create(String name, double price) {
return TestProduct(DateTime.now().toString(), name, price);
}
@override
Map<String, dynamic> toJson() => {
'id': id,
'name': name,
'price': price,
};
}
void main() {
group('PackageAnalyzer Tests', () {
late Directory tempDir;
late String libPath;
setUp(() async {
// Create temporary directory structure
tempDir = await Directory.systemTemp.createTemp('package_analyzer_test_');
libPath = path.join(tempDir.path, 'lib');
await Directory(libPath).create();
// Create test files
await File(path.join(libPath, 'test_user.dart')).writeAsString('''
import 'package:platform_mirrors/mirrors.dart';
@reflectable
class TestUser {
final String id;
String name;
int? age;
List<String> tags;
TestUser(this.id, this.name, {this.age, List<String>? tags})
: tags = tags ?? [];
static TestUser create(String id, String name) => TestUser(id, name);
void addTag(String tag) {
tags.add(tag);
}
String greet() => 'Hello, \$name!';
}
''');
await File(path.join(libPath, 'test_product.dart')).writeAsString('''
import 'package:platform_mirrors/mirrors.dart';
@reflectable
abstract class BaseEntity {
String get id;
Map<String, dynamic> toJson();
}
@reflectable
class TestProduct implements BaseEntity {
@override
final String id;
final String name;
final double price;
const TestProduct(this.id, this.name, this.price);
factory TestProduct.create(String name, double price) {
return TestProduct(DateTime.now().toString(), name, price);
}
@override
Map<String, dynamic> toJson() => {
'id': id,
'name': name,
'price': price,
};
}
''');
});
tearDown(() async {
// Clean up temporary directory
await tempDir.delete(recursive: true);
// Reset reflection registry
ReflectionRegistry.reset();
});
test('discovers all types in package', () {
final types = PackageAnalyzer.discoverTypes(tempDir.path);
expect(types, isNotEmpty);
// Verify type names are registered
final typeNames =
types.map((t) => PackageAnalyzer.getTypeName(t)).toSet();
expect(typeNames, containsAll(['TestUser', 'BaseEntity', 'TestProduct']));
});
test('analyzes class properties correctly', () {
final types = PackageAnalyzer.discoverTypes(tempDir.path);
final userType =
types.firstWhere((t) => PackageAnalyzer.getTypeName(t) == 'TestUser');
final metadata = ReflectionRegistry.getTypeMetadata(userType);
expect(metadata, isNotNull);
expect(metadata!.properties, hasLength(4));
final idProperty = metadata.properties['id']!;
expect(PackageAnalyzer.getTypeName(idProperty.type), equals('String'));
expect(idProperty.isWritable, isFalse);
final nameProperty = metadata.properties['name']!;
expect(PackageAnalyzer.getTypeName(nameProperty.type), equals('String'));
expect(nameProperty.isWritable, isTrue);
});
test('analyzes class methods correctly', () {
final types = PackageAnalyzer.discoverTypes(tempDir.path);
final userType =
types.firstWhere((t) => PackageAnalyzer.getTypeName(t) == 'TestUser');
final metadata = ReflectionRegistry.getTypeMetadata(userType);
expect(metadata, isNotNull);
expect(metadata!.methods, hasLength(3)); // addTag, greet, create
final addTagMethod = metadata.methods['addTag']!;
expect(PackageAnalyzer.getTypeName(addTagMethod.parameterTypes.first),
equals('String'));
expect(addTagMethod.returnsVoid, isTrue);
final greetMethod = metadata.methods['greet']!;
expect(greetMethod.parameterTypes, isEmpty);
expect(PackageAnalyzer.getTypeName(greetMethod.returnType),
equals('String'));
});
test('analyzes constructors correctly', () {
final types = PackageAnalyzer.discoverTypes(tempDir.path);
final userType =
types.firstWhere((t) => PackageAnalyzer.getTypeName(t) == 'TestUser');
final metadata = ReflectionRegistry.getTypeMetadata(userType);
expect(metadata, isNotNull);
expect(metadata!.constructors, hasLength(1));
final constructor = metadata.constructors.first;
expect(constructor.parameterTypes, hasLength(4));
expect(constructor.parameters.where((p) => p.isNamed), hasLength(2));
});
test('analyzes inheritance correctly', () {
final types = PackageAnalyzer.discoverTypes(tempDir.path);
final productType = types
.firstWhere((t) => PackageAnalyzer.getTypeName(t) == 'TestProduct');
final metadata = ReflectionRegistry.getTypeMetadata(productType);
expect(metadata, isNotNull);
expect(metadata!.interfaces, hasLength(1));
expect(metadata.interfaces.first.name, equals('BaseEntity'));
});
test('handles abstract classes correctly', () {
final types = PackageAnalyzer.discoverTypes(tempDir.path);
final baseEntityType = types
.firstWhere((t) => PackageAnalyzer.getTypeName(t) == 'BaseEntity');
final metadata = ReflectionRegistry.getTypeMetadata(baseEntityType);
expect(metadata, isNotNull);
expect(metadata!.methods['toJson'], isNotNull);
expect(metadata.properties['id'], isNotNull);
});
test('handles factory constructors correctly', () {
final types = PackageAnalyzer.discoverTypes(tempDir.path);
final productType = types
.firstWhere((t) => PackageAnalyzer.getTypeName(t) == 'TestProduct');
final metadata = ReflectionRegistry.getTypeMetadata(productType);
expect(metadata, isNotNull);
final factoryConstructor =
metadata!.constructors.firstWhere((c) => c.name == 'create');
expect(factoryConstructor, isNotNull);
expect(
factoryConstructor.parameterTypes
.map((t) => PackageAnalyzer.getTypeName(t)),
equals(['String', 'double']));
});
test('handles static methods correctly', () {
final types = PackageAnalyzer.discoverTypes(tempDir.path);
final userType =
types.firstWhere((t) => PackageAnalyzer.getTypeName(t) == 'TestUser');
final metadata = ReflectionRegistry.getTypeMetadata(userType);
expect(metadata, isNotNull);
final createMethod = metadata!.methods['create']!;
expect(createMethod.isStatic, isTrue);
expect(
createMethod.parameterTypes
.map((t) => PackageAnalyzer.getTypeName(t)),
equals(['String', 'String']));
});
test('handles nullable types correctly', () {
final types = PackageAnalyzer.discoverTypes(tempDir.path);
final userType =
types.firstWhere((t) => PackageAnalyzer.getTypeName(t) == 'TestUser');
final metadata = ReflectionRegistry.getTypeMetadata(userType);
expect(metadata, isNotNull);
final ageProperty = metadata!.properties['age']!;
expect(PackageAnalyzer.getTypeName(ageProperty.type), equals('int'));
// Note: Currently we don't track nullability information
// This could be enhanced in future versions
});
test('caches discovered types', () {
final firstRun = PackageAnalyzer.discoverTypes(tempDir.path);
final secondRun = PackageAnalyzer.discoverTypes(tempDir.path);
expect(identical(firstRun, secondRun), isTrue);
});
test('handles generic types correctly', () {
final types = PackageAnalyzer.discoverTypes(tempDir.path);
final userType =
types.firstWhere((t) => PackageAnalyzer.getTypeName(t) == 'TestUser');
final metadata = ReflectionRegistry.getTypeMetadata(userType);
expect(metadata, isNotNull);
final tagsProperty = metadata!.properties['tags']!;
expect(PackageAnalyzer.getTypeName(tagsProperty.type), equals('List'));
// Note: Currently we don't track generic type arguments
// This could be enhanced in future versions
});
});
}