From 4172eb0c810d533b5819e8c80a485de418beae79 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 23 Sep 2024 17:17:52 +0200 Subject: [PATCH] Create rule S7083: For elements should be preferred to Map.fromIterable (prefer_for_elements_to_map_fromIterable) Co-authored-by: Antonio Aversa --- rules/S7083/dart/metadata.json | 24 ++++++++ rules/S7083/dart/rule.adoc | 106 +++++++++++++++++++++++++++++++++ rules/S7083/metadata.json | 2 + 3 files changed, 132 insertions(+) create mode 100644 rules/S7083/dart/metadata.json create mode 100644 rules/S7083/dart/rule.adoc create mode 100644 rules/S7083/metadata.json diff --git a/rules/S7083/dart/metadata.json b/rules/S7083/dart/metadata.json new file mode 100644 index 00000000000..b637137cb51 --- /dev/null +++ b/rules/S7083/dart/metadata.json @@ -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" + } +} diff --git a/rules/S7083/dart/rule.adoc b/rules/S7083/dart/rule.adoc new file mode 100644 index 00000000000..681a2ec3c1c --- /dev/null +++ b/rules/S7083/dart/rule.adoc @@ -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.fromIterable( // Explicit Map 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 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` 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.fromIterable(l1, key: (item) => ...); // OK +Map.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.fromIterable(l1, key: key, value: value); +---- + +== How to fix it + +In order to replace `Map.fromIterable` with `for` elements: + +* replace the `Map.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.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 class] +* Dart API Reference - https://api.dart.dev/stable/dart-core/Iterable-class.html[Iterable class] +* Dart API Reference - https://api.dart.dev/stable/dart-core/Map/Map.fromIterable.html[Map.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.fromIterable([1, 2, 3], key: (v) => 'The value is $v', value: (v) => v);`. + +endif::env-github,rspecator-view[] diff --git a/rules/S7083/metadata.json b/rules/S7083/metadata.json new file mode 100644 index 00000000000..2c63c085104 --- /dev/null +++ b/rules/S7083/metadata.json @@ -0,0 +1,2 @@ +{ +}