2 use std
::ops
::ControlFlow
;
4 use clippy_utils
::diagnostics
::span_lint_and_then
;
5 use clippy_utils
::source
::snippet
;
6 use rustc_ast
::ast
::{Expr, ExprKind}
;
7 use rustc_ast
::token
::LitKind
;
8 use rustc_errors
::Applicability
;
9 use rustc_lint
::{EarlyContext, EarlyLintPass, LintContext}
;
10 use rustc_middle
::lint
::in_external_macro
;
11 use rustc_session
::impl_lint_pass
;
12 use rustc_span
::{BytePos, Pos, Span}
;
14 declare_clippy_lint
! {
16 /// Checks for raw string literals where a string literal can be used instead.
18 /// ### Why is this bad?
19 /// It's just unnecessary, but there are many cases where using a raw string literal is more
20 /// idiomatic than a string literal, so it's opt-in.
24 /// let r = r"Hello, world!";
28 /// let r = "Hello, world!";
30 #[clippy::version = "1.72.0"]
31 pub NEEDLESS_RAW_STRINGS
,
33 "suggests using a string literal when a raw string literal is unnecessary"
35 declare_clippy_lint
! {
37 /// Checks for raw string literals with an unnecessary amount of hashes around them.
39 /// ### Why is this bad?
40 /// It's just unnecessary, and makes it look like there's more escaping needed than is actually
45 /// let r = r###"Hello, "world"!"###;
49 /// let r = r#"Hello, "world"!"#;
51 #[clippy::version = "1.72.0"]
52 pub NEEDLESS_RAW_STRING_HASHES
,
54 "suggests reducing the number of hashes around a raw string literal"
56 impl_lint_pass
!(RawStrings
=> [NEEDLESS_RAW_STRINGS
, NEEDLESS_RAW_STRING_HASHES
]);
58 pub struct RawStrings
{
59 pub allow_one_hash_in_raw_strings
: bool
,
62 impl EarlyLintPass
for RawStrings
{
63 fn check_expr(&mut self, cx
: &EarlyContext
<'_
>, expr
: &Expr
) {
64 if !in_external_macro(cx
.sess(), expr
.span
)
65 && let ExprKind
::Lit(lit
) = expr
.kind
66 && let LitKind
::StrRaw(max
) | LitKind
::ByteStrRaw(max
) | LitKind
::CStrRaw(max
) = lit
.kind
68 let str = lit
.symbol
.as_str();
69 let prefix
= match lit
.kind
{
70 LitKind
::StrRaw(..) => "r",
71 LitKind
::ByteStrRaw(..) => "br",
72 LitKind
::CStrRaw(..) => "cr",
75 if !snippet(cx
, expr
.span
, prefix
).trim().starts_with(prefix
) {
78 let descr
= lit
.kind
.descr();
80 if !str.contains(['
\\'
, '
"']) {
85 "unnecessary raw string literal
",
87 let (start, end) = hash_spans(expr.span, prefix, 0, max);
89 // BytePos: skip over the `b` in `br`, we checked the prefix appears in the source text
90 let r_pos = expr.span.lo() + BytePos::from_usize(prefix.len() - 1);
91 let start = start.with_lo(r_pos);
93 let mut remove = vec![(start, String::new())];
94 // avoid debug ICE from empty suggestions
96 remove.push((end, String::new()));
99 diag.multipart_suggestion_verbose(
100 format!("use a plain {descr} literal instead
"),
102 Applicability::MachineApplicable,
106 if !matches!(cx.get_lint_level(NEEDLESS_RAW_STRINGS), rustc_lint::Allow) {
112 let mut following_quote = false;
114 // `once` so a raw string ending in hashes is still checked
115 let num = str.as_bytes().iter().chain(once(&0)).try_fold(0u8, |acc, &b| {
117 b'"'
if !following_quote
=> (following_quote
, req
) = (true, 1),
118 b'
#' => req += u8::from(following_quote),
121 following_quote
= false;
124 return ControlFlow
::Break(req
);
127 return ControlFlow
::Continue(acc
.max(req
));
132 ControlFlow
::Continue(acc
)
136 ControlFlow
::Continue(num
) | ControlFlow
::Break(num
) => num
,
139 if self.allow_one_hash_in_raw_strings
{
145 NEEDLESS_RAW_STRING_HASHES
,
147 "unnecessary hashes around raw string literal",
149 let (start
, end
) = hash_spans(expr
.span
, prefix
, req
, max
);
151 let message
= match max
- req
{
152 _
if req
== 0 => format
!("remove all the hashes around the {descr} literal"),
153 1 => format
!("remove one hash from both sides of the {descr} literal"),
154 n
=> format
!("remove {n} hashes from both sides of the {descr} literal"),
157 diag
.multipart_suggestion(
159 vec
![(start
, String
::new()), (end
, String
::new())],
160 Applicability
::MachineApplicable
,
169 /// Returns spans pointing at the unneeded hashes, e.g. for a `req` of `1` and `max` of `3`:
175 fn hash_spans(literal_span
: Span
, prefix
: &str, req
: u8, max
: u8) -> (Span
, Span
) {
176 let literal_span
= literal_span
.data();
178 // BytePos: we checked prefix appears literally in the source text
179 let hash_start
= literal_span
.lo
+ BytePos
::from_usize(prefix
.len());
180 let hash_end
= literal_span
.hi
;
182 // BytePos: req/max are counts of the ASCII character #
183 let start
= Span
::new(
184 hash_start
+ BytePos(req
.into()),
185 hash_start
+ BytePos(max
.into()),
190 hash_end
- BytePos(req
.into()),
191 hash_end
- BytePos(max
.into()),