Skip to main content

ailint_core/rules/structural/
frontmatter_schema.rs

1//! AIL001 `no-frontmatter-schema-error` — YAML frontmatter fails to parse.
2//!
3//! See: `docs/rules/structural/AIL001.md`
4
5use crate::parser::{DocumentContent, ParsedDocument};
6use crate::rules::structural::AIL001;
7use crate::rules::{Rule, RuleContext, RuleId, Severity, Violation};
8
9/// AIL001 no-frontmatter-schema-error: frontmatter must match the file type's schema.
10#[derive(Debug, Default)]
11pub struct FrontmatterSchemaRule;
12
13impl Rule for FrontmatterSchemaRule {
14    fn id(&self) -> RuleId {
15        AIL001
16    }
17
18    fn default_severity(&self) -> Severity {
19        Severity::Error
20    }
21
22    fn run(&self, doc: &ParsedDocument, _ctx: &RuleContext<'_>) -> Vec<Violation> {
23        let DocumentContent::Markdown(md) = &doc.content else {
24            return Vec::new();
25        };
26        let Some(fm) = md.frontmatter.as_ref() else {
27            return Vec::new();
28        };
29        let Err(err) = serde_yaml::from_str::<serde_yaml::Value>(&fm.raw) else {
30            return Vec::new();
31        };
32        let line = line_at_offset(&doc.raw, fm.byte_range.start);
33        let mut v = Violation::new(
34            AIL001,
35            self.default_severity(),
36            doc.path.clone(),
37            format!("invalid YAML frontmatter: {err}"),
38        )
39        .at(line, 1);
40        v.fix_hint = Some(
41            "check indentation, quote strings containing colons, and ensure list items are properly aligned"
42                .into(),
43        );
44        vec![v]
45    }
46}
47
48fn line_at_offset(raw: &str, offset: usize) -> usize {
49    let end = offset.min(raw.len());
50    raw.as_bytes()[..end]
51        .iter()
52        .filter(|&&b| b == b'\n')
53        .count()
54        + 1
55}