1use std::path::{Path, PathBuf};23use jrsonnet_parser::{CodeLocation, Source};45use crate::{error::Error, LocError, State};678#[derive(Clone)]9pub enum PathResolver {10 11 FileName,12 13 Absolute,14 15 Relative(PathBuf),16}1718impl PathResolver {19 20 pub fn new_cwd_fallback() -> Self {21 match std::env::current_dir() {22 Ok(v) => Self::Relative(v),23 Err(_) => Self::Absolute,24 }25 }26 pub fn resolve(&self, from: &Path) -> String {27 match self {28 Self::FileName => from29 .file_name()30 .expect("file name exists")31 .to_string_lossy()32 .into_owned(),33 Self::Absolute => from.to_string_lossy().into_owned(),34 Self::Relative(base) => {35 if from.is_relative() {36 return from.to_string_lossy().into_owned();37 }38 pathdiff::diff_paths(from, base)39 .expect("base is absolute")40 .to_string_lossy()41 .into_owned()42 }43 }44 }45}464748#[allow(clippy::module_name_repetitions)]49pub trait TraceFormat {50 fn write_trace(51 &self,52 out: &mut dyn std::fmt::Write,53 s: &State,54 error: &LocError,55 ) -> Result<(), std::fmt::Error>;56}5758fn print_code_location(59 out: &mut impl std::fmt::Write,60 start: &CodeLocation,61 end: &CodeLocation,62) -> Result<(), std::fmt::Error> {63 if start.line == end.line {64 if start.column == end.column {65 write!(out, "{}:{}", start.line, end.column.saturating_sub(1))?;66 } else {67 write!(out, "{}:{}-{}", start.line, start.column - 1, end.column)?;68 }69 } else {70 write!(71 out,72 "{}:{}-{}:{}",73 start.line,74 end.column.saturating_sub(1),75 start.line,76 end.column77 )?;78 }79 Ok(())80}818283pub struct CompactFormat {84 pub resolver: PathResolver,85 pub padding: usize,86}8788impl TraceFormat for CompactFormat {89 fn write_trace(90 &self,91 out: &mut dyn std::fmt::Write,92 _s: &State,93 error: &LocError,94 ) -> Result<(), std::fmt::Error> {95 write!(out, "{}", error.error())?;96 if let Error::ImportSyntaxError { path, error } = error.error() {97 use std::fmt::Write;9899 writeln!(out)?;100 let mut n = match path.source_path().path() {101 Some(r) => self.resolver.resolve(r),102 None => path.source_path().to_string(),103 };104 let mut offset = error.location.offset;105 let is_eof = if offset >= path.code().len() {106 offset = path.code().len().saturating_sub(1);107 true108 } else {109 false110 };111 let mut location = path112 .map_source_locations(&[offset as u32])113 .into_iter()114 .next()115 .unwrap();116 if is_eof {117 location.column += 1;118 }119120 write!(n, ":").unwrap();121 print_code_location(&mut n, &location, &location).unwrap();122 write!(out, "{:<p$}{}", "", n, p = self.padding,)?;123 }124 let file_names = error125 .trace()126 .0127 .iter()128 .map(|el| &el.location)129 .map(|location| {130 use std::fmt::Write;131 #[allow(clippy::option_if_let_else)]132 if let Some(location) = location {133 let mut resolved_path = match location.0.source_path().path() {134 Some(r) => self.resolver.resolve(r),135 None => location.0.source_path().to_string(),136 };137 138 let location = location.0.map_source_locations(&[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 = source.0.map_source_locations(&[source.1, source.2]);188 let resolved_path = match source.0.source_path().path() {189 Some(r) => r.display().to_string(),190 None => source.0.source_path().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 { path, error } = error.error() {221 writeln!(out)?;222 let offset = error.location.offset;223 let location = path224 .map_source_locations(&[offset as u32])225 .into_iter()226 .next()227 .unwrap();228 let mut end_location = location.clone();229 end_location.offset += 1;230231 self.print_snippet(232 out,233 path.code(),234 path,235 &location,236 &end_location,237 "syntax error",238 )?;239 }240 let trace = &error.trace();241 for item in &trace.0 {242 writeln!(out)?;243 let desc = &item.desc;244 if let Some(source) = &item.location {245 let start_end = source.0.map_source_locations(&[source.1, source.2]);246 self.print_snippet(247 out,248 source.0.code(),249 &source.0,250 &start_end[0],251 &start_end[1],252 desc,253 )?;254 } else {255 write!(out, "{}", desc)?;256 }257 }258 Ok(())259 }260}261262impl ExplainingFormat {263 fn print_snippet(264 &self,265 out: &mut dyn std::fmt::Write,266 source: &str,267 origin: &Source,268 start: &CodeLocation,269 end: &CodeLocation,270 desc: &str,271 ) -> Result<(), std::fmt::Error> {272 use annotate_snippets::{273 display_list::{DisplayList, FormatOptions},274 snippet::{AnnotationType, Slice, Snippet, SourceAnnotation},275 };276277 let source_fragment: String = source278 .chars()279 .skip(start.line_start_offset)280 .take(end.line_end_offset - end.line_start_offset)281 .collect();282283 let origin = match origin.source_path().path() {284 Some(r) => self.resolver.resolve(r),285 None => origin.source_path().to_string(),286 };287 let snippet = Snippet {288 opt: FormatOptions {289 color: true,290 ..FormatOptions::default()291 },292 title: None,293 footer: vec![],294 slices: vec![Slice {295 source: &source_fragment,296 line_start: start.line,297 origin: Some(&origin),298 fold: false,299 annotations: vec![SourceAnnotation {300 label: desc,301 annotation_type: AnnotationType::Error,302 range: (303 start.offset - start.line_start_offset,304 (end.offset - start.line_start_offset).min(source_fragment.len()),305 ),306 }],307 }],308 };309310 let dl = DisplayList::from(snippet);311 write!(out, "{}", dl)?;312313 Ok(())314 }315}