code-moniker-check 0.6.0

Rules engine for code-moniker: DSL, rule config/profiles, evaluation over the symbol graph, and suppression.
Documentation
use std::fmt;

#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
pub struct ExpectedViolation {
	pub path: String,
	pub lines: (u32, u32),
	pub rule_id: String,
}

impl ExpectedViolation {
	pub fn parse(line: &str) -> Result<Self, String> {
		parse_expected(line)
	}
}

impl fmt::Display for ExpectedViolation {
	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
		write!(
			f,
			"{} @ {}:{}",
			self.rule_id,
			self.path,
			format_span(self.lines)
		)
	}
}

fn format_span((start, end): (u32, u32)) -> String {
	if start == end {
		format!("L{start}")
	} else {
		format!("L{start}-L{end}")
	}
}

fn parse_expected(line: &str) -> Result<ExpectedViolation, String> {
	let (rule_id, location) = line
		.split_once('@')
		.ok_or_else(|| expectation_syntax(line))?;
	let rule_id = rule_id.trim();
	let (path, span) = location
		.trim()
		.rsplit_once(':')
		.ok_or_else(|| expectation_syntax(line))?;
	if rule_id.is_empty() || path.is_empty() {
		return Err(expectation_syntax(line));
	}
	Ok(ExpectedViolation {
		rule_id: rule_id.to_string(),
		path: path.to_string(),
		lines: parse_span(span).ok_or_else(|| expectation_syntax(line))?,
	})
}

fn parse_span(span: &str) -> Option<(u32, u32)> {
	let span = span.strip_prefix('L')?;
	match span.split_once("-L") {
		Some((start, end)) => Some((start.parse().ok()?, end.parse().ok()?)),
		None => {
			let line = span.parse().ok()?;
			Some((line, line))
		}
	}
}

fn expectation_syntax(line: &str) -> String {
	format!("expected `<rule-id> @ <path>:L<start>[-L<end>]`, got `{line}`")
}