use std::collections::BTreeMap;
use std::sync::Arc;
use pedant_core::check_config::{CheckConfig, ConfigFile, NamingCheck, PathOverride, PatternCheck};
use pedant_core::lint::analyze;
use pedant_core::violation::ViolationType;
use pedant_core::{Config, lint_file, lint_str};
fn default_config() -> CheckConfig {
CheckConfig::default()
}
fn permissive_config() -> CheckConfig {
CheckConfig {
max_depth: 10,
forbid_unsafe: false,
..default_config()
}
}
#[test]
fn test_nested_if_detection() {
let source = include_str!("fixtures/nested_if.rs");
let violations = analyze("nested_if.rs", source, &permissive_config(), None)
.unwrap()
.violations;
assert_eq!(violations.len(), 1);
assert_eq!(violations[0].violation_type, ViolationType::NestedIf);
assert_eq!(violations[0].line, 3);
}
#[test]
fn test_if_in_match_detection() {
let source = include_str!("fixtures/if_in_match.rs");
let violations = analyze("if_in_match.rs", source, &permissive_config(), None)
.unwrap()
.violations;
assert_eq!(violations.len(), 1);
assert_eq!(violations[0].violation_type, ViolationType::IfInMatch);
assert_eq!(violations[0].line, 4);
}
#[test]
fn test_nested_match_detection() {
let source = include_str!("fixtures/nested_match.rs");
let violations = analyze("nested_match.rs", source, &permissive_config(), None)
.unwrap()
.violations;
assert_eq!(violations.len(), 1);
assert_eq!(violations[0].violation_type, ViolationType::NestedMatch);
assert_eq!(violations[0].line, 4);
}
#[test]
fn test_deep_nesting_detection() {
let source = include_str!("fixtures/deep_nesting.rs");
let violations = analyze("deep_nesting.rs", source, &default_config(), None)
.unwrap()
.violations;
let max_depth_violations: Vec<_> = violations
.iter()
.filter(|v| v.violation_type == ViolationType::MaxDepth)
.collect();
assert!(!max_depth_violations.is_empty());
}
#[test]
fn test_else_chain_detection() {
let source = include_str!("fixtures/else_chain.rs");
let violations = analyze("else_chain.rs", source, &permissive_config(), None)
.unwrap()
.violations;
let else_chain_violations: Vec<_> = violations
.iter()
.filter(|v| v.violation_type == ViolationType::ElseChain)
.collect();
assert_eq!(else_chain_violations.len(), 1);
}
#[test]
fn test_clean_code_no_violations() {
let source = include_str!("fixtures/clean.rs");
let violations = analyze("clean.rs", source, &permissive_config(), None)
.unwrap()
.violations;
assert!(violations.is_empty());
}
#[test]
fn test_disabled_checks() {
let source = include_str!("fixtures/nested_if.rs");
let config = CheckConfig {
check_nested_if: false,
..permissive_config()
};
let violations = analyze("nested_if.rs", source, &config, None)
.unwrap()
.violations;
assert!(
violations
.iter()
.all(|v| v.violation_type != ViolationType::NestedIf)
);
}
#[test]
fn test_custom_max_depth() {
let source = include_str!("fixtures/deep_nesting.rs");
let violations = analyze("deep_nesting.rs", source, &permissive_config(), None)
.unwrap()
.violations;
assert!(
violations
.iter()
.all(|v| v.violation_type != ViolationType::MaxDepth)
);
}
#[test]
fn test_forbidden_attribute_detection() {
let source = include_str!("fixtures/forbidden_attr.rs");
let config = CheckConfig {
forbid_attributes: PatternCheck {
enabled: true,
patterns: vec![
Arc::from("allow(dead_code)"),
Arc::from("allow(unused*)"),
Arc::from("allow(clippy::*)"),
]
.into(),
},
..permissive_config()
};
let violations = analyze("forbidden_attr.rs", source, &config, None)
.unwrap()
.violations;
assert_eq!(violations.len(), 3);
assert!(
violations
.iter()
.all(|v| matches!(v.violation_type, ViolationType::ForbiddenAttribute { .. }))
);
}
#[test]
fn test_forbidden_attribute_disabled() {
let source = include_str!("fixtures/forbidden_attr.rs");
let config = CheckConfig {
forbid_attributes: PatternCheck {
enabled: false,
patterns: vec![Arc::from("allow(dead_code)")].into(),
},
..permissive_config()
};
let violations = analyze("forbidden_attr.rs", source, &config, None)
.unwrap()
.violations;
assert!(
violations
.iter()
.all(|v| !matches!(v.violation_type, ViolationType::ForbiddenAttribute { .. }))
);
}
#[test]
fn test_forbidden_type_detection() {
let source = include_str!("fixtures/forbidden_types.rs");
let config = CheckConfig {
forbid_types: PatternCheck {
enabled: true,
patterns: vec![
Arc::from("Arc<String>"),
Arc::from("Arc<Vec<*>>"),
Arc::from("Box<dyn*Error*>"),
]
.into(),
},
..permissive_config()
};
let violations = analyze("forbidden_types.rs", source, &config, None)
.unwrap()
.violations;
let type_violations: Vec<_> = violations
.iter()
.filter(|v| matches!(v.violation_type, ViolationType::ForbiddenType { .. }))
.collect();
assert_eq!(type_violations.len(), 4);
}
#[test]
fn test_forbidden_type_disabled() {
let source = include_str!("fixtures/forbidden_types.rs");
let config = CheckConfig {
forbid_types: PatternCheck {
enabled: false,
patterns: vec![Arc::from("Arc<String>")].into(),
},
..permissive_config()
};
let violations = analyze("forbidden_types.rs", source, &config, None)
.unwrap()
.violations;
assert!(
violations
.iter()
.all(|v| !matches!(v.violation_type, ViolationType::ForbiddenType { .. }))
);
}
#[test]
fn test_forbidden_call_detection() {
let source = include_str!("fixtures/forbidden_calls.rs");
let config = CheckConfig {
forbid_calls: PatternCheck {
enabled: true,
patterns: vec![
Arc::from(".unwrap()"),
Arc::from(".expect(*)"),
Arc::from(".clone()"),
]
.into(),
},
..permissive_config()
};
let violations = analyze("forbidden_calls.rs", source, &config, None)
.unwrap()
.violations;
let call_violations: Vec<_> = violations
.iter()
.filter(|v| matches!(v.violation_type, ViolationType::ForbiddenCall { .. }))
.collect();
assert_eq!(call_violations.len(), 3);
}
#[test]
fn test_forbidden_macro_detection() {
let source = include_str!("fixtures/forbidden_macros.rs");
let config = CheckConfig {
forbid_macros: PatternCheck {
enabled: true,
patterns: vec![
Arc::from("panic!"),
Arc::from("todo!"),
Arc::from("unimplemented!"),
Arc::from("dbg!"),
Arc::from("println!"),
]
.into(),
},
..permissive_config()
};
let violations = analyze("forbidden_macros.rs", source, &config, None)
.unwrap()
.violations;
let macro_violations: Vec<_> = violations
.iter()
.filter(|v| matches!(v.violation_type, ViolationType::ForbiddenMacro { .. }))
.collect();
assert_eq!(macro_violations.len(), 5);
}
#[test]
fn test_lint_str_api() {
let source = r#"
fn nested() {
if true {
if false {
println!("nested");
}
}
}
"#;
let config = Config::default();
let violations = lint_str(source, &config).unwrap().violations;
assert!(!violations.is_empty());
assert!(
violations
.iter()
.any(|v| v.violation_type == ViolationType::NestedIf)
);
}
#[test]
fn test_lint_file_api() {
use std::path::Path;
let path = Path::new("tests/fixtures/nested_if.rs");
let config = Config {
max_depth: 10,
..Config::default()
};
let violations = lint_file(path, &config).unwrap().violations;
assert!(!violations.is_empty());
assert!(
violations
.iter()
.any(|v| v.violation_type == ViolationType::NestedIf)
);
}
#[test]
fn test_forbidden_else() {
let source = include_str!("fixtures/forbidden_keywords.rs");
let config = CheckConfig {
forbid_else: true,
..permissive_config()
};
let violations = analyze("forbidden_keywords.rs", source, &config, None)
.unwrap()
.violations;
let else_violations: Vec<_> = violations
.iter()
.filter(|v| matches!(v.violation_type, ViolationType::ForbiddenElse))
.collect();
assert_eq!(else_violations.len(), 1);
}
#[test]
fn test_forbidden_unsafe() {
let source = include_str!("fixtures/forbidden_keywords.rs");
let config = CheckConfig {
forbid_unsafe: true,
..permissive_config()
};
let violations = analyze("forbidden_keywords.rs", source, &config, None)
.unwrap()
.violations;
let unsafe_violations: Vec<_> = violations
.iter()
.filter(|v| matches!(v.violation_type, ViolationType::ForbiddenUnsafe))
.collect();
assert_eq!(unsafe_violations.len(), 1);
}
#[test]
fn test_dyn_return_detection() {
let source = include_str!("fixtures/dyn_return.rs");
let config = CheckConfig {
check_dyn_return: true,
..permissive_config()
};
let violations = analyze("dyn_return.rs", source, &config, None)
.unwrap()
.violations;
let dyn_violations: Vec<_> = violations
.iter()
.filter(|v| matches!(v.violation_type, ViolationType::DynReturn))
.collect();
assert_eq!(dyn_violations.len(), 2);
}
#[test]
fn test_dyn_return_disabled() {
let source = include_str!("fixtures/dyn_return.rs");
let violations = analyze("dyn_return.rs", source, &permissive_config(), None)
.unwrap()
.violations;
assert!(
violations
.iter()
.all(|v| !matches!(v.violation_type, ViolationType::DynReturn))
);
}
#[test]
fn test_dyn_param_detection() {
let source = include_str!("fixtures/dyn_param.rs");
let config = CheckConfig {
check_dyn_param: true,
..permissive_config()
};
let violations = analyze("dyn_param.rs", source, &config, None)
.unwrap()
.violations;
let dyn_violations: Vec<_> = violations
.iter()
.filter(|v| matches!(v.violation_type, ViolationType::DynParam))
.collect();
assert_eq!(dyn_violations.len(), 2);
}
#[test]
fn test_dyn_param_disabled() {
let source = include_str!("fixtures/dyn_param.rs");
let violations = analyze("dyn_param.rs", source, &permissive_config(), None)
.unwrap()
.violations;
assert!(
violations
.iter()
.all(|v| !matches!(v.violation_type, ViolationType::DynParam))
);
}
#[test]
fn test_vec_box_dyn_detection() {
let source = include_str!("fixtures/vec_box_dyn.rs");
let config = CheckConfig {
check_vec_box_dyn: true,
..permissive_config()
};
let violations = analyze("vec_box_dyn.rs", source, &config, None)
.unwrap()
.violations;
let vbd_violations: Vec<_> = violations
.iter()
.filter(|v| matches!(v.violation_type, ViolationType::VecBoxDyn))
.collect();
assert_eq!(vbd_violations.len(), 2);
}
#[test]
fn test_dyn_field_detection() {
let source = include_str!("fixtures/dyn_field.rs");
let config = CheckConfig {
check_dyn_field: true,
..permissive_config()
};
let violations = analyze("dyn_field.rs", source, &config, None)
.unwrap()
.violations;
let field_violations: Vec<_> = violations
.iter()
.filter(|v| matches!(v.violation_type, ViolationType::DynField))
.collect();
assert_eq!(field_violations.len(), 3);
}
#[test]
fn test_dyn_field_disabled() {
let source = include_str!("fixtures/dyn_field.rs");
let violations = analyze("dyn_field.rs", source, &permissive_config(), None)
.unwrap()
.violations;
assert!(
violations
.iter()
.all(|v| !matches!(v.violation_type, ViolationType::DynField))
);
}
#[test]
fn test_clone_in_loop_detection() {
let source = include_str!("fixtures/clone_in_loop.rs");
let config = CheckConfig {
check_clone_in_loop: true,
..permissive_config()
};
let violations = analyze("clone_in_loop.rs", source, &config, None)
.unwrap()
.violations;
let clone_violations: Vec<_> = violations
.iter()
.filter(|v| matches!(v.violation_type, ViolationType::CloneInLoop))
.collect();
assert_eq!(clone_violations.len(), 4);
}
#[test]
fn test_clone_in_loop_disabled() {
let source = include_str!("fixtures/clone_in_loop.rs");
let violations = analyze("clone_in_loop.rs", source, &permissive_config(), None)
.unwrap()
.violations;
assert!(
violations
.iter()
.all(|v| !matches!(v.violation_type, ViolationType::CloneInLoop))
);
}
#[test]
fn test_default_hasher_detection() {
let source = include_str!("fixtures/default_hasher.rs");
let config = CheckConfig {
check_default_hasher: true,
..permissive_config()
};
let violations = analyze("default_hasher.rs", source, &config, None)
.unwrap()
.violations;
let hasher_violations: Vec<_> = violations
.iter()
.filter(|v| matches!(v.violation_type, ViolationType::DefaultHasher))
.collect();
assert_eq!(hasher_violations.len(), 4);
}
#[test]
fn test_default_hasher_disabled() {
let source = include_str!("fixtures/default_hasher.rs");
let violations = analyze("default_hasher.rs", source, &permissive_config(), None)
.unwrap()
.violations;
assert!(
violations
.iter()
.all(|v| !matches!(v.violation_type, ViolationType::DefaultHasher))
);
}
#[test]
fn test_mixed_concerns_detection() {
let source = include_str!("fixtures/mixed_concerns.rs");
let config = CheckConfig {
check_mixed_concerns: true,
..permissive_config()
};
let violations = analyze("mixed_concerns.rs", source, &config, None)
.unwrap()
.violations;
let mc_violations: Vec<_> = violations
.iter()
.filter(|v| matches!(v.violation_type, ViolationType::MixedConcerns))
.collect();
assert_eq!(mc_violations.len(), 1);
assert!(
mc_violations[0]
.message
.contains("disconnected type groups")
);
}
#[test]
fn test_mixed_concerns_clean() {
let source = include_str!("fixtures/mixed_concerns_clean.rs");
let config = CheckConfig {
check_mixed_concerns: true,
..permissive_config()
};
let violations = analyze("mixed_concerns_clean.rs", source, &config, None)
.unwrap()
.violations;
assert!(
violations
.iter()
.all(|v| !matches!(v.violation_type, ViolationType::MixedConcerns))
);
}
#[test]
fn test_mixed_concerns_body_coupling() {
let source = include_str!("fixtures/mixed_concerns_body.rs");
let config = CheckConfig {
check_mixed_concerns: true,
..permissive_config()
};
let violations = analyze("mixed_concerns_body.rs", source, &config, None)
.unwrap()
.violations;
assert!(
violations
.iter()
.all(|v| !matches!(v.violation_type, ViolationType::MixedConcerns))
);
}
#[test]
fn test_mixed_concerns_disabled() {
let source = include_str!("fixtures/mixed_concerns.rs");
let violations = analyze("mixed_concerns.rs", source, &permissive_config(), None)
.unwrap()
.violations;
assert!(
violations
.iter()
.all(|v| !matches!(v.violation_type, ViolationType::MixedConcerns))
);
}
#[test]
fn test_inline_tests_detection() {
let source = include_str!("fixtures/inline_tests.rs");
let config = CheckConfig {
check_inline_tests: true,
..permissive_config()
};
let violations = analyze("inline_tests.rs", source, &config, None)
.unwrap()
.violations;
let it_violations: Vec<_> = violations
.iter()
.filter(|v| matches!(v.violation_type, ViolationType::InlineTests))
.collect();
assert_eq!(it_violations.len(), 1);
assert!(it_violations[0].message.contains("tests"));
}
#[test]
fn test_inline_tests_disabled() {
let source = include_str!("fixtures/inline_tests.rs");
let violations = analyze("inline_tests.rs", source, &permissive_config(), None)
.unwrap()
.violations;
assert!(
violations
.iter()
.all(|v| !matches!(v.violation_type, ViolationType::InlineTests))
);
}
#[test]
fn test_let_underscore_result_detection() {
let source = include_str!("fixtures/let_underscore_result.rs");
let config = CheckConfig {
check_let_underscore_result: true,
..permissive_config()
};
let violations = analyze("let_underscore_result.rs", source, &config, None)
.unwrap()
.violations;
let lur_violations: Vec<_> = violations
.iter()
.filter(|v| matches!(v.violation_type, ViolationType::LetUnderscoreResult))
.collect();
assert_eq!(lur_violations.len(), 3);
assert_eq!(lur_violations[0].line, 5);
assert_eq!(lur_violations[1].line, 9);
assert_eq!(lur_violations[2].line, 13);
assert!(
lur_violations
.iter()
.all(|v| v.violation_type.code() == "let-underscore-result")
);
}
#[test]
fn test_let_underscore_result_disabled() {
let source = include_str!("fixtures/let_underscore_result.rs");
let violations = analyze(
"let_underscore_result.rs",
source,
&permissive_config(),
None,
)
.unwrap()
.violations;
assert!(
violations
.iter()
.all(|v| !matches!(v.violation_type, ViolationType::LetUnderscoreResult))
);
}
#[test]
fn test_let_underscore_result_no_init() {
let source = r#"
fn no_init() {
let _;
let x = 5;
}
"#;
let config = CheckConfig {
check_let_underscore_result: true,
..permissive_config()
};
let violations = analyze("no_init.rs", source, &config, None)
.unwrap()
.violations;
assert!(
violations
.iter()
.all(|v| !matches!(v.violation_type, ViolationType::LetUnderscoreResult))
);
}
#[test]
fn test_let_underscore_result_write_to_string_suppressed() {
let source = include_str!("fixtures/let_underscore_result_suppressed.rs");
let config = CheckConfig {
check_let_underscore_result: true,
..permissive_config()
};
let violations = analyze("let_underscore_result_suppressed.rs", source, &config, None)
.unwrap()
.violations;
assert!(
violations
.iter()
.all(|v| !matches!(v.violation_type, ViolationType::LetUnderscoreResult))
);
}
#[test]
fn test_let_underscore_result_write_to_non_string_not_suppressed() {
let source = r#"
use std::fmt::Write;
fn write_to_vec() {
let mut buf: Vec<u8> = Vec::new();
let _ = write!(buf, "x");
}
fn write_to_unknown() {
let _ = write!(stderr, "y");
}
"#;
let config = CheckConfig {
check_let_underscore_result: true,
..permissive_config()
};
let violations = analyze("non_string.rs", source, &config, None)
.unwrap()
.violations;
let lur_violations: Vec<_> = violations
.iter()
.filter(|v| matches!(v.violation_type, ViolationType::LetUnderscoreResult))
.collect();
assert_eq!(lur_violations.len(), 2);
}
#[test]
fn test_let_underscore_result_write_untyped_not_suppressed() {
let source = r#"
use std::fmt::Write;
fn write_to_untyped() {
let mut buf = String::new();
let _ = write!(buf, "x");
}
"#;
let config = CheckConfig {
check_let_underscore_result: true,
..permissive_config()
};
let violations = analyze("untyped.rs", source, &config, None)
.unwrap()
.violations;
let lur_violations: Vec<_> = violations
.iter()
.filter(|v| matches!(v.violation_type, ViolationType::LetUnderscoreResult))
.collect();
assert_eq!(lur_violations.len(), 1);
}
#[test]
fn test_high_param_count_detected() {
let source = include_str!("fixtures/high_param_count.rs");
let config = CheckConfig {
check_high_param_count: true,
max_params: 5,
..permissive_config()
};
let violations = analyze("high_param_count.rs", source, &config, None)
.unwrap()
.violations;
let hpc_violations: Vec<_> = violations
.iter()
.filter(|v| matches!(v.violation_type, ViolationType::HighParamCount))
.collect();
assert_eq!(hpc_violations.len(), 1);
assert_eq!(hpc_violations[0].line, 1);
}
#[test]
fn test_high_param_count_below_threshold() {
let source = include_str!("fixtures/high_param_count.rs");
let config = CheckConfig {
check_high_param_count: true,
max_params: 6,
..permissive_config()
};
let violations = analyze("high_param_count.rs", source, &config, None)
.unwrap()
.violations;
assert!(
violations
.iter()
.all(|v| !matches!(v.violation_type, ViolationType::HighParamCount))
);
}
#[test]
fn test_high_param_count_excludes_self() {
let source = include_str!("fixtures/high_param_count.rs");
let config = CheckConfig {
check_high_param_count: true,
max_params: 5,
..permissive_config()
};
let violations = analyze("high_param_count.rs", source, &config, None)
.unwrap()
.violations;
let hpc_violations: Vec<_> = violations
.iter()
.filter(|v| matches!(v.violation_type, ViolationType::HighParamCount))
.collect();
assert_eq!(hpc_violations.len(), 1);
assert!(hpc_violations[0].message.contains("many_params"));
}
#[test]
fn test_high_param_count_disabled_by_default() {
let source = include_str!("fixtures/high_param_count.rs");
let violations = analyze("high_param_count.rs", source, &permissive_config(), None)
.unwrap()
.violations;
assert!(
violations
.iter()
.all(|v| !matches!(v.violation_type, ViolationType::HighParamCount))
);
}
#[test]
fn test_path_override_precedence_matches_documented_behavior() {
let base = CheckConfig::default();
let mut overrides = BTreeMap::new();
overrides.insert(
Box::from("src/**"),
PathOverride {
max_depth: Some(10),
..PathOverride::default()
},
);
overrides.insert(
Box::from("src/hot/**"),
PathOverride {
max_depth: Some(2),
..PathOverride::default()
},
);
let fc = ConfigFile {
overrides,
..ConfigFile::default()
};
let resolved = base
.resolve_for_path("src/hot/inner.rs", Some(&fc))
.expect("override should not disable analysis");
assert_eq!(resolved.max_depth, 2, "most-specific override should win");
let resolved = base
.resolve_for_path("src/other.rs", Some(&fc))
.expect("override should not disable analysis");
assert_eq!(
resolved.max_depth, 10,
"only matching override should apply"
);
let resolved = base
.resolve_for_path("lib/foo.rs", Some(&fc))
.expect("no override, base config returned");
assert_eq!(
resolved.max_depth,
CheckConfig::default().max_depth,
"non-matching path gets base config"
);
}
#[test]
fn test_path_override_disabled_most_specific_wins() {
let base = CheckConfig::default();
let mut overrides = BTreeMap::new();
overrides.insert(
Box::from("tests/**"),
PathOverride {
max_depth: Some(10),
..PathOverride::default()
},
);
overrides.insert(
Box::from("tests/generated/**"),
PathOverride {
enabled: Some(false),
..PathOverride::default()
},
);
let fc = ConfigFile {
overrides,
..ConfigFile::default()
};
let resolved = base.resolve_for_path("tests/generated/foo.rs", Some(&fc));
assert!(
resolved.is_none(),
"most-specific override disables analysis"
);
let resolved = base
.resolve_for_path("tests/unit.rs", Some(&fc))
.expect("broad override applies");
assert_eq!(resolved.max_depth, 10);
}
#[test]
fn test_config_file_rejects_unknown_top_level_key() {
let error = toml::from_str::<ConfigFile>(
r#"
max_depth = 4
unknown_key = true
"#,
)
.expect_err("unknown top-level key should fail");
assert!(error.to_string().contains("unknown field `unknown_key`"));
}
#[test]
fn test_config_file_rejects_unknown_path_override_key() {
let error = toml::from_str::<ConfigFile>(
r#"
[overrides."src/**"]
max_depth = 4
unknown_key = true
"#,
)
.expect_err("unknown override key should fail");
assert!(error.to_string().contains("unknown field `unknown_key`"));
}
fn naming_config() -> CheckConfig {
CheckConfig {
check_naming: NamingCheck {
enabled: true,
..NamingCheck::default()
},
..permissive_config()
}
}
#[test]
fn test_generic_naming_detection() {
let source = include_str!("fixtures/generic_naming.rs");
let violations = analyze("generic_naming.rs", source, &naming_config(), None)
.unwrap()
.violations;
let naming_violations: Vec<_> = violations
.iter()
.filter(|v| matches!(v.violation_type, ViolationType::GenericNaming))
.collect();
assert_eq!(naming_violations.len(), 2);
}
#[test]
fn test_generic_naming_disabled() {
let source = include_str!("fixtures/generic_naming.rs");
let violations = analyze("generic_naming.rs", source, &permissive_config(), None)
.unwrap()
.violations;
assert!(
violations
.iter()
.all(|v| !matches!(v.violation_type, ViolationType::GenericNaming))
);
}
#[test]
fn test_generic_naming_custom_config() {
let source = include_str!("fixtures/generic_naming.rs");
let config = CheckConfig {
check_naming: NamingCheck {
enabled: true,
generic_names: vec![Arc::from("config")].into(),
min_generic_count: 1,
..NamingCheck::default()
},
..permissive_config()
};
let violations = analyze("generic_naming.rs", source, &config, None)
.unwrap()
.violations;
let naming_violations: Vec<_> = violations
.iter()
.filter(|v| matches!(v.violation_type, ViolationType::GenericNaming))
.collect();
assert!(
naming_violations
.iter()
.any(|v| v.message.contains("config"))
);
}