Skip to content

Commit

Permalink
Create rule S7083: For elements should be preferred to Map.fromIterab…
Browse files Browse the repository at this point in the history
…le (prefer_for_elements_to_map_fromIterable)

Co-authored-by: Antonio Aversa <[email protected]>
  • Loading branch information
github-actions[bot] and antonioaversa authored Sep 23, 2024
1 parent b9bf820 commit 4172eb0
Show file tree
Hide file tree
Showing 3 changed files with 132 additions and 0 deletions.
24 changes: 24 additions & 0 deletions rules/S7083/dart/metadata.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"title": "For elements should be preferred to Map.fromIterable",
"type": "CODE_SMELL",
"status": "ready",
"remediation": {
"func": "Constant\/Issue",
"constantCost": "5min"
},
"tags": [
"performance"
],
"defaultSeverity": "Major",
"ruleSpecification": "RSPEC-7083",
"sqKey": "S7083",
"scope": "All",
"defaultQualityProfiles": ["Sonar way"],
"quickfix": "unknown",
"code": {
"impacts": {
"MAINTAINABILITY": "HIGH"
},
"attribute": "EFFICIENT"
}
}
106 changes: 106 additions & 0 deletions rules/S7083/dart/rule.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
https://dart.dev/language/collections#control-flow-operators[`for` elements] should be preferred to https://api.dart.dev/stable/dart-core/Map/Map.fromIterable.html[`fromIterable`] when building a https://api.dart.dev/stable/dart-core/Map/Map.html[`Map`] from an https://api.dart.dev/stable/dart-core/Iterable-class.html[`Iterable`].

== Why is this an issue?

While it is possible to build a `Map` from an `Iterable` using `Map.fromIterable`, writing an explicit `for` loop with `for` elements should be used instead.

The main reason to prefer `for` elements is that it can be optimized by the Dart compiler for performance, depending on the iterable being visited.

Moreover, `for` elements are more idiomatic and generally easier to understand than `Map.fromIterable`, which requires explicit map type upfront, as well as the input iterable and two different lambda functions as parameters.

[source,dart]
----
Map<String, int>.fromIterable( // Explicit Map<String, int> type required
inputIterable,
key: (item) => ..., // Key generator
value: (item) => ..., // Value generator
);
----

On the other hand, `for` elements are more flexible, look like normal `for` loops, and support better inference of the resulting `Map` type.

[source,dart]
----
{
for (final item in inputIterable)
'The value is $v': v // Implicit Map<String, int> type inference
}
----

=== Exceptions

The rule only applies to the `fromIterable` factory method of `Map`. It does not apply to other `fromIterable` methods, such as the one from `LinkedHashMap<T, U>` in `dart:collection`.

Moreover, it only applies when all the arguments of the `Map.fromIterable` call are provided. If either the `key` or the `value` parameter is omitted, the rule does not apply.

[source,dart]
----
Map<int, int>.fromIterable(l1, key: (item) => ...); // OK
Map<int, int>.fromIterable(l1, value: (item) => ...); // OK
----

The rule also does not apply when the `key` and `value` parameters are not inline function expressions, but variables defined elesewhere in the code.

[source,dart]
----
final key = (item) => ...;
final value = (item) => ...;
Map<int, int>.fromIterable(l1, key: key, value: value);
----

== How to fix it

In order to replace `Map.fromIterable` with `for` elements:

* replace the `Map<String, int>.fromIterable` call with a `for` loop over the input iterable, that is the first argument of the call
* define the body of the `for` elements as a `key: value` pair, where `key` is the body of the key generator lambda, and `value` is body of the value generator lambda

=== Code examples

==== Noncompliant code example

[source,dart,diff-id=1,diff-type=noncompliant]
----
Map<String, int>.fromIterable(
[1, 2, 3],
key: (v) => 'The value is $v',
value: (v) => v,
);
----

==== Compliant solution

[source,dart,diff-id=1,diff-type=compliant]
----
{
for (final v in [1, 2, 3])
'The value is $v': v
}
----

== Resources

=== Documentation

* Dart Docs - https://dart.dev/tools/linter-rules/prefer_for_elements_to_map_fromIterable[Linter rule - prefer_for_elements_to_map_fromIterable]
* Dart Docs - https://dart.dev/language/collections#control-flow-operators[Language - Control-flow operators]
* Dart API Reference - https://api.dart.dev/stable/dart-core/Map/Map.html[Map<K, V> class]
* Dart API Reference - https://api.dart.dev/stable/dart-core/Iterable-class.html[Iterable<E> class]
* Dart API Reference - https://api.dart.dev/stable/dart-core/Map/Map.fromIterable.html[Map<K, V>.fromIterable constructor]


ifdef::env-github,rspecator-view[]

'''
== Implementation Specification
(visible only on this page)

=== Message

Use 'for' elements when building maps from iterables.

=== Highlighting

The entire `Map.fromIterable` call, including parentheses and arguments: e.g. `Map<String, int>.fromIterable([1, 2, 3], key: (v) => 'The value is $v', value: (v) => v);`.

endif::env-github,rspecator-view[]
2 changes: 2 additions & 0 deletions rules/S7083/metadata.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
{
}

0 comments on commit 4172eb0

Please sign in to comment.