update: package analyzer pass 10 fail 1
This commit is contained in:
parent
d0ff4db4a0
commit
d728fcb9dd
6 changed files with 963 additions and 3 deletions
|
@ -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(
|
||||||
|
|
654
packages/mirrors/lib/src/discovery/package_analyzer.dart
Normal file
654
packages/mirrors/lib/src/discovery/package_analyzer.dart
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
20
packages/mirrors/lib/src/discovery/proxy_type.dart
Normal file
20
packages/mirrors/lib/src/discovery/proxy_type.dart
Normal 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;
|
||||||
|
}
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
279
packages/mirrors/test/discovery/package_analyzer_test.dart
Normal file
279
packages/mirrors/test/discovery/package_analyzer_test.dart
Normal 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
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
Loading…
Reference in a new issue