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

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		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}4344/// Implements pretty-printing of traces45#[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}7879/// vanilla-like jsonnet formatting80pub 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					// TODO: Process all trace elements first135					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}202203/// rustc-like trace displaying204#[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}