git.delta.rocks / jrsonnet / refs/commits / 449686f01d55

difftreelog

source

crates/jrsonnet-evaluator/src/trace/mod.rs8.5 KiBsourcehistory
1#[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.0 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 offset = error.location;268			// To inclusive range269			if offset.1 > offset.0 {270				offset.1 -= 1;271			}272			let mut builder = SnippetBuilder::new(path.code());273			builder274				.error(Text::fragment("syntax error", Formatting::default()))275				.range(offset.0 as usize..=offset.1 as usize)276				.build();277			let source = builder.build();278			let ansi = source_to_ansi(&source);279			write!(out, "{ansi}")?;280		}281		let trace = &error.trace();282		let snippet_builder: RefCell<Option<SnippetBuilder>> = RefCell::new(None);283		let mut last_location: Option<Span> = None;284		let mut flush_builder = |data: Option<ResetData>| {285			use std::fmt::Write;286			let mut out = String::new();287			let location_changed = if let Some(ResetData { loc }) = &data {288				if last_location.as_ref().map(|l| l.0.code()) != Some(loc.0.code()) {289					true290				} else if let (Some(last), new) = (&last_location, loc) {291					// Reverse condition if traceback292					last.1 > new.1 || last.2 > new.2293				} else {294					false295				}296			} else {297				true298			};299			if location_changed {300				if let Some(builder) = snippet_builder.borrow_mut().take() {301					let rendered = builder.build();302					let ansi = source_to_ansi(&rendered);303					if let Some(loc) = &last_location {304						let _ = writeln!(out, "...at {}", loc.0.source_path());305					}306					let _ = write!(out, "{}", ansi.trim_end());307				}308				last_location = None;309310				if let Some(ResetData { loc }) = data {311					*snippet_builder.borrow_mut() = Some(SnippetBuilder::new(loc.0.code()));312					last_location = Some(loc);313				}314			}315			if out.is_empty() {316				return None;317			}318			Some(out)319		};320		for item in &trace.0 {321			let desc = &item.desc;322			if let Some(source) = &item.location {323				if let Some(flushed) = flush_builder(Some(ResetData {324					loc: source.clone(),325				})) {326					writeln!(out)?;327					write!(out, "{flushed}")?;328				}329				let mut builder = snippet_builder.borrow_mut();330				let builder = builder.as_mut().unwrap();331				builder332					.note(Text::fragment(desc, Formatting::default()))333					.range(source.1 as usize..=(source.2 as usize - 1).max(source.1 as usize))334					.build();335			} else {336				if let Some(flushed) = flush_builder(None) {337					writeln!(out)?;338					write!(out, "{flushed}")?;339				}340				writeln!(out)?;341				write!(out, "   {desc}")?;342			}343		}344345		if let Some(flushed) = flush_builder(None) {346			writeln!(out)?;347			write!(out, "{flushed}")?;348		}349		Ok(())350	}351352	fn as_any(&self) -> &dyn Any {353		self354	}355356	fn as_any_mut(&mut self) -> &mut dyn Any {357		self358	}359}