pub mod dom;
pub mod rules;
pub mod selector;
pub mod validator;
use rules::{AttrRule, ElmRule};
pub struct Rules {
pub valid: bool,
pub elm: Option<ElmRule>,
pub attr: Vec<AttrRule>,
}
impl Rules {
pub fn from_config(
valid: bool,
elm: &toml::Table,
attr: &[toml::Table],
) -> Result<Self, String> {
let elm = if elm.is_empty() {
None
} else {
Some(ElmRule::from_toml(elm)?)
};
let attr = attr
.iter()
.enumerate()
.map(|(i, t)| AttrRule::from_toml(t).map_err(|e| format!("{e} (attr entry {})", i + 1)))
.collect::<Result<Vec<_>, _>>()?;
Ok(Self { valid, elm, attr })
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct At {
pub line: usize,
pub column: usize,
pub name: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Report {
pub rule: String,
pub text: String,
pub at: Option<At>,
}
pub fn lint(source: &str, rules: &Rules) -> Result<Vec<Report>, String> {
let dom = dom::parse(source);
if dom.roots.is_empty() && !source.trim().is_empty() {
return Err("Unable to parse SVG".to_string());
}
let mut out = Vec::new();
let locate = |dom: &dom::Dom, node: Option<dom::NodeId>| {
node.map(|id| {
let (line, column) = dom.position(dom.node(id).start_index);
At {
line,
column,
name: dom.tag_name(id).unwrap_or("").to_string(),
}
})
};
for (i, rule) in rules.attr.iter().enumerate() {
let name = format!("attr-{}", i + 1);
for m in rule.run(&dom) {
out.push(Report {
rule: name.clone(),
text: m.text,
at: locate(&dom, m.node),
});
}
}
if let Some(elm) = &rules.elm {
for m in elm.run(&dom) {
out.push(Report {
rule: "elm".to_string(),
text: m.text,
at: locate(&dom, m.node),
});
}
}
if rules.valid
&& !source.is_empty()
&& let Some(text) = validator::validate(source)
{
out.push(Report {
rule: "valid".to_string(),
text,
at: None,
});
}
Ok(out)
}
#[cfg(test)]
mod tests;