1
1
use std:: iter:: once;
2
2
use std:: ops:: ControlFlow ;
3
3
4
- use clippy_utils:: diagnostics:: span_lint_and_sugg ;
4
+ use clippy_utils:: diagnostics:: span_lint_and_then ;
5
5
use clippy_utils:: source:: snippet;
6
6
use rustc_ast:: ast:: { Expr , ExprKind } ;
7
7
use rustc_ast:: token:: LitKind ;
8
8
use rustc_errors:: Applicability ;
9
9
use rustc_lint:: { EarlyContext , EarlyLintPass , LintContext } ;
10
10
use rustc_middle:: lint:: in_external_macro;
11
11
use rustc_session:: { declare_tool_lint, impl_lint_pass} ;
12
+ use rustc_span:: { BytePos , Pos , Span } ;
12
13
13
14
declare_clippy_lint ! {
14
15
/// ### What it does
@@ -76,14 +77,24 @@ impl EarlyLintPass for RawStrings {
76
77
}
77
78
78
79
if !str. contains ( [ '\\' , '"' ] ) {
79
- span_lint_and_sugg (
80
+ span_lint_and_then (
80
81
cx,
81
82
NEEDLESS_RAW_STRINGS ,
82
83
expr. span ,
83
84
"unnecessary raw string literal" ,
84
- "try" ,
85
- format ! ( "{}\" {}\" " , prefix. replace( 'r' , "" ) , lit. symbol) ,
86
- Applicability :: MachineApplicable ,
85
+ |diag| {
86
+ let ( start, end) = hash_spans ( expr. span , prefix, 0 , max) ;
87
+
88
+ // BytePos: skip over the `b` in `br`, we checked the prefix appears in the source text
89
+ let r_pos = expr. span . lo ( ) + BytePos :: from_usize ( prefix. len ( ) - 1 ) ;
90
+ let start = start. with_lo ( r_pos) ;
91
+
92
+ diag. multipart_suggestion (
93
+ "try" ,
94
+ vec ! [ ( start, String :: new( ) ) , ( end, String :: new( ) ) ] ,
95
+ Applicability :: MachineApplicable ,
96
+ ) ;
97
+ } ,
87
98
) ;
88
99
89
100
return ;
@@ -96,13 +107,6 @@ impl EarlyLintPass for RawStrings {
96
107
let num = str. as_bytes ( ) . iter ( ) . chain ( once ( & 0 ) ) . try_fold ( 0u8 , |acc, & b| {
97
108
match b {
98
109
b'"' if !following_quote => ( 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
110
b'#' => req += u8:: from ( following_quote) ,
107
111
_ => {
108
112
if following_quote {
@@ -126,18 +130,58 @@ impl EarlyLintPass for RawStrings {
126
130
} ;
127
131
128
132
if req < max {
129
- let hashes = "#" . repeat ( req as usize ) ;
130
-
131
- span_lint_and_sugg (
133
+ span_lint_and_then (
132
134
cx,
133
135
NEEDLESS_RAW_STRING_HASHES ,
134
136
expr. span ,
135
137
"unnecessary hashes around raw string literal" ,
136
- "try" ,
137
- format ! ( r#"{prefix}{hashes}"{}"{hashes}"# , lit. symbol) ,
138
- Applicability :: MachineApplicable ,
138
+ |diag| {
139
+ let ( start, end) = hash_spans ( expr. span , prefix, req, max) ;
140
+
141
+ let message = match max - req {
142
+ _ if req == 0 => "remove all the hashes around the literal" . to_string ( ) ,
143
+ 1 => "remove one hash from both sides of the literal" . to_string ( ) ,
144
+ n => format ! ( "remove {n} hashes from both sides of the literal" ) ,
145
+ } ;
146
+
147
+ diag. multipart_suggestion (
148
+ message,
149
+ vec ! [ ( start, String :: new( ) ) , ( end, String :: new( ) ) ] ,
150
+ Applicability :: MachineApplicable ,
151
+ ) ;
152
+ } ,
139
153
) ;
140
154
}
141
155
}
142
156
}
143
157
}
158
+
159
+ /// Returns spans pointing at the unneeded hashes, e.g. for a `req` of `1` and `max` of `3`:
160
+ ///
161
+ /// ```ignore
162
+ /// r###".."###
163
+ /// ^^ ^^
164
+ /// ```
165
+ fn hash_spans ( literal_span : Span , prefix : & str , req : u8 , max : u8 ) -> ( Span , Span ) {
166
+ let literal_span = literal_span. data ( ) ;
167
+
168
+ // BytePos: we checked prefix appears literally in the source text
169
+ let hash_start = literal_span. lo + BytePos :: from_usize ( prefix. len ( ) ) ;
170
+ let hash_end = literal_span. hi ;
171
+
172
+ // BytePos: req/max are counts of the ASCII character #
173
+ let start = Span :: new (
174
+ hash_start + BytePos ( req. into ( ) ) ,
175
+ hash_start + BytePos ( max. into ( ) ) ,
176
+ literal_span. ctxt ,
177
+ None ,
178
+ ) ;
179
+ let end = Span :: new (
180
+ hash_end - BytePos ( req. into ( ) ) ,
181
+ hash_end - BytePos ( max. into ( ) ) ,
182
+ literal_span. ctxt ,
183
+ None ,
184
+ ) ;
185
+
186
+ ( start, end)
187
+ }
0 commit comments