rust-clippy/clippy_lints/src/excessive_bools.rs

177 lines
5.1 KiB
Rust
Raw Normal View History

use clippy_utils::diagnostics::span_lint_and_help;
use rustc_ast::ast::{AssocItemKind, Extern, Fn, FnSig, Impl, Item, ItemKind, Trait, Ty, TyKind};
2020-02-01 23:49:52 +00:00
use rustc_lint::{EarlyContext, EarlyLintPass};
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::{sym, Span};
2020-02-01 23:49:52 +00:00
declare_clippy_lint! {
/// ### What it does
/// Checks for excessive
2020-02-01 23:49:52 +00:00
/// use of bools in structs.
///
/// ### Why is this bad?
/// Excessive bools in a struct
2020-02-01 23:49:52 +00:00
/// is often a sign that it's used as a state machine,
/// which is much better implemented as an enum.
/// If it's not the case, excessive bools usually benefit
/// from refactoring into two-variant enums for better
/// readability and API.
///
/// ### Example
2020-02-01 23:49:52 +00:00
/// ```rust
/// struct S {
/// is_pending: bool,
/// is_processing: bool,
/// is_finished: bool,
/// }
/// ```
///
2022-05-28 13:22:59 +00:00
/// Use instead:
2020-02-01 23:49:52 +00:00
/// ```rust
/// enum S {
/// Pending,
/// Processing,
/// Finished,
/// }
/// ```
Added `clippy::version` attribute to all normal lints So, some context for this, well, more a story. I'm not used to scripting, I've never really scripted anything, even if it's a valuable skill. I just never really needed it. Now, `@flip1995` correctly suggested using a script for this in `rust-clippy#7813`... And I decided to write a script using nushell because why not? This was a mistake... I spend way more time on this than I would like to admit. It has definitely been more than 4 hours. It shouldn't take that long, but me being new to scripting and nushell just wasn't a good mixture... Anyway, here is the script that creates another script which adds the versions. Fun... Just execute this on the `gh-pages` branch and the resulting `replacer.sh` in `clippy_lints` and it should all work. ```nu mv v0.0.212 rust-1.00.0; mv beta rust-1.57.0; mv master rust-1.58.0; let paths = (open ./rust-1.58.0/lints.json | select id id_span | flatten | select id path); let versions = ( ls | where name =~ "rust-" | select name | format {name}/lints.json | each { open $it | select id | insert version $it | str substring "5,11" version} | group-by id | rotate counter-clockwise id version | update version {get version | first 1} | flatten | select id version); $paths | each { |row| let version = ($versions | where id == ($row.id) | format {version}) let idu = ($row.id | str upcase) $"sed -i '0,/($idu),/{s/pub ($idu),/#[clippy::version = "($version)"]\n pub ($idu),/}' ($row.path)" } | str collect ";" | str find-replace --all '1.00.0' 'pre 1.29.0' | save "replacer.sh"; ``` And this still has some problems, but at this point I just want to be done -.-
2021-10-21 19:06:26 +00:00
#[clippy::version = "1.43.0"]
2020-02-01 23:49:52 +00:00
pub STRUCT_EXCESSIVE_BOOLS,
pedantic,
"using too many bools in a struct"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for excessive use of
2020-02-01 23:49:52 +00:00
/// bools in function definitions.
///
/// ### Why is this bad?
/// Calls to such functions
2020-02-01 23:49:52 +00:00
/// are confusing and error prone, because it's
/// hard to remember argument order and you have
/// no type system support to back you up. Using
/// two-variant enums instead of bools often makes
/// API easier to use.
///
/// ### Example
2020-02-01 23:49:52 +00:00
/// ```rust,ignore
/// fn f(is_round: bool, is_hot: bool) { ... }
/// ```
///
2022-06-05 19:24:41 +00:00
/// Use instead:
2020-02-01 23:49:52 +00:00
/// ```rust,ignore
/// enum Shape {
/// Round,
/// Spiky,
/// }
///
/// enum Temperature {
/// Hot,
/// IceCold,
/// }
///
/// fn f(shape: Shape, temperature: Temperature) { ... }
/// ```
Added `clippy::version` attribute to all normal lints So, some context for this, well, more a story. I'm not used to scripting, I've never really scripted anything, even if it's a valuable skill. I just never really needed it. Now, `@flip1995` correctly suggested using a script for this in `rust-clippy#7813`... And I decided to write a script using nushell because why not? This was a mistake... I spend way more time on this than I would like to admit. It has definitely been more than 4 hours. It shouldn't take that long, but me being new to scripting and nushell just wasn't a good mixture... Anyway, here is the script that creates another script which adds the versions. Fun... Just execute this on the `gh-pages` branch and the resulting `replacer.sh` in `clippy_lints` and it should all work. ```nu mv v0.0.212 rust-1.00.0; mv beta rust-1.57.0; mv master rust-1.58.0; let paths = (open ./rust-1.58.0/lints.json | select id id_span | flatten | select id path); let versions = ( ls | where name =~ "rust-" | select name | format {name}/lints.json | each { open $it | select id | insert version $it | str substring "5,11" version} | group-by id | rotate counter-clockwise id version | update version {get version | first 1} | flatten | select id version); $paths | each { |row| let version = ($versions | where id == ($row.id) | format {version}) let idu = ($row.id | str upcase) $"sed -i '0,/($idu),/{s/pub ($idu),/#[clippy::version = "($version)"]\n pub ($idu),/}' ($row.path)" } | str collect ";" | str find-replace --all '1.00.0' 'pre 1.29.0' | save "replacer.sh"; ``` And this still has some problems, but at this point I just want to be done -.-
2021-10-21 19:06:26 +00:00
#[clippy::version = "1.43.0"]
2020-02-01 23:49:52 +00:00
pub FN_PARAMS_EXCESSIVE_BOOLS,
pedantic,
"using too many bools in function parameters"
}
pub struct ExcessiveBools {
max_struct_bools: u64,
max_fn_params_bools: u64,
}
impl ExcessiveBools {
#[must_use]
pub fn new(max_struct_bools: u64, max_fn_params_bools: u64) -> Self {
Self {
max_struct_bools,
max_fn_params_bools,
}
}
fn check_fn_sig(&self, cx: &EarlyContext<'_>, fn_sig: &FnSig, span: Span) {
match fn_sig.header.ext {
Extern::Implicit | Extern::Explicit(_) => return,
Extern::None => (),
}
let fn_sig_bools = fn_sig
.decl
.inputs
.iter()
.filter(|param| is_bool_ty(&param.ty))
.count()
.try_into()
.unwrap();
if self.max_fn_params_bools < fn_sig_bools {
span_lint_and_help(
cx,
FN_PARAMS_EXCESSIVE_BOOLS,
span,
&format!("more than {} bools in function parameters", self.max_fn_params_bools),
None,
2020-02-01 23:49:52 +00:00
"consider refactoring bools into two-variant enums",
);
}
}
}
impl_lint_pass!(ExcessiveBools => [STRUCT_EXCESSIVE_BOOLS, FN_PARAMS_EXCESSIVE_BOOLS]);
fn is_bool_ty(ty: &Ty) -> bool {
if let TyKind::Path(None, path) = &ty.kind {
if let [name] = path.segments.as_slice() {
return name.ident.name == sym::bool;
}
2020-02-01 23:49:52 +00:00
}
false
}
impl EarlyLintPass for ExcessiveBools {
fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) {
if item.span.from_expansion() {
2020-02-01 23:49:52 +00:00
return;
}
match &item.kind {
ItemKind::Struct(variant_data, _) => {
if item.attrs.iter().any(|attr| attr.has_name(sym::repr)) {
2020-02-01 23:49:52 +00:00
return;
}
let struct_bools = variant_data
.fields()
.iter()
.filter(|field| is_bool_ty(&field.ty))
.count()
.try_into()
.unwrap();
if self.max_struct_bools < struct_bools {
span_lint_and_help(
cx,
STRUCT_EXCESSIVE_BOOLS,
item.span,
&format!("more than {} bools in a struct", self.max_struct_bools),
None,
2020-02-01 23:49:52 +00:00
"consider using a state machine or refactoring bools into two-variant enums",
);
}
},
ItemKind::Impl(box Impl {
of_trait: None, items, ..
})
| ItemKind::Trait(box Trait { items, .. }) => {
2020-02-01 23:49:52 +00:00
for item in items {
if let AssocItemKind::Fn(box Fn { sig, .. }) = &item.kind {
self.check_fn_sig(cx, sig, item.span);
2020-02-01 23:49:52 +00:00
}
}
},
ItemKind::Fn(box Fn { sig, .. }) => self.check_fn_sig(cx, sig, item.span),
2020-02-01 23:49:52 +00:00
_ => (),
}
}
}