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#"
98//- minicore: fn
99fn foo() -> u8 {
100 let bar = || return 2;
101 bar() //^^^^^^^^ 💡 weak: replace return <expr>; with <expr>
102}
103"#,
104 );
105 check_diagnostics(
106 r#"
107//- minicore: fn
108fn foo() -> u8 {
109 let bar = || {
110 return 2;
111 };//^^^^^^^^^ 💡 weak: replace return <expr>; with <expr>
112 bar()
113}
114"#,
115 );
116 }
117
118 #[test]
119 fn remove_trailing_return_unit() {
120 check_diagnostics(
121 r#"
122fn foo() {
123 return
124} //^^^^^^ 💡 weak: replace return <expr>; with <expr>
125"#,
126 );
127 }
128
129 #[test]
130 fn remove_trailing_return_no_semi() {
131 check_diagnostics(
132 r#"
133fn foo() -> u8 {
134 return 2
135} //^^^^^^^^ 💡 weak: replace return <expr>; with <expr>
136"#,
137 );
138 }
139
140 #[test]
141 fn remove_trailing_return_in_if() {
142 check_diagnostics_with_disabled(
143 r#"
144fn foo(x: usize) -> u8 {
145 if x > 0 {
146 return 1;
147 //^^^^^^^^^ 💡 weak: replace return <expr>; with <expr>
148 } else {
149 return 0;
150 } //^^^^^^^^^ 💡 weak: replace return <expr>; with <expr>
151}
152"#,
153 &["remove-unnecessary-else"],
154 );
155 }
156
157 #[test]
158 fn remove_trailing_return_in_match() {
159 check_diagnostics(
160 r#"
161fn foo<T, E>(x: Result<T, E>) -> u8 {
162 match x {
163 Ok(_) => return 1,
164 //^^^^^^^^ 💡 weak: replace return <expr>; with <expr>
165 Err(_) => return 0,
166 } //^^^^^^^^ 💡 weak: replace return <expr>; with <expr>
167}
168"#,
169 );
170 }
171
172 #[test]
173 fn no_diagnostic_if_no_return_keyword() {
174 check_diagnostics(
175 r#"
176fn foo() -> u8 {
177 3
178}
179"#,
180 );
181 }
182
183 #[test]
184 fn no_diagnostic_if_not_last_statement() {
185 check_diagnostics(
186 r#"
187fn foo() -> u8 {
188 if true { return 2; }
189 3
190}
191"#,
192 );
193 }
194
195 #[test]
196 fn no_diagnostic_if_not_last_statement2() {
197 check_diagnostics(
198 r#"
199fn foo() -> u8 {
200 return 2;
201 fn bar() {}
202}
203"#,
204 );
205 }
206
207 #[test]
208 fn replace_with_expr() {
209 check_fix(
210 r#"
211fn foo() -> u8 {
212 return$0 2;
213}
214"#,
215 r#"
216fn foo() -> u8 {
217 2
218}
219"#,
220 );
221 }
222
223 #[test]
224 fn replace_with_unit() {
225 check_fix(
226 r#"
227fn foo() {
228 return$0/*ensure tidy is happy*/
229}
230"#,
231 r#"
232fn foo() {
233 /*ensure tidy is happy*/
234}
235"#,
236 );
237 }
238
239 #[test]
240 fn replace_with_expr_no_semi() {
241 check_fix(
242 r#"
243fn foo() -> u8 {
244 return$0 2
245}
246"#,
247 r#"
248fn foo() -> u8 {
249 2
250}
251"#,
252 );
253 }
254
255 #[test]
256 fn replace_in_inner_function() {
257 check_fix(
258 r#"
259fn foo() -> u8 {
260 fn bar() -> u8 {
261 return$0 2;
262 }
263 bar()
264}
265"#,
266 r#"
267fn foo() -> u8 {
268 fn bar() -> u8 {
269 2
270 }
271 bar()
272}
273"#,
274 );
275 }
276
277 #[test]
278 fn replace_in_closure() {
279 check_fix(
280 r#"
281//- minicore: fn
282fn foo() -> u8 {
283 let bar = || return$0 2;
284 bar()
285}
286"#,
287 r#"
288fn foo() -> u8 {
289 let bar = || 2;
290 bar()
291}
292"#,
293 );
294 check_fix(
295 r#"
296//- minicore: fn
297fn foo() -> u8 {
298 let bar = || {
299 return$0 2;
300 };
301 bar()
302}
303"#,
304 r#"
305fn foo() -> u8 {
306 let bar = || {
307 2
308 };
309 bar()
310}
311"#,
312 );
313 }
314
315 #[test]
316 fn replace_in_if() {
317 check_fix_with_disabled(
318 r#"
319fn foo(x: usize) -> u8 {
320 if x > 0 {
321 return$0 1;
322 } else {
323 0
324 }
325}
326"#,
327 r#"
328fn foo(x: usize) -> u8 {
329 if x > 0 {
330 1
331 } else {
332 0
333 }
334}
335"#,
336 &["remove-unnecessary-else"],
337 );
338 check_fix(
339 r#"
340fn foo(x: usize) -> u8 {
341 if x > 0 {
342 1
343 } else {
344 return$0 0;
345 }
346}
347"#,
348 r#"
349fn foo(x: usize) -> u8 {
350 if x > 0 {
351 1
352 } else {
353 0
354 }
355}
356"#,
357 );
358 }
359
360 #[test]
361 fn replace_in_match() {
362 check_fix(
363 r#"
364fn foo<T, E>(x: Result<T, E>) -> u8 {
365 match x {
366 Ok(_) => return$0 1,
367 Err(_) => 0,
368 }
369}
370"#,
371 r#"
372fn foo<T, E>(x: Result<T, E>) -> u8 {
373 match x {
374 Ok(_) => 1,
375 Err(_) => 0,
376 }
377}
378"#,
379 );
380 check_fix(
381 r#"
382fn foo<T, E>(x: Result<T, E>) -> u8 {
383 match x {
384 Ok(_) => 1,
385 Err(_) => return$0 0,
386 }
387}
388"#,
389 r#"
390fn foo<T, E>(x: Result<T, E>) -> u8 {
391 match x {
392 Ok(_) => 1,
393 Err(_) => 0,
394 }
395}
396"#,
397 );
398 }
399}