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';
|
export 'src/core/mirror_system.dart';
|
||||||
|
|
||||||
/// Discovery
|
/// Discovery
|
||||||
export 'src/discovery/type_analyzer.dart';
|
export 'src/discovery/analyzers/library_analyzer.dart';
|
||||||
export 'src/discovery/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_library_discoverer.dart';
|
||||||
export 'src/discovery/runtime_type_discoverer.dart';
|
export 'src/discovery/runtime_type_discoverer.dart';
|
||||||
export 'src/discovery/package_analyzer.dart';
|
|
||||||
|
|
||||||
/// Discovery Models
|
/// Discovery Models
|
||||||
export 'src/discovery/models/models.dart';
|
export 'src/discovery/models/models.dart';
|
||||||
|
@ -29,7 +29,9 @@ export 'src/exceptions/not_reflectable_exception.dart';
|
||||||
export 'src/exceptions/reflection_exception.dart';
|
export 'src/exceptions/reflection_exception.dart';
|
||||||
|
|
||||||
/// Metadata
|
/// Metadata
|
||||||
|
export 'src/metadata/class_metadata.dart';
|
||||||
export 'src/metadata/constructor_metadata.dart';
|
export 'src/metadata/constructor_metadata.dart';
|
||||||
|
export 'src/metadata/extended_method_metadata.dart';
|
||||||
export 'src/metadata/function_metadata.dart';
|
export 'src/metadata/function_metadata.dart';
|
||||||
export 'src/metadata/method_metadata.dart';
|
export 'src/metadata/method_metadata.dart';
|
||||||
export 'src/metadata/parameter_metadata.dart';
|
export 'src/metadata/parameter_metadata.dart';
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'package:path/path.dart' as path;
|
import 'package:path/path.dart' as path;
|
||||||
import '../metadata/type_metadata.dart';
|
import '../../metadata/type_metadata.dart';
|
||||||
import '../metadata/constructor_metadata.dart';
|
import '../../metadata/constructor_metadata.dart';
|
||||||
import '../metadata/method_metadata.dart';
|
import '../../metadata/method_metadata.dart';
|
||||||
import '../metadata/parameter_metadata.dart';
|
import '../../metadata/parameter_metadata.dart';
|
||||||
import '../metadata/property_metadata.dart';
|
import '../../metadata/property_metadata.dart';
|
||||||
import '../registry/reflection_registry.dart';
|
import '../../registry/reflection_registry.dart';
|
||||||
import '../reflector/runtime_reflector.dart';
|
import '../../reflector/runtime_reflector.dart';
|
||||||
import 'proxy_type.dart';
|
import '../../types/proxy_type.dart';
|
||||||
|
|
||||||
/// Discovers and analyzes types in a package automatically.
|
/// Discovers and analyzes types in a package automatically.
|
||||||
class PackageAnalyzer {
|
class PackageAnalyzer {
|
||||||
|
@ -172,6 +172,33 @@ class PackageAnalyzer {
|
||||||
) {
|
) {
|
||||||
final type = _getTypeForName(className);
|
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
|
// Register properties
|
||||||
properties.forEach((name, metadata) {
|
properties.forEach((name, metadata) {
|
||||||
ReflectionRegistry.registerProperty(
|
ReflectionRegistry.registerProperty(
|
||||||
|
@ -212,23 +239,7 @@ class PackageAnalyzer {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Register constructors
|
// Create and register type metadata last
|
||||||
for (final constructor in constructors) {
|
|
||||||
// Get parameter names for named parameters
|
|
||||||
final parameterNames = constructor.parameters
|
|
||||||
.where((p) => p.isNamed)
|
|
||||||
.map((p) => p.name)
|
|
||||||
.toList();
|
|
||||||
|
|
||||||
ReflectionRegistry.registerConstructor(
|
|
||||||
type,
|
|
||||||
constructor.name,
|
|
||||||
parameterTypes: constructor.parameterTypes,
|
|
||||||
parameterNames: parameterNames.isNotEmpty ? parameterNames : null,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create type metadata
|
|
||||||
final metadata = TypeMetadata(
|
final metadata = TypeMetadata(
|
||||||
type: type,
|
type: type,
|
||||||
name: className,
|
name: className,
|
||||||
|
@ -259,7 +270,6 @@ class PackageAnalyzer {
|
||||||
const [],
|
const [],
|
||||||
);
|
);
|
||||||
|
|
||||||
// Register metadata with reflection system
|
|
||||||
ReflectionRegistry.registerTypeMetadata(type, metadata);
|
ReflectionRegistry.registerTypeMetadata(type, metadata);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -513,142 +523,156 @@ class PackageAnalyzer {
|
||||||
|
|
||||||
print('Found ${matches.length} constructor matches:');
|
print('Found ${matches.length} constructor matches:');
|
||||||
for (final match in matches) {
|
for (final match in matches) {
|
||||||
final fullMatch = classContent.substring(match.start, match.end);
|
final matchContent = classContent.substring(match.start, match.end);
|
||||||
print('Full match: $fullMatch');
|
print('Full match: $matchContent');
|
||||||
print('Group 1 (name): ${match.group(1)}');
|
print('Group 1 (name): ${match.group(1)}');
|
||||||
print('Group 2 (params): ${match.group(2)}');
|
print('Group 2 (params): ${match.group(2)}');
|
||||||
|
|
||||||
// Get the line containing this match
|
// Get the line before this match
|
||||||
final lineStart = classContent.lastIndexOf('\n', match.start) + 1;
|
final beforeLineStart =
|
||||||
final lineEnd = classContent.indexOf('\n', match.start);
|
classContent.lastIndexOf('\n', match.start - 1) + 1;
|
||||||
final line = classContent.substring(
|
final beforeLineEnd = match.start - 1;
|
||||||
lineStart, lineEnd > 0 ? lineEnd : classContent.length);
|
final beforeLine = beforeLineStart < beforeLineEnd
|
||||||
|
? classContent.substring(beforeLineStart, beforeLineEnd).trim()
|
||||||
// Skip if this is part of a static method or return statement
|
: '';
|
||||||
if (line.trim().startsWith('static') ||
|
|
||||||
line.trim().startsWith('return')) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
final name = match.group(1) ?? '';
|
final name = match.group(1) ?? '';
|
||||||
final params = match.group(2) ?? '';
|
final params = match.group(2) ?? '';
|
||||||
final isFactory = line.trim().startsWith('factory');
|
final isFactory = matchContent.trim().startsWith('factory');
|
||||||
|
final isStatic = beforeLine.startsWith('static');
|
||||||
// For factory constructors without a name, use 'create'
|
|
||||||
final constructorName = isFactory && name.isEmpty ? 'create' : name;
|
|
||||||
|
|
||||||
// Parse parameters
|
// Parse parameters
|
||||||
final parameterTypes = _extractParameterTypes(params);
|
final parameterTypes = _extractParameterTypes(params);
|
||||||
final parameters = _extractParameters(params);
|
final parameters = _extractParameters(params);
|
||||||
|
|
||||||
// Add constructor
|
// Add constructor with appropriate name
|
||||||
constructors.add(ConstructorMetadata(
|
final constructorName = isFactory && name.isEmpty ? 'create' : name;
|
||||||
name: constructorName,
|
|
||||||
parameterTypes: parameterTypes,
|
if (isStatic) {
|
||||||
parameters: parameters,
|
// 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');
|
print('Returning ${constructors.length} constructors');
|
||||||
return constructors;
|
return constructors;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Extracts parameter types from a parameter list string.
|
/// Extracts parameter types and metadata from a parameter list string.
|
||||||
static List<Type> _extractParameterTypes(String params) {
|
static (List<Type>, List<ParameterMetadata>) _extractParameterInfo(
|
||||||
|
String params) {
|
||||||
final types = <Type>[];
|
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 parameters = <ParameterMetadata>[];
|
||||||
final paramRegex = RegExp(
|
final paramRegex = RegExp(
|
||||||
r'(?:required\s+)?(\w+(?:<[^>]+>)?)\s+(?:this\.)?(\w+)(?:\s*\?)?(?:\s*=\s*([^,}]+))?',
|
r'(?:required\s+)?(\w+(?:<[^>]+>)?)\s+(?:this\.)?(\w+)(?:\s*\?)?(?:\s*=\s*([^,}]+))?',
|
||||||
multiLine: true,
|
multiLine: true,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Split parameters by commas, handling both positional and named parameters
|
// Find named parameter section
|
||||||
final parts =
|
final namedStart = params.indexOf('{');
|
||||||
params.split(',').map((p) => p.trim()).where((p) => p.isNotEmpty);
|
final namedEnd = params.lastIndexOf('}');
|
||||||
for (final part in parts) {
|
final hasNamedParams = namedStart != -1 && namedEnd != -1;
|
||||||
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');
|
|
||||||
|
|
||||||
parameters.add(ParameterMetadata(
|
// Handle positional parameters
|
||||||
name: name,
|
final positionalPart =
|
||||||
type: _getTypeForName(type),
|
hasNamedParams ? params.substring(0, namedStart).trim() : params.trim();
|
||||||
isRequired: isRequired,
|
final positionalParams = positionalPart.split(',').map((p) => p.trim());
|
||||||
isNamed: true,
|
for (final param in positionalParams) {
|
||||||
defaultValue: defaultValue != null
|
if (param.isEmpty) continue;
|
||||||
? _parseDefaultValue(defaultValue)
|
final match = paramRegex.firstMatch(param);
|
||||||
: null,
|
if (match != null) {
|
||||||
));
|
final type = match.group(1)!;
|
||||||
}
|
final name = match.group(2)!;
|
||||||
}
|
final defaultValue = match.group(3);
|
||||||
}
|
|
||||||
} else {
|
types.add(_getTypeForName(type));
|
||||||
// Handle positional parameter
|
parameters.add(ParameterMetadata(
|
||||||
final match = paramRegex.firstMatch(part);
|
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) {
|
if (match != null) {
|
||||||
final type = match.group(1)!;
|
final type = match.group(1)!;
|
||||||
final name = match.group(2)!;
|
final name = match.group(2)!;
|
||||||
|
final defaultValue = match.group(3);
|
||||||
|
final isRequired = param.contains('required $type');
|
||||||
|
|
||||||
|
types.add(_getTypeForName(type));
|
||||||
parameters.add(ParameterMetadata(
|
parameters.add(ParameterMetadata(
|
||||||
name: name,
|
name: name,
|
||||||
type: _getTypeForName(type),
|
type: _getTypeForName(type),
|
||||||
isRequired: true,
|
isRequired: isRequired,
|
||||||
isNamed: false,
|
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;
|
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 {
|
class MethodMetadata {
|
||||||
/// The name of the method.
|
/// The name of the method.
|
||||||
final String name;
|
final String name;
|
||||||
|
|
||||||
/// The parameter types of the method in order.
|
/// The parameter types of the method.
|
||||||
final List<Type> parameterTypes;
|
final List<Type> parameterTypes;
|
||||||
|
|
||||||
/// Detailed metadata about each parameter.
|
/// The parameters of the method.
|
||||||
final List<ParameterMetadata> parameters;
|
final List<ParameterMetadata> parameters;
|
||||||
|
|
||||||
/// Whether the method is static.
|
|
||||||
final bool isStatic;
|
|
||||||
|
|
||||||
/// Whether the method returns void.
|
/// Whether the method returns void.
|
||||||
final bool returnsVoid;
|
final bool returnsVoid;
|
||||||
|
|
||||||
/// The return type of the method.
|
/// The return type of the method.
|
||||||
final Type returnType;
|
final Type returnType;
|
||||||
|
|
||||||
/// Any attributes (annotations) on this method.
|
/// Whether the method is static.
|
||||||
final List<Object> attributes;
|
final bool isStatic;
|
||||||
|
|
||||||
/// Type parameters for generic methods.
|
/// Whether the method is async.
|
||||||
final List<TypeParameterMetadata> typeParameters;
|
final bool isAsync;
|
||||||
|
|
||||||
/// Creates a new method metadata instance.
|
/// Whether the method is a generator (sync* or async*).
|
||||||
const MethodMetadata({
|
final bool isGenerator;
|
||||||
|
|
||||||
|
/// Whether the method is external.
|
||||||
|
final bool isExternal;
|
||||||
|
|
||||||
|
MethodMetadata({
|
||||||
required this.name,
|
required this.name,
|
||||||
required this.parameterTypes,
|
required this.parameterTypes,
|
||||||
required this.parameters,
|
required this.parameters,
|
||||||
required this.returnsVoid,
|
required this.returnsVoid,
|
||||||
required this.returnType,
|
required this.returnType,
|
||||||
this.isStatic = false,
|
this.isStatic = false,
|
||||||
this.attributes = const [],
|
this.isAsync = false,
|
||||||
this.typeParameters = const [],
|
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 {
|
class ParameterMetadata {
|
||||||
/// The name of the parameter.
|
/// The name of the parameter.
|
||||||
final String name;
|
final String name;
|
||||||
|
@ -6,25 +6,24 @@ class ParameterMetadata {
|
||||||
/// The type of the parameter.
|
/// The type of the parameter.
|
||||||
final Type type;
|
final Type type;
|
||||||
|
|
||||||
/// Whether this parameter is required.
|
/// Whether the parameter is required.
|
||||||
final bool isRequired;
|
final bool isRequired;
|
||||||
|
|
||||||
/// Whether this parameter is named.
|
/// Whether the parameter is named.
|
||||||
final bool isNamed;
|
final bool isNamed;
|
||||||
|
|
||||||
/// The default value for this parameter, if any.
|
/// Whether the parameter is nullable.
|
||||||
final Object? defaultValue;
|
final bool isNullable;
|
||||||
|
|
||||||
/// Any attributes (annotations) on this parameter.
|
/// The default value of the parameter, if any.
|
||||||
final List<Object> attributes;
|
final dynamic defaultValue;
|
||||||
|
|
||||||
/// Creates a new parameter metadata instance.
|
ParameterMetadata({
|
||||||
const ParameterMetadata({
|
|
||||||
required this.name,
|
required this.name,
|
||||||
required this.type,
|
required this.type,
|
||||||
required this.isRequired,
|
this.isRequired = true,
|
||||||
this.isNamed = false,
|
this.isNamed = false,
|
||||||
|
this.isNullable = false,
|
||||||
this.defaultValue,
|
this.defaultValue,
|
||||||
this.attributes = const [],
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
/// Represents metadata about a type's property.
|
/// Metadata about a class property.
|
||||||
class PropertyMetadata {
|
class PropertyMetadata {
|
||||||
/// The name of the property.
|
/// The name of the property.
|
||||||
final String name;
|
final String name;
|
||||||
|
@ -12,15 +12,30 @@ class PropertyMetadata {
|
||||||
/// Whether the property can be written to.
|
/// Whether the property can be written to.
|
||||||
final bool isWritable;
|
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.
|
/// Any attributes (annotations) on this property.
|
||||||
final List<Object> attributes;
|
final List<Object> attributes;
|
||||||
|
|
||||||
/// Creates a new property metadata instance.
|
PropertyMetadata({
|
||||||
const PropertyMetadata({
|
|
||||||
required this.name,
|
required this.name,
|
||||||
required this.type,
|
required this.type,
|
||||||
this.isReadable = true,
|
this.isReadable = true,
|
||||||
this.isWritable = true,
|
this.isWritable = true,
|
||||||
|
this.isStatic = false,
|
||||||
|
this.isLate = false,
|
||||||
|
this.isNullable = false,
|
||||||
this.attributes = const [],
|
this.attributes = const [],
|
||||||
|
this.hasInitializer = false,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,20 +1,18 @@
|
||||||
name: platform_mirrors
|
name: platform_mirrors
|
||||||
description: A lightweight, cross-platform reflection system for Dart
|
description: A runtime reflection system for Dart that provides introspection capabilities.
|
||||||
version: 0.1.0
|
version: 1.0.0
|
||||||
publish_to: none
|
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: '>=3.0.0 <4.0.0'
|
sdk: ">=3.0.0 <4.0.0"
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
collection: ^1.17.0
|
collection: ^1.17.0
|
||||||
meta: ^1.9.0
|
meta: ^1.9.0
|
||||||
path: ^1.9.1
|
path: ^1.9.1
|
||||||
platform_contracts: ^0.1.0
|
platform_contracts: ^0.1.0
|
||||||
|
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
test: ^1.24.0
|
test: ^1.24.0
|
||||||
mockito: ^5.4.0
|
|
||||||
build_runner: ^2.4.0
|
|
||||||
lints: ^2.1.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