92 lines
2.3 KiB
Rust
92 lines
2.3 KiB
Rust
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<Diagnostic> {
|
|
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)
|
|
}
|