1+ use std:: { iter:: once, ops:: ControlFlow } ;
2+
13use clippy_utils:: { diagnostics:: span_lint_and_sugg, source:: snippet} ;
24use rustc_ast:: {
35 ast:: { Expr , ExprKind } ,
@@ -13,7 +15,8 @@ declare_clippy_lint! {
1315 /// Checks for raw string literals where a string literal can be used instead.
1416 ///
1517 /// ### Why is this bad?
16- /// It's just unnecessary.
18+ /// It's just unnecessary, but there are many cases where using a raw string literal is more
19+ /// idiomatic than a string literal, so it's opt-in.
1720 ///
1821 /// ### Example
1922 /// ```rust
@@ -24,8 +27,8 @@ declare_clippy_lint! {
2427 /// let r = "Hello, world!";
2528 /// ```
2629 #[ clippy:: version = "1.72.0" ]
27- pub NEEDLESS_RAW_STRING ,
28- complexity ,
30+ pub NEEDLESS_RAW_STRINGS ,
31+ restriction ,
2932 "suggests using a string literal when a raw string literal is unnecessary"
3033}
3134declare_clippy_lint ! {
@@ -46,10 +49,10 @@ declare_clippy_lint! {
4649 /// ```
4750 #[ clippy:: version = "1.72.0" ]
4851 pub NEEDLESS_RAW_STRING_HASHES ,
49- complexity ,
52+ style ,
5053 "suggests reducing the number of hashes around a raw string literal"
5154}
52- impl_lint_pass ! ( RawStrings => [ NEEDLESS_RAW_STRING , NEEDLESS_RAW_STRING_HASHES ] ) ;
55+ impl_lint_pass ! ( RawStrings => [ NEEDLESS_RAW_STRINGS , NEEDLESS_RAW_STRING_HASHES ] ) ;
5356
5457pub struct RawStrings {
5558 pub needless_raw_string_hashes_allow_one : bool ,
@@ -59,8 +62,9 @@ impl EarlyLintPass for RawStrings {
5962 fn check_expr ( & mut self , cx : & EarlyContext < ' _ > , expr : & Expr ) {
6063 if !in_external_macro ( cx. sess ( ) , expr. span )
6164 && let ExprKind :: Lit ( lit) = expr. kind
62- && let LitKind :: StrRaw ( num ) | LitKind :: ByteStrRaw ( num ) | LitKind :: CStrRaw ( num ) = lit. kind
65+ && let LitKind :: StrRaw ( max ) | LitKind :: ByteStrRaw ( max ) | LitKind :: CStrRaw ( max ) = lit. kind
6366 {
67+ let str = lit. symbol . as_str ( ) ;
6468 let prefix = match lit. kind {
6569 LitKind :: StrRaw ( ..) => "r" ,
6670 LitKind :: ByteStrRaw ( ..) => "br" ,
@@ -71,10 +75,10 @@ impl EarlyLintPass for RawStrings {
7175 return ;
7276 }
7377
74- if !lit . symbol . as_str ( ) . contains ( [ '\\' , '"' ] ) {
78+ if !str . contains ( [ '\\' , '"' ] ) {
7579 span_lint_and_sugg (
7680 cx,
77- NEEDLESS_RAW_STRING ,
81+ NEEDLESS_RAW_STRINGS ,
7882 expr. span ,
7983 "unnecessary raw string literal" ,
8084 "try" ,
@@ -85,15 +89,43 @@ impl EarlyLintPass for RawStrings {
8589 return ;
8690 }
8791
88- #[ expect( clippy:: cast_possible_truncation) ]
89- let req = lit. symbol . as_str ( ) . as_bytes ( )
90- . split ( |& b| b == b'"' )
91- . skip ( 1 )
92- . map ( |bs| 1 + bs. iter ( ) . take_while ( |& & b| b == b'#' ) . count ( ) as u8 )
93- . max ( )
94- . unwrap_or ( 0 ) ;
92+ let req = {
93+ let mut following_quote = false ;
94+ let mut req = 0 ;
95+ // `once` so a raw string ending in hashes is still checked
96+ let num = str. as_bytes ( ) . iter ( ) . chain ( once ( & 0 ) ) . try_fold ( 0u8 , |acc, & b| {
97+ match b {
98+ b'"' => ( following_quote, req) = ( true , 1 ) ,
99+ // I'm a bit surprised the compiler didn't optimize this out, there's no
100+ // branch but it still ends up doing an unnecessary comparison, it's:
101+ // - cmp r9b,1h
102+ // - sbb cl,-1h
103+ // which will add 1 if it's true. With this change, it becomes:
104+ // - add cl,r9b
105+ // isn't that so much nicer?
106+ b'#' => req += u8:: from ( following_quote) ,
107+ _ => {
108+ if following_quote {
109+ following_quote = false ;
110+
111+ if req == max {
112+ return ControlFlow :: Break ( req) ;
113+ }
114+
115+ return ControlFlow :: Continue ( acc. max ( req) ) ;
116+ }
117+ } ,
118+ }
119+
120+ ControlFlow :: Continue ( acc)
121+ } ) ;
122+
123+ match num {
124+ ControlFlow :: Continue ( num) | ControlFlow :: Break ( num) => num,
125+ }
126+ } ;
95127
96- if req < num {
128+ if req < max {
97129 let hashes = "#" . repeat ( req as usize ) ;
98130
99131 span_lint_and_sugg (
0 commit comments