use std::path::Path;
use crate::bib::parse;
use crate::bib::semantic::{self, Model};
use crate::bib::syntax::{SyntaxKind, SyntaxNode};
use crate::linter::diagnostic::{Diagnostic, Severity};
use super::rules::{BibRuleContext, all_rules};
use super::suppression::BibSuppressionMap;
pub fn check_document(path: &Path, text: &str) -> Vec<Diagnostic> {
let parsed = parse(text);
let mut diagnostics: Vec<Diagnostic> = parsed
.errors
.iter()
.map(|err| Diagnostic {
rule: "parse",
severity: Severity::Error,
path: path.to_path_buf(),
start: err.start,
end: err.end,
message: err.message.clone(),
fix: None,
})
.collect();
let root = parsed.syntax();
let model = Model::build(&root);
diagnostics.extend(lint_document(path, &root, &model));
diagnostics
}
pub fn lint_document(path: &Path, root: &SyntaxNode, model: &Model) -> Vec<Diagnostic> {
let ctx = BibRuleContext {
path,
root,
model,
db: semantic::builtin(),
};
let rules = all_rules();
let mut diagnostics: Vec<Diagnostic> = Vec::new();
let mut by_kind: Vec<Vec<usize>> = vec![Vec::new(); SyntaxKind::COUNT];
let mut any_node_rules = false;
for (i, rule) in rules.iter().enumerate() {
for kind in rule.interests() {
by_kind[*kind as usize].push(i);
any_node_rules = true;
}
}
if any_node_rules {
for el in root.descendants_with_tokens() {
for &i in &by_kind[el.kind() as usize] {
rules[i].check(&el, &ctx, &mut diagnostics);
}
}
}
for rule in &rules {
rule.check_file(&ctx, &mut diagnostics);
}
let suppress = BibSuppressionMap::build(root);
diagnostics.retain(|d| !suppress.is_suppressed(d.rule, d.start, d.end));
for d in &mut diagnostics {
d.path = path.to_path_buf();
}
diagnostics.sort_by_key(|d| (d.start, d.end, d.rule));
diagnostics
}
#[cfg(test)]
mod tests {
use super::*;
fn rules_of(src: &str) -> Vec<&'static str> {
check_document(Path::new("x.bib"), src)
.iter()
.map(|d| d.rule)
.collect()
}
#[test]
fn clean_file_produces_nothing() {
let src = "@article{k,\n author = {A},\n title = {T},\n journaltitle = {J},\n year = 2020,\n}\n";
assert!(rules_of(src).is_empty(), "got: {:?}", rules_of(src));
}
#[test]
fn collects_multiple_rule_families_sorted() {
let src = "@string{unused = {U}}\n@misc{k, title = {A}}\n@misc{k, title = {B}}\n";
let rules = rules_of(src);
assert!(rules.contains(&"unused-string"));
assert!(rules.contains(&"duplicate-key"));
}
}