feat: add manual_is_variant_and lint
This commit is contained in:
parent
cda17014fa
commit
c4a80f2e3e
11 changed files with 290 additions and 6 deletions
59
clippy_lints/src/methods/manual_is_variant_and.rs
Normal file
59
clippy_lints/src/methods/manual_is_variant_and.rs
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
use clippy_config::msrvs::{Msrv, OPTION_RESULT_IS_VARIANT_AND};
|
||||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::source::snippet;
|
||||
use clippy_utils::ty::is_type_diagnostic_item;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_span::{sym, Span};
|
||||
|
||||
use super::MANUAL_IS_VARIANT_AND;
|
||||
|
||||
pub(super) fn check<'tcx>(
|
||||
cx: &LateContext<'_>,
|
||||
expr: &'tcx rustc_hir::Expr<'_>,
|
||||
map_recv: &'tcx rustc_hir::Expr<'_>,
|
||||
map_arg: &'tcx rustc_hir::Expr<'_>,
|
||||
map_span: Span,
|
||||
msrv: &Msrv,
|
||||
) {
|
||||
// Don't lint if:
|
||||
|
||||
// 1. the `expr` is generated by a macro
|
||||
if expr.span.from_expansion() {
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. the caller of `map()` is neither `Option` nor `Result`
|
||||
let is_option = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(map_recv), sym::Option);
|
||||
let is_result = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(map_recv), sym::Result);
|
||||
if !is_option && !is_result {
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. the caller of `unwrap_or_default` is neither `Option<bool>` nor `Result<bool, _>`
|
||||
if !cx.typeck_results().expr_ty(expr).is_bool() {
|
||||
return;
|
||||
}
|
||||
|
||||
// 4. msrv doesn't meet `OPTION_RESULT_IS_VARIANT_AND`
|
||||
if !msrv.meets(OPTION_RESULT_IS_VARIANT_AND) {
|
||||
return;
|
||||
}
|
||||
|
||||
let lint_msg = if is_option {
|
||||
"called `map(<f>).unwrap_or_default()` on an `Option` value"
|
||||
} else {
|
||||
"called `map(<f>).unwrap_or_default()` on a `Result` value"
|
||||
};
|
||||
let suggestion = if is_option { "is_some_and" } else { "is_ok_and" };
|
||||
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
MANUAL_IS_VARIANT_AND,
|
||||
expr.span.with_lo(map_span.lo()),
|
||||
lint_msg,
|
||||
"use",
|
||||
format!("{}({})", suggestion, snippet(cx, map_arg.span, "..")),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
|
|
@ -51,6 +51,7 @@ mod iter_skip_zero;
|
|||
mod iter_with_drain;
|
||||
mod iterator_step_by_zero;
|
||||
mod join_absolute_paths;
|
||||
mod manual_is_variant_and;
|
||||
mod manual_next_back;
|
||||
mod manual_ok_or;
|
||||
mod manual_saturating_arithmetic;
|
||||
|
|
@ -3829,6 +3830,32 @@ declare_clippy_lint! {
|
|||
"filtering an iterator over `Result`s for `Ok` can be achieved with `flatten`"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// Checks for usage of `option.map(f).unwrap_or_default()` and `result.map(f).unwrap_or_default()` where f is a function or closure that returns the `bool` type.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// Readability. These can be written more concisely as `option.is_some_and(f)` and `result.is_ok_and(f)`.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```no_run
|
||||
/// # let option = Some(1);
|
||||
/// # let result: Result<usize, ()> = Ok(1);
|
||||
/// option.map(|a| a > 10).unwrap_or_default();
|
||||
/// result.map(|a| a > 10).unwrap_or_default();
|
||||
/// ```
|
||||
/// Use instead:
|
||||
/// ```no_run
|
||||
/// # let option = Some(1);
|
||||
/// # let result: Result<usize, ()> = Ok(1);
|
||||
/// option.is_some_and(|a| a > 10);
|
||||
/// result.is_ok_and(|a| a > 10);
|
||||
/// ```
|
||||
#[clippy::version = "1.76.0"]
|
||||
pub MANUAL_IS_VARIANT_AND,
|
||||
pedantic,
|
||||
"using `.map(f).unwrap_or_default()`, which is more succinctly expressed as `is_some_and(f)` or `is_ok_and(f)`"
|
||||
}
|
||||
|
||||
pub struct Methods {
|
||||
avoid_breaking_exported_api: bool,
|
||||
msrv: Msrv,
|
||||
|
|
@ -3983,6 +4010,7 @@ impl_lint_pass!(Methods => [
|
|||
RESULT_FILTER_MAP,
|
||||
ITER_FILTER_IS_SOME,
|
||||
ITER_FILTER_IS_OK,
|
||||
MANUAL_IS_VARIANT_AND,
|
||||
]);
|
||||
|
||||
/// Extracts a method call name, args, and `Span` of the method name.
|
||||
|
|
@ -4664,7 +4692,13 @@ impl Methods {
|
|||
}
|
||||
unnecessary_literal_unwrap::check(cx, expr, recv, name, args);
|
||||
},
|
||||
("unwrap_or_default" | "unwrap_unchecked" | "unwrap_err_unchecked", []) => {
|
||||
("unwrap_or_default", []) => {
|
||||
if let Some(("map", m_recv, [arg], span, _)) = method_call(recv) {
|
||||
manual_is_variant_and::check(cx, expr, m_recv, arg, span, &self.msrv);
|
||||
}
|
||||
unnecessary_literal_unwrap::check(cx, expr, recv, name, args);
|
||||
},
|
||||
("unwrap_unchecked" | "unwrap_err_unchecked", []) => {
|
||||
unnecessary_literal_unwrap::check(cx, expr, recv, name, args);
|
||||
},
|
||||
("unwrap_or_else", [u_arg]) => {
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ pub(super) fn check<'tcx>(
|
|||
}
|
||||
|
||||
// is_some_and is stabilised && `unwrap_or` argument is false; suggest `is_some_and` instead
|
||||
let suggest_is_some_and = msrv.meets(msrvs::OPTION_IS_SOME_AND)
|
||||
let suggest_is_some_and = msrv.meets(msrvs::OPTION_RESULT_IS_VARIANT_AND)
|
||||
&& matches!(&unwrap_arg.kind, ExprKind::Lit(lit)
|
||||
if matches!(lit.node, rustc_ast::LitKind::Bool(false)));
|
||||
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ pub(super) fn check(
|
|||
&& path.chars().all(char::is_alphanumeric)
|
||||
{
|
||||
let mut sugg = snippet(cx, recv.span, "..").into_owned();
|
||||
if msrv.meets(msrvs::OPTION_IS_SOME_AND) {
|
||||
if msrv.meets(msrvs::OPTION_RESULT_IS_VARIANT_AND) {
|
||||
let _ = write!(sugg, r#".extension().is_some_and(|ext| ext == "{path}")"#);
|
||||
} else {
|
||||
let _ = write!(sugg, r#".extension().map_or(false, |ext| ext == "{path}")"#);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue