refactor: refactoring platform mirrors 55 pass 7 fail

This commit is contained in:
Patrick Stewart 2024-12-26 04:02:32 -07:00
parent dacf949af4
commit 0453c67177
19 changed files with 2258 additions and 178 deletions

View file

@ -13,11 +13,11 @@ export 'src/annotations/reflectable.dart';
export 'src/core/mirror_system.dart';
/// Discovery
export 'src/discovery/type_analyzer.dart';
export 'src/discovery/library_analyzer.dart';
export 'src/discovery/analyzers/library_analyzer.dart';
export 'src/discovery/analyzers/type_analyzer.dart';
export 'src/discovery/analyzers/package_analyzer.dart';
export 'src/discovery/runtime_library_discoverer.dart';
export 'src/discovery/runtime_type_discoverer.dart';
export 'src/discovery/package_analyzer.dart';
/// Discovery Models
export 'src/discovery/models/models.dart';
@ -29,7 +29,9 @@ export 'src/exceptions/not_reflectable_exception.dart';
export 'src/exceptions/reflection_exception.dart';
/// Metadata
export 'src/metadata/class_metadata.dart';
export 'src/metadata/constructor_metadata.dart';
export 'src/metadata/extended_method_metadata.dart';
export 'src/metadata/function_metadata.dart';
export 'src/metadata/method_metadata.dart';
export 'src/metadata/parameter_metadata.dart';

View file

@ -1,13 +1,13 @@
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';
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 '../../types/proxy_type.dart';
/// Discovers and analyzes types in a package automatically.
class PackageAnalyzer {
@ -172,6 +172,33 @@ class PackageAnalyzer {
) {
final type = _getTypeForName(className);
// Register constructors first
for (final constructor in constructors) {
// Get parameter names for named parameters
final parameterNames = constructor.parameters
.where((p) => p.isNamed)
.map((p) => p.name)
.toList();
// Get required flags for named parameters
final isRequired = constructor.parameters
.where((p) => p.isNamed)
.map((p) => p.isRequired)
.toList();
// Get isNamed flags for all parameters
final isNamed = constructor.parameters.map((p) => p.isNamed).toList();
ReflectionRegistry.registerConstructor(
type,
constructor.name,
parameterTypes: constructor.parameterTypes,
parameterNames: parameterNames.isNotEmpty ? parameterNames : null,
isRequired: isRequired.isNotEmpty ? isRequired : null,
isNamed: isNamed.isNotEmpty ? isNamed : null,
);
}
// Register properties
properties.forEach((name, metadata) {
ReflectionRegistry.registerProperty(
@ -212,23 +239,7 @@ class PackageAnalyzer {
);
});
// 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
// Create and register type metadata last
final metadata = TypeMetadata(
type: type,
name: className,
@ -259,7 +270,6 @@ class PackageAnalyzer {
const [],
);
// Register metadata with reflection system
ReflectionRegistry.registerTypeMetadata(type, metadata);
}
@ -513,142 +523,156 @@ class PackageAnalyzer {
print('Found ${matches.length} constructor matches:');
for (final match in matches) {
final fullMatch = classContent.substring(match.start, match.end);
print('Full match: $fullMatch');
final matchContent = classContent.substring(match.start, match.end);
print('Full match: $matchContent');
print('Group 1 (name): ${match.group(1)}');
print('Group 2 (params): ${match.group(2)}');
// Get the line containing this match
final lineStart = classContent.lastIndexOf('\n', match.start) + 1;
final lineEnd = classContent.indexOf('\n', match.start);
final line = classContent.substring(
lineStart, lineEnd > 0 ? lineEnd : classContent.length);
// Skip if this is part of a static method or return statement
if (line.trim().startsWith('static') ||
line.trim().startsWith('return')) {
continue;
}
// Get the line before this match
final beforeLineStart =
classContent.lastIndexOf('\n', match.start - 1) + 1;
final beforeLineEnd = match.start - 1;
final beforeLine = beforeLineStart < beforeLineEnd
? classContent.substring(beforeLineStart, beforeLineEnd).trim()
: '';
final name = match.group(1) ?? '';
final params = match.group(2) ?? '';
final isFactory = line.trim().startsWith('factory');
// For factory constructors without a name, use 'create'
final constructorName = isFactory && name.isEmpty ? 'create' : name;
final isFactory = matchContent.trim().startsWith('factory');
final isStatic = beforeLine.startsWith('static');
// Parse parameters
final parameterTypes = _extractParameterTypes(params);
final parameters = _extractParameters(params);
// Add constructor
constructors.add(ConstructorMetadata(
name: constructorName,
parameterTypes: parameterTypes,
parameters: parameters,
));
// Add constructor with appropriate name
final constructorName = isFactory && name.isEmpty ? 'create' : name;
if (isStatic) {
// Handle static factory methods
if (matchContent.trim().contains('return $className')) {
// Add both the static factory method and its constructor call
constructors.add(ConstructorMetadata(
name: name,
parameterTypes: parameterTypes,
parameters: parameters,
));
// Extract the constructor call parameters
final callMatch =
RegExp('$className\\(([^)]*)\\)').firstMatch(matchContent);
if (callMatch != null) {
final callParams = callMatch.group(1) ?? '';
final callParamTypes = _extractParameterTypes(callParams);
final callParameters = _extractParameters(callParams);
constructors.add(ConstructorMetadata(
name: '',
parameterTypes: callParamTypes,
parameters: callParameters,
));
}
}
} else {
// Regular constructor
constructors.add(ConstructorMetadata(
name: constructorName,
parameterTypes: parameterTypes,
parameters: parameters,
));
// For factory constructors, also add a default constructor
if (isFactory && name.isNotEmpty) {
constructors.add(ConstructorMetadata(
name: '',
parameterTypes: parameterTypes,
parameters: parameters,
));
}
}
}
print('Returning ${constructors.length} constructors');
return constructors;
}
/// Extracts parameter types from a parameter list string.
static List<Type> _extractParameterTypes(String params) {
/// Extracts parameter types and metadata from a parameter list string.
static (List<Type>, List<ParameterMetadata>) _extractParameterInfo(
String params) {
final types = <Type>[];
final paramRegex = RegExp(
r'(?:required\s+)?(\w+(?:<[^>]+>)?)\s+(?:this\.)?(\w+)(?:\s*\?)?',
multiLine: true,
);
// Split parameters by commas, handling both positional and named parameters
final parts =
params.split(',').map((p) => p.trim()).where((p) => p.isNotEmpty);
for (final part in parts) {
if (part.startsWith('{') || part.endsWith('}')) {
// Handle named parameter section
final namedParams = part.replaceAll(RegExp(r'[{}]'), '').trim();
if (namedParams.isNotEmpty) {
final namedParts = namedParams
.split(',')
.map((p) => p.trim())
.where((p) => p.isNotEmpty);
for (final namedPart in namedParts) {
final match = paramRegex.firstMatch(namedPart);
if (match != null) {
types.add(_getTypeForName(match.group(1)!));
}
}
}
} else {
// Handle positional parameter
final match = paramRegex.firstMatch(part);
if (match != null) {
types.add(_getTypeForName(match.group(1)!));
}
}
}
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*([^,}]+))?',
multiLine: true,
);
// Split parameters by commas, handling both positional and named parameters
final parts =
params.split(',').map((p) => p.trim()).where((p) => p.isNotEmpty);
for (final part in parts) {
if (part.startsWith('{') || part.endsWith('}')) {
// Handle named parameter section
final namedParams = part.replaceAll(RegExp(r'[{}]'), '').trim();
if (namedParams.isNotEmpty) {
final namedParts = namedParams
.split(',')
.map((p) => p.trim())
.where((p) => p.isNotEmpty);
for (final namedPart in namedParts) {
final match = paramRegex.firstMatch(namedPart);
if (match != null) {
final type = match.group(1)!;
final name = match.group(2)!;
final defaultValue = match.group(3);
final isRequired = namedPart.contains('required');
// Find named parameter section
final namedStart = params.indexOf('{');
final namedEnd = params.lastIndexOf('}');
final hasNamedParams = namedStart != -1 && namedEnd != -1;
parameters.add(ParameterMetadata(
name: name,
type: _getTypeForName(type),
isRequired: isRequired,
isNamed: true,
defaultValue: defaultValue != null
? _parseDefaultValue(defaultValue)
: null,
));
}
}
}
} else {
// Handle positional parameter
final match = paramRegex.firstMatch(part);
// Handle positional parameters
final positionalPart =
hasNamedParams ? params.substring(0, namedStart).trim() : params.trim();
final positionalParams = positionalPart.split(',').map((p) => p.trim());
for (final param in positionalParams) {
if (param.isEmpty) continue;
final match = paramRegex.firstMatch(param);
if (match != null) {
final type = match.group(1)!;
final name = match.group(2)!;
final defaultValue = match.group(3);
types.add(_getTypeForName(type));
parameters.add(ParameterMetadata(
name: name,
type: _getTypeForName(type),
isRequired: true,
isNamed: false,
defaultValue:
defaultValue != null ? _parseDefaultValue(defaultValue) : null,
));
}
}
// Handle named parameters if present
if (hasNamedParams) {
final namedPart = params.substring(namedStart + 1, namedEnd).trim();
final namedParams = namedPart.split(',').map((p) => p.trim());
for (final param in namedParams) {
if (param.isEmpty) continue;
final match = paramRegex.firstMatch(param);
if (match != null) {
final type = match.group(1)!;
final name = match.group(2)!;
final defaultValue = match.group(3);
final isRequired = param.contains('required $type');
types.add(_getTypeForName(type));
parameters.add(ParameterMetadata(
name: name,
type: _getTypeForName(type),
isRequired: true,
isNamed: false,
isRequired: isRequired,
isNamed: true,
defaultValue:
defaultValue != null ? _parseDefaultValue(defaultValue) : null,
));
}
}
}
return (types, parameters);
}
/// Extracts parameter types from a parameter list string.
static List<Type> _extractParameterTypes(String params) {
final (types, _) = _extractParameterInfo(params);
return types;
}
/// Extracts parameters from a parameter list string.
static List<ParameterMetadata> _extractParameters(String params) {
final (_, parameters) = _extractParameterInfo(params);
return parameters;
}

View file

@ -0,0 +1,157 @@
import 'parser.dart';
/// Base class for all parsers that provides common functionality.
abstract class BaseParser implements Parser {
String _source = '';
int _position = 0;
int _line = 1;
int _column = 1;
final List<String> _errors = [];
final List<String> _warnings = [];
@override
int get position => _position;
@override
set position(int value) {
if (value < 0) value = 0;
if (value > _source.length) value = _source.length;
if (value < _position) {
// Moving backwards, recalculate line and column
var text = _source.substring(0, value);
_line = '\n'.allMatches(text).length + 1;
var lastNewline = text.lastIndexOf('\n');
_column = lastNewline == -1 ? value + 1 : value - lastNewline;
} else {
// Moving forwards, update line and column
var text = _source.substring(_position, value);
var newlines = '\n'.allMatches(text).length;
if (newlines > 0) {
_line += newlines;
var lastNewline = text.lastIndexOf('\n');
_column = text.length - lastNewline;
} else {
_column += text.length;
}
}
_position = value;
}
@override
int get line => _line;
@override
int get column => _column;
@override
List<String> get errors => List.unmodifiable(_errors);
@override
List<String> get warnings => List.unmodifiable(_warnings);
/// Initializes the parser with the given source code.
void init(String source) {
_source = source;
_position = 0;
_line = 1;
_column = 1;
_errors.clear();
_warnings.clear();
}
/// Gets the current character without advancing the position.
String? peek() {
if (_position >= _source.length) return null;
return _source[_position];
}
/// Gets the character at the given offset from current position without advancing.
String? peekAhead(int offset) {
final pos = _position + offset;
if (pos >= _source.length) return null;
return _source[pos];
}
/// Advances the position by one and returns the character.
String? advance() {
if (_position >= _source.length) return null;
final char = _source[_position];
position = _position + 1;
return char;
}
/// Returns true if the current position is at the end of the source.
bool isAtEnd() => _position >= _source.length;
/// Adds an error message with the current position information.
void addError(String message) {
_errors.add('$message at line $_line, column $_column');
}
/// Adds a warning message with the current position information.
void addWarning(String message) {
_warnings.add('$message at line $_line, column $_column');
}
/// Skips whitespace characters.
void skipWhitespace() {
while (!isAtEnd()) {
final char = peek();
if (char == ' ' || char == '\t' || char == '\r' || char == '\n') {
advance();
} else {
break;
}
}
}
/// Matches and consumes the given string if it exists at the current position.
/// Returns true if matched, false otherwise.
bool match(String text) {
if (_position + text.length > _source.length) return false;
if (_source.substring(_position, _position + text.length) == text) {
position = _position + text.length;
return true;
}
return false;
}
/// Looks ahead for the given string without consuming it.
/// Returns true if found, false otherwise.
bool lookAhead(String text) {
if (_position + text.length > _source.length) return false;
return _source.substring(_position, _position + text.length) == text;
}
/// Consumes characters until the given predicate returns false.
String consumeWhile(bool Function(String) predicate) {
final buffer = StringBuffer();
while (!isAtEnd()) {
final char = peek();
if (char == null || !predicate(char)) break;
buffer.write(advance());
}
return buffer.toString();
}
/// Consumes characters until the given string is found.
/// Returns the consumed characters not including the delimiter.
/// If includeDelimiter is true, advances past the delimiter.
String consumeUntil(String delimiter, {bool includeDelimiter = false}) {
final buffer = StringBuffer();
while (!isAtEnd()) {
if (lookAhead(delimiter)) {
if (includeDelimiter) {
position = _position + delimiter.length;
}
break;
}
buffer.write(advance());
}
return buffer.toString();
}
}

View file

@ -0,0 +1,325 @@
import '../../metadata/constructor_metadata.dart';
import '../../metadata/method_metadata.dart';
import '../../metadata/property_metadata.dart';
import 'base_parser.dart';
import 'parser.dart';
import '../../metadata/class_metadata.dart';
import 'property_parser.dart';
import 'method_parser.dart';
import 'constructor_parser.dart';
import 'type_parser.dart';
/// Parser for Dart class declarations.
class ClassParser extends BaseParser {
// Specialized parsers for class members
late final PropertyParser _propertyParser;
late final MethodParser _methodParser;
late final ConstructorParser _constructorParser;
late final TypeParser _typeParser;
@override
bool canParse(String source) {
init(source);
skipWhitespace();
// Check for class modifiers
if (lookAhead('abstract') || lookAhead('final')) {
return true;
}
// Check for class keyword
return lookAhead('class');
}
@override
ParseResult<ClassMetadata> parse(String source) {
init(source);
try {
// Parse class declaration
final metadata = _parseClassDeclaration();
if (metadata == null) {
return ParseResult.failure(['Failed to parse class declaration']);
}
// Initialize member parsers with class name
_propertyParser = PropertyParser();
_methodParser = MethodParser();
_constructorParser = ConstructorParser(metadata.name);
_typeParser = TypeParser();
// Parse class body
final body = _parseClassBody();
if (body == null) {
return ParseResult.failure(['Failed to parse class body']);
}
// Update metadata with parsed body
return ParseResult.success(ClassMetadata(
name: metadata.name,
typeParameters: metadata.typeParameters,
superclass: metadata.superclass,
interfaces: metadata.interfaces,
isAbstract: metadata.isAbstract,
isFinal: metadata.isFinal,
properties: body['properties'] as Map<String, PropertyMetadata>,
methods: body['methods'] as Map<String, MethodMetadata>,
constructors: body['constructors'] as List<ConstructorMetadata>,
));
} catch (e) {
return ParseResult.failure(['Error parsing class: $e']);
}
}
/// Parses the class declaration including modifiers, name, type parameters,
/// superclass, and interfaces.
ClassMetadata? _parseClassDeclaration() {
skipWhitespace();
// Parse modifiers
bool isAbstract = false;
bool isFinal = false;
if (match('abstract')) {
isAbstract = true;
skipWhitespace();
} else if (match('final')) {
isFinal = true;
skipWhitespace();
}
// Parse 'class' keyword
if (!match('class')) {
addError("Expected 'class' keyword");
return null;
}
skipWhitespace();
// Parse class name
final name = _parseIdentifier();
if (name == null) {
addError('Expected class name');
return null;
}
// Parse type parameters if present
final typeParameters = _parseTypeParameters();
skipWhitespace();
// Parse superclass if present
String? superclass;
if (match('extends')) {
skipWhitespace();
final type = _typeParser
.parse(consumeUntil('implements', includeDelimiter: false).trim());
if (type.isSuccess) {
superclass = type.result!.fullName;
}
}
skipWhitespace();
// Parse interfaces if present
final interfaces = <String>[];
if (match('implements')) {
skipWhitespace();
while (!isAtEnd() && !lookAhead('{')) {
final type = _typeParser
.parse(consumeUntil(',', includeDelimiter: false).trim());
if (type.isSuccess) {
interfaces.add(type.result!.fullName);
}
skipWhitespace();
if (!match(',')) break;
skipWhitespace();
}
}
return ClassMetadata(
name: name,
typeParameters: typeParameters,
superclass: superclass,
interfaces: interfaces,
isAbstract: isAbstract,
isFinal: isFinal,
);
}
/// Parses the class body including properties, methods, and constructors.
Map<String, dynamic>? _parseClassBody() {
skipWhitespace();
if (!match('{')) {
addError("Expected '{' to begin class body");
return null;
}
final properties = <String, PropertyMetadata>{};
final methods = <String, MethodMetadata>{};
final constructors = <ConstructorMetadata>[];
// Parse class members until we reach the closing brace
while (!isAtEnd() && !lookAhead('}')) {
skipWhitespace();
// Try to parse as constructor first (since constructors start with the class name)
if (_constructorParser.canParse(peekLine())) {
final result = _constructorParser.parse(consumeMember());
if (result.isSuccess) {
constructors.add(result.result!);
continue;
}
}
// Try to parse as property
if (_propertyParser.canParse(peekLine())) {
final result = _propertyParser.parse(consumeMember());
if (result.isSuccess) {
properties[result.result!.name] = result.result!;
continue;
}
}
// Try to parse as method
if (_methodParser.canParse(peekLine())) {
final result = _methodParser.parse(consumeMember());
if (result.isSuccess) {
methods[result.result!.name] = result.result!;
continue;
}
}
// If we get here, we couldn't parse the member
addError('Invalid class member');
return null;
}
if (!match('}')) {
addError("Expected '}' to end class body");
return null;
}
return {
'properties': properties,
'methods': methods,
'constructors': constructors,
};
}
/// Peeks at the current line without consuming it.
String peekLine() {
final start = position;
final line = consumeUntil(';', includeDelimiter: false);
position = start;
return line;
}
/// Consumes a complete class member (property, method, or constructor).
String consumeMember() {
final buffer = StringBuffer();
var braceCount = 0;
var inString = false;
var stringChar = '';
while (!isAtEnd()) {
final char = peek();
if (char == null) break;
if (!inString) {
if (char == '{') braceCount++;
if (char == '}') braceCount--;
if (char == '"' || char == "'") {
inString = true;
stringChar = char;
}
if (char == ';' && braceCount == 0) {
buffer.write(advance());
break;
}
if (braceCount < 0) break; // End of class body
} else if (char == stringChar && peekAhead(-1) != '\\') {
inString = false;
}
buffer.write(advance());
}
return buffer.toString();
}
/// Parses type parameters (e.g., <T> or <T extends Base>).
List<String> _parseTypeParameters() {
if (!match('<')) return [];
final params = <String>[];
while (!isAtEnd() && !lookAhead('>')) {
skipWhitespace();
final param = _parseTypeParameter();
if (param == null) {
addError('Invalid type parameter');
return [];
}
params.add(param);
skipWhitespace();
if (!match(',')) break;
}
if (!match('>')) {
addError("Expected '>' to close type parameters");
return [];
}
return params;
}
/// Parses a single type parameter, including any bounds.
String? _parseTypeParameter() {
final name = _parseIdentifier();
if (name == null) return null;
skipWhitespace();
// Parse type bounds if present
if (match('extends')) {
skipWhitespace();
final type =
_typeParser.parse(consumeUntil(',', includeDelimiter: false).trim());
if (type.isSuccess) {
return '$name extends ${type.result!.fullName}';
}
return null;
}
return name;
}
/// Parses an identifier (e.g., class name, method name).
String? _parseIdentifier() {
skipWhitespace();
if (isAtEnd()) return null;
final char = peek();
if (char == null || !_isIdentifierStart(char)) return null;
return consumeWhile(_isIdentifierPart);
}
/// Returns true if the character can start an identifier.
bool _isIdentifierStart(String char) {
return char == '_' || char.toLowerCase() != char.toUpperCase();
}
/// Returns true if the character can be part of an identifier.
bool _isIdentifierPart(String char) {
return _isIdentifierStart(char) ||
char.codeUnitAt(0) >= 48 && char.codeUnitAt(0) <= 57;
}
}

View file

@ -0,0 +1,364 @@
import '../../metadata/constructor_metadata.dart';
import '../../metadata/parameter_metadata.dart';
import 'parser.dart';
import 'base_parser.dart';
/// Parser for class constructors.
class ConstructorParser extends BaseParser {
final String className;
ConstructorParser(this.className);
@override
bool canParse(String source) {
init(source);
skipWhitespace();
// Check for constructor modifiers
if (lookAhead('const') || lookAhead('factory')) {
return true;
}
// Check for constructor name
return lookAhead(className);
}
@override
ParseResult<ConstructorMetadata> parse(String source) {
init(source);
try {
// Parse constructor declaration
final metadata = _parseConstructorDeclaration();
if (metadata == null) {
return ParseResult.failure(['Failed to parse constructor declaration']);
}
return ParseResult.success(metadata);
} catch (e) {
return ParseResult.failure(['Error parsing constructor: $e']);
}
}
/// Parses a constructor declaration.
ConstructorMetadata? _parseConstructorDeclaration() {
skipWhitespace();
// Parse modifiers
bool isConst = false;
bool isFactory = false;
while (true) {
if (match('const')) {
if (isConst) {
addError("Duplicate 'const' modifier");
return null;
}
isConst = true;
} else if (match('factory')) {
if (isFactory) {
addError("Duplicate 'factory' modifier");
return null;
}
isFactory = true;
} else {
break;
}
skipWhitespace();
}
// Parse constructor name
if (!match(className)) {
addError('Expected constructor name');
return null;
}
// Parse named constructor if present
String constructorName = '';
if (match('.')) {
final name = _parseIdentifier();
if (name == null) {
addError('Expected named constructor identifier');
return null;
}
constructorName = name;
}
skipWhitespace();
// Parse parameters
if (!match('(')) {
addError("Expected '(' after constructor name");
return null;
}
final parameters = <ParameterMetadata>[];
final parameterTypes = <Type>[];
// Parse parameter list
if (!match(')')) {
while (true) {
skipWhitespace();
final param = _parseParameter();
if (param == null) {
addError('Invalid parameter');
return null;
}
parameters.add(param);
parameterTypes.add(param.type);
skipWhitespace();
if (match(')')) break;
if (!match(',')) {
addError("Expected ',' or ')' after parameter");
return null;
}
}
}
skipWhitespace();
// Parse initializer list if present
if (match(':')) {
skipWhitespace();
while (!isAtEnd() && !lookAhead('{') && !lookAhead('=>')) {
// Skip initializer
consumeUntil(',', includeDelimiter: true);
skipWhitespace();
}
}
// Parse constructor body unless redirecting
if (!match('=>')) {
if (!match('{')) {
addError("Expected '{' to begin constructor body");
return null;
}
// Skip constructor body
var braceCount = 1;
while (!isAtEnd() && braceCount > 0) {
if (peek() == '{') braceCount++;
if (peek() == '}') braceCount--;
advance();
}
} else {
// Skip redirecting constructor call
consumeUntil(';', includeDelimiter: true);
}
return ConstructorMetadata(
name: constructorName,
parameterTypes: parameterTypes,
parameters: parameters,
);
}
/// Parses a parameter declaration.
ParameterMetadata? _parseParameter() {
bool isRequired = false;
bool isNamed = false;
// Check if we're in a named parameter group
if (lookAhead('{')) {
isNamed = true;
match('{');
skipWhitespace();
}
// Check for required modifier
if (match('required')) {
isRequired = true;
skipWhitespace();
}
// Parse type
final typeStr = _parseType();
if (typeStr == null) {
addError('Expected parameter type');
return null;
}
skipWhitespace();
// Check for 'this.' prefix
bool isFieldFormal = match('this.');
// Parse name
final name = _parseIdentifier();
if (name == null) {
addError('Expected parameter name');
return null;
}
skipWhitespace();
// Check for nullable type
bool isNullable = match('?');
if (isNullable) skipWhitespace();
// Parse default value if present
String? defaultValue;
if (match('=')) {
skipWhitespace();
defaultValue = _parseDefaultValue();
}
// Check for end of named parameter group
if (isNamed && match('}')) {
skipWhitespace();
}
return ParameterMetadata(
name: name,
type: _getTypeForName(typeStr),
isRequired: isRequired || !isNamed,
isNamed: isNamed,
isNullable: isNullable,
defaultValue:
defaultValue != null ? _parseDefaultValueLiteral(defaultValue) : null,
);
}
/// Parses a type name, which could include generics.
String? _parseType() {
final identifier = _parseIdentifier();
if (identifier == null) return null;
skipWhitespace();
// Parse type arguments if present
if (match('<')) {
final args = <String>[];
while (!isAtEnd() && !lookAhead('>')) {
skipWhitespace();
final type = _parseType();
if (type == null) {
addError('Invalid type argument');
return null;
}
args.add(type);
skipWhitespace();
if (!match(',')) break;
}
if (!match('>')) {
addError("Expected '>' to close type arguments");
return null;
}
return '$identifier<${args.join(', ')}>';
}
return identifier;
}
/// Parses a default value expression.
String? _parseDefaultValue() {
final buffer = StringBuffer();
var bracketCount = 0;
var inString = false;
var stringChar = '';
while (!isAtEnd()) {
final char = peek();
if (char == null) break;
if (!inString) {
if ((char == ',' || char == '}' || char == ')') && bracketCount == 0)
break;
if (char == '{' || char == '[' || char == '(') bracketCount++;
if (char == '}' || char == ']' || char == ')') bracketCount--;
if (char == '"' || char == "'") {
inString = true;
stringChar = char;
}
} else if (char == stringChar && peekAhead(-1) != '\\') {
inString = false;
}
buffer.write(advance());
}
final result = buffer.toString().trim();
return result.isEmpty ? null : result;
}
/// Parses a default value literal into its actual value.
dynamic _parseDefaultValueLiteral(String value) {
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;
}
/// Parses an identifier (e.g., type name, parameter name).
String? _parseIdentifier() {
skipWhitespace();
if (isAtEnd()) return null;
final char = peek();
if (char == null || !_isIdentifierStart(char)) return null;
return consumeWhile(_isIdentifierPart);
}
/// Returns true if the character can start an identifier.
bool _isIdentifierStart(String char) {
return char == '_' || char.toLowerCase() != char.toUpperCase();
}
/// Returns true if the character can be part of an identifier.
bool _isIdentifierPart(String char) {
return _isIdentifierStart(char) ||
char.codeUnitAt(0) >= 48 && char.codeUnitAt(0) <= 57;
}
/// Gets or creates a Type instance for a type name.
Type _getTypeForName(String name) {
// Remove any generic type parameters and whitespace
name = name.split('<')[0].trim();
// 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
return Object; // TODO: Handle custom types properly
}
}
}

View file

@ -0,0 +1,365 @@
import '../../metadata/parameter_metadata.dart';
import 'parser.dart';
import 'base_parser.dart';
import '../../metadata/extended_method_metadata.dart';
/// Parser for class methods.
class MethodParser extends BaseParser {
@override
bool canParse(String source) {
init(source);
skipWhitespace();
// Check for method modifiers
if (lookAhead('static') || lookAhead('async') || lookAhead('external')) {
return true;
}
// Check for return type or method name
return _isIdentifierStart(peek() ?? '');
}
@override
ParseResult<ExtendedMethodMetadata> parse(String source) {
init(source);
try {
// Parse method declaration
final metadata = _parseMethodDeclaration();
if (metadata == null) {
return ParseResult.failure(['Failed to parse method declaration']);
}
return ParseResult.success(metadata);
} catch (e) {
return ParseResult.failure(['Error parsing method: $e']);
}
}
/// Parses a method declaration.
ExtendedMethodMetadata? _parseMethodDeclaration() {
skipWhitespace();
// Parse modifiers
bool isStatic = false;
bool isAsync = false;
bool isExternal = false;
bool isGenerator = false;
while (true) {
if (match('static')) {
if (isStatic) {
addError("Duplicate 'static' modifier");
return null;
}
isStatic = true;
} else if (match('async')) {
if (isAsync) {
addError("Duplicate 'async' modifier");
return null;
}
isAsync = true;
} else if (match('external')) {
if (isExternal) {
addError("Duplicate 'external' modifier");
return null;
}
isExternal = true;
} else {
break;
}
skipWhitespace();
}
// Parse return type
final returnTypeStr = _parseType();
if (returnTypeStr == null) {
addError('Expected return type');
return null;
}
skipWhitespace();
// Parse method name
final name = _parseIdentifier();
if (name == null) {
addError('Expected method name');
return null;
}
skipWhitespace();
// Parse parameters
if (!match('(')) {
addError("Expected '(' after method name");
return null;
}
final parameters = <ParameterMetadata>[];
final parameterTypes = <Type>[];
// Parse parameter list
if (!match(')')) {
while (true) {
skipWhitespace();
final param = _parseParameter();
if (param == null) {
addError('Invalid parameter');
return null;
}
parameters.add(param);
parameterTypes.add(param.type);
skipWhitespace();
if (match(')')) break;
if (!match(',')) {
addError("Expected ',' or ')' after parameter");
return null;
}
}
}
skipWhitespace();
// Check for generator modifier
if (match('sync*')) {
isGenerator = true;
} else if (match('async*')) {
isGenerator = true;
isAsync = true;
}
// Parse method body unless external
if (!isExternal) {
if (!match('=>') && !match('{')) {
addError("Expected '=>' or '{' after parameter list");
return null;
}
// Skip method body
if (peek() == '{') {
var braceCount = 1;
advance(); // Skip opening brace
while (!isAtEnd() && braceCount > 0) {
if (peek() == '{') braceCount++;
if (peek() == '}') braceCount--;
advance();
}
} else {
// Skip arrow and expression until semicolon
consumeUntil(';', includeDelimiter: true);
}
} else {
// External methods must end with semicolon
if (!match(';')) {
addError("Expected ';' after external method declaration");
return null;
}
}
return ExtendedMethodMetadata(
name: name,
parameterTypes: parameterTypes,
parameters: parameters,
returnsVoid: returnTypeStr == 'void',
returnType: _getTypeForName(returnTypeStr),
isStatic: isStatic,
isAsync: isAsync,
isGenerator: isGenerator,
isExternal: isExternal,
);
}
/// Parses a parameter declaration.
ParameterMetadata? _parseParameter() {
bool isRequired = false;
bool isNamed = false;
// Check for required modifier
if (match('required')) {
isRequired = true;
skipWhitespace();
}
// Parse type
final typeStr = _parseType();
if (typeStr == null) {
addError('Expected parameter type');
return null;
}
skipWhitespace();
// Parse name
final name = _parseIdentifier();
if (name == null) {
addError('Expected parameter name');
return null;
}
skipWhitespace();
// Check for nullable type
bool isNullable = match('?');
if (isNullable) skipWhitespace();
// Parse default value if present
String? defaultValue;
if (match('=')) {
skipWhitespace();
defaultValue = _parseDefaultValue();
}
return ParameterMetadata(
name: name,
type: _getTypeForName(typeStr),
isRequired: isRequired,
isNamed: isNamed,
defaultValue:
defaultValue != null ? _parseDefaultValueLiteral(defaultValue) : null,
);
}
/// Parses a type name, which could include generics.
String? _parseType() {
final identifier = _parseIdentifier();
if (identifier == null) return null;
skipWhitespace();
// Parse type arguments if present
if (match('<')) {
final args = <String>[];
while (!isAtEnd() && !lookAhead('>')) {
skipWhitespace();
final type = _parseType();
if (type == null) {
addError('Invalid type argument');
return null;
}
args.add(type);
skipWhitespace();
if (!match(',')) break;
}
if (!match('>')) {
addError("Expected '>' to close type arguments");
return null;
}
return '$identifier<${args.join(', ')}>';
}
return identifier;
}
/// Parses a default value expression.
String? _parseDefaultValue() {
final buffer = StringBuffer();
var bracketCount = 0;
var inString = false;
var stringChar = '';
while (!isAtEnd()) {
final char = peek();
if (char == null) break;
if (!inString) {
if ((char == ',' || char == '}' || char == ')') && bracketCount == 0)
break;
if (char == '{' || char == '[' || char == '(') bracketCount++;
if (char == '}' || char == ']' || char == ')') bracketCount--;
if (char == '"' || char == "'") {
inString = true;
stringChar = char;
}
} else if (char == stringChar && peekAhead(-1) != '\\') {
inString = false;
}
buffer.write(advance());
}
final result = buffer.toString().trim();
return result.isEmpty ? null : result;
}
/// Parses a default value literal into its actual value.
dynamic _parseDefaultValueLiteral(String value) {
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;
}
/// Parses an identifier (e.g., type name, method name).
String? _parseIdentifier() {
skipWhitespace();
if (isAtEnd()) return null;
final char = peek();
if (char == null || !_isIdentifierStart(char)) return null;
return consumeWhile(_isIdentifierPart);
}
/// Returns true if the character can start an identifier.
bool _isIdentifierStart(String char) {
return char == '_' || char.toLowerCase() != char.toUpperCase();
}
/// Returns true if the character can be part of an identifier.
bool _isIdentifierPart(String char) {
return _isIdentifierStart(char) ||
char.codeUnitAt(0) >= 48 && char.codeUnitAt(0) <= 57;
}
/// Gets or creates a Type instance for a type name.
Type _getTypeForName(String name) {
// Remove any generic type parameters and whitespace
name = name.split('<')[0].trim();
// 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
return Object; // TODO: Handle custom types properly
}
}
}

View file

@ -0,0 +1,62 @@
/// Base interface for all parsers in the reflection system.
abstract class Parser {
/// Parses the given source code and returns the result.
///
/// The return type varies based on the specific parser implementation.
dynamic parse(String source);
/// Validates if the given source code can be parsed by this parser.
///
/// Returns true if the source is valid for this parser, false otherwise.
bool canParse(String source);
/// Gets the current position in the source code.
int get position;
/// Sets the current position in the source code.
set position(int value);
/// Gets the current line number being parsed.
int get line;
/// Gets the current column number being parsed.
int get column;
/// Gets any error messages from the parsing process.
List<String> get errors;
/// Gets any warning messages from the parsing process.
List<String> get warnings;
}
/// Result of a parsing operation.
class ParseResult<T> {
/// The parsed result.
final T? result;
/// Any errors that occurred during parsing.
final List<String> errors;
/// Any warnings that occurred during parsing.
final List<String> warnings;
/// Whether the parsing was successful.
bool get isSuccess => errors.isEmpty && result != null;
ParseResult({
this.result,
this.errors = const [],
this.warnings = const [],
});
/// Creates a successful parse result.
factory ParseResult.success(T result, {List<String> warnings = const []}) {
return ParseResult(result: result, warnings: warnings);
}
/// Creates a failed parse result.
factory ParseResult.failure(List<String> errors,
{List<String> warnings = const []}) {
return ParseResult(errors: errors, warnings: warnings);
}
}

View file

@ -0,0 +1,305 @@
import '../../metadata/property_metadata.dart';
import 'parser.dart';
import 'base_parser.dart';
/// Parser for class properties, including fields and getters/setters.
class PropertyParser extends BaseParser {
@override
bool canParse(String source) {
init(source);
skipWhitespace();
// Check for property modifiers
if (lookAhead('static') || lookAhead('final') || lookAhead('const')) {
return true;
}
// Check for getter/setter
if (lookAhead('get') || lookAhead('set')) {
return true;
}
// Check for type declaration
return _isIdentifierStart(peek() ?? '');
}
@override
ParseResult<PropertyMetadata> parse(String source) {
init(source);
try {
// Parse property declaration
final metadata = _parsePropertyDeclaration();
if (metadata == null) {
return ParseResult.failure(['Failed to parse property declaration']);
}
return ParseResult.success(metadata);
} catch (e) {
return ParseResult.failure(['Error parsing property: $e']);
}
}
/// Parses a property declaration.
PropertyMetadata? _parsePropertyDeclaration() {
skipWhitespace();
// Parse modifiers
bool isStatic = false;
bool isFinal = false;
bool isConst = false;
bool isLate = false;
while (true) {
if (match('static')) {
if (isStatic) {
addError("Duplicate 'static' modifier");
return null;
}
isStatic = true;
} else if (match('final')) {
if (isFinal || isConst) {
addError("Cannot have both 'final' and 'const'");
return null;
}
isFinal = true;
} else if (match('const')) {
if (isConst || isFinal) {
addError("Cannot have both 'const' and 'final'");
return null;
}
isConst = true;
} else if (match('late')) {
if (isLate) {
addError("Duplicate 'late' modifier");
return null;
}
isLate = true;
} else {
break;
}
skipWhitespace();
}
// Check for getter/setter
bool isGetter = false;
bool isSetter = false;
if (match('get')) {
isGetter = true;
skipWhitespace();
return _parseAccessor(true);
} else if (match('set')) {
isSetter = true;
skipWhitespace();
return _parseAccessor(false);
}
// Parse type
final typeStr = _parseType();
if (typeStr == null) {
addError('Expected type name');
return null;
}
skipWhitespace();
// Parse name
final name = _parseIdentifier();
if (name == null) {
addError('Expected property name');
return null;
}
skipWhitespace();
// Check for nullable type
bool isNullable = match('?');
if (isNullable) skipWhitespace();
// Parse initializer if present
String? initializer;
if (match('=')) {
skipWhitespace();
initializer = _parseInitializer();
}
// Expect semicolon
if (!match(';')) {
addError("Expected ';' after property declaration");
return null;
}
return PropertyMetadata(
name: name,
type: _getTypeForName(typeStr),
isReadable: true,
isWritable: !isFinal && !isConst,
);
}
/// Parses a getter or setter declaration.
PropertyMetadata? _parseAccessor(bool isGetter) {
final name = _parseIdentifier();
if (name == null) {
addError('Expected accessor name');
return null;
}
skipWhitespace();
// Parse getter/setter body
if (!match('=>') && !match('{')) {
addError("Expected '=>' or '{' after accessor name");
return null;
}
// Skip body until we find the end
if (peek() == '{') {
var braceCount = 1;
advance(); // Skip opening brace
while (!isAtEnd() && braceCount > 0) {
if (peek() == '{') braceCount++;
if (peek() == '}') braceCount--;
advance();
}
} else {
// Skip arrow and expression until semicolon
consumeUntil(';', includeDelimiter: true);
}
return PropertyMetadata(
name: name,
type: _getTypeForName(
'dynamic'), // Type will be inferred from getter return type
isReadable: isGetter,
isWritable: !isGetter,
);
}
/// Parses a type name, which could include generics.
String? _parseType() {
final identifier = _parseIdentifier();
if (identifier == null) return null;
skipWhitespace();
// Parse type arguments if present
if (match('<')) {
final args = <String>[];
while (!isAtEnd() && !lookAhead('>')) {
skipWhitespace();
final type = _parseType();
if (type == null) {
addError('Invalid type argument');
return null;
}
args.add(type);
skipWhitespace();
if (!match(',')) break;
}
if (!match('>')) {
addError("Expected '>' to close type arguments");
return null;
}
return '$identifier<${args.join(', ')}>';
}
return identifier;
}
/// Parses an initializer expression.
String? _parseInitializer() {
final buffer = StringBuffer();
var bracketCount = 0;
var inString = false;
var stringChar = '';
while (!isAtEnd()) {
final char = peek();
if (char == null) break;
if (!inString) {
if (char == ';' && bracketCount == 0) break;
if (char == '{' || char == '[' || char == '(') bracketCount++;
if (char == '}' || char == ']' || char == ')') bracketCount--;
if (char == '"' || char == "'") {
inString = true;
stringChar = char;
}
} else if (char == stringChar && peekAhead(-1) != '\\') {
inString = false;
}
buffer.write(advance());
}
final result = buffer.toString().trim();
return result.isEmpty ? null : result;
}
/// Parses an identifier (e.g., type name, property name).
String? _parseIdentifier() {
skipWhitespace();
if (isAtEnd()) return null;
final char = peek();
if (char == null || !_isIdentifierStart(char)) return null;
return consumeWhile(_isIdentifierPart);
}
/// Returns true if the character can start an identifier.
bool _isIdentifierStart(String char) {
return char == '_' || char.toLowerCase() != char.toUpperCase();
}
/// Returns true if the character can be part of an identifier.
bool _isIdentifierPart(String char) {
return _isIdentifierStart(char) ||
char.codeUnitAt(0) >= 48 && char.codeUnitAt(0) <= 57;
}
/// Gets or creates a Type instance for a type name.
Type _getTypeForName(String name) {
// Remove any generic type parameters and whitespace
name = name.split('<')[0].trim();
// 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
return Object; // TODO: Handle custom types properly
}
}
}

View file

@ -0,0 +1,175 @@
import 'parser.dart';
import 'base_parser.dart';
/// Represents a parsed type with its generic type arguments.
class ParsedType {
/// The base type name.
final String name;
/// The type arguments if this is a generic type.
final List<ParsedType> typeArguments;
/// Whether this type is nullable.
final bool isNullable;
/// The full type string including generics and nullability.
String get fullName {
final buffer = StringBuffer(name);
if (typeArguments.isNotEmpty) {
buffer.write('<');
buffer.write(typeArguments.map((t) => t.fullName).join(', '));
buffer.write('>');
}
if (isNullable) buffer.write('?');
return buffer.toString();
}
ParsedType({
required this.name,
this.typeArguments = const [],
this.isNullable = false,
});
}
/// Parser for Dart type declarations.
class TypeParser extends BaseParser {
@override
bool canParse(String source) {
init(source);
skipWhitespace();
return _isIdentifierStart(peek() ?? '');
}
@override
ParseResult<ParsedType> parse(String source) {
init(source);
try {
// Parse type declaration
final type = _parseType();
if (type == null) {
return ParseResult.failure(['Failed to parse type declaration']);
}
return ParseResult.success(type);
} catch (e) {
return ParseResult.failure(['Error parsing type: $e']);
}
}
/// Parses a type declaration.
ParsedType? _parseType() {
skipWhitespace();
// Parse base type name
final name = _parseIdentifier();
if (name == null) {
addError('Expected type name');
return null;
}
skipWhitespace();
// Parse type arguments if present
final typeArguments = <ParsedType>[];
if (match('<')) {
while (!isAtEnd() && !lookAhead('>')) {
skipWhitespace();
final typeArg = _parseType();
if (typeArg == null) {
addError('Invalid type argument');
return null;
}
typeArguments.add(typeArg);
skipWhitespace();
if (!match(',')) break;
}
if (!match('>')) {
addError("Expected '>' to close type arguments");
return null;
}
}
skipWhitespace();
// Check for nullable type
final isNullable = match('?');
return ParsedType(
name: name,
typeArguments: typeArguments,
isNullable: isNullable,
);
}
/// Parses an identifier (e.g., type name).
String? _parseIdentifier() {
skipWhitespace();
if (isAtEnd()) return null;
final char = peek();
if (char == null || !_isIdentifierStart(char)) return null;
return consumeWhile(_isIdentifierPart);
}
/// Returns true if the character can start an identifier.
bool _isIdentifierStart(String char) {
return char == '_' || char.toLowerCase() != char.toUpperCase();
}
/// Returns true if the character can be part of an identifier.
bool _isIdentifierPart(String char) {
return _isIdentifierStart(char) ||
char.codeUnitAt(0) >= 48 && char.codeUnitAt(0) <= 57;
}
/// Gets the Type instance for a ParsedType.
Type getTypeForParsedType(ParsedType parsedType) {
// Remove any generic type parameters and whitespace
final name = parsedType.name.trim();
// 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
return Object; // TODO: Handle custom types properly
}
}
/// Parses a type string and returns its Type instance.
Type parseAndGetType(String typeStr) {
final result = parse(typeStr);
if (!result.isSuccess) {
return Object; // Return Object as fallback for invalid types
}
return getTypeForParsedType(result.result!);
}
}

View file

@ -0,0 +1,45 @@
import 'constructor_metadata.dart';
import 'method_metadata.dart';
import 'property_metadata.dart';
/// Metadata about a parsed class.
class ClassMetadata {
/// The name of the class.
final String name;
/// The type parameters if this is a generic class (e.g., <T, U>).
final List<String> typeParameters;
/// The superclass this class extends, if any.
final String? superclass;
/// The interfaces this class implements.
final List<String> interfaces;
/// Whether this is an abstract class.
final bool isAbstract;
/// Whether this is a final class.
final bool isFinal;
/// The properties defined in this class.
final Map<String, PropertyMetadata> properties;
/// The methods defined in this class.
final Map<String, MethodMetadata> methods;
/// The constructors defined in this class.
final List<ConstructorMetadata> constructors;
ClassMetadata({
required this.name,
this.typeParameters = const [],
this.superclass,
this.interfaces = const [],
this.isAbstract = false,
this.isFinal = false,
this.properties = const {},
this.methods = const {},
this.constructors = const [],
});
}

View file

@ -0,0 +1,36 @@
import 'parameter_metadata.dart';
import 'method_metadata.dart';
/// Extended metadata for methods that includes additional Dart-specific features.
class ExtendedMethodMetadata extends MethodMetadata {
/// Whether this is an async method.
@override
final bool isAsync;
/// Whether this is a generator method (sync* or async*).
@override
final bool isGenerator;
/// Whether this is an external method.
@override
final bool isExternal;
ExtendedMethodMetadata({
required String name,
required List<Type> parameterTypes,
required List<ParameterMetadata> parameters,
required bool returnsVoid,
required Type returnType,
bool isStatic = false,
this.isAsync = false,
this.isGenerator = false,
this.isExternal = false,
}) : super(
name: name,
parameterTypes: parameterTypes,
parameters: parameters,
returnsVoid: returnsVoid,
returnType: returnType,
isStatic: isStatic,
);
}

View file

@ -1,54 +1,43 @@
import 'package:platform_mirrors/mirrors.dart';
import 'parameter_metadata.dart';
/// Represents metadata about a type's method.
/// Metadata about a class method.
class MethodMetadata {
/// The name of the method.
final String name;
/// The parameter types of the method in order.
/// The parameter types of the method.
final List<Type> parameterTypes;
/// Detailed metadata about each parameter.
/// The parameters of the method.
final List<ParameterMetadata> parameters;
/// Whether the method is static.
final bool isStatic;
/// Whether the method returns void.
final bool returnsVoid;
/// The return type of the method.
final Type returnType;
/// Any attributes (annotations) on this method.
final List<Object> attributes;
/// Whether the method is static.
final bool isStatic;
/// Type parameters for generic methods.
final List<TypeParameterMetadata> typeParameters;
/// Whether the method is async.
final bool isAsync;
/// Creates a new method metadata instance.
const MethodMetadata({
/// Whether the method is a generator (sync* or async*).
final bool isGenerator;
/// Whether the method is external.
final bool isExternal;
MethodMetadata({
required this.name,
required this.parameterTypes,
required this.parameters,
required this.returnsVoid,
required this.returnType,
this.isStatic = false,
this.attributes = const [],
this.typeParameters = const [],
this.isAsync = false,
this.isGenerator = false,
this.isExternal = false,
});
/// Validates the given arguments against this method's parameter types.
bool validateArguments(List<Object?> arguments) {
if (arguments.length != parameterTypes.length) return false;
for (var i = 0; i < arguments.length; i++) {
final arg = arguments[i];
if (arg != null && arg.runtimeType != parameterTypes[i]) {
return false;
}
}
return true;
}
}

View file

@ -1,4 +1,4 @@
/// Represents metadata about a parameter.
/// Metadata about a method or constructor parameter.
class ParameterMetadata {
/// The name of the parameter.
final String name;
@ -6,25 +6,24 @@ class ParameterMetadata {
/// The type of the parameter.
final Type type;
/// Whether this parameter is required.
/// Whether the parameter is required.
final bool isRequired;
/// Whether this parameter is named.
/// Whether the parameter is named.
final bool isNamed;
/// The default value for this parameter, if any.
final Object? defaultValue;
/// Whether the parameter is nullable.
final bool isNullable;
/// Any attributes (annotations) on this parameter.
final List<Object> attributes;
/// The default value of the parameter, if any.
final dynamic defaultValue;
/// Creates a new parameter metadata instance.
const ParameterMetadata({
ParameterMetadata({
required this.name,
required this.type,
required this.isRequired,
this.isRequired = true,
this.isNamed = false,
this.isNullable = false,
this.defaultValue,
this.attributes = const [],
});
}

View file

@ -1,4 +1,4 @@
/// Represents metadata about a type's property.
/// Metadata about a class property.
class PropertyMetadata {
/// The name of the property.
final String name;
@ -12,15 +12,30 @@ class PropertyMetadata {
/// Whether the property can be written to.
final bool isWritable;
/// Whether the property is static.
final bool isStatic;
/// Whether the property is late.
final bool isLate;
/// Whether the property is nullable.
final bool isNullable;
/// Whether the property has an initializer.
final bool hasInitializer;
/// Any attributes (annotations) on this property.
final List<Object> attributes;
/// Creates a new property metadata instance.
const PropertyMetadata({
PropertyMetadata({
required this.name,
required this.type,
this.isReadable = true,
this.isWritable = true,
this.isStatic = false,
this.isLate = false,
this.isNullable = false,
this.attributes = const [],
this.hasInitializer = false,
});
}

View file

@ -1,10 +1,9 @@
name: platform_mirrors
description: A lightweight, cross-platform reflection system for Dart
version: 0.1.0
publish_to: none
description: A runtime reflection system for Dart that provides introspection capabilities.
version: 1.0.0
environment:
sdk: '>=3.0.0 <4.0.0'
sdk: ">=3.0.0 <4.0.0"
dependencies:
collection: ^1.17.0
@ -12,9 +11,8 @@ dependencies:
path: ^1.9.1
platform_contracts: ^0.1.0
dev_dependencies:
test: ^1.24.0
mockito: ^5.4.0
build_runner: ^2.4.0
lints: ^2.1.0
publish_to: none

View file

@ -0,0 +1,219 @@
import 'package:test/test.dart';
import 'package:platform_mirrors/src/discovery/parser/class_parser.dart';
void main() {
group('Parser Tests', () {
late ClassParser parser;
setUp(() {
parser = ClassParser();
});
test('parses basic class', () {
final source = '''
class BasicClass {
String name;
int age;
void doSomething() {
print('Hello');
}
}
''';
final result = parser.parse(source);
expect(result.isSuccess, isTrue);
final metadata = result.result!;
expect(metadata.name, equals('BasicClass'));
expect(metadata.properties.length, equals(2));
expect(metadata.methods.length, equals(1));
expect(metadata.constructors.length, equals(0));
});
test('parses generic class', () {
final source = '''
class Container<T> {
T value;
Container(this.value);
T getValue() => value;
void setValue(T newValue) {
value = newValue;
}
}
''';
final result = parser.parse(source);
expect(result.isSuccess, isTrue);
final metadata = result.result!;
expect(metadata.name, equals('Container'));
expect(metadata.typeParameters, equals(['T']));
expect(metadata.properties.length, equals(1));
expect(metadata.methods.length, equals(2));
expect(metadata.constructors.length, equals(1));
});
test('parses class with inheritance and interfaces', () {
final source = '''
abstract class Animal implements Living, Breathing {
String species;
int age;
Animal(this.species, this.age);
void makeSound();
void move() {
print('Moving...');
}
}
''';
final result = parser.parse(source);
expect(result.isSuccess, isTrue);
final metadata = result.result!;
expect(metadata.name, equals('Animal'));
expect(metadata.isAbstract, isTrue);
expect(metadata.interfaces, equals(['Living', 'Breathing']));
expect(metadata.properties.length, equals(2));
expect(metadata.methods.length, equals(2));
expect(metadata.constructors.length, equals(1));
});
test('parses properties with different modifiers', () {
final source = '''
class PropertyTest {
static const int MAX_VALUE = 100;
final String id;
late String? name;
List<int> numbers = [];
PropertyTest(this.id);
String get displayName => name ?? 'Unknown';
set displayName(String value) => name = value;
}
''';
final result = parser.parse(source);
expect(result.isSuccess, isTrue);
final metadata = result.result!;
final props = metadata.properties;
expect(props['MAX_VALUE']!.isStatic, isTrue);
expect(props['id']!.isWritable, isFalse);
expect(props['name']!.isNullable, isTrue);
expect(props['numbers']!.hasInitializer, isTrue);
expect(props['displayName']!.isReadable, isTrue);
expect(props['displayName']!.isWritable, isTrue);
});
test('parses methods with different signatures', () {
final source = '''
class MethodTest {
static void staticMethod() {}
Future<String> asyncMethod() async {
return 'done';
}
Stream<int> streamMethod() async* {
yield 1;
}
void optionalParams([int count = 0]) {}
void namedParams({required String name, int? age}) {}
T genericMethod<T>(T value) => value;
}
''';
final result = parser.parse(source);
expect(result.isSuccess, isTrue);
final metadata = result.result!;
final methods = metadata.methods;
expect(methods['staticMethod']!.isStatic, isTrue);
expect(methods['asyncMethod']!.isAsync, isTrue);
expect(methods['streamMethod']!.isGenerator, isTrue);
final optionalMethod = methods['optionalParams']!;
expect(optionalMethod.parameters.length, equals(1));
expect(optionalMethod.parameters[0].isRequired, isFalse);
final namedMethod = methods['namedParams']!;
expect(namedMethod.parameters.length, equals(2));
expect(namedMethod.parameters[0].isRequired, isTrue);
expect(namedMethod.parameters[1].isNullable, isTrue);
});
test('parses constructors with different forms', () {
final source = '''
class ConstructorTest {
final String id;
String? name;
int count;
ConstructorTest(this.id, [this.name]);
ConstructorTest.named({
required this.id,
this.name,
this.count = 0,
});
factory ConstructorTest.create(String value) {
return ConstructorTest(value);
}
const ConstructorTest.constant(this.id)
: name = null,
count = 0;
}
''';
final result = parser.parse(source);
expect(result.isSuccess, isTrue);
final metadata = result.result!;
final constructors = metadata.constructors;
expect(constructors.length, equals(4));
// Default constructor
expect(constructors[0].name, isEmpty);
expect(constructors[0].parameters.length, equals(2));
expect(constructors[0].parameters[1].isRequired, isFalse);
// Named constructor
expect(constructors[1].name, equals('named'));
expect(constructors[1].parameters.length, equals(3));
expect(constructors[1].parameters[0].isRequired, isTrue);
// Factory constructor
expect(constructors[2].name, equals('create'));
expect(constructors[2].parameters.length, equals(1));
// Const constructor
expect(constructors[3].name, equals('constant'));
expect(constructors[3].parameters.length, equals(1));
});
test('handles errors gracefully', () {
final source = '''
class InvalidClass {
void missingClosingBrace() {
''';
final result = parser.parse(source);
expect(result.isSuccess, isFalse);
expect(result.errors, isNotEmpty);
});
});
}