Skip to content

Commit

Permalink
New Lint: byte_char_slice
Browse files Browse the repository at this point in the history
This patch adds a new lint that checks for potentially harder to read
byte char slices: `&[b'a', b'b']` and suggests to replace them with the
easier to read `b"ab"` form.

Signed-Off-By: Marcel Müller <m.mueller@ifm.com>
Co-authored-by: Matthias Beyer <matthias.beyer@ifm.com>

Use iterator to skip validation

Signed-off-by: Marcel Müller <m.mueller@ifm.com>
Suggested-by: Alex Macleod <alex@macleod.io>

Convert quote escapes to proper form

Signed-off-by: Marcel Müller <m.mueller@ifm.com>

Add more convertable test cases

Signed-off-by: Marcel Müller <m.mueller@ifm.com>
  • Loading branch information
TheNeikos authored and xFrednet committed Jun 20, 2024
1 parent 3e84ca8 commit 07c9a7a
Show file tree
Hide file tree
Showing 7 changed files with 148 additions and 0 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
78 changes: 78 additions & 0 deletions clippy_lints/src/byte_char_slice.rs
Original file line number Diff line number Diff line change
@@ -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<String> {
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::<Option<String>>()
},
_ => None,
}
} else {
None
}
}
1 change: 1 addition & 0 deletions clippy_lints/src/declared_lints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
2 changes: 2 additions & 0 deletions clippy_lints/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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`
}

Expand Down
14 changes: 14 additions & 0 deletions tests/ui/byte_char_slice.fixed
Original file line number Diff line number Diff line change
@@ -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();
}
14 changes: 14 additions & 0 deletions tests/ui/byte_char_slice.rs
Original file line number Diff line number Diff line change
@@ -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();
}
38 changes: 38 additions & 0 deletions tests/ui/byte_char_slice.stderr
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit 07c9a7a

Please sign in to comment.