diff options
author | bors[bot] <26634292+bors[bot]@users.noreply.github.com> | 2021-06-13 18:35:03 +0100 |
---|---|---|
committer | GitHub <[email protected]> | 2021-06-13 18:35:03 +0100 |
commit | 2ad78924621420cb323efdeb3d875ca3f47d940f (patch) | |
tree | eb34ec6e46f8a15b015e62dc404773a938e45819 /crates/ide/src/diagnostics | |
parent | 60ca03e8aa00956d1511969da5f1844a02483bc9 (diff) | |
parent | b66f4bb8d1748b83a6f4c5edc3c77a46b213e1c2 (diff) |
Merge #9253
9253: internal: refactor missing or or some diagnostic r=matklad a=matklad
bors r+
🤖
Co-authored-by: Aleksey Kladov <[email protected]>
Diffstat (limited to 'crates/ide/src/diagnostics')
-rw-r--r-- | crates/ide/src/diagnostics/fixes.rs | 4 | ||||
-rw-r--r-- | crates/ide/src/diagnostics/fixes/create_field.rs | 1 | ||||
-rw-r--r-- | crates/ide/src/diagnostics/fixes/remove_semicolon.rs | 41 | ||||
-rw-r--r-- | crates/ide/src/diagnostics/fixes/replace_with_find_map.rs | 84 | ||||
-rw-r--r-- | crates/ide/src/diagnostics/missing_fields.rs | 71 | ||||
-rw-r--r-- | crates/ide/src/diagnostics/missing_ok_or_some_in_tail_expr.rs (renamed from crates/ide/src/diagnostics/fixes/wrap_tail_expr.rs) | 59 | ||||
-rw-r--r-- | crates/ide/src/diagnostics/no_such_field.rs | 2 | ||||
-rw-r--r-- | crates/ide/src/diagnostics/remove_this_semicolon.rs | 64 | ||||
-rw-r--r-- | crates/ide/src/diagnostics/replace_filter_map_next_with_find_map.rs | 182 |
9 files changed, 357 insertions, 151 deletions
diff --git a/crates/ide/src/diagnostics/fixes.rs b/crates/ide/src/diagnostics/fixes.rs index a2e792b3b..e4bd90c3f 100644 --- a/crates/ide/src/diagnostics/fixes.rs +++ b/crates/ide/src/diagnostics/fixes.rs | |||
@@ -1,10 +1,6 @@ | |||
1 | //! Provides a way to attach fixes to the diagnostics. | 1 | //! Provides a way to attach fixes to the diagnostics. |
2 | //! The same module also has all curret custom fixes for the diagnostics implemented. | 2 | //! The same module also has all curret custom fixes for the diagnostics implemented. |
3 | mod change_case; | 3 | mod change_case; |
4 | mod create_field; | ||
5 | mod remove_semicolon; | ||
6 | mod replace_with_find_map; | ||
7 | mod wrap_tail_expr; | ||
8 | 4 | ||
9 | use hir::{diagnostics::Diagnostic, Semantics}; | 5 | use hir::{diagnostics::Diagnostic, Semantics}; |
10 | use ide_assists::AssistResolveStrategy; | 6 | use ide_assists::AssistResolveStrategy; |
diff --git a/crates/ide/src/diagnostics/fixes/create_field.rs b/crates/ide/src/diagnostics/fixes/create_field.rs deleted file mode 100644 index 8b1378917..000000000 --- a/crates/ide/src/diagnostics/fixes/create_field.rs +++ /dev/null | |||
@@ -1 +0,0 @@ | |||
1 | |||
diff --git a/crates/ide/src/diagnostics/fixes/remove_semicolon.rs b/crates/ide/src/diagnostics/fixes/remove_semicolon.rs deleted file mode 100644 index f1724d479..000000000 --- a/crates/ide/src/diagnostics/fixes/remove_semicolon.rs +++ /dev/null | |||
@@ -1,41 +0,0 @@ | |||
1 | use hir::{db::AstDatabase, diagnostics::RemoveThisSemicolon, Semantics}; | ||
2 | use ide_assists::{Assist, AssistResolveStrategy}; | ||
3 | use ide_db::{source_change::SourceChange, RootDatabase}; | ||
4 | use syntax::{ast, AstNode}; | ||
5 | use text_edit::TextEdit; | ||
6 | |||
7 | use crate::diagnostics::{fix, DiagnosticWithFixes}; | ||
8 | |||
9 | impl DiagnosticWithFixes for RemoveThisSemicolon { | ||
10 | fn fixes( | ||
11 | &self, | ||
12 | sema: &Semantics<RootDatabase>, | ||
13 | _resolve: &AssistResolveStrategy, | ||
14 | ) -> Option<Vec<Assist>> { | ||
15 | let root = sema.db.parse_or_expand(self.file)?; | ||
16 | |||
17 | let semicolon = self | ||
18 | .expr | ||
19 | .to_node(&root) | ||
20 | .syntax() | ||
21 | .parent() | ||
22 | .and_then(ast::ExprStmt::cast) | ||
23 | .and_then(|expr| expr.semicolon_token())? | ||
24 | .text_range(); | ||
25 | |||
26 | let edit = TextEdit::delete(semicolon); | ||
27 | let source_change = SourceChange::from_text_edit(self.file.original_file(sema.db), edit); | ||
28 | |||
29 | Some(vec![fix("remove_semicolon", "Remove this semicolon", source_change, semicolon)]) | ||
30 | } | ||
31 | } | ||
32 | |||
33 | #[cfg(test)] | ||
34 | mod tests { | ||
35 | use crate::diagnostics::tests::check_fix; | ||
36 | |||
37 | #[test] | ||
38 | fn remove_semicolon() { | ||
39 | check_fix(r#"fn f() -> i32 { 92$0; }"#, r#"fn f() -> i32 { 92 }"#); | ||
40 | } | ||
41 | } | ||
diff --git a/crates/ide/src/diagnostics/fixes/replace_with_find_map.rs b/crates/ide/src/diagnostics/fixes/replace_with_find_map.rs deleted file mode 100644 index 444bf563b..000000000 --- a/crates/ide/src/diagnostics/fixes/replace_with_find_map.rs +++ /dev/null | |||
@@ -1,84 +0,0 @@ | |||
1 | use hir::{db::AstDatabase, diagnostics::ReplaceFilterMapNextWithFindMap, Semantics}; | ||
2 | use ide_assists::{Assist, AssistResolveStrategy}; | ||
3 | use ide_db::{source_change::SourceChange, RootDatabase}; | ||
4 | use syntax::{ | ||
5 | ast::{self, ArgListOwner}, | ||
6 | AstNode, TextRange, | ||
7 | }; | ||
8 | use text_edit::TextEdit; | ||
9 | |||
10 | use crate::diagnostics::{fix, DiagnosticWithFixes}; | ||
11 | |||
12 | impl DiagnosticWithFixes for ReplaceFilterMapNextWithFindMap { | ||
13 | fn fixes( | ||
14 | &self, | ||
15 | sema: &Semantics<RootDatabase>, | ||
16 | _resolve: &AssistResolveStrategy, | ||
17 | ) -> Option<Vec<Assist>> { | ||
18 | let root = sema.db.parse_or_expand(self.file)?; | ||
19 | let next_expr = self.next_expr.to_node(&root); | ||
20 | let next_call = ast::MethodCallExpr::cast(next_expr.syntax().clone())?; | ||
21 | |||
22 | let filter_map_call = ast::MethodCallExpr::cast(next_call.receiver()?.syntax().clone())?; | ||
23 | let filter_map_name_range = filter_map_call.name_ref()?.ident_token()?.text_range(); | ||
24 | let filter_map_args = filter_map_call.arg_list()?; | ||
25 | |||
26 | let range_to_replace = | ||
27 | TextRange::new(filter_map_name_range.start(), next_expr.syntax().text_range().end()); | ||
28 | let replacement = format!("find_map{}", filter_map_args.syntax().text()); | ||
29 | let trigger_range = next_expr.syntax().text_range(); | ||
30 | |||
31 | let edit = TextEdit::replace(range_to_replace, replacement); | ||
32 | |||
33 | let source_change = SourceChange::from_text_edit(self.file.original_file(sema.db), edit); | ||
34 | |||
35 | Some(vec![fix( | ||
36 | "replace_with_find_map", | ||
37 | "Replace filter_map(..).next() with find_map()", | ||
38 | source_change, | ||
39 | trigger_range, | ||
40 | )]) | ||
41 | } | ||
42 | } | ||
43 | |||
44 | #[cfg(test)] | ||
45 | mod tests { | ||
46 | use crate::diagnostics::tests::check_fix; | ||
47 | |||
48 | #[test] | ||
49 | fn replace_with_wind_map() { | ||
50 | check_fix( | ||
51 | r#" | ||
52 | //- /main.rs crate:main deps:core | ||
53 | use core::iter::Iterator; | ||
54 | use core::option::Option::{self, Some, None}; | ||
55 | fn foo() { | ||
56 | let m = [1, 2, 3].iter().$0filter_map(|x| if *x == 2 { Some (4) } else { None }).next(); | ||
57 | } | ||
58 | //- /core/lib.rs crate:core | ||
59 | pub mod option { | ||
60 | pub enum Option<T> { Some(T), None } | ||
61 | } | ||
62 | pub mod iter { | ||
63 | pub trait Iterator { | ||
64 | type Item; | ||
65 | fn filter_map<B, F>(self, f: F) -> FilterMap where F: FnMut(Self::Item) -> Option<B> { FilterMap } | ||
66 | fn next(&mut self) -> Option<Self::Item>; | ||
67 | } | ||
68 | pub struct FilterMap {} | ||
69 | impl Iterator for FilterMap { | ||
70 | type Item = i32; | ||
71 | fn next(&mut self) -> i32 { 7 } | ||
72 | } | ||
73 | } | ||
74 | "#, | ||
75 | r#" | ||
76 | use core::iter::Iterator; | ||
77 | use core::option::Option::{self, Some, None}; | ||
78 | fn foo() { | ||
79 | let m = [1, 2, 3].iter().find_map(|x| if *x == 2 { Some (4) } else { None }); | ||
80 | } | ||
81 | "#, | ||
82 | ) | ||
83 | } | ||
84 | } | ||
diff --git a/crates/ide/src/diagnostics/missing_fields.rs b/crates/ide/src/diagnostics/missing_fields.rs index 95cd64956..d01f05041 100644 --- a/crates/ide/src/diagnostics/missing_fields.rs +++ b/crates/ide/src/diagnostics/missing_fields.rs | |||
@@ -94,6 +94,77 @@ fn baz(s: S) { | |||
94 | } | 94 | } |
95 | 95 | ||
96 | #[test] | 96 | #[test] |
97 | fn missing_record_pat_field_no_diagnostic_if_not_exhaustive() { | ||
98 | check_diagnostics( | ||
99 | r" | ||
100 | struct S { foo: i32, bar: () } | ||
101 | fn baz(s: S) -> i32 { | ||
102 | match s { | ||
103 | S { foo, .. } => foo, | ||
104 | } | ||
105 | } | ||
106 | ", | ||
107 | ) | ||
108 | } | ||
109 | |||
110 | #[test] | ||
111 | fn missing_record_pat_field_box() { | ||
112 | check_diagnostics( | ||
113 | r" | ||
114 | struct S { s: Box<u32> } | ||
115 | fn x(a: S) { | ||
116 | let S { box s } = a; | ||
117 | } | ||
118 | ", | ||
119 | ) | ||
120 | } | ||
121 | |||
122 | #[test] | ||
123 | fn missing_record_pat_field_ref() { | ||
124 | check_diagnostics( | ||
125 | r" | ||
126 | struct S { s: u32 } | ||
127 | fn x(a: S) { | ||
128 | let S { ref s } = a; | ||
129 | } | ||
130 | ", | ||
131 | ) | ||
132 | } | ||
133 | |||
134 | #[test] | ||
135 | fn range_mapping_out_of_macros() { | ||
136 | // FIXME: this is very wrong, but somewhat tricky to fix. | ||
137 | check_fix( | ||
138 | r#" | ||
139 | fn some() {} | ||
140 | fn items() {} | ||
141 | fn here() {} | ||
142 | |||
143 | macro_rules! id { ($($tt:tt)*) => { $($tt)*}; } | ||
144 | |||
145 | fn main() { | ||
146 | let _x = id![Foo { a: $042 }]; | ||
147 | } | ||
148 | |||
149 | pub struct Foo { pub a: i32, pub b: i32 } | ||
150 | "#, | ||
151 | r#" | ||
152 | fn some(, b: () ) {} | ||
153 | fn items() {} | ||
154 | fn here() {} | ||
155 | |||
156 | macro_rules! id { ($($tt:tt)*) => { $($tt)*}; } | ||
157 | |||
158 | fn main() { | ||
159 | let _x = id![Foo { a: 42 }]; | ||
160 | } | ||
161 | |||
162 | pub struct Foo { pub a: i32, pub b: i32 } | ||
163 | "#, | ||
164 | ); | ||
165 | } | ||
166 | |||
167 | #[test] | ||
97 | fn test_fill_struct_fields_empty() { | 168 | fn test_fill_struct_fields_empty() { |
98 | check_fix( | 169 | check_fix( |
99 | r#" | 170 | r#" |
diff --git a/crates/ide/src/diagnostics/fixes/wrap_tail_expr.rs b/crates/ide/src/diagnostics/missing_ok_or_some_in_tail_expr.rs index dcb21e037..06005d156 100644 --- a/crates/ide/src/diagnostics/fixes/wrap_tail_expr.rs +++ b/crates/ide/src/diagnostics/missing_ok_or_some_in_tail_expr.rs | |||
@@ -1,26 +1,45 @@ | |||
1 | use hir::{db::AstDatabase, diagnostics::MissingOkOrSomeInTailExpr, Semantics}; | 1 | use hir::db::AstDatabase; |
2 | use ide_assists::{Assist, AssistResolveStrategy}; | 2 | use ide_assists::Assist; |
3 | use ide_db::{source_change::SourceChange, RootDatabase}; | 3 | use ide_db::source_change::SourceChange; |
4 | use syntax::AstNode; | 4 | use syntax::AstNode; |
5 | use text_edit::TextEdit; | 5 | use text_edit::TextEdit; |
6 | 6 | ||
7 | use crate::diagnostics::{fix, DiagnosticWithFixes}; | 7 | use crate::diagnostics::{fix, Diagnostic, DiagnosticsContext}; |
8 | 8 | ||
9 | impl DiagnosticWithFixes for MissingOkOrSomeInTailExpr { | 9 | // Diagnostic: missing-ok-or-some-in-tail-expr |
10 | fn fixes( | 10 | // |
11 | &self, | 11 | // This diagnostic is triggered if a block that should return `Result` returns a value not wrapped in `Ok`, |
12 | sema: &Semantics<RootDatabase>, | 12 | // or if a block that should return `Option` returns a value not wrapped in `Some`. |
13 | _resolve: &AssistResolveStrategy, | 13 | // |
14 | ) -> Option<Vec<Assist>> { | 14 | // Example: |
15 | let root = sema.db.parse_or_expand(self.file)?; | 15 | // |
16 | let tail_expr = self.expr.to_node(&root); | 16 | // ```rust |
17 | let tail_expr_range = tail_expr.syntax().text_range(); | 17 | // fn foo() -> Result<u8, ()> { |
18 | let replacement = format!("{}({})", self.required, tail_expr.syntax()); | 18 | // 10 |
19 | let edit = TextEdit::replace(tail_expr_range, replacement); | 19 | // } |
20 | let source_change = SourceChange::from_text_edit(self.file.original_file(sema.db), edit); | 20 | // ``` |
21 | let name = if self.required == "Ok" { "Wrap with Ok" } else { "Wrap with Some" }; | 21 | pub(super) fn missing_ok_or_some_in_tail_expr( |
22 | Some(vec![fix("wrap_tail_expr", name, source_change, tail_expr_range)]) | 22 | ctx: &DiagnosticsContext<'_>, |
23 | } | 23 | d: &hir::MissingOkOrSomeInTailExpr, |
24 | ) -> Diagnostic { | ||
25 | Diagnostic::new( | ||
26 | "missing-ok-or-some-in-tail-expr", | ||
27 | format!("wrap return expression in {}", d.required), | ||
28 | ctx.sema.diagnostics_display_range(d.expr.clone().map(|it| it.into())).range, | ||
29 | ) | ||
30 | .with_fixes(fixes(ctx, d)) | ||
31 | } | ||
32 | |||
33 | fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::MissingOkOrSomeInTailExpr) -> Option<Vec<Assist>> { | ||
34 | let root = ctx.sema.db.parse_or_expand(d.expr.file_id)?; | ||
35 | let tail_expr = d.expr.value.to_node(&root); | ||
36 | let tail_expr_range = tail_expr.syntax().text_range(); | ||
37 | let replacement = format!("{}({})", d.required, tail_expr.syntax()); | ||
38 | let edit = TextEdit::replace(tail_expr_range, replacement); | ||
39 | let source_change = | ||
40 | SourceChange::from_text_edit(d.expr.file_id.original_file(ctx.sema.db), edit); | ||
41 | let name = if d.required == "Ok" { "Wrap with Ok" } else { "Wrap with Some" }; | ||
42 | Some(vec![fix("wrap_tail_expr", name, source_change, tail_expr_range)]) | ||
24 | } | 43 | } |
25 | 44 | ||
26 | #[cfg(test)] | 45 | #[cfg(test)] |
diff --git a/crates/ide/src/diagnostics/no_such_field.rs b/crates/ide/src/diagnostics/no_such_field.rs index 61962de28..edc63c246 100644 --- a/crates/ide/src/diagnostics/no_such_field.rs +++ b/crates/ide/src/diagnostics/no_such_field.rs | |||
@@ -17,7 +17,7 @@ use crate::{ | |||
17 | pub(super) fn no_such_field(ctx: &DiagnosticsContext<'_>, d: &hir::NoSuchField) -> Diagnostic { | 17 | pub(super) fn no_such_field(ctx: &DiagnosticsContext<'_>, d: &hir::NoSuchField) -> Diagnostic { |
18 | Diagnostic::new( | 18 | Diagnostic::new( |
19 | "no-such-field", | 19 | "no-such-field", |
20 | "no such field".to_string(), | 20 | "no such field", |
21 | ctx.sema.diagnostics_display_range(d.field.clone().map(|it| it.into())).range, | 21 | ctx.sema.diagnostics_display_range(d.field.clone().map(|it| it.into())).range, |
22 | ) | 22 | ) |
23 | .with_fixes(fixes(ctx, d)) | 23 | .with_fixes(fixes(ctx, d)) |
diff --git a/crates/ide/src/diagnostics/remove_this_semicolon.rs b/crates/ide/src/diagnostics/remove_this_semicolon.rs new file mode 100644 index 000000000..814cb0f8c --- /dev/null +++ b/crates/ide/src/diagnostics/remove_this_semicolon.rs | |||
@@ -0,0 +1,64 @@ | |||
1 | use hir::db::AstDatabase; | ||
2 | use ide_db::source_change::SourceChange; | ||
3 | use syntax::{ast, AstNode}; | ||
4 | use text_edit::TextEdit; | ||
5 | |||
6 | use crate::{ | ||
7 | diagnostics::{fix, Diagnostic, DiagnosticsContext}, | ||
8 | Assist, | ||
9 | }; | ||
10 | |||
11 | // Diagnostic: remove-this-semicolon | ||
12 | // | ||
13 | // This diagnostic is triggered when there's an erroneous `;` at the end of the block. | ||
14 | pub(super) fn remove_this_semicolon( | ||
15 | ctx: &DiagnosticsContext<'_>, | ||
16 | d: &hir::RemoveThisSemicolon, | ||
17 | ) -> Diagnostic { | ||
18 | Diagnostic::new( | ||
19 | "remove-this-semicolon", | ||
20 | "remove this semicolon", | ||
21 | ctx.sema.diagnostics_display_range(d.expr.clone().map(|it| it.into())).range, | ||
22 | ) | ||
23 | .with_fixes(fixes(ctx, d)) | ||
24 | } | ||
25 | |||
26 | fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::RemoveThisSemicolon) -> Option<Vec<Assist>> { | ||
27 | let root = ctx.sema.db.parse_or_expand(d.expr.file_id)?; | ||
28 | |||
29 | let semicolon = d | ||
30 | .expr | ||
31 | .value | ||
32 | .to_node(&root) | ||
33 | .syntax() | ||
34 | .parent() | ||
35 | .and_then(ast::ExprStmt::cast) | ||
36 | .and_then(|expr| expr.semicolon_token())? | ||
37 | .text_range(); | ||
38 | |||
39 | let edit = TextEdit::delete(semicolon); | ||
40 | let source_change = | ||
41 | SourceChange::from_text_edit(d.expr.file_id.original_file(ctx.sema.db), edit); | ||
42 | |||
43 | Some(vec![fix("remove_semicolon", "Remove this semicolon", source_change, semicolon)]) | ||
44 | } | ||
45 | |||
46 | #[cfg(test)] | ||
47 | mod tests { | ||
48 | use crate::diagnostics::tests::{check_diagnostics, check_fix}; | ||
49 | |||
50 | #[test] | ||
51 | fn missing_semicolon() { | ||
52 | check_diagnostics( | ||
53 | r#" | ||
54 | fn test() -> i32 { 123; } | ||
55 | //^^^ remove this semicolon | ||
56 | "#, | ||
57 | ); | ||
58 | } | ||
59 | |||
60 | #[test] | ||
61 | fn remove_semicolon() { | ||
62 | check_fix(r#"fn f() -> i32 { 92$0; }"#, r#"fn f() -> i32 { 92 }"#); | ||
63 | } | ||
64 | } | ||
diff --git a/crates/ide/src/diagnostics/replace_filter_map_next_with_find_map.rs b/crates/ide/src/diagnostics/replace_filter_map_next_with_find_map.rs new file mode 100644 index 000000000..f3b011495 --- /dev/null +++ b/crates/ide/src/diagnostics/replace_filter_map_next_with_find_map.rs | |||
@@ -0,0 +1,182 @@ | |||
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 | } | ||