difftreelog
feat simplify root-based paths
in: master
1 file changed
crates/jrsonnet-evaluator/src/trace/mod.rsdiffbeforeafterboth1#[cfg(feature = "explaining-traces")]2use std::cell::RefCell;3use std::{4 any::Any,5 path::{Path, PathBuf},6};78use jrsonnet_gcmodule::Trace;9use jrsonnet_ir::CodeLocation;10#[cfg(feature = "explaining-traces")]11use jrsonnet_ir::Span;1213use crate::{Error, error::ErrorKind};1415/// The way paths should be displayed16#[derive(Clone, Trace)]17pub enum PathResolver {18 /// Only filename19 FileName,20 /// Absolute path21 Absolute,22 /// Path relative to base directory23 Relative(PathBuf),24}2526impl PathResolver {27 /// Will return `Self::Relative(cwd)`, or `Self::Absolute` on cwd failure28 pub fn new_cwd_fallback() -> Self {29 std::env::current_dir().map_or(Self::Absolute, Self::Relative)30 }31 pub fn resolve(&self, from: &Path) -> String {32 match self {33 Self::FileName => from34 .file_name()35 .expect("file name exists")36 .to_string_lossy()37 .into_owned(),38 Self::Absolute => from.to_string_lossy().into_owned(),39 Self::Relative(base) => {40 if from.is_relative() {41 return from.to_string_lossy().into_owned();42 }43 pathdiff::diff_paths(from, base)44 .expect("base is absolute")45 .to_string_lossy()46 .into_owned()47 }48 }49 }50}5152/// Implements pretty-printing of traces53#[allow(clippy::module_name_repetitions)]54pub trait TraceFormat: Trace {55 fn write_trace(56 &self,57 out: &mut dyn std::fmt::Write,58 error: &Error,59 ) -> Result<(), std::fmt::Error>;60 fn format(&self, error: &Error) -> Result<String, std::fmt::Error> {61 let mut out = String::new();62 self.write_trace(&mut out, error)?;63 Ok(out)64 }65 fn as_any(&self) -> &dyn Any;66 fn as_any_mut(&mut self) -> &mut dyn Any;67}6869fn print_code_location(70 out: &mut impl std::fmt::Write,71 start: &CodeLocation,72 end: &CodeLocation,73) -> Result<(), std::fmt::Error> {74 if start.line == end.line {75 if start.column == end.column {76 write!(out, "{}:{}", start.line, end.column.saturating_sub(1))?;77 } else {78 write!(out, "{}:{}-{}", start.line, start.column - 1, end.column)?;79 }80 } else {81 write!(82 out,83 "{}:{}-{}:{}",84 start.line,85 end.column.saturating_sub(1),86 start.line,87 end.column88 )?;89 }90 Ok(())91}9293/// vanilla-like jsonnet formatting94#[derive(Trace)]95pub struct CompactFormat {96 pub resolver: PathResolver,97 pub max_trace: usize,98 pub padding: usize,99}100impl Default for CompactFormat {101 fn default() -> Self {102 Self {103 resolver: PathResolver::Absolute,104 max_trace: 20,105 padding: 4,106 }107 }108}109110impl TraceFormat for CompactFormat {111 fn write_trace(112 &self,113 out: &mut dyn std::fmt::Write,114 error: &Error,115 ) -> Result<(), std::fmt::Error> {116 write!(out, "{}", error.error())?;117 if let ErrorKind::ImportSyntaxError { path, error } = error.error() {118 use std::fmt::Write;119120 writeln!(out)?;121 let mut n = path.source_path().path().map_or_else(122 || path.source_path().to_string(),123 |r| self.resolver.resolve(r),124 );125 let mut offset = error.location.1 as usize;126 let is_eof = if offset >= path.code().len() {127 offset = path.code().len().saturating_sub(1);128 true129 } else {130 false131 };132 #[expect(clippy::cast_possible_truncation, reason = "code is limited by 4gb")]133 let mut location = path134 .map_source_locations(&[offset as u32])135 .into_iter()136 .next()137 .unwrap();138 if is_eof {139 location.column += 1;140 }141142 write!(n, ":").unwrap();143 print_code_location(&mut n, &location, &location).unwrap();144 write!(out, "{:<p$}{n}", "", p = self.padding)?;145 }146 let file_names = error147 .trace()148 .0149 .iter()150 .map(|el| &el.location)151 .map(|location| {152 use std::fmt::Write;153 #[allow(clippy::option_if_let_else)]154 if let Some(location) = location {155 let mut resolved_path = match location.0.source_path().path() {156 Some(r) => self.resolver.resolve(r),157 None => location.0.source_path().to_string(),158 };159 // TODO: Process all trace elements first160 let location = location.0.map_source_locations(&[location.1, location.2]);161 write!(resolved_path, ":").unwrap();162 print_code_location(&mut resolved_path, &location[0], &location[1]).unwrap();163 write!(resolved_path, ":").unwrap();164 Some(resolved_path)165 } else {166 None167 }168 })169 .collect::<Vec<_>>();170 let align = file_names171 .iter()172 .flatten()173 .map(String::len)174 .max()175 .unwrap_or(0);176 for (el, file) in error.trace().0.iter().zip(file_names) {177 writeln!(out)?;178 if let Some(file) = file {179 write!(180 out,181 "{:<p$}{:<w$} {}",182 "",183 file,184 el.desc,185 p = self.padding,186 w = align187 )?;188 } else {189 write!(out, "{:<p$}{}", "", el.desc, p = self.padding,)?;190 }191 }192 Ok(())193 }194195 fn as_any(&self) -> &dyn Any {196 self197 }198199 fn as_any_mut(&mut self) -> &mut dyn Any {200 self201 }202}203204#[derive(Trace)]205pub struct JsFormat {206 pub max_trace: usize,207}208impl TraceFormat for JsFormat {209 fn write_trace(210 &self,211 out: &mut dyn std::fmt::Write,212 error: &Error,213 ) -> Result<(), std::fmt::Error> {214 write!(out, "{}", error.error())?;215 for item in &error.trace().0 {216 writeln!(out)?;217 let desc = &item.desc;218 if let Some(source) = &item.location {219 let start_end = source.0.map_source_locations(&[source.1, source.2]);220 let resolved_path = source.0.source_path().path().map_or_else(221 || source.0.source_path().to_string(),222 |r| r.display().to_string(),223 );224225 write!(226 out,227 " at {} ({}:{}:{})",228 desc, resolved_path, start_end[0].line, start_end[0].column,229 )?;230 } else {231 write!(out, " during {desc}")?;232 }233 }234 Ok(())235 }236237 fn as_any(&self) -> &dyn Any {238 self239 }240241 fn as_any_mut(&mut self) -> &mut dyn Any {242 self243 }244}245246#[cfg(feature = "explaining-traces")]247#[derive(Trace)]248pub struct HiDocFormat {249 pub resolver: PathResolver,250 pub max_trace: usize,251}252#[cfg(feature = "explaining-traces")]253impl TraceFormat for HiDocFormat {254 fn write_trace(255 &self,256 out: &mut dyn std::fmt::Write,257 error: &Error,258 ) -> Result<(), std::fmt::Error> {259 struct ResetData {260 loc: Span,261 }262 use hi_doc::{Formatting, SnippetBuilder, Text, source_to_ansi};263264 write!(out, "{}", error.error())?;265 if let ErrorKind::ImportSyntaxError { path, error } = error.error() {266 writeln!(out)?;267 let mut builder = SnippetBuilder::new(path.code());268 builder269 .error(Text::fragment("syntax error", Formatting::default()))270 .range(error.location.range())271 .build();272 let source = builder.build();273 let ansi = source_to_ansi(&source);274 write!(out, "{ansi}")?;275 }276 if let ErrorKind::StaticAnalysisError(diagnostics) = error.error() {277 use crate::analyze::DiagLevel;278 let mut builder: Option<SnippetBuilder> = None;279 let mut current_src: Option<&str> = None;280 let flush = |builder: Option<SnippetBuilder>,281 out: &mut dyn std::fmt::Write|282 -> Result<(), std::fmt::Error> {283 if let Some(b) = builder {284 let ansi = source_to_ansi(&b.build());285 write!(out, "\n{}", ansi.trim_end())?;286 }287 Ok(())288 };289 for diag in diagnostics {290 if let Some(span) = &diag.span {291 let src = span.0.code();292 if current_src != Some(src) {293 flush(builder.take(), out)?;294 builder = Some(SnippetBuilder::new(src));295 current_src = Some(src);296 }297 let b = builder.as_mut().unwrap();298 let ab = match diag.level {299 DiagLevel::Error => {300 b.error(Text::fragment(diag.message.clone(), Formatting::default()))301 }302 DiagLevel::Warning => {303 b.warning(Text::fragment(diag.message.clone(), Formatting::default()))304 }305 };306 ab.range(span.range()).build();307 } else {308 flush(builder.take(), out)?;309 current_src = None;310 let prefix = match diag.level {311 DiagLevel::Error => "error",312 DiagLevel::Warning => "warning",313 };314 write!(out, "\n{prefix}: {}", diag.message)?;315 }316 }317 flush(builder, out)?;318 }319 let trace = &error.trace();320 let snippet_builder: RefCell<Option<SnippetBuilder>> = RefCell::new(None);321 let mut last_location: Option<Span> = None;322 let mut flush_builder = |data: Option<ResetData>| {323 use std::fmt::Write;324 let mut out = String::new();325 let location_changed = if let Some(ResetData { loc }) = &data {326 if last_location.as_ref().map(|l| l.0.code()) != Some(loc.0.code()) {327 true328 } else if let (Some(last), new) = (&last_location, loc) {329 // Reverse condition if traceback330 last.1 > new.1 || last.2 > new.2331 } else {332 false333 }334 } else {335 true336 };337 if location_changed {338 if let Some(builder) = snippet_builder.borrow_mut().take() {339 let rendered = builder.build();340 let ansi = source_to_ansi(&rendered);341 if let Some(loc) = &last_location {342 let _ = writeln!(out, "...at {}", loc.0.source_path());343 }344 let _ = write!(out, "{}", ansi.trim_end());345 }346 last_location = None;347348 if let Some(ResetData { loc }) = data {349 *snippet_builder.borrow_mut() = Some(SnippetBuilder::new(loc.0.code()));350 last_location = Some(loc);351 }352 }353 if out.is_empty() {354 return None;355 }356 Some(out)357 };358 for item in &trace.0 {359 let desc = &item.desc;360 if let Some(source) = &item.location {361 if let Some(flushed) = flush_builder(Some(ResetData {362 loc: source.clone(),363 })) {364 writeln!(out)?;365 write!(out, "{flushed}")?;366 }367 let mut builder = snippet_builder.borrow_mut();368 let builder = builder.as_mut().unwrap();369 builder370 .note(Text::fragment(desc, Formatting::default()))371 .range(source.1 as usize..=(source.2 as usize - 1).max(source.1 as usize))372 .build();373 } else {374 if let Some(flushed) = flush_builder(None) {375 writeln!(out)?;376 write!(out, "{flushed}")?;377 }378 writeln!(out)?;379 write!(out, " {desc}")?;380 }381 }382383 if let Some(flushed) = flush_builder(None) {384 writeln!(out)?;385 write!(out, "{flushed}")?;386 }387 Ok(())388 }389390 fn as_any(&self) -> &dyn Any {391 self392 }393394 fn as_any_mut(&mut self) -> &mut dyn Any {395 self396 }397}1#[cfg(feature = "explaining-traces")]2use std::cell::RefCell;3use std::{4 any::Any,5 path::{Component, Path, PathBuf},6};78use jrsonnet_gcmodule::Trace;9use jrsonnet_ir::CodeLocation;10#[cfg(feature = "explaining-traces")]11use jrsonnet_ir::Span;1213use crate::{Error, error::ErrorKind};1415/// The way paths should be displayed16#[derive(Clone, Trace)]17pub enum PathResolver {18 /// Only filename19 FileName,20 /// Absolute path21 Absolute,22 /// Path relative to base directory23 Relative(PathBuf),24}2526impl PathResolver {27 /// Will return `Self::Relative(cwd)`, or `Self::Absolute` on cwd failure28 pub fn new_cwd_fallback() -> Self {29 std::env::current_dir().map_or(Self::Absolute, Self::Relative)30 }31 pub fn resolve(&self, from: &Path) -> String {32 match self {33 Self::FileName => from34 .file_name()35 .expect("file name exists")36 .to_string_lossy()37 .into_owned(),38 Self::Absolute => from.to_string_lossy().into_owned(),39 Self::Relative(base) => {40 if from.is_relative() {41 return from.to_string_lossy().into_owned();42 }43 let diff = pathdiff::diff_paths(from, base).expect("base is absolute");44 let parents = diff45 .components()46 .take_while(|c| matches!(c, Component::ParentDir))47 .count();48 let base_depth = base49 .components()50 .filter(|c| matches!(c, Component::Normal(_)))51 .count();52 if parents > 0 && parents >= base_depth {53 return from.to_string_lossy().into_owned();54 }55 diff.to_string_lossy().into_owned()56 }57 }58 }59}6061/// Implements pretty-printing of traces62#[allow(clippy::module_name_repetitions)]63pub trait TraceFormat: Trace {64 fn write_trace(65 &self,66 out: &mut dyn std::fmt::Write,67 error: &Error,68 ) -> Result<(), std::fmt::Error>;69 fn format(&self, error: &Error) -> Result<String, std::fmt::Error> {70 let mut out = String::new();71 self.write_trace(&mut out, error)?;72 Ok(out)73 }74 fn as_any(&self) -> &dyn Any;75 fn as_any_mut(&mut self) -> &mut dyn Any;76}7778fn print_code_location(79 out: &mut impl std::fmt::Write,80 start: &CodeLocation,81 end: &CodeLocation,82) -> Result<(), std::fmt::Error> {83 if start.line == end.line {84 if start.column == end.column {85 write!(out, "{}:{}", start.line, end.column.saturating_sub(1))?;86 } else {87 write!(out, "{}:{}-{}", start.line, start.column - 1, end.column)?;88 }89 } else {90 write!(91 out,92 "{}:{}-{}:{}",93 start.line,94 end.column.saturating_sub(1),95 start.line,96 end.column97 )?;98 }99 Ok(())100}101102/// vanilla-like jsonnet formatting103#[derive(Trace)]104pub struct CompactFormat {105 pub resolver: PathResolver,106 pub max_trace: usize,107 pub padding: usize,108}109impl Default for CompactFormat {110 fn default() -> Self {111 Self {112 resolver: PathResolver::Absolute,113 max_trace: 20,114 padding: 4,115 }116 }117}118119impl TraceFormat for CompactFormat {120 fn write_trace(121 &self,122 out: &mut dyn std::fmt::Write,123 error: &Error,124 ) -> Result<(), std::fmt::Error> {125 write!(out, "{}", error.error())?;126 if let ErrorKind::ImportSyntaxError { path, error } = error.error() {127 use std::fmt::Write;128129 writeln!(out)?;130 let mut n = path.source_path().path().map_or_else(131 || path.source_path().to_string(),132 |r| self.resolver.resolve(r),133 );134 let mut offset = error.location.1 as usize;135 let is_eof = if offset >= path.code().len() {136 offset = path.code().len().saturating_sub(1);137 true138 } else {139 false140 };141 #[expect(clippy::cast_possible_truncation, reason = "code is limited by 4gb")]142 let mut location = path143 .map_source_locations(&[offset as u32])144 .into_iter()145 .next()146 .unwrap();147 if is_eof {148 location.column += 1;149 }150151 write!(n, ":").unwrap();152 print_code_location(&mut n, &location, &location).unwrap();153 write!(out, "{:<p$}{n}", "", p = self.padding)?;154 }155 let file_names = error156 .trace()157 .0158 .iter()159 .map(|el| &el.location)160 .map(|location| {161 use std::fmt::Write;162 #[allow(clippy::option_if_let_else)]163 if let Some(location) = location {164 let mut resolved_path = match location.0.source_path().path() {165 Some(r) => self.resolver.resolve(r),166 None => location.0.source_path().to_string(),167 };168 // TODO: Process all trace elements first169 let location = location.0.map_source_locations(&[location.1, location.2]);170 write!(resolved_path, ":").unwrap();171 print_code_location(&mut resolved_path, &location[0], &location[1]).unwrap();172 write!(resolved_path, ":").unwrap();173 Some(resolved_path)174 } else {175 None176 }177 })178 .collect::<Vec<_>>();179 let align = file_names180 .iter()181 .flatten()182 .map(String::len)183 .max()184 .unwrap_or(0);185 for (el, file) in error.trace().0.iter().zip(file_names) {186 writeln!(out)?;187 if let Some(file) = file {188 write!(189 out,190 "{:<p$}{:<w$} {}",191 "",192 file,193 el.desc,194 p = self.padding,195 w = align196 )?;197 } else {198 write!(out, "{:<p$}{}", "", el.desc, p = self.padding,)?;199 }200 }201 Ok(())202 }203204 fn as_any(&self) -> &dyn Any {205 self206 }207208 fn as_any_mut(&mut self) -> &mut dyn Any {209 self210 }211}212213#[derive(Trace)]214pub struct JsFormat {215 pub max_trace: usize,216}217impl TraceFormat for JsFormat {218 fn write_trace(219 &self,220 out: &mut dyn std::fmt::Write,221 error: &Error,222 ) -> Result<(), std::fmt::Error> {223 write!(out, "{}", error.error())?;224 for item in &error.trace().0 {225 writeln!(out)?;226 let desc = &item.desc;227 if let Some(source) = &item.location {228 let start_end = source.0.map_source_locations(&[source.1, source.2]);229 let resolved_path = source.0.source_path().path().map_or_else(230 || source.0.source_path().to_string(),231 |r| r.display().to_string(),232 );233234 write!(235 out,236 " at {} ({}:{}:{})",237 desc, resolved_path, start_end[0].line, start_end[0].column,238 )?;239 } else {240 write!(out, " during {desc}")?;241 }242 }243 Ok(())244 }245246 fn as_any(&self) -> &dyn Any {247 self248 }249250 fn as_any_mut(&mut self) -> &mut dyn Any {251 self252 }253}254255#[cfg(feature = "explaining-traces")]256#[derive(Trace)]257pub struct HiDocFormat {258 pub resolver: PathResolver,259 pub max_trace: usize,260}261#[cfg(feature = "explaining-traces")]262impl TraceFormat for HiDocFormat {263 fn write_trace(264 &self,265 out: &mut dyn std::fmt::Write,266 error: &Error,267 ) -> Result<(), std::fmt::Error> {268 struct ResetData {269 loc: Span,270 }271 use hi_doc::{Formatting, SnippetBuilder, Text, source_to_ansi};272273 write!(out, "{}", error.error())?;274 if let ErrorKind::ImportSyntaxError { path, error } = error.error() {275 writeln!(out)?;276 let mut builder = SnippetBuilder::new(path.code());277 builder278 .error(Text::fragment("syntax error", Formatting::default()))279 .range(error.location.range())280 .build();281 let source = builder.build();282 let ansi = source_to_ansi(&source);283 write!(out, "{ansi}")?;284 }285 if let ErrorKind::StaticAnalysisError(diagnostics) = error.error() {286 use crate::analyze::DiagLevel;287 let mut builder: Option<SnippetBuilder> = None;288 let mut current_src: Option<&str> = None;289 let flush = |builder: Option<SnippetBuilder>,290 out: &mut dyn std::fmt::Write|291 -> Result<(), std::fmt::Error> {292 if let Some(b) = builder {293 let ansi = source_to_ansi(&b.build());294 write!(out, "\n{}", ansi.trim_end())?;295 }296 Ok(())297 };298 for diag in diagnostics {299 if let Some(span) = &diag.span {300 let src = span.0.code();301 if current_src != Some(src) {302 flush(builder.take(), out)?;303 builder = Some(SnippetBuilder::new(src));304 current_src = Some(src);305 }306 let b = builder.as_mut().unwrap();307 let ab = match diag.level {308 DiagLevel::Error => {309 b.error(Text::fragment(diag.message.clone(), Formatting::default()))310 }311 DiagLevel::Warning => {312 b.warning(Text::fragment(diag.message.clone(), Formatting::default()))313 }314 };315 ab.range(span.range()).build();316 } else {317 flush(builder.take(), out)?;318 current_src = None;319 let prefix = match diag.level {320 DiagLevel::Error => "error",321 DiagLevel::Warning => "warning",322 };323 write!(out, "\n{prefix}: {}", diag.message)?;324 }325 }326 flush(builder, out)?;327 }328 let trace = &error.trace();329 let snippet_builder: RefCell<Option<SnippetBuilder>> = RefCell::new(None);330 let mut last_location: Option<Span> = None;331 let mut flush_builder = |data: Option<ResetData>| {332 use std::fmt::Write;333 let mut out = String::new();334 let location_changed = if let Some(ResetData { loc }) = &data {335 if last_location.as_ref().map(|l| l.0.code()) != Some(loc.0.code()) {336 true337 } else if let (Some(last), new) = (&last_location, loc) {338 // Reverse condition if traceback339 last.1 > new.1 || last.2 > new.2340 } else {341 false342 }343 } else {344 true345 };346 if location_changed {347 if let Some(builder) = snippet_builder.borrow_mut().take() {348 let rendered = builder.build();349 let ansi = source_to_ansi(&rendered);350 if let Some(loc) = &last_location {351 let _ = writeln!(out, "...at {}", loc.0.source_path());352 }353 let _ = write!(out, "{}", ansi.trim_end());354 }355 last_location = None;356357 if let Some(ResetData { loc }) = data {358 *snippet_builder.borrow_mut() = Some(SnippetBuilder::new(loc.0.code()));359 last_location = Some(loc);360 }361 }362 if out.is_empty() {363 return None;364 }365 Some(out)366 };367 for item in &trace.0 {368 let desc = &item.desc;369 if let Some(source) = &item.location {370 if let Some(flushed) = flush_builder(Some(ResetData {371 loc: source.clone(),372 })) {373 writeln!(out)?;374 write!(out, "{flushed}")?;375 }376 let mut builder = snippet_builder.borrow_mut();377 let builder = builder.as_mut().unwrap();378 builder379 .note(Text::fragment(desc, Formatting::default()))380 .range(source.1 as usize..=(source.2 as usize - 1).max(source.1 as usize))381 .build();382 } else {383 if let Some(flushed) = flush_builder(None) {384 writeln!(out)?;385 write!(out, "{flushed}")?;386 }387 writeln!(out)?;388 write!(out, " {desc}")?;389 }390 }391392 if let Some(flushed) = flush_builder(None) {393 writeln!(out)?;394 write!(out, "{flushed}")?;395 }396 Ok(())397 }398399 fn as_any(&self) -> &dyn Any {400 self401 }402403 fn as_any_mut(&mut self) -> &mut dyn Any {404 self405 }406}