Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Added automatic generation of == and hashCode method with @StoreConfig #972

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@
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 @@
}

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.