11use std:: iter:: once;
22use std:: ops:: ControlFlow ;
33
4- use clippy_utils:: diagnostics:: span_lint_and_sugg ;
4+ use clippy_utils:: diagnostics:: span_lint_and_then ;
55use clippy_utils:: source:: snippet;
66use rustc_ast:: ast:: { Expr , ExprKind } ;
77use rustc_ast:: token:: LitKind ;
88use rustc_errors:: Applicability ;
99use rustc_lint:: { EarlyContext , EarlyLintPass , LintContext } ;
1010use rustc_middle:: lint:: in_external_macro;
1111use rustc_session:: { declare_tool_lint, impl_lint_pass} ;
12+ use rustc_span:: { BytePos , Pos , Span } ;
1213
1314declare_clippy_lint ! {
1415 /// ### What it does
@@ -76,14 +77,24 @@ impl EarlyLintPass for RawStrings {
7677 }
7778
7879 if !str. contains ( [ '\\' , '"' ] ) {
79- span_lint_and_sugg (
80+ span_lint_and_then (
8081 cx,
8182 NEEDLESS_RAW_STRINGS ,
8283 expr. span ,
8384 "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+ } ,
8798 ) ;
8899
89100 return ;
@@ -96,13 +107,6 @@ impl EarlyLintPass for RawStrings {
96107 let num = str. as_bytes ( ) . iter ( ) . chain ( once ( & 0 ) ) . try_fold ( 0u8 , |acc, & b| {
97108 match b {
98109 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?
106110 b'#' => req += u8:: from ( following_quote) ,
107111 _ => {
108112 if following_quote {
@@ -126,18 +130,58 @@ impl EarlyLintPass for RawStrings {
126130 } ;
127131
128132 if req < max {
129- let hashes = "#" . repeat ( req as usize ) ;
130-
131- span_lint_and_sugg (
133+ span_lint_and_then (
132134 cx,
133135 NEEDLESS_RAW_STRING_HASHES ,
134136 expr. span ,
135137 "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+ } ,
139153 ) ;
140154 }
141155 }
142156 }
143157}
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