1mod location;23use std::path::{Path, PathBuf};45use jrsonnet_parser::Source;6pub use location::*;78use crate::{error::Error, LocError, State};91011pub enum PathResolver {12 13 FileName,14 15 Absolute,16 17 Relative(PathBuf),18}1920impl PathResolver {21 pub fn resolve(&self, from: &Path) -> String {22 match self {23 Self::FileName => from24 .file_name()25 .expect("file name exists")26 .to_string_lossy()27 .into_owned(),28 Self::Absolute => from.to_string_lossy().into_owned(),29 Self::Relative(base) => {30 if from.is_relative() {31 return from.to_string_lossy().into_owned();32 }33 pathdiff::diff_paths(from, base)34 .expect("base is absolute")35 .to_string_lossy()36 .into_owned()37 }38 }39 }40}414243#[allow(clippy::module_name_repetitions)]44pub trait TraceFormat {45 fn write_trace(46 &self,47 out: &mut dyn std::fmt::Write,48 s: &State,49 error: &LocError,50 ) -> Result<(), std::fmt::Error>;51}5253fn print_code_location(54 out: &mut impl std::fmt::Write,55 start: &CodeLocation,56 end: &CodeLocation,57) -> Result<(), std::fmt::Error> {58 if start.line == end.line {59 if start.column == end.column {60 write!(out, "{}:{}", start.line, end.column.saturating_sub(1))?;61 } else {62 write!(out, "{}:{}-{}", start.line, start.column - 1, end.column)?;63 }64 } else {65 write!(66 out,67 "{}:{}-{}:{}",68 start.line,69 end.column.saturating_sub(1),70 start.line,71 end.column72 )?;73 }74 Ok(())75}767778pub struct CompactFormat {79 pub resolver: PathResolver,80 pub padding: usize,81}8283impl TraceFormat for CompactFormat {84 fn write_trace(85 &self,86 out: &mut dyn std::fmt::Write,87 s: &State,88 error: &LocError,89 ) -> Result<(), std::fmt::Error> {90 write!(out, "{}", error.error())?;91 if let Error::ImportSyntaxError {92 path,93 source_code,94 error,95 } = error.error()96 {97 use std::fmt::Write;9899 writeln!(out)?;100 let mut n = match path.repr() {101 Ok(r) => self.resolver.resolve(r),102 Err(v) => v.to_string(),103 };104 let mut offset = error.location.offset;105 let is_eof = if offset >= source_code.len() {106 offset = source_code.len().saturating_sub(1);107 true108 } else {109 false110 };111 let mut location = offset_to_location(source_code, &[offset as u32])112 .into_iter()113 .next()114 .unwrap();115 if is_eof {116 location.column += 1;117 }118119 write!(n, ":").unwrap();120 print_code_location(&mut n, &location, &location).unwrap();121 write!(out, "{:<p$}{}", "", n, p = self.padding,)?;122 }123 let file_names = error124 .trace()125 .0126 .iter()127 .map(|el| &el.location)128 .map(|location| {129 use std::fmt::Write;130 #[allow(clippy::option_if_let_else)]131 if let Some(location) = location {132 let mut resolved_path = match location.0.repr() {133 Ok(r) => self.resolver.resolve(r),134 Err(v) => v.to_string(),135 };136 137 let location =138 s.map_source_locations(location.0.clone(), &[location.1, location.2]);139 write!(resolved_path, ":").unwrap();140 print_code_location(&mut resolved_path, &location[0], &location[1]).unwrap();141 write!(resolved_path, ":").unwrap();142 Some(resolved_path)143 } else {144 None145 }146 })147 .collect::<Vec<_>>();148 let align = file_names149 .iter()150 .flatten()151 .map(String::len)152 .max()153 .unwrap_or(0);154 for (el, file) in error.trace().0.iter().zip(file_names) {155 writeln!(out)?;156 if let Some(file) = file {157 write!(158 out,159 "{:<p$}{:<w$} {}",160 "",161 file,162 el.desc,163 p = self.padding,164 w = align165 )?;166 } else {167 write!(out, "{:<p$}{}", "", el.desc, p = self.padding,)?;168 }169 }170 Ok(())171 }172}173174pub struct JsFormat;175impl TraceFormat for JsFormat {176 fn write_trace(177 &self,178 out: &mut dyn std::fmt::Write,179 s: &State,180 error: &LocError,181 ) -> Result<(), std::fmt::Error> {182 write!(out, "{}", error.error())?;183 for item in &error.trace().0 {184 writeln!(out)?;185 let desc = &item.desc;186 if let Some(source) = &item.location {187 let start_end = s.map_source_locations(source.0.clone(), &[source.1, source.2]);188 let resolved_path = match source.0.repr() {189 Ok(r) => r.display().to_string(),190 Err(v) => v.to_string(),191 };192193 write!(194 out,195 " at {} ({}:{}:{})",196 desc, resolved_path, start_end[0].line, start_end[0].column,197 )?;198 } else {199 write!(out, " during {}", desc)?;200 }201 }202 Ok(())203 }204}205206207#[cfg(feature = "explaining-traces")]208pub struct ExplainingFormat {209 pub resolver: PathResolver,210}211#[cfg(feature = "explaining-traces")]212impl TraceFormat for ExplainingFormat {213 fn write_trace(214 &self,215 out: &mut dyn std::fmt::Write,216 s: &State,217 error: &LocError,218 ) -> Result<(), std::fmt::Error> {219 write!(out, "{}", error.error())?;220 if let Error::ImportSyntaxError {221 path,222 source_code,223 error,224 } = error.error()225 {226 writeln!(out)?;227 let offset = error.location.offset;228 let location = offset_to_location(source_code, &[offset as u32])229 .into_iter()230 .next()231 .unwrap();232 let mut end_location = location.clone();233 end_location.offset += 1;234235 self.print_snippet(236 out,237 source_code,238 path,239 &location,240 &end_location,241 "syntax error",242 )?;243 }244 let trace = &error.trace();245 for item in &trace.0 {246 writeln!(out)?;247 let desc = &item.desc;248 if let Some(source) = &item.location {249 let start_end = s.map_source_locations(source.0.clone(), &[source.1, source.2]);250 self.print_snippet(251 out,252 &s.get_source(source.0.clone()).unwrap(),253 &source.0,254 &start_end[0],255 &start_end[1],256 desc,257 )?;258 } else {259 write!(out, "{}", desc)?;260 }261 }262 Ok(())263 }264}265266impl ExplainingFormat {267 fn print_snippet(268 &self,269 out: &mut dyn std::fmt::Write,270 source: &str,271 origin: &Source,272 start: &CodeLocation,273 end: &CodeLocation,274 desc: &str,275 ) -> Result<(), std::fmt::Error> {276 use annotate_snippets::{277 display_list::{DisplayList, FormatOptions},278 snippet::{AnnotationType, Slice, Snippet, SourceAnnotation},279 };280281 let source_fragment: String = source282 .chars()283 .skip(start.line_start_offset)284 .take(end.line_end_offset - end.line_start_offset)285 .collect();286287 let origin = match origin.repr() {288 Ok(r) => self.resolver.resolve(r),289 Err(v) => v.to_string(),290 };291 let snippet = Snippet {292 opt: FormatOptions {293 color: true,294 ..FormatOptions::default()295 },296 title: None,297 footer: vec![],298 slices: vec![Slice {299 source: &source_fragment,300 line_start: start.line,301 origin: Some(&origin),302 fold: false,303 annotations: vec![SourceAnnotation {304 label: desc,305 annotation_type: AnnotationType::Error,306 range: (307 start.offset - start.line_start_offset,308 (end.offset - start.line_start_offset).min(source_fragment.len()),309 ),310 }],311 }],312 };313314 let dl = DisplayList::from(snippet);315 write!(out, "{}", dl)?;316317 Ok(())318 }319}