11use clippy_config:: Conf ;
22use clippy_utils:: diagnostics:: span_lint_and_then;
3- use clippy_utils:: source:: SpanRangeExt ;
3+ use clippy_utils:: source:: { SpanRangeExt , snippet_opt } ;
44use rustc_ast:: ast:: { Expr , ExprKind } ;
55use rustc_ast:: token:: LitKind ;
66use rustc_errors:: Applicability ;
@@ -71,6 +71,23 @@ impl RawStrings {
7171
7272impl EarlyLintPass for RawStrings {
7373 fn check_expr ( & mut self , cx : & EarlyContext < ' _ > , expr : & Expr ) {
74+ if let ExprKind :: FormatArgs ( format_args) = & expr. kind
75+ && !in_external_macro ( cx. sess ( ) , format_args. span )
76+ && format_args. span . check_source_text ( cx, |src| src. starts_with ( 'r' ) )
77+ && let Some ( str) = snippet_opt ( cx. sess ( ) , format_args. span )
78+ && let count_hash = str. bytes ( ) . skip ( 1 ) . take_while ( |b| * b == b'#' ) . count ( )
79+ && let Some ( str) = str. get ( count_hash + 2 ..str. len ( ) - count_hash - 1 )
80+ {
81+ self . check_raw_string (
82+ cx,
83+ str,
84+ format_args. span ,
85+ "r" ,
86+ u8:: try_from ( count_hash) . unwrap ( ) ,
87+ "string" ,
88+ ) ;
89+ }
90+
7491 if let ExprKind :: Lit ( lit) = expr. kind
7592 && let ( prefix, max) = match lit. kind {
7693 LitKind :: StrRaw ( max) => ( "r" , max) ,
@@ -81,94 +98,105 @@ impl EarlyLintPass for RawStrings {
8198 && !in_external_macro ( cx. sess ( ) , expr. span )
8299 && expr. span . check_source_text ( cx, |src| src. starts_with ( prefix) )
83100 {
84- let str = lit. symbol . as_str ( ) ;
85- let descr = lit. kind . descr ( ) ;
86-
87- if !str. contains ( [ '\\' , '"' ] ) {
88- span_lint_and_then (
89- cx,
90- NEEDLESS_RAW_STRINGS ,
91- expr. span ,
92- "unnecessary raw string literal" ,
93- |diag| {
94- let ( start, end) = hash_spans ( expr. span , prefix. len ( ) , 0 , max) ;
95-
96- // BytePos: skip over the `b` in `br`, we checked the prefix appears in the source text
97- let r_pos = expr. span . lo ( ) + BytePos :: from_usize ( prefix. len ( ) - 1 ) ;
98- let start = start. with_lo ( r_pos) ;
99-
100- let mut remove = vec ! [ ( start, String :: new( ) ) ] ;
101- // avoid debug ICE from empty suggestions
102- if !end. is_empty ( ) {
103- remove. push ( ( end, String :: new ( ) ) ) ;
104- }
101+ self . check_raw_string ( cx, lit. symbol . as_str ( ) , expr. span , prefix, max, lit. kind . descr ( ) ) ;
102+ }
103+ }
104+ }
105105
106- diag. multipart_suggestion_verbose (
107- format ! ( "use a plain {descr} literal instead" ) ,
108- remove,
109- Applicability :: MachineApplicable ,
110- ) ;
111- } ,
112- ) ;
113- if !matches ! ( cx. get_lint_level( NEEDLESS_RAW_STRINGS ) , rustc_lint:: Allow ) {
114- return ;
115- }
106+ impl RawStrings {
107+ fn check_raw_string (
108+ & mut self ,
109+ cx : & EarlyContext < ' _ > ,
110+ str : & str ,
111+ lit_span : Span ,
112+ prefix : & str ,
113+ max : u8 ,
114+ descr : & str ,
115+ ) {
116+ if !str. contains ( [ '\\' , '"' ] ) {
117+ span_lint_and_then (
118+ cx,
119+ NEEDLESS_RAW_STRINGS ,
120+ lit_span,
121+ "unnecessary raw string literal" ,
122+ |diag| {
123+ let ( start, end) = hash_spans ( lit_span, prefix. len ( ) , 0 , max) ;
124+
125+ // BytePos: skip over the `b` in `br`, we checked the prefix appears in the source text
126+ let r_pos = lit_span. lo ( ) + BytePos :: from_usize ( prefix. len ( ) - 1 ) ;
127+ let start = start. with_lo ( r_pos) ;
128+
129+ let mut remove = vec ! [ ( start, String :: new( ) ) ] ;
130+ // avoid debug ICE from empty suggestions
131+ if !end. is_empty ( ) {
132+ remove. push ( ( end, String :: new ( ) ) ) ;
133+ }
134+
135+ diag. multipart_suggestion_verbose (
136+ format ! ( "use a plain {descr} literal instead" ) ,
137+ remove,
138+ Applicability :: MachineApplicable ,
139+ ) ;
140+ } ,
141+ ) ;
142+ if !matches ! ( cx. get_lint_level( NEEDLESS_RAW_STRINGS ) , rustc_lint:: Allow ) {
143+ return ;
116144 }
145+ }
117146
118- let mut req = {
119- let mut following_quote = false ;
120- let mut req = 0 ;
121- // `once` so a raw string ending in hashes is still checked
122- let num = str. as_bytes ( ) . iter ( ) . chain ( once ( & 0 ) ) . try_fold ( 0u8 , |acc, & b| {
123- match b {
124- b'"' if !following_quote => ( following_quote, req) = ( true , 1 ) ,
125- b'#' => req += u8:: from ( following_quote) ,
126- _ => {
127- if following_quote {
128- following_quote = false ;
129-
130- if req == max {
131- return ControlFlow :: Break ( req) ;
132- }
133-
134- return ControlFlow :: Continue ( acc. max ( req) ) ;
147+ let mut req = {
148+ let mut following_quote = false ;
149+ let mut req = 0 ;
150+ // `once` so a raw string ending in hashes is still checked
151+ let num = str. as_bytes ( ) . iter ( ) . chain ( once ( & 0 ) ) . try_fold ( 0u8 , |acc, & b| {
152+ match b {
153+ b'"' if !following_quote => ( following_quote, req) = ( true , 1 ) ,
154+ b'#' => req += u8:: from ( following_quote) ,
155+ _ => {
156+ if following_quote {
157+ following_quote = false ;
158+
159+ if req == max {
160+ return ControlFlow :: Break ( req) ;
135161 }
136- } ,
137- }
138162
139- ControlFlow :: Continue ( acc)
140- } ) ;
141-
142- match num {
143- ControlFlow :: Continue ( num) | ControlFlow :: Break ( num) => num,
144- }
145- } ;
146- if self . allow_one_hash_in_raw_strings {
147- req = req. max ( 1 ) ;
148- }
149- if req < max {
150- span_lint_and_then (
151- cx,
152- NEEDLESS_RAW_STRING_HASHES ,
153- expr. span ,
154- "unnecessary hashes around raw string literal" ,
155- |diag| {
156- let ( start, end) = hash_spans ( expr. span , prefix. len ( ) , req, max) ;
157-
158- let message = match max - req {
159- _ if req == 0 => format ! ( "remove all the hashes around the {descr} literal" ) ,
160- 1 => format ! ( "remove one hash from both sides of the {descr} literal" ) ,
161- n => format ! ( "remove {n} hashes from both sides of the {descr} literal" ) ,
162- } ;
163-
164- diag. multipart_suggestion (
165- message,
166- vec ! [ ( start, String :: new( ) ) , ( end, String :: new( ) ) ] ,
167- Applicability :: MachineApplicable ,
168- ) ;
163+ return ControlFlow :: Continue ( acc. max ( req) ) ;
164+ }
169165 } ,
170- ) ;
166+ }
167+
168+ ControlFlow :: Continue ( acc)
169+ } ) ;
170+
171+ match num {
172+ ControlFlow :: Continue ( num) | ControlFlow :: Break ( num) => num,
171173 }
174+ } ;
175+ if self . allow_one_hash_in_raw_strings {
176+ req = req. max ( 1 ) ;
177+ }
178+ if req < max {
179+ span_lint_and_then (
180+ cx,
181+ NEEDLESS_RAW_STRING_HASHES ,
182+ lit_span,
183+ "unnecessary hashes around raw string literal" ,
184+ |diag| {
185+ let ( start, end) = hash_spans ( lit_span, prefix. len ( ) , req, max) ;
186+
187+ let message = match max - req {
188+ _ if req == 0 => format ! ( "remove all the hashes around the {descr} literal" ) ,
189+ 1 => format ! ( "remove one hash from both sides of the {descr} literal" ) ,
190+ n => format ! ( "remove {n} hashes from both sides of the {descr} literal" ) ,
191+ } ;
192+
193+ diag. multipart_suggestion (
194+ message,
195+ vec ! [ ( start, String :: new( ) ) , ( end, String :: new( ) ) ] ,
196+ Applicability :: MachineApplicable ,
197+ ) ;
198+ } ,
199+ ) ;
172200 }
173201 }
174202}
0 commit comments