From d0e13170e3f9fbdf8e4d6a5b4fd45a3ffd76fab8 Mon Sep 17 00:00:00 2001 From: Remi Rousselet Date: Thu, 5 Jan 2023 13:30:35 +0100 Subject: [PATCH 1/7] Remove dynamic invocation Since the matchers are using FeatureMatcher, the typedMatches method receives a typed object instead of dynamic --- lib/src/string_matchers.dart | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/src/string_matchers.dart b/lib/src/string_matchers.dart index 8b2f95a..198ed7e 100644 --- a/lib/src/string_matchers.dart +++ b/lib/src/string_matchers.dart @@ -80,7 +80,7 @@ class _StringStartsWith extends FeatureMatcher { const _StringStartsWith(this._prefix); @override - bool typedMatches(dynamic item, Map matchState) => item.startsWith(_prefix); + bool typedMatches(String item, Map matchState) => item.startsWith(_prefix); @override Description describe(Description description) => @@ -97,7 +97,7 @@ class _StringEndsWith extends FeatureMatcher { const _StringEndsWith(this._suffix); @override - bool typedMatches(dynamic item, Map matchState) => item.endsWith(_suffix); + bool typedMatches(String item, Map matchState) => item.endsWith(_suffix); @override Description describe(Description description) => @@ -119,7 +119,7 @@ class _StringContainsInOrder extends FeatureMatcher { const _StringContainsInOrder(this._substrings); @override - bool typedMatches(dynamic item, Map matchState) { + bool typedMatches(String item, Map matchState) { var fromIndex = 0; for (var s in _substrings) { var index = item.indexOf(s, fromIndex); @@ -152,7 +152,7 @@ class _MatchesRegExp extends FeatureMatcher { : throw ArgumentError('matches requires a regexp or string'); @override - bool typedMatches(dynamic item, Map matchState) => _regexp.hasMatch(item); + bool typedMatches(String item, Map matchState) => _regexp.hasMatch(item); @override Description describe(Description description) => From 2fa0d2ef3c00540bd819aef408046e24e317b61f Mon Sep 17 00:00:00 2001 From: Remi Rousselet Date: Thu, 5 Jan 2023 13:32:08 +0100 Subject: [PATCH 2/7] Update num matchers --- lib/src/numeric_matchers.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/numeric_matchers.dart b/lib/src/numeric_matchers.dart index 5193d30..f201622 100644 --- a/lib/src/numeric_matchers.dart +++ b/lib/src/numeric_matchers.dart @@ -18,7 +18,7 @@ class _IsCloseTo extends FeatureMatcher { const _IsCloseTo(this._value, this._delta); @override - bool typedMatches(dynamic item, Map matchState) { + bool typedMatches(num item, Map matchState) { var diff = item - _value; if (diff < 0) diff = -diff; return diff <= _delta; @@ -67,7 +67,7 @@ class _InRange extends FeatureMatcher { this._low, this._high, this._lowMatchValue, this._highMatchValue); @override - bool typedMatches(dynamic value, Map matchState) { + bool typedMatches(num value, Map matchState) { if (value < _low || value > _high) { return false; } From df9e5af879b2832e60fd4981d98004b36ef7f2b1 Mon Sep 17 00:00:00 2001 From: Nate Bosch Date: Fri, 10 Mar 2023 23:05:42 +0000 Subject: [PATCH 3/7] Enable lint avoid_dynamic_calls Fix more cases, and ignore the intentional dynamic calls. Add changelog --- CHANGELOG.md | 2 ++ analysis_options.yaml | 1 + lib/src/core_matchers.dart | 14 ++++++++++---- lib/src/expect/throws_matcher.dart | 4 ++-- lib/src/iterable_matchers.dart | 2 +- lib/src/map_matchers.dart | 10 +++++++++- lib/src/numeric_matchers.dart | 4 ++-- lib/src/operator_matchers.dart | 2 +- lib/src/order_matchers.dart | 2 ++ lib/src/util.dart | 2 +- test/expect_async_test.dart | 8 ++++---- test/matcher/throws_type_test.dart | 1 + 12 files changed, 36 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 05c3e78..720baf3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ * Add `package:matcher/expect.dart` library. Copies the implementation of `expect` and the asynchronous matchers from `package:test`. +* Add some explicit casts to remove some dynamic calls. There remain a large + number of intentionally dynamic calls. ## 0.12.14 diff --git a/analysis_options.yaml b/analysis_options.yaml index 217fb64..da2bb77 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -3,6 +3,7 @@ include: package:lints/recommended.yaml linter: rules: - always_declare_return_types + - avoid_dynamic_calls - avoid_private_typedef_functions - avoid_returning_null - avoid_returning_null_for_future diff --git a/lib/src/core_matchers.dart b/lib/src/core_matchers.dart index 45d1f27..5b1c518 100644 --- a/lib/src/core_matchers.dart +++ b/lib/src/core_matchers.dart @@ -14,7 +14,9 @@ class _Empty extends Matcher { const _Empty(); @override - bool matches(Object? item, Map matchState) => (item as dynamic).isEmpty; + bool matches(Object? item, Map matchState) => + // ignore: avoid_dynamic_calls + (item as dynamic).isEmpty; @override Description describe(Description description) => description.add('empty'); @@ -27,7 +29,9 @@ class _NotEmpty extends Matcher { const _NotEmpty(); @override - bool matches(Object? item, Map matchState) => (item as dynamic).isNotEmpty; + bool matches(Object? item, Map matchState) => + // ignore: avoid_dynamic_calls + (item as dynamic).isNotEmpty; @override Description describe(Description description) => description.add('non-empty'); @@ -144,11 +148,11 @@ class isInstanceOf extends TypeMatcher { /// a wrapper will have to be created. const Matcher returnsNormally = _ReturnsNormally(); -class _ReturnsNormally extends FeatureMatcher { +class _ReturnsNormally extends FeatureMatcher { const _ReturnsNormally(); @override - bool typedMatches(Function f, Map matchState) { + bool typedMatches(Function() f, Map matchState) { try { f(); return true; @@ -190,6 +194,7 @@ class _HasLength extends Matcher { @override bool matches(Object? item, Map matchState) { try { + // ignore: avoid_dynamic_calls final length = (item as dynamic).length; return _matcher.matches(length, matchState); } catch (e) { @@ -205,6 +210,7 @@ class _HasLength extends Matcher { Description describeMismatch(Object? item, Description mismatchDescription, Map matchState, bool verbose) { try { + // ignore: avoid_dynamic_calls final length = (item as dynamic).length; return mismatchDescription.add('has length of ').addDescriptionOf(length); } catch (e) { diff --git a/lib/src/expect/throws_matcher.dart b/lib/src/expect/throws_matcher.dart index 0ee3144..a2aba50 100644 --- a/lib/src/expect/throws_matcher.dart +++ b/lib/src/expect/throws_matcher.dart @@ -73,7 +73,7 @@ class Throws extends AsyncMatcher { // function. @override dynamic /*FutureOr*/ matchAsync(Object? item) { - if (item is! Function && item is! Future) { + if (item is! Function() && item is! Future) { return 'was not a Function or Future'; } @@ -82,7 +82,7 @@ class Throws extends AsyncMatcher { } try { - item as Function; + item as Function(); var value = item(); if (value is Future) { return _matchFuture(value, 'returned a Future that emitted '); diff --git a/lib/src/iterable_matchers.dart b/lib/src/iterable_matchers.dart index 918650a..7badb52 100644 --- a/lib/src/iterable_matchers.dart +++ b/lib/src/iterable_matchers.dart @@ -206,7 +206,7 @@ class _UnorderedMatches extends _IterableMatcher { @override Description describeTypedMismatch(dynamic item, Description mismatchDescription, Map matchState, bool verbose) => - mismatchDescription.add(_test(item.toList())!); + mismatchDescription.add(_test((item as Iterable).toList())!); /// Returns `true` if the value at [valueIndex] can be paired with some /// unmatched matcher and updates the state of [matched]. diff --git a/lib/src/map_matchers.dart b/lib/src/map_matchers.dart index 97be1ea..16b74ae 100644 --- a/lib/src/map_matchers.dart +++ b/lib/src/map_matchers.dart @@ -15,6 +15,7 @@ class _ContainsValue extends Matcher { @override bool matches(Object? item, Map matchState) => + // ignore: avoid_dynamic_calls (item as dynamic).containsValue(_value); @override Description describe(Description description) => @@ -34,7 +35,9 @@ class _ContainsMapping extends Matcher { @override bool matches(Object? item, Map matchState) => + // ignore: avoid_dynamic_calls (item as dynamic).containsKey(_key) && + // ignore: avoid_dynamic_calls _valueMatcher.matches(item[_key], matchState); @override @@ -49,6 +52,7 @@ class _ContainsMapping extends Matcher { @override Description describeMismatch(Object? item, Description mismatchDescription, Map matchState, bool verbose) { + // ignore: avoid_dynamic_calls if (!(item as dynamic).containsKey(_key)) { return mismatchDescription .add(" doesn't contain key ") @@ -59,7 +63,11 @@ class _ContainsMapping extends Matcher { .addDescriptionOf(_key) .add(' but with value '); _valueMatcher.describeMismatch( - item[_key], mismatchDescription, matchState, verbose); + // ignore: avoid_dynamic_calls + item[_key], + mismatchDescription, + matchState, + verbose); return mismatchDescription; } } diff --git a/lib/src/numeric_matchers.dart b/lib/src/numeric_matchers.dart index f201622..a798160 100644 --- a/lib/src/numeric_matchers.dart +++ b/lib/src/numeric_matchers.dart @@ -32,8 +32,8 @@ class _IsCloseTo extends FeatureMatcher { .addDescriptionOf(_value); @override - Description describeTypedMismatch(dynamic item, - Description mismatchDescription, Map matchState, bool verbose) { + Description describeTypedMismatch( + num item, Description mismatchDescription, Map matchState, bool verbose) { var diff = item - _value; if (diff < 0) diff = -diff; return mismatchDescription.add(' differs by ').addDescriptionOf(diff); diff --git a/lib/src/operator_matchers.dart b/lib/src/operator_matchers.dart index ced25fc..15e50ff 100644 --- a/lib/src/operator_matchers.dart +++ b/lib/src/operator_matchers.dart @@ -57,7 +57,7 @@ class _AllOf extends Matcher { @override Description describeMismatch(dynamic item, Description mismatchDescription, Map matchState, bool verbose) { - var matcher = matchState['matcher']; + var matcher = matchState['matcher'] as Matcher; matcher.describeMismatch( item, mismatchDescription, matchState['state'], verbose); return mismatchDescription; diff --git a/lib/src/order_matchers.dart b/lib/src/order_matchers.dart index 1146f6a..747e39a 100644 --- a/lib/src/order_matchers.dart +++ b/lib/src/order_matchers.dart @@ -78,8 +78,10 @@ class _OrderingMatcher extends Matcher { bool matches(Object? item, Map matchState) { if (item == _value) { return _equalValue; + // ignore: avoid_dynamic_calls } else if ((item as dynamic) < _value) { return _lessThanValue; + // ignore: avoid_dynamic_calls } else if (item > _value) { return _greaterThanValue; } else { diff --git a/lib/src/util.dart b/lib/src/util.dart index af0ba2c..c9b9454 100644 --- a/lib/src/util.dart +++ b/lib/src/util.dart @@ -43,7 +43,7 @@ Matcher wrapMatcher(Object? valueOrMatcher) { } else if (valueOrMatcher is bool Function(Never)) { // unary predicate, but expects a specific type // so wrap it. - // ignore: unnecessary_lambdas + // ignore: unnecessary_lambdas, avoid_dynamic_calls return predicate((a) => (valueOrMatcher as dynamic)(a)); } else { return equals(valueOrMatcher); diff --git a/test/expect_async_test.dart b/test/expect_async_test.dart index 991ca28..b3c24f7 100644 --- a/test/expect_async_test.dart +++ b/test/expect_async_test.dart @@ -336,7 +336,7 @@ void main() { test('works with no arguments', () async { var callbackRun = false; var monitor = await TestCaseMonitor.run(() { - // ignore: deprecated_member_use + // ignore: deprecated_member_use, avoid_dynamic_calls expectAsync(() { callbackRun = true; })(); @@ -349,7 +349,7 @@ void main() { test('works with dynamic arguments', () async { var callbackRun = false; var monitor = await TestCaseMonitor.run(() { - // ignore: deprecated_member_use + // ignore: deprecated_member_use, avoid_dynamic_calls expectAsync((arg1, arg2) { callbackRun = true; })(1, 2); @@ -362,7 +362,7 @@ void main() { test('works with non-nullable arguments', () async { var callbackRun = false; var monitor = await TestCaseMonitor.run(() { - // ignore: deprecated_member_use + // ignore: deprecated_member_use, avoid_dynamic_calls expectAsync((int arg1, int arg2) { callbackRun = true; })(1, 2); @@ -375,7 +375,7 @@ void main() { test('works with 6 arguments', () async { var callbackRun = false; var monitor = await TestCaseMonitor.run(() { - // ignore: deprecated_member_use + // ignore: deprecated_member_use, avoid_dynamic_calls expectAsync((arg1, arg2, arg3, arg4, arg5, arg6) { callbackRun = true; })(1, 2, 3, 4, 5, 6); diff --git a/test/matcher/throws_type_test.dart b/test/matcher/throws_type_test.dart index 1c3d193..25a870c 100644 --- a/test/matcher/throws_type_test.dart +++ b/test/matcher/throws_type_test.dart @@ -95,6 +95,7 @@ void main() { group('[throwsNoSuchMethodError]', () { test('passes when a NoSuchMethodError is thrown', () { expect(() { + // ignore: avoid_dynamic_calls (1 as dynamic).notAMethodOnInt(); }, throwsNoSuchMethodError); }); From f277c91ead7c3b925ded75ac4542d182f10b9343 Mon Sep 17 00:00:00 2001 From: Nate Bosch Date: Thu, 16 Mar 2023 22:01:42 +0000 Subject: [PATCH 4/7] Change in type check output --- test/core_matchers_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/core_matchers_test.dart b/test/core_matchers_test.dart index 04fc8b3..7c14dc4 100644 --- a/test/core_matchers_test.dart +++ b/test/core_matchers_test.dart @@ -103,7 +103,7 @@ void main() { r' Actual: ' r' Which: threw StateError:')); shouldFail('not a function', returnsNormally, - contains('not an ')); + contains('not an dynamic\'>')); }); test('hasLength', () { From dd59642b86dd2e60419329bd5d5a2757d643afa9 Mon Sep 17 00:00:00 2001 From: Nate Bosch Date: Tue, 21 Nov 2023 23:48:39 +0000 Subject: [PATCH 5/7] Move changelog entry --- CHANGELOG.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 244f83d..e4af0a2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ ## 0.12.17-wip +* Add some explicit casts to remove some dynamic calls. There remain a large + number of intentionally dynamic calls. + ## 0.12.16 * Expand bounds on `test_api` dependency to allow the next breaking release @@ -9,8 +12,6 @@ * Add `package:matcher/expect.dart` library. Copies the implementation of `expect` and the asynchronous matchers from `package:test`. -* Add some explicit casts to remove some dynamic calls. There remain a large - number of intentionally dynamic calls. ## 0.12.14 From b3623925310e4bf026ca160cf90359b676402953 Mon Sep 17 00:00:00 2001 From: Nate Bosch Date: Wed, 22 Nov 2023 00:46:52 +0000 Subject: [PATCH 6/7] Retain a check against `Function` The refactoring started rejecting generic functions, a `Function()` is not a subtype of a `Function()`. --- lib/src/expect/throws_matcher.dart | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/src/expect/throws_matcher.dart b/lib/src/expect/throws_matcher.dart index a2aba50..96a8b6d 100644 --- a/lib/src/expect/throws_matcher.dart +++ b/lib/src/expect/throws_matcher.dart @@ -73,7 +73,7 @@ class Throws extends AsyncMatcher { // function. @override dynamic /*FutureOr*/ matchAsync(Object? item) { - if (item is! Function() && item is! Future) { + if (item is! Function && item is! Future) { return 'was not a Function or Future'; } @@ -82,7 +82,8 @@ class Throws extends AsyncMatcher { } try { - item as Function(); + item as Function; + // ignore: avoid_dynamic_calls var value = item(); if (value is Future) { return _matchFuture(value, 'returned a Future that emitted '); From 1fa085a107f1722057dccedbc63e402b5d4572a4 Mon Sep 17 00:00:00 2001 From: Nate Bosch Date: Wed, 22 Nov 2023 01:16:45 +0000 Subject: [PATCH 7/7] More places where Function() is too strict --- lib/src/core_matchers.dart | 5 +++-- test/core_matchers_test.dart | 3 ++- test/test_utils.dart | 1 + 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/src/core_matchers.dart b/lib/src/core_matchers.dart index cd86765..bb4eddf 100644 --- a/lib/src/core_matchers.dart +++ b/lib/src/core_matchers.dart @@ -148,12 +148,13 @@ class isInstanceOf extends TypeMatcher { /// a wrapper will have to be created. const Matcher returnsNormally = _ReturnsNormally(); -class _ReturnsNormally extends FeatureMatcher { +class _ReturnsNormally extends FeatureMatcher { const _ReturnsNormally(); @override - bool typedMatches(Function() f, Map matchState) { + bool typedMatches(Function f, Map matchState) { try { + // ignore: avoid_dynamic_calls f(); return true; } catch (e, s) { diff --git a/test/core_matchers_test.dart b/test/core_matchers_test.dart index 7c14dc4..3a12934 100644 --- a/test/core_matchers_test.dart +++ b/test/core_matchers_test.dart @@ -96,6 +96,7 @@ void main() { test('returnsNormally', () { shouldPass(doesNotThrow, returnsNormally); + shouldPass(doesNotThrowGeneric, returnsNormally); shouldFail( doesThrow, returnsNormally, @@ -103,7 +104,7 @@ void main() { r' Actual: ' r' Which: threw StateError:')); shouldFail('not a function', returnsNormally, - contains('not an dynamic\'>')); + contains('not an ')); }); test('hasLength', () { diff --git a/test/test_utils.dart b/test/test_utils.dart index 67b61a1..b4cc5f5 100644 --- a/test/test_utils.dart +++ b/test/test_utils.dart @@ -28,6 +28,7 @@ void shouldPass(Object? value, Matcher matcher) { } void doesNotThrow() {} +void doesNotThrowGeneric() {} void doesThrow() { throw StateError('X'); }