Files
clippy-lints/src/main.rs

550 lines
16 KiB
Rust
Raw Normal View History

2024-11-09 20:19:23 +01:00
use std::fmt::{self, Write as _};
use anyhow::{anyhow, Result};
use clap::{Parser, ValueEnum};
use serde::Deserialize;
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
enum LintGroup {
Cargo,
Complexity,
Correctness,
Nursery,
Pedantic,
Perf,
Restriction,
Style,
Suspicious,
Deprecated,
}
#[derive(Clone, Copy, Debug, ValueEnum)]
enum Profile {
Publish,
Personal,
}
#[derive(Parser, Debug)]
struct Args {
#[arg(long)]
profile: Profile,
#[arg(long)]
workspace: bool,
}
impl LintGroup {
fn as_str(self) -> &'static str {
match self {
Self::Cargo => "cargo",
Self::Complexity => "complexity",
Self::Correctness => "correctness",
Self::Nursery => "nursery",
Self::Pedantic => "pedantic",
Self::Perf => "perf",
Self::Restriction => "restriction",
Self::Style => "style",
Self::Suspicious => "suspicious",
Self::Deprecated => "deprecated",
}
}
}
impl fmt::Display for LintGroup {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.as_str())
}
}
#[derive(Clone, Copy, Debug, Deserialize)]
#[serde(rename_all = "lowercase")]
enum LintLevel {
Allow,
Warn,
Deny,
None,
}
impl LintLevel {
fn as_str(self) -> &'static str {
match self {
Self::Allow => "allow",
Self::Warn => "warn",
Self::Deny => "deny",
Self::None => "none",
}
}
}
2024-11-10 18:39:24 +01:00
#[derive(Debug)]
struct Lint<'a> {
id: LintId<'a>,
group: LintGroup,
}
#[derive(Debug, Deserialize)]
2024-11-09 20:19:23 +01:00
#[expect(dead_code, reason = "this is an external data definition")]
2024-11-10 18:39:24 +01:00
struct LintResponse {
id: String,
2024-11-09 20:19:23 +01:00
group: LintGroup,
#[serde(rename = "level")]
default_level: LintLevel,
version: String,
}
#[derive(Clone, Copy, Debug)]
enum PrioritySetting {
Explicit(isize),
Unspecified,
}
2024-11-10 18:39:24 +01:00
impl From<Option<isize>> for PrioritySetting {
fn from(value: Option<isize>) -> Self {
match value {
Some(i) => Self::Explicit(i),
None => Self::Unspecified,
}
}
}
#[derive(Debug, Deserialize, PartialEq, Eq)]
struct LintId<'a>(&'a str);
2024-11-09 20:19:23 +01:00
2024-11-10 18:39:24 +01:00
impl From<&'static str> for LintId<'static> {
fn from(value: &'static str) -> Self {
Self(value)
}
}
impl fmt::Display for LintId<'_> {
2024-11-09 20:19:23 +01:00
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
2024-11-10 18:39:24 +01:00
struct LintList<'a>(Vec<LintId<'a>>);
2024-11-09 20:19:23 +01:00
2024-11-10 18:39:24 +01:00
impl<'a> From<Vec<&'a str>> for LintList<'a> {
fn from(value: Vec<&'a str>) -> Self {
Self(value.into_iter().map(|s| LintId(s)).collect())
2024-11-09 20:19:23 +01:00
}
}
#[derive(Debug)]
2024-11-10 18:39:24 +01:00
struct SingleLintConfig<'a> {
lint: &'a LintId<'a>,
2024-11-09 20:19:23 +01:00
priority: PrioritySetting,
level: LintLevel,
}
#[derive(Debug)]
struct GroupConfig {
group: LintGroup,
priority: PrioritySetting,
level: LintLevel,
}
#[derive(Debug)]
2024-11-10 18:39:24 +01:00
enum Setting<'a> {
Single(SingleLintConfig<'a>),
2024-11-09 20:19:23 +01:00
Group(GroupConfig),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum ExhaustiveGroupClassification {
Default,
Exception,
}
#[derive(Debug)]
2024-11-10 18:39:24 +01:00
struct ExhausiveGroup<'a> {
defaults: Vec<Setting<'a>>,
exceptions: Vec<Setting<'a>>,
2024-11-09 20:19:23 +01:00
}
2024-11-10 18:39:24 +01:00
struct Exceptions<'a> {
2024-11-09 20:19:23 +01:00
level: LintLevel,
2024-11-10 18:39:24 +01:00
lints: LintList<'a>,
2024-11-09 20:19:23 +01:00
}
2024-11-10 18:39:24 +01:00
impl<'a> Setting<'a> {
fn group(group: LintGroup, level: LintLevel, priority: impl Into<PrioritySetting>) -> Self {
2024-11-09 20:19:23 +01:00
Self::Group(GroupConfig {
group,
2024-11-10 18:39:24 +01:00
priority: priority.into(),
2024-11-09 20:19:23 +01:00
level,
})
}
2024-11-10 18:39:24 +01:00
fn allow(
all_lints: &'a AllLints,
group: LintGroup,
lints: &'a [LintId<'a>],
) -> Result<Vec<Self>> {
2024-11-09 20:19:23 +01:00
lints
.iter()
.map(|lint| {
2024-11-10 18:39:24 +01:00
let found = all_lints
.0
.iter()
.find(|r| r.id == *lint && r.group == group);
2024-11-09 20:19:23 +01:00
if found.is_none() {
Err(anyhow!("lint {} not in group {}", lint, group.as_str()))
} else {
Ok(Self::Single(SingleLintConfig {
lint,
priority: PrioritySetting::Unspecified,
level: LintLevel::Allow,
}))
}
})
.collect()
}
fn split_group_exhaustive(
2024-11-10 18:39:24 +01:00
all_lints: &'a AllLints,
2024-11-09 20:19:23 +01:00
group: LintGroup,
default_level: LintLevel,
2024-11-10 18:39:24 +01:00
exceptions: &Exceptions<'a>,
) -> Result<ExhausiveGroup<'a>> {
let all_lints_in_group: Vec<&LintId> = all_lints
2024-11-09 20:19:23 +01:00
.0
.iter()
2024-11-10 18:39:24 +01:00
.filter(|lint| lint.group == group)
.map(|lint| &lint.id)
2024-11-09 20:19:23 +01:00
.collect();
2024-11-10 18:39:24 +01:00
let all_lints_in_group_len = all_lints_in_group.len();
exceptions
.lints
.0
.iter()
.find(|lint| (!all_lints_in_group.contains(lint)))
.map(|lint| Err(anyhow!("lint {lint} not part of group {group}")))
.unwrap_or(Ok(()))?;
2024-11-09 20:19:23 +01:00
Ok(all_lints_in_group
.into_iter()
.map(|lint| {
2024-11-10 18:39:24 +01:00
if exceptions.lints.0.contains(lint) {
2024-11-09 20:19:23 +01:00
(
ExhaustiveGroupClassification::Exception,
Self::Single(SingleLintConfig {
lint,
priority: PrioritySetting::Unspecified,
level: exceptions.level,
}),
)
} else {
(
ExhaustiveGroupClassification::Default,
Self::Single(SingleLintConfig {
lint,
priority: PrioritySetting::Unspecified,
level: default_level,
}),
)
}
})
.fold(
2024-11-10 18:39:24 +01:00
{
let len_1 = exceptions.lints.0.len();
ExhausiveGroup {
defaults: Vec::with_capacity(
all_lints_in_group_len.checked_sub(len_1).expect(
"exceptions are a subset of of all lints in group, checked above",
),
),
exceptions: Vec::with_capacity(len_1),
}
2024-11-09 20:19:23 +01:00
},
|mut acc, (classification, setting)| {
match classification {
ExhaustiveGroupClassification::Default => acc.defaults.push(setting),
ExhaustiveGroupClassification::Exception => acc.exceptions.push(setting),
};
acc
},
))
}
}
#[derive(Debug)]
2024-11-10 18:39:24 +01:00
struct ConfigGroup<'a> {
2024-11-09 20:19:23 +01:00
comment: Option<String>,
2024-11-10 18:39:24 +01:00
settings: Vec<Setting<'a>>,
2024-11-09 20:19:23 +01:00
}
#[derive(Debug)]
2024-11-10 18:39:24 +01:00
struct Config<'a>(Vec<ConfigGroup<'a>>);
2024-11-09 20:19:23 +01:00
2024-11-10 18:39:24 +01:00
impl Config<'_> {
2024-11-09 20:19:23 +01:00
fn to_toml(&self, args: &Args) -> String {
let mut output = if args.workspace {
String::from("[workspace.lints.clippy]\n")
} else {
String::from("[lints.clippy]\n")
};
let mut iter_group = self.0.iter().peekable();
while let Some(group) = iter_group.next() {
let last_group = iter_group.peek().is_none();
if let Some(ref comment) = group.comment {
writeln!(output, "# {comment}").expect("writing to string succeeds");
}
let mut iter_setting = group.settings.iter().peekable();
while let Some(setting) = iter_setting.next() {
let last_setting = iter_setting.peek().is_none();
match *setting {
Setting::Single(ref single_lint_config) => match single_lint_config.priority {
PrioritySetting::Explicit(priority) => write!(
output,
"{} = {{ level = \"{}\", priority = {} }}",
single_lint_config.lint.0,
single_lint_config.level.as_str(),
priority
)
.expect("writing to string succeeds"),
PrioritySetting::Unspecified => write!(
output,
"{} = \"{}\"",
single_lint_config.lint.0,
single_lint_config.level.as_str()
)
.expect("writing to string succeeds"),
},
Setting::Group(ref group_config) => match group_config.priority {
PrioritySetting::Explicit(priority) => write!(
output,
"{} = {{ level = \"{}\", priority = {} }}",
group_config.group.as_str(),
group_config.level.as_str(),
priority
)
.expect("writing to string succeeds"),
PrioritySetting::Unspecified => write!(
output,
"{} = \"{}\"",
group_config.group.as_str(),
group_config.level.as_str(),
)
.expect("writing to string succeeds"),
},
};
if !last_setting {
output.push('\n');
}
if last_setting && !last_group {
output.push('\n');
}
}
if !last_group {
output.push('\n');
}
}
output
}
}
#[derive(Debug, Deserialize)]
2024-11-10 18:39:24 +01:00
struct Response(Vec<LintResponse>);
#[derive(Debug)]
struct AllLints<'a>(Vec<Lint<'a>>);
impl<'a> AllLints<'a> {
fn from_response(response: &'a Response) -> Self {
Self(
response
.0
.iter()
.map(|lint| Lint {
id: LintId(&lint.id),
group: lint.group,
})
.collect(),
)
}
}
2024-11-09 20:19:23 +01:00
fn main() -> Result<()> {
let args = Args::parse();
let response: Response = ureq::get("https://rust-lang.github.io/rust-clippy/stable/lints.json")
.call()?
2024-11-10 18:39:24 +01:00
.into_json::<Response>()?;
let all_lints = AllLints::from_response(&response);
2024-11-09 20:19:23 +01:00
let restriction_group = Setting::split_group_exhaustive(
2024-11-10 18:39:24 +01:00
&all_lints,
2024-11-09 20:19:23 +01:00
LintGroup::Restriction,
LintLevel::Allow,
&Exceptions {
level: LintLevel::Warn,
lints: vec![
"allow_attributes",
"allow_attributes_without_reason",
"arithmetic_side_effects",
"as_conversions",
"assertions_on_result_states",
"cfg_not_test",
"clone_on_ref_ptr",
"create_dir",
"dbg_macro",
"decimal_literal_representation",
"default_numeric_fallback",
"deref_by_slicing",
"disallowed_script_idents",
"else_if_without_else",
"empty_drop",
"empty_enum_variants_with_brackets",
"empty_structs_with_brackets",
"exit",
"filetype_is_file",
"float_arithmetic",
"float_cmp_const",
"fn_to_numeric_cast_any",
"format_push_string",
"get_unwrap",
"indexing_slicing",
"infinite_loop",
"inline_asm_x86_att_syntax",
"inline_asm_x86_intel_syntax",
"integer_division",
"iter_over_hash_type",
"large_include_file",
"let_underscore_must_use",
"let_underscore_untyped",
"little_endian_bytes",
"lossy_float_literal",
"map_err_ignore",
"mem_forget",
"missing_assert_message",
"missing_asserts_for_indexing",
"mixed_read_write_in_expression",
"modulo_arithmetic",
"multiple_inherent_impl",
"multiple_unsafe_ops_per_block",
"mutex_atomic",
"panic",
"partial_pub_fields",
"pattern_type_mismatch",
"print_stderr",
"print_stdout",
"pub_without_shorthand",
"rc_buffer",
"rc_mutex",
"redundant_type_annotations",
"renamed_function_params",
"rest_pat_in_fully_bound_structs",
"same_name_method",
"self_named_module_files",
"semicolon_inside_block",
"str_to_string",
"string_add",
"string_lit_chars_any",
"string_slice",
"string_to_string",
"suspicious_xor_used_as_pow",
"tests_outside_test_module",
"todo",
"try_err",
"undocumented_unsafe_blocks",
"unimplemented",
"unnecessary_safety_comment",
"unnecessary_safety_doc",
"unnecessary_self_imports",
"unneeded_field_pattern",
"unseparated_literal_suffix",
"unused_result_ok",
"unwrap_used",
"use_debug",
"verbose_file_reads",
]
.into(),
},
)?;
2024-11-10 18:39:24 +01:00
let cargo_lints = {
let mut v = vec!["multiple_crate_versions".into()];
match args.profile {
Profile::Publish => (),
Profile::Personal => v.push("cargo_common_metadata".into()),
}
v
};
let pedantic_allows = &[
"too_many_lines".into(),
"must_use_candidate".into(),
"map_unwrap_or".into(),
"missing_errors_doc".into(),
"if_not_else".into(),
];
let nursery_allows = &["missing_const_for_fn".into(), "option_if_let_else".into()];
let complexity_allows = &["too_many_arguments".into()];
let style_allows = &["new_without_default".into(), "redundant_closure".into()];
2024-11-09 20:19:23 +01:00
let config = Config(vec![
ConfigGroup {
comment: Some("enabled groups".to_owned()),
settings: vec![
2024-11-10 18:39:24 +01:00
Setting::group(LintGroup::Correctness, LintLevel::Deny, Some(-1)),
Setting::group(LintGroup::Suspicious, LintLevel::Warn, Some(-1)),
Setting::group(LintGroup::Style, LintLevel::Warn, Some(-1)),
Setting::group(LintGroup::Complexity, LintLevel::Warn, Some(-1)),
Setting::group(LintGroup::Perf, LintLevel::Warn, Some(-1)),
Setting::group(LintGroup::Cargo, LintLevel::Warn, Some(-1)),
Setting::group(LintGroup::Pedantic, LintLevel::Warn, Some(-1)),
Setting::group(LintGroup::Nursery, LintLevel::Warn, Some(-1)),
2024-11-09 20:19:23 +01:00
],
},
ConfigGroup {
comment: Some("pedantic overrides".to_owned()),
2024-11-10 18:39:24 +01:00
settings: Setting::allow(&all_lints, LintGroup::Pedantic, pedantic_allows)?,
2024-11-09 20:19:23 +01:00
},
ConfigGroup {
comment: Some("nursery overrides".to_owned()),
2024-11-10 18:39:24 +01:00
settings: Setting::allow(&all_lints, LintGroup::Nursery, nursery_allows)?,
2024-11-09 20:19:23 +01:00
},
ConfigGroup {
comment: Some("complexity overrides".to_owned()),
2024-11-10 18:39:24 +01:00
settings: Setting::allow(&all_lints, LintGroup::Complexity, complexity_allows)?,
2024-11-09 20:19:23 +01:00
},
ConfigGroup {
comment: Some("style overrides".to_owned()),
2024-11-10 18:39:24 +01:00
settings: Setting::allow(&all_lints, LintGroup::Style, style_allows)?,
2024-11-09 20:19:23 +01:00
},
ConfigGroup {
comment: Some("cargo overrides".to_owned()),
2024-11-10 18:39:24 +01:00
settings: Setting::allow(&all_lints, LintGroup::Cargo, &cargo_lints)?,
2024-11-09 20:19:23 +01:00
},
ConfigGroup {
comment: Some("selected restrictions".to_owned()),
settings: restriction_group.exceptions,
},
ConfigGroup {
comment: Some("restrictions explicit allows".to_owned()),
settings: restriction_group.defaults,
},
]);
let output = config.to_toml(&args);
#[expect(clippy::print_stdout, reason = "this is the main program output")]
{
println!("{output}");
}
Ok(())
}