prefer_safe_collection_access
This rule flags .first, .last and .single on an Iterable inside an fpdart pipeline, where fpdart offers head, lastOption and singleOption instead.
Why use this rule
Section titled “Why use this rule”list.first throws StateError on an empty list. list.head returns None.
Inside a pipeline that difference matters twice over. The throw escapes the error channel the pipeline exists to carry, so the StateError surfaces past every fold and match the caller wrote — the one place the code promised every failure was handled. And the empty case, which the type system was about to make explicit, silently becomes a runtime crash instead.
By default only expressions inside an fpdart pipeline are reported, since that is where the total accessor composes. Set report_outside_pipelines: true to apply the same rule to the whole file.
See also: fpdart: iterable extensions
TaskEither<Failure, Player> firstPlayer(List<Player> players) => TaskEither.of(players.first);TaskEither<Failure, Player> firstPlayer(List<Player> players) => players.head.toEither(() => const NoPlayersFailure()).toTaskEither();Since 1.2.0 the total accessors go further than head: singleOption, elementAtOption(i), singleWhereOption and lastWhereOption cover the rest of the throwing family, and map.lookup(key) replaces map[key]!.
Quick fix
Section titled “Quick fix”A quick fix swaps the accessor and adds the fpdart import when it is missing.
Unlike the other fixes in this family it is one at a time, not file-wide, because it changes the expression’s type: first yields T, head yields Option<T>. Every use site needs a follow-up — usually getOrElse, toEither or a match — and applying the swap in bulk would leave a file of type errors to untangle. That type change is the point of the rule, not a side effect: it is what forces the empty case to be handled.
An accessor added through accessors: has no known counterpart, so it warns without offering a fix.
Known limitations
Section titled “Known limitations”The receiver must resolve to an Iterable, so a user-defined first on an unrelated class is never flagged.
The “inside a pipeline” test is deliberately generous — anywhere an enclosing expression or the enclosing member’s return type is an fpdart type counts. A narrower test would miss a helper whose result feeds a pipeline one call away.
Options
Section titled “Options”many_lints: rules: prefer_safe_collection_access: report_outside_pipelines: true additional_accessors: - firstOrThrowrules: prefer_safe_collection_access: report_outside_pipelines: true additional_accessors: - firstOrThrow| Option | Type | Default | Description |
|---|---|---|---|
report_outside_pipelines |
bool | false |
Report every throwing accessor in the file, not only those inside an fpdart pipeline |
accessors |
list of strings | first, last, single |
Replace the set of accessor names that are checked |
additional_accessors |
list of strings | [] |
Extend the set instead of replacing it |
Configuration
Section titled “Configuration”This rule is in the recommended preset, so it is on with
preset: recommended or preset: opinionated. Add it to preset: core with
prefer_safe_collection_access: true.
To turn it off:
rules: prefer_safe_collection_access: falseTo keep the rule on but skip certain paths, use per-rule exclude.
Related rules
Section titled “Related rules”avoid_unsafe_collection_methods— Check for emptiness before using first, last, single or reduce.avoid_accessing_collections_by_constant_index— Avoid accessing a collection by a constant index inside a loop.avoid_untyped_safe_cast— safeCast without explicit type arguments infers dynamic and always succeeds.avoid_ad_hoc_left_type— A pipeline only composes when every step shares one error type.