ide_diagnostics/handlers/
remove_trailing_return.rs1use hir::{FileRange, db::ExpandDatabase, diagnostics::RemoveTrailingReturn};
2use ide_db::text_edit::TextEdit;
3use ide_db::{assists::Assist, source_change::SourceChange};
4use syntax::{AstNode, ast};
5
6use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext, adjusted_display_range, fix};
7
8pub(crate) fn remove_trailing_return(
13 ctx: &DiagnosticsContext<'_>,
14 d: &RemoveTrailingReturn,
15) -> Option<Diagnostic> {
16 if d.return_expr.file_id.macro_file().is_some() {
17 return None;
19 }
20
21 let display_range = adjusted_display_range(ctx, d.return_expr, &|return_expr| {
22 return_expr
23 .syntax()
24 .parent()
25 .and_then(ast::ExprStmt::cast)
26 .map(|stmt| stmt.syntax().text_range())
27 });
28 Some(
29 Diagnostic::new(
30 DiagnosticCode::Clippy("needless_return"),
31 "replace return <expr>; with <expr>",
32 display_range,
33 )
34 .stable()
35 .with_fixes(fixes(ctx, d)),
36 )
37}
38
39fn fixes(ctx: &DiagnosticsContext<'_>, d: &RemoveTrailingReturn) -> Option<Vec<Assist>> {
40 let root = ctx.sema.db.parse_or_expand(d.return_expr.file_id);
41 let return_expr = d.return_expr.value.to_node(&root);
42 let stmt = return_expr.syntax().parent().and_then(ast::ExprStmt::cast);
43
44 let FileRange { range, file_id } =
45 ctx.sema.original_range_opt(stmt.as_ref().map_or(return_expr.syntax(), AstNode::syntax))?;
46 if Some(file_id) != d.return_expr.file_id.file_id() {
47 return None;
48 }
49
50 let replacement =
51 return_expr.expr().map_or_else(String::new, |expr| format!("{}", expr.syntax().text()));
52 let edit = TextEdit::replace(range, replacement);
53 let source_change = SourceChange::from_text_edit(file_id.file_id(ctx.sema.db), edit);
54
55 Some(vec![fix(
56 "remove_trailing_return",
57 "Replace return <expr>; with <expr>",
58 source_change,
59 range,
60 )])
61}
62
63#[cfg(test)]
64mod tests {
65 use crate::tests::{
66 check_diagnostics, check_diagnostics_with_disabled, check_fix, check_fix_with_disabled,
67 };
68
69 #[test]
70 fn remove_trailing_return() {
71 check_diagnostics(
72 r#"
73fn foo() -> u8 {
74 return 2;
75} //^^^^^^^^^ 💡 weak: replace return <expr>; with <expr>
76"#,
77 );
78 }
79
80 #[test]
81 fn remove_trailing_return_inner_function() {
82 check_diagnostics(
83 r#"
84fn foo() -> u8 {
85 fn bar() -> u8 {
86 return 2;
87 } //^^^^^^^^^ 💡 weak: replace return <expr>; with <expr>
88 bar()
89}
90"#,
91 );
92 }
93
94 #[test]
95 fn remove_trailing_return_closure() {
96 check_diagnostics(
97 r#"
98fn foo() -> u8 {
99 let bar = || return 2;
100 bar() //^^^^^^^^ 💡 weak: replace return <expr>; with <expr>
101}
102"#,
103 );
104 check_diagnostics(
105 r#"
106fn foo() -> u8 {
107 let bar = || {
108 return 2;
109 };//^^^^^^^^^ 💡 weak: replace return <expr>; with <expr>
110 bar()
111}
112"#,
113 );
114 }
115
116 #[test]
117 fn remove_trailing_return_unit() {
118 check_diagnostics(
119 r#"
120fn foo() {
121 return
122} //^^^^^^ 💡 weak: replace return <expr>; with <expr>
123"#,
124 );
125 }
126
127 #[test]
128 fn remove_trailing_return_no_semi() {
129 check_diagnostics(
130 r#"
131fn foo() -> u8 {
132 return 2
133} //^^^^^^^^ 💡 weak: replace return <expr>; with <expr>
134"#,
135 );
136 }
137
138 #[test]
139 fn remove_trailing_return_in_if() {
140 check_diagnostics_with_disabled(
141 r#"
142fn foo(x: usize) -> u8 {
143 if x > 0 {
144 return 1;
145 //^^^^^^^^^ 💡 weak: replace return <expr>; with <expr>
146 } else {
147 return 0;
148 } //^^^^^^^^^ 💡 weak: replace return <expr>; with <expr>
149}
150"#,
151 &["remove-unnecessary-else"],
152 );
153 }
154
155 #[test]
156 fn remove_trailing_return_in_match() {
157 check_diagnostics(
158 r#"
159fn foo<T, E>(x: Result<T, E>) -> u8 {
160 match x {
161 Ok(_) => return 1,
162 //^^^^^^^^ 💡 weak: replace return <expr>; with <expr>
163 Err(_) => return 0,
164 } //^^^^^^^^ 💡 weak: replace return <expr>; with <expr>
165}
166"#,
167 );
168 }
169
170 #[test]
171 fn no_diagnostic_if_no_return_keyword() {
172 check_diagnostics(
173 r#"
174fn foo() -> u8 {
175 3
176}
177"#,
178 );
179 }
180
181 #[test]
182 fn no_diagnostic_if_not_last_statement() {
183 check_diagnostics(
184 r#"
185fn foo() -> u8 {
186 if true { return 2; }
187 3
188}
189"#,
190 );
191 }
192
193 #[test]
194 fn no_diagnostic_if_not_last_statement2() {
195 check_diagnostics(
196 r#"
197fn foo() -> u8 {
198 return 2;
199 fn bar() {}
200}
201"#,
202 );
203 }
204
205 #[test]
206 fn replace_with_expr() {
207 check_fix(
208 r#"
209fn foo() -> u8 {
210 return$0 2;
211}
212"#,
213 r#"
214fn foo() -> u8 {
215 2
216}
217"#,
218 );
219 }
220
221 #[test]
222 fn replace_with_unit() {
223 check_fix(
224 r#"
225fn foo() {
226 return$0/*ensure tidy is happy*/
227}
228"#,
229 r#"
230fn foo() {
231 /*ensure tidy is happy*/
232}
233"#,
234 );
235 }
236
237 #[test]
238 fn replace_with_expr_no_semi() {
239 check_fix(
240 r#"
241fn foo() -> u8 {
242 return$0 2
243}
244"#,
245 r#"
246fn foo() -> u8 {
247 2
248}
249"#,
250 );
251 }
252
253 #[test]
254 fn replace_in_inner_function() {
255 check_fix(
256 r#"
257fn foo() -> u8 {
258 fn bar() -> u8 {
259 return$0 2;
260 }
261 bar()
262}
263"#,
264 r#"
265fn foo() -> u8 {
266 fn bar() -> u8 {
267 2
268 }
269 bar()
270}
271"#,
272 );
273 }
274
275 #[test]
276 fn replace_in_closure() {
277 check_fix(
278 r#"
279fn foo() -> u8 {
280 let bar = || return$0 2;
281 bar()
282}
283"#,
284 r#"
285fn foo() -> u8 {
286 let bar = || 2;
287 bar()
288}
289"#,
290 );
291 check_fix(
292 r#"
293fn foo() -> u8 {
294 let bar = || {
295 return$0 2;
296 };
297 bar()
298}
299"#,
300 r#"
301fn foo() -> u8 {
302 let bar = || {
303 2
304 };
305 bar()
306}
307"#,
308 );
309 }
310
311 #[test]
312 fn replace_in_if() {
313 check_fix_with_disabled(
314 r#"
315fn foo(x: usize) -> u8 {
316 if x > 0 {
317 return$0 1;
318 } else {
319 0
320 }
321}
322"#,
323 r#"
324fn foo(x: usize) -> u8 {
325 if x > 0 {
326 1
327 } else {
328 0
329 }
330}
331"#,
332 std::iter::once("remove-unnecessary-else".to_owned()),
333 );
334 check_fix(
335 r#"
336fn foo(x: usize) -> u8 {
337 if x > 0 {
338 1
339 } else {
340 return$0 0;
341 }
342}
343"#,
344 r#"
345fn foo(x: usize) -> u8 {
346 if x > 0 {
347 1
348 } else {
349 0
350 }
351}
352"#,
353 );
354 }
355
356 #[test]
357 fn replace_in_match() {
358 check_fix(
359 r#"
360fn foo<T, E>(x: Result<T, E>) -> u8 {
361 match x {
362 Ok(_) => return$0 1,
363 Err(_) => 0,
364 }
365}
366"#,
367 r#"
368fn foo<T, E>(x: Result<T, E>) -> u8 {
369 match x {
370 Ok(_) => 1,
371 Err(_) => 0,
372 }
373}
374"#,
375 );
376 check_fix(
377 r#"
378fn foo<T, E>(x: Result<T, E>) -> u8 {
379 match x {
380 Ok(_) => 1,
381 Err(_) => return$0 0,
382 }
383}
384"#,
385 r#"
386fn foo<T, E>(x: Result<T, E>) -> u8 {
387 match x {
388 Ok(_) => 1,
389 Err(_) => 0,
390 }
391}
392"#,
393 );
394 }
395}