difftreelog
feat show full error range instead of just start
in: master
4 files changed
crates/jrsonnet-evaluator/src/error.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/error.rs
+++ b/crates/jrsonnet-evaluator/src/error.rs
@@ -15,14 +15,9 @@
};
#[derive(Debug, Clone)]
-pub struct SyntaxErrorLocation {
- pub offset: usize,
-}
-
-#[derive(Debug, Clone)]
pub struct SyntaxError {
pub message: String,
- pub location: SyntaxErrorLocation,
+ pub location: (u32, u32),
}
impl fmt::Display for SyntaxError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
crates/jrsonnet-evaluator/src/lib.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/lib.rs
+++ b/crates/jrsonnet-evaluator/src/lib.rs
@@ -50,7 +50,7 @@
#[cfg(not(any(feature = "ir-parser", feature = "peg-parser")))]
compile_error!("at least one of `ir-parser` or `peg-parser` features must be enabled");
-pub use error::{SyntaxError, SyntaxErrorLocation};
+pub use error::SyntaxError;
pub use obj::*;
pub use rustc_hash;
use rustc_hash::FxHashMap;
@@ -87,9 +87,7 @@
jrsonnet_ir_parser::parse(code, &jrsonnet_ir_parser::ParserSettings { source }).map_err(|e| {
SyntaxError {
message: e.message,
- location: SyntaxErrorLocation {
- offset: e.location.offset,
- },
+ location: (e.location.0, e.location.1),
}
})
}
@@ -107,7 +105,7 @@
"expected {}, got {:?}",
e.expected,
code.chars()
- .nth(e.location.offset)
+ .nth(e.location.0)
.map_or_else(|| "EOF".into(), |c: char| c.to_string())
)
},
@@ -115,9 +113,7 @@
);
SyntaxError {
message,
- location: SyntaxErrorLocation {
- offset: e.location.offset,
- },
+ location: e.location,
}
})
}
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.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 let mut location = path133 .map_source_locations(&[offset as u32])134 .into_iter()135 .next()136 .unwrap();137 if is_eof {138 location.column += 1;139 }140141 write!(n, ":").unwrap();142 print_code_location(&mut n, &location, &location).unwrap();143 write!(out, "{:<p$}{n}", "", p = self.padding)?;144 }145 let file_names = error146 .trace()147 .0148 .iter()149 .map(|el| &el.location)150 .map(|location| {151 use std::fmt::Write;152 #[allow(clippy::option_if_let_else)]153 if let Some(location) = location {154 let mut resolved_path = match location.0.source_path().path() {155 Some(r) => self.resolver.resolve(r),156 None => location.0.source_path().to_string(),157 };158 // TODO: Process all trace elements first159 let location = location.0.map_source_locations(&[location.1, location.2]);160 write!(resolved_path, ":").unwrap();161 print_code_location(&mut resolved_path, &location[0], &location[1]).unwrap();162 write!(resolved_path, ":").unwrap();163 Some(resolved_path)164 } else {165 None166 }167 })168 .collect::<Vec<_>>();169 let align = file_names170 .iter()171 .flatten()172 .map(String::len)173 .max()174 .unwrap_or(0);175 for (el, file) in error.trace().0.iter().zip(file_names) {176 writeln!(out)?;177 if let Some(file) = file {178 write!(179 out,180 "{:<p$}{:<w$} {}",181 "",182 file,183 el.desc,184 p = self.padding,185 w = align186 )?;187 } else {188 write!(out, "{:<p$}{}", "", el.desc, p = self.padding,)?;189 }190 }191 Ok(())192 }193194 fn as_any(&self) -> &dyn Any {195 self196 }197198 fn as_any_mut(&mut self) -> &mut dyn Any {199 self200 }201}202203#[derive(Trace)]204pub struct JsFormat {205 pub max_trace: usize,206}207impl TraceFormat for JsFormat {208 fn write_trace(209 &self,210 out: &mut dyn std::fmt::Write,211 error: &Error,212 ) -> Result<(), std::fmt::Error> {213 write!(out, "{}", error.error())?;214 for item in &error.trace().0 {215 writeln!(out)?;216 let desc = &item.desc;217 if let Some(source) = &item.location {218 let start_end = source.0.map_source_locations(&[source.1, source.2]);219 let resolved_path = source.0.source_path().path().map_or_else(220 || source.0.source_path().to_string(),221 |r| r.display().to_string(),222 );223224 write!(225 out,226 " at {} ({}:{}:{})",227 desc, resolved_path, start_end[0].line, start_end[0].column,228 )?;229 } else {230 write!(out, " during {desc}")?;231 }232 }233 Ok(())234 }235236 fn as_any(&self) -> &dyn Any {237 self238 }239240 fn as_any_mut(&mut self) -> &mut dyn Any {241 self242 }243}244245#[cfg(feature = "explaining-traces")]246#[derive(Trace)]247pub struct HiDocFormat {248 pub resolver: PathResolver,249 pub max_trace: usize,250}251#[cfg(feature = "explaining-traces")]252impl TraceFormat for HiDocFormat {253 fn write_trace(254 &self,255 out: &mut dyn std::fmt::Write,256 error: &Error,257 ) -> Result<(), std::fmt::Error> {258 struct ResetData {259 loc: Span,260 }261 use hi_doc::{Formatting, SnippetBuilder, Text, source_to_ansi};262263 write!(out, "{}", error.error())?;264 if let ErrorKind::ImportSyntaxError { path, error } = error.error() {265 writeln!(out)?;266 let mut offset = error.location;267 // To inclusive range268 if offset.1 > offset.0 {269 offset.1 -= 1;270 }271 let mut builder = SnippetBuilder::new(path.code());272 builder273 .error(Text::fragment("syntax error", Formatting::default()))274 .range(offset.0 as usize..=offset.1 as usize)275 .build();276 let source = builder.build();277 let ansi = source_to_ansi(&source);278 write!(out, "{ansi}")?;279 }280 let trace = &error.trace();281 let snippet_builder: RefCell<Option<SnippetBuilder>> = RefCell::new(None);282 let mut last_location: Option<Span> = None;283 let mut flush_builder = |data: Option<ResetData>| {284 use std::fmt::Write;285 let mut out = String::new();286 let location_changed = if let Some(ResetData { loc }) = &data {287 if last_location.as_ref().map(|l| l.0.code()) != Some(loc.0.code()) {288 true289 } else if let (Some(last), new) = (&last_location, loc) {290 // Reverse condition if traceback291 last.1 > new.1 || last.2 > new.2292 } else {293 false294 }295 } else {296 true297 };298 if location_changed {299 if let Some(builder) = snippet_builder.borrow_mut().take() {300 let rendered = builder.build();301 let ansi = source_to_ansi(&rendered);302 if let Some(loc) = &last_location {303 let _ = writeln!(out, "...at {}", loc.0.source_path());304 }305 let _ = write!(out, "{}", ansi.trim_end());306 }307 last_location = None;308309 if let Some(ResetData { loc }) = data {310 *snippet_builder.borrow_mut() = Some(SnippetBuilder::new(loc.0.code()));311 last_location = Some(loc);312 }313 }314 if out.is_empty() {315 return None;316 }317 Some(out)318 };319 for item in &trace.0 {320 let desc = &item.desc;321 if let Some(source) = &item.location {322 if let Some(flushed) = flush_builder(Some(ResetData {323 loc: source.clone(),324 })) {325 writeln!(out)?;326 write!(out, "{flushed}")?;327 }328 let mut builder = snippet_builder.borrow_mut();329 let builder = builder.as_mut().unwrap();330 builder331 .note(Text::fragment(desc, Formatting::default()))332 .range(source.1 as usize..=(source.2 as usize - 1).max(source.1 as usize))333 .build();334 } else {335 if let Some(flushed) = flush_builder(None) {336 writeln!(out)?;337 write!(out, "{flushed}")?;338 }339 writeln!(out)?;340 write!(out, " {desc}")?;341 }342 }343344 if let Some(flushed) = flush_builder(None) {345 writeln!(out)?;346 write!(out, "{flushed}")?;347 }348 Ok(())349 }350351 fn as_any(&self) -> &dyn Any {352 self353 }354355 fn as_any_mut(&mut self) -> &mut dyn Any {356 self357 }358}crates/jrsonnet-ir-parser/src/lib.rsdiffbeforeafterboth--- a/crates/jrsonnet-ir-parser/src/lib.rs
+++ b/crates/jrsonnet-ir-parser/src/lib.rs
@@ -7,21 +7,16 @@
ImportKind, IndexPart, LiteralType, Member, ObjBody, ObjComp, ObjMembers, Slice, SliceDesc,
Source, Span, Spanned, UnaryOpType, Visibility, unescape,
};
-use jrsonnet_lexer::{Lexeme, Lexer, SyntaxKind, T, collect_lexed_str_block};
+use jrsonnet_lexer::{Lexeme, Lexer, Span as LexSpan, SyntaxKind, T, collect_lexed_str_block};
pub struct ParserSettings {
pub source: Source,
-}
-
-#[derive(Debug, Clone)]
-pub struct ParseErrorLocation {
- pub offset: usize,
}
#[derive(Debug, Clone)]
pub struct ParseError {
pub message: String,
- pub location: ParseErrorLocation,
+ pub location: LexSpan,
}
impl std::fmt::Display for ParseError {
@@ -131,9 +126,7 @@
fn error(&self, message: String) -> ParseError {
ParseError {
- location: ParseErrorLocation {
- offset: self.span_start() as usize,
- },
+ location: self.lexemes[self.offset].range,
message,
}
}
@@ -143,9 +136,6 @@
return Err(self.error(format!("expected identifier, got {}", self.current_desc())));
}
let text = self.text();
- if is_reserved(text) {
- return Err(self.error(format!("expected identifier, got reserved word '{text}'")));
- }
let s: IStr = text.into();
self.eat_any();
Ok(s)
@@ -156,23 +146,6 @@
}
}
-fn is_reserved(s: &str) -> bool {
- matches!(
- s,
- "assert"
- | "else" | "error"
- | "false" | "for"
- | "function"
- | "if" | "import"
- | "importstr"
- | "importbin"
- | "in" | "local"
- | "null" | "tailstrict"
- | "then" | "self"
- | "super" | "true"
- )
-}
-
fn spanned<T: Acyclic>(
p: &mut Parser<'_>,
cb: impl FnOnce(&mut Parser<'_>) -> Result<T>,
@@ -1040,9 +1013,7 @@
if let Some(desc) = lexeme.kind.error_description() {
return Err(ParseError {
message: desc.to_owned(),
- location: ParseErrorLocation {
- offset: lexeme.range.0 as usize,
- },
+ location: lexeme.range,
});
}
}