1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
use std::error::Error;
use std::fmt::Display;
use std::fmt::Formatter;
use std::fmt::Result as FmtResult;

use fastobo::ast::*;

use fastobo::visit::Visit;

use super::ValidationError;
use super::Validator;

#[derive(Debug)]
pub struct ObsoletionError {
    id: Ident,
    replaced_by: usize,
    consider: usize,
}

impl Display for ObsoletionError {
    fn fmt(&self, f: &mut Formatter) -> FmtResult {
        if self.replaced_by > 0 {
            write!(
                f,
                "non-obsolete entity contains {} `replaced_by` clause(s)",
                self.replaced_by
            )
        } else {
            write!(
                f,
                "non-obsolete entity contains {} `consider` clause(s)",
                self.consider
            )
        }
    }
}

impl Error for ObsoletionError {
    fn description(&self) -> &str {
        "non-obsolete frame contains obsoletion clauses"
    }
}

#[derive(Default)]
pub struct ObsoletionChecker {
    errors: Vec<ObsoletionError>,
}

macro_rules! impl_visit {
    ($name:ident, $frame:ty, $clause:ident) => {
        fn $name(&mut self, frame: &'a $frame) {
            let mut obsolete = false;
            let mut replaced_by: Vec<&'a Ident> = Vec::new();
            let mut consider: Vec<&'a Ident> = Vec::new();

            for clause in frame.clauses() {
                match clause.as_inner() {
                    $clause::IsObsolete(true) => obsolete = true,
                    $clause::ReplacedBy(id) => replaced_by.push(id.as_ref().as_ref()),
                    $clause::Consider(id) => consider.push(id.as_ref().as_ref()),
                    _ => (),
                }
            }

            if (replaced_by.len() > 0 || consider.len() > 1) && !obsolete {
                self.errors.push(ObsoletionError {
                    id: frame.id().as_inner().as_ref().as_ref().clone(),
                    replaced_by: replaced_by.len(),
                    consider: consider.len(),
                })
            }
        }
    };
}

impl<'a> Visit<'a> for ObsoletionChecker {
    impl_visit!(visit_term_frame, TermFrame, TermClause);
    impl_visit!(visit_typedef_frame, TypedefFrame, TypedefClause);
    impl_visit!(visit_instance_frame, InstanceFrame, InstanceClause);
}

impl Validator for ObsoletionChecker {
    fn validate(doc: &OboDoc) -> Vec<ValidationError> {
        let mut checker = Self::default();
        checker.visit_doc(doc);

        let mut errors = Vec::new();
        for err in checker.errors {
            errors.push(ValidationError {
                location: format!("frame {}", err.id),
                cause: Box::new(err),
            })
        }

        errors
    }
}