1
1
use clippy_config:: Conf ;
2
2
use clippy_utils:: diagnostics:: span_lint_and_then;
3
- use clippy_utils:: source:: SpanRangeExt ;
3
+ use clippy_utils:: source:: { SpanRangeExt , snippet_opt } ;
4
4
use rustc_ast:: ast:: { Expr , ExprKind } ;
5
5
use rustc_ast:: token:: LitKind ;
6
6
use rustc_errors:: Applicability ;
@@ -71,6 +71,23 @@ impl RawStrings {
71
71
72
72
impl EarlyLintPass for RawStrings {
73
73
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
+
74
91
if let ExprKind :: Lit ( lit) = expr. kind
75
92
&& let ( prefix, max) = match lit. kind {
76
93
LitKind :: StrRaw ( max) => ( "r" , max) ,
@@ -81,94 +98,105 @@ impl EarlyLintPass for RawStrings {
81
98
&& !in_external_macro ( cx. sess ( ) , expr. span )
82
99
&& expr. span . check_source_text ( cx, |src| src. starts_with ( prefix) )
83
100
{
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
+ }
105
105
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 ;
116
144
}
145
+ }
117
146
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) ;
135
161
}
136
- } ,
137
- }
138
162
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
+ }
169
165
} ,
170
- ) ;
166
+ }
167
+
168
+ ControlFlow :: Continue ( acc)
169
+ } ) ;
170
+
171
+ match num {
172
+ ControlFlow :: Continue ( num) | ControlFlow :: Break ( num) => num,
171
173
}
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
+ ) ;
172
200
}
173
201
}
174
202
}
0 commit comments