refactor: refactoring platform mirrors 55 pass 7 fail
This commit is contained in:
parent
dacf949af4
commit
0453c67177
19 changed files with 2258 additions and 178 deletions
|
@ -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';
|
||||
|
|
|
@ -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
|
||||
// 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);
|
||||
// Find named parameter section
|
||||
final namedStart = params.indexOf('{');
|
||||
final namedEnd = params.lastIndexOf('}');
|
||||
final hasNamedParams = namedStart != -1 && namedEnd != -1;
|
||||
|
||||
// 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);
|
||||
final isRequired = namedPart.contains('required');
|
||||
|
||||
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);
|
||||
if (match != null) {
|
||||
final type = match.group(1)!;
|
||||
final name = match.group(2)!;
|
||||
|
||||
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: 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;
|
||||
}
|
||||
|
157
packages/mirrors/lib/src/discovery/parser/base_parser.dart
Normal file
157
packages/mirrors/lib/src/discovery/parser/base_parser.dart
Normal 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();
|
||||
}
|
||||
}
|
325
packages/mirrors/lib/src/discovery/parser/class_parser.dart
Normal file
325
packages/mirrors/lib/src/discovery/parser/class_parser.dart
Normal 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;
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
365
packages/mirrors/lib/src/discovery/parser/method_parser.dart
Normal file
365
packages/mirrors/lib/src/discovery/parser/method_parser.dart
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
62
packages/mirrors/lib/src/discovery/parser/parser.dart
Normal file
62
packages/mirrors/lib/src/discovery/parser/parser.dart
Normal 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);
|
||||
}
|
||||
}
|
305
packages/mirrors/lib/src/discovery/parser/property_parser.dart
Normal file
305
packages/mirrors/lib/src/discovery/parser/property_parser.dart
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
175
packages/mirrors/lib/src/discovery/parser/type_parser.dart
Normal file
175
packages/mirrors/lib/src/discovery/parser/type_parser.dart
Normal 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!);
|
||||
}
|
||||
}
|
45
packages/mirrors/lib/src/metadata/class_metadata.dart
Normal file
45
packages/mirrors/lib/src/metadata/class_metadata.dart
Normal 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 [],
|
||||
});
|
||||
}
|
|
@ -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,
|
||||
);
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 [],
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
219
packages/mirrors/test/discovery/parser_test.dart
Normal file
219
packages/mirrors/test/discovery/parser_test.dart
Normal 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);
|
||||
});
|
||||
});
|
||||
}
|
Loading…
Reference in a new issue