grass/src/error.rs

172 lines
4.7 KiB
Rust
Raw Normal View History

2020-06-16 20:00:11 -04:00
use std::{
error::Error,
fmt::{self, Display},
io,
rc::Rc,
2020-06-16 20:00:11 -04:00
string::FromUtf8Error,
};
2020-04-12 19:37:12 -04:00
use codemap::{Span, SpanLoc};
2020-03-01 14:53:52 -05:00
2020-06-26 05:12:28 -04:00
pub type SassResult<T> = Result<T, Box<SassError>>;
2020-07-06 10:24:29 -04:00
/// `SassError`s can be either a structured error
/// specific to `grass` or an `io::Error`.
///
/// In the former case, the best way to interact with
/// the error is to simply print it to the user. The
/// `Display` implementation of this kind of error
/// mirrors that of the errors `dart-sass` emits, e.g.
///```scss
/// Error: $number: foo is not a number.
/// |
/// 308 | color: unit(foo);
/// | ^^^
/// |
/// ./input.scss:308:17
///```
///
/// The file name, line number, and column are structured in
/// such a way as to allow Visual Studio Code users to go
/// directly to the error by simply clicking the file name.
///
/// Note that this is a deviation from the Sass specification.
#[derive(Debug, Clone)]
2020-01-06 18:26:07 -05:00
pub struct SassError {
2020-04-12 19:37:12 -04:00
kind: SassErrorKind,
2020-01-06 18:26:07 -05:00
}
impl SassError {
2020-04-12 19:37:12 -04:00
pub(crate) fn raw(self) -> (String, Span) {
match self.kind {
SassErrorKind::Raw(string, span) => (string, span),
2020-04-24 22:57:39 -04:00
e => todo!("unable to get raw of {:?}", e),
2020-04-12 19:37:12 -04:00
}
}
pub(crate) const fn from_loc(message: String, loc: SpanLoc, unicode: bool) -> Self {
2020-01-06 19:23:52 -05:00
SassError {
kind: SassErrorKind::ParseError {
message,
loc,
unicode,
},
2020-01-06 19:23:52 -05:00
}
2020-01-06 18:26:07 -05:00
}
}
#[derive(Debug, Clone)]
2020-04-12 19:37:12 -04:00
enum SassErrorKind {
/// A raw error with no additional metadata
/// It contains only a `String` message and
/// a span
Raw(String, Span),
ParseError {
message: String,
loc: SpanLoc,
unicode: bool,
2020-04-12 19:37:12 -04:00
},
// we put IoErrors in an `Rc` to allow it to be
// cloneable
IoError(Rc<io::Error>),
2020-04-12 19:37:12 -04:00
FromUtf8Error(String),
}
2020-01-06 18:26:07 -05:00
impl Display for SassError {
2020-04-14 22:40:19 -04:00
// TODO: trim whitespace from start of line shown in error
2020-04-12 19:37:12 -04:00
// TODO: color errors
// TODO: integrate with codemap-diagnostics
2020-04-21 18:22:26 -04:00
#[inline]
2020-01-06 18:26:07 -05:00
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let (message, loc, unicode) = match &self.kind {
SassErrorKind::ParseError {
message,
loc,
unicode,
} => (message, loc, *unicode),
2020-04-23 11:33:42 -04:00
SassErrorKind::FromUtf8Error(s) => return writeln!(f, "Error: {}", s),
SassErrorKind::IoError(s) => return writeln!(f, "Error: {}", s),
SassErrorKind::Raw(..) => todo!(),
2020-04-12 19:37:12 -04:00
};
let first_bar = if unicode { '╷' } else { '|' };
let second_bar = if unicode { '│' } else { '|' };
let third_bar = if unicode { '│' } else { '|' };
let fourth_bar = if unicode { '╵' } else { '|' };
2020-04-12 19:37:12 -04:00
let line = loc.begin.line + 1;
let col = loc.begin.column + 1;
writeln!(f, "Error: {}", message)?;
let padding = vec![' '; format!("{}", line).len() + 1]
.iter()
.collect::<String>();
writeln!(f, "{}{}", padding, first_bar)?;
writeln!(
f,
"{} {} {}",
line,
second_bar,
loc.file.source_line(loc.begin.line)
)?;
2020-04-12 19:37:12 -04:00
writeln!(
f,
"{}{} {}{}",
2020-04-12 19:37:12 -04:00
padding,
third_bar,
2020-04-12 19:37:12 -04:00
vec![' '; loc.begin.column].iter().collect::<String>(),
vec!['^'; loc.end.column.max(loc.begin.column) - loc.begin.column.min(loc.end.column)]
2020-04-12 19:37:12 -04:00
.iter()
.collect::<String>()
)?;
writeln!(f, "{}{}", padding, fourth_bar)?;
2020-04-12 19:37:12 -04:00
writeln!(f, "./{}:{}:{}", loc.file.name(), line, col)?;
Ok(())
2020-01-06 18:26:07 -05:00
}
}
2020-06-26 05:12:28 -04:00
impl From<io::Error> for Box<SassError> {
2020-04-21 18:22:26 -04:00
#[inline]
2020-06-26 05:12:28 -04:00
fn from(error: io::Error) -> Box<SassError> {
Box::new(SassError {
kind: SassErrorKind::IoError(Rc::new(error)),
2020-06-26 05:12:28 -04:00
})
2020-01-06 18:26:07 -05:00
}
}
2020-06-26 05:12:28 -04:00
impl From<FromUtf8Error> for Box<SassError> {
2020-04-21 18:22:26 -04:00
#[inline]
2020-06-26 05:12:28 -04:00
fn from(error: FromUtf8Error) -> Box<SassError> {
Box::new(SassError {
2020-04-12 19:37:12 -04:00
kind: SassErrorKind::FromUtf8Error(format!(
"Invalid UTF-8 character \"\\x{:X?}\"",
error.as_bytes()[0]
)),
2020-06-26 05:12:28 -04:00
})
}
}
2020-06-26 05:12:28 -04:00
impl From<(&str, Span)> for Box<SassError> {
2020-02-16 10:54:25 -05:00
#[inline]
2020-06-26 05:12:28 -04:00
fn from(error: (&str, Span)) -> Box<SassError> {
Box::new(SassError {
2020-04-12 19:37:12 -04:00
kind: SassErrorKind::Raw(error.0.to_owned(), error.1),
2020-06-26 05:12:28 -04:00
})
2020-02-16 10:54:25 -05:00
}
}
2020-06-26 05:12:28 -04:00
impl From<(String, Span)> for Box<SassError> {
2020-02-16 10:54:25 -05:00
#[inline]
2020-06-26 05:12:28 -04:00
fn from(error: (String, Span)) -> Box<SassError> {
Box::new(SassError {
2020-04-12 19:37:12 -04:00
kind: SassErrorKind::Raw(error.0, error.1),
2020-06-26 05:12:28 -04:00
})
2020-02-16 10:54:25 -05:00
}
}
2020-01-06 18:26:07 -05:00
impl Error for SassError {
2020-04-21 18:22:26 -04:00
#[inline]
2020-01-06 18:26:07 -05:00
fn description(&self) -> &'static str {
"Sass parsing error"
2020-01-06 18:26:07 -05:00
}
}