1
- use crate :: utils:: { snippet_with_applicability, span_lint, span_lint_and_sugg} ;
1
+ use crate :: utils:: { snippet_with_applicability, span_lint, span_lint_and_sugg, span_lint_and_then } ;
2
2
use rustc:: lint:: { EarlyContext , EarlyLintPass , LintArray , LintPass } ;
3
3
use rustc:: { declare_lint_pass, declare_tool_lint} ;
4
4
use rustc_errors:: Applicability ;
5
5
use std:: borrow:: Cow ;
6
6
use syntax:: ast:: * ;
7
7
use syntax:: parse:: { parser, token} ;
8
8
use syntax:: tokenstream:: { TokenStream , TokenTree } ;
9
- use syntax_pos:: symbol:: Symbol ;
9
+ use syntax_pos:: { symbol:: Symbol , BytePos , Span } ;
10
10
11
11
declare_clippy_lint ! {
12
12
/// **What it does:** This lint warns when you use `println!("")` to
@@ -184,7 +184,7 @@ impl EarlyLintPass for Write {
184
184
fn check_mac ( & mut self , cx : & EarlyContext < ' _ > , mac : & Mac ) {
185
185
if mac. node . path == sym ! ( println) {
186
186
span_lint ( cx, PRINT_STDOUT , mac. span , "use of `println!`" ) ;
187
- if let Some ( fmtstr) = check_tts ( cx, & mac. node . tts , false ) . 0 {
187
+ if let Some ( ( fmtstr, .. ) ) = check_tts ( cx, & mac. node . tts , false ) . 0 {
188
188
if fmtstr == "" {
189
189
span_lint_and_sugg (
190
190
cx,
@@ -199,32 +199,50 @@ impl EarlyLintPass for Write {
199
199
}
200
200
} else if mac. node . path == sym ! ( print) {
201
201
span_lint ( cx, PRINT_STDOUT , mac. span , "use of `print!`" ) ;
202
- if let ( Some ( fmtstr) , _, is_raw) = check_tts ( cx, & mac. node . tts , false ) {
202
+ if let ( Some ( ( fmtstr, fmtstyle , fmt_sp ) ) , _, is_raw) = check_tts ( cx, & mac. node . tts , false ) {
203
203
if check_newlines ( & fmtstr, is_raw) {
204
- span_lint (
204
+ span_lint_and_then (
205
205
cx,
206
206
PRINT_WITH_NEWLINE ,
207
207
mac. span ,
208
- "using `print!()` with a format string that ends in a \
209
- single newline, consider using `println!()` instead",
208
+ "using `print!()` with a format string that ends in a single newline" ,
209
+ |err| {
210
+ err. multipart_suggestion (
211
+ "use `println!` instead" ,
212
+ vec ! [
213
+ ( mac. node. path. span, String :: from( "println" ) ) ,
214
+ ( newline_span( & fmtstr, fmtstyle, fmt_sp) , String :: new( ) ) ,
215
+ ] ,
216
+ Applicability :: MachineApplicable ,
217
+ ) ;
218
+ } ,
210
219
) ;
211
220
}
212
221
}
213
222
} else if mac. node . path == sym ! ( write) {
214
- if let ( Some ( fmtstr) , _, is_raw) = check_tts ( cx, & mac. node . tts , true ) {
223
+ if let ( Some ( ( fmtstr, fmtstyle , fmt_sp ) ) , _, is_raw) = check_tts ( cx, & mac. node . tts , true ) {
215
224
if check_newlines ( & fmtstr, is_raw) {
216
- span_lint (
225
+ span_lint_and_then (
217
226
cx,
218
227
WRITE_WITH_NEWLINE ,
219
228
mac. span ,
220
- "using `write!()` with a format string that ends in a \
221
- single newline, consider using `writeln!()` instead",
222
- ) ;
229
+ "using `write!()` with a format string that ends in a single newline" ,
230
+ |err| {
231
+ err. multipart_suggestion (
232
+ "use `writeln!()` instead" ,
233
+ vec ! [
234
+ ( mac. node. path. span, String :: from( "writeln" ) ) ,
235
+ ( newline_span( & fmtstr, fmtstyle, fmt_sp) , String :: new( ) ) ,
236
+ ] ,
237
+ Applicability :: MachineApplicable ,
238
+ ) ;
239
+ } ,
240
+ )
223
241
}
224
242
}
225
243
} else if mac. node . path == sym ! ( writeln) {
226
244
let check_tts = check_tts ( cx, & mac. node . tts , true ) ;
227
- if let Some ( fmtstr) = check_tts. 0 {
245
+ if let Some ( ( fmtstr, .. ) ) = check_tts. 0 {
228
246
if fmtstr == "" {
229
247
let mut applicability = Applicability :: MachineApplicable ;
230
248
let suggestion = check_tts. 1 . map_or_else (
@@ -250,10 +268,28 @@ impl EarlyLintPass for Write {
250
268
}
251
269
}
252
270
271
+ /// Given a format string that ends in a newline and its span, calculates the span of the newline.
272
+ fn newline_span ( fmt_str : & str , str_style : StrStyle , sp : Span ) -> Span {
273
+ let newline_sp_hi = sp. hi ( )
274
+ - match str_style {
275
+ StrStyle :: Cooked => BytePos ( 1 ) ,
276
+ StrStyle :: Raw ( hashes) => BytePos ( ( 1 + hashes) . into ( ) ) ,
277
+ } ;
278
+
279
+ let newline_sp_len = if fmt_str. ends_with ( '\n' ) {
280
+ BytePos ( 1 )
281
+ } else {
282
+ BytePos ( 2 )
283
+ } ;
284
+
285
+ sp. with_lo ( newline_sp_hi - newline_sp_len) . with_hi ( newline_sp_hi)
286
+ }
287
+
253
288
/// Checks the arguments of `print[ln]!` and `write[ln]!` calls. It will return a tuple of two
254
- /// options and a bool. The first part of the tuple is `format_str` of the macros. The second part
255
- /// of the tuple is in the `write[ln]!` case the expression the `format_str` should be written to.
256
- /// The final part is a boolean flag indicating if the string is a raw string.
289
+ /// `Option`s. The first `Option` of the tuple is the macro's `format_str`. It includes
290
+ /// the contents of the string, whether it's a raw string, and the span of the literal in the
291
+ /// source. The second `Option` in the tuple is, in the `write[ln]!` case, the expression the
292
+ /// `format_str` should be written to.
257
293
///
258
294
/// Example:
259
295
///
@@ -266,9 +302,13 @@ impl EarlyLintPass for Write {
266
302
/// ```
267
303
/// will return
268
304
/// ```rust,ignore
269
- /// (Some("string to write: {}"), Some(buf), false )
305
+ /// (Some(( "string to write: {}"), StrStyle::Cooked, "1:15-1:36"), Some(buf))
270
306
/// ```
271
- fn check_tts < ' a > ( cx : & EarlyContext < ' a > , tts : & TokenStream , is_write : bool ) -> ( Option < String > , Option < Expr > , bool ) {
307
+ fn check_tts < ' a > (
308
+ cx : & EarlyContext < ' a > ,
309
+ tts : & TokenStream ,
310
+ is_write : bool ,
311
+ ) -> ( Option < ( String , StrStyle , Span ) > , Option < Expr > , bool ) {
272
312
use fmt_macros:: * ;
273
313
let tts = tts. clone ( ) ;
274
314
let mut is_raw = false ;
@@ -299,10 +339,11 @@ fn check_tts<'a>(cx: &EarlyContext<'a>, tts: &TokenStream, is_write: bool) -> (O
299
339
}
300
340
}
301
341
302
- let fmtstr = match parser. parse_str ( ) . map_err ( |mut err| err. cancel ( ) ) {
303
- Ok ( token ) => token . 0 . to_string ( ) ,
342
+ let ( fmtstr, fmtstyle ) = match parser. parse_str ( ) . map_err ( |mut err| err. cancel ( ) ) {
343
+ Ok ( ( fmtstr , fmtstyle ) ) => ( fmtstr . to_string ( ) , fmtstyle ) ,
304
344
Err ( _) => return ( None , expr, is_raw) ,
305
345
} ;
346
+ let fmtspan = parser. prev_span ;
306
347
let tmp = fmtstr. clone ( ) ;
307
348
let mut args = vec ! [ ] ;
308
349
let mut fmt_parser = Parser :: new ( & tmp, None , Vec :: new ( ) , false ) ;
@@ -330,11 +371,11 @@ fn check_tts<'a>(cx: &EarlyContext<'a>, tts: &TokenStream, is_write: bool) -> (O
330
371
ty : "" ,
331
372
} ;
332
373
if !parser. eat ( & token:: Comma ) {
333
- return ( Some ( fmtstr) , expr, is_raw) ;
374
+ return ( Some ( ( fmtstr, fmtstyle , fmtspan ) ) , expr, is_raw) ;
334
375
}
335
376
let token_expr = match parser. parse_expr ( ) . map_err ( |mut err| err. cancel ( ) ) {
336
377
Ok ( expr) => expr,
337
- Err ( _) => return ( Some ( fmtstr) , None , is_raw) ,
378
+ Err ( _) => return ( Some ( ( fmtstr, fmtstyle , fmtspan ) ) , None , is_raw) ,
338
379
} ;
339
380
match & token_expr. node {
340
381
ExprKind :: Lit ( _) => {
0 commit comments