1 use std::{ 2 error::Error, 3 fmt::{self, Display}, 4 io, 5 rc::Rc, 6 string::FromUtf8Error, 7 }; 8 9 use codemap::{Span, SpanLoc}; 10 11 pub type SassResult<T> = Result<T, Box<SassError>>; 12 13 /// `SassError`s can be either a structured error 14 /// specific to `grass` or an `io::Error`. 15 /// 16 /// In the former case, the best way to interact with 17 /// the error is to simply print it to the user. The 18 /// `Display` implementation of this kind of error 19 /// mirrors that of the errors `dart-sass` emits, e.g. 20 ///```scss 21 /// Error: $number: foo is not a number. 22 /// | 23 /// 308 | color: unit(foo); 24 /// | ^^^ 25 /// | 26 /// ./input.scss:308:17 27 ///``` 28 /// 29 /// The file name, line number, and column are structured in 30 /// such a way as to allow Visual Studio Code users to go 31 /// directly to the error by simply clicking the file name. 32 /// 33 /// Note that this is a deviation from the Sass specification. 34 #[derive(Debug, Clone)] 35 pub struct SassError { 36 kind: SassErrorKind, 37 } 38 39 impl SassError { raw(self) -> (String, Span)40 pub(crate) fn raw(self) -> (String, Span) { 41 match self.kind { 42 SassErrorKind::Raw(string, span) => (string, span), 43 e => todo!("unable to get raw of {:?}", e), 44 } 45 } 46 from_loc(message: String, loc: SpanLoc, unicode: bool) -> Self47 pub(crate) const fn from_loc(message: String, loc: SpanLoc, unicode: bool) -> Self { 48 SassError { 49 kind: SassErrorKind::ParseError { 50 message, 51 loc, 52 unicode, 53 }, 54 } 55 } 56 } 57 58 #[derive(Debug, Clone)] 59 enum SassErrorKind { 60 /// A raw error with no additional metadata 61 /// It contains only a `String` message and 62 /// a span 63 Raw(String, Span), 64 ParseError { 65 message: String, 66 loc: SpanLoc, 67 unicode: bool, 68 }, 69 // we put IoErrors in an `Rc` to allow it to be 70 // cloneable 71 IoError(Rc<io::Error>), 72 FromUtf8Error(String), 73 } 74 75 impl Display for SassError { 76 // TODO: trim whitespace from start of line shown in error 77 // TODO: color errors 78 // TODO: integrate with codemap-diagnostics 79 #[inline] fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result80 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 81 let (message, loc, unicode) = match &self.kind { 82 SassErrorKind::ParseError { 83 message, 84 loc, 85 unicode, 86 } => (message, loc, *unicode), 87 SassErrorKind::FromUtf8Error(s) => return writeln!(f, "Error: {}", s), 88 SassErrorKind::IoError(s) => return writeln!(f, "Error: {}", s), 89 SassErrorKind::Raw(..) => todo!(), 90 }; 91 92 let first_bar = if unicode { '╷' } else { '|' }; 93 let second_bar = if unicode { '│' } else { '|' }; 94 let third_bar = if unicode { '│' } else { '|' }; 95 let fourth_bar = if unicode { '╵' } else { '|' }; 96 97 let line = loc.begin.line + 1; 98 let col = loc.begin.column + 1; 99 writeln!(f, "Error: {}", message)?; 100 let padding = vec![' '; format!("{}", line).len() + 1] 101 .iter() 102 .collect::<String>(); 103 writeln!(f, "{}{}", padding, first_bar)?; 104 writeln!( 105 f, 106 "{} {} {}", 107 line, 108 second_bar, 109 loc.file.source_line(loc.begin.line) 110 )?; 111 writeln!( 112 f, 113 "{}{} {}{}", 114 padding, 115 third_bar, 116 vec![' '; loc.begin.column].iter().collect::<String>(), 117 vec!['^'; loc.end.column.max(loc.begin.column) - loc.begin.column.min(loc.end.column)] 118 .iter() 119 .collect::<String>() 120 )?; 121 writeln!(f, "{}{}", padding, fourth_bar)?; 122 writeln!(f, "./{}:{}:{}", loc.file.name(), line, col)?; 123 Ok(()) 124 } 125 } 126 127 impl From<io::Error> for Box<SassError> { 128 #[inline] from(error: io::Error) -> Box<SassError>129 fn from(error: io::Error) -> Box<SassError> { 130 Box::new(SassError { 131 kind: SassErrorKind::IoError(Rc::new(error)), 132 }) 133 } 134 } 135 136 impl From<FromUtf8Error> for Box<SassError> { 137 #[inline] from(error: FromUtf8Error) -> Box<SassError>138 fn from(error: FromUtf8Error) -> Box<SassError> { 139 Box::new(SassError { 140 kind: SassErrorKind::FromUtf8Error(format!( 141 "Invalid UTF-8 character \"\\x{:X?}\"", 142 error.as_bytes()[0] 143 )), 144 }) 145 } 146 } 147 148 impl From<(&str, Span)> for Box<SassError> { 149 #[inline] from(error: (&str, Span)) -> Box<SassError>150 fn from(error: (&str, Span)) -> Box<SassError> { 151 Box::new(SassError { 152 kind: SassErrorKind::Raw(error.0.to_owned(), error.1), 153 }) 154 } 155 } 156 157 impl From<(String, Span)> for Box<SassError> { 158 #[inline] from(error: (String, Span)) -> Box<SassError>159 fn from(error: (String, Span)) -> Box<SassError> { 160 Box::new(SassError { 161 kind: SassErrorKind::Raw(error.0, error.1), 162 }) 163 } 164 } 165 166 impl Error for SassError { 167 #[inline] description(&self) -> &'static str168 fn description(&self) -> &'static str { 169 "Sass parsing error" 170 } 171 } 172