diff --git a/packages/leancode_lint/.fvmrc b/packages/leancode_lint/.fvmrc
index d80cf577..457360f8 100644
--- a/packages/leancode_lint/.fvmrc
+++ b/packages/leancode_lint/.fvmrc
@@ -1,3 +1,3 @@
{
- "flutter": "3.41.9"
+ "flutter": "3.44.0"
}
\ No newline at end of file
diff --git a/packages/leancode_lint/CHANGELOG.md b/packages/leancode_lint/CHANGELOG.md
index 7ef3966d..66f8f0e3 100644
--- a/packages/leancode_lint/CHANGELOG.md
+++ b/packages/leancode_lint/CHANGELOG.md
@@ -1,6 +1,7 @@
# 24.0.0
-- Add new custom lint:
+- Add new custom lints:
+ - [`avoid_catch_error`](https://github.com/leancodepl/flutter_corelibrary/tree/master/packages/leancode_lint#avoid_catch_error)
- [`missing_equatable_props`](https://github.com/leancodepl/flutter_corelibrary/tree/master/packages/leancode_lint#missing_equatable_props)
# 23.0.0
diff --git a/packages/leancode_lint/README.md b/packages/leancode_lint/README.md
index 10e6ca4c..38d4d644 100644
--- a/packages/leancode_lint/README.md
+++ b/packages/leancode_lint/README.md
@@ -140,6 +140,44 @@ None.
+
+avoid_catch_error
+
+### `avoid_catch_error`
+
+**AVOID** using `Future.catchError`.
+
+Prefer `async`/`await` with `try`/`catch`, which keeps error handling in normal
+control flow and preserves stack traces more predictably.
+
+**BAD:**
+
+```dart
+Future load() {
+ return repository.load().catchError((err, st) {
+ logger.error(err, st);
+ });
+}
+```
+
+**GOOD:**
+
+```dart
+Future load() async {
+ try {
+ await repository.load();
+ } catch (err, st) {
+ logger.error(err, st);
+ }
+}
+```
+
+#### Configuration
+
+None.
+
+
+
avoid_conditional_hooks
diff --git a/packages/leancode_lint/lib/plugin.dart b/packages/leancode_lint/lib/plugin.dart
index 8c800a74..6478b277 100644
--- a/packages/leancode_lint/lib/plugin.dart
+++ b/packages/leancode_lint/lib/plugin.dart
@@ -5,6 +5,7 @@ import 'package:leancode_lint/src/assists/convert_iterable_map_to_collection_for
import 'package:leancode_lint/src/assists/convert_positional_to_named_formal.dart';
import 'package:leancode_lint/src/assists/convert_record_into_nominal_type.dart';
import 'package:leancode_lint/src/lints/add_cubit_suffix_for_cubits.dart';
+import 'package:leancode_lint/src/lints/avoid_catch_error.dart';
import 'package:leancode_lint/src/lints/avoid_conditional_hooks.dart';
import 'package:leancode_lint/src/lints/avoid_single_child_in_multi_child_widget.dart';
import 'package:leancode_lint/src/lints/bloc_related_class_naming.dart';
@@ -60,6 +61,7 @@ final class LeanCodeLintPlugin extends Plugin {
..registerWarningRule(
CatchParameterNames(config: config.catchParameterNames),
)
+ ..registerWarningRule(AvoidCatchError())
..registerWarningRule(AvoidConditionalHooks())
..registerWarningRule(HookWidgetDoesNotUseHooks())
..registerFixForRule(
diff --git a/packages/leancode_lint/lib/src/lints/avoid_catch_error.dart b/packages/leancode_lint/lib/src/lints/avoid_catch_error.dart
new file mode 100644
index 00000000..f5fb03a6
--- /dev/null
+++ b/packages/leancode_lint/lib/src/lints/avoid_catch_error.dart
@@ -0,0 +1,54 @@
+import 'package:analyzer/analysis_rule/analysis_rule.dart';
+import 'package:analyzer/analysis_rule/rule_context.dart';
+import 'package:analyzer/analysis_rule/rule_visitor_registry.dart';
+import 'package:analyzer/dart/ast/ast.dart';
+import 'package:analyzer/dart/ast/visitor.dart';
+import 'package:analyzer/dart/element/element.dart';
+import 'package:analyzer/dart/element/type.dart';
+import 'package:analyzer/error/error.dart';
+
+class AvoidCatchError extends AnalysisRule {
+ AvoidCatchError()
+ : super(name: code.lowerCaseName, description: code.problemMessage);
+
+ static const code = LintCode(
+ 'avoid_catch_error',
+ 'Avoid using Future.catchError.',
+ correctionMessage: 'Use async/await with try/catch instead.',
+ severity: .WARNING,
+ );
+
+ @override
+ LintCode get diagnosticCode => code;
+
+ @override
+ void registerNodeProcessors(
+ RuleVisitorRegistry registry,
+ RuleContext context,
+ ) {
+ registry.addMethodInvocation(this, _Visitor(this, context));
+ }
+}
+
+class _Visitor extends SimpleAstVisitor {
+ _Visitor(this.rule, this.context);
+
+ final AnalysisRule rule;
+ final RuleContext context;
+
+ @override
+ void visitMethodInvocation(MethodInvocation node) {
+ if (node.methodName case SimpleIdentifier(
+ name: 'catchError',
+ element: Element(
+ baseElement: MethodElement(
+ enclosingElement: InterfaceElement(
+ thisType: InterfaceType(isDartAsyncFuture: true),
+ ),
+ ),
+ ),
+ )) {
+ rule.reportAtNode(node.methodName);
+ }
+ }
+}
diff --git a/packages/leancode_lint/test/test_cases/avoid_catch_error_test.dart b/packages/leancode_lint/test/test_cases/avoid_catch_error_test.dart
new file mode 100644
index 00000000..22ff8a3d
--- /dev/null
+++ b/packages/leancode_lint/test/test_cases/avoid_catch_error_test.dart
@@ -0,0 +1,95 @@
+import 'package:analyzer_testing/analysis_rule/analysis_rule.dart';
+import 'package:leancode_lint/src/lints/avoid_catch_error.dart';
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import '../assert_ranges.dart';
+
+void main() {
+ defineReflectiveSuite(() {
+ defineReflectiveTests(AvoidCatchErrorTest);
+ });
+}
+
+@reflectiveTest
+class AvoidCatchErrorTest extends AnalysisRuleTest {
+ @override
+ void setUp() {
+ rule = AvoidCatchError();
+
+ super.setUp();
+ }
+
+ Future test_future_catch_error_is_marked() async {
+ await assertDiagnosticsInRanges('''
+import 'dart:async';
+
+Future doSomething() async {}
+
+Future test(Future future) {
+ return future.[!catchError!]((Object err, StackTrace st) {
+ return doSomething();
+ });
+}
+''');
+ }
+
+ Future test_future_catch_error_cascade_is_marked() async {
+ await assertDiagnosticsInRanges('''
+import 'dart:async';
+
+Future test(Future future) async {
+ future..[!catchError!]((Object err, StackTrace st) {});
+}
+''');
+ }
+
+ Future test_catch_error_does_not_catch_synchronous_throw() async {
+ await assertDiagnosticsInRanges(r'''
+import 'dart:async';
+
+Future syncThrowing() {
+ throw 'sync';
+}
+
+Future asyncThrowing() async {
+ throw 'async';
+}
+
+Future main() async {
+ try {
+ await asyncThrowing()./*[0*/catchError/*0]*/((e) => print('Caught $e'));
+ await syncThrowing()./*[1*/catchError/*1]*/((e) => print('Caught $e'));
+ } catch (e) {
+ print('Uncaught $e');
+ }
+}
+''');
+ }
+
+ Future test_try_catch_is_not_marked() async {
+ await assertNoDiagnostics('''
+import 'dart:async';
+
+Future test(Future future) async {
+ try {
+ await future;
+ } catch (err, st) {
+ print(st);
+ throw err;
+ }
+}
+''');
+ }
+
+ Future test_custom_method_named_catch_error_is_not_marked() async {
+ await assertNoDiagnostics('''
+class HasCatchError {
+ void catchError(void Function() onError) {}
+}
+
+void test(HasCatchError value) {
+ value.catchError(() {});
+}
+''');
+ }
+}