Skip to main content

ailint_core/rules/semantic/
excessive_length.rs

1//! AIL102 `excessive-rule-length` — list items exceeding a word cap.
2//!
3//! See: `docs/rules/semantic/AIL102.md`
4
5use 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/// AIL102 excessive-rule-length: flags documents past the context-length budget.
20#[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 description(&self) -> &'static str {
33        "Rule item is longer than the configured word budget."
34    }
35
36    fn fix_hint(&self) -> &'static str {
37        "Split into smaller focused rules, or move detail to a sub-list."
38    }
39
40    fn run(&self, doc: &ParsedDocument, ctx: &RuleContext<'_>) -> Vec<Violation> {
41        let md = match &doc.content {
42            DocumentContent::Markdown(m) => m,
43            _ => return Vec::new(),
44        };
45        let opts: Options = ctx
46            .options
47            .and_then(|v| serde_yaml::from_value(v.clone()).ok())
48            .unwrap_or_default();
49        let max_words = opts.max_words.unwrap_or(DEFAULT_MAX_WORDS);
50
51        let mut out = Vec::new();
52        for item in &md.list_items {
53            let count = item.text.split_whitespace().count();
54            if count <= max_words {
55                continue;
56            }
57            let v = Violation::new(
58                AIL102,
59                self.default_severity(),
60                doc.path.clone(),
61                format!("rule exceeds {} words", max_words),
62            )
63            .at(item.line, 1)
64            .with_detail(format!("{} words", count));
65            out.push(v);
66        }
67        out
68    }
69}