Skip to content

Commit 6c08e58

Browse files
committed
feat(cargo-lints): Add a lint for unknown_lints
1 parent 9159ebb commit 6c08e58

File tree

8 files changed

+368
-33
lines changed

8 files changed

+368
-33
lines changed

src/cargo/util/lints.rs

Lines changed: 175 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -29,45 +29,68 @@ pub fn analyze_cargo_lints_table(
2929
let manifest = pkg.manifest();
3030
let manifest_path = rel_cwd_manifest_path(path, gctx);
3131
let ws_path = rel_cwd_manifest_path(ws_path, gctx);
32-
33-
for lint_name in pkg_lints
32+
let mut unknown_lints = Vec::new();
33+
for (lint_name, specified_in) in pkg_lints
3434
.keys()
35-
.chain(ws_lints.map(|l| l.keys()).unwrap_or_default())
35+
.map(|name| (name, SpecifiedIn::Package))
36+
.chain(
37+
ws_lints
38+
.map(|l| l.keys())
39+
.unwrap_or_default()
40+
.map(|name| (name, SpecifiedIn::Workspace)),
41+
)
3642
{
37-
if let Some((name, default_level, edition_lint_opts, feature_gate)) =
43+
let Some((name, default_level, edition_lint_opts, feature_gate)) =
3844
find_lint_or_group(lint_name)
39-
{
40-
let (_, reason, _) = level_priority(
41-
name,
42-
*default_level,
43-
*edition_lint_opts,
44-
pkg_lints,
45-
ws_lints,
46-
manifest.edition(),
47-
);
48-
49-
// Only run analysis on user-specified lints
50-
if !reason.is_user_specified() {
51-
continue;
52-
}
45+
else {
46+
unknown_lints.push((lint_name, specified_in));
47+
continue;
48+
};
5349

54-
// Only run this on lints that are gated by a feature
55-
if let Some(feature_gate) = feature_gate {
56-
verify_feature_enabled(
57-
name,
58-
feature_gate,
59-
reason,
60-
manifest,
61-
&manifest_path,
62-
ws_contents,
63-
ws_document,
64-
&ws_path,
65-
&mut error_count,
66-
gctx,
67-
)?;
68-
}
50+
let (_, reason, _) = level_priority(
51+
name,
52+
*default_level,
53+
*edition_lint_opts,
54+
pkg_lints,
55+
ws_lints,
56+
manifest.edition(),
57+
);
58+
59+
// Only run analysis on user-specified lints
60+
if !reason.is_user_specified() {
61+
continue;
62+
}
63+
64+
// Only run this on lints that are gated by a feature
65+
if let Some(feature_gate) = feature_gate {
66+
verify_feature_enabled(
67+
name,
68+
feature_gate,
69+
reason,
70+
manifest,
71+
&manifest_path,
72+
ws_contents,
73+
ws_document,
74+
&ws_path,
75+
&mut error_count,
76+
gctx,
77+
)?;
6978
}
7079
}
80+
81+
output_unknown_lints(
82+
unknown_lints,
83+
manifest,
84+
&manifest_path,
85+
pkg_lints,
86+
ws_lints,
87+
ws_contents,
88+
ws_document,
89+
&ws_path,
90+
&mut error_count,
91+
gctx,
92+
)?;
93+
7194
if error_count > 0 {
7295
Err(anyhow::anyhow!(
7396
"encountered {error_count} errors(s) while verifying lints",
@@ -380,6 +403,11 @@ impl LintLevelReason {
380403
}
381404
}
382405

406+
enum SpecifiedIn {
407+
Package,
408+
Workspace,
409+
}
410+
383411
fn level_priority(
384412
name: &str,
385413
default_level: LintLevel,
@@ -573,6 +601,120 @@ pub fn check_implicit_features(
573601
Ok(())
574602
}
575603

604+
const UNKNOWN_LINTS: Lint = Lint {
605+
name: "unknown_lints",
606+
desc: "unknown lint",
607+
groups: &[],
608+
default_level: LintLevel::Warn,
609+
edition_lint_opts: None,
610+
feature_gate: None,
611+
};
612+
613+
fn output_unknown_lints(
614+
unknown_lints: Vec<(&String, SpecifiedIn)>,
615+
manifest: &Manifest,
616+
manifest_path: &str,
617+
pkg_lints: &TomlToolLints,
618+
ws_lints: Option<&TomlToolLints>,
619+
ws_contents: &str,
620+
ws_document: &ImDocument<String>,
621+
ws_path: &str,
622+
error_count: &mut usize,
623+
gctx: &GlobalContext,
624+
) -> CargoResult<()> {
625+
let (lint_level, reason) = UNKNOWN_LINTS.level(
626+
pkg_lints,
627+
ws_lints,
628+
manifest.edition(),
629+
manifest.unstable_features(),
630+
);
631+
if lint_level == LintLevel::Allow {
632+
return Ok(());
633+
}
634+
635+
let level = lint_level.to_diagnostic_level();
636+
let mut emitted_source = None;
637+
for (lint_name, specified_in) in unknown_lints {
638+
if lint_level == LintLevel::Forbid || lint_level == LintLevel::Deny {
639+
*error_count += 1;
640+
}
641+
let title = format!("{}: `{lint_name}`", UNKNOWN_LINTS.desc);
642+
let second_title = format!("`cargo::{}` was inherited", lint_name);
643+
let underscore_lint_name = lint_name.replace("-", "_");
644+
let matching = if let Some(lint) = LINTS.iter().find(|l| l.name == underscore_lint_name) {
645+
Some((lint.name, "lint"))
646+
} else if let Some(group) = LINT_GROUPS.iter().find(|g| g.name == underscore_lint_name) {
647+
Some((group.name, "group"))
648+
} else {
649+
None
650+
};
651+
let help =
652+
matching.map(|(name, kind)| format!("there is a {kind} with a similar name: `{name}`"));
653+
654+
let mut message = match specified_in {
655+
SpecifiedIn::Package => {
656+
let span =
657+
get_span(manifest.document(), &["lints", "cargo", lint_name], false).unwrap();
658+
659+
level.title(&title).snippet(
660+
Snippet::source(manifest.contents())
661+
.origin(&manifest_path)
662+
.annotation(Level::Error.span(span))
663+
.fold(true),
664+
)
665+
}
666+
SpecifiedIn::Workspace => {
667+
let lint_span = get_span(
668+
ws_document,
669+
&["workspace", "lints", "cargo", lint_name],
670+
false,
671+
)
672+
.unwrap();
673+
let inherit_span_key =
674+
get_span(manifest.document(), &["lints", "workspace"], false).unwrap();
675+
let inherit_span_value =
676+
get_span(manifest.document(), &["lints", "workspace"], true).unwrap();
677+
678+
level
679+
.title(&title)
680+
.snippet(
681+
Snippet::source(ws_contents)
682+
.origin(&ws_path)
683+
.annotation(Level::Error.span(lint_span))
684+
.fold(true),
685+
)
686+
.footer(
687+
Level::Note.title(&second_title).snippet(
688+
Snippet::source(manifest.contents())
689+
.origin(&manifest_path)
690+
.annotation(
691+
Level::Note
692+
.span(inherit_span_key.start..inherit_span_value.end),
693+
)
694+
.fold(true),
695+
),
696+
)
697+
}
698+
};
699+
700+
if emitted_source.is_none() {
701+
emitted_source = Some(format!(
702+
"`cargo::{}` is set to `{lint_level}` {reason}",
703+
UNKNOWN_LINTS.name
704+
));
705+
message = message.footer(Level::Note.title(emitted_source.as_ref().unwrap()));
706+
}
707+
708+
if let Some(help) = help.as_ref() {
709+
message = message.footer(Level::Help.title(help));
710+
}
711+
712+
gctx.shell().print_message(message)?;
713+
}
714+
715+
Ok(())
716+
}
717+
576718
const UNUSED_OPTIONAL_DEPENDENCY: Lint = Lint {
577719
name: "unused_optional_dependency",
578720
desc: "unused optional dependency",

tests/testsuite/lints/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
mod implicit_features;
2+
mod unknown_lints;
23
mod unused_optional_dependencies;
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
use cargo_test_support::prelude::*;
2+
use cargo_test_support::str;
3+
use cargo_test_support::{file, project};
4+
5+
#[cargo_test]
6+
fn case() {
7+
let p = project()
8+
.file(
9+
"Cargo.toml",
10+
r#"
11+
[package]
12+
name = "foo"
13+
version = "0.0.1"
14+
edition = "2015"
15+
authors = []
16+
17+
[lints.cargo]
18+
this-lint-does-not-exist = "warn"
19+
"#,
20+
)
21+
.file("src/lib.rs", "")
22+
.build();
23+
24+
snapbox::cmd::Command::cargo_ui()
25+
.masquerade_as_nightly_cargo(&["cargo-lints"])
26+
.current_dir(p.root())
27+
.arg("check")
28+
.arg("-Zcargo-lints")
29+
.assert()
30+
.success()
31+
.stdout_matches(str![""])
32+
.stderr_matches(file!["stderr.term.svg"]);
33+
}
Lines changed: 47 additions & 0 deletions
Loading
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
use cargo_test_support::prelude::*;
2+
use cargo_test_support::str;
3+
use cargo_test_support::{file, project};
4+
5+
#[cargo_test]
6+
fn case() {
7+
let p = project()
8+
.file(
9+
"Cargo.toml",
10+
r#"
11+
[workspace]
12+
members = ["foo"]
13+
14+
[workspace.lints.cargo]
15+
this-lint-does-not-exist = "warn"
16+
"#,
17+
)
18+
.file(
19+
"foo/Cargo.toml",
20+
r#"
21+
[package]
22+
name = "foo"
23+
version = "0.0.1"
24+
edition = "2015"
25+
authors = []
26+
27+
[lints]
28+
workspace = true
29+
"#,
30+
)
31+
.file("foo/src/lib.rs", "")
32+
.build();
33+
34+
snapbox::cmd::Command::cargo_ui()
35+
.masquerade_as_nightly_cargo(&["cargo-lints"])
36+
.current_dir(p.root())
37+
.arg("check")
38+
.arg("-Zcargo-lints")
39+
.assert()
40+
.success()
41+
.stdout_matches(str![""])
42+
.stderr_matches(file!["stderr.term.svg"]);
43+
}

0 commit comments

Comments
 (0)