mod config;
mod context;
mod craft;
mod defaults;
mod language;
mod lexicon;
mod similarity;
mod structural;
mod text;
mod v2;
use std::cmp::Ordering;
use std::fmt;
pub use config::{LintConfig, Similarity};
pub use defaults::defaults_text;
use crate::codec::{Site, Sites};
use crate::document::Document;
use crate::identity::Key;
use crate::wire::wire_names;
use self::context::Context;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum RuleId {
Sh001,
Sh002,
Sh003,
Sh004,
Sh005,
Sh006,
Sh007,
Sh008,
Sh009,
Sh010,
Lq001,
Lq002,
Lq003,
Lq004,
Lq005,
Lq006,
Lq007,
Lq008,
Lq009,
V2001,
V2002,
V2003,
V2004,
V2005,
V2006,
V2007,
V2008,
}
wire_names!(RuleId {
Sh001 => "SH-001",
Sh002 => "SH-002",
Sh003 => "SH-003",
Sh004 => "SH-004",
Sh005 => "SH-005",
Sh006 => "SH-006",
Sh007 => "SH-007",
Sh008 => "SH-008",
Sh009 => "SH-009",
Sh010 => "SH-010",
Lq001 => "LQ-001",
Lq002 => "LQ-002",
Lq003 => "LQ-003",
Lq004 => "LQ-004",
Lq005 => "LQ-005",
Lq006 => "LQ-006",
Lq007 => "LQ-007",
Lq008 => "LQ-008",
Lq009 => "LQ-009",
V2001 => "V2-001",
V2002 => "V2-002",
V2003 => "V2-003",
V2004 => "V2-004",
V2005 => "V2-005",
V2006 => "V2-006",
V2007 => "V2-007",
V2008 => "V2-008",
});
impl fmt::Display for RuleId {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(self.as_str())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Severity {
Hint,
Info,
Warning,
}
wire_names!(Severity {
Hint => "hint",
Info => "info",
Warning => "warning",
});
impl fmt::Display for Severity {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(self.as_str())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Subject {
pub key: Key,
pub site: Option<Site>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Finding {
pub rule: RuleId,
pub severity: Severity,
pub subjects: Vec<Subject>,
pub term: Option<String>,
pub body_line: Option<usize>,
pub message: String,
}
impl Finding {
fn order(&self, other: &Self) -> Ordering {
let site = |finding: &Self| finding.subjects.first().and_then(|s| s.site.clone());
let keys = |finding: &Self| finding.subjects.iter().map(|s| s.key).collect::<Vec<_>>();
site(self)
.cmp(&site(other))
.then(self.rule.cmp(&other.rule))
.then_with(|| keys(self).cmp(&keys(other)))
.then(self.body_line.cmp(&other.body_line))
.then_with(|| self.term.cmp(&other.term))
}
}
impl fmt::Display for Finding {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
for subject in &self.subjects {
match &subject.site {
Some(site) => write!(formatter, "{site}: ")?,
None => write!(formatter, "{}: ", subject.key)?,
}
}
write!(
formatter,
"{} {}: {}",
self.severity, self.rule, self.message
)?;
if let Some(term) = &self.term {
write!(formatter, " `{term}`")?;
}
if let Some(line) = self.body_line {
write!(formatter, " (body line {line})")?;
}
Ok(())
}
}
pub(crate) struct Raw {
pub rule: RuleId,
pub keys: Vec<Key>,
pub term: Option<String>,
pub body_line: Option<usize>,
pub message: String,
}
impl Raw {
pub(crate) fn new(rule: RuleId, keys: Vec<Key>, message: impl Into<String>) -> Self {
Self {
rule,
keys,
term: None,
body_line: None,
message: message.into(),
}
}
pub(crate) fn term(mut self, term: impl Into<String>) -> Self {
self.term = Some(term.into());
self
}
pub(crate) const fn line(mut self, body_line: Option<usize>) -> Self {
self.body_line = body_line;
self
}
}
#[must_use]
pub fn lint(document: &Document, sites: &Sites, config: &LintConfig) -> Vec<Finding> {
let context = Context::new(document);
let mut raw = Vec::new();
structural::check(&context, &mut raw);
language::check(&context, config, &mut raw);
v2::check(&context, &mut raw);
craft::check(&context, config, &mut raw);
let mut findings: Vec<Finding> = raw
.into_iter()
.filter(|finding| config.enabled(finding.rule))
.filter(|finding| !config.suppresses(finding.rule, &finding.keys))
.map(|raw| Finding {
rule: raw.rule,
severity: config.severity(raw.rule),
subjects: raw
.keys
.iter()
.map(|key| Subject {
key: *key,
site: sites.get(*key).cloned(),
})
.collect(),
term: raw.term,
body_line: raw.body_line,
message: raw.message,
})
.collect();
findings.sort_by(Finding::order);
findings
}