Add lint almost_complete_letter_range
This commit is contained in:
parent
39231b4b50
commit
eb2908b4ea
11 changed files with 361 additions and 1 deletions
100
clippy_lints/src/almost_complete_letter_range.rs
Normal file
100
clippy_lints/src/almost_complete_letter_range.rs
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::source::{trim_span, walk_span_to_context};
|
||||
use clippy_utils::{meets_msrv, msrvs};
|
||||
use rustc_ast::ast::{Expr, ExprKind, LitKind, Pat, PatKind, RangeEnd, RangeLimits};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_lint::{EarlyContext, EarlyLintPass, LintContext};
|
||||
use rustc_semver::RustcVersion;
|
||||
use rustc_session::{declare_tool_lint, impl_lint_pass};
|
||||
use rustc_span::Span;
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for ranges which almost include the entire range of letters from 'a' to 'z', but
|
||||
/// don't because they're a half open range.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// This (`'a'..'z'`) is almost certainly a typo meant to include all letters.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```rust
|
||||
/// let _ = 'a'..'z';
|
||||
/// ```
|
||||
/// Use instead:
|
||||
/// ```rust
|
||||
/// let _ = 'a'..='z';
|
||||
/// ```
|
||||
#[clippy::version = "1.63.0"]
|
||||
pub ALMOST_COMPLETE_LETTER_RANGE,
|
||||
suspicious,
|
||||
"almost complete letter range"
|
||||
}
|
||||
impl_lint_pass!(AlmostCompleteLetterRange => [ALMOST_COMPLETE_LETTER_RANGE]);
|
||||
|
||||
pub struct AlmostCompleteLetterRange {
|
||||
msrv: Option<RustcVersion>,
|
||||
}
|
||||
impl AlmostCompleteLetterRange {
|
||||
pub fn new(msrv: Option<RustcVersion>) -> Self {
|
||||
Self { msrv }
|
||||
}
|
||||
}
|
||||
impl EarlyLintPass for AlmostCompleteLetterRange {
|
||||
fn check_expr(&mut self, cx: &EarlyContext<'_>, e: &Expr) {
|
||||
if let ExprKind::Range(Some(start), Some(end), RangeLimits::HalfOpen) = &e.kind {
|
||||
let ctxt = e.span.ctxt();
|
||||
let sugg = if let Some(start) = walk_span_to_context(start.span, ctxt)
|
||||
&& let Some(end) = walk_span_to_context(end.span, ctxt)
|
||||
&& meets_msrv(self.msrv, msrvs::RANGE_INCLUSIVE)
|
||||
{
|
||||
Some((trim_span(cx.sess().source_map(), start.between(end)), "..="))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
check_range(cx, e.span, start, end, sugg);
|
||||
}
|
||||
}
|
||||
|
||||
fn check_pat(&mut self, cx: &EarlyContext<'_>, p: &Pat) {
|
||||
if let PatKind::Range(Some(start), Some(end), kind) = &p.kind
|
||||
&& matches!(kind.node, RangeEnd::Excluded)
|
||||
{
|
||||
let sugg = if meets_msrv(self.msrv, msrvs::RANGE_INCLUSIVE) {
|
||||
"..="
|
||||
} else {
|
||||
"..."
|
||||
};
|
||||
check_range(cx, p.span, start, end, Some((kind.span, sugg)));
|
||||
}
|
||||
}
|
||||
|
||||
extract_msrv_attr!(EarlyContext);
|
||||
}
|
||||
|
||||
fn check_range(cx: &EarlyContext<'_>, span: Span, start: &Expr, end: &Expr, sugg: Option<(Span, &str)>) {
|
||||
if let ExprKind::Lit(start_lit) = &start.peel_parens().kind
|
||||
&& let ExprKind::Lit(end_lit) = &end.peel_parens().kind
|
||||
&& matches!(
|
||||
(&start_lit.kind, &end_lit.kind),
|
||||
(LitKind::Byte(b'a') | LitKind::Char('a'), LitKind::Byte(b'z') | LitKind::Char('z'))
|
||||
| (LitKind::Byte(b'A') | LitKind::Char('A'), LitKind::Byte(b'Z') | LitKind::Char('Z'))
|
||||
)
|
||||
{
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
ALMOST_COMPLETE_LETTER_RANGE,
|
||||
span,
|
||||
"almost complete ascii letter range",
|
||||
|diag| {
|
||||
if let Some((span, sugg)) = sugg {
|
||||
diag.span_suggestion(
|
||||
span,
|
||||
"use an inclusive range",
|
||||
sugg.to_owned(),
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
store.register_group(true, "clippy::all", Some("clippy_all"), vec![
|
||||
LintId::of(absurd_extreme_comparisons::ABSURD_EXTREME_COMPARISONS),
|
||||
LintId::of(almost_complete_letter_range::ALMOST_COMPLETE_LETTER_RANGE),
|
||||
LintId::of(approx_const::APPROX_CONSTANT),
|
||||
LintId::of(assertions_on_constants::ASSERTIONS_ON_CONSTANTS),
|
||||
LintId::of(assign_ops::ASSIGN_OP_PATTERN),
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ store.register_lints(&[
|
|||
#[cfg(feature = "internal")]
|
||||
utils::internal_lints::UNNECESSARY_SYMBOL_STR,
|
||||
absurd_extreme_comparisons::ABSURD_EXTREME_COMPARISONS,
|
||||
almost_complete_letter_range::ALMOST_COMPLETE_LETTER_RANGE,
|
||||
approx_const::APPROX_CONSTANT,
|
||||
arithmetic::FLOAT_ARITHMETIC,
|
||||
arithmetic::INTEGER_ARITHMETIC,
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
// Manual edits will be overwritten.
|
||||
|
||||
store.register_group(true, "clippy::suspicious", Some("clippy_suspicious"), vec![
|
||||
LintId::of(almost_complete_letter_range::ALMOST_COMPLETE_LETTER_RANGE),
|
||||
LintId::of(assign_ops::MISREFACTORED_ASSIGN_OP),
|
||||
LintId::of(attrs::BLANKET_CLIPPY_RESTRICTION_LINTS),
|
||||
LintId::of(await_holding_invalid::AWAIT_HOLDING_INVALID_TYPE),
|
||||
|
|
|
|||
|
|
@ -168,6 +168,7 @@ mod renamed_lints;
|
|||
|
||||
// begin lints modules, do not remove this comment, it’s used in `update_lints`
|
||||
mod absurd_extreme_comparisons;
|
||||
mod almost_complete_letter_range;
|
||||
mod approx_const;
|
||||
mod arithmetic;
|
||||
mod as_conversions;
|
||||
|
|
@ -911,6 +912,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
|
|||
store.register_early_pass(|| Box::new(duplicate_mod::DuplicateMod::default()));
|
||||
store.register_late_pass(|| Box::new(get_first::GetFirst));
|
||||
store.register_early_pass(|| Box::new(unused_rounding::UnusedRounding));
|
||||
store.register_early_pass(move || Box::new(almost_complete_letter_range::AlmostCompleteLetterRange::new(msrv)));
|
||||
// add lints here, do not remove this comment, it's used in `new_lint`
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue