blob: 1d0731f713a95ed840e8272fd422126f772a404b [file] [edit]
mod blanket_hint_mostly_unused;
mod deferred_parse_diagnostics;
mod im_a_teapot;
mod missing_lints_features;
mod missing_lints_inheritance;
mod non_kebab_case_bins;
mod non_kebab_case_features;
mod non_kebab_case_packages;
mod non_snake_case_features;
mod non_snake_case_packages;
mod redundant_homepage;
mod redundant_readme;
mod text_direction_codepoint_in_comment;
mod text_direction_codepoint_in_literal;
mod unknown_lints;
pub mod unused_dependencies;
mod unused_workspace_dependencies;
mod unused_workspace_package_fields;
use super::LintGroup;
use super::LintLevel;
use super::passes::ParsePassRule;
use crate::workspace::Feature;
pub const PARSE_PASS_RULES: &[ParsePassRule<'static>] = &[
ParsePassRule::DiagnosticManifest {
rule: deferred_parse_diagnostics::diagnose_manifest,
},
ParsePassRule::DiagnosticManifest {
rule: missing_lints_features::diagnose_manifest,
},
ParsePassRule::LintManifest {
rule: text_direction_codepoint_in_comment::lint_manifest,
lint: text_direction_codepoint_in_comment::LINT,
},
ParsePassRule::LintManifest {
rule: text_direction_codepoint_in_literal::lint_manifest,
lint: text_direction_codepoint_in_literal::LINT,
},
ParsePassRule::LintManifest {
rule: unknown_lints::lint_manifest,
lint: unknown_lints::LINT,
},
ParsePassRule::LintWorkspace {
rule: blanket_hint_mostly_unused::lint_workspace,
lint: blanket_hint_mostly_unused::LINT,
},
ParsePassRule::LintWorkspace {
rule: unused_workspace_dependencies::lint_workspace,
lint: unused_workspace_dependencies::LINT,
},
ParsePassRule::LintWorkspace {
rule: unused_workspace_package_fields::lint_workspace,
lint: unused_workspace_package_fields::LINT,
},
// `warn`
ParsePassRule::LintPackage {
rule: missing_lints_inheritance::lint_package,
lint: missing_lints_inheritance::LINT,
},
ParsePassRule::LintPackage {
rule: non_kebab_case_bins::lint_package,
lint: non_kebab_case_bins::LINT,
},
ParsePassRule::LintPackage {
rule: redundant_homepage::lint_package,
lint: redundant_homepage::LINT,
},
ParsePassRule::LintPackage {
rule: redundant_readme::lint_package,
lint: redundant_readme::LINT,
},
ParsePassRule::LintPackage {
rule: unused_dependencies::lint_package,
lint: unused_dependencies::LINT,
},
ParsePassRule::LintPackage {
rule: im_a_teapot::lint_package,
lint: im_a_teapot::LINT,
},
// `allow`
ParsePassRule::LintPackage {
rule: non_kebab_case_features::lint_package,
lint: non_kebab_case_features::LINT,
},
ParsePassRule::LintPackage {
rule: non_kebab_case_packages::lint_package,
lint: non_kebab_case_packages::LINT,
},
ParsePassRule::LintPackage {
rule: non_snake_case_features::lint_package,
lint: non_snake_case_features::LINT,
},
ParsePassRule::LintPackage {
rule: non_snake_case_packages::lint_package,
lint: non_snake_case_packages::LINT,
},
];
pub static LINTS: &[&crate::diagnostics::Lint] = &[
blanket_hint_mostly_unused::LINT,
im_a_teapot::LINT,
missing_lints_inheritance::LINT,
non_kebab_case_bins::LINT,
non_kebab_case_features::LINT,
non_kebab_case_packages::LINT,
non_snake_case_features::LINT,
non_snake_case_packages::LINT,
redundant_homepage::LINT,
redundant_readme::LINT,
text_direction_codepoint_in_comment::LINT,
text_direction_codepoint_in_literal::LINT,
unknown_lints::LINT,
unused_dependencies::LINT,
unused_workspace_dependencies::LINT,
unused_workspace_package_fields::LINT,
];
/// Version required for specifying `[lints.cargo]`
///
/// Before this, it was an error. No on-by-default lint should fire before this time without
/// another way of disabling it.
static CARGO_LINTS_MSRV: cargo_util_schemas::manifest::RustVersion =
cargo_util_schemas::manifest::RustVersion::new(1, 79, 0);
pub static LINT_GROUPS: &[LintGroup] = &[
DEFAULT,
CORRECTNESS,
COMPLEXITY,
PERF,
STYLE,
SUSPICIOUS,
NURSERY,
PEDANTIC,
RESTRICTION,
TEST_DUMMY_UNSTABLE,
];
const DEFAULT: LintGroup = LintGroup {
name: "default",
desc: "all lints that are on by default (correctness, suspicious, style, complexity, perf)",
default_level: LintLevel::Warn,
feature_gate: None,
hidden: false,
};
const COMPLEXITY: LintGroup = LintGroup {
name: "complexity",
desc: "code that does something simple but in a complex way",
default_level: LintLevel::Warn,
feature_gate: None,
hidden: false,
};
const CORRECTNESS: LintGroup = LintGroup {
name: "correctness",
desc: "code that is outright wrong or useless",
default_level: LintLevel::Deny,
feature_gate: None,
hidden: false,
};
const NURSERY: LintGroup = LintGroup {
name: "nursery",
desc: "new lints that are still under development",
default_level: LintLevel::Allow,
feature_gate: None,
hidden: false,
};
const PEDANTIC: LintGroup = LintGroup {
name: "pedantic",
desc: "lints which are rather strict or have occasional false positives",
default_level: LintLevel::Allow,
feature_gate: None,
hidden: false,
};
const PERF: LintGroup = LintGroup {
name: "perf",
desc: "code that can be written to run faster",
default_level: LintLevel::Warn,
feature_gate: None,
hidden: false,
};
const RESTRICTION: LintGroup = LintGroup {
name: "restriction",
desc: "lints which prevent the use of Cargo features",
default_level: LintLevel::Allow,
feature_gate: None,
hidden: false,
};
const STYLE: LintGroup = LintGroup {
name: "style",
desc: "code that should be written in a more idiomatic way",
default_level: LintLevel::Warn,
feature_gate: None,
hidden: false,
};
const SUSPICIOUS: LintGroup = LintGroup {
name: "suspicious",
desc: "code that is most likely wrong or useless",
default_level: LintLevel::Warn,
feature_gate: None,
hidden: false,
};
/// This lint group is only to be used for testing purposes
const TEST_DUMMY_UNSTABLE: LintGroup = LintGroup {
name: "test_dummy_unstable",
desc: "test_dummy_unstable is meant to only be used in tests",
default_level: LintLevel::Allow,
feature_gate: Some(crate::workspace::Feature::test_dummy_unstable()),
hidden: true,
};
fn find_lint_or_group<'a>(
name: &str,
) -> Option<(&'static str, &LintLevel, &Option<&'static Feature>)> {
if let Some(lint) = LINTS.iter().find(|l| l.name == name) {
Some((
lint.name,
&lint.primary_group.default_level,
&lint.feature_gate,
))
} else if let Some(group) = LINT_GROUPS.iter().find(|g| g.name == name) {
Some((group.name, &group.default_level, &group.feature_gate))
} else {
None
}
}
#[cfg(test)]
mod tests {
use crate::util::data_structures::HashSet;
use crate::util::data_structures::IndexMap;
use itertools::Itertools;
use snapbox::ToDebug;
use std::cmp::Reverse;
use super::*;
#[test]
fn ensure_lint_groups_do_not_default_to_forbid() {
let forbid_groups = LINT_GROUPS
.iter()
.filter(|g| matches!(g.default_level, LintLevel::Forbid))
.collect::<Vec<_>>();
assert!(
forbid_groups.is_empty(),
"\n`LintGroup`s should never default to `forbid`, but the following do:\n\
{}\n",
forbid_groups.iter().map(|g| g.name).join("\n")
);
}
#[test]
fn ensure_visible_lint_msrv() {
let invalid_msrvs = LINTS
.iter()
// Only relevant for default lints
.filter(|l| !matches!(l.primary_group.default_level, LintLevel::Allow))
.filter(|l| l.msrv.map(|v| v < CARGO_LINTS_MSRV).unwrap_or(false))
.map(|l| l.name)
.join(", ");
assert!(
invalid_msrvs.is_empty(),
"{invalid_msrvs} need `msrv` set so users can use `[lints.cargo]` to disable them"
);
}
#[test]
fn ensure_docs_sections() {
let expected_sections_restriction = &[
"### What it does",
"### Why restrict this?",
"### Drawbacks",
"### Example",
];
let expected_sections = &[
"### What it does",
"### Why is this bad?",
"### Drawbacks",
"### Example",
];
for lint in LINTS {
dbg!(lint.name);
let mut sections = IndexMap::default();
let mut title = "";
let mut body = Vec::new();
let Some(docs) = lint.docs else {
continue;
};
for line in docs.trim().lines() {
if line.starts_with("#") {
if !title.is_empty() || !body.is_empty() {
let old = sections.insert(title, body);
assert!(old.is_none(), "duplicate title: `{title:?}`");
}
title = line;
body = Vec::new();
} else {
body.push(line);
}
}
if !title.is_empty() || !body.is_empty() {
let old = sections.insert(title, body);
assert!(old.is_none(), "duplicate title: `{title:?}`");
}
let mut expected = Vec::new();
let expected_sections = match lint.primary_group.name {
"restriction" => expected_sections_restriction,
_ => expected_sections,
};
for section in expected_sections {
let body = match sections.get(section) {
Some(body) => body,
None => continue,
};
expected.push(*section);
expected.extend(body.iter().copied());
}
let expected = expected.join("\n");
snapbox::assert_data_eq!(docs.trim(), expected);
}
}
#[test]
fn ensure_sorted_lints() {
// This will be printed out if the fields are not sorted.
let location = std::panic::Location::caller();
println!("\nTo fix this test, sort `LINTS` in {}\n", location.file(),);
let actual = LINTS
.iter()
.map(|l| l.name.to_uppercase())
.collect::<Vec<_>>();
let mut expected = actual.clone();
expected.sort();
snapbox::assert_data_eq!(actual.to_debug(), expected.to_debug());
}
#[test]
fn ensure_sorted_lint_groups() {
// This will be printed out if the fields are not sorted.
let location = std::panic::Location::caller();
println!(
"\nTo fix this test, sort `LINT_GROUPS` in {}\n",
location.file(),
);
let actual = LINT_GROUPS
.iter()
.map(|l| {
(
l.name != "default",
Reverse(l.default_level),
l.name.to_uppercase(),
)
})
.collect::<Vec<_>>();
let mut expected = actual.clone();
expected.sort();
snapbox::assert_data_eq!(actual.to_debug(), expected.to_debug());
}
#[test]
fn ensure_sorted_parse_pass_rules() {
let actual = parse_pass_rule_names(PARSE_PASS_RULES);
let mut ordered_parse_pass = PARSE_PASS_RULES.to_vec();
ordered_parse_pass.sort_by_key(|rule| {
let (lint, scope) = match rule {
ParsePassRule::DiagnosticManifest { .. } => {
let scope = 0;
(None, scope)
}
ParsePassRule::LintManifest { lint, .. } => {
let scope = 0;
(Some(lint), scope)
}
ParsePassRule::DiagnosticWorkspace { .. } => {
let scope = 1;
(None, scope)
}
ParsePassRule::LintWorkspace { lint, .. } => {
let scope = 1;
(Some(lint), scope)
}
ParsePassRule::DiagnosticPackage { .. } => {
let scope = 2;
(None, scope)
}
ParsePassRule::LintPackage { lint, .. } => {
let scope = 2;
(Some(lint), scope)
}
};
let is_lint = lint.is_some();
let level = lint.map(|l| std::cmp::Reverse(l.primary_group.default_level));
let name = lint.map(|l| l.name);
(is_lint, scope, level, name)
});
let expected = parse_pass_rule_names(&ordered_parse_pass);
println!("`PARSE_PASS_RULES` sort order:");
snapbox::assert_data_eq!(actual.join("\n"), expected.join("\n"));
}
#[test]
fn ensure_parse_passed_in_lints() {
let parse_pass_lint_names =
HashSet::from_iter(parse_pass_rule_names(PARSE_PASS_RULES).into_iter());
let lint_names = LINTS.iter().map(|l| l.name).collect::<HashSet<_>>();
let diff = parse_pass_lint_names
.difference(&lint_names)
.sorted()
.collect::<Vec<_>>();
let mut need_added = String::new();
for name in &diff {
need_added.push_str(&format!("{name}\n"));
}
assert!(
diff.is_empty(),
"\n`LINTS` did not contain all `Lint`s found in `PARSE_PASS_RULES`\n\
Please add the following to `LINTS`:\n\
{need_added}",
);
}
fn parse_pass_rule_names(rules: &[ParsePassRule<'_>]) -> Vec<&'static str> {
rules
.iter()
.filter_map(|rule| match rule {
ParsePassRule::DiagnosticManifest { .. }
| ParsePassRule::DiagnosticWorkspace { .. }
| ParsePassRule::DiagnosticPackage { .. } => None,
ParsePassRule::LintManifest { lint, .. }
| ParsePassRule::LintWorkspace { lint, .. }
| ParsePassRule::LintPackage { lint, .. } => Some(lint.name),
})
.collect()
}
#[test]
fn ensure_updated_lints() {
let dir = snapbox::utils::current_dir!();
let mut expected = HashSet::default();
for entry in std::fs::read_dir(&dir).unwrap() {
let entry = entry.unwrap();
let path = entry.path();
if path.ends_with("mod.rs") {
continue;
}
let content = std::fs::read_to_string(&path).unwrap();
if !content.contains("LINT") {
// diagnostic
continue;
}
let lint_name = path.file_stem().unwrap().to_string_lossy();
assert!(expected.insert(lint_name.into()), "duplicate lint found");
}
let actual = LINTS
.iter()
.map(|l| l.name.to_string())
.collect::<HashSet<_>>();
let diff = expected.difference(&actual).sorted().collect::<Vec<_>>();
let mut need_added = String::new();
for name in &diff {
need_added.push_str(&format!("{name}\n"));
}
assert!(
diff.is_empty(),
"\n`LINTS` did not contain all `Lint`s found in {}\n\
Please add the following to `LINTS`:\n\
{need_added}",
dir.display(),
);
}
#[test]
fn ensure_updated_lint_groups() {
let path = snapbox::utils::current_rs!();
let expected = std::fs::read_to_string(&path).unwrap();
let expected = expected
.lines()
.filter_map(|l| {
if l.ends_with(": LintGroup = LintGroup {") {
Some(
l.chars()
.skip(6)
.take_while(|c| *c != ':')
.collect::<String>(),
)
} else {
None
}
})
.collect::<HashSet<_>>();
let actual = LINT_GROUPS
.iter()
.map(|l| l.name.to_uppercase())
.collect::<HashSet<_>>();
let diff = expected.difference(&actual).sorted().collect::<Vec<_>>();
let mut need_added = String::new();
for name in &diff {
need_added.push_str(&format!("{}\n", name));
}
assert!(
diff.is_empty(),
"\n`LINT_GROUPS` did not contain all `LintGroup`s found in {}\n\
Please add the following to `LINT_GROUPS`:\n\
{}",
path.display(),
need_added
);
}
}