blob: 10235b25e21de5b098e663f27ae16796cf275e66 [file] [log] [blame] [edit]
import 'package:collection/collection.dart';
import 'package:macros/macros.dart';
/// {@template equatable}
/// An experimental macro which implements value-equality.
///
/// ```dart
/// @Equatable()
/// class Person {
/// const Person({required this.name});
/// final String name;
/// }
///
/// void main() {
/// // Generated `hashCode` and `operator==` for value based equality.
/// print(Person(name: 'Dash') == Person(name: 'Dash')); // true
/// print(Person(name: 'Dash') == Person(name: 'Sparky')); // false
/// }
/// ```
/// {@endtemplate}
macro class Equatable implements ClassDeclarationsMacro, ClassDefinitionMacro {
/// {@macro equatable}
const Equatable();
@override
Future<void> buildDeclarationsForClass(
ClassDeclaration clazz,
MemberDeclarationBuilder builder,
) {
return [
_declareEquals(clazz, builder),
_declareHashCode(clazz, builder),
].wait;
}
@override
Future<void> buildDefinitionForClass(
ClassDeclaration clazz,
TypeDefinitionBuilder builder,
) {
return [
_buildEquals(clazz, builder),
_buildHashCode(clazz, builder),
].wait;
}
Future<void> _declareEquals(
ClassDeclaration clazz,
MemberDeclarationBuilder builder,
) async {
final boolean = await builder.codeFrom(_dartCore, 'bool');
return builder.declareInType(
DeclarationCode.fromParts(
['external ', boolean, ' operator==(', ' covariant ${clazz.identifier.name}', ' other);'],
),
);
}
Future<void> _declareHashCode(
ClassDeclaration clazz,
MemberDeclarationBuilder builder,
) async {
final integer = await builder.codeFrom(_dartCore, 'int');
return builder.declareInType(
DeclarationCode.fromParts(['external ', integer, ' get hashCode;']),
);
}
Future<void> _buildEquals(
ClassDeclaration clazz,
TypeDefinitionBuilder builder,
) async {
final methods = await builder.methodsOf(clazz);
final equality = methods.firstWhereOrNull(
(m) => m.identifier.name == '==',
);
if (equality == null) return;
final (equalsMethod, deepEquals, identical, fields) = await (
builder.buildMethod(equality.identifier),
builder.codeFrom(_equatable, 'deepEquals'),
builder.codeFrom(_dartCore, 'identical'),
builder.allFieldsOf(clazz),
).wait;
fields.removeWhere((f) => f.hasStatic);
if (fields.isEmpty) {
return equalsMethod.augment(
FunctionBodyCode.fromParts(
[
'{',
'if (', identical,' (this, other)',')', 'return true;',
'return other is ${clazz.identifier.name} && ',
'other.runtimeType == runtimeType;',
'}',
],
),
);
}
final fieldNames = fields.map((f) => f.identifier.name);
final lastField = fieldNames.last;
return equalsMethod.augment(
FunctionBodyCode.fromParts(
[
'{',
'if (', identical,' (this, other)',')', 'return true;',
'return other is ${clazz.identifier.name} && ',
'other.runtimeType == runtimeType && ',
for (final field in fieldNames)
...[
deepEquals,
'($field, other.$field)',
if (field != lastField) ' && ',
],
';',
'}',
],
),
);
}
Future<void> _buildHashCode(
ClassDeclaration clazz,
TypeDefinitionBuilder builder,
) async {
final methods = await builder.methodsOf(clazz);
final hashCode = methods.firstWhereOrNull(
(m) => m.identifier.name == 'hashCode',
);
if (hashCode == null) return;
final (hashCodeMethod, jenkinsHash, fields) = await (
builder.buildMethod(hashCode.identifier),
builder.codeFrom(_equatable, 'jenkinsHash'),
builder.allFieldsOf(clazz),
).wait;
fields.removeWhere((f) => f.hasStatic);
final fieldNames = fields.map((f) => f.identifier.name);
return hashCodeMethod.augment(
FunctionBodyCode.fromParts(
[
'=> ',
jenkinsHash,
'([',
fieldNames.join(', '),
']);',
],
),
);
}
}
extension on DeclarationBuilder {
Future<NamedTypeAnnotationCode> codeFrom(Uri library, String name) async {
// ignore: deprecated_member_use
return _codeFrom(library, name, resolveIdentifier);
}
}
extension on DefinitionBuilder {
Future<List<FieldDeclaration>> allFieldsOf(ClassDeclaration clazz) async {
final allFields = <FieldDeclaration>[...await fieldsOf(clazz)];
var superclass = await superclassOf(clazz);
while (superclass != null && superclass.identifier.name != 'Object') {
allFields.addAll(await fieldsOf(superclass));
superclass = await superclassOf(superclass);
}
return allFields;
}
Future<NamedTypeAnnotationCode> codeFrom(Uri library, String name) async {
// ignore: deprecated_member_use
return _codeFrom(library, name, resolveIdentifier);
}
Future<ClassDeclaration?> superclassOf(ClassDeclaration clazz) async {
final superclassType = clazz.superclass != null
? await typeDeclarationOf(clazz.superclass!.identifier)
: null;
return superclassType is ClassDeclaration ? superclassType : null;
}
}
Future<NamedTypeAnnotationCode> _codeFrom(
Uri library,
String name,
Future<Identifier> Function(Uri library, String name) resolveIdentifier,
) async {
final identifier = await resolveIdentifier(library, name);
return NamedTypeAnnotationCode(name: identifier);
}
// Used libraries
final _dartCore = Uri.parse('dart:core');
final _equatable = Uri.parse(
'package:equatable/equatable.dart',
);