Skip to content

Commit

Permalink
Add clippy::almost_complete_range written in Marker
Browse files Browse the repository at this point in the history
  • Loading branch information
xFrednet committed Jul 17, 2023
1 parent 20feca4 commit c425d87
Show file tree
Hide file tree
Showing 7 changed files with 177 additions and 52 deletions.
19 changes: 19 additions & 0 deletions src/clippy.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//! This is the parent module for lints, which are inspired by Clippy lints.
//! See [Clippy's lint list] for a full list.
//!
//! It can happen that some lints from Clippy are simply not reproducible as Clippy
//! has access to more compiler internals than Marker might ever expose via its public API.
//!
//! [Clippy's lint list]: <https://rust-lang.github.io/rust-clippy/master/index.html>

use marker_api::{lint::Lint, prelude::*};

mod almost_complete_range;

pub fn lints() -> Vec<&'static Lint> {
vec![almost_complete_range::ALMOST_COMPLETE_RANGE]
}

pub(crate) fn check_expr<'ast>(cx: &'ast AstContext<'ast>, expr: ExprKind<'ast>) {
almost_complete_range::check(cx, expr)
}
80 changes: 80 additions & 0 deletions src/clippy/almost_complete_range.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
use marker_api::{ast::expr::LitExprKind, diagnostic::Applicability, prelude::*};

marker_api::declare_lint! {
/// # What it does
/// Checks for ranges that almost check an entire ascii range. The linted
/// ranges are:
/// * `'a'..'z'`
/// * `'A'..'Z'`
/// * `'0'..'9'`
///
/// These ranges should probably be inclusive.
///
/// # Example
/// ```
/// # let x = 0;
/// ('a'..'z').contains(&c);
/// ```
///
/// Use instead
/// ```
/// # let x = 0;
/// ('a'..='z').contains(&c);
/// ```
///
/// # Note
/// This lint was inspired by the [clippy::almost_complete_range] lint.
///
/// [clippy::almost_complete_range]: <https://rust-lang.github.io/rust-clippy/master/index.html#/almost_complete_range>
ALMOST_COMPLETE_RANGE,
Warn,
}

pub(crate) fn check<'ast>(cx: &AstContext<'ast>, expr: ExprKind<'ast>) {
if let ExprKind::Range(range) = expr
&& !range.is_inclusive()
&& let Some(start) = range.start()
&& let Some(end) = range.end()
&& is_almost_complete(start, end)
{
cx.emit_lint(ALMOST_COMPLETE_RANGE, expr.id(), "almost complete ascii range", expr.span(), |diag| {
let mut app = Applicability::MachineApplicable;
let a = start.span().snippet_with_applicability("<start>", &mut app);
let b = start.span().snippet_with_applicability("<end>", &mut app);
diag.span_suggestion("try", expr.span(), format!("{a}..={b}"), app)
})
}
}

fn is_almost_complete(start: ExprKind<'_>, end: ExprKind<'_>) -> bool {
if let (Ok(start), Ok(end)) = (
TryInto::<LitExprKind>::try_into(start),
TryInto::<LitExprKind>::try_into(end),
) {
match (start, end) {
(LitExprKind::Int(start), LitExprKind::Int(end)) => {
if start.value() < u8::MAX.into() || end.value() < u8::MAX.into() {
match (start.value() as u8, end.value() as u8) {
(b'a', b'z') => true,
(b'A', b'Z') => true,
(b'0', b'9') => true,
_ => false,
}
} else {
false
}
}
(LitExprKind::Char(start), LitExprKind::Char(end)) => {
match (start.value(), end.value()) {
('a', 'z') => true,
('A', 'Z') => true,
('0', '9') => true,
_ => false,
}
}
_ => false,
}
} else {
false
}
}
40 changes: 7 additions & 33 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,48 +1,22 @@
#![feature(let_chains)]
#![warn(clippy::pedantic)]

use marker_api::ast::expr::ExprKind;
use marker_api::prelude::*;
use marker_api::{LintPass, LintPassInfo, LintPassInfoBuilder};

mod clippy;

#[derive(Default)]
struct MyLintPass {}
marker_api::export_lint_pass!(MyLintPass);

marker_api::declare_lint! {
/// # What it does
/// Here you can explain what your lint does. The description supports normal
/// markdown.
///
/// # Example
/// ```rs
/// // Bad example
/// ```
///
/// Use instead:
/// ```rs
/// // Good example
/// ```
MY_LINT,
Warn,
}

impl LintPass for MyLintPass {
fn info(&self) -> LintPassInfo {
LintPassInfoBuilder::new(Box::new([MY_LINT])).build()
LintPassInfoBuilder::new(clippy::lints().into_boxed_slice()).build()
}

fn check_item<'ast>(&mut self, cx: &'ast AstContext<'ast>, item: ItemKind<'ast>) {
if let ItemKind::Fn(func) = item {
if let Some(ident) = func.ident() {
if ident.name() == "main" {
cx.emit_lint(
MY_LINT,
item.id(),
"hello, main (From Marker)",
item.span(),
|_| {},
);
}
}
}
fn check_expr<'ast>(&mut self, cx: &'ast AstContext<'ast>, expr: ExprKind<'ast>) {
clippy::check_expr(cx, expr);
}
}
31 changes: 31 additions & 0 deletions tests/ui/almost_complete_range.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#[allow(clippy::manual_is_ascii_check)]

fn main() {
let x = 0;
let c = 'x';

// Lint
let _ = (b'a'..b'z').contains(&x);
let _ = (b'A'..b'Z').contains(&x);
let _ = (b'0'..b'9').contains(&x);
let _ = ('a'..'z').contains(&c);
let _ = ('A'..'Z').contains(&c);
let _ = ('0'..'9').contains(&c);

// Don't lint (inclusive)
let _ = (b'a'..=b'z').contains(&x);
let _ = (b'A'..=b'Z').contains(&x);
let _ = (b'0'..=b'9').contains(&x);
let _ = ('a'..='z').contains(&c);
let _ = ('A'..='Z').contains(&c);
let _ = ('0'..='9').contains(&c);

let b: i32 = 32;

// Don't lint (something else)
let _ = (b'a'..).contains(&x);
let _ = (..=b'Z').contains(&x);
let _ = (256..7272).contains(&b);
let _ = (0..1).contains(&x);
let _ = (0..9).contains(&x);
}
40 changes: 40 additions & 0 deletions tests/ui/almost_complete_range.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
warning: almost complete ascii range
--> $DIR/almost_complete_range.rs:8:13
|
8 | let _ = (b'a'..b'z').contains(&x);
| ^^^^^^^^^^^^ help: try: `b'a'..=b'a'`
|
= note: `#[warn(marker::almost_complete_range)]` on by default

warning: almost complete ascii range
--> $DIR/almost_complete_range.rs:9:13
|
9 | let _ = (b'A'..b'Z').contains(&x);
| ^^^^^^^^^^^^ help: try: `b'A'..=b'A'`

warning: almost complete ascii range
--> $DIR/almost_complete_range.rs:10:13
|
10 | let _ = (b'0'..b'9').contains(&x);
| ^^^^^^^^^^^^ help: try: `b'0'..=b'0'`

warning: almost complete ascii range
--> $DIR/almost_complete_range.rs:11:13
|
11 | let _ = ('a'..'z').contains(&c);
| ^^^^^^^^^^ help: try: `'a'..='a'`

warning: almost complete ascii range
--> $DIR/almost_complete_range.rs:12:13
|
12 | let _ = ('A'..'Z').contains(&c);
| ^^^^^^^^^^ help: try: `'A'..='A'`

warning: almost complete ascii range
--> $DIR/almost_complete_range.rs:13:13
|
13 | let _ = ('0'..'9').contains(&c);
| ^^^^^^^^^^ help: try: `'0'..='0'`

warning: 6 warnings emitted

7 changes: 0 additions & 7 deletions tests/ui/hello_marker.rs

This file was deleted.

12 changes: 0 additions & 12 deletions tests/ui/hello_marker.stderr

This file was deleted.

0 comments on commit c425d87

Please sign in to comment.