ailint_core/rules/semantic/
excessive_length.rs1use serde::Deserialize;
6
7use crate::parser::{DocumentContent, ParsedDocument};
8use crate::rules::semantic::AIL102;
9use crate::rules::{Rule, RuleContext, RuleId, Severity, Violation};
10
11const DEFAULT_MAX_WORDS: usize = 60;
12
13#[derive(Debug, Default, Deserialize)]
14#[serde(default, deny_unknown_fields)]
15struct Options {
16 max_words: Option<usize>,
17}
18
19#[derive(Debug, Default)]
21pub struct ExcessiveRuleLengthRule;
22
23impl Rule for ExcessiveRuleLengthRule {
24 fn id(&self) -> RuleId {
25 AIL102
26 }
27
28 fn default_severity(&self) -> Severity {
29 Severity::Warning
30 }
31
32 fn run(&self, doc: &ParsedDocument, ctx: &RuleContext<'_>) -> Vec<Violation> {
33 let md = match &doc.content {
34 DocumentContent::Markdown(m) => m,
35 _ => return Vec::new(),
36 };
37 let opts: Options = ctx
38 .options
39 .and_then(|v| serde_yaml::from_value(v.clone()).ok())
40 .unwrap_or_default();
41 let max_words = opts.max_words.unwrap_or(DEFAULT_MAX_WORDS);
42
43 let mut out = Vec::new();
44 for item in &md.list_items {
45 let count = item.text.split_whitespace().count();
46 if count <= max_words {
47 continue;
48 }
49 let mut v = Violation::new(
50 AIL102,
51 self.default_severity(),
52 doc.path.clone(),
53 format!("rule exceeds {} words ({} words)", max_words, count),
54 )
55 .at(item.line, 1);
56 v.fix_hint =
57 Some("split into multiple smaller rules or move detail to a sub-list".into());
58 out.push(v);
59 }
60 out
61 }
62}