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 } ;
22use rustc:: lint:: { EarlyContext , EarlyLintPass , LintArray , LintPass } ;
33use rustc:: { declare_lint_pass, declare_tool_lint} ;
44use rustc_errors:: Applicability ;
55use std:: borrow:: Cow ;
66use syntax:: ast:: * ;
77use syntax:: parse:: { parser, token} ;
8- use syntax:: tokenstream:: { TokenStream , TokenTree } ;
9- use syntax_pos:: symbol:: Symbol ;
8+ use syntax:: tokenstream:: TokenStream ;
9+ use syntax_pos:: { symbol:: Symbol , BytePos , Span } ;
1010
1111declare_clippy_lint ! {
1212 /// **What it does:** This lint warns when you use `println!("")` to
@@ -184,8 +184,8 @@ impl EarlyLintPass for Write {
184184 fn check_mac ( & mut self , cx : & EarlyContext < ' _ > , mac : & Mac ) {
185185 if mac. node . path == sym ! ( println) {
186186 span_lint ( cx, PRINT_STDOUT , mac. span , "use of `println!`" ) ;
187- if let Some ( fmtstr ) = check_tts ( cx, & mac. node . tts , false ) . 0 {
188- if fmtstr == "" {
187+ if let ( Some ( fmt_str ) , _ ) = check_tts ( cx, & mac. node . tts , false ) {
188+ if fmt_str . contents . is_empty ( ) {
189189 span_lint_and_sugg (
190190 cx,
191191 PRINTLN_EMPTY_STRING ,
@@ -199,35 +199,52 @@ impl EarlyLintPass for Write {
199199 }
200200 } else if mac. node . path == sym ! ( print) {
201201 span_lint ( cx, PRINT_STDOUT , mac. span , "use of `print!`" ) ;
202- if let ( Some ( fmtstr ) , _, is_raw ) = check_tts ( cx, & mac. node . tts , false ) {
203- if check_newlines ( & fmtstr , is_raw ) {
204- span_lint (
202+ if let ( Some ( fmt_str ) , _) = check_tts ( cx, & mac. node . tts , false ) {
203+ if check_newlines ( & fmt_str ) {
204+ span_lint_and_then (
205205 cx,
206206 PRINT_WITH_NEWLINE ,
207207 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+ ( fmt_str. newline_span( ) , String :: new( ) ) ,
215+ ] ,
216+ Applicability :: MachineApplicable ,
217+ ) ;
218+ } ,
210219 ) ;
211220 }
212221 }
213222 } else if mac. node . path == sym ! ( write) {
214- if let ( Some ( fmtstr ) , _, is_raw ) = check_tts ( cx, & mac. node . tts , true ) {
215- if check_newlines ( & fmtstr , is_raw ) {
216- span_lint (
223+ if let ( Some ( fmt_str ) , _) = check_tts ( cx, & mac. node . tts , true ) {
224+ if check_newlines ( & fmt_str ) {
225+ span_lint_and_then (
217226 cx,
218227 WRITE_WITH_NEWLINE ,
219228 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+ ( fmt_str. newline_span( ) , String :: new( ) ) ,
236+ ] ,
237+ Applicability :: MachineApplicable ,
238+ ) ;
239+ } ,
240+ )
223241 }
224242 }
225243 } else if mac. node . path == sym ! ( writeln) {
226- let check_tts = check_tts ( cx, & mac. node . tts , true ) ;
227- if let Some ( fmtstr) = check_tts. 0 {
228- if fmtstr == "" {
244+ if let ( Some ( fmt_str) , expr) = check_tts ( cx, & mac. node . tts , true ) {
245+ if fmt_str. contents . is_empty ( ) {
229246 let mut applicability = Applicability :: MachineApplicable ;
230- let suggestion = check_tts . 1 . map_or_else (
247+ let suggestion = expr . map_or_else (
231248 move || {
232249 applicability = Applicability :: HasPlaceholders ;
233250 Cow :: Borrowed ( "v" )
@@ -250,10 +267,44 @@ impl EarlyLintPass for Write {
250267 }
251268}
252269
270+ /// The arguments of a `print[ln]!` or `write[ln]!` invocation.
271+ struct FmtStr {
272+ /// The contents of the format string (inside the quotes).
273+ contents : String ,
274+ style : StrStyle ,
275+ /// The span of the format string, including quotes, the raw marker, and any raw hashes.
276+ span : Span ,
277+ }
278+
279+ impl FmtStr {
280+ /// Given a format string that ends in a newline and its span, calculates the span of the
281+ /// newline.
282+ fn newline_span ( & self ) -> Span {
283+ let sp = self . span ;
284+
285+ let newline_sp_hi = sp. hi ( )
286+ - match self . style {
287+ StrStyle :: Cooked => BytePos ( 1 ) ,
288+ StrStyle :: Raw ( hashes) => BytePos ( ( 1 + hashes) . into ( ) ) ,
289+ } ;
290+
291+ let newline_sp_len = if self . contents . ends_with ( '\n' ) {
292+ BytePos ( 1 )
293+ } else if self . contents . ends_with ( r"\n" ) {
294+ BytePos ( 2 )
295+ } else {
296+ panic ! ( "expected format string to contain a newline" ) ;
297+ } ;
298+
299+ sp. with_lo ( newline_sp_hi - newline_sp_len) . with_hi ( newline_sp_hi)
300+ }
301+ }
302+
253303/// 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.
304+ /// `Option`s. The first `Option` of the tuple is the macro's format string. It includes
305+ /// the contents of the string, whether it's a raw string, and the span of the literal in the
306+ /// source. The second `Option` in the tuple is, in the `write[ln]!` case, the expression the
307+ /// `format_str` should be written to.
257308///
258309/// Example:
259310///
@@ -266,49 +317,36 @@ impl EarlyLintPass for Write {
266317/// ```
267318/// will return
268319/// ```rust,ignore
269- /// (Some("string to write: {}"), Some(buf), false )
320+ /// (Some("string to write: {}"), Some(buf))
270321/// ```
271- fn check_tts < ' a > ( cx : & EarlyContext < ' a > , tts : & TokenStream , is_write : bool ) -> ( Option < String > , Option < Expr > , bool ) {
322+ fn check_tts < ' a > ( cx : & EarlyContext < ' a > , tts : & TokenStream , is_write : bool ) -> ( Option < FmtStr > , Option < Expr > ) {
272323 use fmt_macros:: * ;
273324 let tts = tts. clone ( ) ;
274- let mut is_raw = false ;
275- if let TokenStream ( Some ( tokens) ) = & tts {
276- for token in tokens. iter ( ) {
277- if let ( TokenTree :: Token ( _, token:: Token :: Literal ( lit) ) , _) = token {
278- match lit. kind {
279- token:: Str => break ,
280- token:: StrRaw ( _) => {
281- is_raw = true ;
282- break ;
283- } ,
284- _ => { } ,
285- }
286- }
287- }
288- }
325+
289326 let mut parser = parser:: Parser :: new ( & cx. sess . parse_sess , tts, None , false , false , None ) ;
290327 let mut expr: Option < Expr > = None ;
291328 if is_write {
292329 expr = match parser. parse_expr ( ) . map_err ( |mut err| err. cancel ( ) ) {
293330 Ok ( p) => Some ( p. into_inner ( ) ) ,
294- Err ( _) => return ( None , None , is_raw ) ,
331+ Err ( _) => return ( None , None ) ,
295332 } ;
296333 // might be `writeln!(foo)`
297334 if parser. expect ( & token:: Comma ) . map_err ( |mut err| err. cancel ( ) ) . is_err ( ) {
298- return ( None , expr, is_raw ) ;
335+ return ( None , expr) ;
299336 }
300337 }
301338
302- let fmtstr = match parser. parse_str ( ) . map_err ( |mut err| err. cancel ( ) ) {
303- Ok ( token ) => token . 0 . to_string ( ) ,
304- Err ( _) => return ( None , expr, is_raw ) ,
339+ let ( fmtstr, fmtstyle ) = match parser. parse_str ( ) . map_err ( |mut err| err. cancel ( ) ) {
340+ Ok ( ( fmtstr , fmtstyle ) ) => ( fmtstr . to_string ( ) , fmtstyle ) ,
341+ Err ( _) => return ( None , expr) ,
305342 } ;
343+ let fmtspan = parser. prev_span ;
306344 let tmp = fmtstr. clone ( ) ;
307345 let mut args = vec ! [ ] ;
308346 let mut fmt_parser = Parser :: new ( & tmp, None , Vec :: new ( ) , false ) ;
309347 while let Some ( piece) = fmt_parser. next ( ) {
310348 if !fmt_parser. errors . is_empty ( ) {
311- return ( None , expr, is_raw ) ;
349+ return ( None , expr) ;
312350 }
313351 if let Piece :: NextArgument ( arg) = piece {
314352 if arg. format . ty == "?" {
@@ -330,11 +368,26 @@ fn check_tts<'a>(cx: &EarlyContext<'a>, tts: &TokenStream, is_write: bool) -> (O
330368 ty : "" ,
331369 } ;
332370 if !parser. eat ( & token:: Comma ) {
333- return ( Some ( fmtstr) , expr, is_raw) ;
371+ return (
372+ Some ( FmtStr {
373+ contents : fmtstr,
374+ style : fmtstyle,
375+ span : fmtspan,
376+ } ) ,
377+ expr,
378+ ) ;
334379 }
335- let token_expr = match parser. parse_expr ( ) . map_err ( |mut err| err. cancel ( ) ) {
336- Ok ( expr) => expr,
337- Err ( _) => return ( Some ( fmtstr) , None , is_raw) ,
380+ let token_expr = if let Ok ( expr) = parser. parse_expr ( ) . map_err ( |mut err| err. cancel ( ) ) {
381+ expr
382+ } else {
383+ return (
384+ Some ( FmtStr {
385+ contents : fmtstr,
386+ style : fmtstyle,
387+ span : fmtspan,
388+ } ) ,
389+ None ,
390+ ) ;
338391 } ;
339392 match & token_expr. node {
340393 ExprKind :: Lit ( _) => {
@@ -383,12 +436,15 @@ fn check_tts<'a>(cx: &EarlyContext<'a>, tts: &TokenStream, is_write: bool) -> (O
383436 }
384437}
385438
386- // Checks if `s` constains a single newline that terminates it
387- // Literal and escaped newlines are both checked (only literal for raw strings)
388- fn check_newlines ( s : & str , is_raw : bool ) -> bool {
439+ /// Checks if the format string constains a single newline that terminates it.
440+ ///
441+ /// Literal and escaped newlines are both checked (only literal for raw strings).
442+ fn check_newlines ( fmt_str : & FmtStr ) -> bool {
443+ let s = & fmt_str. contents ;
444+
389445 if s. ends_with ( '\n' ) {
390446 return true ;
391- } else if is_raw {
447+ } else if let StrStyle :: Raw ( _ ) = fmt_str . style {
392448 return false ;
393449 }
394450
0 commit comments