diff options
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.rs | 179 |
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..10d5da15d --- /dev/null +++ b/crates/ide_diagnostics/src/handlers/replace_filter_map_next_with_find_map.rs | |||
@@ -0,0 +1,179 @@ | |||
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::{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(..)`. | ||
14 | pub(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 | |||
27 | fn 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)] | ||
57 | mod 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 | ||
65 | use core::iter::Iterator; | ||
66 | use core::option::Option::{self, Some, None}; | ||
67 | "#; | ||
68 | let suffix = r#" | ||
69 | //- /core/lib.rs crate:core | ||
70 | pub mod option { | ||
71 | pub enum Option<T> { Some(T), None } | ||
72 | } | ||
73 | pub 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| if *x == 2 { Some (4) } else { None }).next(); | ||
95 | } //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 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#" | ||
104 | fn foo() { | ||
105 | let m = [1, 2, 3] | ||
106 | .iter() | ||
107 | .filter_map(|x| if *x == 2 { Some (4) } else { None }) | ||
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#" | ||
118 | fn foo() { | ||
119 | let m = [1, 2, 3] | ||
120 | .iter() | ||
121 | .filter_map(|x| if *x == 2 { Some (4) } else { None }) | ||
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#" | ||
133 | fn foo() { | ||
134 | let m = [1, 2, 3] | ||
135 | .iter() | ||
136 | .filter_map(|x| if *x == 2 { Some (4) } else { None }); | ||
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 | ||
148 | use core::iter::Iterator; | ||
149 | use core::option::Option::{self, Some, None}; | ||
150 | fn foo() { | ||
151 | let m = [1, 2, 3].iter().$0filter_map(|x| if *x == 2 { Some (4) } else { None }).next(); | ||
152 | } | ||
153 | //- /core/lib.rs crate:core | ||
154 | pub mod option { | ||
155 | pub enum Option<T> { Some(T), None } | ||
156 | } | ||
157 | pub 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#" | ||
171 | use core::iter::Iterator; | ||
172 | use core::option::Option::{self, Some, None}; | ||
173 | fn foo() { | ||
174 | let m = [1, 2, 3].iter().find_map(|x| if *x == 2 { Some (4) } else { None }); | ||
175 | } | ||
176 | "#, | ||
177 | ) | ||
178 | } | ||
179 | } | ||