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