ide_diagnostics/handlers/
remove_trailing_return.rs

1use 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
8// Diagnostic: remove-trailing-return
9//
10// This diagnostic is triggered when there is a redundant `return` at the end of a function
11// or closure.
12pub(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        // FIXME: Our infra can't handle allow from within macro expansions rn
18        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}