use std::fmt; use std::path::{Path, PathBuf}; const PARSE_ERROR_RULE: &str = "parse_error"; #[derive(Debug, Clone, PartialEq, Eq)] pub struct Diagnostic { pub path: PathBuf, pub rule: &'static str, pub message: String, pub line: usize, pub column: usize, } impl Diagnostic { pub(crate) fn at_offset( path: &Path, source: &str, rule: &'static str, message: String, offset: usize, ) -> Self { let (line, column) = line_column(source, offset); Self { path: path.to_path_buf(), rule, message, line, column, } } } impl fmt::Display for Diagnostic { fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { write!( formatter, "{}:{}:{}: {}: {}", self.path.display(), self.line, self.column, self.rule, self.message ) } } pub(crate) fn parse_error_diagnostics( path: &Path, source: &str, errors: &[rnix::ParseError], ) -> Vec { errors .iter() .map(|error| { Diagnostic::at_offset( path, source, PARSE_ERROR_RULE, error.to_string(), parse_error_offset(error, source.len()), ) }) .collect() } fn parse_error_offset(error: &rnix::ParseError, end_offset: usize) -> usize { let range = match error { rnix::ParseError::Unexpected(range) | rnix::ParseError::UnexpectedExtra(range) | rnix::ParseError::UnexpectedWanted(_, range, _) | rnix::ParseError::UnexpectedDoubleBind(range) | rnix::ParseError::DuplicatedArgs(range, _) => Some(range), rnix::ParseError::UnexpectedEOF | rnix::ParseError::UnexpectedEOFWanted(_) | rnix::ParseError::RecursionLimitExceeded => None, _ => None, }; range.map_or(end_offset, |range| usize::from(range.start())) } fn line_column(source: &str, offset: usize) -> (usize, usize) { let prefix = &source[..offset.min(source.len())]; let line = prefix.bytes().filter(|byte| *byte == b'\n').count() + 1; let line_start = prefix.rfind('\n').map_or(0, |index| index + 1); let column = prefix[line_start..].chars().count() + 1; (line, column) }