]>
Commit | Line | Data |
---|---|---|
136023e0 | 1 | use clippy_utils::diagnostics::span_lint_and_sugg; |
31ef2f64 | 2 | use clippy_utils::macros::{find_format_arg_expr, root_macro_call_first_node, FormatArgsStorage}; |
353b0b11 | 3 | use clippy_utils::source::{snippet_opt, snippet_with_context}; |
cdc7bbd5 | 4 | use clippy_utils::sugg::Sugg; |
353b0b11 | 5 | use rustc_ast::{FormatArgsPiece, FormatOptions, FormatTrait}; |
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; | |
31ef2f64 | 10 | use rustc_session::impl_lint_pass; |
064997fb | 11 | use rustc_span::{sym, Span}; |
f20569fa XL |
12 | |
13 | declare_clippy_lint! { | |
94222f64 XL |
14 | /// ### What it does |
15 | /// Checks for the use of `format!("string literal with no | |
f20569fa XL |
16 | /// argument")` and `format!("{}", foo)` where `foo` is a string. |
17 | /// | |
94222f64 XL |
18 | /// ### Why is this bad? |
19 | /// There is no point of doing that. `format!("foo")` can | |
f20569fa XL |
20 | /// be replaced by `"foo".to_owned()` if you really need a `String`. The even |
21 | /// worse `&format!("foo")` is often encountered in the wild. `format!("{}", | |
22 | /// foo)` can be replaced by `foo.clone()` if `foo: String` or `foo.to_owned()` | |
23 | /// if `foo: &str`. | |
24 | /// | |
94222f64 | 25 | /// ### Examples |
ed00b5ec | 26 | /// ```no_run |
f20569fa XL |
27 | /// let foo = "foo"; |
28 | /// format!("{}", foo); | |
923072b8 | 29 | /// ``` |
f20569fa | 30 | /// |
923072b8 | 31 | /// Use instead: |
ed00b5ec | 32 | /// ```no_run |
923072b8 | 33 | /// let foo = "foo"; |
f20569fa XL |
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 | ||
31ef2f64 FG |
42 | #[allow(clippy::module_name_repetitions)] |
43 | pub struct UselessFormat { | |
44 | format_args: FormatArgsStorage, | |
45 | } | |
46 | ||
47 | impl UselessFormat { | |
48 | pub fn new(format_args: FormatArgsStorage) -> Self { | |
49 | Self { format_args } | |
50 | } | |
51 | } | |
52 | ||
53 | impl_lint_pass!(UselessFormat => [USELESS_FORMAT]); | |
f20569fa XL |
54 | |
55 | impl<'tcx> LateLintPass<'tcx> for UselessFormat { | |
56 | fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { | |
781aab86 FG |
57 | if let Some(macro_call) = root_macro_call_first_node(cx, expr) |
58 | && cx.tcx.is_diagnostic_item(sym::format_macro, macro_call.def_id) | |
31ef2f64 | 59 | && let Some(format_args) = self.format_args.get(cx, expr, macro_call.expn) |
781aab86 | 60 | { |
353b0b11 FG |
61 | let mut applicability = Applicability::MachineApplicable; |
62 | let call_site = macro_call.span; | |
f20569fa | 63 | |
353b0b11 FG |
64 | match (format_args.arguments.all_args(), &format_args.template[..]) { |
65 | ([], []) => span_useless_format_empty(cx, call_site, "String::new()".to_owned(), applicability), | |
66 | ([], [_]) => { | |
f2b60f7d | 67 | // Simulate macro expansion, converting {{ and }} to { and }. |
ed00b5ec FG |
68 | let Some(snippet) = snippet_opt(cx, format_args.span) else { |
69 | return; | |
70 | }; | |
353b0b11 | 71 | let s_expand = snippet.replace("{{", "{").replace("}}", "}"); |
2b03887a | 72 | let sugg = format!("{s_expand}.to_string()"); |
f2b60f7d | 73 | span_useless_format(cx, call_site, sugg, applicability); |
5099ac24 | 74 | }, |
353b0b11 FG |
75 | ([arg], [piece]) => { |
76 | if let Ok(value) = find_format_arg_expr(expr, arg) | |
77 | && let FormatArgsPiece::Placeholder(placeholder) = piece | |
78 | && placeholder.format_trait == FormatTrait::Display | |
79 | && placeholder.format_options == FormatOptions::default() | |
80 | && match cx.typeck_results().expr_ty(value).peel_refs().kind() { | |
81 | ty::Adt(adt, _) => Some(adt.did()) == cx.tcx.lang_items().string(), | |
82 | ty::Str => true, | |
83 | _ => false, | |
84 | } | |
85 | { | |
86 | let is_new_string = match value.kind { | |
87 | ExprKind::Binary(..) => true, | |
88 | ExprKind::MethodCall(path, ..) => path.ident.name == sym::to_string, | |
89 | _ => false, | |
90 | }; | |
91 | let sugg = if is_new_string { | |
ed00b5ec FG |
92 | snippet_with_context(cx, value.span, call_site.ctxt(), "..", &mut applicability) |
93 | .0 | |
94 | .into_owned() | |
353b0b11 FG |
95 | } else { |
96 | let sugg = Sugg::hir_with_context(cx, value, call_site.ctxt(), "<arg>", &mut applicability); | |
97 | format!("{}.to_string()", sugg.maybe_par()) | |
98 | }; | |
99 | span_useless_format(cx, call_site, sugg, applicability); | |
353b0b11 FG |
100 | } |
101 | }, | |
102 | _ => {}, | |
f20569fa | 103 | } |
781aab86 | 104 | } |
f20569fa | 105 | } |
f20569fa XL |
106 | } |
107 | ||
3c0e092e | 108 | fn span_useless_format_empty(cx: &LateContext<'_>, span: Span, sugg: String, applicability: Applicability) { |
136023e0 XL |
109 | span_lint_and_sugg( |
110 | cx, | |
111 | USELESS_FORMAT, | |
112 | span, | |
113 | "useless use of `format!`", | |
3c0e092e | 114 | "consider using `String::new()`", |
136023e0 XL |
115 | sugg, |
116 | applicability, | |
117 | ); | |
f20569fa XL |
118 | } |
119 | ||
3c0e092e XL |
120 | fn span_useless_format(cx: &LateContext<'_>, span: Span, sugg: String, applicability: Applicability) { |
121 | span_lint_and_sugg( | |
122 | cx, | |
123 | USELESS_FORMAT, | |
124 | span, | |
125 | "useless use of `format!`", | |
126 | "consider using `.to_string()`", | |
127 | sugg, | |
128 | applicability, | |
129 | ); | |
f20569fa | 130 | } |