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 std::env::current_dir().map_or(Self::Absolute, Self::Relative)22 }23 pub fn resolve(&self, from: &Path) -> String {24 match self {25 Self::FileName => from26 .file_name()27 .expect("file name exists")28 .to_string_lossy()29 .into_owned(),30 Self::Absolute => from.to_string_lossy().into_owned(),31 Self::Relative(base) => {32 if from.is_relative() {33 return from.to_string_lossy().into_owned();34 }35 pathdiff::diff_paths(from, base)36 .expect("base is absolute")37 .to_string_lossy()38 .into_owned()39 }40 }41 }42}434445#[allow(clippy::module_name_repetitions)]46pub trait TraceFormat {47 fn write_trace(48 &self,49 out: &mut dyn std::fmt::Write,50 s: &State,51 error: &LocError,52 ) -> Result<(), std::fmt::Error>;53}5455fn print_code_location(56 out: &mut impl std::fmt::Write,57 start: &CodeLocation,58 end: &CodeLocation,59) -> Result<(), std::fmt::Error> {60 if start.line == end.line {61 if start.column == end.column {62 write!(out, "{}:{}", start.line, end.column.saturating_sub(1))?;63 } else {64 write!(out, "{}:{}-{}", start.line, start.column - 1, end.column)?;65 }66 } else {67 write!(68 out,69 "{}:{}-{}:{}",70 start.line,71 end.column.saturating_sub(1),72 start.line,73 end.column74 )?;75 }76 Ok(())77}787980pub struct CompactFormat {81 pub resolver: PathResolver,82 pub padding: usize,83}8485impl TraceFormat for CompactFormat {86 fn write_trace(87 &self,88 out: &mut dyn std::fmt::Write,89 _s: &State,90 error: &LocError,91 ) -> Result<(), std::fmt::Error> {92 write!(out, "{}", error.error())?;93 if let Error::ImportSyntaxError { path, error } = error.error() {94 use std::fmt::Write;9596 writeln!(out)?;97 let mut n = path.source_path().path().map_or_else(98 || path.source_path().to_string(),99 |r| self.resolver.resolve(r),100 );101 let mut offset = error.location.offset;102 let is_eof = if offset >= path.code().len() {103 offset = path.code().len().saturating_sub(1);104 true105 } else {106 false107 };108 let mut location = path109 .map_source_locations(&[offset as u32])110 .into_iter()111 .next()112 .unwrap();113 if is_eof {114 location.column += 1;115 }116117 write!(n, ":").unwrap();118 print_code_location(&mut n, &location, &location).unwrap();119 write!(out, "{:<p$}{n}", "", p = self.padding)?;120 }121 let file_names = error122 .trace()123 .0124 .iter()125 .map(|el| &el.location)126 .map(|location| {127 use std::fmt::Write;128 #[allow(clippy::option_if_let_else)]129 if let Some(location) = location {130 let mut resolved_path = match location.0.source_path().path() {131 Some(r) => self.resolver.resolve(r),132 None => location.0.source_path().to_string(),133 };134 135 let location = location.0.map_source_locations(&[location.1, location.2]);136 write!(resolved_path, ":").unwrap();137 print_code_location(&mut resolved_path, &location[0], &location[1]).unwrap();138 write!(resolved_path, ":").unwrap();139 Some(resolved_path)140 } else {141 None142 }143 })144 .collect::<Vec<_>>();145 let align = file_names146 .iter()147 .flatten()148 .map(String::len)149 .max()150 .unwrap_or(0);151 for (el, file) in error.trace().0.iter().zip(file_names) {152 writeln!(out)?;153 if let Some(file) = file {154 write!(155 out,156 "{:<p$}{:<w$} {}",157 "",158 file,159 el.desc,160 p = self.padding,161 w = align162 )?;163 } else {164 write!(out, "{:<p$}{}", "", el.desc, p = self.padding,)?;165 }166 }167 Ok(())168 }169}170171pub struct JsFormat;172impl TraceFormat for JsFormat {173 fn write_trace(174 &self,175 out: &mut dyn std::fmt::Write,176 _s: &State,177 error: &LocError,178 ) -> Result<(), std::fmt::Error> {179 write!(out, "{}", error.error())?;180 for item in &error.trace().0 {181 writeln!(out)?;182 let desc = &item.desc;183 if let Some(source) = &item.location {184 let start_end = source.0.map_source_locations(&[source.1, source.2]);185 let resolved_path = source.0.source_path().path().map_or_else(186 || source.0.source_path().to_string(),187 |r| r.display().to_string(),188 );189190 write!(191 out,192 " at {} ({}:{}:{})",193 desc, resolved_path, start_end[0].line, start_end[0].column,194 )?;195 } else {196 write!(out, " during {desc}")?;197 }198 }199 Ok(())200 }201}202203204#[cfg(feature = "explaining-traces")]205pub struct ExplainingFormat {206 pub resolver: PathResolver,207}208#[cfg(feature = "explaining-traces")]209impl TraceFormat for ExplainingFormat {210 fn write_trace(211 &self,212 out: &mut dyn std::fmt::Write,213 _s: &State,214 error: &LocError,215 ) -> Result<(), std::fmt::Error> {216 write!(out, "{}", error.error())?;217 if let Error::ImportSyntaxError { path, error } = error.error() {218 writeln!(out)?;219 let offset = error.location.offset;220 let location = path221 .map_source_locations(&[offset as u32])222 .into_iter()223 .next()224 .unwrap();225 let mut end_location = location.clone();226 end_location.offset += 1;227228 self.print_snippet(229 out,230 path.code(),231 path,232 &location,233 &end_location,234 "syntax error",235 )?;236 }237 let trace = &error.trace();238 for item in &trace.0 {239 writeln!(out)?;240 let desc = &item.desc;241 if let Some(source) = &item.location {242 let start_end = source.0.map_source_locations(&[source.1, source.2]);243 self.print_snippet(244 out,245 source.0.code(),246 &source.0,247 &start_end[0],248 &start_end[1],249 desc,250 )?;251 } else {252 write!(out, "{desc}")?;253 }254 }255 Ok(())256 }257}258259impl ExplainingFormat {260 fn print_snippet(261 &self,262 out: &mut dyn std::fmt::Write,263 source: &str,264 origin: &Source,265 start: &CodeLocation,266 end: &CodeLocation,267 desc: &str,268 ) -> Result<(), std::fmt::Error> {269 use annotate_snippets::{270 display_list::{DisplayList, FormatOptions},271 snippet::{AnnotationType, Slice, Snippet, SourceAnnotation},272 };273274 let source_fragment: String = source275 .chars()276 .skip(start.line_start_offset)277 .take(end.line_end_offset - end.line_start_offset)278 .collect();279280 let origin = origin.source_path().path().map_or_else(281 || origin.source_path().to_string(),282 |r| self.resolver.resolve(r),283 );284 let snippet = Snippet {285 opt: FormatOptions {286 color: true,287 ..FormatOptions::default()288 },289 title: None,290 footer: vec![],291 slices: vec![Slice {292 source: &source_fragment,293 line_start: start.line,294 origin: Some(&origin),295 fold: false,296 annotations: vec![SourceAnnotation {297 label: desc,298 annotation_type: AnnotationType::Error,299 range: (300 start.offset - start.line_start_offset,301 (end.offset - start.line_start_offset).min(source_fragment.len()),302 ),303 }],304 }],305 };306307 let dl = DisplayList::from(snippet);308 write!(out, "{dl}")?;309310 Ok(())311 }312}