1 use hir::{db::AstDatabase, InFile};
2 use ide_db::source_change::SourceChange;
4 ast::{self, ArgListOwner},
7 use text_edit::TextEdit;
10 diagnostics::{fix, Diagnostic, DiagnosticsContext},
14 // Diagnostic: replace-filter-map-next-with-find-map
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,
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,
26 .severity(Severity::WeakWarning)
27 .with_fixes(fixes(ctx, d))
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())?;
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()?;
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();
47 let edit = TextEdit::replace(range_to_replace, replacement);
49 let source_change = SourceChange::from_text_edit(d.file.original_file(ctx.sema.db), edit);
52 "replace_with_find_map",
53 "Replace filter_map(..).next() with find_map()",
61 use crate::diagnostics::tests::check_fix;
63 // Register the required standard library types to make the tests work
65 fn check_diagnostics(ra_fixture: &str) {
67 //- /main.rs crate:main deps:core
68 use core::iter::Iterator;
69 use core::option::Option::{self, Some, None};
72 //- /core/lib.rs crate:core
74 pub enum Option<T> { Some(T), None }
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>;
82 pub struct FilterMap {}
83 impl Iterator for FilterMap {
85 fn next(&mut self) -> i32 { 7 }
89 crate::diagnostics::tests::check_diagnostics(&format!("{}{}{}", prefix, ra_fixture, suffix))
93 fn replace_filter_map_next_with_find_map2() {
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(..)
104 fn replace_filter_map_next_with_find_map_no_diagnostic_without_next() {
110 .filter_map(|x| if *x == 2 { Some (4) } else { None })
118 fn replace_filter_map_next_with_find_map_no_diagnostic_with_intervening_methods() {
124 .filter_map(|x| if *x == 2 { Some (4) } else { None })
133 fn replace_filter_map_next_with_find_map_no_diagnostic_if_not_in_chain() {
139 .filter_map(|x| if *x == 2 { Some (4) } else { None });
147 fn replace_with_wind_map() {
150 //- /main.rs crate:main deps:core
151 use core::iter::Iterator;
152 use core::option::Option::{self, Some, None};
154 let m = [1, 2, 3].iter().$0filter_map(|x| if *x == 2 { Some (4) } else { None }).next();
156 //- /core/lib.rs crate:core
158 pub enum Option<T> { Some(T), None }
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>;
166 pub struct FilterMap {}
167 impl Iterator for FilterMap {
169 fn next(&mut self) -> i32 { 7 }
174 use core::iter::Iterator;
175 use core::option::Option::{self, Some, None};
177 let m = [1, 2, 3].iter().find_map(|x| if *x == 2 { Some (4) } else { None });