diff options
Diffstat (limited to 'crates/ide/src/diagnostics/missing_ok_or_some_in_tail_expr.rs')
-rw-r--r-- | crates/ide/src/diagnostics/missing_ok_or_some_in_tail_expr.rs | 230 |
1 files changed, 230 insertions, 0 deletions
diff --git a/crates/ide/src/diagnostics/missing_ok_or_some_in_tail_expr.rs b/crates/ide/src/diagnostics/missing_ok_or_some_in_tail_expr.rs new file mode 100644 index 000000000..06005d156 --- /dev/null +++ b/crates/ide/src/diagnostics/missing_ok_or_some_in_tail_expr.rs | |||
@@ -0,0 +1,230 @@ | |||
1 | use hir::db::AstDatabase; | ||
2 | use ide_assists::Assist; | ||
3 | use ide_db::source_change::SourceChange; | ||
4 | use syntax::AstNode; | ||
5 | use text_edit::TextEdit; | ||
6 | |||
7 | use crate::diagnostics::{fix, Diagnostic, DiagnosticsContext}; | ||
8 | |||
9 | // Diagnostic: missing-ok-or-some-in-tail-expr | ||
10 | // | ||
11 | // This diagnostic is triggered if a block that should return `Result` returns a value not wrapped in `Ok`, | ||
12 | // or if a block that should return `Option` returns a value not wrapped in `Some`. | ||
13 | // | ||
14 | // Example: | ||
15 | // | ||
16 | // ```rust | ||
17 | // fn foo() -> Result<u8, ()> { | ||
18 | // 10 | ||
19 | // } | ||
20 | // ``` | ||
21 | pub(super) fn missing_ok_or_some_in_tail_expr( | ||
22 | ctx: &DiagnosticsContext<'_>, | ||
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)]) | ||
43 | } | ||
44 | |||
45 | #[cfg(test)] | ||
46 | mod tests { | ||
47 | use crate::diagnostics::tests::{check_diagnostics, check_fix}; | ||
48 | |||
49 | #[test] | ||
50 | fn test_wrap_return_type_option() { | ||
51 | check_fix( | ||
52 | r#" | ||
53 | //- /main.rs crate:main deps:core | ||
54 | use core::option::Option::{self, Some, None}; | ||
55 | |||
56 | fn div(x: i32, y: i32) -> Option<i32> { | ||
57 | if y == 0 { | ||
58 | return None; | ||
59 | } | ||
60 | x / y$0 | ||
61 | } | ||
62 | //- /core/lib.rs crate:core | ||
63 | pub mod result { | ||
64 | pub enum Result<T, E> { Ok(T), Err(E) } | ||
65 | } | ||
66 | pub mod option { | ||
67 | pub enum Option<T> { Some(T), None } | ||
68 | } | ||
69 | "#, | ||
70 | r#" | ||
71 | use core::option::Option::{self, Some, None}; | ||
72 | |||
73 | fn div(x: i32, y: i32) -> Option<i32> { | ||
74 | if y == 0 { | ||
75 | return None; | ||
76 | } | ||
77 | Some(x / y) | ||
78 | } | ||
79 | "#, | ||
80 | ); | ||
81 | } | ||
82 | |||
83 | #[test] | ||
84 | fn test_wrap_return_type() { | ||
85 | check_fix( | ||
86 | r#" | ||
87 | //- /main.rs crate:main deps:core | ||
88 | use core::result::Result::{self, Ok, Err}; | ||
89 | |||
90 | fn div(x: i32, y: i32) -> Result<i32, ()> { | ||
91 | if y == 0 { | ||
92 | return Err(()); | ||
93 | } | ||
94 | x / y$0 | ||
95 | } | ||
96 | //- /core/lib.rs crate:core | ||
97 | pub mod result { | ||
98 | pub enum Result<T, E> { Ok(T), Err(E) } | ||
99 | } | ||
100 | pub mod option { | ||
101 | pub enum Option<T> { Some(T), None } | ||
102 | } | ||
103 | "#, | ||
104 | r#" | ||
105 | use core::result::Result::{self, Ok, Err}; | ||
106 | |||
107 | fn div(x: i32, y: i32) -> Result<i32, ()> { | ||
108 | if y == 0 { | ||
109 | return Err(()); | ||
110 | } | ||
111 | Ok(x / y) | ||
112 | } | ||
113 | "#, | ||
114 | ); | ||
115 | } | ||
116 | |||
117 | #[test] | ||
118 | fn test_wrap_return_type_handles_generic_functions() { | ||
119 | check_fix( | ||
120 | r#" | ||
121 | //- /main.rs crate:main deps:core | ||
122 | use core::result::Result::{self, Ok, Err}; | ||
123 | |||
124 | fn div<T>(x: T) -> Result<T, i32> { | ||
125 | if x == 0 { | ||
126 | return Err(7); | ||
127 | } | ||
128 | $0x | ||
129 | } | ||
130 | //- /core/lib.rs crate:core | ||
131 | pub mod result { | ||
132 | pub enum Result<T, E> { Ok(T), Err(E) } | ||
133 | } | ||
134 | pub mod option { | ||
135 | pub enum Option<T> { Some(T), None } | ||
136 | } | ||
137 | "#, | ||
138 | r#" | ||
139 | use core::result::Result::{self, Ok, Err}; | ||
140 | |||
141 | fn div<T>(x: T) -> Result<T, i32> { | ||
142 | if x == 0 { | ||
143 | return Err(7); | ||
144 | } | ||
145 | Ok(x) | ||
146 | } | ||
147 | "#, | ||
148 | ); | ||
149 | } | ||
150 | |||
151 | #[test] | ||
152 | fn test_wrap_return_type_handles_type_aliases() { | ||
153 | check_fix( | ||
154 | r#" | ||
155 | //- /main.rs crate:main deps:core | ||
156 | use core::result::Result::{self, Ok, Err}; | ||
157 | |||
158 | type MyResult<T> = Result<T, ()>; | ||
159 | |||
160 | fn div(x: i32, y: i32) -> MyResult<i32> { | ||
161 | if y == 0 { | ||
162 | return Err(()); | ||
163 | } | ||
164 | x $0/ y | ||
165 | } | ||
166 | //- /core/lib.rs crate:core | ||
167 | pub mod result { | ||
168 | pub enum Result<T, E> { Ok(T), Err(E) } | ||
169 | } | ||
170 | pub mod option { | ||
171 | pub enum Option<T> { Some(T), None } | ||
172 | } | ||
173 | "#, | ||
174 | r#" | ||
175 | use core::result::Result::{self, Ok, Err}; | ||
176 | |||
177 | type MyResult<T> = Result<T, ()>; | ||
178 | |||
179 | fn div(x: i32, y: i32) -> MyResult<i32> { | ||
180 | if y == 0 { | ||
181 | return Err(()); | ||
182 | } | ||
183 | Ok(x / y) | ||
184 | } | ||
185 | "#, | ||
186 | ); | ||
187 | } | ||
188 | |||
189 | #[test] | ||
190 | fn test_wrap_return_type_not_applicable_when_expr_type_does_not_match_ok_type() { | ||
191 | check_diagnostics( | ||
192 | r#" | ||
193 | //- /main.rs crate:main deps:core | ||
194 | use core::result::Result::{self, Ok, Err}; | ||
195 | |||
196 | fn foo() -> Result<(), i32> { 0 } | ||
197 | |||
198 | //- /core/lib.rs crate:core | ||
199 | pub mod result { | ||
200 | pub enum Result<T, E> { Ok(T), Err(E) } | ||
201 | } | ||
202 | pub mod option { | ||
203 | pub enum Option<T> { Some(T), None } | ||
204 | } | ||
205 | "#, | ||
206 | ); | ||
207 | } | ||
208 | |||
209 | #[test] | ||
210 | fn test_wrap_return_type_not_applicable_when_return_type_is_not_result_or_option() { | ||
211 | check_diagnostics( | ||
212 | r#" | ||
213 | //- /main.rs crate:main deps:core | ||
214 | use core::result::Result::{self, Ok, Err}; | ||
215 | |||
216 | enum SomeOtherEnum { Ok(i32), Err(String) } | ||
217 | |||
218 | fn foo() -> SomeOtherEnum { 0 } | ||
219 | |||
220 | //- /core/lib.rs crate:core | ||
221 | pub mod result { | ||
222 | pub enum Result<T, E> { Ok(T), Err(E) } | ||
223 | } | ||
224 | pub mod option { | ||
225 | pub enum Option<T> { Some(T), None } | ||
226 | } | ||
227 | "#, | ||
228 | ); | ||
229 | } | ||
230 | } | ||