check ranges with .contains calls

This commit is contained in:
naosense 2022-12-09 11:40:50 +08:00
parent 911864df46
commit 25c9718c04
4 changed files with 54 additions and 39 deletions

View file

@ -1,11 +1,11 @@
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::{diagnostics::span_lint_and_sugg, in_constant, macros::root_macro_call, source::snippet};
use clippy_utils::{diagnostics::span_lint_and_sugg, higher, in_constant, macros::root_macro_call, source::snippet};
use rustc_ast::LitKind::{Byte, Char};
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind, PatKind, RangeEnd};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::{def_id::DefId, sym};
use rustc_span::{def_id::DefId, sym, Span};
declare_clippy_lint! {
/// ### What it does
@ -75,47 +75,54 @@ impl<'tcx> LateLintPass<'tcx> for ManualIsAsciiCheck {
return;
}
let Some(macro_call) = root_macro_call(expr.span) else { return };
if is_matches_macro(cx, macro_call.def_id) {
if let Some(macro_call) = root_macro_call(expr.span)
&& is_matches_macro(cx, macro_call.def_id) {
if let ExprKind::Match(recv, [arm, ..], _) = expr.kind {
let range = check_pat(&arm.pat.kind);
if let Some(sugg) = match range {
CharRange::UpperChar => Some("is_ascii_uppercase"),
CharRange::LowerChar => Some("is_ascii_lowercase"),
CharRange::FullChar => Some("is_ascii_alphabetic"),
CharRange::Digit => Some("is_ascii_digit"),
CharRange::Otherwise => None,
} {
let default_snip = "..";
// `snippet_with_applicability` may set applicability to `MaybeIncorrect` for
// macro span, so we check applicability manually by comparing `recv` is not default.
let recv = snippet(cx, recv.span, default_snip);
let applicability = if recv == default_snip {
Applicability::HasPlaceholders
} else {
Applicability::MachineApplicable
};
span_lint_and_sugg(
cx,
MANUAL_IS_ASCII_CHECK,
macro_call.span,
"manual check for common ascii range",
"try",
format!("{recv}.{sugg}()"),
applicability,
);
}
check_is_ascii(cx, macro_call.span, recv, &range);
}
} else if let ExprKind::MethodCall(path, receiver, [arg], ..) = expr.kind
&& path.ident.name == sym!(contains)
&& let Some(higher::Range { start: Some(start), end: Some(end), .. }) = higher::Range::hir(receiver) {
let range = check_range(start, end);
check_is_ascii(cx, expr.span, arg, &range);
}
}
extract_msrv_attr!(LateContext);
}
fn check_is_ascii(cx: &LateContext<'_>, span: Span, recv: &Expr<'_>, range: &CharRange) {
if let Some(sugg) = match range {
CharRange::UpperChar => Some("is_ascii_uppercase"),
CharRange::LowerChar => Some("is_ascii_lowercase"),
CharRange::FullChar => Some("is_ascii_alphabetic"),
CharRange::Digit => Some("is_ascii_digit"),
CharRange::Otherwise => None,
} {
let default_snip = "..";
// `snippet_with_applicability` may set applicability to `MaybeIncorrect` for
// macro span, so we check applicability manually by comparing `recv` is not default.
let recv = snippet(cx, recv.span, default_snip);
let applicability = if recv == default_snip {
Applicability::HasPlaceholders
} else {
Applicability::MachineApplicable
};
span_lint_and_sugg(
cx,
MANUAL_IS_ASCII_CHECK,
span,
"manual check for common ascii range",
"try",
format!("{recv}.{sugg}()"),
applicability,
);
}
}
fn check_pat(pat_kind: &PatKind<'_>) -> CharRange {
match pat_kind {
PatKind::Or(pats) => {

View file

@ -15,6 +15,7 @@ fn main() {
assert!('x'.is_ascii_alphabetic());
assert!(matches!('x', 'A'..='Z' | 'a'..='z' | '_'));
assert!(&b'0'.is_ascii_digit());
}
#[clippy::msrv = "1.23"]

View file

@ -15,6 +15,7 @@ fn main() {
assert!(matches!('x', 'A'..='Z' | 'a'..='z'));
assert!(matches!('x', 'A'..='Z' | 'a'..='z' | '_'));
assert!((b'0'..=b'9').contains(&b'0'));
}
#[clippy::msrv = "1.23"]

View file

@ -43,28 +43,34 @@ 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:29:13
--> $DIR/manual_is_ascii_check.rs:18:13
|
LL | assert!((b'0'..=b'9').contains(&b'0'));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&b'0'.is_ascii_digit()`
error: manual check for common ascii range
--> $DIR/manual_is_ascii_check.rs:30: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:30:13
--> $DIR/manual_is_ascii_check.rs:31: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:31:13
--> $DIR/manual_is_ascii_check.rs:32: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:41:23
--> $DIR/manual_is_ascii_check.rs:42:23
|
LL | const FOO: bool = matches!('x', '0'..='9');
| ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `'x'.is_ascii_digit()`
error: aborting due to 11 previous errors
error: aborting due to 12 previous errors