Skip to content

Commit

Permalink
feat: Added automatic generation of == and hashCode method with `…
Browse files Browse the repository at this point in the history
…@StoreConfig` annotation
  • Loading branch information
amondnet committed Dec 18, 2023
1 parent 52515a1 commit e5e72e5
Show file tree
Hide file tree
Showing 21 changed files with 156 additions and 11 deletions.
4 changes: 4 additions & 0 deletions mobx/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 2.3.0

- Added automatic generation of `==` and `hashCode` method with `@StoreConfig` annotation

## 2.2.3

- Avoid unnecessary observable notifications of `@observable` `Iterable` or `Map` fields of Stores by [@amondnet](https://github.com/amondnet) in [#951](https://github.com/mobxjs/mobx.dart/pull/951)
Expand Down
3 changes: 2 additions & 1 deletion mobx/lib/src/api/annotations.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@
/// Currently the only configuration used is boolean to indicate generation of toString method (true), or not (false)
class StoreConfig {
const StoreConfig({this.hasToString = true});
const StoreConfig({this.hasToString = true, this.hasEqualsAndHashCode = true});

Check warning on line 5 in mobx/lib/src/api/annotations.dart

View check run for this annotation

Codecov / codecov/patch

mobx/lib/src/api/annotations.dart#L5

Added line #L5 was not covered by tests

final bool hasToString;
final bool hasEqualsAndHashCode;
}

/// Internal class only used for code-generation with `mobx_codegen`.
Expand Down
15 changes: 15 additions & 0 deletions mobx/lib/src/api/store.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'package:mobx/mobx.dart';
import 'package:mobx/src/utils.dart';

/// The `Store` mixin is primarily meant for code-generation and used as part of the
/// `mobx_codegen` package.
Expand All @@ -9,4 +10,18 @@ import 'package:mobx/mobx.dart';
mixin Store {
/// Override this method to use a custom context.
ReactiveContext get context => mainContext;

List<Object?> props = [];

@override

Check warning on line 16 in mobx/lib/src/api/store.dart

View check run for this annotation

Codecov / codecov/patch

mobx/lib/src/api/store.dart#L16

Added line #L16 was not covered by tests
bool operator ==(Object other) {
return identical(this, other) ||
other is Store &&
runtimeType == other.runtimeType &&
equatable(props, other.props);

Check warning on line 21 in mobx/lib/src/api/store.dart

View check run for this annotation

Codecov / codecov/patch

mobx/lib/src/api/store.dart#L19-L21

Added lines #L19 - L21 were not covered by tests
}

@override
int get hashCode => runtimeType.hashCode ^ mapPropsToHashCode(props);

Check warning on line 25 in mobx/lib/src/api/store.dart

View check run for this annotation

Codecov / codecov/patch

mobx/lib/src/api/store.dart#L24-L25

Added lines #L24 - L25 were not covered by tests

}
39 changes: 38 additions & 1 deletion mobx/lib/src/utils.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import 'dart:async';

import 'package:collection/collection.dart' show DeepCollectionEquality;
import 'package:collection/collection.dart' show DeepCollectionEquality, IterableExtension;

const Duration ms = Duration(milliseconds: 1);

Expand Down Expand Up @@ -38,3 +38,40 @@ bool equatable<T>(T a, T b) {
}

const DeepCollectionEquality _equality = DeepCollectionEquality();

/// Returns a `hashCode` for [props].
int mapPropsToHashCode(Iterable<Object?>? props) {
return _finish(props == null ? 0 : props.fold(0, _combine));

Check warning on line 44 in mobx/lib/src/utils.dart

View check run for this annotation

Codecov / codecov/patch

mobx/lib/src/utils.dart#L43-L44

Added lines #L43 - L44 were not covered by tests
}

/// Jenkins Hash Functions
/// https://en.wikipedia.org/wiki/Jenkins_hash_function
int _combine(int hash, Object? object) {
if (object is Map) {
object.keys
.sorted((Object? a, Object? b) => a.hashCode - b.hashCode)
.forEach((Object? key) {
hash = hash ^ _combine(hash, [key, (object! as Map)[key]]);

Check warning on line 54 in mobx/lib/src/utils.dart

View check run for this annotation

Codecov / codecov/patch

mobx/lib/src/utils.dart#L49-L54

Added lines #L49 - L54 were not covered by tests
});
return hash;
}
if (object is Set) {
object = object.sorted((Object? a, Object? b) => a.hashCode - b.hashCode);

Check warning on line 59 in mobx/lib/src/utils.dart

View check run for this annotation

Codecov / codecov/patch

mobx/lib/src/utils.dart#L58-L59

Added lines #L58 - L59 were not covered by tests
}
if (object is Iterable) {
for (final value in object) {
hash = hash ^ _combine(hash, value);

Check warning on line 63 in mobx/lib/src/utils.dart

View check run for this annotation

Codecov / codecov/patch

mobx/lib/src/utils.dart#L61-L63

Added lines #L61 - L63 were not covered by tests
}
return hash ^ object.length;

Check warning on line 65 in mobx/lib/src/utils.dart

View check run for this annotation

Codecov / codecov/patch

mobx/lib/src/utils.dart#L65

Added line #L65 was not covered by tests
}

hash = 0x1fffffff & (hash + object.hashCode);
hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10));
return hash ^ (hash >> 6);

Check warning on line 70 in mobx/lib/src/utils.dart

View check run for this annotation

Codecov / codecov/patch

mobx/lib/src/utils.dart#L68-L70

Added lines #L68 - L70 were not covered by tests
}

int _finish(int hash) {
hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3));
hash = hash ^ (hash >> 11);
return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));

Check warning on line 76 in mobx/lib/src/utils.dart

View check run for this annotation

Codecov / codecov/patch

mobx/lib/src/utils.dart#L73-L76

Added lines #L73 - L76 were not covered by tests
}
2 changes: 1 addition & 1 deletion mobx/lib/version.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Generated via set_version.dart. !!!DO NOT MODIFY BY HAND!!!

/// The current version as per `pubspec.yaml`.
const version = '2.2.3';
const version = '2.3.0';
2 changes: 1 addition & 1 deletion mobx/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: mobx
version: 2.2.3
version: 2.3.0
description: "MobX is a library for reactively managing the state of your applications. Use the power of observables, actions, and reactions to supercharge your Dart and Flutter apps."

homepage: https://github.com/mobxjs/mobx.dart
Expand Down
4 changes: 4 additions & 0 deletions mobx_codegen/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 2.5.0

- Added automatic generation of `==` and `hashCode` method with `@StoreConfig` annotation

## 2.4.0

- Require `analyzer: ^5.12.0`
Expand Down
17 changes: 17 additions & 0 deletions mobx_codegen/lib/src/store_class_visitor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ class StoreClassVisitor extends SimpleElementVisitor {
}
// if the class is annotated to generate toString() method we add the information to the _storeTemplate
_storeTemplate.generateToString = hasGeneratedToString(options, element);
_storeTemplate.generateEquals = hasGeneratedEquals(options, element);
}

@override
Expand Down Expand Up @@ -283,6 +284,22 @@ bool hasGeneratedToString(BuilderOptions options, ClassElement? classElement) {
return true;
}

bool hasGeneratedEquals(BuilderOptions options, ClassElement? classElement) {
const fieldKey = 'hasEqualsAndHashCode';

if (classElement != null && isStoreConfigAnnotatedStoreClass(classElement)) {
final annotation =
_toStringAnnotationChecker.firstAnnotationOfExact(classElement);
return annotation?.getField(fieldKey)?.toBoolValue() ?? false;
}

if (options.config.containsKey(fieldKey)) {
return options.config[fieldKey]! as bool;
}

return true;
}

bool _any(List<bool> list) => list.any(_identity);

T _identity<T>(T value) => value;
22 changes: 22 additions & 0 deletions mobx_codegen/lib/src/template/store.dart
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,10 @@ abstract class StoreTemplate {
final Rows<ObservableFutureTemplate> observableFutures = Rows();
final Rows<ObservableStreamTemplate> observableStreams = Rows();
final List<String> toStringList = [];
final List<String> props = [];

bool generateToString = false;
bool generateEquals = true;
String? _actionControllerName;
String get actionControllerName =>
_actionControllerName ??= '_\$${parentTypeName}ActionController';
Expand Down Expand Up @@ -72,6 +74,24 @@ ${allStrings.join(',\n')}
''';
}

String get propsMethod {
if (!generateEquals) {
return '';
}

final allObservables = observables.templates
.map((current) => 'super.${current.name}');

final allProps = props..addAll(allObservables);

// The indents have been kept to ensure each field comes on a separate line without any tabs/spaces
return '''
@override
List<Object?> get props => [${allProps.join(', ')}];
''';
}


String get storeBody => '''
$computeds
Expand All @@ -88,5 +108,7 @@ ${allStrings.join(',\n')}
$actions
$toStringMethod
$propsMethod
''';
}
2 changes: 1 addition & 1 deletion mobx_codegen/lib/version.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Generated via set_version.dart. !!!DO NOT MODIFY BY HAND!!!

/// The current version as per `pubspec.yaml`.
const version = '2.4.0';
const version = '2.5.0';
4 changes: 2 additions & 2 deletions mobx_codegen/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: mobx_codegen
description: Code generator for MobX that adds support for annotating your code with @observable, @computed, @action and also creating Store classes.
version: 2.4.0
version: 2.5.0

homepage: https://github.com/mobxjs/mobx.dart
issue_tracker: https://github.com/mobxjs/mobx.dart/issues
Expand All @@ -13,7 +13,7 @@ dependencies:
build: ^2.2.1
build_resolvers: ^2.0.6
meta: ^1.3.0
mobx: ^2.2.0
mobx: ^2.3.0
path: ^1.8.0
source_gen: ^1.2.1

Expand Down
3 changes: 3 additions & 0 deletions mobx_codegen/test/data/annotations_test_class_output.dart
Original file line number Diff line number Diff line change
Expand Up @@ -73,4 +73,7 @@ mixin _$AnnotationsTestClass on AnnotationsTestClassBase, Store {
foo: ${foo}
''';
}

@override
List<Object?> get props => [super.foo];
}
2 changes: 1 addition & 1 deletion mobx_codegen/test/data/valid_generic_store_input.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ part 'generator_sample.g.dart';

class Item<A extends num> = _Item<A> with _$Item<A>;

@StoreConfig(hasToString: false)
@StoreConfig(hasToString: false, hasEqualsAndHashCode: false)
abstract class _Item<T extends num> with Store {
@observable
T value1;
Expand Down
2 changes: 1 addition & 1 deletion mobx_codegen/test/data/valid_import_prefixed_input.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ part 'generator_sample.g.dart';

class User<T extends io.Process> = UserBase<T> with _$User<T>;

@StoreConfig(hasToString: false)
@StoreConfig(hasToString: false, hasEqualsAndHashCode: false)
abstract class UserBase<T extends io.Process> with Store {
UserBase();

Expand Down
2 changes: 1 addition & 1 deletion mobx_codegen/test/data/valid_input.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ typedef VoidCallback = void Function();

class User = UserBase with _$User;

@StoreConfig(hasToString: false)
@StoreConfig(hasToString: false, hasEqualsAndHashCode: false)
abstract class UserBase with Store {
UserBase(this.id);

Expand Down
2 changes: 1 addition & 1 deletion mobx_codegen/test/data/valid_late_variables_input.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ part 'generator_sample.g.dart';

class TestStore = _TestStore with _$TestStore;

@StoreConfig(hasToString: false)
@StoreConfig(hasToString: false, hasEqualsAndHashCode: false)
abstract class _TestStore with Store {
@observable
late String username;
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 14 additions & 0 deletions mobx_codegen/test/generator_usage_test.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions mobx_codegen/test/nested_store.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions mobx_codegen/test/store_class_visitor_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -73,5 +73,12 @@ void main() {
true,
);
});

test('hasGeneratedEquals understands BuilderOptions', () {
expect(
hasGeneratedEquals(BuilderOptions({'hasEqualsAndHashCode': true}), null),
true,
);
});
});
}
3 changes: 3 additions & 0 deletions mobx_codegen/test/store_with_custom_context.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit e5e72e5

Please sign in to comment.