lints

`rustc` lints.
git clone https://git.philomathiclife.com/repos/lints
Log | Files | Refs | README

main.rs (9547B)


      1 //! Consult [`README.md`](https://crates.io/crates/lints/).
      2 /// CLI-argument parsing.
      3 mod args;
      4 /// Parsing of `rustc -Whelp` output.
      5 mod parse;
      6 /// `rustc` execution.
      7 mod rustc;
      8 use args::{Cmd, E as ArgsErr};
      9 use core::{convert, str};
     10 use parse::{Data, E as ParseErr, LintGroup, WARNINGS};
     11 #[cfg(target_os = "openbsd")]
     12 use priv_sep::{Permissions, Promises};
     13 use rustc::E as RustcErr;
     14 use std::{
     15     collections::HashSet,
     16     env,
     17     io::{self, Error, Write as _},
     18     process::ExitCode,
     19 };
     20 /// Writes `err` to `stderr`.
     21 #[expect(
     22     clippy::needless_pass_by_value,
     23     reason = "Error is small and makes the signature more pleasant when dealing with Result::map_or_else"
     24 )]
     25 fn into_exit_code(err: Error) -> ExitCode {
     26     writeln!(io::stderr().lock(), "{err}").map_or(ExitCode::FAILURE, |()| ExitCode::FAILURE)
     27 }
     28 /// Help message.
     29 const HELP_MSG: &str =
     30     "Denies or allows all lints based on rustc -Whelp and what command is passed
     31 
     32 Usage: lints [COMMAND] [OPTIONS]
     33 
     34 Commands:
     35     allow      Allows all lints
     36     deny       Denies all lints
     37     help       This message
     38     version    Prints version info
     39 
     40 Options:
     41   --all                      Writes all individual lints even if they're already part of a lint group
     42   --allow-undefined-lints    Lint groups that contain undefined lints are allowed
     43   -                          If the last argument, stdin is read instead of getting the output from rustc
     44 
     45 When nothing or deny is passed, all lint groups and allow-by-default lints that are not part of a group
     46 are set to deny.
     47 When allow is passed, \"warnings\" and all deny-by-default lints are set to allow.
     48 stdout is written to in the format of the TOML table, lints.rust, which can be added to Cargo.toml.";
     49 /// Version of `lints`.
     50 const VERSION: &str = concat!("lints ", env!("CARGO_PKG_VERSION"));
     51 /// No-op.
     52 #[expect(clippy::unnecessary_wraps, reason = "unify with OpenBSD")]
     53 #[cfg(not(target_os = "openbsd"))]
     54 const fn priv_sep<Never>() -> Result<(), Never> {
     55     Ok(())
     56 }
     57 /// `pledge(2)`s `c"exec proc rpath stdio unveil"` before `unveil(2)`ing no file-system access.
     58 #[cfg(target_os = "openbsd")]
     59 fn priv_sep() -> Result<(), Error> {
     60     Promises::pledge_raw(c"exec proc rpath stdio unveil")
     61         .and_then(|()| Permissions::unveil_raw(c"/", c""))
     62         .map_err(Error::from)
     63 }
     64 /// Transforms `ascii` into a `str` based on the assumption that it's already been verified to be valid
     65 /// UTF-8.
     66 #[expect(clippy::unreachable, reason = "want to crash when there is a bug")]
     67 fn as_str(ascii: &[u8]) -> &str {
     68     str::from_utf8(ascii).unwrap_or_else(|_e| {
     69         unreachable!("names of lints and lint groups are verified to be a subset of ASCII")
     70     })
     71 }
     72 /// Entry to application.
     73 #[expect(clippy::too_many_lines, reason = "101 lines is OK")]
     74 fn main() -> ExitCode {
     75     priv_sep().map_or_else(into_exit_code, |()| {
     76         Cmd::try_from_args(env::args_os()).map_or_else(ArgsErr::into_exit_code, |cmd| match cmd {
     77             Cmd::Allow(opts) | Cmd::Deny(opts) => {
     78                 rustc::execute(opts.read_stdin).map_or_else(RustcErr::into_exit_code, |output| {
     79                     Data::new(&output, opts.allow_undefined_lints)
     80                         .map_err(ParseErr::into_exit_code)
     81                         .and_then(|mut data| {
     82                             let mut stdout = io::stdout().lock();
     83                             if opts.all_lints {
     84                                 let level = if matches!(cmd, Cmd::Allow(_)) {
     85                                     "allow"
     86                                 } else {
     87                                     "deny"
     88                                 };
     89                                 // Since `"warnings"` is both a lint and lint group, `data.groups`
     90                                 // does not contain it; instead `data.warn` is the set of all
     91                                 // `warn`-by-default lints sans the lint `"warnings"`.
     92                                 // We write groups first, so we add an empty `"warnings"` group.
     93                                 data.groups.push(LintGroup {
     94                                     name: WARNINGS.as_bytes(),
     95                                     lints: HashSet::new(),
     96                                 });
     97                                 data.groups.sort_unstable();
     98                                 data.groups
     99                                     .into_iter()
    100                                     .try_fold((), |(), group| {
    101                                         writeln!(
    102                                             stdout,
    103                                             "{} = {{ level = \"{level}\", priority = -1 }}",
    104                                             as_str(group.name)
    105                                         )
    106                                     })
    107                                     .and_then(|()| {
    108                                         // There are likely more `warn`-by-default lints, so
    109                                         // we use its memory allocation to append the other lints.
    110                                         data.warn.extend_from_slice(&data.allow);
    111                                         // Reduce memory when we can.
    112                                         drop(data.allow);
    113                                         data.warn.extend_from_slice(&data.deny);
    114                                         // Reduce memory when we can.
    115                                         drop(data.deny);
    116                                         data.warn.sort_unstable();
    117                                         data.warn.iter().try_fold((), |(), lint| {
    118                                             writeln!(
    119                                                 stdout,
    120                                                 "{} = {{ level = \"{level}\", priority = -1 }}",
    121                                                 as_str(lint)
    122                                             )
    123                                         })
    124                                     })
    125                             } else if matches!(cmd, Cmd::Allow(_)) {
    126                                 writeln!(
    127                                     stdout,
    128                                     "{WARNINGS} = {{ level = \"allow\", priority = -1 }}"
    129                                 )
    130                                 .and_then(|()| {
    131                                     data.deny.iter().try_fold((), |(), lint| {
    132                                         writeln!(
    133                                             stdout,
    134                                             "{} = {{ level = \"allow\", priority = -1 }}",
    135                                             as_str(lint)
    136                                         )
    137                                     })
    138                                 })
    139                             } else {
    140                                 // Since `"warnings"` is both a lint and lint group, `data.groups`
    141                                 // does not contain it; instead `data.warn` is the set of all
    142                                 // `warn`-by-default lints sans the lint `"warnings"`.
    143                                 // We write groups first, so we add an empty `"warnings"` group.
    144                                 data.groups.push(LintGroup {
    145                                     name: WARNINGS.as_bytes(),
    146                                     lints: HashSet::new(),
    147                                 });
    148                                 data.groups.sort_unstable();
    149                                 data.groups
    150                                     .iter()
    151                                     .try_fold((), |(), group| {
    152                                         writeln!(
    153                                             stdout,
    154                                             "{} = {{ level = \"deny\", priority = -1 }}",
    155                                             as_str(group.name)
    156                                         )
    157                                     })
    158                                     .and_then(|()| {
    159                                         data.allow.iter().try_fold((), |(), lint| {
    160                                             if data.groups.iter().any(|group| {
    161                                                 group
    162                                                     .lints
    163                                                     .iter()
    164                                                     .any(|group_lint| group_lint == lint)
    165                                             }) {
    166                                                 Ok(())
    167                                             } else {
    168                                                 writeln!(
    169                                                     stdout,
    170                                                     "{} = {{ level = \"deny\", priority = -1 }}",
    171                                                     as_str(lint)
    172                                                 )
    173                                             }
    174                                         })
    175                                     })
    176                             }
    177                             .map_err(into_exit_code)
    178                         })
    179                         .map_or_else(convert::identity, |()| ExitCode::SUCCESS)
    180                 })
    181             }
    182             Cmd::Help => writeln!(io::stdout().lock(), "{HELP_MSG}")
    183                 .map_or_else(into_exit_code, |()| ExitCode::SUCCESS),
    184             Cmd::Version => writeln!(io::stdout().lock(), "{VERSION}")
    185                 .map_or_else(into_exit_code, |()| ExitCode::SUCCESS),
    186         })
    187     })
    188 }