mirror of
https://github.com/rust-lang/cargo
synced 2024-09-28 20:15:04 +00:00
feat: Add an xtask to generate lint documentation
This commit is contained in:
parent
30a7eb55b9
commit
7d7b7c2c8b
|
@ -2,6 +2,7 @@
|
|||
build-man = "run --package xtask-build-man --"
|
||||
stale-label = "run --package xtask-stale-label --"
|
||||
bump-check = "run --package xtask-bump-check --"
|
||||
lint-docs = "run --package xtask-lint-docs --"
|
||||
|
||||
[env]
|
||||
# HACK: Until this is stabilized, `snapbox`s polyfill could get confused
|
||||
|
|
7
.github/workflows/main.yml
vendored
7
.github/workflows/main.yml
vendored
|
@ -83,6 +83,13 @@ jobs:
|
|||
- run: rustup update stable && rustup default stable
|
||||
- run: cargo stale-label
|
||||
|
||||
lint-docs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- run: rustup update stable && rustup default stable
|
||||
- run: cargo lint-docs --check
|
||||
|
||||
# Ensure Cargo.lock is up-to-date
|
||||
lockfile:
|
||||
runs-on: ubuntu-latest
|
||||
|
|
10
Cargo.lock
generated
10
Cargo.lock
generated
|
@ -4021,6 +4021,16 @@ dependencies = [
|
|||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "xtask-lint-docs"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"cargo",
|
||||
"clap",
|
||||
"itertools 0.12.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "xtask-stale-label"
|
||||
version = "0.0.0"
|
||||
|
|
14
crates/xtask-lint-docs/Cargo.toml
Normal file
14
crates/xtask-lint-docs/Cargo.toml
Normal file
|
@ -0,0 +1,14 @@
|
|||
[package]
|
||||
name = "xtask-lint-docs"
|
||||
version = "0.1.0"
|
||||
edition.workspace = true
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
cargo.workspace = true
|
||||
clap.workspace = true
|
||||
itertools.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
108
crates/xtask-lint-docs/src/main.rs
Normal file
108
crates/xtask-lint-docs/src/main.rs
Normal file
|
@ -0,0 +1,108 @@
|
|||
use cargo::util::command_prelude::{flag, ArgMatchesExt};
|
||||
use cargo::util::lints::{Lint, LintLevel};
|
||||
use itertools::Itertools;
|
||||
use std::fmt::Write;
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn cli() -> clap::Command {
|
||||
clap::Command::new("xtask-lint-docs").arg(flag("check", "Check that the docs are up-to-date"))
|
||||
}
|
||||
|
||||
fn main() -> anyhow::Result<()> {
|
||||
let args = cli().get_matches();
|
||||
let check = args.flag("check");
|
||||
|
||||
let mut allow = Vec::new();
|
||||
let mut warn = Vec::new();
|
||||
let mut deny = Vec::new();
|
||||
let mut forbid = Vec::new();
|
||||
|
||||
let mut lint_docs = String::new();
|
||||
for lint in cargo::util::lints::LINTS
|
||||
.iter()
|
||||
.sorted_by_key(|lint| lint.name)
|
||||
{
|
||||
if lint.docs.is_some() {
|
||||
let sectipn = match lint.default_level {
|
||||
LintLevel::Allow => &mut allow,
|
||||
LintLevel::Warn => &mut warn,
|
||||
LintLevel::Deny => &mut deny,
|
||||
LintLevel::Forbid => &mut forbid,
|
||||
};
|
||||
sectipn.push(lint.name);
|
||||
add_lint(lint, &mut lint_docs)?;
|
||||
}
|
||||
}
|
||||
|
||||
let mut buf = String::new();
|
||||
writeln!(buf, "# Lints\n")?;
|
||||
writeln!(
|
||||
buf,
|
||||
"Note: [Cargo's linting system is unstable](unstable.md#lintscargo) and can only be used on nightly toolchains"
|
||||
)?;
|
||||
writeln!(buf)?;
|
||||
|
||||
if !allow.is_empty() {
|
||||
add_level_section(LintLevel::Allow, &allow, &mut buf)?;
|
||||
}
|
||||
if !warn.is_empty() {
|
||||
add_level_section(LintLevel::Warn, &warn, &mut buf)?;
|
||||
}
|
||||
if !deny.is_empty() {
|
||||
add_level_section(LintLevel::Deny, &deny, &mut buf)?;
|
||||
}
|
||||
if !forbid.is_empty() {
|
||||
add_level_section(LintLevel::Forbid, &forbid, &mut buf)?;
|
||||
}
|
||||
|
||||
buf.push_str(&lint_docs);
|
||||
|
||||
if check {
|
||||
let old = std::fs::read_to_string(lint_docs_path())?;
|
||||
if old != buf {
|
||||
anyhow::bail!(
|
||||
"The lints documentation is out-of-date. Run `cargo lint-docs` to update it."
|
||||
);
|
||||
}
|
||||
} else {
|
||||
std::fs::write(lint_docs_path(), buf)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn add_lint(lint: &Lint, buf: &mut String) -> std::fmt::Result {
|
||||
writeln!(buf, "## `{}`", lint.name)?;
|
||||
writeln!(buf, "Set to `{}` by default", lint.default_level)?;
|
||||
writeln!(buf, "{}\n", lint.docs.as_ref().unwrap())
|
||||
}
|
||||
|
||||
fn add_level_section(level: LintLevel, lint_names: &[&str], buf: &mut String) -> std::fmt::Result {
|
||||
let title = match level {
|
||||
LintLevel::Allow => "Allowed-by-default",
|
||||
LintLevel::Warn => "Warn-by-default",
|
||||
LintLevel::Deny => "Deny-by-default",
|
||||
LintLevel::Forbid => "Forbid-by-default",
|
||||
};
|
||||
writeln!(buf, "## {title}\n")?;
|
||||
writeln!(
|
||||
buf,
|
||||
"These lints are all set to the '{}' level by default.",
|
||||
level
|
||||
)?;
|
||||
|
||||
for name in lint_names {
|
||||
writeln!(buf, "- [`{}`](#{})", name, name)?;
|
||||
}
|
||||
writeln!(buf)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn lint_docs_path() -> PathBuf {
|
||||
let pkg_root = env!("CARGO_MANIFEST_DIR");
|
||||
let ws_root = PathBuf::from(format!("{pkg_root}/../.."));
|
||||
let path = {
|
||||
let path = ws_root.join("src/doc/src/reference/lints.md");
|
||||
path.canonicalize().unwrap_or(path)
|
||||
};
|
||||
path
|
||||
}
|
|
@ -13,7 +13,7 @@ use std::path::Path;
|
|||
use toml_edit::ImDocument;
|
||||
|
||||
const LINT_GROUPS: &[LintGroup] = &[TEST_DUMMY_UNSTABLE];
|
||||
const LINTS: &[Lint] = &[
|
||||
pub const LINTS: &[Lint] = &[
|
||||
IM_A_TEAPOT,
|
||||
IMPLICIT_FEATURES,
|
||||
UNKNOWN_LINTS,
|
||||
|
|
|
@ -45,6 +45,7 @@
|
|||
* [SemVer Compatibility](reference/semver.md)
|
||||
* [Future incompat report](reference/future-incompat-report.md)
|
||||
* [Reporting build timings](reference/timings.md)
|
||||
* [Lints](reference/lints.md)
|
||||
* [Unstable Features](reference/unstable.md)
|
||||
|
||||
* [Cargo Commands](commands/index.md)
|
||||
|
|
|
@ -23,4 +23,5 @@ The reference covers the details of various areas of Cargo.
|
|||
* [SemVer Compatibility](semver.md)
|
||||
* [Future incompat report](future-incompat-report.md)
|
||||
* [Reporting build timings](timings.md)
|
||||
* [Lints](lints.md)
|
||||
* [Unstable Features](unstable.md)
|
||||
|
|
116
src/doc/src/reference/lints.md
Normal file
116
src/doc/src/reference/lints.md
Normal file
|
@ -0,0 +1,116 @@
|
|||
# Lints
|
||||
|
||||
Note: [Cargo's linting system is unstable](unstable.md#lintscargo) and can only be used on nightly toolchains
|
||||
|
||||
## Allowed-by-default
|
||||
|
||||
These lints are all set to the 'allow' level by default.
|
||||
- [`implicit_features`](#implicit_features)
|
||||
|
||||
## Warn-by-default
|
||||
|
||||
These lints are all set to the 'warn' level by default.
|
||||
- [`unknown_lints`](#unknown_lints)
|
||||
- [`unused_optional_dependency`](#unused_optional_dependency)
|
||||
|
||||
## `implicit_features`
|
||||
Set to `allow` by default
|
||||
|
||||
### What it does
|
||||
Checks for implicit features for optional dependencies
|
||||
|
||||
### Why it is bad
|
||||
By default, cargo will treat any optional dependency as a [feature]. As of
|
||||
cargo 1.60, these can be disabled by declaring a feature that activates the
|
||||
optional dependency as `dep:<name>` (see [RFC #3143]).
|
||||
|
||||
In the 2024 edition, `cargo` will stop exposing optional dependencies as
|
||||
features implicitly, requiring users to add `foo = ["dep:foo"]` if they
|
||||
still want it exposed.
|
||||
|
||||
For more information, see [RFC #3491]
|
||||
|
||||
### Example
|
||||
```toml
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
bar = { version = "0.1.0", optional = true }
|
||||
|
||||
[features]
|
||||
# No explicit feature activation for `bar`
|
||||
```
|
||||
|
||||
Instead, the dependency should have an explicit feature:
|
||||
```toml
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
bar = { version = "0.1.0", optional = true }
|
||||
|
||||
[features]
|
||||
bar = ["dep:bar"]
|
||||
```
|
||||
|
||||
[feature]: https://doc.rust-lang.org/cargo/reference/features.html
|
||||
[RFC #3143]: https://rust-lang.github.io/rfcs/3143-cargo-weak-namespaced-features.html
|
||||
[RFC #3491]: https://rust-lang.github.io/rfcs/3491-remove-implicit-features.html
|
||||
|
||||
|
||||
## `unknown_lints`
|
||||
Set to `warn` by default
|
||||
|
||||
### What it does
|
||||
Checks for unknown lints in the `[lints.cargo]` table
|
||||
|
||||
### Why it is bad
|
||||
- The lint name could be misspelled, leading to confusion as to why it is
|
||||
not working as expected
|
||||
- The unknown lint could end up causing an error if `cargo` decides to make
|
||||
a lint with the same name in the future
|
||||
|
||||
### Example
|
||||
```toml
|
||||
[lints.cargo]
|
||||
this-lint-does-not-exist = "warn"
|
||||
```
|
||||
|
||||
|
||||
## `unused_optional_dependency`
|
||||
Set to `warn` by default
|
||||
|
||||
### What it does
|
||||
Checks for optional dependencies that are not activated by any feature
|
||||
|
||||
### Why it is bad
|
||||
Starting in the 2024 edition, `cargo` no longer implicitly creates features
|
||||
for optional dependencies (see [RFC #3491]). This means that any optional
|
||||
dependency not specified with `"dep:<name>"` in some feature is now unused.
|
||||
This change may be surprising to users who have been using the implicit
|
||||
features `cargo` has been creating for optional dependencies.
|
||||
|
||||
### Example
|
||||
```toml
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
bar = { version = "0.1.0", optional = true }
|
||||
|
||||
[features]
|
||||
# No explicit feature activation for `bar`
|
||||
```
|
||||
|
||||
Instead, the dependency should be removed or activated in a feature:
|
||||
```toml
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
bar = { version = "0.1.0", optional = true }
|
||||
|
||||
[features]
|
||||
bar = ["dep:bar"]
|
||||
```
|
||||
|
||||
[RFC #3491]: https://rust-lang.github.io/rfcs/3491-remove-implicit-features.html
|
||||
|
||||
|
Loading…
Reference in a new issue