use std::collections::HashMap;
use scraps_libs::markdown::query::wikilinks;
use scraps_libs::model::{key::ScrapKey, scrap::Scrap, tags::Tags};
use crate::usecase::build::model::backlinks_map::BacklinksMap;
use crate::usecase::lint::rule::{scrap_relative_path, LintRule, LintRuleName, LintWarning};
pub struct OverlinkingRule;
impl LintRule for OverlinkingRule {
fn name(&self) -> LintRuleName {
LintRuleName::Overlinking
}
fn check(
&self,
scraps: &[Scrap],
_backlinks_map: &BacklinksMap,
_tags: &Tags,
) -> Vec<LintWarning> {
scraps
.iter()
.flat_map(|scrap| {
let all_links: Vec<ScrapKey> = wikilinks(scrap.md_text())
.iter()
.map(ScrapKey::from)
.collect();
let mut counts: HashMap<ScrapKey, usize> = HashMap::new();
for link in &all_links {
*counts.entry(link.clone()).or_insert(0) += 1;
}
counts
.into_iter()
.filter(|(_, count)| *count > 1)
.map(|(key, count)| {
let pattern = format!("[[{}]]", key);
let source = scrap.md_text().to_string();
let span = source.find(&pattern).map(|s| (s, s + pattern.len()));
LintWarning {
rule_name: self.name(),
scrap_path: scrap_relative_path(scrap),
message: format!("link [[{}]] appears {} times", key, count),
source: Some(source),
span,
}
})
.collect::<Vec<_>>()
})
.collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn detect_duplicate_link() {
let scrap = Scrap::new("test", &None, "[[a]] text [[a]]");
let scraps = vec![scrap];
let backlinks_map = BacklinksMap::new(&scraps);
let tags = Tags::new(&scraps);
let warnings = OverlinkingRule.check(&scraps, &backlinks_map, &tags);
assert_eq!(warnings.len(), 1);
assert_eq!(warnings[0].rule_name, LintRuleName::Overlinking);
assert!(warnings[0].message.contains("2 times"));
}
#[test]
fn detect_triple_link_as_one_warning() {
let scrap = Scrap::new("test", &None, "[[a]] [[a]] [[a]]");
let scraps = vec![scrap];
let backlinks_map = BacklinksMap::new(&scraps);
let tags = Tags::new(&scraps);
let warnings = OverlinkingRule.check(&scraps, &backlinks_map, &tags);
assert_eq!(warnings.len(), 1);
assert!(warnings[0].message.contains("3 times"));
}
#[test]
fn alias_and_plain_same_link() {
let scrap = Scrap::new(
"test",
&None,
"[[Domain Driven Design|DDD]] [[Domain Driven Design]]",
);
let scraps = vec![scrap];
let backlinks_map = BacklinksMap::new(&scraps);
let tags = Tags::new(&scraps);
let warnings = OverlinkingRule.check(&scraps, &backlinks_map, &tags);
assert_eq!(warnings.len(), 1);
assert!(warnings[0].message.contains("2 times"));
}
#[test]
fn skip_all_unique_links() {
let scrap = Scrap::new("test", &None, "[[a]] [[b]] [[c]]");
let scraps = vec![scrap];
let backlinks_map = BacklinksMap::new(&scraps);
let tags = Tags::new(&scraps);
let warnings = OverlinkingRule.check(&scraps, &backlinks_map, &tags);
assert!(warnings.is_empty());
}
}