1// Copyright 2014 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5import 'package:analyzer/dart/analysis/results.dart';
6import 'package:analyzer/dart/ast/ast.dart';
7import 'package:analyzer/dart/ast/visitor.dart';
8import 'package:analyzer/dart/element/type.dart';
9
10import '../utils.dart';
11import 'analyze.dart';
12
13/// Don't use Future.catchError or Future.onError.
14///
15/// See https://github.com/flutter/flutter/pull/130662 for more context.
16///
17/// **BAD:**
18///
19/// ```dart
20/// Future<Object?> doSomething() {
21/// return doSomethingAsync().catchError((_) => null);
22/// }
23///
24/// Future<Object?> doSomethingAsync() {
25/// return Future<Object>.error(Exception('error'));
26/// }
27/// ```
28///
29/// **GOOD:**
30///
31/// ```dart
32/// Future<Object?> doSomething() {
33/// return doSomethingAsync().then(
34/// (Object? obj) => obj,
35/// onError: (_) => null,
36/// );
37/// }
38///
39/// Future<Object?> doSomethingAsync() {
40/// return Future<Object>.error(Exception('error'));
41/// }
42/// ```
43class AvoidFutureCatchError extends AnalyzeRule {
44 final Map<ResolvedUnitResult, List<AstNode>> _errors = <ResolvedUnitResult, List<AstNode>>{};
45
46 @override
47 void applyTo(ResolvedUnitResult unit) {
48 final _Visitor visitor = _Visitor();
49 unit.unit.visitChildren(visitor);
50 if (visitor._offendingNodes.isNotEmpty) {
51 _errors.putIfAbsent(unit, () => <AstNode>[]).addAll(visitor._offendingNodes);
52 }
53 }
54
55 @override
56 void reportViolations(String workingDirectory) {
57 if (_errors.isEmpty) {
58 return;
59 }
60
61 foundError(<String>[
62 for (final MapEntry<ResolvedUnitResult, List<AstNode>> entry in _errors.entries)
63 for (final AstNode node in entry.value)
64 '${locationInFile(entry.key, node, workingDirectory)}: ${node.parent}',
65 '\n${bold}Future.catchError and Future.onError are not type safe--instead use Future.then: https://github.com/dart-lang/sdk/issues/51248$reset',
66 ]);
67 }
68
69 @override
70 String toString() => 'Avoid "Future.catchError" and "Future.onError"';
71}
72
73class _Visitor extends RecursiveAstVisitor<void> {
74 _Visitor();
75
76 final List<AstNode> _offendingNodes = <AstNode>[];
77
78 @override
79 void visitMethodInvocation(MethodInvocation node) {
80 if (node case MethodInvocation(
81 methodName: SimpleIdentifier(name: 'onError' || 'catchError'),
82 realTarget: Expression(staticType: DartType(isDartAsyncFuture: true)),
83 )) {
84 _offendingNodes.add(node);
85 }
86 }
87}
88