1
1
use clippy_utils:: diagnostics:: { span_lint_and_sugg, span_lint_and_then} ;
2
2
use clippy_utils:: macros:: FormatParamKind :: { Implicit , Named , Numbered , Starred } ;
3
- use clippy_utils:: macros:: { is_format_macro, is_panic, FormatArgsExpn , FormatParam , FormatParamUsage } ;
3
+ use clippy_utils:: macros:: {
4
+ is_format_macro, is_panic, root_macro_call, Count , FormatArg , FormatArgsExpn , FormatParam , FormatParamUsage ,
5
+ } ;
4
6
use clippy_utils:: source:: snippet_opt;
5
- use clippy_utils:: ty:: implements_trait;
7
+ use clippy_utils:: ty:: { implements_trait, is_type_diagnostic_item } ;
6
8
use clippy_utils:: { is_diag_trait_item, meets_msrv, msrvs} ;
7
9
use if_chain:: if_chain;
8
10
use itertools:: Itertools ;
@@ -117,7 +119,43 @@ declare_clippy_lint! {
117
119
"using non-inlined variables in `format!` calls"
118
120
}
119
121
120
- impl_lint_pass ! ( FormatArgs => [ FORMAT_IN_FORMAT_ARGS , UNINLINED_FORMAT_ARGS , TO_STRING_IN_FORMAT_ARGS ] ) ;
122
+ declare_clippy_lint ! {
123
+ /// ### What it does
124
+ /// Detects [formatting parameters] that have no effect on the output of a
125
+ /// `format!()`, `println!()` or similar macro.
126
+ ///
127
+ /// ### Why is this bad?
128
+ /// Shorter format specifiers are easier to read, it may also indicate that
129
+ /// an expected formatting operation such as adding padding isn't happening.
130
+ ///
131
+ /// ### Example
132
+ /// ```rust
133
+ /// println!("{:.}", 1.0);
134
+ ///
135
+ /// println!("not padded: {:5}", format_args!("..."));
136
+ /// ```
137
+ /// Use instead:
138
+ /// ```rust
139
+ /// println!("{}", 1.0);
140
+ ///
141
+ /// println!("not padded: {}", format_args!("..."));
142
+ /// // OR
143
+ /// println!("padded: {:5}", format!("..."));
144
+ /// ```
145
+ ///
146
+ /// [formatting parameters]: https://doc.rust-lang.org/std/fmt/index.html#formatting-parameters
147
+ #[ clippy:: version = "1.66.0" ]
148
+ pub UNUSED_FORMAT_SPECS ,
149
+ complexity,
150
+ "use of a format specifier that has no effect"
151
+ }
152
+
153
+ impl_lint_pass ! ( FormatArgs => [
154
+ FORMAT_IN_FORMAT_ARGS ,
155
+ TO_STRING_IN_FORMAT_ARGS ,
156
+ UNINLINED_FORMAT_ARGS ,
157
+ UNUSED_FORMAT_SPECS ,
158
+ ] ) ;
121
159
122
160
pub struct FormatArgs {
123
161
msrv : Option < RustcVersion > ,
@@ -132,34 +170,103 @@ impl FormatArgs {
132
170
133
171
impl < ' tcx > LateLintPass < ' tcx > for FormatArgs {
134
172
fn check_expr ( & mut self , cx : & LateContext < ' tcx > , expr : & ' tcx Expr < ' tcx > ) {
135
- if_chain ! {
136
- if let Some ( format_args) = FormatArgsExpn :: parse( cx, expr) ;
137
- let expr_expn_data = expr. span. ctxt( ) . outer_expn_data( ) ;
138
- let outermost_expn_data = outermost_expn_data( expr_expn_data) ;
139
- if let Some ( macro_def_id) = outermost_expn_data. macro_def_id;
140
- if is_format_macro( cx, macro_def_id) ;
141
- if let ExpnKind :: Macro ( _, name) = outermost_expn_data. kind;
142
- then {
143
- for arg in & format_args. args {
144
- if !arg. format. is_default( ) {
145
- continue ;
146
- }
147
- if is_aliased( & format_args, arg. param. value. hir_id) {
148
- continue ;
149
- }
150
- check_format_in_format_args( cx, outermost_expn_data. call_site, name, arg. param. value) ;
151
- check_to_string_in_format_args( cx, name, arg. param. value) ;
173
+ if let Some ( format_args) = FormatArgsExpn :: parse ( cx, expr)
174
+ && let expr_expn_data = expr. span . ctxt ( ) . outer_expn_data ( )
175
+ && let outermost_expn_data = outermost_expn_data ( expr_expn_data)
176
+ && let Some ( macro_def_id) = outermost_expn_data. macro_def_id
177
+ && is_format_macro ( cx, macro_def_id)
178
+ && let ExpnKind :: Macro ( _, name) = outermost_expn_data. kind
179
+ {
180
+ for arg in & format_args. args {
181
+ check_unused_format_specifier ( cx, arg) ;
182
+ if !arg. format . is_default ( ) {
183
+ continue ;
152
184
}
153
- if meets_msrv ( self . msrv , msrvs :: FORMAT_ARGS_CAPTURE ) {
154
- check_uninlined_args ( cx , & format_args , outermost_expn_data . call_site , macro_def_id ) ;
185
+ if is_aliased ( & format_args , arg . param . value . hir_id ) {
186
+ continue ;
155
187
}
188
+ check_format_in_format_args ( cx, outermost_expn_data. call_site , name, arg. param . value ) ;
189
+ check_to_string_in_format_args ( cx, name, arg. param . value ) ;
190
+ }
191
+ if meets_msrv ( self . msrv , msrvs:: FORMAT_ARGS_CAPTURE ) {
192
+ check_uninlined_args ( cx, & format_args, outermost_expn_data. call_site , macro_def_id) ;
156
193
}
157
194
}
158
195
}
159
196
160
197
extract_msrv_attr ! ( LateContext ) ;
161
198
}
162
199
200
+ fn check_unused_format_specifier ( cx : & LateContext < ' _ > , arg : & FormatArg < ' _ > ) {
201
+ let param_ty = cx. typeck_results ( ) . expr_ty ( arg. param . value ) . peel_refs ( ) ;
202
+
203
+ if let Count :: Implied ( Some ( mut span) ) = arg. format . precision
204
+ && !span. is_empty ( )
205
+ {
206
+ span_lint_and_then (
207
+ cx,
208
+ UNUSED_FORMAT_SPECS ,
209
+ span,
210
+ "empty precision specifier has no effect" ,
211
+ |diag| {
212
+ if param_ty. is_floating_point ( ) {
213
+ diag. note ( "a precision specifier is not required to format floats" ) ;
214
+ }
215
+
216
+ if arg. format . is_default ( ) {
217
+ // If there's no other specifiers remove the `:` too
218
+ span = arg. format_span ( ) ;
219
+ }
220
+
221
+ diag. span_suggestion_verbose ( span, "remove the `.`" , "" , Applicability :: MachineApplicable ) ;
222
+ } ,
223
+ ) ;
224
+ }
225
+
226
+ if is_type_diagnostic_item ( cx, param_ty, sym:: Arguments ) && !arg. format . is_default_for_trait ( ) {
227
+ span_lint_and_then (
228
+ cx,
229
+ UNUSED_FORMAT_SPECS ,
230
+ arg. span ,
231
+ "format specifiers have no effect on `format_args!()`" ,
232
+ |diag| {
233
+ let mut suggest_format = |spec, span| {
234
+ let message = format ! ( "for the {spec} to apply consider using `format!()`" ) ;
235
+
236
+ if let Some ( mac_call) = root_macro_call ( arg. param . value . span )
237
+ && cx. tcx . is_diagnostic_item ( sym:: format_args_macro, mac_call. def_id )
238
+ && arg. span . eq_ctxt ( mac_call. span )
239
+ {
240
+ diag. span_suggestion (
241
+ cx. sess ( ) . source_map ( ) . span_until_char ( mac_call. span , '!' ) ,
242
+ message,
243
+ "format" ,
244
+ Applicability :: MaybeIncorrect ,
245
+ ) ;
246
+ } else if let Some ( span) = span {
247
+ diag. span_help ( span, message) ;
248
+ }
249
+ } ;
250
+
251
+ if !arg. format . width . is_implied ( ) {
252
+ suggest_format ( "width" , arg. format . width . span ( ) ) ;
253
+ }
254
+
255
+ if !arg. format . precision . is_implied ( ) {
256
+ suggest_format ( "precision" , arg. format . precision . span ( ) ) ;
257
+ }
258
+
259
+ diag. span_suggestion_verbose (
260
+ arg. format_span ( ) ,
261
+ "if the current behavior is intentional, remove the format specifiers" ,
262
+ "" ,
263
+ Applicability :: MaybeIncorrect ,
264
+ ) ;
265
+ } ,
266
+ ) ;
267
+ }
268
+ }
269
+
163
270
fn check_uninlined_args ( cx : & LateContext < ' _ > , args : & FormatArgsExpn < ' _ > , call_site : Span , def_id : DefId ) {
164
271
if args. format_string . span . from_expansion ( ) {
165
272
return ;
0 commit comments