ailint_core/rules/structural/
frontmatter_schema.rs1use crate::parser::{DocumentContent, ParsedDocument};
6use crate::rules::structural::AIL001;
7use crate::rules::{Rule, RuleContext, RuleId, Severity, Violation};
8
9#[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}