code_moniker_check/scenario/
expect.rs1use std::fmt;
2
3#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
4pub struct ExpectedViolation {
5 pub path: String,
6 pub lines: (u32, u32),
7 pub rule_id: String,
8}
9
10impl ExpectedViolation {
11 pub fn parse(line: &str) -> Result<Self, String> {
12 parse_expected(line)
13 }
14}
15
16impl fmt::Display for ExpectedViolation {
17 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
18 write!(
19 f,
20 "{} @ {}:{}",
21 self.rule_id,
22 self.path,
23 format_span(self.lines)
24 )
25 }
26}
27
28fn format_span((start, end): (u32, u32)) -> String {
29 if start == end {
30 format!("L{start}")
31 } else {
32 format!("L{start}-L{end}")
33 }
34}
35
36fn parse_expected(line: &str) -> Result<ExpectedViolation, String> {
37 let (rule_id, location) = line
38 .split_once('@')
39 .ok_or_else(|| expectation_syntax(line))?;
40 let rule_id = rule_id.trim();
41 let (path, span) = location
42 .trim()
43 .rsplit_once(':')
44 .ok_or_else(|| expectation_syntax(line))?;
45 if rule_id.is_empty() || path.is_empty() {
46 return Err(expectation_syntax(line));
47 }
48 Ok(ExpectedViolation {
49 rule_id: rule_id.to_string(),
50 path: path.to_string(),
51 lines: parse_span(span).ok_or_else(|| expectation_syntax(line))?,
52 })
53}
54
55fn parse_span(span: &str) -> Option<(u32, u32)> {
56 let span = span.strip_prefix('L')?;
57 match span.split_once("-L") {
58 Some((start, end)) => Some((start.parse().ok()?, end.parse().ok()?)),
59 None => {
60 let line = span.parse().ok()?;
61 Some((line, line))
62 }
63 }
64}
65
66fn expectation_syntax(line: &str) -> String {
67 format!("expected `<rule-id> @ <path>:L<start>[-L<end>]`, got `{line}`")
68}