1+ use std:: borrow:: Cow ;
2+ use std:: ops:: Range ;
3+
14use crate :: utils:: { snippet_with_applicability, span_lint, span_lint_and_sugg, span_lint_and_then} ;
25use rustc:: lint:: { EarlyContext , EarlyLintPass , LintArray , LintPass } ;
36use rustc:: { declare_lint_pass, declare_tool_lint} ;
47use rustc_errors:: Applicability ;
8+ use rustc_lexer:: unescape:: { self , EscapeError } ;
59use rustc_parse:: parser;
6- use std:: borrow:: Cow ;
710use syntax:: ast:: * ;
811use syntax:: token;
912use syntax:: tokenstream:: TokenStream ;
@@ -202,7 +205,7 @@ impl EarlyLintPass for Write {
202205 } else if mac. path == sym ! ( print) {
203206 span_lint ( cx, PRINT_STDOUT , mac. span , "use of `print!`" ) ;
204207 if let ( Some ( fmt_str) , _) = check_tts ( cx, & mac. tts , false ) {
205- if check_newlines ( & fmt_str) {
208+ if check_newlines ( & fmt_str. contents , fmt_str . style ) {
206209 span_lint_and_then (
207210 cx,
208211 PRINT_WITH_NEWLINE ,
@@ -223,7 +226,7 @@ impl EarlyLintPass for Write {
223226 }
224227 } else if mac. path == sym ! ( write) {
225228 if let ( Some ( fmt_str) , _) = check_tts ( cx, & mac. tts , true ) {
226- if check_newlines ( & fmt_str) {
229+ if check_newlines ( & fmt_str. contents , fmt_str . style ) {
227230 span_lint_and_then (
228231 cx,
229232 WRITE_WITH_NEWLINE ,
@@ -442,38 +445,31 @@ fn check_tts<'a>(cx: &EarlyContext<'a>, tts: &TokenStream, is_write: bool) -> (O
442445 }
443446}
444447
445- /// Checks if the format string constains a single newline that terminates it.
448+ /// Checks if the format string contains a single newline that terminates it.
446449///
447450/// Literal and escaped newlines are both checked (only literal for raw strings).
448- fn check_newlines ( fmt_str : & FmtStr ) -> bool {
449- let s = & fmt_str. contents ;
451+ fn check_newlines ( contents : & str , style : StrStyle ) -> bool {
452+ let mut has_internal_newline = false ;
453+ let mut last_was_cr = false ;
454+ let mut should_lint = false ;
450455
451- if s. ends_with ( '\n' ) {
452- return true ;
453- } else if let StrStyle :: Raw ( _) = fmt_str. style {
454- return false ;
455- }
456-
457- if s. len ( ) < 2 {
458- return false ;
459- }
456+ let mut cb = |r : Range < usize > , c : Result < char , EscapeError > | {
457+ let c = c. unwrap ( ) ;
460458
461- let bytes = s. as_bytes ( ) ;
462- if bytes[ bytes. len ( ) - 2 ] != b'\\' || bytes[ bytes. len ( ) - 1 ] != b'n' {
463- return false ;
464- }
465-
466- let mut escaping = false ;
467- for ( index, & byte) in bytes. iter ( ) . enumerate ( ) {
468- if escaping {
469- if byte == b'n' {
470- return index == bytes. len ( ) - 1 ;
459+ if r. end == contents. len ( ) && c == '\n' && !last_was_cr && !has_internal_newline {
460+ should_lint = true ;
461+ } else {
462+ last_was_cr = c == '\r' ;
463+ if c == '\n' {
464+ has_internal_newline = true ;
471465 }
472- escaping = false ;
473- } else if byte == b'\\' {
474- escaping = true ;
475466 }
467+ } ;
468+
469+ match style {
470+ StrStyle :: Cooked => unescape:: unescape_str ( contents, & mut cb) ,
471+ StrStyle :: Raw ( _) => unescape:: unescape_raw_str ( contents, & mut cb) ,
476472 }
477473
478- false
474+ should_lint
479475}
0 commit comments