diff --git a/CHANGELOG.md b/CHANGELOG.md index fe9fbd486c9c..88ee66c90e8e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5236,6 +5236,7 @@ Released 2018-09-13 [`boxed_local`]: https://rust-lang.github.io/rust-clippy/master/index.html#boxed_local [`branches_sharing_code`]: https://rust-lang.github.io/rust-clippy/master/index.html#branches_sharing_code [`builtin_type_shadow`]: https://rust-lang.github.io/rust-clippy/master/index.html#builtin_type_shadow +[`byte_char_slice`]: https://rust-lang.github.io/rust-clippy/master/index.html#byte_char_slice [`bytes_count_to_len`]: https://rust-lang.github.io/rust-clippy/master/index.html#bytes_count_to_len [`bytes_nth`]: https://rust-lang.github.io/rust-clippy/master/index.html#bytes_nth [`cargo_common_metadata`]: https://rust-lang.github.io/rust-clippy/master/index.html#cargo_common_metadata diff --git a/clippy_lints/src/byte_char_slice.rs b/clippy_lints/src/byte_char_slice.rs new file mode 100644 index 000000000000..75125be0bfec --- /dev/null +++ b/clippy_lints/src/byte_char_slice.rs @@ -0,0 +1,78 @@ +use clippy_utils::diagnostics::span_lint_and_sugg; +use rustc_ast::ast::{BorrowKind, Expr, ExprKind, Mutability}; +use rustc_ast::token::{Lit, LitKind}; +use rustc_errors::Applicability; +use rustc_lint::{EarlyContext, EarlyLintPass}; +use rustc_session::declare_lint_pass; + +declare_clippy_lint! { + /// ### What it does + /// Checks for hard to read slices of byte characters, that could be more easily expressed as a + /// byte string. + /// + /// ### Why is this bad? + /// + /// Potentially makes the string harder to read. + /// + /// ### Example + /// ```rust + /// &[b'H', b'e', b'l', b'l', b'o'] + /// ``` + /// Use instead: + /// ```rust + /// b"Hello" + /// ``` + #[clippy::version = "1.68.0"] + pub BYTE_CHAR_SLICE, + style, + "hard to read byte char slice" +} +declare_lint_pass!(ByteCharSlice => [BYTE_CHAR_SLICE]); + +impl EarlyLintPass for ByteCharSlice { + fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) { + if let Some(slice) = is_byte_char_slice(expr) && !expr.span.from_expansion() { + span_lint_and_sugg( + cx, + BYTE_CHAR_SLICE, + expr.span, + "can be more succinctly written as a byte str", + "try", + format!("b\"{slice}\""), + Applicability::MaybeIncorrect, + ); + } + } +} + +fn is_byte_char_slice(expr: &Expr) -> Option { + if let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, expr) = &expr.kind { + match &expr.kind { + ExprKind::Array(members) => { + if members.is_empty() { + return None; + } + + members + .iter() + .map(|member| match &member.kind { + ExprKind::Lit(Lit { + kind: LitKind::Byte, + symbol, + .. + }) => Some(symbol.as_str()), + _ => None, + }) + .map(|maybe_quote| match maybe_quote { + Some("\"") => Some("\\\""), + Some("\\'") => Some("'"), + other => other, + }) + .collect::>() + }, + _ => None, + } + } else { + None + } +} diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index 3b3d0db79dcc..5eb666d73633 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -73,6 +73,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[ crate::booleans::OVERLY_COMPLEX_BOOL_EXPR_INFO, crate::borrow_deref_ref::BORROW_DEREF_REF_INFO, crate::box_default::BOX_DEFAULT_INFO, + crate::byte_char_slice::BYTE_CHAR_SLICE_INFO, crate::cargo::CARGO_COMMON_METADATA_INFO, crate::cargo::LINT_GROUPS_PRIORITY_INFO, crate::cargo::MULTIPLE_CRATE_VERSIONS_INFO, diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 1bd6dfe24617..99376cba5f4b 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -89,6 +89,7 @@ mod bool_to_int_with_if; mod booleans; mod borrow_deref_ref; mod box_default; +mod byte_char_slice; mod cargo; mod casts; mod checked_conversions; @@ -1171,6 +1172,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) { }); store.register_late_pass(move |_| Box::new(string_patterns::StringPatterns::new(msrv()))); store.register_early_pass(|| Box::new(field_scoped_visibility_modifiers::FieldScopedVisibilityModifiers)); + store.register_early_pass(|| Box::new(byte_char_slice::ByteCharSlice)); // add lints here, do not remove this comment, it's used in `new_lint` } diff --git a/tests/ui/byte_char_slice.fixed b/tests/ui/byte_char_slice.fixed new file mode 100644 index 000000000000..852024ea6ab0 --- /dev/null +++ b/tests/ui/byte_char_slice.fixed @@ -0,0 +1,14 @@ +// run-rustfix +#![allow(unused)] +#![warn(clippy::byte_char_slice)] + +fn main() { + let bad = b"abc"; + let quotes = b"\"Hi"; + let quotes = b"'Sup"; + let escapes = b"\x42Esc"; + + let good = &[b'a', 0x42]; + let good = [b'a', b'a']; + let good: u8 = [b'a', b'c'].into_iter().sum(); +} diff --git a/tests/ui/byte_char_slice.rs b/tests/ui/byte_char_slice.rs new file mode 100644 index 000000000000..93d493943c5b --- /dev/null +++ b/tests/ui/byte_char_slice.rs @@ -0,0 +1,14 @@ +// run-rustfix +#![allow(unused)] +#![warn(clippy::byte_char_slice)] + +fn main() { + let bad = &[b'a', b'b', b'c']; + let quotes = &[b'"', b'H', b'i']; + let quotes = &[b'\'', b'S', b'u', b'p']; + let escapes = &[b'\x42', b'E', b's', b'c']; + + let good = &[b'a', 0x42]; + let good = vec![b'a', b'a']; + let good: u8 = [b'a', b'c'].into_iter().sum(); +} diff --git a/tests/ui/byte_char_slice.stderr b/tests/ui/byte_char_slice.stderr new file mode 100644 index 000000000000..177614f92cdb --- /dev/null +++ b/tests/ui/byte_char_slice.stderr @@ -0,0 +1,38 @@ +error: can be more succinctly written as a byte str + --> tests/ui/byte_char_slice.rs:6:15 + | +LL | let bad = &[b'a', b'b', b'c']; + | ^^^^^^^^^^^^^^^^^^^ help: try: `b"abc"` + | + = note: `-D clippy::byte-char-slice` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::byte_char_slice)]` + +error: can be more succinctly written as a byte str + --> tests/ui/byte_char_slice.rs:7:18 + | +LL | let quotes = &[b'"', b'H', b'i']; + | ^^^^^^^^^^^^^^^^^^^ help: try: `b"\"Hi"` + +error: can be more succinctly written as a byte str + --> tests/ui/byte_char_slice.rs:8:18 + | +LL | let quotes = &[b'\'', b'S', b'u', b'p']; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `b"'Sup"` + +error: can be more succinctly written as a byte str + --> tests/ui/byte_char_slice.rs:9:19 + | +LL | let escapes = &[b'\x42', b'E', b's', b'c']; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `b"\x42Esc"` + +error: useless use of `vec!` + --> tests/ui/byte_char_slice.rs:12:16 + | +LL | let good = vec![b'a', b'a']; + | ^^^^^^^^^^^^^^^^ help: you can use an array directly: `[b'a', b'a']` + | + = note: `-D clippy::useless-vec` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::useless_vec)]` + +error: aborting due to 5 previous errors +