use crate::crate_name::CrateName;
use crate::error::{InstallerError, Result};
pub const LINT_CRATES: &[&str] = &[
"bumpy_road_function",
"conditional_max_n_branches",
"function_attrs_follow_docs",
"module_max_lines",
"module_must_have_inner_docs",
"no_expect_outside_tests",
"test_must_not_have_example",
"no_std_fs_operations",
"no_unwrap_or_else_panic",
];
pub const EXPERIMENTAL_LINT_CRATES: &[&str] = &[];
pub const SUITE_CRATE: &str = "whitaker_suite";
#[derive(Debug, Clone, Default)]
pub struct CrateResolutionOptions {
pub individual_lints: bool,
pub experimental: bool,
}
#[must_use]
pub fn resolve_crates(
specific_lints: &[CrateName],
options: &CrateResolutionOptions,
) -> Vec<CrateName> {
if !specific_lints.is_empty() {
return specific_lints.to_vec();
}
if options.individual_lints {
let mut crates: Vec<CrateName> = LINT_CRATES.iter().map(|&c| CrateName::from(c)).collect();
if options.experimental {
crates.extend(EXPERIMENTAL_LINT_CRATES.iter().map(|&c| CrateName::from(c)));
}
return crates;
}
vec![CrateName::from(SUITE_CRATE)]
}
#[must_use]
pub fn is_known_crate(name: &CrateName) -> bool {
let s = name.as_str();
LINT_CRATES.contains(&s) || EXPERIMENTAL_LINT_CRATES.contains(&s) || s == SUITE_CRATE
}
pub fn validate_crate_names(names: &[CrateName]) -> Result<()> {
for name in names {
if !is_known_crate(name) {
return Err(InstallerError::LintCrateNotFound { name: name.clone() });
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use rstest::rstest;
struct ResolveCratesCase {
individual_lints: bool,
experimental: bool,
expect_lint: bool,
expect_suite: bool,
expect_bumpy_road: bool,
}
#[rstest]
#[case::default_suite_only(ResolveCratesCase { individual_lints: false, experimental: false, expect_lint: false, expect_suite: true, expect_bumpy_road: false })]
#[case::individual_lints(ResolveCratesCase { individual_lints: true, experimental: false, expect_lint: true, expect_suite: false, expect_bumpy_road: true })]
#[case::individual_with_experimental(ResolveCratesCase { individual_lints: true, experimental: true, expect_lint: true, expect_suite: false, expect_bumpy_road: true })]
#[case::suite_with_experimental(ResolveCratesCase { individual_lints: false, experimental: true, expect_lint: false, expect_suite: true, expect_bumpy_road: false })]
fn resolve_crates_variants(#[case] case: ResolveCratesCase) {
let options = CrateResolutionOptions {
individual_lints: case.individual_lints,
experimental: case.experimental,
};
let crates = resolve_crates(&[], &options);
assert_eq!(
crates.contains(&CrateName::from("module_max_lines")),
case.expect_lint,
"lint crate inclusion mismatch"
);
assert_eq!(
crates.contains(&CrateName::from(SUITE_CRATE)),
case.expect_suite,
"suite crate inclusion mismatch"
);
assert_eq!(
crates.contains(&CrateName::from("bumpy_road_function")),
case.expect_bumpy_road,
"bumpy_road_function inclusion mismatch"
);
}
#[test]
fn resolve_crates_specific_lints() {
let specific = vec![CrateName::from("module_max_lines")];
let crates = resolve_crates(&specific, &CrateResolutionOptions::default());
assert_eq!(crates, vec![CrateName::from("module_max_lines")]);
}
#[rstest]
#[case::valid(&["module_max_lines", "whitaker_suite"], true)]
#[case::bumpy_road_function(&["bumpy_road_function"], true)]
#[case::unknown(&["nonexistent_lint"], false)]
fn validate_crate_names_variants(#[case] names: &[&str], #[case] expect_ok: bool) {
let crate_names: Vec<CrateName> = names.iter().map(|&s| CrateName::from(s)).collect();
let res = validate_crate_names(&crate_names);
if expect_ok {
assert!(res.is_ok());
} else {
let err = res.expect_err("expected validation failure");
assert!(
matches!(&err, InstallerError::LintCrateNotFound { name } if *name == crate_names[0]),
"unexpected error: {err:?}"
);
}
}
}