wowlab_tidy/languages/rust/rules/complexity/
too_many_lines_in_file.rs1#[cfg(test)]
2use googletest::prelude::*;
3
4use crate::{Example, FileCtx, Violation, violation};
5
6const PERCENT_DENOMINATOR: usize = 100;
7
8#[rustfmt::skip]
9const EXAMPLES: &[Example] = &[];
10
11crate::line_rule!(
12 too_many_lines_in_file,
13 "Flag files exceeding threshold lines.",
14 "Files over 1500 lines are a sign that the module has too many responsibilities and should be split.",
15 Medium,
16 params {
17 threshold: i64 = 1500,
18 slack_percent: i64 = 20
19 },
20);
21
22fn check_too_many_lines_in_file(ctx: &FileCtx<'_>) -> Vec<Violation> {
23 let max_lines = ctx
24 .config
25 .get_usize("rust_too_many_lines_in_file", &PARAMS[0]);
26 let slack_percent = ctx
27 .config
28 .get_usize("rust_too_many_lines_in_file", &PARAMS[1]);
29 let budget = ctx.lines.iter().find_map(|line| {
30 let crate::infra::parse::DirectiveResult::Valid(directive) =
31 crate::infra::parse::directive(line)?
32 else {
33 return None;
34 };
35
36 (directive.scope == crate::infra::parse::Scope::File)
37 .then(|| directive.values.get("rust_too_many_lines_in_file").copied())
38 .flatten()
39 });
40 let allowed = budget.map_or(max_lines, |budget| {
41 budget.saturating_mul(PERCENT_DENOMINATOR + slack_percent) / PERCENT_DENOMINATOR
42 });
43
44 if ctx.lines.len() > allowed {
45 let message = budget.map_or_else(
46 || {
47 format!(
48 "file has {} lines (max {max_lines}), consider splitting into smaller modules",
49 ctx.lines.len()
50 )
51 },
52 |budget| {
53 format!(
54 "file has {} lines, exceeding declared budget {budget} with {slack_percent}% slack (max {allowed})",
55 ctx.lines.len()
56 )
57 },
58 );
59
60 vec![violation(ctx.rel, 1, message)]
61 } else {
62 Vec::new()
63 }
64}
65
66crate::tidy_test!(check_too_many_lines_in_file, {
67 #[gtest]
68 fn too_many_lines_fails() -> Result<()> {
69 let source = (0..1501)
70 .map(|i| format!("// line {i}"))
71 .collect::<Vec<_>>()
72 .join("\n");
73 let v = run(&source);
74 verify_eq!(v.len(), 1)?;
75 verify_eq!(v[0].line, 1)?;
76 verify_true!(v[0].message.contains("1501 lines"))?;
77
78 Ok(())
79 }
80
81 #[gtest]
82 fn exactly_max_passes() -> Result<()> {
83 let source = (0..1500)
84 .map(|i| format!("// line {i}"))
85 .collect::<Vec<_>>()
86 .join("\n");
87 verify_true!(run(&source).is_empty())?;
88
89 Ok(())
90 }
91
92 #[gtest]
93 fn declared_budget_allows_slack() -> Result<()> {
94 let mut lines =
95 vec!["// #t(file: rust_too_many_lines_in_file = 1300) cohesive table".to_owned()];
96 lines.extend((1..=1559).map(|i| format!("// line {i}")));
97 verify_true!(run(&lines.join("\n")).is_empty())?;
98
99 Ok(())
100 }
101
102 #[gtest]
103 fn declared_budget_reports_growth_beyond_slack() -> Result<()> {
104 let mut lines =
105 vec!["// #t(file: rust_too_many_lines_in_file = 1300) cohesive table".to_owned()];
106 lines.extend((1..=1560).map(|i| format!("// line {i}")));
107 let violations = run(&lines.join("\n"));
108 verify_eq!(violations.len(), 1)?;
109 verify_true!(violations[0].message.contains("declared budget 1300"))?;
110
111 Ok(())
112 }
113});