about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--clippy_lints/src/manual_is_ascii_check.rs24
-rw-r--r--tests/ui/manual_is_ascii_check.fixed4
-rw-r--r--tests/ui/manual_is_ascii_check.rs4
-rw-r--r--tests/ui/manual_is_ascii_check.stderr22
4 files changed, 46 insertions, 8 deletions
diff --git a/clippy_lints/src/manual_is_ascii_check.rs b/clippy_lints/src/manual_is_ascii_check.rs
index f264424470d..9da20a28fba 100644
--- a/clippy_lints/src/manual_is_ascii_check.rs
+++ b/clippy_lints/src/manual_is_ascii_check.rs
@@ -15,12 +15,13 @@ use rustc_span::{sym, Span};
 declare_clippy_lint! {
     /// ### What it does
     /// Suggests to use dedicated built-in methods,
-    /// `is_ascii_(lowercase|uppercase|digit)` for checking on corresponding ascii range
+    /// `is_ascii_(lowercase|uppercase|digit|hexdigit)` for checking on corresponding
+    /// ascii range
     ///
     /// ### Why is this bad?
     /// Using the built-in functions is more readable and makes it
     /// clear that it's not a specific subset of characters, but all
-    /// ASCII (lowercase|uppercase|digit) characters.
+    /// ASCII (lowercase|uppercase|digit|hexdigit) characters.
     /// ### Example
     /// ```rust
     /// fn main() {
@@ -28,6 +29,7 @@ declare_clippy_lint! {
     ///     assert!(matches!(b'X', b'A'..=b'Z'));
     ///     assert!(matches!('2', '0'..='9'));
     ///     assert!(matches!('x', 'A'..='Z' | 'a'..='z'));
+    ///     assert!(matches!('C', '0'..='9' | 'a'..='f' | 'A'..='F'));
     ///
     ///     ('0'..='9').contains(&'0');
     ///     ('a'..='z').contains(&'a');
@@ -41,6 +43,7 @@ declare_clippy_lint! {
     ///     assert!(b'X'.is_ascii_uppercase());
     ///     assert!('2'.is_ascii_digit());
     ///     assert!('x'.is_ascii_alphabetic());
+    ///     assert!('C'.is_ascii_hexdigit());
     ///
     ///     '0'.is_ascii_digit();
     ///     'a'.is_ascii_lowercase();
@@ -75,6 +78,12 @@ enum CharRange {
     FullChar,
     /// '0..=9'
     Digit,
+    /// 'a..=f'
+    LowerHexLetter,
+    /// 'A..=F'
+    UpperHexLetter,
+    /// '0..=9' | 'a..=f' | 'A..=F'
+    HexDigit,
     Otherwise,
 }
 
@@ -116,7 +125,8 @@ fn check_is_ascii(cx: &LateContext<'_>, span: Span, recv: &Expr<'_>, range: &Cha
         CharRange::LowerChar => Some("is_ascii_lowercase"),
         CharRange::FullChar => Some("is_ascii_alphabetic"),
         CharRange::Digit => Some("is_ascii_digit"),
-        CharRange::Otherwise => None,
+        CharRange::HexDigit => Some("is_ascii_hexdigit"),
+        CharRange::Otherwise | CharRange::LowerHexLetter | CharRange::UpperHexLetter => None,
     } {
         let default_snip = "..";
         let mut app = Applicability::MachineApplicable;
@@ -141,6 +151,12 @@ fn check_pat(pat_kind: &PatKind<'_>) -> CharRange {
 
             if ranges.len() == 2 && ranges.contains(&CharRange::UpperChar) && ranges.contains(&CharRange::LowerChar) {
                 CharRange::FullChar
+            } else if ranges.len() == 3
+                && ranges.contains(&CharRange::Digit)
+                && ranges.contains(&CharRange::LowerHexLetter)
+                && ranges.contains(&CharRange::UpperHexLetter)
+            {
+                CharRange::HexDigit
             } else {
                 CharRange::Otherwise
             }
@@ -156,6 +172,8 @@ fn check_range(start: &Expr<'_>, end: &Expr<'_>) -> CharRange {
         match (&start_lit.node, &end_lit.node) {
             (Char('a'), Char('z')) | (Byte(b'a'), Byte(b'z')) => CharRange::LowerChar,
             (Char('A'), Char('Z')) | (Byte(b'A'), Byte(b'Z')) => CharRange::UpperChar,
+            (Char('a'), Char('f')) | (Byte(b'a'), Byte(b'f')) => CharRange::LowerHexLetter,
+            (Char('A'), Char('F')) | (Byte(b'A'), Byte(b'F')) => CharRange::UpperHexLetter,
             (Char('0'), Char('9')) | (Byte(b'0'), Byte(b'9')) => CharRange::Digit,
             _ => CharRange::Otherwise,
         }
diff --git a/tests/ui/manual_is_ascii_check.fixed b/tests/ui/manual_is_ascii_check.fixed
index 5be2dd280b8..9c4bd335ad8 100644
--- a/tests/ui/manual_is_ascii_check.fixed
+++ b/tests/ui/manual_is_ascii_check.fixed
@@ -33,6 +33,7 @@ fn msrv_1_23() {
     assert!(matches!(b'1', b'0'..=b'9'));
     assert!(matches!('X', 'A'..='Z'));
     assert!(matches!('x', 'A'..='Z' | 'a'..='z'));
+    assert!(matches!('x', '0'..='9' | 'a'..='f' | 'A'..='F'));
 }
 
 #[clippy::msrv = "1.24"]
@@ -40,14 +41,17 @@ fn msrv_1_24() {
     assert!(b'1'.is_ascii_digit());
     assert!('X'.is_ascii_uppercase());
     assert!('x'.is_ascii_alphabetic());
+    assert!('x'.is_ascii_hexdigit());
 }
 
 #[clippy::msrv = "1.46"]
 fn msrv_1_46() {
     const FOO: bool = matches!('x', '0'..='9');
+    const BAR: bool = matches!('x', '0'..='9' | 'a'..='f' | 'A'..='F');
 }
 
 #[clippy::msrv = "1.47"]
 fn msrv_1_47() {
     const FOO: bool = 'x'.is_ascii_digit();
+    const BAR: bool = 'x'.is_ascii_hexdigit();
 }
diff --git a/tests/ui/manual_is_ascii_check.rs b/tests/ui/manual_is_ascii_check.rs
index f9249e22a02..785943cd24d 100644
--- a/tests/ui/manual_is_ascii_check.rs
+++ b/tests/ui/manual_is_ascii_check.rs
@@ -33,6 +33,7 @@ fn msrv_1_23() {
     assert!(matches!(b'1', b'0'..=b'9'));
     assert!(matches!('X', 'A'..='Z'));
     assert!(matches!('x', 'A'..='Z' | 'a'..='z'));
+    assert!(matches!('x', '0'..='9' | 'a'..='f' | 'A'..='F'));
 }
 
 #[clippy::msrv = "1.24"]
@@ -40,14 +41,17 @@ fn msrv_1_24() {
     assert!(matches!(b'1', b'0'..=b'9'));
     assert!(matches!('X', 'A'..='Z'));
     assert!(matches!('x', 'A'..='Z' | 'a'..='z'));
+    assert!(matches!('x', '0'..='9' | 'a'..='f' | 'A'..='F'));
 }
 
 #[clippy::msrv = "1.46"]
 fn msrv_1_46() {
     const FOO: bool = matches!('x', '0'..='9');
+    const BAR: bool = matches!('x', '0'..='9' | 'a'..='f' | 'A'..='F');
 }
 
 #[clippy::msrv = "1.47"]
 fn msrv_1_47() {
     const FOO: bool = matches!('x', '0'..='9');
+    const BAR: bool = matches!('x', '0'..='9' | 'a'..='f' | 'A'..='F');
 }
diff --git a/tests/ui/manual_is_ascii_check.stderr b/tests/ui/manual_is_ascii_check.stderr
index e0fb46e59fa..f69522c5ff8 100644
--- a/tests/ui/manual_is_ascii_check.stderr
+++ b/tests/ui/manual_is_ascii_check.stderr
@@ -98,28 +98,40 @@ LL |     ('A'..='Z').contains(cool_letter);
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `cool_letter.is_ascii_uppercase()`
 
 error: manual check for common ascii range
-  --> $DIR/manual_is_ascii_check.rs:40:13
+  --> $DIR/manual_is_ascii_check.rs:41:13
    |
 LL |     assert!(matches!(b'1', b'0'..=b'9'));
    |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `b'1'.is_ascii_digit()`
 
 error: manual check for common ascii range
-  --> $DIR/manual_is_ascii_check.rs:41:13
+  --> $DIR/manual_is_ascii_check.rs:42:13
    |
 LL |     assert!(matches!('X', 'A'..='Z'));
    |             ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `'X'.is_ascii_uppercase()`
 
 error: manual check for common ascii range
-  --> $DIR/manual_is_ascii_check.rs:42:13
+  --> $DIR/manual_is_ascii_check.rs:43:13
    |
 LL |     assert!(matches!('x', 'A'..='Z' | 'a'..='z'));
    |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `'x'.is_ascii_alphabetic()`
 
 error: manual check for common ascii range
-  --> $DIR/manual_is_ascii_check.rs:52:23
+  --> $DIR/manual_is_ascii_check.rs:44:13
+   |
+LL |     assert!(matches!('x', '0'..='9' | 'a'..='f' | 'A'..='F'));
+   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `'x'.is_ascii_hexdigit()`
+
+error: manual check for common ascii range
+  --> $DIR/manual_is_ascii_check.rs:55:23
    |
 LL |     const FOO: bool = matches!('x', '0'..='9');
    |                       ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `'x'.is_ascii_digit()`
 
-error: aborting due to 20 previous errors
+error: manual check for common ascii range
+  --> $DIR/manual_is_ascii_check.rs:56:23
+   |
+LL |     const BAR: bool = matches!('x', '0'..='9' | 'a'..='f' | 'A'..='F');
+   |                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `'x'.is_ascii_hexdigit()`
+
+error: aborting due to 22 previous errors