-
Notifications
You must be signed in to change notification settings - Fork 2.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Add an xtask to generate lint documentation
- Loading branch information
Showing
9 changed files
with
270 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
[package] | ||
name = "xtask-lint-docs" | ||
version = "0.1.0" | ||
edition.workspace = true | ||
publish = false | ||
|
||
[dependencies] | ||
anyhow.workspace = true | ||
cargo.workspace = true | ||
clap.workspace = true | ||
|
||
[lints] | ||
workspace = true |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
use cargo::util::command_prelude::{flag, ArgMatchesExt}; | ||
use cargo::util::lints::{Lint, LintLevel}; | ||
use std::fmt::Write; | ||
use std::path::PathBuf; | ||
|
||
struct LintSection { | ||
level: LintLevel, | ||
names: Vec<&'static str>, | ||
docs: String, | ||
} | ||
|
||
impl LintSection { | ||
fn new(level: LintLevel) -> Self { | ||
Self { | ||
level, | ||
names: Vec::new(), | ||
docs: String::new(), | ||
} | ||
} | ||
|
||
fn add_lint(&mut self, lint: &Lint) { | ||
self.names.push(lint.name); | ||
|
||
// We want to add an extra `#` to each heading as it may be unexpected that | ||
// each lint is a subheading of the lint level. | ||
// We only want to do this for headings that are at least `##` as `#` is | ||
// used for comments and links. | ||
let docs = lint.docs.unwrap().replace("## ", "### "); | ||
|
||
self.docs.push_str("### `"); | ||
self.docs.push_str(lint.name); | ||
self.docs.push_str("`\n"); | ||
self.docs.push_str(&docs); | ||
self.docs.push_str("\n"); | ||
} | ||
|
||
fn write(&self, buf: &mut String) -> std::fmt::Result { | ||
let title = match self.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.", | ||
self.level | ||
)?; | ||
for name in &self.names { | ||
writeln!(buf, "- [`{}`](#{})", name, name)?; | ||
} | ||
writeln!(buf, "\n{}", self.docs) | ||
} | ||
} | ||
|
||
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 = LintSection::new(LintLevel::Allow); | ||
let mut warn = LintSection::new(LintLevel::Warn); | ||
let mut deny = LintSection::new(LintLevel::Deny); | ||
let mut forbid = LintSection::new(LintLevel::Forbid); | ||
for lint in cargo::util::lints::LINTS { | ||
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.add_lint(lint); | ||
} | ||
} | ||
|
||
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" | ||
)?; | ||
|
||
if !allow.names.is_empty() { | ||
allow.write(&mut buf)?; | ||
} | ||
if !warn.names.is_empty() { | ||
warn.write(&mut buf)?; | ||
} | ||
if !deny.names.is_empty() { | ||
deny.write(&mut buf)?; | ||
} | ||
if !forbid.names.is_empty() { | ||
forbid.write(&mut buf)?; | ||
} | ||
|
||
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 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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
# 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) | ||
|
||
### `implicit_features` | ||
|
||
Note: This only runs on edition 2021 and below | ||
|
||
#### 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 | ||
|
||
|
||
## Warn-by-default | ||
|
||
These lints are all set to the 'warn' level by default. | ||
- [`unknown_lints`](#unknown_lints) | ||
- [`unused_optional_dependency`](#unused_optional_dependency) | ||
|
||
### `unknown_lints` | ||
|
||
#### 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` | ||
|
||
Note: This only runs on edition 2024+ | ||
|
||
#### 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 | ||
|
||
|