]> git.lizzy.rs Git - rust.git/blob - crates/ide/src/diagnostics/replace_filter_map_next_with_find_map.rs
internal: use cov-mark rather than bailing out diagnostic
[rust.git] / crates / ide / src / diagnostics / replace_filter_map_next_with_find_map.rs
1 use hir::{db::AstDatabase, InFile};
2 use ide_db::source_change::SourceChange;
3 use syntax::{
4     ast::{self, ArgListOwner},
5     AstNode, TextRange,
6 };
7 use text_edit::TextEdit;
8
9 use crate::{
10     diagnostics::{fix, Diagnostic, DiagnosticsContext},
11     Assist, Severity,
12 };
13
14 // Diagnostic: replace-filter-map-next-with-find-map
15 //
16 // This diagnostic is triggered when `.filter_map(..).next()` is used, rather than the more concise `.find_map(..)`.
17 pub(super) fn replace_filter_map_next_with_find_map(
18     ctx: &DiagnosticsContext<'_>,
19     d: &hir::ReplaceFilterMapNextWithFindMap,
20 ) -> Diagnostic {
21     Diagnostic::new(
22         "replace-filter-map-next-with-find-map",
23         "replace filter_map(..).next() with find_map(..)",
24         ctx.sema.diagnostics_display_range(InFile::new(d.file, d.next_expr.clone().into())).range,
25     )
26     .severity(Severity::WeakWarning)
27     .with_fixes(fixes(ctx, d))
28 }
29
30 fn fixes(
31     ctx: &DiagnosticsContext<'_>,
32     d: &hir::ReplaceFilterMapNextWithFindMap,
33 ) -> Option<Vec<Assist>> {
34     let root = ctx.sema.db.parse_or_expand(d.file)?;
35     let next_expr = d.next_expr.to_node(&root);
36     let next_call = ast::MethodCallExpr::cast(next_expr.syntax().clone())?;
37
38     let filter_map_call = ast::MethodCallExpr::cast(next_call.receiver()?.syntax().clone())?;
39     let filter_map_name_range = filter_map_call.name_ref()?.ident_token()?.text_range();
40     let filter_map_args = filter_map_call.arg_list()?;
41
42     let range_to_replace =
43         TextRange::new(filter_map_name_range.start(), next_expr.syntax().text_range().end());
44     let replacement = format!("find_map{}", filter_map_args.syntax().text());
45     let trigger_range = next_expr.syntax().text_range();
46
47     let edit = TextEdit::replace(range_to_replace, replacement);
48
49     let source_change = SourceChange::from_text_edit(d.file.original_file(ctx.sema.db), edit);
50
51     Some(vec![fix(
52         "replace_with_find_map",
53         "Replace filter_map(..).next() with find_map()",
54         source_change,
55         trigger_range,
56     )])
57 }
58
59 #[cfg(test)]
60 mod tests {
61     use crate::diagnostics::tests::check_fix;
62
63     // Register the required standard library types to make the tests work
64     #[track_caller]
65     fn check_diagnostics(ra_fixture: &str) {
66         let prefix = r#"
67 //- /main.rs crate:main deps:core
68 use core::iter::Iterator;
69 use core::option::Option::{self, Some, None};
70 "#;
71         let suffix = r#"
72 //- /core/lib.rs crate:core
73 pub mod option {
74     pub enum Option<T> { Some(T), None }
75 }
76 pub mod iter {
77     pub trait Iterator {
78         type Item;
79         fn filter_map<B, F>(self, f: F) -> FilterMap where F: FnMut(Self::Item) -> Option<B> { FilterMap }
80         fn next(&mut self) -> Option<Self::Item>;
81     }
82     pub struct FilterMap {}
83     impl Iterator for FilterMap {
84         type Item = i32;
85         fn next(&mut self) -> i32 { 7 }
86     }
87 }
88 "#;
89         crate::diagnostics::tests::check_diagnostics(&format!("{}{}{}", prefix, ra_fixture, suffix))
90     }
91
92     #[test]
93     fn replace_filter_map_next_with_find_map2() {
94         check_diagnostics(
95             r#"
96     fn foo() {
97         let m = [1, 2, 3].iter().filter_map(|x| if *x == 2 { Some (4) } else { None }).next();
98     }         //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ replace filter_map(..).next() with find_map(..)
99 "#,
100         );
101     }
102
103     #[test]
104     fn replace_filter_map_next_with_find_map_no_diagnostic_without_next() {
105         check_diagnostics(
106             r#"
107 fn foo() {
108     let m = [1, 2, 3]
109         .iter()
110         .filter_map(|x| if *x == 2 { Some (4) } else { None })
111         .len();
112 }
113 "#,
114         );
115     }
116
117     #[test]
118     fn replace_filter_map_next_with_find_map_no_diagnostic_with_intervening_methods() {
119         check_diagnostics(
120             r#"
121 fn foo() {
122     let m = [1, 2, 3]
123         .iter()
124         .filter_map(|x| if *x == 2 { Some (4) } else { None })
125         .map(|x| x + 2)
126         .len();
127 }
128 "#,
129         );
130     }
131
132     #[test]
133     fn replace_filter_map_next_with_find_map_no_diagnostic_if_not_in_chain() {
134         check_diagnostics(
135             r#"
136 fn foo() {
137     let m = [1, 2, 3]
138         .iter()
139         .filter_map(|x| if *x == 2 { Some (4) } else { None });
140     let n = m.next();
141 }
142 "#,
143         );
144     }
145
146     #[test]
147     fn replace_with_wind_map() {
148         check_fix(
149             r#"
150 //- /main.rs crate:main deps:core
151 use core::iter::Iterator;
152 use core::option::Option::{self, Some, None};
153 fn foo() {
154     let m = [1, 2, 3].iter().$0filter_map(|x| if *x == 2 { Some (4) } else { None }).next();
155 }
156 //- /core/lib.rs crate:core
157 pub mod option {
158     pub enum Option<T> { Some(T), None }
159 }
160 pub mod iter {
161     pub trait Iterator {
162         type Item;
163         fn filter_map<B, F>(self, f: F) -> FilterMap where F: FnMut(Self::Item) -> Option<B> { FilterMap }
164         fn next(&mut self) -> Option<Self::Item>;
165     }
166     pub struct FilterMap {}
167     impl Iterator for FilterMap {
168         type Item = i32;
169         fn next(&mut self) -> i32 { 7 }
170     }
171 }
172 "#,
173             r#"
174 use core::iter::Iterator;
175 use core::option::Option::{self, Some, None};
176 fn foo() {
177     let m = [1, 2, 3].iter().find_map(|x| if *x == 2 { Some (4) } else { None });
178 }
179 "#,
180         )
181     }
182 }