]>
Commit | Line | Data |
---|---|---|
136023e0 | 1 | use clippy_utils::diagnostics::span_lint_and_sugg; |
5099ac24 | 2 | use clippy_utils::macros::{root_macro_call_first_node, FormatArgsExpn}; |
136023e0 | 3 | use clippy_utils::source::{snippet_opt, snippet_with_applicability}; |
cdc7bbd5 | 4 | use clippy_utils::sugg::Sugg; |
f20569fa | 5 | use if_chain::if_chain; |
f20569fa | 6 | use rustc_errors::Applicability; |
3c0e092e | 7 | use rustc_hir::{Expr, ExprKind}; |
136023e0 XL |
8 | use rustc_lint::{LateContext, LateLintPass}; |
9 | use rustc_middle::ty; | |
f20569fa | 10 | use rustc_session::{declare_lint_pass, declare_tool_lint}; |
136023e0 | 11 | use rustc_span::symbol::kw; |
3c0e092e | 12 | use rustc_span::{sym, BytePos, Span}; |
f20569fa XL |
13 | |
14 | declare_clippy_lint! { | |
94222f64 XL |
15 | /// ### What it does |
16 | /// Checks for the use of `format!("string literal with no | |
f20569fa XL |
17 | /// argument")` and `format!("{}", foo)` where `foo` is a string. |
18 | /// | |
94222f64 XL |
19 | /// ### Why is this bad? |
20 | /// There is no point of doing that. `format!("foo")` can | |
f20569fa XL |
21 | /// be replaced by `"foo".to_owned()` if you really need a `String`. The even |
22 | /// worse `&format!("foo")` is often encountered in the wild. `format!("{}", | |
23 | /// foo)` can be replaced by `foo.clone()` if `foo: String` or `foo.to_owned()` | |
24 | /// if `foo: &str`. | |
25 | /// | |
94222f64 | 26 | /// ### Examples |
f20569fa XL |
27 | /// ```rust |
28 | /// | |
29 | /// // Bad | |
30 | /// let foo = "foo"; | |
31 | /// format!("{}", foo); | |
32 | /// | |
33 | /// // Good | |
34 | /// foo.to_owned(); | |
35 | /// ``` | |
a2a8927a | 36 | #[clippy::version = "pre 1.29.0"] |
f20569fa XL |
37 | pub USELESS_FORMAT, |
38 | complexity, | |
39 | "useless use of `format!`" | |
40 | } | |
41 | ||
42 | declare_lint_pass!(UselessFormat => [USELESS_FORMAT]); | |
43 | ||
44 | impl<'tcx> LateLintPass<'tcx> for UselessFormat { | |
45 | fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { | |
5099ac24 FG |
46 | let (format_args, call_site) = if_chain! { |
47 | if let Some(macro_call) = root_macro_call_first_node(cx, expr); | |
48 | if cx.tcx.is_diagnostic_item(sym::format_macro, macro_call.def_id); | |
49 | if let Some(format_args) = FormatArgsExpn::find_nested(cx, expr, macro_call.expn); | |
50 | then { | |
51 | (format_args, macro_call.span) | |
52 | } else { | |
53 | return | |
54 | } | |
f20569fa XL |
55 | }; |
56 | ||
136023e0 XL |
57 | let mut applicability = Applicability::MachineApplicable; |
58 | if format_args.value_args.is_empty() { | |
5099ac24 FG |
59 | match *format_args.format_string_parts { |
60 | [] => span_useless_format_empty(cx, call_site, "String::new()".to_owned(), applicability), | |
61 | [_] => { | |
62 | if let Some(s_src) = snippet_opt(cx, format_args.format_string_span) { | |
3c0e092e XL |
63 | // Simulate macro expansion, converting {{ and }} to { and }. |
64 | let s_expand = s_src.replace("{{", "{").replace("}}", "}"); | |
65 | let sugg = format!("{}.to_string()", s_expand); | |
66 | span_useless_format(cx, call_site, sugg, applicability); | |
67 | } | |
5099ac24 FG |
68 | }, |
69 | [..] => {}, | |
136023e0 XL |
70 | } |
71 | } else if let [value] = *format_args.value_args { | |
72 | if_chain! { | |
5099ac24 | 73 | if format_args.format_string_parts == [kw::Empty]; |
136023e0 | 74 | if match cx.typeck_results().expr_ty(value).peel_refs().kind() { |
ee023bcb | 75 | ty::Adt(adt, _) => cx.tcx.is_diagnostic_item(sym::String, adt.did()), |
136023e0 XL |
76 | ty::Str => true, |
77 | _ => false, | |
78 | }; | |
3c0e092e | 79 | if let Some(args) = format_args.args(); |
5099ac24 | 80 | if args.iter().all(|arg| arg.format_trait == sym::Display && !arg.has_string_formatting()); |
136023e0 XL |
81 | then { |
82 | let is_new_string = match value.kind { | |
83 | ExprKind::Binary(..) => true, | |
84 | ExprKind::MethodCall(path, ..) => path.ident.name.as_str() == "to_string", | |
85 | _ => false, | |
86 | }; | |
3c0e092e XL |
87 | let sugg = if format_args.format_string_span.contains(value.span) { |
88 | // Implicit argument. e.g. `format!("{x}")` span points to `{x}` | |
89 | let spdata = value.span.data(); | |
90 | let span = Span::new( | |
91 | spdata.lo + BytePos(1), | |
92 | spdata.hi - BytePos(1), | |
93 | spdata.ctxt, | |
94 | spdata.parent | |
95 | ); | |
96 | let snip = snippet_with_applicability(cx, span, "..", &mut applicability); | |
97 | if is_new_string { | |
98 | snip.into() | |
99 | } else { | |
a2a8927a | 100 | format!("{snip}.to_string()") |
3c0e092e XL |
101 | } |
102 | } else if is_new_string { | |
136023e0 XL |
103 | snippet_with_applicability(cx, value.span, "..", &mut applicability).into_owned() |
104 | } else { | |
105 | let sugg = Sugg::hir_with_applicability(cx, value, "<arg>", &mut applicability); | |
106 | format!("{}.to_string()", sugg.maybe_par()) | |
107 | }; | |
108 | span_useless_format(cx, call_site, sugg, applicability); | |
f20569fa | 109 | } |
f20569fa | 110 | } |
136023e0 | 111 | }; |
f20569fa | 112 | } |
f20569fa XL |
113 | } |
114 | ||
3c0e092e | 115 | fn span_useless_format_empty(cx: &LateContext<'_>, span: Span, sugg: String, applicability: Applicability) { |
136023e0 XL |
116 | span_lint_and_sugg( |
117 | cx, | |
118 | USELESS_FORMAT, | |
119 | span, | |
120 | "useless use of `format!`", | |
3c0e092e | 121 | "consider using `String::new()`", |
136023e0 XL |
122 | sugg, |
123 | applicability, | |
124 | ); | |
f20569fa XL |
125 | } |
126 | ||
3c0e092e XL |
127 | fn span_useless_format(cx: &LateContext<'_>, span: Span, sugg: String, applicability: Applicability) { |
128 | span_lint_and_sugg( | |
129 | cx, | |
130 | USELESS_FORMAT, | |
131 | span, | |
132 | "useless use of `format!`", | |
133 | "consider using `.to_string()`", | |
134 | sugg, | |
135 | applicability, | |
136 | ); | |
f20569fa | 137 | } |