use std::path::PathBuf;
use crate::linter::diagnostic::{Diagnostic, Severity};
use super::{BibRule, BibRuleContext};
pub struct DuplicateKey;
impl BibRule for DuplicateKey {
fn id(&self) -> &'static str {
"duplicate-key"
}
fn default_severity(&self) -> Severity {
Severity::Warning
}
fn check_file(&self, ctx: &BibRuleContext<'_>, sink: &mut Vec<Diagnostic>) {
for entry in ctx.model.duplicate_keys() {
sink.push(Diagnostic {
rule: self.id(),
severity: self.default_severity(),
path: PathBuf::new(),
start: usize::from(entry.key_range.start()),
end: usize::from(entry.key_range.end()),
message: format!("cite key `{}` is defined more than once", entry.key),
fix: None,
});
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::bib::parse;
use crate::bib::semantic::Model;
fn findings(src: &str) -> Vec<Diagnostic> {
let root = parse(src).syntax();
let model = Model::build(&root);
let ctx = BibRuleContext {
path: std::path::Path::new("x.bib"),
root: &root,
model: &model,
db: crate::bib::semantic::builtin(),
};
let mut out = Vec::new();
DuplicateKey.check_file(&ctx, &mut out);
out
}
#[test]
fn flags_only_the_second_definition() {
let out = findings("@misc{dup, t = {a}}\n@book{dup, t = {b}}\n");
assert_eq!(out.len(), 1);
assert_eq!(out[0].rule, "duplicate-key");
assert!(out[0].message.contains("dup"));
}
#[test]
fn flags_case_insensitively() {
let out = findings("@misc{Key, t = {a}}\n@book{key, t = {b}}\n");
assert_eq!(out.len(), 1);
assert_eq!(out[0].start, "@misc{Key, t = {a}}\n@book{".len());
}
#[test]
fn distinct_keys_are_fine() {
assert!(findings("@misc{a, t = {x}}\n@book{b, t = {y}}\n").is_empty());
}
#[test]
fn three_definitions_flag_two() {
assert_eq!(
findings("@misc{k, t = {a}}\n@book{k, t = {b}}\n@misc{k, t = {c}}\n").len(),
2
);
}
}