use std::ops::Range;
use std::path::PathBuf;
use serde::{Deserialize, Serialize};
use crate::rule::Rule;
use crate::span::{Locator, Position, Ranged, Span};
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
pub struct Diagnostic<L = Span> {
pub rule: Rule,
pub location: Option<L>,
pub path: Option<PathBuf>,
}
impl Diagnostic {
pub fn new(rule: Rule, location: Option<Span>) -> Self {
Self {
rule,
location,
path: None,
}
}
}
impl<L> Diagnostic<L> {
pub fn code(&self) -> &str {
self.rule.code()
}
}
impl<Span: Ranged<usize>> Diagnostic<Span> {
pub fn locate(self, locator: &Locator) -> Diagnostic<Position> {
Diagnostic {
rule: self.rule,
location: self.location.map(|s| locator.position(&s.range())),
path: self.path,
}
}
}
impl<L: Ranged<usize>> Diagnostic<L> {
pub fn message(&self, source: &str) -> String {
let range = self.location.as_ref().map(|l| l.range());
match range {
Some(range) => {
let snippet = &source[range];
self.rule.message().replace("{}", snippet)
}
None => self.rule.message().to_string(),
}
}
pub fn range(&self) -> Option<Range<usize>> {
Some(self.location.as_ref()?.range())
}
pub(crate) fn position(&self, locator: &Locator) -> Option<Position> {
self.location.as_ref().map(|l| locator.position(&l.range()))
}
}
impl Diagnostic<Position> {
pub fn line(&self) -> Option<usize> {
self.location.map(|p| p.start.line)
}
pub fn column(&self) -> Option<usize> {
self.location.map(|p| p.start.column)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_new() {
assert_eq!(
Diagnostic::new(Rule::MissingTitle, None),
Diagnostic {
rule: Rule::MissingTitle,
location: None,
path: None
}
);
assert_eq!(
Diagnostic::new(Rule::MissingTitle, Some(Span::default())),
Diagnostic {
rule: Rule::MissingTitle,
location: Some(Span::default()),
path: None
}
);
}
#[test]
fn test_code() {
assert_eq!(
Diagnostic::new(Rule::MissingTitle, None).code(),
Rule::MissingTitle.code()
);
}
#[test]
fn test_message() {
let source = "";
let diagnostic = Diagnostic::new(Rule::MissingTitle, None);
assert_eq!(diagnostic.message(source), Rule::MissingTitle.message());
let source = "# Changelog";
let diagnostic = Diagnostic::new(Rule::DuplicateTitle, Some(Span::new(2, 11)));
assert_eq!(
diagnostic.message(source),
Rule::DuplicateTitle.message().replace("{}", "Changelog")
);
}
}