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 description(&self) -> &'static str {
23 "YAML frontmatter fails to parse."
24 }
25
26 fn fix_hint(&self) -> &'static str {
27 "Check indentation, quote strings containing colons, and align list items."
28 }
29
30 fn run(&self, doc: &ParsedDocument, _ctx: &RuleContext<'_>) -> Vec<Violation> {
31 let DocumentContent::Markdown(md) = &doc.content else {
32 return Vec::new();
33 };
34 let Some(fm) = md.frontmatter.as_ref() else {
35 return Vec::new();
36 };
37 let Err(err) = serde_yaml::from_str::<serde_yaml::Value>(&fm.raw) else {
38 return Vec::new();
39 };
40 let line = line_at_offset(&doc.raw, fm.byte_range.start);
41 let v = Violation::new(
42 AIL001,
43 self.default_severity(),
44 doc.path.clone(),
45 "invalid YAML frontmatter",
46 )
47 .at(line, 1)
48 .with_detail(err.to_string());
49 vec![v]
50 }
51}
52
53fn line_at_offset(raw: &str, offset: usize) -> usize {
54 let end = offset.min(raw.len());
55 raw.as_bytes()[..end]
56 .iter()
57 .filter(|&&b| b == b'\n')
58 .count()
59 + 1
60}