diff --git a/CHANGELOG.md b/CHANGELOG.md index e81e87bf077..a7284a70a1f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5048,8 +5048,8 @@ Released 2018-09-13 [`needless_pass_by_value`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_pass_by_value [`needless_question_mark`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_question_mark [`needless_range_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_range_loop -[`needless_raw_string`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_raw_string [`needless_raw_string_hashes`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_raw_string_hashes +[`needless_raw_strings`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_raw_strings [`needless_return`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_return [`needless_splitn`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_splitn [`needless_update`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_update @@ -5414,5 +5414,5 @@ Released 2018-09-13 [`min-ident-chars-threshold`]: https://doc.rust-lang.org/clippy/lint_configuration.html#min-ident-chars-threshold [`accept-comment-above-statement`]: https://doc.rust-lang.org/clippy/lint_configuration.html#accept-comment-above-statement [`accept-comment-above-attributes`]: https://doc.rust-lang.org/clippy/lint_configuration.html#accept-comment-above-attributes -[`allow-one-hash-in-raw-string`]: https://doc.rust-lang.org/clippy/lint_configuration.html#allow-one-hash-in-raw-string +[`allow-one-hash-in-raw-strings`]: https://doc.rust-lang.org/clippy/lint_configuration.html#allow-one-hash-in-raw-strings diff --git a/book/src/lint_configuration.md b/book/src/lint_configuration.md index efb48131502..293195e858c 100644 --- a/book/src/lint_configuration.md +++ b/book/src/lint_configuration.md @@ -717,7 +717,7 @@ Whether to accept a safety comment to be placed above the attributes for the `un * [`undocumented_unsafe_blocks`](https://rust-lang.github.io/rust-clippy/master/index.html#undocumented_unsafe_blocks) -## `allow-one-hash-in-raw-string` +## `allow-one-hash-in-raw-strings` Whether to allow `r#""#` when `r""` can be used **Default Value:** `false` (`bool`) diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index c67ed14b3b0..dbd4a53d836 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -540,7 +540,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[ crate::ranges::RANGE_MINUS_ONE_INFO, crate::ranges::RANGE_PLUS_ONE_INFO, crate::ranges::REVERSED_EMPTY_RANGES_INFO, - crate::raw_strings::NEEDLESS_RAW_STRING_INFO, + crate::raw_strings::NEEDLESS_RAW_STRINGS_INFO, crate::raw_strings::NEEDLESS_RAW_STRING_HASHES_INFO, crate::rc_clone_in_vec_init::RC_CLONE_IN_VEC_INIT_INFO, crate::read_zero_byte_vec::READ_ZERO_BYTE_VEC_INFO, diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index e1a7eedd93e..d926b7b84ea 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -1062,6 +1062,12 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: def_id_to_usage: rustc_data_structures::fx::FxHashMap::default(), }) }); + let needless_raw_string_hashes_allow_one = conf.allow_one_hash_in_raw_strings; + store.register_early_pass(move || { + Box::new(raw_strings::RawStrings { + needless_raw_string_hashes_allow_one, + }) + }); // add lints here, do not remove this comment, it's used in `new_lint` } diff --git a/clippy_lints/src/raw_strings.rs b/clippy_lints/src/raw_strings.rs index b465c7566f0..f45bb1ef3e1 100644 --- a/clippy_lints/src/raw_strings.rs +++ b/clippy_lints/src/raw_strings.rs @@ -1,3 +1,5 @@ +use std::{iter::once, ops::ControlFlow}; + use clippy_utils::{diagnostics::span_lint_and_sugg, source::snippet}; use rustc_ast::{ ast::{Expr, ExprKind}, @@ -13,7 +15,8 @@ declare_clippy_lint! { /// Checks for raw string literals where a string literal can be used instead. /// /// ### Why is this bad? - /// It's just unnecessary. + /// It's just unnecessary, but there are many cases where using a raw string literal is more + /// idiomatic than a string literal, so it's opt-in. /// /// ### Example /// ```rust @@ -24,8 +27,8 @@ declare_clippy_lint! { /// let r = "Hello, world!"; /// ``` #[clippy::version = "1.72.0"] - pub NEEDLESS_RAW_STRING, - complexity, + pub NEEDLESS_RAW_STRINGS, + restriction, "suggests using a string literal when a raw string literal is unnecessary" } declare_clippy_lint! { @@ -46,10 +49,10 @@ declare_clippy_lint! { /// ``` #[clippy::version = "1.72.0"] pub NEEDLESS_RAW_STRING_HASHES, - complexity, + style, "suggests reducing the number of hashes around a raw string literal" } -impl_lint_pass!(RawStrings => [NEEDLESS_RAW_STRING, NEEDLESS_RAW_STRING_HASHES]); +impl_lint_pass!(RawStrings => [NEEDLESS_RAW_STRINGS, NEEDLESS_RAW_STRING_HASHES]); pub struct RawStrings { pub needless_raw_string_hashes_allow_one: bool, @@ -59,8 +62,9 @@ impl EarlyLintPass for RawStrings { fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) { if !in_external_macro(cx.sess(), expr.span) && let ExprKind::Lit(lit) = expr.kind - && let LitKind::StrRaw(num) | LitKind::ByteStrRaw(num) | LitKind::CStrRaw(num) = lit.kind + && let LitKind::StrRaw(max) | LitKind::ByteStrRaw(max) | LitKind::CStrRaw(max) = lit.kind { + let str = lit.symbol.as_str(); let prefix = match lit.kind { LitKind::StrRaw(..) => "r", LitKind::ByteStrRaw(..) => "br", @@ -71,10 +75,10 @@ impl EarlyLintPass for RawStrings { return; } - if !lit.symbol.as_str().contains(['\\', '"']) { + if !str.contains(['\\', '"']) { span_lint_and_sugg( cx, - NEEDLESS_RAW_STRING, + NEEDLESS_RAW_STRINGS, expr.span, "unnecessary raw string literal", "try", @@ -85,15 +89,43 @@ impl EarlyLintPass for RawStrings { return; } - #[expect(clippy::cast_possible_truncation)] - let req = lit.symbol.as_str().as_bytes() - .split(|&b| b == b'"') - .skip(1) - .map(|bs| 1 + bs.iter().take_while(|&&b| b == b'#').count() as u8) - .max() - .unwrap_or(0); + let req = { + let mut following_quote = false; + let mut req = 0; + // `once` so a raw string ending in hashes is still checked + let num = str.as_bytes().iter().chain(once(&0)).try_fold(0u8, |acc, &b| { + match b { + b'"' => (following_quote, req) = (true, 1), + // I'm a bit surprised the compiler didn't optimize this out, there's no + // branch but it still ends up doing an unnecessary comparison, it's: + // - cmp r9b,1h + // - sbb cl,-1h + // which will add 1 if it's true. With this change, it becomes: + // - add cl,r9b + // isn't that so much nicer? + b'#' => req += u8::from(following_quote), + _ => { + if following_quote { + following_quote = false; - if req < num { + if req == max { + return ControlFlow::Break(req); + } + + return ControlFlow::Continue(acc.max(req)); + } + }, + } + + ControlFlow::Continue(acc) + }); + + match num { + ControlFlow::Continue(num) | ControlFlow::Break(num) => num, + } + }; + + if req < max { let hashes = "#".repeat(req as usize); span_lint_and_sugg( diff --git a/clippy_lints/src/utils/conf.rs b/clippy_lints/src/utils/conf.rs index 9f88960f7ce..9be94920f96 100644 --- a/clippy_lints/src/utils/conf.rs +++ b/clippy_lints/src/utils/conf.rs @@ -550,7 +550,7 @@ define_Conf! { /// Lint: UNNECESSARY_RAW_STRING_HASHES. /// /// Whether to allow `r#""#` when `r""` can be used - (allow_one_hash_in_raw_string: bool = false), + (allow_one_hash_in_raw_strings: bool = false), } /// Search for the configuration file. diff --git a/tests/compile-test.rs b/tests/compile-test.rs index c5e8622bc85..13013f646e5 100644 --- a/tests/compile-test.rs +++ b/tests/compile-test.rs @@ -50,7 +50,7 @@ fn base_config(test_dir: &str) -> compiletest::Config { config.program.args.push("-Dwarnings".into()); // Normalize away slashes in windows paths. - config.stderr_filter(r#"\\"#, "/"); + config.stderr_filter(r"\\", "/"); //config.build_base = profile_path.join("test").join(test_dir); config.program.program = profile_path.join(if cfg!(windows) { diff --git a/tests/ui-toml/toml_disallowed_methods/conf_disallowed_methods.rs b/tests/ui-toml/toml_disallowed_methods/conf_disallowed_methods.rs index d26bc72787b..63fdea710cb 100644 --- a/tests/ui-toml/toml_disallowed_methods/conf_disallowed_methods.rs +++ b/tests/ui-toml/toml_disallowed_methods/conf_disallowed_methods.rs @@ -1,6 +1,6 @@ //@compile-flags: --crate-name conf_disallowed_methods -#![allow(clippy::needless_raw_string)] +#![allow(clippy::needless_raw_strings)] #![warn(clippy::disallowed_methods)] #![allow(clippy::useless_vec)] diff --git a/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr b/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr index 8724dc29d29..6ba26e97730 100644 --- a/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr +++ b/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr @@ -4,7 +4,7 @@ error: error reading Clippy's configuration file: unknown field `foobar`, expect allow-dbg-in-tests allow-expect-in-tests allow-mixed-uninlined-format-args - allow-one-hash-in-raw-string + allow-one-hash-in-raw-strings allow-print-in-tests allow-private-module-inception allow-unwrap-in-tests @@ -73,7 +73,7 @@ error: error reading Clippy's configuration file: unknown field `barfoo`, expect allow-dbg-in-tests allow-expect-in-tests allow-mixed-uninlined-format-args - allow-one-hash-in-raw-string + allow-one-hash-in-raw-strings allow-print-in-tests allow-private-module-inception allow-unwrap-in-tests diff --git a/tests/ui/needless_raw_string.fixed b/tests/ui/needless_raw_string.fixed index ffc1644335d..6438e46977b 100644 --- a/tests/ui/needless_raw_string.fixed +++ b/tests/ui/needless_raw_string.fixed @@ -1,6 +1,6 @@ //@run-rustfix #![allow(clippy::needless_raw_string_hashes, clippy::no_effect, unused)] -#![warn(clippy::needless_raw_string)] +#![warn(clippy::needless_raw_strings)] #![feature(c_str_literals)] fn main() { diff --git a/tests/ui/needless_raw_string.rs b/tests/ui/needless_raw_string.rs index b06b9a0ec85..f7ddc68265e 100644 --- a/tests/ui/needless_raw_string.rs +++ b/tests/ui/needless_raw_string.rs @@ -1,6 +1,6 @@ //@run-rustfix #![allow(clippy::needless_raw_string_hashes, clippy::no_effect, unused)] -#![warn(clippy::needless_raw_string)] +#![warn(clippy::needless_raw_strings)] #![feature(c_str_literals)] fn main() { diff --git a/tests/ui/needless_raw_string.stderr b/tests/ui/needless_raw_string.stderr index 3eb699dea36..0179978b7b0 100644 --- a/tests/ui/needless_raw_string.stderr +++ b/tests/ui/needless_raw_string.stderr @@ -4,7 +4,7 @@ error: unnecessary raw string literal LL | r#"aaa"#; | ^^^^^^^^ help: try: `"aaa"` | - = note: `-D clippy::needless-raw-string` implied by `-D warnings` + = note: `-D clippy::needless-raw-strings` implied by `-D warnings` error: unnecessary raw string literal --> $DIR/needless_raw_string.rs:10:5 diff --git a/tests/ui/needless_raw_string_hashes.fixed b/tests/ui/needless_raw_string_hashes.fixed index 8d443dabcb7..e4d7d8fb017 100644 --- a/tests/ui/needless_raw_string_hashes.fixed +++ b/tests/ui/needless_raw_string_hashes.fixed @@ -1,5 +1,5 @@ //@run-rustfix -#![allow(clippy::needless_raw_string, clippy::no_effect, unused)] +#![allow(clippy::no_effect, unused)] #![warn(clippy::needless_raw_string_hashes)] #![feature(c_str_literals)] diff --git a/tests/ui/needless_raw_string_hashes.rs b/tests/ui/needless_raw_string_hashes.rs index dedd774ea0a..e2d85c52e78 100644 --- a/tests/ui/needless_raw_string_hashes.rs +++ b/tests/ui/needless_raw_string_hashes.rs @@ -1,5 +1,5 @@ //@run-rustfix -#![allow(clippy::needless_raw_string, clippy::no_effect, unused)] +#![allow(clippy::no_effect, unused)] #![warn(clippy::needless_raw_string_hashes)] #![feature(c_str_literals)] diff --git a/tests/ui/regex.rs b/tests/ui/regex.rs index 850c9813462..89d1d949454 100644 --- a/tests/ui/regex.rs +++ b/tests/ui/regex.rs @@ -1,6 +1,6 @@ #![allow( unused, - clippy::needless_raw_string, + clippy::needless_raw_strings, clippy::needless_raw_string_hashes, clippy::needless_borrow )] diff --git a/tests/ui/single_char_add_str.fixed b/tests/ui/single_char_add_str.fixed index ee0177776bf..cb301c8bc15 100644 --- a/tests/ui/single_char_add_str.fixed +++ b/tests/ui/single_char_add_str.fixed @@ -1,6 +1,6 @@ //@run-rustfix #![warn(clippy::single_char_add_str)] -#![allow(clippy::needless_raw_string, clippy::needless_raw_string_hashes)] +#![allow(clippy::needless_raw_strings, clippy::needless_raw_string_hashes)] macro_rules! get_string { () => { diff --git a/tests/ui/single_char_add_str.rs b/tests/ui/single_char_add_str.rs index e3b7d8597c2..99baf35ac29 100644 --- a/tests/ui/single_char_add_str.rs +++ b/tests/ui/single_char_add_str.rs @@ -1,6 +1,6 @@ //@run-rustfix #![warn(clippy::single_char_add_str)] -#![allow(clippy::needless_raw_string, clippy::needless_raw_string_hashes)] +#![allow(clippy::needless_raw_strings, clippy::needless_raw_string_hashes)] macro_rules! get_string { () => { diff --git a/tests/ui/single_char_pattern.fixed b/tests/ui/single_char_pattern.fixed index 920d43318ae..7ae62231acc 100644 --- a/tests/ui/single_char_pattern.fixed +++ b/tests/ui/single_char_pattern.fixed @@ -1,6 +1,6 @@ //@run-rustfix -#![allow(clippy::needless_raw_string, clippy::needless_raw_string_hashes, unused_must_use)] +#![allow(clippy::needless_raw_strings, clippy::needless_raw_string_hashes, unused_must_use)] use std::collections::HashSet; diff --git a/tests/ui/single_char_pattern.rs b/tests/ui/single_char_pattern.rs index 3a53084e30b..0604624e767 100644 --- a/tests/ui/single_char_pattern.rs +++ b/tests/ui/single_char_pattern.rs @@ -1,6 +1,6 @@ //@run-rustfix -#![allow(clippy::needless_raw_string, clippy::needless_raw_string_hashes, unused_must_use)] +#![allow(clippy::needless_raw_strings, clippy::needless_raw_string_hashes, unused_must_use)] use std::collections::HashSet; diff --git a/tests/ui/write_literal_2.rs b/tests/ui/write_literal_2.rs index 1d9b9603bd8..805127e2750 100644 --- a/tests/ui/write_literal_2.rs +++ b/tests/ui/write_literal_2.rs @@ -1,5 +1,5 @@ #![allow(unused_must_use)] -#![warn(clippy::needless_raw_string, clippy::write_literal)] +#![warn(clippy::needless_raw_strings, clippy::write_literal)] use std::io::Write; diff --git a/tests/ui/write_literal_2.stderr b/tests/ui/write_literal_2.stderr index a69d031b011..18591250aad 100644 --- a/tests/ui/write_literal_2.stderr +++ b/tests/ui/write_literal_2.stderr @@ -4,7 +4,7 @@ error: unnecessary raw string literal LL | writeln!(v, r"{}", r"{hello}"); | ^^^^^^^^^^ help: try: `"{hello}"` | - = note: `-D clippy::needless-raw-string` implied by `-D warnings` + = note: `-D clippy::needless-raw-strings` implied by `-D warnings` error: literal with an empty format string --> $DIR/write_literal_2.rs:9:23