litcheck_filecheck/rules/
empty.rs

1use crate::common::*;
2
3#[derive(Debug)]
4pub struct CheckEmpty {
5    span: SourceSpan,
6}
7impl CheckEmpty {
8    pub fn new(span: SourceSpan) -> Self {
9        Self { span }
10    }
11}
12impl Spanned for CheckEmpty {
13    fn span(&self) -> SourceSpan {
14        self.span
15    }
16}
17impl Rule for CheckEmpty {
18    fn kind(&self) -> Check {
19        Check::Empty
20    }
21
22    fn apply<'input, 'context, C>(&self, context: &mut C) -> DiagResult<Matches<'input>>
23    where
24        C: Context<'input, 'context> + ?Sized,
25    {
26        // Next line must be empty, meaning the start of the line is the end of the line
27        let cursor = context.cursor_mut();
28        let next_line_start = cursor.start_of_next_line();
29        let next_eol = cursor
30            .next_newline_from(next_line_start)
31            .unwrap_or_else(|| cursor.end_of_file());
32        let next_line = next_line_start..next_eol;
33        if cursor.at_end_of_line() && next_line.is_empty() {
34            let span = SourceSpan::from(next_line_start..next_eol);
35            // Move to the end of the empty line
36            cursor.set_start(next_eol);
37            Ok(MatchResult {
38                ty: MatchType::MatchFoundAndExpected,
39                info: Some(MatchInfo::new(span, self.span)),
40            }
41            .into())
42        } else {
43            Ok(MatchResult {
44                ty: MatchType::Failed(CheckFailedError::MatchNoneButExpected {
45                    span: self.span,
46                    match_file: context.match_file(),
47                    note: None,
48                }),
49                info: None,
50            }
51            .into())
52        }
53    }
54}
55
56#[cfg(test)]
57mod tests {
58    use super::*;
59
60    #[test]
61    fn check_empty_test() -> DiagResult<()> {
62        let mut context = TestContext::new();
63        context.with_checks("CHECK-EMPTY:").with_input(
64            "
65abc
66
67
68def",
69        );
70        let mut mctx = context.match_context();
71        let rule = CheckEmpty::new(SourceSpan::from(0..12));
72        let matches = rule
73            .apply(&mut mctx)
74            .expect("expected non-fatal application of rule");
75        let test_result = TestResult::from_matches(matches, &mctx);
76        assert!(test_result.is_failed());
77        match test_result.errors() {
78            [CheckFailedError::MatchNoneButExpected { .. }] => (),
79            _ => return test_result.into_result().map(|_| ()).map_err(Report::new),
80        }
81
82        // Move to the 'abc' line
83        mctx.cursor_mut().set_start(5);
84
85        let matches = rule
86            .apply(&mut mctx)
87            .expect("expected non-fatal application of rule");
88        let test_result = TestResult::from_matches(matches, &mctx);
89        assert!(test_result.is_ok());
90        assert_eq!(test_result.num_matched(), 1);
91
92        let matched = test_result.into_result()?;
93        assert_eq!(matched[0].span.offset(), 6);
94        let buffer = mctx.cursor().buffer();
95        assert_eq!(buffer[5], b'\n');
96        assert_eq!(buffer[6], b'\n');
97        assert_eq!(buffer[7], b'd');
98        assert_eq!(matched[0].span.len(), 0);
99
100        Ok(())
101    }
102}