mod args;
mod cmd;
mod parse;
use args::{Cmd, E as ArgsErr};
use cmd::E as CmdErr;
use core::{convert, str};
use parse::{Data, E as ParseErr, LintGroup, WARNINGS};
#[cfg(target_os = "openbsd")]
use priv_sep::{Permissions, Promises};
use std::{
collections::HashSet,
env,
io::{self, Error, Write as _},
process::ExitCode,
};
#[expect(
clippy::needless_pass_by_value,
reason = "Error is small and makes the signature more pleasant when dealing with Result::map_or_else"
)]
fn into_exit_code(err: Error) -> ExitCode {
writeln!(io::stderr().lock(), "{err}").map_or(ExitCode::FAILURE, |()| ExitCode::FAILURE)
}
const HELP_MSG: &str =
"Denies or allows all lints based on rustc -Whelp and what command is passed
Usage: lints [COMMAND] [OPTIONS]
Commands:
allow Allows all lints
deny Denies all lints
help This message
version Prints version info
Options:
--all Writes all individual lints even if they're already part of a lint group
--allow-undefined-lints Lint groups that contain undefined lints are allowed
When nothing or deny is passed, all lint groups and allow-by-default lints that are not part of a group
are set to deny.
When allow is passed, \"warnings\" and all deny-by-default lints are set to allow.
stdout is written to in the format of the TOML table, lints.rust, which can be added to Cargo.toml.";
const VERSION: &str = concat!("lints ", env!("CARGO_PKG_VERSION"));
#[expect(clippy::unnecessary_wraps, reason = "unify with OpenBSD")]
#[cfg(not(target_os = "openbsd"))]
const fn priv_sep<Never>() -> Result<(), Never> {
Ok(())
}
#[cfg(target_os = "openbsd")]
fn priv_sep() -> Result<(), Error> {
Promises::pledge_raw(c"exec proc rpath stdio unveil")
.and_then(|()| Permissions::unveil_raw(c"/", c""))
.map_err(Error::from)
}
#[expect(clippy::unreachable, reason = "want to crash when there is a bug")]
fn as_str(ascii: &[u8]) -> &str {
str::from_utf8(ascii).unwrap_or_else(|_e| {
unreachable!("names of lints and lint groups are verified to be a subset of ASCII")
})
}
#[expect(clippy::too_many_lines, reason = "101 lines is OK")]
fn main() -> ExitCode {
priv_sep().map_or_else(into_exit_code, |()| {
Cmd::try_from_args(env::args_os()).map_or_else(ArgsErr::into_exit_code, |cmd| match cmd {
Cmd::Allow(all, allow_undefined) | Cmd::Deny(all, allow_undefined) => {
cmd::execute_rustc().map_or_else(CmdErr::into_exit_code, |output| {
Data::new(&output, allow_undefined)
.map_err(ParseErr::into_exit_code)
.and_then(|mut data| {
let mut stdout = io::stdout().lock();
if all {
let level = if matches!(cmd, Cmd::Allow(_, _)) {
"allow"
} else {
"deny"
};
data.groups.push(LintGroup {
name: WARNINGS.as_bytes(),
lints: HashSet::new(),
});
data.groups.sort_unstable();
data.groups
.into_iter()
.try_fold((), |(), group| {
writeln!(
stdout,
"{} = {{ level = \"{level}\", priority = -1 }}",
as_str(group.name)
)
})
.and_then(|()| {
data.warn.extend_from_slice(&data.allow);
drop(data.allow);
data.warn.extend_from_slice(&data.deny);
drop(data.deny);
data.warn.sort_unstable();
data.warn.iter().try_fold((), |(), lint| {
writeln!(
stdout,
"{} = {{ level = \"{level}\", priority = -1 }}",
as_str(lint)
)
})
})
} else if matches!(cmd, Cmd::Allow(_, _)) {
writeln!(
stdout,
"{WARNINGS} = {{ level = \"allow\", priority = -1 }}"
)
.and_then(|()| {
data.deny.iter().try_fold((), |(), lint| {
writeln!(
stdout,
"{} = {{ level = \"allow\", priority = -1 }}",
as_str(lint)
)
})
})
} else {
data.groups.push(LintGroup {
name: WARNINGS.as_bytes(),
lints: HashSet::new(),
});
data.groups.sort_unstable();
data.groups
.iter()
.try_fold((), |(), group| {
writeln!(
stdout,
"{} = {{ level = \"deny\", priority = -1 }}",
as_str(group.name)
)
})
.and_then(|()| {
data.allow.iter().try_fold((), |(), lint| {
if data.groups.iter().any(|group| {
group
.lints
.iter()
.any(|group_lint| group_lint == lint)
}) {
Ok(())
} else {
writeln!(
stdout,
"{} = {{ level = \"deny\", priority = -1 }}",
as_str(lint)
)
}
})
})
}
.map_err(into_exit_code)
})
.map_or_else(convert::identity, |()| ExitCode::SUCCESS)
})
}
Cmd::Help => writeln!(io::stdout().lock(), "{HELP_MSG}")
.map_or_else(into_exit_code, |()| ExitCode::SUCCESS),
Cmd::Version => writeln!(io::stdout().lock(), "{VERSION}")
.map_or_else(into_exit_code, |()| ExitCode::SUCCESS),
})
})
}