avoid_chain_first_swallowing_failure
This rule flags chainFirst on an Either, TaskEither or IOEither. fpdart’s chainFirst ignores the failure of the effect it runs.
Why use this rule
Section titled “Why use this rule”fpdart declares chainFirst like this:
TaskEither<L, R> chainFirst<C>(TaskEither<L, C> Function(R b) chain) => flatMap((b) => chain(b).map((c) => b).orElse((l) => TaskEither.right(b)));The trailing orElse turns a failing effect back into success, and the pipeline carries on with the original value. The name reads like “run this step too”, so it gets used for checks and writes. A permission check chained this way cannot reject anything, and a save chained this way can fail without anyone noticing.
flatMap with an inner map keeps the value and lets the failure through. Keep chainFirst only where ignoring the effect’s failure is what you want, such as a best-effort log line.
See also: fpdart: TaskEither.chainFirst
import 'package:fpdart/fpdart.dart';
TaskEither<String, int> loadUser(int id) => TaskEither.right(id);TaskEither<String, Unit> saveUser(int user) => TaskEither.left('disk full');
// Succeeds even though saveUser failed.TaskEither<String, int> save(int id) => loadUser(id).chainFirst(saveUser);import 'package:fpdart/fpdart.dart';
TaskEither<String, int> loadUser(int id) => TaskEither.right(id);TaskEither<String, Unit> saveUser(int user) => TaskEither.left('disk full');
// Fails with 'disk full'.TaskEither<String, int> save(int id) => loadUser(id).flatMap((user) => saveUser(user).map((_) => user));Quick fix
Section titled “Quick fix”The fix rewrites x.chainFirst(effect) to x.flatMap((value) => effect(value).map((_) => value)). It handles a tear-off (saveUser, repo.save) and a lambda with an expression body ((user) => saveUser(user)). A block-bodied lambda, explicit type arguments or a callback computed by an expression are left for you to rewrite.
The fix changes behaviour on purpose: after it, a failing effect fails the pipeline. It is applied one location at a time, never in bulk.
Known limitations
Section titled “Known limitations”Every chainFirst on these types is reported, including the ones where dropping the failure was intended. Suppress those with an // ignore: comment that says why.
Files under test/ are skipped by default. Set ignore_tests: false to report them too.
Options
Section titled “Options”many_lints: rules: avoid_chain_first_swallowing_failure: ignore_tests: falserules: avoid_chain_first_swallowing_failure: ignore_tests: false| Option | Type | Default | Description |
|---|---|---|---|
ignore_tests |
bool | true |
Skip files under test/ |
Configuration
Section titled “Configuration”This rule is in the pedantic preset. The “Convert to chainFirst” assist offers chainFirst deliberately, so a codebase may have chosen it. Turn it on explicitly:
rules: avoid_chain_first_swallowing_failure: trueTo keep the rule on but skip certain paths, use per-rule exclude.
Related rules
Section titled “Related rules”avoid_get_or_else_swallowing_failure— getOrElse is handed the failure; ignoring it should be a visible decision.prefer_chain_either— chainEither lifts a synchronous Either step for you.avoid_throw_in_fp_callback— A throw inside an fpdart callback escapes the error channel the pipeline is built to carry.prefer_and_then— andThen says that the previous value is not used.