diff --git a/CHANGELOG.md b/CHANGELOG.md index 153ee15..54548de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ +## 2.2.4 + +- Added basic support for Records types. + - Added logic to handle record types and generate typedefs for record declarations. +- Added new tests for Records and generics in `test/reflection_factory_build_test.dart`. + +- test: ^1.24.9 +- coverage: ^1.6.4 +- data_serializer: ^1.0.12 + ## 2.2.3 - Added `ClassProxy.ignoreMethods2`. diff --git a/example/reflection_factory_bridge_example.reflection.g.dart b/example/reflection_factory_bridge_example.reflection.g.dart index b17ade4..f0cf4e9 100644 --- a/example/reflection_factory_bridge_example.reflection.g.dart +++ b/example/reflection_factory_bridge_example.reflection.g.dart @@ -1,6 +1,6 @@ // // GENERATED CODE - DO NOT MODIFY BY HAND! -// BUILDER: reflection_factory/2.2.3 +// BUILDER: reflection_factory/2.2.4 // BUILD COMMAND: dart run build_runner build // @@ -20,7 +20,7 @@ typedef __TI = TypeInfo; typedef __PR = ParameterReflection; mixin __ReflectionMixin { - static final Version _version = Version.parse('2.2.3'); + static final Version _version = Version.parse('2.2.4'); Version get reflectionFactoryVersion => _version; diff --git a/example/reflection_factory_example.reflection.g.dart b/example/reflection_factory_example.reflection.g.dart index a65b355..43370b6 100644 --- a/example/reflection_factory_example.reflection.g.dart +++ b/example/reflection_factory_example.reflection.g.dart @@ -1,6 +1,6 @@ // // GENERATED CODE - DO NOT MODIFY BY HAND! -// BUILDER: reflection_factory/2.2.3 +// BUILDER: reflection_factory/2.2.4 // BUILD COMMAND: dart run build_runner build // @@ -20,7 +20,7 @@ typedef __TI = TypeInfo; typedef __PR = ParameterReflection; mixin __ReflectionMixin { - static final Version _version = Version.parse('2.2.3'); + static final Version _version = Version.parse('2.2.4'); Version get reflectionFactoryVersion => _version; diff --git a/lib/src/reflection_factory_base.dart b/lib/src/reflection_factory_base.dart index ca945ec..42844cf 100644 --- a/lib/src/reflection_factory_base.dart +++ b/lib/src/reflection_factory_base.dart @@ -21,7 +21,7 @@ import 'reflection_factory_utils.dart'; /// Class with all registered reflections ([ClassReflection]). class ReflectionFactory { // ignore: constant_identifier_names - static const String VERSION = '2.2.3'; + static const String VERSION = '2.2.4'; static final ReflectionFactory _instance = ReflectionFactory._(); @@ -2506,8 +2506,8 @@ class TypeReflection { } var typeStr = type.toString(); - var idx = typeStr.indexOf('<'); - if (idx > 0) typeStr = typeStr.substring(0, idx); + typeStr = TypeInfo.removeTypeGenerics(typeStr); + return typeStr; } diff --git a/lib/src/reflection_factory_builder.dart b/lib/src/reflection_factory_builder.dart index 829ff4b..0357e4a 100644 --- a/lib/src/reflection_factory_builder.dart +++ b/lib/src/reflection_factory_builder.dart @@ -1,4 +1,3 @@ -import 'dart:async'; import 'dart:collection'; import 'dart:io'; @@ -14,13 +13,10 @@ import 'package:build/build.dart'; import 'package:collection/collection.dart'; import 'package:dart_style/dart_style.dart'; import 'package:path/path.dart' as pack_path; -import 'package:pub_semver/pub_semver.dart'; +import 'package:reflection_factory/reflection_factory.dart'; import 'analyzer/library.dart'; import 'analyzer/type_checker.dart'; -import 'reflection_factory_annotation.dart'; -import 'reflection_factory_base.dart'; -import 'reflection_factory_extension.dart'; /// The reflection builder. class ReflectionBuilder implements Builder { @@ -263,6 +259,8 @@ class ReflectionBuilder implements Builder { fullCode.write("part of '${genPart.partOf}';\n\n"); fullCode.write(codeTable.typeAliasTable.code); + fullCode.write('\n'); + fullCode.write(codeTable.codeReflectionMixin); var codeKeys = codeTable.allKeys.toList(); @@ -978,7 +976,7 @@ class _TypeAliasTable { late final String fReturnValue; late final String fReturnFuture; late final String fReturnFutureOr; - late final String code; + late final StringBuffer code; int fReturnValueUseCount = 0; @@ -992,22 +990,39 @@ class _TypeAliasTable { return _TypeAliasTable(privateNames); } - _TypeAliasTable(List privateNames) { + final List privateNames; + + _TypeAliasTable(this.privateNames) { trName = _buildAliasName('__TR', privateNames); tiName = _buildAliasName('__TI', privateNames); prName = _buildAliasName('__PR', privateNames); + reflectionMixinName = _buildAliasName('__ReflectionMixin', privateNames); fReturnValue = _buildAliasName('__retVal\$', privateNames); fReturnFuture = _buildAliasName('__retFut\$', privateNames); fReturnFutureOr = _buildAliasName('__retFutOr\$', privateNames); - var str = StringBuffer(); + code = StringBuffer(); + + code.write('typedef $trName = TypeReflection;\n'); + code.write('typedef $tiName = TypeInfo;\n'); + code.write('typedef $prName = ParameterReflection;\n\n'); + } + + final Map _recordsAliases = {}; - str.write('typedef $trName = TypeReflection;\n'); - str.write('typedef $tiName = TypeInfo;\n'); - str.write('typedef $prName = ParameterReflection;\n\n'); + String aliasForRecordType(String recordDeclaration) { + var alias = _recordsAliases[recordDeclaration]; + if (alias != null) return alias; - code = str.toString(); + var nextID = _recordsAliases.length + 1; + alias = _buildAliasName('__RCD$nextID', privateNames); + + _recordsAliases[recordDeclaration] = alias; + + code.write('typedef $alias = $recordDeclaration;\n'); + + return alias; } String _buildAliasName(String prefix, Iterable usedNames) { @@ -2185,7 +2200,9 @@ class _ClassTree extends RecursiveElementVisitor { var declaringType = method.declaringType!.typeNameResolvable; var returnType = method.returnTypeNameAsCode; + var returnTypeAsCode = method.returnTypeAsCode; + var nullable = method.returnNullable ? 'true' : 'false'; return "MethodReflection<$className,$returnType>(" @@ -3006,6 +3023,8 @@ class _Method extends _Element { bool get isStatic => methodElement.isStatic; + DartType get returnType => methodElement.returnType; + String get returnTypeNameAsCode => methodElement.returnType.typeNameAsNullableCode; @@ -3142,6 +3161,10 @@ extension _DartTypeExtension on DartType { String get typeNameResolvable => resolveTypeName(); String resolveTypeName({Iterable? typeParameters}) { + if (isRecordType) { + return recordDeclaration(typeParameters: typeParameters)!; + } + if (typeParameters != null && isParameterType && typeParameters.isNotEmpty) { @@ -3170,7 +3193,39 @@ extension _DartTypeExtension on DartType { return withNullability && isNullable ? '$name<$args>?' : '$name<$args>'; } + bool get isRecordType => this is RecordType; + + String? recordDeclaration({Iterable? typeParameters}) { + if (!isRecordType) return null; + + var recordType = this as RecordType; + + var recordTypesNamesPos = recordType.positionalFields + .map((t) => t.type.resolveTypeName(typeParameters: typeParameters)); + + var recordTypesNamesNamed = recordType.namedFields.map((t) => + '${t.name} ${t.type.resolveTypeName(typeParameters: typeParameters)}'); + + var recordDeclaration = [ + '(', + recordTypesNamesPos.join(', '), + if (recordTypesNamesNamed.isNotEmpty) + { + '{', + recordTypesNamesNamed.join(', '), + '}', + }, + ')', + ].join(); + + return recordDeclaration; + } + String get typeName { + if (isRecordType) { + return recordDeclaration()!; + } + var name = elementDeclaration?.name; if (name == null) { @@ -3182,10 +3237,7 @@ extension _DartTypeExtension on DartType { (idx > 0 && name.substring(idx - 1, idx).trim().isEmpty)) { name = 'Function'; } else { - idx = name.indexOf('<'); - if (idx > 0) { - name = name.substring(0, idx); - } + name = TypeInfo.removeTypeGenerics(name); } } @@ -3351,7 +3403,10 @@ extension _DartTypeExtension on DartType { if (constName != null) { return '$tr.$constName'; } else { - if (this is TypeParameterType) { + if (isRecordType) { + var typeAlias = typeAliasTable.aliasForRecordType(name); + return '$tr<$typeAlias>($typeAlias)'; + } else if (this is TypeParameterType) { return '$tr.tDynamic'; } else { return '$tr<$name>($name)'; diff --git a/lib/src/reflection_factory_type.dart b/lib/src/reflection_factory_type.dart index a14150f..9b13e42 100644 --- a/lib/src/reflection_factory_type.dart +++ b/lib/src/reflection_factory_type.dart @@ -2,8 +2,8 @@ import 'dart:async'; import 'dart:convert'; import 'dart:typed_data'; -import 'package:collection/collection.dart'; import 'package:base_codecs/base_codecs.dart' as base_codecs; +import 'package:collection/collection.dart'; import 'reflection_factory_base.dart'; import 'reflection_factory_json.dart'; @@ -1250,11 +1250,24 @@ class TypeInfo { } var typeStr = type.toString(); - var idx = typeStr.indexOf('<'); - if (idx > 0) typeStr = typeStr.substring(0, idx); + typeStr = removeTypeGenerics(typeStr); + return typeStr; } + /// Removes generics from the type or record type string. + static String removeTypeGenerics(String type) { + while (true) { + var type2 = type.replaceAll(RegExp(r"<[^<>]+>"), ''); + + if (type2.length == type.length) { + return type; + } else { + type = type2; + } + } + } + /// The [type] arguments (generics). final List _arguments; diff --git a/pubspec.yaml b/pubspec.yaml index f962873..a214345 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: reflection_factory description: Allows Dart reflection with an easy approach, even for third-party classes, using code generation portable for all Dart platforms. -version: 2.2.3 +version: 2.2.4 homepage: https://github.com/gmpassos/reflection_factory environment: @@ -24,8 +24,8 @@ dev_dependencies: build_test: ^2.2.0 lints: ^2.1.1 pubspec: ^2.3.0 - data_serializer: ^1.0.10 + data_serializer: ^1.0.12 dependency_validator: ^3.2.2 - test: ^1.24.6 - coverage: ^1.6.3 + test: ^1.24.9 + coverage: ^1.6.4 benchmark: ^0.3.0 diff --git a/test/reflection_factory_build_test.dart b/test/reflection_factory_build_test.dart index 2ff5ccb..514b9d8 100644 --- a/test/reflection_factory_build_test.dart +++ b/test/reflection_factory_build_test.dart @@ -354,6 +354,150 @@ void main() { ); }); + test('EnableReflection: records', () async { + var builder = ReflectionBuilder(verbose: true); + + var sourceAssets = { + '$_pkgName|lib/foo.dart': ''' + + import 'package:reflection_factory/reflection_factory.dart'; + + part 'foo.reflection.g.dart'; + + @EnableReflection() + class User { + + String? email ; + String pass ; + + User(this.email, {required this.pass}); + + (bool,String?) checkPassword(String pass, {bool ignoreCase = false}) { + var ok = ignoreCase ? this.pass.toLowerCase() == pass.toLowerCase() : this.pass == pass ; + var error = ok ? null : 'Invalid password'; + return (ok, error); + } + + } + + ''' + }; + + await testBuilder( + builder, + sourceAssets, + reader: await PackageAssetReader.currentIsolate(), + generateFor: {'$_pkgName|lib/foo.dart'}, + outputs: { + '$_pkgName|lib/foo.reflection.g.dart': decodedMatches(allOf( + allOf( + contains('GENERATED CODE - DO NOT MODIFY BY HAND'), + contains( + 'BUILDER: reflection_factory/${ReflectionFactory.VERSION}'), + contains("part of 'foo.dart'"), + contains( + "Version _version = Version.parse('${ReflectionFactory.VERSION}')"), + ), + allOf( + contains('User\$reflection'), + contains('User\$reflectionExtension'), + ), + matches(RegExp( + r"'ignoreCase':\s*__PR\(\s*__TR.tBool\s*,\s*'ignoreCase'\s*,\s*false\s*,\s*false\s*,\s*false\s*\)")), + allOf( + matches(RegExp(r"typedef __RCD1 = \(bool, String\);")), + matches(RegExp(r"MethodReflection\(")), + matches(RegExp(r"'checkPassword',\s+__TR<__RCD1>\(__RCD1\),")), + ), + )), + }, + onLog: (msg) { + print(msg); + }, + ); + }); + + test('EnableReflection: records+generics', () async { + var builder = ReflectionBuilder(verbose: true); + + var sourceAssets = { + '$_pkgName|lib/foo.dart': ''' + + import 'package:reflection_factory/reflection_factory.dart'; + + part 'foo.reflection.g.dart'; + + class BaseA {} + + class Info { + final A a; + final B b; + + Info(this.a, this.b); + } + + @EnableReflection() + class User { + + String? email ; + String pass ; + + User(this.email, {required this.pass}); + + (T,Info) base1(T a) { + return (a, Info(a,b) ); + } + + T base2(T a) { + return a; + } + + (A,Info) info(A a, B b) { + return (a, Info(a,b) ); + } + + } + + ''' + }; + + await testBuilder( + builder, + sourceAssets, + reader: await PackageAssetReader.currentIsolate(), + generateFor: {'$_pkgName|lib/foo.dart'}, + outputs: { + '$_pkgName|lib/foo.reflection.g.dart': decodedMatches(allOf( + allOf( + contains('GENERATED CODE - DO NOT MODIFY BY HAND'), + contains( + 'BUILDER: reflection_factory/${ReflectionFactory.VERSION}'), + contains("part of 'foo.dart'"), + contains( + "Version _version = Version.parse('${ReflectionFactory.VERSION}')"), + ), + allOf( + contains('User\$reflection'), + contains('User\$reflectionExtension'), + ), + allOf( + matches(RegExp(r"typedef\s+__RCD1\s+=\s+\(dynamic,\s+Info\);")), + matches(RegExp( + r"case\s+'base1':\s+return\s+MethodReflection\(")), + matches(RegExp( + r"case\s+'base2':\s+return\s+MethodReflection\(")), + matches(RegExp( + r"case\s+'info':\s+return\s+MethodReflection\(")), + matches(RegExp(r"__TR<__RCD1>\(__RCD1\),")), + ), + )), + }, + onLog: (msg) { + print(msg); + }, + ); + }); + test('EnableReflection(reflectionClassName, reflectionExtensionName)', () async { var builder = ReflectionBuilder(verbose: true); diff --git a/test/src/reflection/user_with_reflection.g.dart b/test/src/reflection/user_with_reflection.g.dart index 400eff6..d51ee5c 100644 --- a/test/src/reflection/user_with_reflection.g.dart +++ b/test/src/reflection/user_with_reflection.g.dart @@ -1,6 +1,6 @@ // // GENERATED CODE - DO NOT MODIFY BY HAND! -// BUILDER: reflection_factory/2.2.3 +// BUILDER: reflection_factory/2.2.4 // BUILD COMMAND: dart run build_runner build // @@ -20,7 +20,7 @@ typedef __TI = TypeInfo; typedef __PR = ParameterReflection; mixin __ReflectionMixin { - static final Version _version = Version.parse('2.2.3'); + static final Version _version = Version.parse('2.2.4'); Version get reflectionFactoryVersion => _version; diff --git a/test/src/user_reflection_bridge.reflection.g.dart b/test/src/user_reflection_bridge.reflection.g.dart index 83e0ceb..1010716 100644 --- a/test/src/user_reflection_bridge.reflection.g.dart +++ b/test/src/user_reflection_bridge.reflection.g.dart @@ -1,6 +1,6 @@ // // GENERATED CODE - DO NOT MODIFY BY HAND! -// BUILDER: reflection_factory/2.2.3 +// BUILDER: reflection_factory/2.2.4 // BUILD COMMAND: dart run build_runner build // @@ -20,7 +20,7 @@ typedef __TI = TypeInfo; typedef __PR = ParameterReflection; mixin __ReflectionMixin { - static final Version _version = Version.parse('2.2.3'); + static final Version _version = Version.parse('2.2.4'); Version get reflectionFactoryVersion => _version;