1mod location;23use std::path::{Path, PathBuf};45pub use location::*;67use crate::{error::Error, LocError, State};8910pub enum PathResolver {11 12 FileName,13 14 Absolute,15 16 Relative(PathBuf),17}1819impl PathResolver {20 pub fn resolve(&self, from: &Path) -> String {21 match self {22 Self::FileName => from23 .file_name()24 .expect("file name exists")25 .to_string_lossy()26 .into_owned(),27 Self::Absolute => from.to_string_lossy().into_owned(),28 Self::Relative(base) => {29 if from.is_relative() {30 return from.to_string_lossy().into_owned();31 }32 pathdiff::diff_paths(from, base)33 .expect("base is absolute")34 .to_string_lossy()35 .into_owned()36 }37 }38 }39}404142#[allow(clippy::module_name_repetitions)]43pub trait TraceFormat {44 fn write_trace(45 &self,46 out: &mut dyn std::fmt::Write,47 s: &State,48 error: &LocError,49 ) -> Result<(), std::fmt::Error>;50}5152fn print_code_location(53 out: &mut impl std::fmt::Write,54 start: &CodeLocation,55 end: &CodeLocation,56) -> Result<(), std::fmt::Error> {57 if start.line == end.line {58 if start.column == end.column {59 write!(out, "{}:{}", start.line, end.column - 1)?;60 } else {61 write!(out, "{}:{}-{}", start.line, start.column - 1, end.column)?;62 }63 } else {64 write!(65 out,66 "{}:{}-{}:{}",67 start.line,68 end.column.saturating_sub(1),69 start.line,70 end.column71 )?;72 }73 Ok(())74}757677pub struct CompactFormat {78 pub resolver: PathResolver,79 pub padding: usize,80}8182impl TraceFormat for CompactFormat {83 fn write_trace(84 &self,85 out: &mut dyn std::fmt::Write,86 s: &State,87 error: &LocError,88 ) -> Result<(), std::fmt::Error> {89 write!(out, "{}", error.error())?;90 if let Error::ImportSyntaxError {91 path,92 source_code,93 error,94 } = error.error()95 {96 use std::fmt::Write;9798 writeln!(out)?;99 let mut n = self.resolver.resolve(path);100 let mut offset = error.location.offset;101 let is_eof = if offset >= source_code.len() {102 offset = source_code.len().saturating_sub(1);103 true104 } else {105 false106 };107 let mut location = offset_to_location(source_code, &[offset])108 .into_iter()109 .next()110 .unwrap();111 if is_eof {112 location.column += 1;113 }114115 write!(n, ":").unwrap();116 print_code_location(&mut n, &location, &location).unwrap();117 write!(out, "{:<p$}{}", "", n, p = self.padding,)?;118 }119 let file_names = error120 .trace()121 .0122 .iter()123 .map(|el| &el.location)124 .map(|location| {125 use std::fmt::Write;126 #[allow(clippy::option_if_let_else)]127 if let Some(location) = location {128 let mut resolved_path = self.resolver.resolve(&location.0);129 130 let location = s.map_source_locations(&location.0, &[location.1, location.2]);131 write!(resolved_path, ":").unwrap();132 print_code_location(&mut resolved_path, &location[0], &location[1]).unwrap();133 write!(resolved_path, ":").unwrap();134 Some(resolved_path)135 } else {136 None137 }138 })139 .collect::<Vec<_>>();140 let align = file_names141 .iter()142 .flatten()143 .map(String::len)144 .max()145 .unwrap_or(0);146 for (el, file) in error.trace().0.iter().zip(file_names) {147 writeln!(out)?;148 if let Some(file) = file {149 write!(150 out,151 "{:<p$}{:<w$} {}",152 "",153 file,154 el.desc,155 p = self.padding,156 w = align157 )?;158 } else {159 write!(out, "{:<p$}{}", "", el.desc, p = self.padding,)?;160 }161 }162 Ok(())163 }164}165166pub struct JsFormat;167impl TraceFormat for JsFormat {168 fn write_trace(169 &self,170 out: &mut dyn std::fmt::Write,171 s: &State,172 error: &LocError,173 ) -> Result<(), std::fmt::Error> {174 write!(out, "{}", error.error())?;175 for item in &error.trace().0 {176 writeln!(out)?;177 let desc = &item.desc;178 if let Some(source) = &item.location {179 let start_end = s.map_source_locations(&source.0, &[source.1, source.2]);180181 write!(182 out,183 " at {} ({}:{}:{})",184 desc,185 source.0.to_str().unwrap(),186 start_end[0].line,187 start_end[0].column,188 )?;189 } else {190 write!(out, " during {}", desc)?;191 }192 }193 Ok(())194 }195}196197198#[cfg(feature = "explaining-traces")]199pub struct ExplainingFormat {200 pub resolver: PathResolver,201}202#[cfg(feature = "explaining-traces")]203impl TraceFormat for ExplainingFormat {204 fn write_trace(205 &self,206 out: &mut dyn std::fmt::Write,207 s: &State,208 error: &LocError,209 ) -> Result<(), std::fmt::Error> {210 write!(out, "{}", error.error())?;211 if let Error::ImportSyntaxError {212 path,213 source_code,214 error,215 } = error.error()216 {217 writeln!(out)?;218 let offset = error.location.offset;219 let location = offset_to_location(source_code, &[offset])220 .into_iter()221 .next()222 .unwrap();223 let mut end_location = location.clone();224 end_location.offset += 1;225226 self.print_snippet(227 out,228 source_code,229 path,230 &location,231 &end_location,232 "syntax error",233 )?;234 }235 let trace = &error.trace();236 for item in &trace.0 {237 writeln!(out)?;238 let desc = &item.desc;239 if let Some(source) = &item.location {240 let start_end = s.map_source_locations(&source.0, &[source.1, source.2]);241 self.print_snippet(242 out,243 &s.get_source(&source.0).unwrap(),244 &source.0,245 &start_end[0],246 &start_end[1],247 desc,248 )?;249 } else {250 write!(out, "{}", desc)?;251 }252 }253 Ok(())254 }255}256257impl ExplainingFormat {258 fn print_snippet(259 &self,260 out: &mut dyn std::fmt::Write,261 source: &str,262 origin: &Path,263 start: &CodeLocation,264 end: &CodeLocation,265 desc: &str,266 ) -> Result<(), std::fmt::Error> {267 use annotate_snippets::{268 display_list::{DisplayList, FormatOptions},269 snippet::{AnnotationType, Slice, Snippet, SourceAnnotation},270 };271272 let source_fragment: String = source273 .chars()274 .skip(start.line_start_offset)275 .take(end.line_end_offset - end.line_start_offset)276 .collect();277278 let origin = self.resolver.resolve(origin);279 let snippet = Snippet {280 opt: FormatOptions {281 color: true,282 ..FormatOptions::default()283 },284 title: None,285 footer: vec![],286 slices: vec![Slice {287 source: &source_fragment,288 line_start: start.line,289 origin: Some(&origin),290 fold: false,291 annotations: vec![SourceAnnotation {292 label: desc,293 annotation_type: AnnotationType::Error,294 range: (295 start.offset - start.line_start_offset,296 (end.offset - start.line_start_offset).min(source_fragment.len()),297 ),298 }],299 }],300 };301302 let dl = DisplayList::from(snippet);303 write!(out, "{}", dl)?;304305 Ok(())306 }307}