ra_ap_ide_diagnostics/handlers/
replace_filter_map_next_with_find_map.rs1use hir::{InFile, db::ExpandDatabase};
2use ide_db::source_change::SourceChange;
3use ide_db::text_edit::TextEdit;
4use syntax::{
5 AstNode, TextRange,
6 ast::{self, HasArgList},
7};
8
9use crate::{Assist, Diagnostic, DiagnosticCode, DiagnosticsContext, fix};
10
11pub(crate) fn replace_filter_map_next_with_find_map(
15 ctx: &DiagnosticsContext<'_>,
16 d: &hir::ReplaceFilterMapNextWithFindMap,
17) -> Diagnostic {
18 Diagnostic::new_with_syntax_node_ptr(
19 ctx,
20 DiagnosticCode::Clippy("filter_map_next"),
21 "replace filter_map(..).next() with find_map(..)",
22 InFile::new(d.file, d.next_expr.into()),
23 )
24 .stable()
25 .with_fixes(fixes(ctx, d))
26}
27
28fn fixes(
29 ctx: &DiagnosticsContext<'_>,
30 d: &hir::ReplaceFilterMapNextWithFindMap,
31) -> Option<Vec<Assist>> {
32 let root = ctx.sema.db.parse_or_expand(d.file);
33 let next_expr = d.next_expr.to_node(&root);
34 let next_call = ast::MethodCallExpr::cast(next_expr.syntax().clone())?;
35
36 let filter_map_call = ast::MethodCallExpr::cast(next_call.receiver()?.syntax().clone())?;
37 let filter_map_name_range = filter_map_call.name_ref()?.ident_token()?.text_range();
38 let filter_map_args = filter_map_call.arg_list()?;
39
40 let range_to_replace =
41 TextRange::new(filter_map_name_range.start(), next_expr.syntax().text_range().end());
42 let replacement = format!("find_map{}", filter_map_args.syntax().text());
43 let trigger_range = next_expr.syntax().text_range();
44
45 let edit = TextEdit::replace(range_to_replace, replacement);
46
47 let source_change =
48 SourceChange::from_text_edit(d.file.original_file(ctx.sema.db).file_id(ctx.sema.db), edit);
49
50 Some(vec![fix(
51 "replace_with_find_map",
52 "Replace filter_map(..).next() with find_map()",
53 source_change,
54 trigger_range,
55 )])
56}
57
58#[cfg(test)]
59mod tests {
60 use crate::{
61 DiagnosticsConfig,
62 tests::{check_diagnostics_with_config, check_fix},
63 };
64
65 #[track_caller]
66 pub(crate) fn check_diagnostics(#[rust_analyzer::rust_fixture] ra_fixture: &str) {
67 let mut config = DiagnosticsConfig::test_sample();
68 config.disabled.insert("inactive-code".to_owned());
69 config.disabled.insert("E0599".to_owned());
70 check_diagnostics_with_config(config, ra_fixture)
71 }
72
73 #[test]
74 fn replace_filter_map_next_with_find_map2() {
75 check_diagnostics(
76 r#"
77//- minicore: iterators
78fn foo() {
79 let _m = core::iter::repeat(()).filter_map(|()| Some(92)).next();
80} //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 💡 weak: replace filter_map(..).next() with find_map(..)
81"#,
82 );
83 }
84
85 #[test]
86 fn replace_filter_map_next_dont_work_for_not_sized_issues_16596() {
87 check_diagnostics(
88 r#"
89//- minicore: iterators, dispatch_from_dyn
90fn foo() {
91 let mut j = [0].into_iter();
92 let i: &mut dyn Iterator<Item = i32> = &mut j;
93 let dummy_fn = |v| (v > 0).then_some(v + 1);
94 let _res = i.filter_map(dummy_fn).next();
95}
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//- minicore: iterators
105fn foo() {
106 let m = core::iter::repeat(())
107 .filter_map(|()| Some(92))
108 .count();
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//- minicore: iterators
119fn foo() {
120 let m = core::iter::repeat(())
121 .filter_map(|()| Some(92))
122 .map(|x| x + 2)
123 .next();
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//- minicore: iterators
134fn foo() {
135 let mut m = core::iter::repeat(())
136 .filter_map(|()| Some(92));
137 let _n = m.next();
138}
139"#,
140 );
141 }
142
143 #[test]
144 fn replace_with_find_map() {
145 check_fix(
146 r#"
147//- minicore: iterators
148fn foo() {
149 let m = core::iter::repeat(()).$0filter_map(|()| Some(92)).next();
150}
151"#,
152 r#"
153fn foo() {
154 let m = core::iter::repeat(()).find_map(|()| Some(92));
155}
156"#,
157 )
158 }
159
160 #[test]
161 fn respect_lint_attributes_for_clippy_equivalent() {
162 check_diagnostics(
163 r#"
164//- minicore: iterators
165
166fn foo() {
167 #[allow(clippy::filter_map_next)]
168 let _m = core::iter::repeat(()).filter_map(|()| Some(92)).next();
169}
170
171#[deny(clippy::filter_map_next)]
172fn foo() {
173 let _m = core::iter::repeat(()).filter_map(|()| Some(92)).next();
174} //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 💡 error: replace filter_map(..).next() with find_map(..)
175
176fn foo() {
177 let _m = core::iter::repeat(()).filter_map(|()| Some(92)).next();
178} //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 💡 weak: replace filter_map(..).next() with find_map(..)
179
180#[warn(clippy::filter_map_next)]
181fn foo() {
182 let _m = core::iter::repeat(()).filter_map(|()| Some(92)).next();
183} //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 💡 warn: replace filter_map(..).next() with find_map(..)
184
185"#,
186 );
187 }
188}