Skip to content

Commit c710b48

Browse files
committed
Auto merge of #10884 - Centri3:needless_raw_string_hashes, r=dswij
New lint [`needless_raw_string_hashes`] Emits a warning when there are an extraneous number of hashes(?) around a raw string literal, for example `r##"I'm a "raw string literal"!"##` or `cr#"crunb"#` Closes #10882 I think this could also fit in `style` as well, rather than `complexity`. changelog: Add [`needless_raw_string_hashes`] and [`needless_raw_string`] lints
2 parents ecdea8c + 8cb6c86 commit c710b48

33 files changed

+434
-95
lines changed

CHANGELOG.md

+3
Original file line numberDiff line numberDiff line change
@@ -5048,6 +5048,8 @@ Released 2018-09-13
50485048
[`needless_pass_by_value`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_pass_by_value
50495049
[`needless_question_mark`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_question_mark
50505050
[`needless_range_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_range_loop
5051+
[`needless_raw_string_hashes`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_raw_string_hashes
5052+
[`needless_raw_strings`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_raw_strings
50515053
[`needless_return`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_return
50525054
[`needless_splitn`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_splitn
50535055
[`needless_update`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_update
@@ -5412,4 +5414,5 @@ Released 2018-09-13
54125414
[`min-ident-chars-threshold`]: https://doc.rust-lang.org/clippy/lint_configuration.html#min-ident-chars-threshold
54135415
[`accept-comment-above-statement`]: https://doc.rust-lang.org/clippy/lint_configuration.html#accept-comment-above-statement
54145416
[`accept-comment-above-attributes`]: https://doc.rust-lang.org/clippy/lint_configuration.html#accept-comment-above-attributes
5417+
[`allow-one-hash-in-raw-strings`]: https://doc.rust-lang.org/clippy/lint_configuration.html#allow-one-hash-in-raw-strings
54155418
<!-- end autogenerated links to configuration documentation -->

book/src/lint_configuration.md

+10
Original file line numberDiff line numberDiff line change
@@ -717,3 +717,13 @@ Whether to accept a safety comment to be placed above the attributes for the `un
717717
* [`undocumented_unsafe_blocks`](https://rust-lang.github.io/rust-clippy/master/index.html#undocumented_unsafe_blocks)
718718

719719

720+
## `allow-one-hash-in-raw-strings`
721+
Whether to allow `r#""#` when `r""` can be used
722+
723+
**Default Value:** `false` (`bool`)
724+
725+
---
726+
**Affected lints:**
727+
* [`unnecessary_raw_string_hashes`](https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_raw_string_hashes)
728+
729+

clippy_lints/src/declared_lints.rs

+2
Original file line numberDiff line numberDiff line change
@@ -540,6 +540,8 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
540540
crate::ranges::RANGE_MINUS_ONE_INFO,
541541
crate::ranges::RANGE_PLUS_ONE_INFO,
542542
crate::ranges::REVERSED_EMPTY_RANGES_INFO,
543+
crate::raw_strings::NEEDLESS_RAW_STRINGS_INFO,
544+
crate::raw_strings::NEEDLESS_RAW_STRING_HASHES_INFO,
543545
crate::rc_clone_in_vec_init::RC_CLONE_IN_VEC_INIT_INFO,
544546
crate::read_zero_byte_vec::READ_ZERO_BYTE_VEC_INFO,
545547
crate::redundant_async_block::REDUNDANT_ASYNC_BLOCK_INFO,

clippy_lints/src/lib.rs

+7
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,7 @@ mod pub_use;
262262
mod question_mark;
263263
mod question_mark_used;
264264
mod ranges;
265+
mod raw_strings;
265266
mod rc_clone_in_vec_init;
266267
mod read_zero_byte_vec;
267268
mod redundant_async_block;
@@ -1061,6 +1062,12 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
10611062
def_id_to_usage: rustc_data_structures::fx::FxHashMap::default(),
10621063
})
10631064
});
1065+
let needless_raw_string_hashes_allow_one = conf.allow_one_hash_in_raw_strings;
1066+
store.register_early_pass(move || {
1067+
Box::new(raw_strings::RawStrings {
1068+
needless_raw_string_hashes_allow_one,
1069+
})
1070+
});
10641071
// add lints here, do not remove this comment, it's used in `new_lint`
10651072
}
10661073

clippy_lints/src/raw_strings.rs

+143
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
use std::{iter::once, ops::ControlFlow};
2+
3+
use clippy_utils::{diagnostics::span_lint_and_sugg, source::snippet};
4+
use rustc_ast::{
5+
ast::{Expr, ExprKind},
6+
token::LitKind,
7+
};
8+
use rustc_errors::Applicability;
9+
use rustc_lint::{EarlyContext, EarlyLintPass, LintContext};
10+
use rustc_middle::lint::in_external_macro;
11+
use rustc_session::{declare_tool_lint, impl_lint_pass};
12+
13+
declare_clippy_lint! {
14+
/// ### What it does
15+
/// Checks for raw string literals where a string literal can be used instead.
16+
///
17+
/// ### Why is this bad?
18+
/// It's just unnecessary, but there are many cases where using a raw string literal is more
19+
/// idiomatic than a string literal, so it's opt-in.
20+
///
21+
/// ### Example
22+
/// ```rust
23+
/// let r = r"Hello, world!";
24+
/// ```
25+
/// Use instead:
26+
/// ```rust
27+
/// let r = "Hello, world!";
28+
/// ```
29+
#[clippy::version = "1.72.0"]
30+
pub NEEDLESS_RAW_STRINGS,
31+
restriction,
32+
"suggests using a string literal when a raw string literal is unnecessary"
33+
}
34+
declare_clippy_lint! {
35+
/// ### What it does
36+
/// Checks for raw string literals with an unnecessary amount of hashes around them.
37+
///
38+
/// ### Why is this bad?
39+
/// It's just unnecessary, and makes it look like there's more escaping needed than is actually
40+
/// necessary.
41+
///
42+
/// ### Example
43+
/// ```rust
44+
/// let r = r###"Hello, "world"!"###;
45+
/// ```
46+
/// Use instead:
47+
/// ```rust
48+
/// let r = r#"Hello, "world"!"#;
49+
/// ```
50+
#[clippy::version = "1.72.0"]
51+
pub NEEDLESS_RAW_STRING_HASHES,
52+
style,
53+
"suggests reducing the number of hashes around a raw string literal"
54+
}
55+
impl_lint_pass!(RawStrings => [NEEDLESS_RAW_STRINGS, NEEDLESS_RAW_STRING_HASHES]);
56+
57+
pub struct RawStrings {
58+
pub needless_raw_string_hashes_allow_one: bool,
59+
}
60+
61+
impl EarlyLintPass for RawStrings {
62+
fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) {
63+
if !in_external_macro(cx.sess(), expr.span)
64+
&& let ExprKind::Lit(lit) = expr.kind
65+
&& let LitKind::StrRaw(max) | LitKind::ByteStrRaw(max) | LitKind::CStrRaw(max) = lit.kind
66+
{
67+
let str = lit.symbol.as_str();
68+
let prefix = match lit.kind {
69+
LitKind::StrRaw(..) => "r",
70+
LitKind::ByteStrRaw(..) => "br",
71+
LitKind::CStrRaw(..) => "cr",
72+
_ => unreachable!(),
73+
};
74+
if !snippet(cx, expr.span, prefix).trim().starts_with(prefix) {
75+
return;
76+
}
77+
78+
if !str.contains(['\\', '"']) {
79+
span_lint_and_sugg(
80+
cx,
81+
NEEDLESS_RAW_STRINGS,
82+
expr.span,
83+
"unnecessary raw string literal",
84+
"try",
85+
format!("{}\"{}\"", prefix.replace('r', ""), lit.symbol),
86+
Applicability::MachineApplicable,
87+
);
88+
89+
return;
90+
}
91+
92+
let req = {
93+
let mut following_quote = false;
94+
let mut req = 0;
95+
// `once` so a raw string ending in hashes is still checked
96+
let num = str.as_bytes().iter().chain(once(&0)).try_fold(0u8, |acc, &b| {
97+
match b {
98+
b'"' => (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+
b'#' => req += u8::from(following_quote),
107+
_ => {
108+
if following_quote {
109+
following_quote = false;
110+
111+
if req == max {
112+
return ControlFlow::Break(req);
113+
}
114+
115+
return ControlFlow::Continue(acc.max(req));
116+
}
117+
},
118+
}
119+
120+
ControlFlow::Continue(acc)
121+
});
122+
123+
match num {
124+
ControlFlow::Continue(num) | ControlFlow::Break(num) => num,
125+
}
126+
};
127+
128+
if req < max {
129+
let hashes = "#".repeat(req as usize);
130+
131+
span_lint_and_sugg(
132+
cx,
133+
NEEDLESS_RAW_STRING_HASHES,
134+
expr.span,
135+
"unnecessary hashes around raw string literal",
136+
"try",
137+
format!(r#"{prefix}{hashes}"{}"{hashes}"#, lit.symbol),
138+
Applicability::MachineApplicable,
139+
);
140+
}
141+
}
142+
}
143+
}

clippy_lints/src/utils/conf.rs

+5-1
Original file line numberDiff line numberDiff line change
@@ -543,10 +543,14 @@ define_Conf! {
543543
///
544544
/// Whether to accept a safety comment to be placed above the statement containing the `unsafe` block
545545
(accept_comment_above_statement: bool = false),
546-
/// Lint: UNDOCUMENTED_UNSAFE_BLOCKS.
546+
/// Lint: UNDOCUMENTED_UNSAFE_BLOCKS.
547547
///
548548
/// Whether to accept a safety comment to be placed above the attributes for the `unsafe` block
549549
(accept_comment_above_attributes: bool = false),
550+
/// Lint: UNNECESSARY_RAW_STRING_HASHES.
551+
///
552+
/// Whether to allow `r#""#` when `r""` can be used
553+
(allow_one_hash_in_raw_strings: bool = false),
550554
}
551555

552556
/// Search for the configuration file.

clippy_lints/src/utils/internal_lints/almost_standard_lint_formulation.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ struct StandardFormulations<'a> {
3535
impl AlmostStandardFormulation {
3636
pub fn new() -> Self {
3737
let standard_formulations = vec![StandardFormulations {
38-
wrong_pattern: Regex::new(r"^(Check for|Detects? uses?)").unwrap(),
38+
wrong_pattern: Regex::new("^(Check for|Detects? uses?)").unwrap(),
3939
correction: "Checks for",
4040
}];
4141
Self { standard_formulations }

src/main.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use std::env;
66
use std::path::PathBuf;
77
use std::process::{self, Command};
88

9-
const CARGO_CLIPPY_HELP: &str = r#"Checks a package to catch common mistakes and improve your Rust code.
9+
const CARGO_CLIPPY_HELP: &str = "Checks a package to catch common mistakes and improve your Rust code.
1010
1111
Usage:
1212
cargo clippy [options] [--] [<opts>...]
@@ -31,7 +31,7 @@ with:
3131
You can use tool lints to allow or deny lints from your code, e.g.:
3232
3333
#[allow(clippy::needless_lifetimes)]
34-
"#;
34+
";
3535

3636
fn show_help() {
3737
println!("{CARGO_CLIPPY_HELP}");

tests/compile-test.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ fn base_config(test_dir: &str) -> compiletest::Config {
5050
config.program.args.push("-Dwarnings".into());
5151

5252
// Normalize away slashes in windows paths.
53-
config.stderr_filter(r#"\\"#, "/");
53+
config.stderr_filter(r"\\", "/");
5454

5555
//config.build_base = profile_path.join("test").join(test_dir);
5656
config.program.program = profile_path.join(if cfg!(windows) {

tests/lint_message_convention.rs

+15-15
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,16 @@ impl Message {
2020
// also no punctuation (except for "?" ?) at the end of a line
2121
static REGEX_SET: LazyLock<RegexSet> = LazyLock::new(|| {
2222
RegexSet::new([
23-
r"error: [A-Z]",
24-
r"help: [A-Z]",
25-
r"warning: [A-Z]",
26-
r"note: [A-Z]",
27-
r"try this: [A-Z]",
28-
r"error: .*[.!]$",
29-
r"help: .*[.!]$",
30-
r"warning: .*[.!]$",
31-
r"note: .*[.!]$",
32-
r"try this: .*[.!]$",
23+
"error: [A-Z]",
24+
"help: [A-Z]",
25+
"warning: [A-Z]",
26+
"note: [A-Z]",
27+
"try this: [A-Z]",
28+
"error: .*[.!]$",
29+
"help: .*[.!]$",
30+
"warning: .*[.!]$",
31+
"note: .*[.!]$",
32+
"try this: .*[.!]$",
3333
])
3434
.unwrap()
3535
});
@@ -39,11 +39,11 @@ impl Message {
3939
static EXCEPTIONS_SET: LazyLock<RegexSet> = LazyLock::new(|| {
4040
RegexSet::new([
4141
r"\.\.\.$",
42-
r".*C-like enum variant discriminant is not portable to 32-bit targets",
43-
r".*Intel x86 assembly syntax used",
44-
r".*AT&T x86 assembly syntax used",
45-
r"note: Clippy version: .*",
46-
r"the compiler unexpectedly panicked. this is a bug.",
42+
".*C-like enum variant discriminant is not portable to 32-bit targets",
43+
".*Intel x86 assembly syntax used",
44+
".*AT&T x86 assembly syntax used",
45+
"note: Clippy version: .*",
46+
"the compiler unexpectedly panicked. this is a bug.",
4747
])
4848
.unwrap()
4949
});

tests/ui-toml/toml_disallowed_methods/conf_disallowed_methods.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
//@compile-flags: --crate-name conf_disallowed_methods
22

3+
#![allow(clippy::needless_raw_strings)]
34
#![warn(clippy::disallowed_methods)]
45
#![allow(clippy::useless_vec)]
56

0 commit comments

Comments
 (0)