git.delta.rocks / jrsonnet / refs/commits / f2fefaf94f02

difftreelog

source

crates/jrsonnet-evaluator/src/trace/mod.rs7.1 KiBsourcehistory
1use std::path::{Path, PathBuf};23use jrsonnet_parser::{CodeLocation, Source};45use crate::{error::Error, LocError, State};67/// The way paths should be displayed8#[derive(Clone)]9pub enum PathResolver {10	/// Only filename11	FileName,12	/// Absolute path13	Absolute,14	/// Path relative to base directory15	Relative(PathBuf),16}1718impl PathResolver {19	/// Will return Self::Relative(cwd), or Self::Absolute on cwd failure20	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}4647/// Implements pretty-printing of traces48#[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}8182/// vanilla-like jsonnet formatting83pub 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					// TODO: Process all trace elements first138					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}205206/// rustc-like trace displaying207#[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}