commit c98f90b4d010c776e300bf56d0108d65f9501c8a
parent 9925eaba706db7aca9c59c099f99a89af58e5516
Author: Zack Newman <zack@philomathiclife.com>
Date: Tue, 27 Jan 2026 14:13:06 -0700
read from stdin option. integration test
Diffstat:
| M | Cargo.toml | | | 1 | + |
| M | README.md | | | 7 | ++++--- |
| M | src/args.rs | | | 341 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------- |
| D | src/cmd.rs | | | 240 | ------------------------------------------------------------------------------- |
| M | src/main.rs | | | 19 | ++++++++++--------- |
| M | src/parse.rs | | | 104 | +++++++++++++++++++++++++++++++++++++++---------------------------------------- |
| A | src/rustc.rs | | | 263 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | tests/parse_1_93_0.rs | | | 348 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
8 files changed, 938 insertions(+), 385 deletions(-)
diff --git a/Cargo.toml b/Cargo.toml
@@ -4,6 +4,7 @@ categories = ["command-line-utilities", "development-tools", "rust-patterns"]
description = "Writes [lints.rust] to stdout such that all lints are denied or allowed."
documentation = "https://crates.io/crates/lints/"
edition = "2024"
+exclude = ["outputs/"]
keywords = ["lints", "rust"]
license = "MIT OR Apache-2.0"
name = "lints"
diff --git a/README.md b/README.md
@@ -5,7 +5,7 @@ CLI app for `rustc` lints
[<img alt="crates.io" src="https://img.shields.io/crates/v/lints.svg?style=for-the-badge&color=fc8d62&logo=rust" height="20">](https://crates.io/crates/lints)
`lints` is a CLI application that parses the output of [`rustc -Whelp`](https://doc.rust-lang.org/stable/rustc/)
-before writing the lints to `stdout` in a format that can be added to a TOML table, `lint.rustc` in `Cargo.toml`.
+before writing the lints to `stdout` in a format that can be added to a TOML table, `lints.rust`, in `Cargo.toml`.
## Why is this useful?
@@ -29,6 +29,7 @@ lints.
* `--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.
+* `-`: If the last argument, `stdin` is read instead of getting the output from `rustc`.
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`.
@@ -37,8 +38,8 @@ When `allow` is passed, `"warnings"` and all `deny`-by-default lints are set to
`--allow-undefined-lints` should rarely be necessary since the lints defined in a lint group per `rustc -Whelp`
are almost always one of the lints listed. There have been exceptions though (e.g., `rustc 1.48.0` lists the
-lint group `"rustdoc"` containing the lint `"private-intra-doc-links"` despite that lint not being list in
-the table of lints).
+lint group `"rustdoc"` containing the lint `"private-intra-doc-links"` despite that lint not being in the table
+of lints).
## `lints` in action
diff --git a/src/args.rs b/src/args.rs
@@ -30,134 +30,221 @@ impl E {
}
/// The command passed.
#[cfg_attr(test, derive(Debug, PartialEq))]
+#[derive(Clone, Copy)]
pub(crate) enum Cmd {
/// `"allow"` all lints.
- ///
- /// The first contained `bool` is `true` iff all lint groups and individual lints should be `allow`ed
- /// instead of just `"warnings"` lint group and `deny`-by-default lints.
- /// The second contained `bool` is `true` iff lint groups are allowed to contain undefined lints.
- Allow(bool, bool),
+ Allow(Opts),
/// `"deny"`.
- ///
- /// The first contained `bool` is `true` iff all individual lints should be `deny`ed instead of just
- /// the lint groups and `allow`-by-default lints that are not part of any group.
- /// The second contained `bool` is `true` iff lint groups are allowed to contain undefined lints.
- Deny(bool, bool),
+ Deny(Opts),
/// `"help"`.
Help,
/// `"version"`.
Version,
}
-/// Options we allow.
-#[derive(Clone, Copy)]
-enum Opt {
- /// No option.
- None,
- /// `"--all"`.
- All,
- /// `"--allow-undefined-lints"`.
- AllowUndefined,
- /// `"--all"` and `"--allow-undefined-lints"`.
- AllAndAllowUndefined,
+/// Options to process lints.
+#[cfg_attr(test, derive(Debug, PartialEq))]
+#[derive(Clone, Copy, Default)]
+pub(crate) struct Opts {
+ /// `--all` was passed.
+ pub all_lints: bool,
+ /// `--allow-undefined-lints` was passed.
+ pub allow_undefined_lints: bool,
+ /// `-` was passed.
+ pub read_stdin: bool,
}
-impl Cmd {
- /// Extracts options from `opts`.
- fn get_opts<I: Iterator<Item = OsString>>(
+impl Opts {
+ /// Add options from `opts` to `self`.
+ ///
+ /// Returns `None` iff successful; otherwise returns the problematic argument.
+ fn add<I: Iterator<Item = OsString>>(
+ &mut self,
mut opts: I,
- opt: Opt,
arg: OsString,
- ) -> Result<Opt, E> {
+ ) -> Option<OsString> {
+ /// `b'-'`.
+ const DASH: u8 = b'-';
/// `b"--all"`.
const ALL: &[u8] = b"--all".as_slice();
/// `b"--allow-undefined-lints"`.
const ALLOW_UNDEFINED_LINTS: &[u8] = b"--allow-undefined-lints".as_slice();
match arg.as_encoded_bytes() {
- ALL => match opt {
- Opt::None => Ok(Opt::All),
- Opt::All | Opt::AllAndAllowUndefined => Err(E::UnknownArg(arg)),
- Opt::AllowUndefined => Ok(Opt::AllAndAllowUndefined),
- },
- ALLOW_UNDEFINED_LINTS => match opt {
- Opt::None => Ok(Opt::AllowUndefined),
- Opt::AllowUndefined | Opt::AllAndAllowUndefined => Err(E::UnknownArg(arg)),
- Opt::All => Ok(Opt::AllAndAllowUndefined),
- },
- _ => Err(E::UnknownArg(arg)),
+ &[DASH] => {
+ if self.read_stdin {
+ Err(())
+ } else {
+ self.read_stdin = true;
+ Ok(())
+ }
+ }
+ ALL => {
+ if self.all_lints || self.read_stdin {
+ Err(())
+ } else {
+ self.all_lints = true;
+ Ok(())
+ }
+ }
+ ALLOW_UNDEFINED_LINTS => {
+ if self.allow_undefined_lints || self.read_stdin {
+ Err(())
+ } else {
+ self.allow_undefined_lints = true;
+ Ok(())
+ }
+ }
+ _ => Err(()),
}
- .and_then(|opt2| {
- opts.next()
- .map_or(Ok(opt2), |nxt| Self::get_opts(opts, opt2, nxt))
+ .map_or(Some(arg), |()| {
+ opts.next().and_then(|opt| self.add(opts, opt))
})
}
+}
+impl Cmd {
/// Parses the CLI arguments.
- #[expect(clippy::unreachable, reason = "want to crash when there is a bug")]
pub(crate) fn try_from_args<I: IntoIterator<Item = OsString>>(iter: I) -> Result<Self, E> {
let mut args = iter.into_iter();
args.next().ok_or(E::NoArgs).and_then(|_| {
- args.next().map_or(Ok(Self::Deny(false, false)), |cmd| {
- match cmd.as_encoded_bytes() {
- b"allow" => args.next().map_or(Ok(Self::Allow(false, false)), |arg| {
- Self::get_opts(args, Opt::None, arg).map(|opt| match opt {
- Opt::None => unreachable!("bug in args::Cmd::get_ops"),
- Opt::All => Self::Allow(true, false),
- Opt::AllowUndefined => Self::Allow(false, true),
- Opt::AllAndAllowUndefined => Self::Allow(true, true),
- })
- }),
- b"deny" => args.next().map_or(Ok(Self::Deny(false, false)), |arg| {
- Self::get_opts(args, Opt::None, arg).map(|opt| match opt {
- Opt::None => unreachable!("bug in args::Cmd::get_ops"),
- Opt::All => Self::Deny(true, false),
- Opt::AllowUndefined => Self::Deny(false, true),
- Opt::AllAndAllowUndefined => Self::Deny(true, true),
- })
- }),
+ args.next().map_or_else(
+ || Ok(Self::Deny(Opts::default())),
+ |cmd| match cmd.as_encoded_bytes() {
+ b"allow" => args.next().map_or_else(
+ || Ok(Self::Allow(Opts::default())),
+ |arg| {
+ let mut opts = Opts::default();
+ opts.add(args, arg)
+ .map_or(Ok(Self::Allow(opts)), |opt| Err(E::UnknownArg(opt)))
+ },
+ ),
+ b"deny" => args.next().map_or_else(
+ || Ok(Self::Deny(Opts::default())),
+ |arg| {
+ let mut opts = Opts::default();
+ opts.add(args, arg)
+ .map_or(Ok(Self::Deny(opts)), |opt| Err(E::UnknownArg(opt)))
+ },
+ ),
b"help" => args
.next()
.map_or(Ok(Self::Help), |opt| Err(E::UnknownArg(opt))),
b"version" => args
.next()
.map_or(Ok(Self::Version), |opt| Err(E::UnknownArg(opt))),
- _ => Self::get_opts(args, Opt::None, cmd).map(|opt| match opt {
- Opt::None => unreachable!("bug in args::Cmd::get_ops"),
- Opt::All => Self::Deny(true, false),
- Opt::AllowUndefined => Self::Deny(false, true),
- Opt::AllAndAllowUndefined => Self::Deny(true, true),
- }),
- }
- })
+ _ => {
+ let mut opts = Opts::default();
+ opts.add(args, cmd)
+ .map_or(Ok(Self::Deny(opts)), |opt| Err(E::UnknownArg(opt)))
+ }
+ },
+ )
})
}
}
#[cfg(test)]
mod tests {
- use super::{Cmd, E};
+ use super::{Cmd, E, Opts};
+ #[expect(clippy::too_many_lines, reason = "a lot of permutations to test")]
#[test]
fn successes() {
- assert_eq!(Cmd::try_from_args(["".into()]), Ok(Cmd::Deny(false, false)));
+ assert_eq!(
+ Cmd::try_from_args(["".into()]),
+ Ok(Cmd::Deny(Opts {
+ all_lints: false,
+ allow_undefined_lints: false,
+ read_stdin: false,
+ }))
+ );
assert_eq!(
Cmd::try_from_args(["".into(), "--all".into()]),
- Ok(Cmd::Deny(true, false))
+ Ok(Cmd::Deny(Opts {
+ all_lints: true,
+ allow_undefined_lints: false,
+ read_stdin: false,
+ }))
);
assert_eq!(
Cmd::try_from_args(["".into(), "--allow-undefined-lints".into()]),
- Ok(Cmd::Deny(false, true))
+ Ok(Cmd::Deny(Opts {
+ all_lints: false,
+ allow_undefined_lints: true,
+ read_stdin: false,
+ }))
+ );
+ assert_eq!(
+ Cmd::try_from_args(["".into(), "-".into()]),
+ Ok(Cmd::Deny(Opts {
+ all_lints: false,
+ allow_undefined_lints: false,
+ read_stdin: true,
+ }))
);
assert_eq!(
Cmd::try_from_args(["".into(), "--all".into(), "--allow-undefined-lints".into()]),
- Ok(Cmd::Deny(true, true))
+ Ok(Cmd::Deny(Opts {
+ all_lints: true,
+ allow_undefined_lints: true,
+ read_stdin: false,
+ }))
+ );
+ assert_eq!(
+ Cmd::try_from_args(["".into(), "--all".into(), "-".into()]),
+ Ok(Cmd::Deny(Opts {
+ all_lints: true,
+ allow_undefined_lints: false,
+ read_stdin: true,
+ }))
+ );
+ assert_eq!(
+ Cmd::try_from_args(["".into(), "--allow-undefined-lints".into(), "-".into()]),
+ Ok(Cmd::Deny(Opts {
+ all_lints: false,
+ allow_undefined_lints: true,
+ read_stdin: true,
+ }))
+ );
+ assert_eq!(
+ Cmd::try_from_args([
+ "".into(),
+ "--all".into(),
+ "--allow-undefined-lints".into(),
+ "-".into()
+ ]),
+ Ok(Cmd::Deny(Opts {
+ all_lints: true,
+ allow_undefined_lints: true,
+ read_stdin: true,
+ }))
);
assert_eq!(
Cmd::try_from_args(["".into(), "allow".into()]),
- Ok(Cmd::Allow(false, false))
+ Ok(Cmd::Allow(Opts {
+ all_lints: false,
+ allow_undefined_lints: false,
+ read_stdin: false,
+ }))
);
assert_eq!(
Cmd::try_from_args(["".into(), "allow".into(), "--all".into()]),
- Ok(Cmd::Allow(true, false))
+ Ok(Cmd::Allow(Opts {
+ all_lints: true,
+ allow_undefined_lints: false,
+ read_stdin: false,
+ }))
);
assert_eq!(
Cmd::try_from_args(["".into(), "allow".into(), "--allow-undefined-lints".into()]),
- Ok(Cmd::Allow(false, true))
+ Ok(Cmd::Allow(Opts {
+ all_lints: false,
+ allow_undefined_lints: true,
+ read_stdin: false,
+ }))
+ );
+ assert_eq!(
+ Cmd::try_from_args(["".into(), "allow".into(), "-".into()]),
+ Ok(Cmd::Allow(Opts {
+ all_lints: false,
+ allow_undefined_lints: false,
+ read_stdin: true,
+ }))
);
assert_eq!(
Cmd::try_from_args([
@@ -166,19 +253,65 @@ mod tests {
"--allow-undefined-lints".into(),
"--all".into()
]),
- Ok(Cmd::Allow(true, true))
+ Ok(Cmd::Allow(Opts {
+ all_lints: true,
+ allow_undefined_lints: true,
+ read_stdin: false,
+ }))
+ );
+ assert_eq!(
+ Cmd::try_from_args(["".into(), "allow".into(), "--all".into(), "-".into(),]),
+ Ok(Cmd::Allow(Opts {
+ all_lints: true,
+ allow_undefined_lints: false,
+ read_stdin: true,
+ }))
+ );
+ assert_eq!(
+ Cmd::try_from_args([
+ "".into(),
+ "allow".into(),
+ "--all".into(),
+ "--allow-undefined-lints".into(),
+ "-".into(),
+ ]),
+ Ok(Cmd::Allow(Opts {
+ all_lints: true,
+ allow_undefined_lints: true,
+ read_stdin: true,
+ }))
);
assert_eq!(
Cmd::try_from_args(["".into(), "deny".into()]),
- Ok(Cmd::Deny(false, false))
+ Ok(Cmd::Deny(Opts {
+ all_lints: false,
+ allow_undefined_lints: false,
+ read_stdin: false,
+ }))
);
assert_eq!(
Cmd::try_from_args(["".into(), "deny".into(), "--all".into()]),
- Ok(Cmd::Deny(true, false))
+ Ok(Cmd::Deny(Opts {
+ all_lints: true,
+ allow_undefined_lints: false,
+ read_stdin: false,
+ }))
);
assert_eq!(
Cmd::try_from_args(["".into(), "deny".into(), "--allow-undefined-lints".into()]),
- Ok(Cmd::Deny(false, true))
+ Ok(Cmd::Deny(Opts {
+ all_lints: false,
+ allow_undefined_lints: true,
+ read_stdin: false,
+ }))
+ );
+ assert_eq!(
+ Cmd::try_from_args(["".into(), "deny".into(), "-".into()]),
+ Ok(Cmd::Deny(Opts {
+ all_lints: false,
+ allow_undefined_lints: false,
+ read_stdin: true,
+ }))
);
assert_eq!(
Cmd::try_from_args([
@@ -187,7 +320,38 @@ mod tests {
"--all".into(),
"--allow-undefined-lints".into()
]),
- Ok(Cmd::Deny(true, true))
+ Ok(Cmd::Deny(Opts {
+ all_lints: true,
+ allow_undefined_lints: true,
+ read_stdin: false,
+ }))
+ );
+ assert_eq!(
+ Cmd::try_from_args([
+ "".into(),
+ "deny".into(),
+ "--allow-undefined-lints".into(),
+ "-".into(),
+ ]),
+ Ok(Cmd::Deny(Opts {
+ all_lints: false,
+ allow_undefined_lints: true,
+ read_stdin: true,
+ }))
+ );
+ assert_eq!(
+ Cmd::try_from_args([
+ "".into(),
+ "deny".into(),
+ "--allow-undefined-lints".into(),
+ "--all".into(),
+ "-".into(),
+ ]),
+ Ok(Cmd::Deny(Opts {
+ all_lints: true,
+ allow_undefined_lints: true,
+ read_stdin: true,
+ }))
);
assert_eq!(
Cmd::try_from_args(["".into(), "help".into()]),
@@ -256,5 +420,22 @@ mod tests {
]),
Err(E::UnknownArg("foo".into()))
);
+ assert_eq!(
+ Cmd::try_from_args(["".into(), "-".into(), "deny".into(),]),
+ Err(E::UnknownArg("deny".into()))
+ );
+ assert_eq!(
+ Cmd::try_from_args(["".into(), "-".into(), "--all".into(),]),
+ Err(E::UnknownArg("--all".into()))
+ );
+ assert_eq!(
+ Cmd::try_from_args([
+ "".into(),
+ "allow".into(),
+ "-".into(),
+ "--allow-undefined-lints".into()
+ ]),
+ Err(E::UnknownArg("--allow-undefined-lints".into()))
+ );
}
}
diff --git a/src/cmd.rs b/src/cmd.rs
@@ -1,240 +0,0 @@
-extern crate alloc;
-use super::{
- Error, ExitCode,
- io::{self, Write as _},
-};
-#[cfg(target_os = "openbsd")]
-use super::{Permissions, Promises, env};
-use alloc::string::FromUtf8Error;
-#[cfg(target_os = "openbsd")]
-use std::{ffi::OsStr, fs, io::ErrorKind};
-use std::{
- path::{Path, PathBuf},
- process::{Command, Stdio},
-};
-/// Error when executing `rustc -Whelp`.
-pub(crate) enum E {
- /// I/O error.
- Io(Error),
- /// Error when there is no `$PATH` variable.
- #[cfg(target_os = "openbsd")]
- NoPathVariable,
- /// Error when `"rustc"` is unable to be located in `$PATH`.
- #[cfg(target_os = "openbsd")]
- NoRustcInPath,
- /// `rustc -Whelp` didn't return a status code, and nothing was written to `stderr`.
- RustcNoStatusNoErr,
- /// `rustc -Whelp` didn't return a status code, and invalid UTF-8 was written to `stderr`.
- RustcNoStatusInvalidUtf8(FromUtf8Error),
- /// `rustc -Whelp` didn't return a status code, and the contained `String` was written to `stderr`.
- RustcNoStatusErr(String),
- /// `rustc -Whelp` returned an error code, but nothing was written to `stderr`.
- RustcErrStatusNoErr(i32),
- /// `rustc -Whelp` returned an error code, but `stderr` contained invalid UTF-8.
- RustcErrStatusInvalidUtf8(i32, FromUtf8Error),
- /// `rustc -Whelp` returned an error code, and the contained `String` was written to `stderr`.
- RustcErrStatus(i32, String),
- /// `rustc -Whelp` returned a success code, but `stderr` contained invalid UTF-8.
- RustcSuccessErrInvalidUtf8(FromUtf8Error),
- /// `rustc -Whelp` returned a success code, but the contained `String` was written to `stderr`.
- RustcSuccessErr(String),
-}
-impl E {
- /// Writes `self` into `stderr`.
- pub(crate) fn into_exit_code(self) -> ExitCode {
- let mut stderr = io::stderr().lock();
- match self {
- Self::Io(err) => writeln!(stderr, "I/O issue: {err}."),
- #[cfg(target_os = "openbsd")]
- Self::NoPathVariable => writeln!(
- stderr,
- "No PATH variable."
- ),
- #[cfg(target_os = "openbsd")]
- Self::NoRustcInPath => writeln!(
- stderr,
- "rustc could not be found based on the PATH variable."
- ),
- Self::RustcNoStatusNoErr => writeln!(
- stderr,
- "rustc -Whelp didn't return a status code but didn't write anything to stderr."
- ),
- Self::RustcNoStatusInvalidUtf8(err) => writeln!(
- stderr,
- "rustc -Whelp didn't return a status code, but stderr contained invalid UTF-8: {err}."
- ),
- Self::RustcNoStatusErr(err) => writeln!(
- stderr,
- "rustc -Whelp didn't return a status code, and the following was written to stderr: {err}."
- ),
- Self::RustcErrStatusNoErr(code) => writeln!(
- stderr,
- "rustc -Whelp returned status {code}, but didn't write anything to stderr."
- ),
- Self::RustcErrStatusInvalidUtf8(code, err) => writeln!(
- stderr,
- "rustc -Whelp returned status {code}, but stderr contained invalid UTF-8: {err}."
- ),
- Self::RustcErrStatus(code, err) => writeln!(
- stderr,
- "rustc -Whelp returned status {code}, and the following was written to stderr: {err}."
- ),
- Self::RustcSuccessErrInvalidUtf8(err) => writeln!(
- stderr,
- "rustc -Whelp returned a successful status code, but stderr contained invalid UTF-8: {err}."
- ),
- Self::RustcSuccessErr(err) => writeln!(
- stderr,
- "rustc -Whelp returned a successful status code, but the following was written to stderr: {err}."
- ),
- }.map_or(ExitCode::FAILURE, |()| ExitCode::FAILURE)
- }
-}
-/// `"rustc"`.
-const RUSTC: &str = "rustc";
-/// Returns [`RUSTC`] as a `PathBuf`.
-#[expect(clippy::unnecessary_wraps, reason = "unify with OpenBSD")]
-#[cfg(not(target_os = "openbsd"))]
-fn priv_sep<Never>() -> Result<PathBuf, Never> {
- Ok(RUSTC.into())
-}
-/// `unveil(2)`s the file system for read-only permissions.
-/// Traverses `$PATH` to find `"rustc"`; when found, removes read permissions on the file system before
-/// `unveil(2)`ing `"rustc"` with execute permissions. Last, `pledge(2)`s `c"exec proc stdio unveil"`.
-#[expect(unsafe_code, reason = "comment justifies correctness")]
-#[expect(clippy::option_if_let_else, reason = "false positive")]
-#[cfg(target_os = "openbsd")]
-fn priv_sep() -> Result<PathBuf, E> {
- Permissions::unveil_raw(c"/", c"r")
- .map_err(|e| E::Io(e.into()))
- .and_then(|()| {
- env::var_os("PATH").map_or(Err(E::NoPathVariable), |path| {
- path.as_encoded_bytes()
- .split(|b| *b == b':')
- .try_fold((), |(), dir| {
- // SAFETY:
- // `dir` is obtained directly from `path.as_encoded_bytes` with at most a single
- // `b':'` removed ensuring any valid UTF-8 that existed before still does.
- let dir_os = unsafe { OsStr::from_encoded_bytes_unchecked(dir) };
- fs::read_dir(dir_os).map_or_else(
- |e| {
- if matches!(e.kind(), ErrorKind::NotADirectory) {
- let val = PathBuf::from(dir_os);
- match val.file_name() {
- None => Ok(()),
- Some(file) => {
- if file == RUSTC {
- Err(val)
- } else {
- Ok(())
- }
- }
- }
- } else {
- Ok(())
- }
- },
- |mut ents| {
- ents.try_fold((), |(), ent_res| {
- ent_res.map_or(Ok(()), |ent| {
- if ent.file_name() == RUSTC {
- Err(PathBuf::from(dir_os).join(RUSTC))
- } else {
- Ok(())
- }
- })
- })
- },
- )
- })
- .map_or_else(
- |rustc| {
- Permissions::unveil_raw(c"/", c"")
- .and_then(|()| {
- Permissions::unveil_raw(&rustc, c"x").and_then(|()| {
- Promises::pledge_raw(c"exec proc stdio unveil")
- .map(|()| rustc)
- })
- })
- .map_err(|e| E::Io(e.into()))
- },
- |()| Err(E::NoRustcInPath),
- )
- })
- })
-}
-/// No-op.
-#[expect(clippy::unnecessary_wraps, reason = "unify with OpenBSD")]
-#[cfg(not(target_os = "openbsd"))]
-const fn priv_sep_final<Never>(_: &Path) -> Result<(), Never> {
- Ok(())
-}
-/// Removes execute permissions on `path` before `pledge(2)`ing `c"stdio"`.
-#[cfg(target_os = "openbsd")]
-fn priv_sep_final(path: &Path) -> Result<(), E> {
- Permissions::unveil_raw(path, c"")
- .and_then(|()| Promises::pledge_raw(c"stdio"))
- .map_err(|e| E::Io(e.into()))
-}
-/// Gets output of `rustc -Whelp`.
-pub(crate) fn execute_rustc() -> Result<Vec<u8>, E> {
- priv_sep().and_then(|path| {
- Command::new(&path)
- .arg("-Whelp")
- .stderr(Stdio::piped())
- .stdin(Stdio::null())
- .stdout(Stdio::piped())
- .output()
- .map_err(E::Io)
- .and_then(|output| {
- priv_sep_final(&path).and_then(|()| match output.status.code() {
- None => {
- if output.stderr.is_empty() {
- Err(E::RustcNoStatusNoErr)
- } else {
- String::from_utf8(output.stderr)
- .map_err(E::RustcNoStatusInvalidUtf8)
- .and_then(|err| Err(E::RustcNoStatusErr(err)))
- }
- }
- Some(code) => {
- if code == 0i32 {
- if output.stderr.is_empty() {
- Ok(output.stdout)
- } else {
- String::from_utf8(output.stderr)
- .map_err(E::RustcSuccessErrInvalidUtf8)
- .and_then(|err| Err(E::RustcSuccessErr(err)))
- }
- } else if output.stderr.is_empty() {
- Err(E::RustcErrStatusNoErr(code))
- } else {
- String::from_utf8(output.stderr)
- .map_err(|err| E::RustcErrStatusInvalidUtf8(code, err))
- .and_then(|err| Err(E::RustcErrStatus(code, err)))
- }
- }
- })
- })
- })
-}
-#[cfg(test)]
-mod tests {
- #[cfg(not(target_os = "openbsd"))]
- use core::convert::Infallible;
- #[cfg(target_os = "openbsd")]
- use std::ffi::OsString;
- #[test]
- #[cfg(not(target_os = "openbsd"))]
- fn priv_sep() {
- assert_eq!(super::priv_sep::<Infallible>(), Ok("rustc".into()));
- }
- #[ignore = "interferes with testing. should run separately to avoid issues."]
- #[test]
- #[cfg(target_os = "openbsd")]
- fn priv_sep() {
- assert!(super::priv_sep().is_ok_and(
- |path| path.is_absolute() && path.file_name() == Some(&OsString::from("rustc"))
- ));
- }
-}
diff --git a/src/main.rs b/src/main.rs
@@ -1,16 +1,16 @@
//! Consult [`README.md`](https://crates.io/crates/lints/).
/// CLI-argument parsing.
mod args;
-/// `rustc` execution.
-mod cmd;
/// Parsing of `rustc -Whelp` output.
mod parse;
+/// `rustc` execution.
+mod rustc;
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 rustc::E as RustcErr;
use std::{
collections::HashSet,
env,
@@ -40,6 +40,7 @@ Commands:
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
+ - If the last argument, stdin is read instead of getting the output from rustc
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.
@@ -73,14 +74,14 @@ fn as_str(ascii: &[u8]) -> &str {
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)
+ Cmd::Allow(opts) | Cmd::Deny(opts) => {
+ rustc::execute(opts.read_stdin).map_or_else(RustcErr::into_exit_code, |output| {
+ Data::new(&output, opts.allow_undefined_lints)
.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(_, _)) {
+ if opts.all_lints {
+ let level = if matches!(cmd, Cmd::Allow(_)) {
"allow"
} else {
"deny"
@@ -121,7 +122,7 @@ fn main() -> ExitCode {
)
})
})
- } else if matches!(cmd, Cmd::Allow(_, _)) {
+ } else if matches!(cmd, Cmd::Allow(_)) {
writeln!(
stdout,
"{WARNINGS} = {{ level = \"allow\", priority = -1 }}"
diff --git a/src/parse.rs b/src/parse.rs
@@ -600,7 +600,7 @@ impl<'a> Data<'a> {
}
}
}
-#[cfg(test)]
+#[cfg(all(test, not(target_pointer_width = "16")))]
mod tests {
use super::{Data, E, io::Read as _};
use std::fs::{self, File};
@@ -609,8 +609,10 @@ mod tests {
reason = "want to pretty-print problematic file"
)]
#[expect(clippy::verbose_file_reads, reason = "want to lock file")]
+ #[expect(clippy::tests_outside_test_module, reason = "false positive")]
#[test]
fn outputs() {
+ let mut output = Vec::with_capacity(u16::MAX.into());
assert!(
fs::read_dir("./outputs/").is_ok_and(|mut dir| {
dir.try_fold((), |(), ent_res| {
@@ -620,58 +622,54 @@ mod tests {
.open(ent.path())
.is_ok_and(|mut file| {
file.lock_shared().is_ok_and(|()| {
- file.metadata().is_ok_and(|meta| {
- let mut output = Vec::with_capacity(
- meta.len().try_into().unwrap_or(usize::MAX),
- );
- file.read_to_end(&mut output).is_ok_and(|_| {
- // Release lock.
- drop(file);
- let file_name = ent.file_name();
- let file_name_bytes = file_name.as_encoded_bytes();
- Data::new(&output, false).map_or_else(
- |e| match file_name_bytes {
- b"1.34.0.txt" | b"1.34.1.txt" | b"1.34.2.txt" => {
- assert_eq!(
- e,
- E::LintGroupContainsUnknownLint(
- b"future-incompatible",
- b"duplicate-matcher-binding-name"
- ),
- "1.34.0.txt, 1.34.1.txt, and 1.34.2.txt can't be parsed for a reason other than the expected reason"
- );
- Data::new(&output, true).is_ok()
- }
- b"1.48.0.txt" => {
- assert_eq!(
- e,
- E::LintGroupContainsUnknownLint(
- b"rustdoc",
- b"private-intra-doc-links"
- ),
- "1.48.0.txt can't be parsed for a reason other than the expected reason"
- );
- Data::new(&output, true).is_ok()
- }
- _ => {
- assert!(
- false,
- "{} cannot be parsed due to {e:?}.",
- String::from_utf8_lossy(file_name_bytes),
- );
- false
- }
- },
- |_| {
- if matches!(file_name_bytes, b"1.34.0.txt" | b"1.34.1.txt" | b"1.34.2.txt" | b"1.48.0.txt") {
- assert!(false, "{} shouldn't be parsable", String::from_utf8_lossy(file_name_bytes));
- false
- } else {
- true
- }
- },
- )
- })
+ output.clear();
+ file.read_to_end(&mut output).is_ok_and(|_| {
+ // Release lock.
+ drop(file);
+ let file_name = ent.file_name();
+ let file_name_bytes = file_name.as_encoded_bytes();
+ Data::new(&output, false).map_or_else(
+ |e| match file_name_bytes {
+ b"1.34.0.txt" | b"1.34.1.txt" | b"1.34.2.txt" => {
+ assert_eq!(
+ e,
+ E::LintGroupContainsUnknownLint(
+ b"future-incompatible",
+ b"duplicate-matcher-binding-name"
+ ),
+ "1.34.0.txt, 1.34.1.txt, and 1.34.2.txt can't be parsed for a reason other than the expected reason"
+ );
+ Data::new(&output, true).is_ok()
+ }
+ b"1.48.0.txt" => {
+ assert_eq!(
+ e,
+ E::LintGroupContainsUnknownLint(
+ b"rustdoc",
+ b"private-intra-doc-links"
+ ),
+ "1.48.0.txt can't be parsed for a reason other than the expected reason"
+ );
+ Data::new(&output, true).is_ok()
+ }
+ _ => {
+ assert!(
+ false,
+ "{} cannot be parsed due to {e:?}.",
+ String::from_utf8_lossy(file_name_bytes),
+ );
+ false
+ }
+ },
+ |_| {
+ if matches!(file_name_bytes, b"1.34.0.txt" | b"1.34.1.txt" | b"1.34.2.txt" | b"1.48.0.txt") {
+ assert!(false, "{} shouldn't be parsable", String::from_utf8_lossy(file_name_bytes));
+ false
+ } else {
+ true
+ }
+ },
+ )
})
})
})
diff --git a/src/rustc.rs b/src/rustc.rs
@@ -0,0 +1,263 @@
+extern crate alloc;
+use super::{
+ Error, ExitCode,
+ io::{self, Write as _},
+};
+#[cfg(target_os = "openbsd")]
+use super::{Permissions, Promises, env};
+use alloc::string::FromUtf8Error;
+#[cfg(target_os = "openbsd")]
+use std::{ffi::OsStr, fs, io::ErrorKind};
+use std::{
+ io::Read as _,
+ path::{Path, PathBuf},
+ process::{Command, Stdio},
+};
+/// Error when executing `rustc -Whelp`.
+pub(crate) enum E {
+ /// I/O error.
+ Io(Error),
+ /// Error when there is no `$PATH` variable.
+ #[cfg(target_os = "openbsd")]
+ NoPathVariable,
+ /// Error when `"rustc"` is unable to be located in `$PATH`.
+ #[cfg(target_os = "openbsd")]
+ NoRustcInPath,
+ /// `rustc -Whelp` didn't return a status code, and nothing was written to `stderr`.
+ NoStatusNoErr,
+ /// `rustc -Whelp` didn't return a status code, and invalid UTF-8 was written to `stderr`.
+ NoStatusInvalidUtf8(FromUtf8Error),
+ /// `rustc -Whelp` didn't return a status code, and the contained `String` was written to `stderr`.
+ NoStatusErr(String),
+ /// `rustc -Whelp` returned an error code, but nothing was written to `stderr`.
+ ErrStatusNoErr(i32),
+ /// `rustc -Whelp` returned an error code, but `stderr` contained invalid UTF-8.
+ ErrStatusInvalidUtf8(i32, FromUtf8Error),
+ /// `rustc -Whelp` returned an error code, and the contained `String` was written to `stderr`.
+ ErrStatus(i32, String),
+ /// `rustc -Whelp` returned a success code, but `stderr` contained invalid UTF-8.
+ SuccessErrInvalidUtf8(FromUtf8Error),
+ /// `rustc -Whelp` returned a success code, but the contained `String` was written to `stderr`.
+ SuccessErr(String),
+}
+impl E {
+ /// Writes `self` into `stderr`.
+ pub(crate) fn into_exit_code(self) -> ExitCode {
+ let mut stderr = io::stderr().lock();
+ match self {
+ Self::Io(err) => writeln!(stderr, "I/O issue: {err}."),
+ #[cfg(target_os = "openbsd")]
+ Self::NoPathVariable => writeln!(
+ stderr,
+ "No PATH variable."
+ ),
+ #[cfg(target_os = "openbsd")]
+ Self::NoRustcInPath => writeln!(
+ stderr,
+ "rustc could not be found based on the PATH variable."
+ ),
+ Self::NoStatusNoErr => writeln!(
+ stderr,
+ "rustc -Whelp didn't return a status code but didn't write anything to stderr."
+ ),
+ Self::NoStatusInvalidUtf8(err) => writeln!(
+ stderr,
+ "rustc -Whelp didn't return a status code, but stderr contained invalid UTF-8: {err}."
+ ),
+ Self::NoStatusErr(err) => writeln!(
+ stderr,
+ "rustc -Whelp didn't return a status code, and the following was written to stderr: {err}."
+ ),
+ Self::ErrStatusNoErr(code) => writeln!(
+ stderr,
+ "rustc -Whelp returned status {code}, but didn't write anything to stderr."
+ ),
+ Self::ErrStatusInvalidUtf8(code, err) => writeln!(
+ stderr,
+ "rustc -Whelp returned status {code}, but stderr contained invalid UTF-8: {err}."
+ ),
+ Self::ErrStatus(code, err) => writeln!(
+ stderr,
+ "rustc -Whelp returned status {code}, and the following was written to stderr: {err}."
+ ),
+ Self::SuccessErrInvalidUtf8(err) => writeln!(
+ stderr,
+ "rustc -Whelp returned a successful status code, but stderr contained invalid UTF-8: {err}."
+ ),
+ Self::SuccessErr(err) => writeln!(
+ stderr,
+ "rustc -Whelp returned a successful status code, but the following was written to stderr: {err}."
+ ),
+ }.map_or(ExitCode::FAILURE, |()| ExitCode::FAILURE)
+ }
+}
+/// `"rustc"`.
+const RUSTC: &str = "rustc";
+/// Returns [`RUSTC`] as a `PathBuf`.
+#[expect(clippy::unnecessary_wraps, reason = "unify with OpenBSD")]
+#[cfg(not(target_os = "openbsd"))]
+fn priv_sep<Never>() -> Result<PathBuf, Never> {
+ Ok(RUSTC.into())
+}
+/// `unveil(2)`s the file system for read-only permissions.
+/// Traverses `$PATH` to find `"rustc"`; when found, removes read permissions on the file system before
+/// `unveil(2)`ing `"rustc"` with execute permissions. Last, `pledge(2)`s `c"exec proc stdio unveil"`.
+#[expect(unsafe_code, reason = "comment justifies correctness")]
+#[expect(clippy::option_if_let_else, reason = "false positive")]
+#[cfg(target_os = "openbsd")]
+fn priv_sep() -> Result<PathBuf, E> {
+ Permissions::unveil_raw(c"/", c"r")
+ .map_err(|e| E::Io(e.into()))
+ .and_then(|()| {
+ env::var_os("PATH").map_or(Err(E::NoPathVariable), |path| {
+ path.as_encoded_bytes()
+ .split(|b| *b == b':')
+ .try_fold((), |(), dir| {
+ // SAFETY:
+ // `dir` is obtained directly from `path.as_encoded_bytes` with at most a single
+ // `b':'` removed ensuring any valid UTF-8 that existed before still does.
+ let dir_os = unsafe { OsStr::from_encoded_bytes_unchecked(dir) };
+ fs::read_dir(dir_os).map_or_else(
+ |e| {
+ if matches!(e.kind(), ErrorKind::NotADirectory) {
+ let val = PathBuf::from(dir_os);
+ match val.file_name() {
+ None => Ok(()),
+ Some(file) => {
+ if file == RUSTC {
+ Err(val)
+ } else {
+ Ok(())
+ }
+ }
+ }
+ } else {
+ Ok(())
+ }
+ },
+ |mut ents| {
+ ents.try_fold((), |(), ent_res| {
+ ent_res.map_or(Ok(()), |ent| {
+ if ent.file_name() == RUSTC {
+ Err(PathBuf::from(dir_os).join(RUSTC))
+ } else {
+ Ok(())
+ }
+ })
+ })
+ },
+ )
+ })
+ .map_or_else(
+ |rustc| {
+ Permissions::unveil_raw(c"/", c"")
+ .and_then(|()| {
+ Permissions::unveil_raw(&rustc, c"x").and_then(|()| {
+ Promises::pledge_raw(c"exec proc stdio unveil")
+ .map(|()| rustc)
+ })
+ })
+ .map_err(|e| E::Io(e.into()))
+ },
+ |()| Err(E::NoRustcInPath),
+ )
+ })
+ })
+}
+/// No-op.
+#[expect(clippy::unnecessary_wraps, reason = "unify with OpenBSD")]
+#[cfg(not(target_os = "openbsd"))]
+const fn priv_sep_final<Never>(_: &Path) -> Result<(), Never> {
+ Ok(())
+}
+/// Removes execute permissions on `path` before `pledge(2)`ing `c"stdio"`.
+#[cfg(target_os = "openbsd")]
+fn priv_sep_final(path: &Path) -> Result<(), E> {
+ Permissions::unveil_raw(path, c"")
+ .and_then(|()| Promises::pledge_raw(c"stdio"))
+ .map_err(|e| E::Io(e.into()))
+}
+/// No-op.
+#[expect(clippy::unnecessary_wraps, reason = "unify with OpenBSD")]
+#[cfg(not(target_os = "openbsd"))]
+const fn priv_sep_stdin<Never>() -> Result<(), Never> {
+ Ok(())
+}
+/// `pledge(2)`s `c"stdio"`.
+#[cfg(target_os = "openbsd")]
+fn priv_sep_stdin() -> Result<(), E> {
+ Promises::pledge_raw(c"stdio").map_err(|e| E::Io(e.into()))
+}
+/// Gets output of `rustc -Whelp`.
+pub(crate) fn execute(read_stdin: bool) -> Result<Vec<u8>, E> {
+ if read_stdin {
+ priv_sep_stdin().and_then(|()| {
+ let mut output = Vec::with_capacity(0x8000);
+ io::stdin()
+ .lock()
+ .read_to_end(&mut output)
+ .map_err(E::Io)
+ .map(|_| output)
+ })
+ } else {
+ priv_sep().and_then(|path| {
+ Command::new(&path)
+ .arg("-Whelp")
+ .stderr(Stdio::piped())
+ .stdin(Stdio::null())
+ .stdout(Stdio::piped())
+ .output()
+ .map_err(E::Io)
+ .and_then(|output| {
+ priv_sep_final(&path).and_then(|()| match output.status.code() {
+ None => {
+ if output.stderr.is_empty() {
+ Err(E::NoStatusNoErr)
+ } else {
+ String::from_utf8(output.stderr)
+ .map_err(E::NoStatusInvalidUtf8)
+ .and_then(|err| Err(E::NoStatusErr(err)))
+ }
+ }
+ Some(code) => {
+ if code == 0i32 {
+ if output.stderr.is_empty() {
+ Ok(output.stdout)
+ } else {
+ String::from_utf8(output.stderr)
+ .map_err(E::SuccessErrInvalidUtf8)
+ .and_then(|err| Err(E::SuccessErr(err)))
+ }
+ } else if output.stderr.is_empty() {
+ Err(E::ErrStatusNoErr(code))
+ } else {
+ String::from_utf8(output.stderr)
+ .map_err(|err| E::ErrStatusInvalidUtf8(code, err))
+ .and_then(|err| Err(E::ErrStatus(code, err)))
+ }
+ }
+ })
+ })
+ })
+ }
+}
+#[cfg(test)]
+mod tests {
+ #[cfg(not(target_os = "openbsd"))]
+ use core::convert::Infallible;
+ #[cfg(target_os = "openbsd")]
+ use std::ffi::OsString;
+ #[test]
+ #[cfg(not(target_os = "openbsd"))]
+ fn priv_sep() {
+ assert_eq!(super::priv_sep::<Infallible>(), Ok("rustc".into()));
+ }
+ #[ignore = "interferes with testing. should run separately to avoid issues."]
+ #[test]
+ #[cfg(target_os = "openbsd")]
+ fn priv_sep() {
+ assert!(super::priv_sep().is_ok_and(
+ |path| path.is_absolute() && path.file_name() == Some(&OsString::from("rustc"))
+ ));
+ }
+}
diff --git a/tests/parse_1_93_0.rs b/tests/parse_1_93_0.rs
@@ -0,0 +1,348 @@
+//! Verifies the app correctly parses `rustc -Whelp` output piped to it.
+#![cfg(all(test, not(target_pointer_width = "16")))]
+use std::{
+ env,
+ io::Write as _,
+ process::{Command, Stdio},
+ thread,
+};
+#[expect(clippy::too_many_lines, reason = "rustc -Whelp output is large")]
+#[expect(clippy::tests_outside_test_module, reason = "false positive")]
+#[test]
+fn validate() {
+ const RUSTC_1_93_0: &[u8; 33_025] = b"
+Available lint options:
+ -W <foo> Warn about <foo>
+ -A <foo> Allow <foo>
+ -D <foo> Deny <foo>
+ -F <foo> Forbid <foo> (deny <foo> and all attempts to override)
+
+
+Lint checks provided by rustc:
+
+ name default meaning
+ ---- ------- -------
+ absolute-paths-not-starting-with-crate allow fully qualified paths that start with a module name instead of `crate`, `self`, or an extern crate name
+ ambiguous-negative-literals allow ambiguous negative literals operations
+ closure-returning-async-block allow closure that returns `async {}` could be rewritten as an async closure
+ deprecated-in-future allow detects use of items that will be deprecated in a future version
+ deprecated-safe-2024 allow detects unsafe functions being used as safe functions
+ deref-into-dyn-supertrait allow `Deref` implementation with a supertrait trait object for output is shadowed by trait upcasting
+ edition-2024-expr-fragment-specifier allow The `expr` fragment specifier will accept more expressions in the 2024 edition. To keep the existing behavior, use the `expr_2021` fragment specifier.
+ elided-lifetimes-in-paths allow hidden lifetime parameters in types are deprecated
+ explicit-outlives-requirements allow outlives requirements can be inferred
+ ffi-unwind-calls allow call to foreign functions or function pointers with FFI-unwind ABI
+ fuzzy-provenance-casts allow a fuzzy integer to pointer cast is used
+ if-let-rescope allow `if let` assigns a shorter lifetime to temporary values being pattern-matched against in Edition 2024 and rewriting in `match` is an option to preserve the semantics up to Edition 2021
+ impl-trait-overcaptures allow `impl Trait` will capture more lifetimes than possibly intended in edition 2024
+ impl-trait-redundant-captures allow redundant precise-capturing `use<...>` syntax on an `impl Trait`
+ keyword-idents-2018 allow detects edition keywords being used as an identifier
+ keyword-idents-2024 allow detects edition keywords being used as an identifier
+ let-underscore-drop allow non-binding let on a type that has a destructor
+ linker-messages allow warnings emitted at runtime by the target-specific linker program
+ lossy-provenance-casts allow a lossy pointer to integer cast is used
+ macro-use-extern-crate allow the `#[macro_use]` attribute is now deprecated in favor of using macros via the module system
+ meta-variable-misuse allow possible meta-variable misuse at macro definition
+ missing-copy-implementations allow detects potentially-forgotten implementations of `Copy`
+ missing-debug-implementations allow detects missing implementations of Debug
+ missing-docs allow detects missing documentation for public members
+ missing-unsafe-on-extern allow detects missing unsafe keyword on extern declarations
+ multiple-supertrait-upcastable allow detect when a dyn-compatible trait has multiple supertraits
+ must-not-suspend allow use of a `#[must_not_suspend]` value across a yield point
+ non-ascii-idents allow detects non-ASCII identifiers
+ non-exhaustive-omitted-patterns allow detect when patterns of types marked `non_exhaustive` are missed
+ redundant-imports allow imports that are redundant due to being imported already
+ redundant-lifetimes allow detects lifetime parameters that are redundant because they are equal to some other named lifetime
+ resolving-to-items-shadowing-supertrait-items allow detects when a supertrait item is shadowed by a subtrait item
+ rust-2021-incompatible-closure-captures allow detects closures affected by Rust 2021 changes
+ rust-2021-incompatible-or-patterns allow detects usage of old versions of or-patterns
+ rust-2021-prefixes-incompatible-syntax allow identifiers that will be parsed as a prefix in Rust 2021
+ rust-2021-prelude-collisions allow detects the usage of trait methods which are ambiguous with traits added to the prelude in future editions
+ rust-2024-guarded-string-incompatible-syntax allow will be parsed as a guarded string in Rust 2024
+ rust-2024-incompatible-pat allow detects patterns whose meaning will change in Rust 2024
+ rust-2024-prelude-collisions allow detects the usage of trait methods which are ambiguous with traits added to the prelude in future editions
+ shadowing-supertrait-items allow detects when a supertrait item is shadowed by a subtrait item
+ single-use-lifetimes allow detects lifetime parameters that are only used once
+ tail-expr-drop-order allow Detect and warn on significant change in drop order in tail expression location
+ trivial-casts allow detects trivial casts which could be removed
+ trivial-numeric-casts allow detects trivial casts of numeric types which could be removed
+ unit-bindings allow binding is useless because it has the unit `()` type
+ unnameable-types allow effective visibility of a type is larger than the area in which it can be named
+ unqualified-local-imports allow `use` of a local item without leading `self::`, `super::`, or `crate::`
+ unreachable-pub allow `pub` items not reachable from crate root
+ unsafe-attr-outside-unsafe allow detects unsafe attributes outside of unsafe
+ unsafe-code allow usage of `unsafe` code and other potentially unsound constructs
+ unsafe-op-in-unsafe-fn allow unsafe operations in unsafe functions without an explicit unsafe block are deprecated
+ unstable-features allow enabling unstable features
+ unused-crate-dependencies allow crate dependencies that are never used
+ unused-extern-crates allow extern crates that are never used
+ unused-import-braces allow unnecessary braces around an imported item
+ unused-lifetimes allow detects lifetime parameters that are never used
+ unused-macro-rules allow detects macro rules that were not used
+ unused-qualifications allow detects unnecessarily qualified names
+ unused-results allow unused result of an expression in a statement
+ variant-size-differences allow detects enums with widely varying variant sizes
+ aarch64-softfloat-neon warn detects code that could be affected by ABI issues on aarch64 softfloat targets
+ ambiguous-glob-reexports warn ambiguous glob re-exports
+ ambiguous-wide-pointer-comparisons warn detects ambiguous wide pointer comparisons
+ anonymous-parameters warn detects anonymous parameters
+ array-into-iter warn detects calling `into_iter` on arrays in Rust 2015 and 2018
+ asm-sub-register warn using only a subset of a register for inline asm inputs
+ async-fn-in-trait warn use of `async fn` in definition of a publicly-reachable trait
+ bad-asm-style warn incorrect use of inline assembly
+ bare-trait-objects warn suggest using `dyn Trait` for trait objects
+ boxed-slice-into-iter warn detects calling `into_iter` on boxed slices in Rust 2015, 2018, and 2021
+ break-with-label-and-loop warn `break` expression with label and unlabeled loop as value expression
+ clashing-extern-declarations warn detects when an extern fn has been declared with the same name but different types
+ coherence-leak-check warn distinct impls distinguished only by the leak-check code
+ confusable-idents warn detects visually confusable pairs between identifiers
+ const-evaluatable-unchecked warn detects a generic constant is used in a type without a emitting a warning
+ const-item-interior-mutations warn checks for calls which mutates a interior mutable const-item
+ const-item-mutation warn detects attempts to mutate a `const` item
+ dangling-pointers-from-locals warn detects returning a pointer from a local variable
+ dangling-pointers-from-temporaries warn detects getting a pointer from a temporary
+ dead-code warn detect unused, unexported items
+ deprecated warn detects use of deprecated items
+ deprecated-where-clause-location warn deprecated where clause location
+ double-negations warn detects expressions of the form `--x`
+ dropping-copy-types warn calls to `std::mem::drop` with a value that implements Copy
+ dropping-references warn calls to `std::mem::drop` with a reference instead of an owned value
+ drop-bounds warn bounds of the form `T: Drop` are most likely incorrect
+ duplicate-macro-attributes warn duplicated attribute
+ dyn-drop warn trait objects of the form `dyn Drop` are useless
+ ellipsis-inclusive-range-patterns warn `...` range patterns are deprecated
+ exported-private-dependencies warn public interface leaks type from a private dependency
+ forbidden-lint-groups warn applying forbid to lint-groups
+ forgetting-copy-types warn calls to `std::mem::forget` with a value that implements Copy
+ forgetting-references warn calls to `std::mem::forget` with a reference instead of an owned value
+ for-loops-over-fallibles warn for-looping over an `Option` or a `Result`, which is more clearly expressed as an `if let`
+ function-casts-as-integer warn casting a function into an integer
+ function-item-references warn suggest casting to a function pointer when attempting to take references to function items
+ hidden-glob-reexports warn name introduced by a private item shadows a name introduced by a public glob re-export
+ improper-ctypes warn proper use of libc types in foreign modules
+ improper-ctypes-definitions warn proper use of libc types in foreign item definitions
+ incomplete-features warn incomplete features that may function improperly in some or all cases
+ inline-no-sanitize warn detects incompatible use of `#[inline(always)]` and `#[sanitize(... = \"off\")]`
+ integer-to-ptr-transmutes warn detects integer to pointer transmutes
+ internal-features warn internal features are not supposed to be used
+ invalid-from-utf8 warn using a non UTF-8 literal in `std::str::from_utf8`
+ invalid-nan-comparisons warn detects invalid floating point NaN comparisons
+ invalid-value warn an invalid value is being created (such as a null reference)
+ irrefutable-let-patterns warn detects irrefutable patterns in `if let` and `while let` statements
+ large-assignments warn detects large moves or copies
+ late-bound-lifetime-arguments warn detects generic lifetime arguments in path segments with late bound lifetime parameters
+ malformed-diagnostic-attributes warn detects malformed diagnostic attributes
+ malformed-diagnostic-format-literals warn detects diagnostic attribute with malformed diagnostic format literals
+ map-unit-fn warn `Iterator::map` call that discard the iterator's values
+ mismatched-lifetime-syntaxes warn detects when a lifetime uses different syntax between arguments and return values
+ misplaced-diagnostic-attributes warn detects diagnostic attributes that are placed on the wrong item
+ missing-abi warn No declared ABI for extern declaration
+ mixed-script-confusables warn detects Unicode scripts whose mixed script confusables codepoints are solely used
+ named-arguments-used-positionally warn named arguments in format used positionally
+ non-camel-case-types warn types, variants, traits and type parameters should have camel case names
+ non-contiguous-range-endpoints warn detects off-by-one errors with exclusive range patterns
+ non-fmt-panics warn detect single-argument panic!() invocations in which the argument is not a format string
+ non-local-definitions warn checks for non-local definitions
+ non-shorthand-field-patterns warn using `Struct { x: x }` instead of `Struct { x }` in a pattern
+ non-snake-case warn variables, methods, functions, lifetime parameters and modules should have snake case names
+ non-upper-case-globals warn static constants should have uppercase identifiers
+ noop-method-call warn detects the use of well-known noop methods
+ no-mangle-generic-items warn generic items must be mangled
+ opaque-hidden-inferred-bound warn detects the use of nested `impl Trait` types in associated type bounds that are not general enough
+ overlapping-range-endpoints warn detects range patterns with overlapping endpoints
+ path-statements warn path statements with no effect
+ private-bounds warn private type in secondary interface of an item
+ private-interfaces warn private type in primary interface of an item
+ ptr-to-integer-transmute-in-consts warn detects pointer to integer transmutes in const functions and associated constants
+ redundant-semicolons warn detects unnecessary trailing semicolons
+ refining-impl-trait-internal warn impl trait in impl method signature does not match trait method signature
+ refining-impl-trait-reachable warn impl trait in impl method signature does not match trait method signature
+ renamed-and-removed-lints warn lints that have been renamed or removed
+ repr-c-enums-larger-than-int warn repr(C) enums with discriminant values that do not fit into a C int
+ rtsan-nonblocking-async warn detects incompatible uses of `#[sanitize(realtime = \"nonblocking\")]` on async functions
+ self-constructor-from-outer-item warn detect unsupported use of `Self` from outer item
+ special-module-name warn module declarations for files with a special meaning
+ stable-features warn stable features found in `#[feature]` directive
+ static-mut-refs warn creating a shared reference to mutable static
+ suspicious-double-ref-op warn suspicious call of trait method on `&&T`
+ trivial-bounds warn these bounds don't depend on an type parameters
+ type-alias-bounds warn bounds in type aliases are not enforced
+ tyvar-behind-raw-pointer warn raw pointer to an inference variable
+ uncommon-codepoints warn detects uncommon Unicode codepoints in identifiers
+ unconditional-recursion warn functions that cannot return without calling themselves
+ uncovered-param-in-projection warn impl contains type parameters that are not covered
+ unexpected-cfgs warn detects unexpected names and values in `#[cfg]` conditions
+ unfulfilled-lint-expectations warn unfulfilled lint expectation
+ ungated-async-fn-track-caller warn enabling track_caller on an async fn is a no-op unless the async_fn_track_caller feature is enabled
+ uninhabited-static warn uninhabited static
+ unknown-diagnostic-attributes warn detects unknown diagnostic attributes
+ unknown-lints warn unrecognized lint attribute
+ unnameable-test-items warn detects an item that cannot be named being marked as `#[test_case]`
+ unnecessary-transmutes warn detects transmutes that can also be achieved by other operations
+ unpredictable-function-pointer-comparisons warn detects unpredictable function pointer comparisons
+ unreachable-code warn detects unreachable code paths
+ unreachable-patterns warn detects unreachable patterns
+ unstable-name-collisions warn detects name collision with an existing but unstable method
+ unstable-syntax-pre-expansion warn unstable syntax can change at any point in the future, causing a hard error!
+ unsupported-calling-conventions warn use of unsupported calling convention
+ unused-allocation warn detects unnecessary allocations that can be eliminated
+ unused-assignments warn detect assignments that will never be read
+ unused-associated-type-bounds warn detects unused `Foo = Bar` bounds in `dyn Trait<Foo = Bar>`
+ unused-attributes warn detects attributes that were not used by the compiler
+ unused-braces warn unnecessary braces around an expression
+ unused-comparisons warn comparisons made useless by limits of the types involved
+ unused-doc-comments warn detects doc comments that aren't used by rustdoc
+ unused-features warn unused features found in crate-level `#[feature]` directives
+ unused-imports warn imports that are never used
+ unused-labels warn detects labels that are never used
+ unused-macros warn detects macros that were not used
+ unused-must-use warn unused result of a type flagged as `#[must_use]`
+ unused-mut warn detect mut variables which don't need to be mutable
+ unused-parens warn `if`, `match`, `while` and `return` do not need parentheses
+ unused-unsafe warn unnecessary use of an `unsafe` block
+ unused-variables warn detect variables which are not used in any way
+ useless-ptr-null-checks warn useless checking of non-null-typed pointer
+ uses-power-alignment warn Structs do not follow the power alignment rule under repr(C)
+ varargs-without-pattern warn detects usage of `...` arguments without a pattern in non-foreign items
+ warnings warn mass-change the level for lints which produce warnings
+ while-true warn suggest using `loop { }` instead of `while true { }`
+ ambiguous-associated-items deny ambiguous associated items
+ ambiguous-glob-imports deny detects certain glob imports that require reporting an ambiguity error
+ arithmetic-overflow deny arithmetic operation overflows
+ binary-asm-labels deny labels in inline assembly containing only 0 or 1 digits
+ bindings-with-variant-name deny detects pattern bindings with the same name as one of the matched variants
+ conflicting-repr-hints deny conflicts between `#[repr(..)]` hints that were previously accepted and used in practice
+ dangerous-implicit-autorefs deny implicit reference to a dereference of a raw pointer
+ default-overrides-default-fields deny detect `Default` impl that should use the type's default field values
+ dependency-on-unit-never-type-fallback deny never type fallback affecting unsafe function calls
+ deref-nullptr deny detects when an null pointer is dereferenced
+ elided-lifetimes-in-associated-constant deny elided lifetimes cannot be used in associated constants in impls
+ enum-intrinsics-non-enums deny detects calls to `core::mem::discriminant` and `core::mem::variant_count` with non-enum types
+ explicit-builtin-cfgs-in-flags deny detects builtin cfgs set via the `--cfg`
+ ill-formed-attribute-input deny ill-formed attribute inputs that were previously accepted and used in practice
+ incomplete-include deny trailing content in included file
+ ineffective-unstable-trait-impl deny detects `#[unstable]` on stable trait implementations for stable types
+ invalid-atomic-ordering deny usage of invalid atomic ordering in atomic operations and memory fences
+ invalid-doc-attributes deny detects invalid `#[doc(...)]` attributes
+ invalid-from-utf8-unchecked deny using a non UTF-8 literal in `std::str::from_utf8_unchecked`
+ invalid-macro-export-arguments deny \"invalid_parameter\" isn't a valid argument for `#[macro_export]`
+ invalid-null-arguments deny invalid null pointer in arguments
+ invalid-reference-casting deny casts of `&T` to `&mut T` without interior mutability
+ invalid-type-param-default deny type parameter default erroneously allowed in invalid location
+ legacy-derive-helpers deny detects derive helper attributes that are used before they are introduced
+ let-underscore-lock deny non-binding let on a synchronization lock
+ long-running-const-eval deny detects long const eval operations
+ macro-expanded-macro-exports-accessed-by-absolute-paths deny macro-expanded `macro_export` macros from the current crate cannot be referred to by absolute paths
+ mutable-transmutes deny transmuting &T to &mut T is undefined behavior, even if the reference is unused
+ named-asm-labels deny named labels in inline assembly
+ never-type-fallback-flowing-into-unsafe deny never type fallback affecting unsafe function calls
+ no-mangle-const-items deny const items will not have their symbols exported
+ out-of-scope-macro-calls deny detects out of scope calls to `macro_rules` in key-value attributes
+ overflowing-literals deny literal out of range for its type
+ patterns-in-fns-without-body deny patterns in functions without body were erroneously allowed
+ proc-macro-derive-resolution-fallback deny detects proc macro derives using inaccessible names from parent modules
+ pub-use-of-private-extern-crate deny detect public re-exports of private extern crates
+ repr-transparent-non-zst-fields deny transparent type contains an external ZST that is marked #[non_exhaustive] or contains private fields
+ semicolon-in-expressions-from-macros deny trailing semicolon in macro body used as expression
+ soft-unstable deny a feature gate that doesn't break dependent crates
+ test-unstable-lint deny this unstable lint is only for testing
+ text-direction-codepoint-in-comment deny invisible directionality-changing codepoints in comment
+ text-direction-codepoint-in-literal deny detect special Unicode codepoints that affect the visual representation of text on screen, changing the direction in which text flows
+ unconditional-panic deny operation will cause a panic at runtime
+ undropped-manually-drops deny calls to `std::mem::drop` with `std::mem::ManuallyDrop` instead of it's inner value
+ unknown-crate-types deny unknown crate type found in `#[crate_type]` directive
+ useless-deprecated deny detects deprecation attributes with no effect
+
+
+Lint groups provided by rustc:
+
+ name sub-lints
+ ---- ---------
+ warnings all lints that are set to issue warnings
+ deprecated-safe deprecated-safe-2024
+ future-incompatible aarch64-softfloat-neon, ambiguous-associated-items, ambiguous-glob-imports, coherence-leak-check, conflicting-repr-hints, const-evaluatable-unchecked, elided-lifetimes-in-associated-constant, forbidden-lint-groups, ill-formed-attribute-input, invalid-macro-export-arguments, invalid-type-param-default, late-bound-lifetime-arguments, legacy-derive-helpers, macro-expanded-macro-exports-accessed-by-absolute-paths, out-of-scope-macro-calls, patterns-in-fns-without-body, proc-macro-derive-resolution-fallback, pub-use-of-private-extern-crate, repr-c-enums-larger-than-int, repr-transparent-non-zst-fields, self-constructor-from-outer-item, semicolon-in-expressions-from-macros, soft-unstable, uncovered-param-in-projection, uninhabited-static, unstable-name-collisions, unstable-syntax-pre-expansion, unsupported-calling-conventions, varargs-without-pattern
+ keyword-idents keyword-idents-2018, keyword-idents-2024
+ let-underscore let-underscore-drop, let-underscore-lock
+ nonstandard-style non-camel-case-types, non-snake-case, non-upper-case-globals
+ refining-impl-trait refining-impl-trait-reachable, refining-impl-trait-internal
+ rust-2018-compatibility keyword-idents-2018, anonymous-parameters, absolute-paths-not-starting-with-crate, tyvar-behind-raw-pointer
+ rust-2018-idioms bare-trait-objects, unused-extern-crates, ellipsis-inclusive-range-patterns, elided-lifetimes-in-paths, explicit-outlives-requirements
+ rust-2021-compatibility ellipsis-inclusive-range-patterns, array-into-iter, non-fmt-panics, bare-trait-objects, rust-2021-incompatible-closure-captures, rust-2021-incompatible-or-patterns, rust-2021-prefixes-incompatible-syntax, rust-2021-prelude-collisions
+ rust-2024-compatibility keyword-idents-2024, edition-2024-expr-fragment-specifier, boxed-slice-into-iter, impl-trait-overcaptures, if-let-rescope, static-mut-refs, dependency-on-unit-never-type-fallback, deprecated-safe-2024, missing-unsafe-on-extern, never-type-fallback-flowing-into-unsafe, rust-2024-guarded-string-incompatible-syntax, rust-2024-incompatible-pat, rust-2024-prelude-collisions, tail-expr-drop-order, unsafe-attr-outside-unsafe, unsafe-op-in-unsafe-fn
+ unknown-or-malformed-diagnostic-attributes malformed-diagnostic-attributes, malformed-diagnostic-format-literals, misplaced-diagnostic-attributes, unknown-diagnostic-attributes
+ unused unused-imports, unused-variables, unused-assignments, dead-code, unused-mut, unreachable-code, unreachable-patterns, unused-must-use, unused-unsafe, path-statements, unused-attributes, unused-macros, unused-macro-rules, unused-allocation, unused-doc-comments, unused-extern-crates, unused-features, unused-labels, unused-parens, unused-braces, redundant-semicolons, map-unit-fn
+
+
+Lint tools like Clippy can load additional lints and lint groups.
+";
+ const EXPECTED: &[u8; 2876] = b"deprecated-safe = { level = \"deny\", priority = -1 }
+future-incompatible = { level = \"deny\", priority = -1 }
+keyword-idents = { level = \"deny\", priority = -1 }
+let-underscore = { level = \"deny\", priority = -1 }
+nonstandard-style = { level = \"deny\", priority = -1 }
+refining-impl-trait = { level = \"deny\", priority = -1 }
+rust-2018-compatibility = { level = \"deny\", priority = -1 }
+rust-2018-idioms = { level = \"deny\", priority = -1 }
+rust-2021-compatibility = { level = \"deny\", priority = -1 }
+rust-2024-compatibility = { level = \"deny\", priority = -1 }
+unknown-or-malformed-diagnostic-attributes = { level = \"deny\", priority = -1 }
+unused = { level = \"deny\", priority = -1 }
+warnings = { level = \"deny\", priority = -1 }
+ambiguous-negative-literals = { level = \"deny\", priority = -1 }
+closure-returning-async-block = { level = \"deny\", priority = -1 }
+deprecated-in-future = { level = \"deny\", priority = -1 }
+deref-into-dyn-supertrait = { level = \"deny\", priority = -1 }
+ffi-unwind-calls = { level = \"deny\", priority = -1 }
+fuzzy-provenance-casts = { level = \"deny\", priority = -1 }
+impl-trait-redundant-captures = { level = \"deny\", priority = -1 }
+linker-messages = { level = \"deny\", priority = -1 }
+lossy-provenance-casts = { level = \"deny\", priority = -1 }
+macro-use-extern-crate = { level = \"deny\", priority = -1 }
+meta-variable-misuse = { level = \"deny\", priority = -1 }
+missing-copy-implementations = { level = \"deny\", priority = -1 }
+missing-debug-implementations = { level = \"deny\", priority = -1 }
+missing-docs = { level = \"deny\", priority = -1 }
+multiple-supertrait-upcastable = { level = \"deny\", priority = -1 }
+must-not-suspend = { level = \"deny\", priority = -1 }
+non-ascii-idents = { level = \"deny\", priority = -1 }
+non-exhaustive-omitted-patterns = { level = \"deny\", priority = -1 }
+redundant-imports = { level = \"deny\", priority = -1 }
+redundant-lifetimes = { level = \"deny\", priority = -1 }
+resolving-to-items-shadowing-supertrait-items = { level = \"deny\", priority = -1 }
+shadowing-supertrait-items = { level = \"deny\", priority = -1 }
+single-use-lifetimes = { level = \"deny\", priority = -1 }
+trivial-casts = { level = \"deny\", priority = -1 }
+trivial-numeric-casts = { level = \"deny\", priority = -1 }
+unit-bindings = { level = \"deny\", priority = -1 }
+unnameable-types = { level = \"deny\", priority = -1 }
+unqualified-local-imports = { level = \"deny\", priority = -1 }
+unreachable-pub = { level = \"deny\", priority = -1 }
+unsafe-code = { level = \"deny\", priority = -1 }
+unstable-features = { level = \"deny\", priority = -1 }
+unused-crate-dependencies = { level = \"deny\", priority = -1 }
+unused-import-braces = { level = \"deny\", priority = -1 }
+unused-lifetimes = { level = \"deny\", priority = -1 }
+unused-qualifications = { level = \"deny\", priority = -1 }
+unused-results = { level = \"deny\", priority = -1 }
+variant-size-differences = { level = \"deny\", priority = -1 }
+";
+ assert!(
+ Command::new(env!("CARGO_BIN_EXE_lints"))
+ .arg("-")
+ .stderr(Stdio::piped())
+ .stdin(Stdio::piped())
+ .stdout(Stdio::piped())
+ .spawn()
+ .is_ok_and(|mut child| child.stdin.take().is_some_and(|mut stdin| {
+ let handle = thread::spawn(move || {
+ assert!(stdin.write_all(RUSTC_1_93_0).is_ok_and(|()| true));
+ });
+ child.wait_with_output().is_ok_and(|output| {
+ handle.is_finished()
+ && output.status.code() == Some(0i32)
+ && output.stderr.is_empty()
+ && output.stdout == EXPECTED
+ })
+ }))
+ );
+}