Skip to content

Commit c9895a1

Browse files
authored
Add Clippy like lint groups (#16464)
This PR overhauls how lint groups work within Cargo, aligning them with how Clippy handles groups[^1], all while simplifying the implementation (in my opinion). The first commit adds eight lint groups that mirror [Clippy's groups](https://github.com/rust-lang/rust-clippy?tab=readme-ov-file#clippy), while the subsequent commits work towards matching how Clippy handles groups, as well as improving how lint groups work within the existing linting system. The major changes: - Lints now have a primary group, instead of a set of groups they belong to - Lints now inherit their default level from their primary group - This vastly improved the code for getting the level for a lint - Surprisingly, I found this change helped with deciding what group a lint should belong to Note: Dynamic groups (i.e., `all` or `warnings`) aren't supported at this time Note: There are open questions about which `Level` we should default the groups to. This PR does not attempt to answer those questions and simply uses Clippy's default levels for convenience. [^1]: Clippy uses groups and categories interchangeably
2 parents 91f1a8a + 085d9d1 commit c9895a1

7 files changed

Lines changed: 179 additions & 52 deletions

File tree

crates/xtask-lint-docs/src/main.rs

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ fn main() -> anyhow::Result<()> {
2222
let mut lint_docs = String::new();
2323
for lint in cargo::lints::LINTS.iter().sorted_by_key(|lint| lint.name) {
2424
if lint.docs.is_some() {
25-
let sectipn = match lint.default_level {
25+
let sectipn = match lint.primary_group.default_level {
2626
LintLevel::Allow => &mut allow,
2727
LintLevel::Warn => &mut warn,
2828
LintLevel::Deny => &mut deny,
@@ -41,6 +41,8 @@ fn main() -> anyhow::Result<()> {
4141
)?;
4242
writeln!(buf)?;
4343

44+
lint_groups(&mut buf)?;
45+
4446
if !allow.is_empty() {
4547
add_level_section(LintLevel::Allow, &allow, &mut buf)?;
4648
}
@@ -69,9 +71,51 @@ fn main() -> anyhow::Result<()> {
6971
Ok(())
7072
}
7173

74+
fn lint_groups(buf: &mut String) -> anyhow::Result<()> {
75+
let (max_name_len, max_desc_len) = cargo::lints::LINT_GROUPS.iter().filter(|g| !g.hidden).fold(
76+
(0, 0),
77+
|(max_name_len, max_desc_len), group| {
78+
// We add 9 to account for the "cargo::" prefix and backticks
79+
let name_len = group.name.chars().count() + 9;
80+
let desc_len = group.desc.chars().count();
81+
(max_name_len.max(name_len), max_desc_len.max(desc_len))
82+
},
83+
);
84+
let default_level_len = "Default level".chars().count();
85+
writeln!(buf, "\n")?;
86+
writeln!(
87+
buf,
88+
"| {:<max_name_len$} | {:<max_desc_len$} | Default level |",
89+
"Group", "Description",
90+
)?;
91+
writeln!(
92+
buf,
93+
"|-{}-|-{}-|-{}-|",
94+
"-".repeat(max_name_len),
95+
"-".repeat(max_desc_len),
96+
"-".repeat(default_level_len)
97+
)?;
98+
for group in cargo::lints::LINT_GROUPS.iter() {
99+
if group.hidden {
100+
continue;
101+
}
102+
let group_name = format!("`cargo::{}`", group.name);
103+
writeln!(
104+
buf,
105+
"| {:<max_name_len$} | {:<max_desc_len$} | {:<default_level_len$} |",
106+
group_name,
107+
group.desc,
108+
group.default_level.to_string(),
109+
)?;
110+
}
111+
writeln!(buf, "\n")?;
112+
Ok(())
113+
}
114+
72115
fn add_lint(lint: &Lint, buf: &mut String) -> std::fmt::Result {
73116
writeln!(buf, "## `{}`", lint.name)?;
74-
writeln!(buf, "Set to `{}` by default", lint.default_level)?;
117+
writeln!(buf, "Group: `{}`\n", lint.primary_group.name)?;
118+
writeln!(buf, "Level: `{}`", lint.primary_group.default_level)?;
75119
writeln!(buf, "{}\n", lint.docs.as_ref().unwrap())
76120
}
77121

src/cargo/lints/mod.rs

Lines changed: 103 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,25 @@ use cargo_util_schemas::manifest::TomlToolLints;
1010
use pathdiff::diff_paths;
1111

1212
use std::borrow::Cow;
13+
use std::cmp::{Reverse, max_by_key};
1314
use std::fmt::Display;
1415
use std::ops::Range;
1516
use std::path::Path;
1617

1718
pub mod rules;
1819
pub use rules::LINTS;
1920

20-
const LINT_GROUPS: &[LintGroup] = &[TEST_DUMMY_UNSTABLE];
21+
pub const LINT_GROUPS: &[LintGroup] = &[
22+
COMPLEXITY,
23+
CORRECTNESS,
24+
NURSERY,
25+
PEDANTIC,
26+
PERF,
27+
RESTRICTION,
28+
STYLE,
29+
SUSPICIOUS,
30+
TEST_DUMMY_UNSTABLE,
31+
];
2132

2233
/// Scope at which a lint runs: package-level or workspace-level.
2334
pub enum ManifestFor<'a> {
@@ -141,17 +152,12 @@ fn find_lint_or_group<'a>(
141152
if let Some(lint) = LINTS.iter().find(|l| l.name == name) {
142153
Some((
143154
lint.name,
144-
&lint.default_level,
155+
&lint.primary_group.default_level,
145156
&lint.edition_lint_opts,
146157
&lint.feature_gate,
147158
))
148159
} else if let Some(group) = LINT_GROUPS.iter().find(|g| g.name == name) {
149-
Some((
150-
group.name,
151-
&group.default_level,
152-
&group.edition_lint_opts,
153-
&group.feature_gate,
154-
))
160+
Some((group.name, &group.default_level, &None, &group.feature_gate))
155161
} else {
156162
None
157163
}
@@ -267,25 +273,88 @@ pub struct LintGroup {
267273
pub name: &'static str,
268274
pub default_level: LintLevel,
269275
pub desc: &'static str,
270-
pub edition_lint_opts: Option<(Edition, LintLevel)>,
271276
pub feature_gate: Option<&'static Feature>,
277+
pub hidden: bool,
272278
}
273279

280+
const COMPLEXITY: LintGroup = LintGroup {
281+
name: "complexity",
282+
desc: "code that does something simple but in a complex way",
283+
default_level: LintLevel::Warn,
284+
feature_gate: None,
285+
hidden: false,
286+
};
287+
288+
const CORRECTNESS: LintGroup = LintGroup {
289+
name: "correctness",
290+
desc: "code that is outright wrong or useless",
291+
default_level: LintLevel::Deny,
292+
feature_gate: None,
293+
hidden: false,
294+
};
295+
296+
const NURSERY: LintGroup = LintGroup {
297+
name: "nursery",
298+
desc: "new lints that are still under development",
299+
default_level: LintLevel::Allow,
300+
feature_gate: None,
301+
hidden: false,
302+
};
303+
304+
const PEDANTIC: LintGroup = LintGroup {
305+
name: "pedantic",
306+
desc: "lints which are rather strict or have occasional false positives",
307+
default_level: LintLevel::Allow,
308+
feature_gate: None,
309+
hidden: false,
310+
};
311+
312+
const PERF: LintGroup = LintGroup {
313+
name: "perf",
314+
desc: "code that can be written to run faster",
315+
default_level: LintLevel::Warn,
316+
feature_gate: None,
317+
hidden: false,
318+
};
319+
320+
const RESTRICTION: LintGroup = LintGroup {
321+
name: "restriction",
322+
desc: "lints which prevent the use of Cargo features",
323+
default_level: LintLevel::Allow,
324+
feature_gate: None,
325+
hidden: false,
326+
};
327+
328+
const STYLE: LintGroup = LintGroup {
329+
name: "style",
330+
desc: "code that should be written in a more idiomatic way",
331+
default_level: LintLevel::Warn,
332+
feature_gate: None,
333+
hidden: false,
334+
};
335+
336+
const SUSPICIOUS: LintGroup = LintGroup {
337+
name: "suspicious",
338+
desc: "code that is most likely wrong or useless",
339+
default_level: LintLevel::Warn,
340+
feature_gate: None,
341+
hidden: false,
342+
};
343+
274344
/// This lint group is only to be used for testing purposes
275345
const TEST_DUMMY_UNSTABLE: LintGroup = LintGroup {
276346
name: "test_dummy_unstable",
277347
desc: "test_dummy_unstable is meant to only be used in tests",
278348
default_level: LintLevel::Allow,
279-
edition_lint_opts: None,
280349
feature_gate: Some(Feature::test_dummy_unstable()),
350+
hidden: true,
281351
};
282352

283353
#[derive(Copy, Clone, Debug)]
284354
pub struct Lint {
285355
pub name: &'static str,
286356
pub desc: &'static str,
287-
pub groups: &'static [LintGroup],
288-
pub default_level: LintLevel,
357+
pub primary_group: &'static LintGroup,
289358
pub edition_lint_opts: Option<(Edition, LintLevel)>,
290359
pub feature_gate: Option<&'static Feature>,
291360
/// This is a markdown formatted string that will be used when generating
@@ -310,33 +379,28 @@ impl Lint {
310379
return (LintLevel::Allow, LintLevelReason::Default);
311380
}
312381

313-
self.groups
314-
.iter()
315-
.map(|g| {
316-
(
317-
g.name,
318-
level_priority(
319-
g.name,
320-
g.default_level,
321-
g.edition_lint_opts,
322-
pkg_lints,
323-
edition,
324-
),
325-
)
326-
})
327-
.chain(std::iter::once((
328-
self.name,
329-
level_priority(
330-
self.name,
331-
self.default_level,
332-
self.edition_lint_opts,
333-
pkg_lints,
334-
edition,
335-
),
336-
)))
337-
.max_by_key(|(n, (l, _, p))| (l == &LintLevel::Forbid, *p, std::cmp::Reverse(*n)))
338-
.map(|(_, (l, r, _))| (l, r))
339-
.unwrap()
382+
let lint_level_priority = level_priority(
383+
self.name,
384+
self.primary_group.default_level,
385+
self.edition_lint_opts,
386+
pkg_lints,
387+
edition,
388+
);
389+
390+
let group_level_priority = level_priority(
391+
self.primary_group.name,
392+
self.primary_group.default_level,
393+
None,
394+
pkg_lints,
395+
edition,
396+
);
397+
398+
let (_, (l, r, _)) = max_by_key(
399+
(self.name, lint_level_priority),
400+
(self.primary_group.name, group_level_priority),
401+
|(n, (l, _, p))| (l == &LintLevel::Forbid, *p, Reverse(*n)),
402+
);
403+
(l, r)
340404
}
341405

342406
fn emitted_source(&self, lint_level: LintLevel, reason: LintLevelReason) -> String {

src/cargo/lints/rules/blanket_hint_mostly_unused.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,14 @@ use crate::GlobalContext;
1414
use crate::core::MaybePackage;
1515
use crate::lints::Lint;
1616
use crate::lints::LintLevel;
17+
use crate::lints::SUSPICIOUS;
1718
use crate::lints::get_key_value_span;
1819
use crate::lints::rel_cwd_manifest_path;
1920

2021
pub const LINT: Lint = Lint {
2122
name: "blanket_hint_mostly_unused",
2223
desc: "blanket_hint_mostly_unused lint",
23-
groups: &[],
24-
default_level: LintLevel::Warn,
24+
primary_group: &SUSPICIOUS,
2525
edition_lint_opts: None,
2626
feature_gate: None,
2727
docs: Some(

src/cargo/lints/rules/im_a_teapot.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,7 @@ use crate::lints::rel_cwd_manifest_path;
2121
pub const LINT: Lint = Lint {
2222
name: "im_a_teapot",
2323
desc: "`im_a_teapot` is specified",
24-
groups: &[TEST_DUMMY_UNSTABLE],
25-
default_level: LintLevel::Allow,
24+
primary_group: &TEST_DUMMY_UNSTABLE,
2625
edition_lint_opts: None,
2726
feature_gate: Some(Feature::test_dummy_unstable()),
2827
docs: None,

src/cargo/lints/rules/implicit_minimum_version_req.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,15 @@ use crate::lints::Lint;
2121
use crate::lints::LintLevel;
2222
use crate::lints::LintLevelReason;
2323
use crate::lints::ManifestFor;
24+
use crate::lints::PEDANTIC;
2425
use crate::lints::get_key_value;
2526
use crate::lints::rel_cwd_manifest_path;
2627
use crate::util::OptVersionReq;
2728

2829
pub const LINT: Lint = Lint {
2930
name: "implicit_minimum_version_req",
3031
desc: "dependency version requirement without an explicit minimum version",
31-
groups: &[],
32-
default_level: LintLevel::Allow,
32+
primary_group: &PEDANTIC,
3333
edition_lint_opts: None,
3434
feature_gate: None,
3535
docs: Some(

src/cargo/lints/rules/unknown_lints.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,13 @@ use crate::lints::LINTS;
1212
use crate::lints::Lint;
1313
use crate::lints::LintLevel;
1414
use crate::lints::ManifestFor;
15+
use crate::lints::SUSPICIOUS;
1516
use crate::lints::get_key_value_span;
1617

1718
pub const LINT: Lint = Lint {
1819
name: "unknown_lints",
1920
desc: "unknown lint",
20-
groups: &[],
21-
default_level: LintLevel::Warn,
21+
primary_group: &SUSPICIOUS,
2222
edition_lint_opts: None,
2323
feature_gate: None,
2424
docs: Some(

src/doc/src/reference/lints.md

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,20 @@
22

33
Note: [Cargo's linting system is unstable](unstable.md#lintscargo) and can only be used on nightly toolchains
44

5+
6+
7+
| Group | Description | Default level |
8+
|----------------------|------------------------------------------------------------------|---------------|
9+
| `cargo::complexity` | code that does something simple but in a complex way | warn |
10+
| `cargo::correctness` | code that is outright wrong or useless | deny |
11+
| `cargo::nursery` | new lints that are still under development | allow |
12+
| `cargo::pedantic` | lints which are rather strict or have occasional false positives | allow |
13+
| `cargo::perf` | code that can be written to run faster | warn |
14+
| `cargo::restriction` | lints which prevent the use of Cargo features | allow |
15+
| `cargo::style` | code that should be written in a more idiomatic way | warn |
16+
| `cargo::suspicious` | code that is most likely wrong or useless | warn |
17+
18+
519
## Allowed-by-default
620

721
These lints are all set to the 'allow' level by default.
@@ -14,7 +28,9 @@ These lints are all set to the 'warn' level by default.
1428
- [`unknown_lints`](#unknown_lints)
1529

1630
## `blanket_hint_mostly_unused`
17-
Set to `warn` by default
31+
Group: `suspicious`
32+
33+
Level: `warn`
1834

1935
### What it does
2036
Checks if `hint-mostly-unused` being applied to all dependencies.
@@ -42,7 +58,9 @@ hint-mostly-unused = true
4258

4359

4460
## `implicit_minimum_version_req`
45-
Set to `allow` by default
61+
Group: `pedantic`
62+
63+
Level: `allow`
4664

4765
### What it does
4866

@@ -91,7 +109,9 @@ serde = "1.0.219"
91109

92110

93111
## `unknown_lints`
94-
Set to `warn` by default
112+
Group: `suspicious`
113+
114+
Level: `warn`
95115

96116
### What it does
97117
Checks for unknown lints in the `[lints.cargo]` table

0 commit comments

Comments
 (0)