difftreelog
fix explaining trace underflow
in: master
1 file changed
crates/jrsonnet-evaluator/src/trace/mod.rsdiffbeforeafterboth1use std::path::{Path, PathBuf};23use jrsonnet_gcmodule::Trace;4use jrsonnet_parser::{CodeLocation, Source};56use crate::{error::Error, LocError, State};78/// The way paths should be displayed9#[derive(Clone, Trace)]10pub enum PathResolver {11 /// Only filename12 FileName,13 /// Absolute path14 Absolute,15 /// Path relative to base directory16 Relative(PathBuf),17}1819impl PathResolver {20 /// Will return `Self::Relative(cwd)`, or `Self::Absolute` on cwd failure21 pub fn new_cwd_fallback() -> Self {22 std::env::current_dir().map_or(Self::Absolute, Self::Relative)23 }24 pub fn resolve(&self, from: &Path) -> String {25 match self {26 Self::FileName => from27 .file_name()28 .expect("file name exists")29 .to_string_lossy()30 .into_owned(),31 Self::Absolute => from.to_string_lossy().into_owned(),32 Self::Relative(base) => {33 if from.is_relative() {34 return from.to_string_lossy().into_owned();35 }36 pathdiff::diff_paths(from, base)37 .expect("base is absolute")38 .to_string_lossy()39 .into_owned()40 }41 }42 }43}4445/// Implements pretty-printing of traces46#[allow(clippy::module_name_repetitions)]47pub trait TraceFormat: Trace {48 fn write_trace(49 &self,50 out: &mut dyn std::fmt::Write,51 s: &State,52 error: &LocError,53 ) -> Result<(), std::fmt::Error>;54}5556fn print_code_location(57 out: &mut impl std::fmt::Write,58 start: &CodeLocation,59 end: &CodeLocation,60) -> Result<(), std::fmt::Error> {61 if start.line == end.line {62 if start.column == end.column {63 write!(out, "{}:{}", start.line, end.column.saturating_sub(1))?;64 } else {65 write!(out, "{}:{}-{}", start.line, start.column - 1, end.column)?;66 }67 } else {68 write!(69 out,70 "{}:{}-{}:{}",71 start.line,72 end.column.saturating_sub(1),73 start.line,74 end.column75 )?;76 }77 Ok(())78}7980/// vanilla-like jsonnet formatting81#[derive(Trace)]82pub struct CompactFormat {83 pub resolver: PathResolver,84 pub padding: usize,85}8687impl TraceFormat for CompactFormat {88 fn write_trace(89 &self,90 out: &mut dyn std::fmt::Write,91 _s: &State,92 error: &LocError,93 ) -> Result<(), std::fmt::Error> {94 write!(out, "{}", error.error())?;95 if let Error::ImportSyntaxError { path, error } = error.error() {96 use std::fmt::Write;9798 writeln!(out)?;99 let mut n = path.source_path().path().map_or_else(100 || path.source_path().to_string(),101 |r| self.resolver.resolve(r),102 );103 let mut offset = error.location.offset;104 let is_eof = if offset >= path.code().len() {105 offset = path.code().len().saturating_sub(1);106 true107 } else {108 false109 };110 let mut location = path111 .map_source_locations(&[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.source_path().path() {133 Some(r) => self.resolver.resolve(r),134 None => location.0.source_path().to_string(),135 };136 // TODO: Process all trace elements first137 let location = location.0.map_source_locations(&[location.1, location.2]);138 write!(resolved_path, ":").unwrap();139 print_code_location(&mut resolved_path, &location[0], &location[1]).unwrap();140 write!(resolved_path, ":").unwrap();141 Some(resolved_path)142 } else {143 None144 }145 })146 .collect::<Vec<_>>();147 let align = file_names148 .iter()149 .flatten()150 .map(String::len)151 .max()152 .unwrap_or(0);153 for (el, file) in error.trace().0.iter().zip(file_names) {154 writeln!(out)?;155 if let Some(file) = file {156 write!(157 out,158 "{:<p$}{:<w$} {}",159 "",160 file,161 el.desc,162 p = self.padding,163 w = align164 )?;165 } else {166 write!(out, "{:<p$}{}", "", el.desc, p = self.padding,)?;167 }168 }169 Ok(())170 }171}172173#[derive(Trace)]174pub 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 = source.0.source_path().path().map_or_else(189 || source.0.source_path().to_string(),190 |r| r.display().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}205206/// rustc-like trace displaying207#[cfg(feature = "explaining-traces")]208#[derive(Trace)]209pub struct ExplainingFormat {210 pub resolver: PathResolver,211}212#[cfg(feature = "explaining-traces")]213impl TraceFormat for ExplainingFormat {214 fn write_trace(215 &self,216 out: &mut dyn std::fmt::Write,217 _s: &State,218 error: &LocError,219 ) -> Result<(), std::fmt::Error> {220 write!(out, "{}", error.error())?;221 if let Error::ImportSyntaxError { path, error } = error.error() {222 writeln!(out)?;223 let offset = error.location.offset;224 let location = path225 .map_source_locations(&[offset as u32])226 .into_iter()227 .next()228 .unwrap();229 let mut end_location = location;230 end_location.offset += 1;231232 self.print_snippet(233 out,234 path.code(),235 path,236 &location,237 &end_location,238 "syntax error",239 )?;240 }241 let trace = &error.trace();242 for item in &trace.0 {243 writeln!(out)?;244 let desc = &item.desc;245 if let Some(source) = &item.location {246 let start_end = source.0.map_source_locations(&[source.1, source.2]);247 self.print_snippet(248 out,249 source.0.code(),250 &source.0,251 &start_end[0],252 &start_end[1],253 desc,254 )?;255 } else {256 write!(out, "{desc}")?;257 }258 }259 Ok(())260 }261}262263impl ExplainingFormat {264 fn print_snippet(265 &self,266 out: &mut dyn std::fmt::Write,267 source: &str,268 origin: &Source,269 start: &CodeLocation,270 end: &CodeLocation,271 desc: &str,272 ) -> Result<(), std::fmt::Error> {273 use annotate_snippets::{274 display_list::{DisplayList, FormatOptions},275 snippet::{AnnotationType, Slice, Snippet, SourceAnnotation},276 };277278 let source_fragment: String = source279 .chars()280 .skip(start.line_start_offset)281 .take(end.line_end_offset - end.line_start_offset)282 .collect();283284 let origin = origin.source_path().path().map_or_else(285 || origin.source_path().to_string(),286 |r| self.resolver.resolve(r),287 );288 let snippet = Snippet {289 opt: FormatOptions {290 color: true,291 ..FormatOptions::default()292 },293 title: None,294 footer: vec![],295 slices: vec![Slice {296 source: &source_fragment,297 line_start: start.line,298 origin: Some(&origin),299 fold: false,300 annotations: vec![SourceAnnotation {301 label: desc,302 annotation_type: AnnotationType::Error,303 range: (304 start.offset - start.line_start_offset,305 (end.offset - start.line_start_offset).min(source_fragment.len()),306 ),307 }],308 }],309 };310311 let dl = DisplayList::from(snippet);312 write!(out, "{dl}")?;313314 Ok(())315 }316}